fix: instance link includes domain, project delete cleans up containers and proxies

- InstanceCard appends settings domain to subdomain link (stage-dev-app.example.com instead of just stage-dev-app)
- Project deletion now removes Docker containers and proxy routes before deleting DB records
- Pass domain from settings to InstanceCard via project detail page
This commit is contained in:
2026-04-05 02:38:32 +03:00
parent 0993b3a54e
commit 12d78bec99
3 changed files with 32 additions and 2 deletions
+20
View File
@@ -169,6 +169,26 @@ func (s *Server) updateProject(w http.ResponseWriter, r *http.Request) {
// deleteProject handles DELETE /api/projects/{id}. // deleteProject handles DELETE /api/projects/{id}.
func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request) { func (s *Server) deleteProject(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id") id := chi.URLParam(r, "id")
// Clean up Docker containers and proxy routes before deleting the project.
ctx := r.Context()
stages, _ := s.store.GetStagesByProjectID(id)
for _, stage := range stages {
instances, _ := s.store.GetInstancesByStageID(stage.ID)
for _, inst := range instances {
if inst.ContainerID != "" {
if err := s.docker.RemoveContainer(ctx, inst.ContainerID, true); err != nil {
slog.Warn("delete project: remove container", "container", inst.ContainerID, "error", err)
}
}
if inst.ProxyRouteID != "" {
if err := s.proxyProvider.DeleteRoute(ctx, inst.ProxyRouteID); err != nil {
slog.Warn("delete project: delete proxy route", "route", inst.ProxyRouteID, "error", err)
}
}
}
}
if err := s.store.DeleteProject(id); err != nil { if err := s.store.DeleteProject(id); err != nil {
if errors.Is(err, store.ErrNotFound) { if errors.Is(err, store.ErrNotFound) {
respondNotFound(w, "project") respondNotFound(w, "project")
+5 -2
View File
@@ -13,17 +13,20 @@
interface Props { interface Props {
instance: Instance; instance: Instance;
projectId: string; projectId: string;
domain?: string;
onchange?: () => void; onchange?: () => void;
} }
const { instance, projectId, onchange }: Props = $props(); const { instance, projectId, domain = '', onchange }: Props = $props();
let loading = $state(false); let loading = $state(false);
let error = $state(''); let error = $state('');
let confirmAction = $state<'stop' | 'restart' | 'remove' | null>(null); let confirmAction = $state<'stop' | 'restart' | 'remove' | null>(null);
const subdomainUrl = $derived( const subdomainUrl = $derived(
instance.subdomain ? `https://${instance.subdomain}` : '' instance.subdomain && domain
? `https://${instance.subdomain}.${domain}`
: instance.subdomain ? `https://${instance.subdomain}` : ''
); );
const timeSinceCreated = $derived(() => { const timeSinceCreated = $derived(() => {
@@ -106,6 +106,7 @@
} }
} }
let tagsLoading = $state(false); let tagsLoading = $state(false);
let settingsDomain = $state('');
let showDeleteConfirm = $state(false); let showDeleteConfirm = $state(false);
@@ -142,6 +143,11 @@
} catch { } catch {
deploys = []; deploys = [];
} }
try {
const settings = await api.getSettings();
settingsDomain = settings.domain ?? '';
} catch { /* non-critical */ }
} catch (e) { } catch (e) {
error = e instanceof Error ? e.message : $t('projectDetail.loadFailed'); error = e instanceof Error ? e.message : $t('projectDetail.loadFailed');
} finally { } finally {
@@ -486,6 +492,7 @@
<InstanceCard <InstanceCard
{instance} {instance}
{projectId} {projectId}
domain={settingsDomain}
onchange={loadProject} onchange={loadProject}
/> />
{/each} {/each}