initial win 11 repo setup, user setting for URL for reverse proxy use, testing to commence

This commit is contained in:
christopher 2026-03-21 11:49:59 -07:00
parent f004c002a7
commit b9c8b5b76c
7 changed files with 107 additions and 3 deletions

View File

@ -3,7 +3,7 @@ import { SystemService } from '#services/system_service'
import { SystemUpdateService } from '#services/system_update_service' import { SystemUpdateService } from '#services/system_update_service'
import { ContainerRegistryService } from '#services/container_registry_service' import { ContainerRegistryService } from '#services/container_registry_service'
import { CheckServiceUpdatesJob } from '#jobs/check_service_updates_job' 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 { inject } from '@adonisjs/core'
import type { HttpContext } from '@adonisjs/core/http' 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<string> { private async getHostArch(): Promise<string> {
try { try {
const info = await this.dockerService.docker.info() const info = await this.dockerService.docker.info()

View File

@ -31,3 +31,10 @@ export const updateServiceValidator = vine.compile(
target_version: vine.string().trim(), target_version: vine.string().trim(),
}) })
) )
export const updateServiceLocationValidator = vine.compile(
vine.object({
service_name: vine.string().trim(),
ui_location: vine.string().trim(),
})
)

View File

@ -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 (
<StyledModal
title="Edit Service URL"
onConfirm={() => onSave(uiLocation)}
onCancel={onCancel}
open={true}
confirmText="Save"
cancelText="Cancel"
confirmVariant="primary"
icon={<IconLink className="h-12 w-12 text-desert-green" />}
>
<div className="space-y-4">
<p className="text-text-primary">
Set the URL for <strong>{record.friendly_name || record.service_name}</strong>.
</p>
<p className="text-sm text-text-muted">
Enter a full URL (e.g. <code className="bg-surface-secondary px-1 rounded">https://myservice.example.com</code>) to support reverse proxy setups, or a bare port number (e.g. <code className="bg-surface-secondary px-1 rounded">8080</code>) for direct access.
</p>
<Input
name="ui_location"
label="Service URL or Port"
value={uiLocation}
onChange={(e) => setUiLocation(e.target.value)}
placeholder="https://myservice.example.com or 8080"
/>
</div>
</StyledModal>
)
}

View File

@ -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) { async forceReinstallService(service_name: string) {
return catchInternal(async () => { return catchInternal(async () => {
const response = await this.client.post<{ success: boolean; message: string }>( const response = await this.client.post<{ success: boolean; message: string }>(

View File

@ -10,9 +10,9 @@ export function getServiceLink(ui_location: string): string {
// If it fails, it means it's not a valid URL // 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); 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 // If it's a port number, return a link to the service on that port
return `http://${window.location.hostname}:${parsedPort}`; return `http://${window.location.hostname}:${parsedPort}`;
} }

View File

@ -17,6 +17,7 @@ import { useTransmit } from 'react-adonis-transmit'
import { BROADCAST_CHANNELS } from '../../../constants/broadcast' import { BROADCAST_CHANNELS } from '../../../constants/broadcast'
import { IconArrowUp, IconCheck, IconDownload } from '@tabler/icons-react' import { IconArrowUp, IconCheck, IconDownload } from '@tabler/icons-react'
import UpdateServiceModal from '~/components/UpdateServiceModal' import UpdateServiceModal from '~/components/UpdateServiceModal'
import EditServiceUrlModal from '~/components/EditServiceUrlModal'
function extractTag(containerImage: string): string { function extractTag(containerImage: string): string {
if (!containerImage) return '' if (!containerImage) return ''
@ -199,6 +200,29 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
) )
} }
function handleEditUrl(record: ServiceSlim) {
openModal(
<EditServiceUrlModal
record={record}
onCancel={closeAllModals}
onSave={async (uiLocation: string) => {
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 AppActions = ({ record }: { record: ServiceSlim }) => {
const ForceReinstallButton = () => ( const ForceReinstallButton = () => (
<StyledButton <StyledButton
@ -258,6 +282,12 @@ export default function SettingsPage(props: { system: { services: ServiceSlim[]
> >
Open Open
</StyledButton> </StyledButton>
<StyledButton
icon={'IconEdit'}
onClick={() => handleEditUrl(record)}
>
Edit URL
</StyledButton>
{record.available_update_version && ( {record.available_update_version && (
<StyledButton <StyledButton
icon="IconArrowUp" icon="IconArrowUp"

View File

@ -147,6 +147,7 @@ router
router.post('/services/check-updates', [SystemController, 'checkServiceUpdates']) router.post('/services/check-updates', [SystemController, 'checkServiceUpdates'])
router.get('/services/:name/available-versions', [SystemController, 'getAvailableVersions']) router.get('/services/:name/available-versions', [SystemController, 'getAvailableVersions'])
router.post('/services/update', [SystemController, 'updateService']) router.post('/services/update', [SystemController, 'updateService'])
router.post('/services/update-location', [SystemController, 'updateServiceLocation'])
router.post('/subscribe-release-notes', [SystemController, 'subscribeToReleaseNotes']) router.post('/subscribe-release-notes', [SystemController, 'subscribeToReleaseNotes'])
router.get('/latest-version', [SystemController, 'checkLatestVersion']) router.get('/latest-version', [SystemController, 'checkLatestVersion'])
router.post('/update', [SystemController, 'requestSystemUpdate']) router.post('/update', [SystemController, 'requestSystemUpdate'])