fix and refactor code

This commit is contained in:
2026-04-27 15:45:06 +03:00
parent 316d06b1d2
commit 05076eb367
17 changed files with 89 additions and 91 deletions

View File

@@ -1,8 +0,0 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

View File

@@ -22,7 +22,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { watch } from 'vue' import { watch } from 'vue'
import { useSettingsStore } from './stores/settings' import { useSettingsStore } from '@/stores/settings'
const settings = useSettingsStore() const settings = useSettingsStore()

View File

@@ -34,7 +34,7 @@
to="/dashboard" to="/dashboard"
class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group" class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group"
:class="[ :class="[
$route.path === '/dashboard' ? 'bg-primary-50 text-primary-700' : 'text-gray-700', route.path === '/dashboard' ? 'bg-primary-50 text-primary-700' : 'text-gray-700',
sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3' sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3'
]" ]"
:title="sidebarCollapsed ? t('app.dashboard') : ''" :title="sidebarCollapsed ? t('app.dashboard') : ''"
@@ -50,7 +50,7 @@
to="/users" to="/users"
class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group" class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group"
:class="[ :class="[
$route.path === '/users' ? 'bg-primary-50 text-primary-700' : 'text-gray-700', route.path === '/users' ? 'bg-primary-50 text-primary-700' : 'text-gray-700',
sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3' sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3'
]" ]"
:title="sidebarCollapsed ? t('app.users') : ''" :title="sidebarCollapsed ? t('app.users') : ''"
@@ -65,7 +65,7 @@
to="/restaurants" to="/restaurants"
class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group" class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group"
:class="[ :class="[
$route.path === '/restaurants' ? 'bg-primary-50 text-primary-700' : 'text-gray-700', route.path === '/restaurants' ? 'bg-primary-50 text-primary-700' : 'text-gray-700',
sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3' sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3'
]" ]"
:title="sidebarCollapsed ? t('app.restaurants') : ''" :title="sidebarCollapsed ? t('app.restaurants') : ''"
@@ -81,7 +81,7 @@
to="/settings" to="/settings"
class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group" class="flex items-center rounded-lg hover:bg-gray-100 transition-colors group"
:class="[ :class="[
$route.path === '/settings' ? 'bg-primary-50 text-primary-700' : 'text-gray-700', route.path === '/settings' ? 'bg-primary-50 text-primary-700' : 'text-gray-700',
sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3' sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3'
]" ]"
:title="sidebarCollapsed ? t('app.settings') : ''" :title="sidebarCollapsed ? t('app.settings') : ''"
@@ -101,7 +101,7 @@
class="flex items-center rounded-lg hover:bg-gray-100 transition-colors no-router-link" class="flex items-center rounded-lg hover:bg-gray-100 transition-colors no-router-link"
:class="[ :class="[
sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3', sidebarCollapsed ? 'justify-center p-2' : 'px-4 py-3 space-x-3',
$route.path === '/phpmyadmin' ? 'bg-primary-50 text-primary-700' : 'text-gray-700' route.path === '/phpmyadmin' ? 'bg-primary-50 text-primary-700' : 'text-gray-700'
]" ]"
:title="sidebarCollapsed ? t('app.database') : ''" :title="sidebarCollapsed ? t('app.database') : ''"
> >
@@ -209,12 +209,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, onMounted } from 'vue' import { computed, ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useSettingsStore } from '../../stores/settings' import { useSettingsStore } from '@/stores/settings'
import { useUserStore } from '../../stores/user' import { useUserStore } from '@/stores/user'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useNotification } from '../../composables/useNotification' import { useNotification } from '@/composables/useNotification'
const { notification } = useNotification() const { notification, showNotification } = useNotification()
const settings = useSettingsStore() const settings = useSettingsStore()
const userStore = useUserStore() const userStore = useUserStore()
const route = useRoute() const route = useRoute()

View File

