diff --git a/README.md b/README.md index 15e0d52..16bbda4 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,14 @@ Отредактируй `.env`: ```ini -# токен админ-доступа к API панели — сгенерируй свой -GHARD_ADMIN_TOKEN= - # порт, на котором панель слушает снаружи (по умолчанию 8000) GHARD_PANEL_PORT=8000 ``` -`GHARD_ADMIN_TOKEN` — единственный секрет панели; с ним можно -создавать/удалять серверы и читать метрики, береги его. +`GHARD_ADMIN_TOKEN` — опционален: веб-интерфейс работает без логина. +Если задать токен, API панели (создание серверов, метрики) закроется +Bearer-токеном — удобно, когда панель торчит в интернет, а UI тебе не нужен. +Единственная настоящая авторизация в системе — ключи агентов (`ghm_...`). **2. Запуск:** @@ -78,11 +77,10 @@ ## Добавление сервера и установка агента -Пока нет веб-интерфейса (этап 3), сервер создаётся через API: +Проще всего — кнопка «Добавить сервер» в веб-интерфейсе. Через API то же самое: ```bash curl -X POST https://panel.example.com/api/v1/servers \ - -H "Authorization: Bearer $GHARD_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "web-01"}' ``` @@ -114,8 +112,7 @@ Проверка на панели — сервер должен получить `status: "online"`: ```bash -curl -s https://panel.example.com/api/v1/servers \ - -H "Authorization: Bearer $GHARD_ADMIN_TOKEN" +curl -s https://panel.example.com/api/v1/servers ``` ## Обновление @@ -157,7 +154,7 @@ | Переменная | По умолчанию | Описание | |---|---|---| -| `GHARD_ADMIN_TOKEN` | `change-me` | токен админ-доступа к API | +| `GHARD_ADMIN_TOKEN` | *(пусто)* | опциональный Bearer-токен API; веб-интерфейс работает без логина | | `GHARD_DATABASE_PATH` | `/data/hard-panel.db` | путь к SQLite | | `GHARD_PANEL_PORT` | `8000` | host-порт (docker-compose) | | `GHARD_OFFLINE_MULTIPLIER` | `3` | offline = нет пакетов дольше N × interval | diff --git a/panel/backend/.env.example b/panel/backend/.env.example index 17bfdad..09f4324 100644 --- a/panel/backend/.env.example +++ b/panel/backend/.env.example @@ -1,7 +1,8 @@ # GHard Monitor panel — настройки (env-префикс GHARD_) -# Токен админ-доступа к API панели (сгенерируй: openssl rand -hex 32) -GHARD_ADMIN_TOKEN=change-me +# Опциональный Bearer-токен API (сгенерируй: openssl rand -hex 32). +# Пусто = API открыт без авторизации (веб-интерфейс работает без логина). +# GHARD_ADMIN_TOKEN= # Путь к SQLite-файлу (в docker — volume /data) GHARD_DATABASE_PATH=/data/hard-panel.db diff --git a/panel/backend/app/config.py b/panel/backend/app/config.py index 662650a..2ffc52f 100644 --- a/panel/backend/app/config.py +++ b/panel/backend/app/config.py @@ -10,7 +10,9 @@ model_config = SettingsConfigDict(env_prefix="GHARD_", env_file=".env", extra="ignore") database_path: Path = Path("/data/hard-panel.db") - admin_token: str = "change-me" + # пусто = API панели открыт без авторизации (личная панель); + # если задан — доступ только с Bearer-токеном + admin_token: str = "" # offline = нет данных дольше offline_multiplier * interval агента (сек) offline_multiplier: int = 3 diff --git a/panel/backend/app/security.py b/panel/backend/app/security.py index edf6c0a..a8e699c 100644 --- a/panel/backend/app/security.py +++ b/panel/backend/app/security.py @@ -21,11 +21,14 @@ async def require_admin( creds: HTTPAuthorizationCredentials | None = Depends(_bearer), ) -> None: - """Админ-доступ к API панели (Bearer GHARD_ADMIN_TOKEN). + """Админ-доступ к API панели. - TODO (этап 3): заменить на JWT-логин из веб-интерфейса. + GHARD_ADMIN_TOKEN не задан — API открыт (личная панель без логина). + Задан — требуется Bearer-токен. """ token = get_settings().admin_token + if not token: + return if creds is None or creds.scheme.lower() != "bearer" or creds.credentials != token: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/panel/frontend/index.html b/panel/frontend/index.html index 7780531..ac7d5dd 100644 --- a/panel/frontend/index.html +++ b/panel/frontend/index.html @@ -3,7 +3,7 @@ - + GHard Monitor diff --git a/panel/frontend/public/favicon.svg b/panel/frontend/public/favicon.svg new file mode 100644 index 0000000..cd8461d --- /dev/null +++ b/panel/frontend/public/favicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/panel/frontend/public/logo.svg b/panel/frontend/public/logo.svg new file mode 100644 index 0000000..cd8461d --- /dev/null +++ b/panel/frontend/public/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/panel/frontend/src/App.vue b/panel/frontend/src/App.vue index e1631bb..5f53346 100644 --- a/panel/frontend/src/App.vue +++ b/panel/frontend/src/App.vue @@ -1,27 +1,18 @@ @@ -97,6 +97,8 @@ /* layout-обёртки; стили компонентов — из кита */ .server-card { cursor: pointer; + width: 100%; + max-width: 420px; } .card-title { display: flex; @@ -134,7 +136,8 @@ } .card-net { display: flex; - gap: 16px; + flex-wrap: wrap; + gap: 6px 16px; align-items: center; margin-top: 14px; font-size: 14px; @@ -148,8 +151,9 @@ gap: 6px; align-items: center; } +/* аптайм — всегда на собственной строке */ .card-uptime { - margin-left: auto; + flex-basis: 100%; opacity: 0.6; } .card-note { diff --git a/panel/frontend/src/format.js b/panel/frontend/src/format.js index 2315729..ab9818e 100644 --- a/panel/frontend/src/format.js +++ b/panel/frontend/src/format.js @@ -1,8 +1,7 @@ -// Форматтеры панели - +// Panel formatters export function fmtBytes(value) { if (!value && value !== 0) return '—' - const units = ['Б', 'КБ', 'МБ', 'ГБ', 'ТБ'] + const units = ['B', 'KB', 'MB', 'GB', 'TB'] let v = value let i = 0 while (v >= 1024 && i < units.length - 1) { @@ -25,29 +24,29 @@ export function fmtMbs(value) { if (value === null || value === undefined) return '—' - if (value < 0.01) return '0 МБ/с' - if (value < 1) return `${Math.round(value * 100) / 100} МБ/с` - return `${Math.round(value * 10) / 10} МБ/с` + if (value < 0.01) return '0 MB/s' + if (value < 1) return `${Math.round(value * 100) / 100} MB/s` + return `${Math.round(value * 10) / 10} MB/s` } export function fmtUptime(seconds) { if (!seconds && seconds !== 0) return '—' const days = Math.floor(seconds / 86400) const hours = Math.floor((seconds % 86400) / 3600) - if (days > 0) return `${days}д ${hours}ч` + if (days > 0) return `${days}d ${hours}h` const minutes = Math.floor((seconds % 3600) / 60) - if (hours > 0) return `${hours}ч ${minutes}м` - return `${minutes}м ${seconds % 60}с` + if (hours > 0) return `${hours}h ${minutes}m` + return `${minutes}m ${Math.floor(seconds % 60)}s` } export function fmtAgo(iso) { - if (!iso) return 'никогда' + if (!iso) return 'never' const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1000) - if (seconds < 5) return 'только что' - if (seconds < 60) return `${seconds} с назад` - if (seconds < 3600) return `${Math.floor(seconds / 60)} мин назад` - if (seconds < 86400) return `${Math.floor(seconds / 3600)} ч назад` - return `${Math.floor(seconds / 86400)} дн назад` + if (seconds < 5) return 'just now' + if (seconds < 60) return `${seconds}s ago` + if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago` + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago` + return `${Math.floor(seconds / 86400)}d ago` } export function ramPercent(metrics) { diff --git a/panel/frontend/src/pages/DashboardPage.vue b/panel/frontend/src/pages/DashboardPage.vue index c7c9612..4d41ffe 100644 --- a/panel/frontend/src/pages/DashboardPage.vue +++ b/panel/frontend/src/pages/DashboardPage.vue @@ -33,12 +33,12 @@ } load() -pollTimer = setInterval(load, 10000) +pollTimer = setInterval(load, 5000) onUnmounted(() => clearInterval(pollTimer)) const onlineCount = computed(() => servers.value.filter((s) => s.status === 'online').length) const subtitle = computed(() => - loading.value ? '' : `${servers.value.length} серверов · ${onlineCount.value} онлайн`, + loading.value ? '' : `${servers.value.length} servers · ${onlineCount.value} online`, ) function openServer(server) { @@ -66,7 +66,7 @@ createdKey.value = server.key await load() } catch (e) { - toast.danger({ title: 'Не получилось', text: e.message }) + toast.danger({ title: 'Failed', text: e.message }) } finally { creating.value = false } @@ -74,9 +74,9 @@