feat: proxy routes page, OIDC login fix, NPM test connection, webhook URL fix, and UX improvements
- Add /proxies page showing deploy-managed proxy routes with project/stage links, search, and status - Add GET /api/proxies endpoint joining instances with project/stage names - Add POST /api/settings/npm/test endpoint for NPM connection validation - Add GET /api/auth/mode public endpoint for auth mode detection - Add NPM Test Connection button with validation on save - Fix OIDC SSO button only shown when auth_mode is oidc - Fix webhook URL showing empty when domain not set (fallback to request host) - Fix quick deploy double-tag (image:latest:latest) by splitting tag from image URL - Fix trim() errors on number inputs in deploy and settings forms - Fix NPM client auto-append /api to base URL - Sanitize NPM test error messages (no raw HTML) - Remove healthcheck field from Quick Deploy form - Fix env vars placeholder newline - Make domain field optional in settings - Set polling interval minimum to 60s - Add Proxies and Events to sidebar navigation - Fix SSL cert name flash on NPM settings page - Fix empty state icon on proxies page
This commit is contained in:
+14
-2
@@ -4,11 +4,13 @@ import type {
|
||||
Deploy,
|
||||
DeployLog,
|
||||
DockerHealth,
|
||||
ProxyHealth,
|
||||
EventLogEntry,
|
||||
EventLogStats,
|
||||
InspectResult,
|
||||
Instance,
|
||||
NpmCertificate,
|
||||
ProxyRoute,
|
||||
Project,
|
||||
ProjectDetail,
|
||||
Registry,
|
||||
@@ -265,6 +267,16 @@ export function regenerateWebhookUrl(): Promise<{ webhook_url: string }> {
|
||||
return post<{ webhook_url: string }>('/api/settings/webhook-url/regenerate');
|
||||
}
|
||||
|
||||
// ── Proxy Routes ───────────────────────────────────────────────────
|
||||
|
||||
export function listProxyRoutes(): Promise<ProxyRoute[]> {
|
||||
return get<ProxyRoute[]>('/api/proxies');
|
||||
}
|
||||
|
||||
export function testNpmConnection(data: { npm_url?: string; npm_email?: string; npm_password?: string }): Promise<{ status: string }> {
|
||||
return post<{ status: string }>('/api/settings/npm/test', data);
|
||||
}
|
||||
|
||||
export function listNpmCertificates(): Promise<NpmCertificate[]> {
|
||||
return get<NpmCertificate[]>('/api/settings/npm-certificates');
|
||||
}
|
||||
@@ -315,8 +327,8 @@ export function backupDownloadUrl(id: string): string {
|
||||
|
||||
// ── Health ──────────────────────────────────────────────────────────
|
||||
|
||||
export function getHealth(): Promise<{ docker: DockerHealth }> {
|
||||
return get<{ docker: DockerHealth }>('/api/health');
|
||||
export function getHealth(): Promise<{ docker: DockerHealth; proxy?: ProxyHealth }> {
|
||||
return get<{ docker: DockerHealth; proxy?: ProxyHealth }>('/api/health');
|
||||
}
|
||||
|
||||
// ── Auth ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
"varTag": "Image tag",
|
||||
"varPort": "Container port",
|
||||
"pollingInterval": "Polling Interval (seconds)",
|
||||
"pollingIntervalHelp": "How often to check registries for new tags (10-86400)",
|
||||
"pollingIntervalHelp": "How often to check registries for new tags (60-86400)",
|
||||
"notificationUrl": "Notification URL",
|
||||
"notificationUrlHelp": "Webhook URL for deploy notifications",
|
||||
"saveSettings": "Save Settings",
|
||||
@@ -367,6 +367,13 @@
|
||||
"healthConnected": "Connected",
|
||||
"healthUnreachable": "Unreachable"
|
||||
},
|
||||
"settingsNpm": {
|
||||
"testConnection": "Test Connection",
|
||||
"testing": "Testing...",
|
||||
"testSuccess": "NPM connection successful",
|
||||
"testFailed": "NPM connection failed",
|
||||
"saveFailedConnection": "Cannot save \u2014 connection test failed"
|
||||
},
|
||||
"settingsCredentials": {
|
||||
"title": "Credentials",
|
||||
"description": "Manage credentials for Nginx Proxy Manager and registry tokens. All values are encrypted at rest.",
|
||||
@@ -544,7 +551,7 @@
|
||||
"invalidIp": "Invalid IP format",
|
||||
"invalidEmail": "Invalid email format",
|
||||
"invalidPort": "Port must be between 1 and 65535",
|
||||
"invalidPollingInterval": "Polling interval must be between 10 and 86400 seconds",
|
||||
"invalidPollingInterval": "Polling interval must be between 60 and 86400 seconds",
|
||||
"invalidProjectName": "Only lowercase letters, numbers, and hyphens allowed",
|
||||
"requiredWhenUpdating": "{field} is required when updating credentials",
|
||||
"requiredForNew": "{field} is required for new registries"
|
||||
@@ -628,6 +635,23 @@
|
||||
"skipped": "Skipped"
|
||||
}
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Proxy Routes",
|
||||
"description": "Active proxy routes created by deployments.",
|
||||
"domain": "Domain",
|
||||
"project": "Project",
|
||||
"stage": "Stage",
|
||||
"tag": "Tag",
|
||||
"port": "Port",
|
||||
"status": "Status",
|
||||
"noRoutes": "No proxy routes",
|
||||
"noRoutesDesc": "Proxy routes are created automatically when you deploy a container with proxy enabled.",
|
||||
"searchPlaceholder": "Search by domain, project, or tag...",
|
||||
"noMatch": "No routes match your search.",
|
||||
"loadFailed": "Failed to load proxy routes",
|
||||
"route": "route",
|
||||
"routes": "routes"
|
||||
},
|
||||
"events": {
|
||||
"title": "Event Log",
|
||||
"noEvents": "No events found",
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
"varTag": "Тег образа",
|
||||
"varPort": "Порт контейнера",
|
||||
"pollingInterval": "Интервал опроса (секунды)",
|
||||
"pollingIntervalHelp": "Как часто проверять реестры на новые теги (10-86400)",
|
||||
"pollingIntervalHelp": "Как часто проверять реестры на новые теги (60-86400)",
|
||||
"notificationUrl": "URL уведомлений",
|
||||
"notificationUrlHelp": "URL вебхука для уведомлений о деплоях",
|
||||
"saveSettings": "Сохранить настройки",
|
||||
@@ -367,6 +367,13 @@
|
||||
"healthConnected": "Подключено",
|
||||
"healthUnreachable": "Недоступно"
|
||||
},
|
||||
"settingsNpm": {
|
||||
"testConnection": "Проверить соединение",
|
||||
"testing": "Проверка...",
|
||||
"testSuccess": "Подключение к NPM успешно",
|
||||
"testFailed": "Не удалось подключиться к NPM",
|
||||
"saveFailedConnection": "Невозможно сохранить — проверка соединения не пройдена"
|
||||
},
|
||||
"settingsCredentials": {
|
||||
"title": "Учётные данные",
|
||||
"description": "Управление учётными данными для Nginx Proxy Manager и токенами реестров. Все значения зашифрованы.",
|
||||
@@ -544,7 +551,7 @@
|
||||
"invalidIp": "Неверный формат IP",
|
||||
"invalidEmail": "Неверный формат email",
|
||||
"invalidPort": "Порт должен быть от 1 до 65535",
|
||||
"invalidPollingInterval": "Интервал опроса должен быть от 10 до 86400 секунд",
|
||||
"invalidPollingInterval": "Интервал опроса должен быть от 60 до 86400 секунд",
|
||||
"invalidProjectName": "Допускаются только строчные буквы, цифры и дефисы",
|
||||
"requiredWhenUpdating": "Поле {field} обязательно при обновлении учётных данных",
|
||||
"requiredForNew": "Поле {field} обязательно для новых реестров"
|
||||
@@ -628,6 +635,23 @@
|
||||
"skipped": "Пропущено"
|
||||
}
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Прокси-маршруты",
|
||||
"description": "Активные прокси-маршруты, созданные при развёртывании.",
|
||||
"domain": "Домен",
|
||||
"project": "Проект",
|
||||
"stage": "Этап",
|
||||
"tag": "Тег",
|
||||
"port": "Порт",
|
||||
"status": "Статус",
|
||||
"noRoutes": "Нет прокси-маршрутов",
|
||||
"noRoutesDesc": "Прокси-маршруты создаются автоматически при развёртывании контейнера с включённым прокси.",
|
||||
"searchPlaceholder": "Поиск по домену, проекту или тегу...",
|
||||
"noMatch": "Нет маршрутов, соответствующих поиску.",
|
||||
"loadFailed": "Не удалось загрузить прокси-маршруты",
|
||||
"route": "маршрут",
|
||||
"routes": "маршрутов"
|
||||
},
|
||||
"events": {
|
||||
"title": "Журнал событий",
|
||||
"noEvents": "Событий не найдено",
|
||||
|
||||
@@ -251,6 +251,30 @@ export interface DockerHealth {
|
||||
checked_at?: string;
|
||||
}
|
||||
|
||||
export interface ProxyHealth {
|
||||
connected: boolean;
|
||||
provider: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/** A proxy route managed by a deployed instance. */
|
||||
export interface ProxyRoute {
|
||||
instance_id: string;
|
||||
project_id: string;
|
||||
project_name: string;
|
||||
stage_id: string;
|
||||
stage_name: string;
|
||||
image_tag: string;
|
||||
subdomain: string;
|
||||
domain: string;
|
||||
container_id: string;
|
||||
port: number;
|
||||
proxy_route_id: string;
|
||||
npm_proxy_id: number;
|
||||
status: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
/** A persistent event log entry. */
|
||||
export interface EventLogEntry {
|
||||
id: number;
|
||||
|
||||
Reference in New Issue
Block a user