From b9c8b5b76c2ca30dbf2db7b7caae21bf7ebbf994 Mon Sep 17 00:00:00 2001 From: christopher Date: Sat, 21 Mar 2026 11:49:59 -0700 Subject: [PATCH] initial win 11 repo setup, user setting for URL for reverse proxy use, testing to commence --- admin/app/controllers/system_controller.ts | 14 +++++- admin/app/validators/system.ts | 7 +++ .../components/EditServiceUrlModal.tsx | 44 +++++++++++++++++++ admin/inertia/lib/api.ts | 10 +++++ admin/inertia/lib/navigation.ts | 4 +- admin/inertia/pages/settings/apps.tsx | 30 +++++++++++++ admin/start/routes.ts | 1 + 7 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 admin/inertia/components/EditServiceUrlModal.tsx diff --git a/admin/app/controllers/system_controller.ts b/admin/app/controllers/system_controller.ts index 0c3e1ad..92b169c 100644 --- a/admin/app/controllers/system_controller.ts +++ b/admin/app/controllers/system_controller.ts @@ -3,7 +3,7 @@ import { SystemService } from '#services/system_service' import { SystemUpdateService } from '#services/system_update_service' import { ContainerRegistryService } from '#services/container_registry_service' import { CheckServiceUpdatesJob } from '#jobs/check_service_updates_job' -import { affectServiceValidator, checkLatestVersionValidator, installServiceValidator, subscribeToReleaseNotesValidator, updateServiceValidator } from '#validators/system'; +import { affectServiceValidator, checkLatestVersionValidator, installServiceValidator, subscribeToReleaseNotesValidator, updateServiceLocationValidator, updateServiceValidator } from '#validators/system'; import { inject } from '@adonisjs/core' import type { HttpContext } from '@adonisjs/core/http' @@ -162,6 +162,18 @@ export default class SystemController { } } + async updateServiceLocation({ request, response }: HttpContext) { + const payload = await request.validateUsing(updateServiceLocationValidator) + const Service = (await import('#models/service')).default + const service = await Service.findBy('service_name', payload.service_name) + if (!service) { + return response.status(404).send({ error: 'Service not found' }) + } + service.ui_location = payload.ui_location + await service.save() + return response.send({ success: true, message: 'Service location updated successfully' }) + } + private async getHostArch(): Promise { try { const info = await this.dockerService.docker.info() diff --git a/admin/app/validators/system.ts b/admin/app/validators/system.ts index 41eb6a6..48efd65 100644 --- a/admin/app/validators/system.ts +++ b/admin/app/validators/system.ts @@ -31,3 +31,10 @@ export const updateServiceValidator = vine.compile( target_version: vine.string().trim(), }) ) + +export const updateServiceLocationValidator = vine.compile( + vine.object({ + service_name: vine.string().trim(), + ui_location: vine.string().trim(), + }) +) diff --git a/admin/inertia/components/EditServiceUrlModal.tsx b/admin/inertia/components/EditServiceUrlModal.tsx new file mode 100644 index 0000000..6039d49 --- /dev/null +++ b/admin/inertia/components/EditServiceUrlModal.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react' +import { ServiceSlim } from '../../types/services' +import StyledModal from './StyledModal' +import Input from './inputs/Input' +import { IconLink } from '@tabler/icons-react' + +interface EditServiceUrlModalProps { + record: ServiceSlim + onCancel: () => void + onSave: (uiLocation: string) => void +} + +export default function EditServiceUrlModal({ record, onCancel, onSave }: EditServiceUrlModalProps) { + const [uiLocation, setUiLocation] = useState(record.ui_location || '') + + return ( + onSave(uiLocation)} + onCancel={onCancel} + open={true} + confirmText="Save" + cancelText="Cancel" + confirmVariant="primary" + icon={} + > +
+

+ Set the URL for {record.friendly_name || record.service_name}. +

+

+ Enter a full URL (e.g. https://myservice.example.com) to support reverse proxy setups, or a bare port number (e.g. 8080) for direct access. +

+ setUiLocation(e.target.value)} + placeholder="https://myservice.example.com or 8080" + /> +
+
+ ) +} diff --git a/admin/inertia/lib/api.ts b/admin/inertia/lib/api.ts index 0295a4f..4ff64df 100644 --- a/admin/inertia/lib/api.ts +++ b/admin/inertia/lib/api.ts @@ -191,6 +191,16 @@ class API { })() } + async updateServiceLocation(serviceName: string, uiLocation: string) { + return catchInternal(async () => { + const response = await this.client.post<{ success: boolean; message: string }>( + '/system/services/update-location', + { service_name: serviceName, ui_location: uiLocation } + ) + return response.data + })() + } + async forceReinstallService(service_name: string) { return catchInternal(async () => { const response = await this.client.post<{ success: boolean; message: string }>( diff --git a/admin/inertia/lib/navigation.ts b/admin/inertia/lib/navigation.ts index 49040ef..355812a 100644 --- a/admin/inertia/lib/navigation.ts +++ b/admin/inertia/lib/navigation.ts @@ -10,9 +10,9 @@ export function getServiceLink(ui_location: string): string { // If it fails, it means it's not a valid URL } - // Check if the ui location is a port number + // Check if the ui location is a port number (strict: must be only digits) const parsedPort = parseInt(ui_location, 10); - if (!isNaN(parsedPort)) { + if (!isNaN(parsedPort) && String(parsedPort) === ui_location.trim()) { // If it's a port number, return a link to the service on that port return `http://${window.location.hostname}:${parsedPort}`; } diff --git a/admin/inertia/pages/settings/apps.tsx b/admin/inertia/pages/settings/apps.tsx index 48260c2..43ee43b 100644 --- a/admin/inertia/pages/settings/apps.tsx +++ b/admin/inertia/pages/settings/apps.tsx @@ -17,6 +17,7 @@ import { useTransmit } from 'react-adonis-transmit' import { BROADCAST_CHANNELS } from '../../../constants/broadcast' import { IconArrowUp, IconCheck, IconDownload } from '@tabler/icons-react' import UpdateServiceModal from '~/components/UpdateServiceModal' +import EditServiceUrlModal from '~/components/EditServiceUrlModal' function extractTag(containerImage: string): string { if (!containerImage) return '' @@ -199,6 +200,29 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[] ) } + function handleEditUrl(record: ServiceSlim) { + openModal( + { + closeAllModals() + try { + const response = await api.updateServiceLocation(record.service_name, uiLocation) + if (!response?.success) { + throw new Error(response?.message || 'Update failed') + } + window.location.reload() + } catch (error: any) { + console.error(`Error updating service URL for ${record.service_name}:`, error) + showError(`Failed to update service URL: ${error.message || 'Unknown error'}`) + } + }} + />, + `${record.service_name}-edit-url-modal` + ) + } + const AppActions = ({ record }: { record: ServiceSlim }) => { const ForceReinstallButton = () => ( Open + handleEditUrl(record)} + > + Edit URL + {record.available_update_version && (