forked from getarcaneapp/arcane
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathContainerComposePanel.svelte
More file actions
114 lines (102 loc) · 3.55 KB
/
ContainerComposePanel.svelte
File metadata and controls
114 lines (102 loc) · 3.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<script lang="ts">
import * as Alert from '$lib/components/ui/alert/index.js';
import { ArcaneButton } from '$lib/components/arcane-button';
import CodePanel from '../../projects/components/CodePanel.svelte';
import { projectService } from '$lib/services/project-service';
import { toast } from 'svelte-sonner';
import type { Project, IncludeFile } from '$lib/types/project.type';
import { AlertIcon, ExternalLinkIcon } from '$lib/icons';
import * as m from '$lib/paraglide/messages';
let {
project,
serviceName,
includeFile = null
}: {
project: Project;
serviceName: string;
includeFile?: IncludeFile | null;
} = $props();
const sourceContent = $derived(includeFile ? includeFile.content : (project.composeContent ?? ''));
let composeContent = $state(sourceContent);
let isDirty = $state(false);
// Update composeContent when source changes (e.g., switching containers)
// Only if there are no unsaved edits
let prevSourceContent = $state(sourceContent);
$effect(() => {
if (sourceContent !== prevSourceContent && !isDirty) {
composeContent = sourceContent;
prevSourceContent = sourceContent;
}
});
// Track dirty state when content changes
$effect(() => {
isDirty = composeContent !== sourceContent;
});
let panelOpen = $state(true);
let isSaving = $state(false);
const isReadOnly = $derived(!!project.gitOpsManagedBy);
const fileTitle = $derived(includeFile ? includeFile.relativePath : 'compose.yml');
async function handleSave() {
isSaving = true;
try {
if (includeFile) {
await projectService.updateProjectIncludeFile(project.id, includeFile.relativePath, composeContent);
} else {
await projectService.updateProject(project.id, undefined, composeContent);
}
toast.success(m.container_compose_save_success());
isDirty = false;
} catch (err: any) {
toast.error(err?.message ?? m.container_compose_save_failed());
} finally {
isSaving = false;
}
}
</script>
<div class="flex h-full min-h-0 flex-col gap-4 p-4">
{#if project.gitOpsManagedBy}
<Alert.Root variant="default">
<AlertIcon class="size-4" />
<Alert.Title>{m.container_compose_gitops_managed_title()}</Alert.Title>
<Alert.Description>
{@html m.container_compose_gitops_managed_description({ provider: `<strong>${project.gitOpsManagedBy}</strong>` })}
</Alert.Description>
</Alert.Root>
{/if}
<div class="bg-muted flex items-start gap-2 rounded-lg border px-4 py-3 text-sm">
<span>
{@html isReadOnly
? m.container_compose_viewing_info({
file: `<strong>${fileTitle}</strong>`,
project: `<a href="/projects/${project.id}" class="text-primary font-medium hover:underline">${project.name}</a>`,
service: `<strong>${serviceName}</strong>`
})
: m.container_compose_editing_info({
file: `<strong>${fileTitle}</strong>`,
project: `<a href="/projects/${project.id}" class="text-primary font-medium hover:underline">${project.name}</a>`,
service: `<strong>${serviceName}</strong>`
})}
</span>
</div>
<div class="flex min-h-0 flex-1 flex-col">
<CodePanel
title={fileTitle}
bind:open={panelOpen}
language="yaml"
bind:value={composeContent}
readOnly={isReadOnly}
fileId="container-compose-{project.id}{includeFile ? `-${includeFile.relativePath}` : ''}"
/>
</div>
<div class="flex shrink-0 items-center gap-2">
{#if !isReadOnly}
<ArcaneButton action="save" loading={isSaving} onclick={handleSave} />
{/if}
<ArcaneButton
action="base"
href="/projects/{project.id}"
icon={ExternalLinkIcon}
customLabel={m.container_compose_view_project()}
/>
</div>
</div>