From ca1783fe7d26e5e0d96e3d45549039cd4391b06f Mon Sep 17 00:00:00 2001 From: Falknat Date: Sun, 8 Feb 2026 07:22:46 +0700 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=84=D1=80=D0=BE=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front_vue/src/Core/composables/useConfirm.js | 70 ++++++++ front_vue/src/Core/constants.js | 2 + front_vue/src/Core/i18n/en.json | 6 +- front_vue/src/Core/i18n/ru.json | 5 +- front_vue/src/Core/stores/certs.js | 3 +- front_vue/src/Core/stores/config.js | 3 +- front_vue/src/Core/stores/proxies.js | 3 +- front_vue/src/Core/stores/services.js | 3 +- front_vue/src/Core/stores/sites.js | 3 +- front_vue/src/Design/assets/css/tables.css | 162 ++++++++++-------- .../src/Design/components/layout/TitleBar.vue | 23 ++- .../components/proxies/ProxiesTable.vue | 78 +++++++-- .../components/services/ServiceCard.vue | 137 ++++++--------- .../components/services/ServicesGrid.vue | 23 +-- .../Design/components/sites/SitesTable.vue | 99 ++++++++--- .../Design/components/ui/VConfirmDialog.vue | 142 +++++++++++++++ .../Design/components/ui/VSectionHeader.vue | 72 ++++++++ front_vue/src/Design/layouts/MainLayout.vue | 14 +- front_vue/src/Design/views/DashboardView.vue | 24 ++- front_vue/src/Design/views/ProxyEditView.vue | 30 ++-- front_vue/src/Design/views/SettingsView.vue | 15 +- front_vue/src/Design/views/SiteEditView.vue | 31 ++-- front_vue/src/Design/views/VAccessView.vue | 4 +- front_vue/vite.config.js | 14 ++ 24 files changed, 686 insertions(+), 280 deletions(-) create mode 100644 front_vue/src/Core/composables/useConfirm.js create mode 100644 front_vue/src/Design/components/ui/VConfirmDialog.vue create mode 100644 front_vue/src/Design/components/ui/VSectionHeader.vue diff --git a/front_vue/src/Core/composables/useConfirm.js b/front_vue/src/Core/composables/useConfirm.js new file mode 100644 index 0000000..4fd9596 --- /dev/null +++ b/front_vue/src/Core/composables/useConfirm.js @@ -0,0 +1,70 @@ +const isOpen = ref(false) +const config = ref({ + icon: 'fas fa-question-circle', + iconColor: 'var(--accent-red)', + title: '', + message: '', + buttons: [], +}) +let resolvePromise = null + +export function useConfirm() { + const open = (options = {}) => { + config.value = { + icon: options.icon || 'fas fa-exclamation-triangle', + iconColor: options.iconColor || 'var(--accent-red)', + title: options.title || '', + message: options.message || '', + buttons: options.buttons || [], + } + isOpen.value = true + } + + const close = () => { + isOpen.value = false + if (resolvePromise) { + resolvePromise(false) + resolvePromise = null + } + } + + const confirm = (options = {}) => { + return new Promise((resolve) => { + resolvePromise = resolve + open({ + ...options, + buttons: (options.buttons || []).map(btn => ({ + ...btn, + action: () => { + resolvePromise = null + isOpen.value = false + resolve(btn.value !== undefined ? btn.value : btn.label) + if (btn.action) btn.action() + }, + })), + }) + }) + } + + const confirmDelete = (options = {}) => { + return confirm({ + icon: options.icon || 'fas fa-trash-alt', + iconColor: options.iconColor || 'var(--accent-red)', + title: options.title || '', + message: options.message || '', + buttons: [ + { label: options.cancelText || 'Отмена', variant: 'default', value: false }, + { label: options.confirmText || 'Удалить', variant: 'danger', icon: 'fas fa-trash', value: true }, + ], + }) + } + + return { + isOpen: readonly(isOpen), + config: readonly(config), + open, + close, + confirm, + confirmDelete, + } +} diff --git a/front_vue/src/Core/constants.js b/front_vue/src/Core/constants.js index e289591..d5a1a15 100644 --- a/front_vue/src/Core/constants.js +++ b/front_vue/src/Core/constants.js @@ -43,3 +43,5 @@ export const STORAGE_KEYS = { } export const AUTO_REFRESH_INTERVAL = 5000 + +export const APP_VERSION = __APP_VERSION__ diff --git a/front_vue/src/Core/i18n/en.json b/front_vue/src/Core/i18n/en.json index f8aca1d..ff994ad 100644 --- a/front_vue/src/Core/i18n/en.json +++ b/front_vue/src/Core/i18n/en.json @@ -1,7 +1,7 @@ { "app": { "title": "vServer Admin Panel", - "logo": "vServer", + "logo": "vServer v1.0", "footerAuthor": "Author: Sumaneev Roman", "loading": "Starting vServer..." }, @@ -21,7 +21,9 @@ "stop": "Stop", "starting": "Starting...", "stopping": "Stopping...", - "wait": "Please wait..." + "wait": "Please wait...", + "stopConfirmTitle": "Stop server?", + "stopConfirmMessage": "All services will be stopped" }, "services": { "title": "Services Status", diff --git a/front_vue/src/Core/i18n/ru.json b/front_vue/src/Core/i18n/ru.json index 4c48389..3a9af22 100644 --- a/front_vue/src/Core/i18n/ru.json +++ b/front_vue/src/Core/i18n/ru.json @@ -21,7 +21,9 @@ "stop": "Остановить", "starting": "Запускается...", "stopping": "Выключается...", - "wait": "Ожидайте..." + "wait": "Ожидайте...", + "stopConfirmTitle": "Остановить сервер?", + "stopConfirmMessage": "Все сервисы будут остановлены" }, "services": { "title": "Статус сервисов", @@ -176,7 +178,6 @@ "errorPrefix": "Ошибка" }, "notify": { - "settingsSaved": "Настройки сохранены и сервисы перезапущены!", "settingsSaved": "Настройки сохранены", "dataSaved": "Данные сохранены", "siteCreated": "Сайт успешно создан!", diff --git a/front_vue/src/Core/stores/certs.js b/front_vue/src/Core/stores/certs.js index 592517d..f3b916e 100644 --- a/front_vue/src/Core/stores/certs.js +++ b/front_vue/src/Core/stores/certs.js @@ -1,5 +1,4 @@ -import { defineStore } from 'pinia' -import { api } from '@core/api/index.js' +import { defineStore } from 'pinia' export const useCertsStore = defineStore('certs', { state: () => ({ diff --git a/front_vue/src/Core/stores/config.js b/front_vue/src/Core/stores/config.js index 007af75..83b0e47 100644 --- a/front_vue/src/Core/stores/config.js +++ b/front_vue/src/Core/stores/config.js @@ -1,5 +1,4 @@ -import { defineStore } from 'pinia' -import { api } from '@core/api/index.js' +import { defineStore } from 'pinia' export const useConfigStore = defineStore('config', { state: () => ({ diff --git a/front_vue/src/Core/stores/proxies.js b/front_vue/src/Core/stores/proxies.js index b06b041..78504df 100644 --- a/front_vue/src/Core/stores/proxies.js +++ b/front_vue/src/Core/stores/proxies.js @@ -1,5 +1,4 @@ -import { defineStore } from 'pinia' -import { api } from '@core/api/index.js' +import { defineStore } from 'pinia' export const useProxiesStore = defineStore('proxies', { state: () => ({ diff --git a/front_vue/src/Core/stores/services.js b/front_vue/src/Core/stores/services.js index 8fdfa53..34c125c 100644 --- a/front_vue/src/Core/stores/services.js +++ b/front_vue/src/Core/stores/services.js @@ -1,5 +1,4 @@ -import { defineStore } from 'pinia' -import { api } from '@core/api/index.js' +import { defineStore } from 'pinia' export const useServicesStore = defineStore('services', { state: () => ({ diff --git a/front_vue/src/Core/stores/sites.js b/front_vue/src/Core/stores/sites.js index 7137ee8..5649465 100644 --- a/front_vue/src/Core/stores/sites.js +++ b/front_vue/src/Core/stores/sites.js @@ -1,5 +1,4 @@ -import { defineStore } from 'pinia' -import { api } from '@core/api/index.js' +import { defineStore } from 'pinia' export const useSitesStore = defineStore('sites', { state: () => ({ diff --git a/front_vue/src/Design/assets/css/tables.css b/front_vue/src/Design/assets/css/tables.css index 3c0967d..1d1b89e 100644 --- a/front_vue/src/Design/assets/css/tables.css +++ b/front_vue/src/Design/assets/css/tables.css @@ -1,100 +1,91 @@ /* ============================================ - Общие стили таблиц + Общие стили таблиц — Modern Clean ============================================ */ -.section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: var(--space-lg); -} - -.section-title { - font-size: var(--text-md); - font-weight: var(--font-bold); - color: var(--text-primary); - margin: 0; - text-transform: uppercase; - letter-spacing: 1.5px; - display: flex; - align-items: center; - gap: var(--space-lg); -} - -.section-title::before { - content: ''; - width: 4px; - height: 16px; - background: linear-gradient(180deg, var(--accent-purple), var(--accent-purple-light)); - border-radius: 2px; - flex-shrink: 0; -} +/* Table Container */ .table-container { - background: var(--glass-bg-light); - backdrop-filter: var(--backdrop-blur); - border: 1px solid var(--glass-border); - border-radius: var(--radius); overflow: hidden; - box-shadow: var(--shadow-md); } +/* Table */ .data-table { width: 100%; - border-collapse: collapse; -} - -.data-table thead { - background: var(--table-header-bg); + border-collapse: separate; + border-spacing: 0 8px; } +/* Header */ .data-table thead tr { - border-bottom: 1px solid var(--table-border); + border: none; } .data-table th { - padding: 18px 20px; + padding: 8px 20px; text-align: left; - font-size: var(--text-sm); - font-weight: var(--font-bold); - color: var(--text-secondary); + font-size: 11px; + font-weight: var(--font-semibold); + color: var(--text-muted); text-transform: uppercase; - letter-spacing: 1.2px; + letter-spacing: 1.5px; + border: none; + background: none; } -.data-table th:last-child { - width: 160px; - text-align: center; +.th-icon { + opacity: 0.4; + font-size: 10px; + margin-right: 2px; } +.th-actions { + text-align: right !important; + padding-right: 20px !important; +} + +/* Rows */ .data-table tbody tr { - border-bottom: 1px solid var(--table-border); transition: all var(--transition-base); + background: var(--glass-bg-light); } .data-table tbody tr:hover { - background: var(--table-hover-bg); + background: rgba(var(--accent-rgb), 0.06); } .data-table td { - padding: 16px 20px; + padding: 14px 20px; font-size: var(--text-base); color: var(--text-primary); + border-top: 1px solid var(--glass-border); + border-bottom: 1px solid var(--glass-border); +} + +.data-table td:first-child { + border-left: 1px solid var(--glass-border); + border-radius: var(--radius) 0 0 var(--radius); } .data-table td:last-child { - text-align: center; + border-right: 1px solid var(--glass-border); + border-radius: 0 var(--radius) var(--radius) 0; display: flex; gap: var(--space-xs); - justify-content: center; align-items: center; + justify-content: flex-end; } +.data-table tbody tr:hover td { + border-color: rgba(var(--accent-rgb), 0.15); +} + +/* Code */ .data-table code { font-family: var(--font-mono); font-size: var(--text-sm); } +/* Links */ .clickable-link { color: var(--link-color); cursor: pointer; @@ -117,59 +108,82 @@ .cert-icon { display: inline-flex; align-items: center; - justify-content: center; - width: 22px; - height: 22px; - margin-right: 8px; - border-radius: var(--radius); + margin-right: 6px; font-size: 12px; } .cert-valid { - background: rgba(var(--success-rgb), 0.2); color: var(--accent-green); - border: 1px solid rgba(var(--success-rgb), 0.4); } .cert-expired { - background: rgba(var(--danger-rgb), 0.2); color: var(--accent-red); - border: 1px solid rgba(var(--danger-rgb), 0.4); } .cert-none { - background: rgba(var(--muted-rgb), 0.15); color: var(--text-muted); - border: 1px solid rgba(var(--muted-rgb), 0.3); - opacity: 0.5; + opacity: 0.25; } /* Icon buttons */ .icon-btn { - width: 32px; - height: 32px; + width: 30px; + height: 30px; padding: 0; display: inline-flex; align-items: center; justify-content: center; - background: var(--btn-icon-bg); - border: 1px solid var(--btn-icon-border); + background: transparent; + border: none; border-radius: var(--radius); - color: var(--btn-icon-color); - font-size: var(--text-md); + color: var(--text-muted); + font-size: var(--text-sm); cursor: pointer; transition: all var(--transition-base); } .icon-btn:hover { - background: var(--btn-icon-hover-bg); - border-color: var(--btn-icon-hover-border); - transform: translateY(-1px); + background: rgba(var(--accent-rgb), 0.1); + color: var(--accent-purple-light); } .icon-btn:disabled { opacity: 0.6; cursor: wait; - transform: none; } +.status-toggle { + cursor: pointer; + transition: opacity var(--transition-fast); +} + +.status-toggle:hover { + opacity: 0.7; +} + +/* Drag and drop */ +.drag-grip { + color: var(--text-muted); + opacity: 0.15; + cursor: grab; + font-size: 11px; + margin-right: 8px; + transition: all var(--transition-fast); +} + +.data-table tbody tr:hover .drag-grip { + opacity: 0.4; +} + +.drag-grip:hover { + opacity: 1 !important; + color: var(--accent-purple-light); +} + +.data-table tbody tr.dragging { + opacity: 0.3; +} + +.data-table tbody tr.drag-over td { + border-top-color: var(--accent-purple) !important; +} diff --git a/front_vue/src/Design/components/layout/TitleBar.vue b/front_vue/src/Design/components/layout/TitleBar.vue index 1da07f3..69ec85c 100644 --- a/front_vue/src/Design/components/layout/TitleBar.vue +++ b/front_vue/src/Design/components/layout/TitleBar.vue @@ -1,5 +1,4 @@ - diff --git a/front_vue/src/Design/components/services/ServicesGrid.vue b/front_vue/src/Design/components/services/ServicesGrid.vue index 53cf8e8..a6a9894 100644 --- a/front_vue/src/Design/components/services/ServicesGrid.vue +++ b/front_vue/src/Design/components/services/ServicesGrid.vue @@ -5,7 +5,7 @@ const servicesStore = useServicesStore()