v1.0.19
This commit is contained in:
1 parent
4b430340db
commit
9c451aedf9
120 files changed
+7777
-6046
No files matched your search
@@ -85,49 +85,40 @@
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<!-- General Tab -->
|
||||
<Tabs.Content value="general" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<GeneralTab />
|
||||
{#if activeTab === 'general'}<GeneralTab />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Environments Tab -->
|
||||
<Tabs.Content value="environments" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<EnvironmentsTab {editEnvId} {newEnv} />
|
||||
{#if activeTab === 'environments'}<EnvironmentsTab {editEnvId} {newEnv} />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Registries Tab -->
|
||||
<Tabs.Content value="registries" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<RegistriesTab />
|
||||
{#if activeTab === 'registries'}<RegistriesTab />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Git Tab -->
|
||||
<Tabs.Content value="git" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<GitTab />
|
||||
{#if activeTab === 'git'}<GitTab />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Config Sets Tab -->
|
||||
<Tabs.Content value="config-sets" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<ConfigSetsTab />
|
||||
{#if activeTab === 'config-sets'}<ConfigSetsTab />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Notifications Tab -->
|
||||
<Tabs.Content value="notifications" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<NotificationsTab />
|
||||
{#if activeTab === 'notifications'}<NotificationsTab />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- Auth Tab -->
|
||||
<Tabs.Content value="auth" class="flex-1 min-h-0 flex flex-col">
|
||||
<AuthTab onTabChange={handleTabChange} />
|
||||
{#if activeTab === 'auth'}<AuthTab onTabChange={handleTabChange} />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- License Tab -->
|
||||
<Tabs.Content value="license" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<LicenseTab />
|
||||
{#if activeTab === 'license'}<LicenseTab />{/if}
|
||||
</Tabs.Content>
|
||||
|
||||
<!-- About Tab -->
|
||||
<Tabs.Content value="about" class="flex-1 min-h-0 overflow-y-auto">
|
||||
<AboutTab />
|
||||
{#if activeTab === 'about'}<AboutTab />{/if}
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
@@ -195,8 +195,8 @@
|
||||
storageDriver: string;
|
||||
} | null;
|
||||
runtime: {
|
||||
bun: string | null;
|
||||
bunRevision: string | null;
|
||||
runtimeName: string;
|
||||
runtimeVersion: string;
|
||||
nodeVersion: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
@@ -266,6 +266,7 @@
|
||||
newDigest: string;
|
||||
containerName: string;
|
||||
isComposeManaged: boolean;
|
||||
latestVersion?: string;
|
||||
error?: string;
|
||||
} | null>(null);
|
||||
let updateCheckError = $state<string | null>(null);
|
||||
@@ -299,11 +300,12 @@
|
||||
if (data.updateAvailable) {
|
||||
updateInfo = {
|
||||
currentImage: data.currentImage,
|
||||
newImage: data.currentImage,
|
||||
newImage: data.newImage || data.currentImage,
|
||||
currentDigest: data.currentDigest || '',
|
||||
newDigest: data.newDigest || '',
|
||||
containerName: data.containerName,
|
||||
isComposeManaged: data.isComposeManaged
|
||||
isComposeManaged: data.isComposeManaged,
|
||||
latestVersion: data.latestVersion
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -634,9 +636,9 @@
|
||||
</div>
|
||||
<div class="text-sm pl-5">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
{#if systemInfo.runtime.bun}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-amber-500/15 text-amber-600 dark:text-amber-400 shadow-sm ring-1 ring-amber-500/20">
|
||||
Bun {systemInfo.runtime.bun}
|
||||
{#if systemInfo.runtime.runtimeVersion}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-500/15 text-green-600 dark:text-green-400 shadow-sm ring-1 ring-green-500/20">
|
||||
{systemInfo.runtime.runtimeName} {systemInfo.runtime.runtimeVersion}
|
||||
</span>
|
||||
{/if}
|
||||
<span class="text-muted-foreground/50">|</span>
|
||||
@@ -744,6 +746,11 @@
|
||||
<MessageSquarePlus class="w-4 h-4" />
|
||||
Submit issue or idea
|
||||
</a>
|
||||
<span class="text-muted-foreground/50">|</span>
|
||||
<a href="https://discord.gg/rMxW9Y5cQw" target="_blank" rel="noopener noreferrer" class="hover:underline inline-flex items-center gap-1.5 font-medium text-indigo-500 dark:text-indigo-400">
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||
Discord
|
||||
</a>
|
||||
{#if !$licenseStore.isLicensed}
|
||||
<span class="text-muted-foreground/50">|</span>
|
||||
<a href="https://buymeacoffee.com/dockhand" target="_blank" rel="noopener noreferrer" class="hover:underline inline-flex items-center gap-1.5 font-medium text-amber-600 dark:text-yellow-400">
|
||||
@@ -962,6 +969,7 @@
|
||||
newDigest={updateInfo.newDigest}
|
||||
containerName={updateInfo.containerName}
|
||||
isComposeManaged={updateInfo.isComposeManaged}
|
||||
latestVersion={updateInfo.latestVersion}
|
||||
onclose={() => showSelfUpdateDialog = false}
|
||||
/>
|
||||
{/if}
|
||||
@@ -22,10 +22,13 @@
|
||||
newDigest?: string;
|
||||
containerName: string;
|
||||
isComposeManaged?: boolean;
|
||||
latestVersion?: string;
|
||||
onclose: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), currentImage, newImage, currentDigest = '', newDigest = '', containerName, isComposeManaged = false, onclose }: Props = $props();
|
||||
let { open = $bindable(), currentImage, newImage, currentDigest = '', newDigest = '', containerName, isComposeManaged = false, latestVersion, onclose }: Props = $props();
|
||||
|
||||
const isVersionUpdate = $derived(currentImage !== newImage);
|
||||
|
||||
// Phase management
|
||||
type Phase = 'confirm' | 'preparing' | 'updating' | 'restarting' | 'completed' | 'error';
|
||||
@@ -210,7 +213,7 @@
|
||||
try {
|
||||
const response = await fetch('/api/self-update', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
|
||||
body: JSON.stringify({ newImage })
|
||||
});
|
||||
|
||||
@@ -511,19 +514,30 @@
|
||||
{containerName}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Image</span>
|
||||
<Badge variant="secondary" class="font-mono text-xs">{currentImage}</Badge>
|
||||
</div>
|
||||
{#if currentDigest || newDigest}
|
||||
{#if isVersionUpdate}
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Current digest</span>
|
||||
<span class="font-mono text-xs text-muted-foreground">{currentDigest ? currentDigest.replace('sha256:', '').slice(0, 12) : 'unknown'}</span>
|
||||
<span class="text-muted-foreground">Current image</span>
|
||||
<Badge variant="secondary" class="font-mono text-xs">{currentImage}</Badge>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">New digest</span>
|
||||
<span class="font-mono text-xs text-amber-500">{newDigest ? newDigest.replace('sha256:', '').slice(0, 12) : 'unknown'}</span>
|
||||
<span class="text-muted-foreground">New image</span>
|
||||
<Badge variant="default" class="font-mono text-xs">{newImage}</Badge>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Image</span>
|
||||
<Badge variant="secondary" class="font-mono text-xs">{currentImage}</Badge>
|
||||
</div>
|
||||
{#if currentDigest || newDigest}
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">Current digest</span>
|
||||
<span class="font-mono text-xs text-muted-foreground">{currentDigest ? currentDigest.replace('sha256:', '').slice(0, 12) : 'unknown'}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">New digest</span>
|
||||
<span class="font-mono text-xs text-amber-500">{newDigest ? newDigest.replace('sha256:', '').slice(0, 12) : 'unknown'}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { readJobResponse } from '$lib/utils/sse-fetch';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
@@ -1114,18 +1115,9 @@
|
||||
throw new Error('Failed to pull Grype image');
|
||||
}
|
||||
|
||||
// Read SSE stream to completion
|
||||
const reader = response.body?.getReader();
|
||||
if (reader) {
|
||||
const decoder = new TextDecoder();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
const text = decoder.decode(value, { stream: true });
|
||||
if (text.includes('"status":"error"')) {
|
||||
throw new Error('Pull failed');
|
||||
}
|
||||
}
|
||||
const result = await readJobResponse(response);
|
||||
if (result.success === false) {
|
||||
throw new Error(result.error as string || 'Pull failed');
|
||||
}
|
||||
|
||||
// Refresh scanner status after pull
|
||||
@@ -1155,18 +1147,9 @@
|
||||
throw new Error('Failed to pull Trivy image');
|
||||
}
|
||||
|
||||
// Read SSE stream to completion
|
||||
const reader = response.body?.getReader();
|
||||
if (reader) {
|
||||
const decoder = new TextDecoder();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
const text = decoder.decode(value, { stream: true });
|
||||
if (text.includes('"status":"error"')) {
|
||||
throw new Error('Pull failed');
|
||||
}
|
||||
}
|
||||
const result = await readJobResponse(response);
|
||||
if (result.success === false) {
|
||||
throw new Error(result.error as string || 'Pull failed');
|
||||
}
|
||||
|
||||
// Refresh scanner status after pull
|
||||
|
||||
@@ -177,25 +177,29 @@
|
||||
testResult = 'idle';
|
||||
|
||||
try {
|
||||
const config = getFormConfig();
|
||||
// When editing with no password entered, use stored credentials via [id]/test
|
||||
// to avoid sending blank password and getting "Missing credentials" from SMTP server
|
||||
const useStoredCredentials = isEditing && formType === 'smtp' && !formSmtpPassword && notification?.id;
|
||||
|
||||
// For editing, if password is empty, we can't test without the original password
|
||||
if (isEditing && formType === 'smtp' && !formSmtpPassword && notification?.config?.username) {
|
||||
formError = 'Please enter the password to test the connection';
|
||||
formTesting = false;
|
||||
return;
|
||||
let response: Response;
|
||||
if (useStoredCredentials) {
|
||||
response = await fetch(`/api/notifications/${notification!.id}/test`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
} else {
|
||||
const config = getFormConfig();
|
||||
response = await fetch('/api/notifications/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
type: formType,
|
||||
name: formName.trim() || 'Test',
|
||||
config
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetch('/api/notifications/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
type: formType,
|
||||
name: formName.trim() || 'Test',
|
||||
config
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
|
||||
Reference in new issue
Block a user