@@ -50,7 +50,7 @@
}, },
"dashboard": { "dashboard": {
"totalUsers": "Total Users", "totalUsers": "Total Users",
"activeSessions": "Active Sessions", "totalRestaurants": "Total Restaurants",
"systemHealth": "System Health", "systemHealth": "System Health",
"uptime": "Uptime", "uptime": "Uptime",
"vsLastMonth": "vs last month", "vsLastMonth": "vs last month",

View File

@@ -50,7 +50,7 @@
}, },
"dashboard": { "dashboard": {
"totalUsers": "Всего пользователей", "totalUsers": "Всего пользователей",
"activeSessions": "Активных сессий", "totalRestaurants": "Всего ресторанов",
"systemHealth": "Здоровье системы", "systemHealth": "Здоровье системы",
"uptime": "Время работы", "uptime": "Время работы",
"vsLastMonth": "по сравнению с прошлым месяцем", "vsLastMonth": "по сравнению с прошлым месяцем",

View File

@@ -3,12 +3,12 @@ import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import './style.css' import '@/style.css'
import { useSettingsStore } from './stores/settings' import { useSettingsStore } from './stores/settings'
import { useUserStore } from './stores/user' import { useUserStore } from './stores/user'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
import en from './locales/en.json' import en from '@/locales/en.json'
import ru from './locales/ru.json' import ru from '@/locales/ru.json'
// Функция определения языка браузера // Функция определения языка браузера
function getBrowserLocale(): string { function getBrowserLocale(): string {

View File

@@ -1,15 +1,15 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '../stores/user' import { useUserStore } from '@/stores/user'
import { useSettingsStore } from '../stores/settings' import { useSettingsStore } from '@/stores/settings'
import Login from '../views/auth/Login.vue' import Login from '@/views/auth/Login.vue'
import Setup from '../views/auth/Setup.vue' import Setup from '@/views/auth/Setup.vue'
import Register from '../views/auth/Register.vue' import Register from '@/views/auth/Register.vue'
import Dashboard from '../views/Dashboard.vue' import Dashboard from '@/views/Dashboard.vue'
import Users from '../views/Users.vue' import Users from '@/views/Users.vue'
import Restaurants from '../views/Restaurants.vue' import Restaurants from '@/views/Restaurants.vue'
import AdminSettings from '../views/AdminSettings.vue' import AdminSettings from '@/views/AdminSettings.vue'
import Profile from '../views/Profile.vue' import Profile from '@/views/Profile.vue'
import NotFound from '../views/NotFound.vue' import NotFound from '@/views/NotFound.vue'
const routes = [ const routes = [
{ path: '/login', component: Login, meta: { title: 'Login', requiresAuth: false } }, { path: '/login', component: Login, meta: { title: 'Login', requiresAuth: false } },

View File

@@ -10,7 +10,7 @@ export const useUserStore = defineStore('user', () => {
async function fetchProfile() { async function fetchProfile() {
try { try {
const res = await fetch('/api/admin/profile') const res = await fetch('/api/profile')
if (res.ok) { if (res.ok) {
const data = await res.json() const data = await res.json()
id.value = data.id id.value = data.id
@@ -27,7 +27,7 @@ export const useUserStore = defineStore('user', () => {
} }
async function updateProfile(updates: { email?: string; password?: string; language?: string }) { async function updateProfile(updates: { email?: string; password?: string; language?: string }) {
const res = await fetch('/api/admin/profile', { const res = await fetch('/api/profile', {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates) body: JSON.stringify(updates)

View File

@@ -58,22 +58,18 @@
<button type="submit" class="btn-primary">{{ t('settings.save') }}</button> <button type="submit" class="btn-primary">{{ t('settings.save') }}</button>
</div> </div>
</form> </form>
<div v-if="message" class="mt-4 p-3 rounded-lg" :class="messageClass">
{{ message }}
</div>
</div> </div>
</AppLayout> </AppLayout>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import AppLayout from '../components/Layout/AppLayout.vue'; import AppLayout from '@/components/Layout/AppLayout.vue';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useNotification } from '../composables/useNotification' import { useNotification } from '@/composables/useNotification'
const { showNotification } = useNotification() const { showNotification } = useNotification()
const { t, locale } = useI18n() const { t } = useI18n()
interface FieldMeta { interface FieldMeta {
key: string; key: string;
label: string; label: string;
@@ -86,11 +82,9 @@ interface FieldMeta {
const meta = ref<FieldMeta[]>([]); const meta = ref<FieldMeta[]>([]);
const values = ref<Record<string, string>>({}); const values = ref<Record<string, string>>({});
const message = ref('');
const messageClass = ref('');
async function loadMeta() { async function loadMeta() {
const res = await fetch('/api/settings/meta'); const res = await fetch('/api/admin/settings/meta');
if (res.ok) { if (res.ok) {
meta.value = await res.json(); meta.value = await res.json();
} else { } else {
@@ -99,7 +93,7 @@ async function loadMeta() {
} }
async function loadValues() { async function loadValues() {
const res = await fetch('/api/settings/all'); const res = await fetch('/api/admin/settings');
if (res.ok) { if (res.ok) {
values.value = await res.json(); values.value = await res.json();
} else { } else {
@@ -128,13 +122,5 @@ async function saveSettings() {
} }
} }
function showMessage(text: string, cssClass: string) {
message.value = text;
messageClass.value = cssClass;
setTimeout(() => {
message.value = '';
}, 3000);
}
onMounted(loadData); onMounted(loadData);
</script> </script>

View File

@@ -23,8 +23,8 @@
<div class="card hover:shadow-md transition-shadow"> <div class="card hover:shadow-md transition-shadow">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div> <div>
<p class="text-sm font-medium text-gray-600">{{ t('dashboard.activeSessions') }}</p> <p class="text-sm font-medium text-gray-600">{{ t('dashboard.totalRestaurants') }}</p>
<p class="text-3xl font-bold text-gray-900 mt-2">{{ stats.activeSessions }}</p> <p class="text-3xl font-bold text-gray-900 mt-2">{{ stats.totalRestaurants }}</p>
</div> </div>
<div class="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center"> <div class="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center">
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -175,13 +175,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted } from 'vue';
import AppLayout from '../components/Layout/AppLayout.vue'; import AppLayout from '@/components/Layout/AppLayout.vue';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
import { useNotification } from '../composables/useNotification' import { useNotification } from '@/composables/useNotification'
const { showNotification } = useNotification() const { showNotification } = useNotification()
const stats = ref({ totalUsers: 0, activeSessions: 0, systemHealth: 100, uptime: '99.9%' }); const stats = ref({ totalUsers: 0, totalRestaurants: 0, systemHealth: 100, uptime: '99.9%' });
const userGrowth = ref(12); const userGrowth = ref(12);
const sessionGrowth = ref(5); const sessionGrowth = ref(5);
const recentUsers = ref([]); const recentUsers = ref([]);
@@ -195,20 +195,18 @@ let interval: number;
async function loadDashboardData() { async function loadDashboardData() {
try { try {
const [usersRes, sessionsRes, healthRes, restaurantsRes] = await Promise.all([ const [usersRes, healthRes, restaurantsRes] = await Promise.all([
fetch('/api/admin/users'), fetch('/api/admin/users'),
fetch('/api/admin/active-sessions'),
fetch('/api/health'), fetch('/api/health'),
fetch('/api/admin/restaurants') fetch('/api/admin/restaurants')
]); ]);
const users = await usersRes.json(); const users = await usersRes.json();
const sessions = await sessionsRes.json();
const health = await healthRes.json(); const health = await healthRes.json();
const restaurants = await restaurantsRes.json(); const restaurants = await restaurantsRes.json();
stats.value.totalUsers = users.length; stats.value.totalUsers = users.length;
stats.value.activeSessions = sessions.count || 0; stats.value.totalRestaurants = restaurants.length;
recentUsers.value = users.slice(-5).reverse(); recentUsers.value = users.slice(-5).reverse();
recentRestaurants.value = restaurants.slice(-5).reverse(); recentRestaurants.value = restaurants.slice(-5).reverse();

View File

@@ -84,10 +84,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'; import { ref, reactive, computed, onMounted } from 'vue';
import { useUserStore } from '../stores/user'; import { useUserStore } from '@/stores/user';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import AppLayout from '../components/Layout/AppLayout.vue'; import AppLayout from '@/components/Layout/AppLayout.vue';
import {useNotification} from "@/composables/useNotification";
const { showNotification } = useNotification();
const userStore = useUserStore(); const userStore = useUserStore();
const { t, locale } = useI18n(); const { t, locale } = useI18n();
@@ -129,7 +131,7 @@ async function saveProfile() {
if (ok) { if (ok) {
locale.value = form.language; locale.value = form.language;
showNotification('profile.updateSuccess', 'success'); showNotification('profile.updateSuccess', 'success');
resetForm(); // очищаем поля пароля resetForm();
} else { } else {
showNotification('profile.updateError', 'error'); showNotification('profile.updateError', 'error');
} }

View File

@@ -153,9 +153,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import AppLayout from '../components/Layout/AppLayout.vue'; import AppLayout from '@/components/Layout/AppLayout.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useNotification } from '../composables/useNotification'; import { useNotification } from '@/composables/useNotification';
const { t } = useI18n(); const { t } = useI18n();
const { showNotification } = useNotification(); const { showNotification } = useNotification();

View File

@@ -155,10 +155,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import AppLayout from '../components/Layout/AppLayout.vue'; import AppLayout from '@/components/Layout/AppLayout.vue';
import { useUserStore } from '../stores/user'; import { useUserStore } from '@/stores/user';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useNotification } from '../composables/useNotification'; import { useNotification } from '@/composables/useNotification';
const { t } = useI18n(); const { t } = useI18n();
const { showNotification } = useNotification(); const { showNotification } = useNotification();

View File

@@ -103,8 +103,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useSettingsStore } from '../../stores/settings' import { useSettingsStore } from '@/stores/settings'
import { useUserStore } from '../../stores/user' import { useUserStore } from '@/stores/user'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const settings = useSettingsStore() const settings = useSettingsStore()

20
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"jsx": "preserve",
"jsxImportSource": "vue",
"types": ["vite/client"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules"]
}

View File

@@ -3,6 +3,11 @@ import vue from '@vitejs/plugin-vue'
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
resolve: {
alias: {
'@': '/src',
},
},
server: { server: {
proxy: { proxy: {
'/api': 'http://localhost:8080' // для разработки '/api': 'http://localhost:8080' // для разработки

View File

@@ -18,6 +18,7 @@ import io.vertx.ext.web.sstore.redis.RedisSessionStore;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import su.xserver.iikocon.config.AppConfig; import su.xserver.iikocon.config.AppConfig;
import su.xserver.iikocon.handler.AdminHandler;
import su.xserver.iikocon.handler.AuthHandler; import su.xserver.iikocon.handler.AuthHandler;
import su.xserver.iikocon.handler.SecurityHandler; import su.xserver.iikocon.handler.SecurityHandler;
import su.xserver.iikocon.handler.SetupHandler; import su.xserver.iikocon.handler.SetupHandler;
@@ -35,6 +36,7 @@ public class MainVerticle extends AbstractVerticle {
private RedisService redis; private RedisService redis;
private HttpServer httpServer; private HttpServer httpServer;
private AppConfig config; private AppConfig config;
private SessionStore sessionStore;
private UserService userService; private UserService userService;
private RestaurantService restaurantService; private RestaurantService restaurantService;
@@ -98,7 +100,7 @@ public class MainVerticle extends AbstractVerticle {
} }
long timeoutMs = timeoutMinutes * 60 * 1000; long timeoutMs = timeoutMinutes * 60 * 1000;
SessionStore sessionStore = RedisSessionStore.create(vertx, redis.getRedis()); sessionStore = RedisSessionStore.create(vertx, redis.getRedis());
SessionHandler sessionHandler = SessionHandler.create(sessionStore) SessionHandler sessionHandler = SessionHandler.create(sessionStore)
.setSessionCookieName("admin.session") .setSessionCookieName("admin.session")
.setCookieHttpOnlyFlag(true) .setCookieHttpOnlyFlag(true)
@@ -196,14 +198,14 @@ public class MainVerticle extends AbstractVerticle {
})); }));
// В initRouter после настройки authHandler, до объявления /api/admin/*: // В initRouter после настройки authHandler, до объявления /api/admin/*:
router.route("/api/admin/profile").handler(authHandler::requireAuth); router.route("/api/profile").handler(authHandler::requireAuth);
router.get("/api/admin/profile").handler(rc -> { router.get("/api/profile").handler(rc -> {
Integer userId = rc.session().get("userId"); Integer userId = rc.session().get("userId");
userService.getProfile(userId) userService.getProfile(userId)
.onSuccess(profile -> rc.response().putHeader("Content-Type", "application/json").end(profile.encode())) .onSuccess(profile -> rc.response().putHeader("Content-Type", "application/json").end(profile.encode()))
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage())); .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
}); });
router.put("/api/admin/profile").handler(rc -> { router.put("/api/profile").handler(rc -> {
Integer userId = rc.session().get("userId"); Integer userId = rc.session().get("userId");
JsonObject body = rc.body().asJsonObject(); JsonObject body = rc.body().asJsonObject();
String email = body.getString("email"); String email = body.getString("email");
@@ -235,8 +237,7 @@ public class MainVerticle extends AbstractVerticle {
// Затем существующий блок router.route("/api/admin/*").handler(authHandler::requireAuth); // Затем существующий блок router.route("/api/admin/*").handler(authHandler::requireAuth);
router.route("/api/admin/*").handler(authHandler::requireAuth); router.route("/api/admin/*").handler(authHandler::requireAuth);
// Добавить проверку роли для чувствительных эндпоинтов: // Добавить проверку роли для чувствительных эндпоинтов:
// router.route("/api/admin/users*").handler(AdminHandler::requireAdmin); // router.route("/api/settings/meta*").handler(AdminHandler::requireAdmin);
// router.route("/api/admin/restaurants*").handler(AdminHandler::requireAdmin);
// router.route("/api/admin/settings*").handler(AdminHandler::requireAdmin); // router.route("/api/admin/settings*").handler(AdminHandler::requireAdmin);
// router.route("/api/admin/active-sessions").handler(AdminHandler::requireAdmin); // router.route("/api/admin/active-sessions").handler(AdminHandler::requireAdmin);
@@ -416,20 +417,21 @@ public class MainVerticle extends AbstractVerticle {
}); });
// Получить метаданные всех настроек (для построения формы) // Получить метаданные всех настроек (для построения формы)
router.get("/api/settings/meta").handler(rc -> { router.route("/api/admin/settings*").handler(AdminHandler::requireAdmin);
router.get("/api/admin/settings/meta").handler(rc -> {
settingsService.getMetadata() settingsService.getMetadata()
.onSuccess(meta -> rc.response().putHeader("Content-Type", "application/json").end(meta.encode())) .onSuccess(meta -> rc.response().putHeader("Content-Type", "application/json").end(meta.encode()))
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage())); .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
}); });
// Получить все настройки со значениями по умолчанию // Получить все настройки со значениями по умолчанию
router.get("/api/settings/all").handler(rc -> { router.get("/api/admin/settings").handler(rc -> {
settingsService.getAllWithDefaults() settingsService.getAllWithDefaults()
.onSuccess(settings -> rc.response().putHeader("Content-Type", "application/json").end(settings.encode())) .onSuccess(settings -> rc.response().putHeader("Content-Type", "application/json").end(settings.encode()))
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage())); .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
}); });
// Обновление настроек (админ) // Обновление настроек (админ)
router.put("/api/admin/settings").handler(rc -> { router.put("/api/admin/settings").handler(rc -> {
JsonObject body = rc.body().asJsonObject(); JsonObject body = rc.body().asJsonObject();
List<Future<Void>> futures = new ArrayList<>(); // явно указываем тип Future<Void> List<Future<Void>> futures = new ArrayList<>(); // явно указываем тип Future<Void>
@@ -439,17 +441,10 @@ public class MainVerticle extends AbstractVerticle {
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage())); .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
}); });
// Количество активных сессий (на основе Redis)
router.get("/api/admin/active-sessions").handler(rc -> {
// TODO: реализовать подсчёт активных сессий через Redis или другой механизм
rc.response().end(new JsonObject().put("count", 0).encode());
});
return router; return router;
} }
private void startHttp(Router router, Promise<Void> startPromise) { private void startHttp(Router router, Promise<Void> startPromise) {
// Запуск HTTP-сервера
httpServer = vertx.createHttpServer(); httpServer = vertx.createHttpServer();
httpServer.requestHandler(router).listen(config.server.port, config.server.host) httpServer.requestHandler(router).listen(config.server.port, config.server.host)
.onSuccess(server -> { .onSuccess(server -> {