This commit is contained in:
Jarek Krochmalski committed 2026-01-11 07:16:18 +01:00
1 parent bd7b832394
commit 7643807717
86 files changed
+3644 -1385

No files matched your search

+9 -5
View File
@@ -3,7 +3,7 @@
import * as Card from '$lib/components/ui/card';
import { Badge } from '$lib/components/ui/badge';
import { Input } from '$lib/components/ui/input';
import { Box, Images, HardDrive, Network, Cpu, Server, Crown, Building2, Layers, Clock, Code, Package, ExternalLink, Search, FileText, Tag, Sparkles, Bug, ChevronDown, ChevronRight, Plug, ScrollText, Shield, MessageSquarePlus, GitBranch, Coffee } from 'lucide-svelte';
import { Box, Images, HardDrive, Network, Cpu, Server, Crown, Building2, Layers, Clock, Code, Package, ExternalLink, Search, FileText, Tag, Sparkles, Bug, ChevronDown, ChevronRight, Plug, ScrollText, Shield, MessageSquarePlus, GitBranch, Coffee, Monitor, Cog, MemoryStick, Database } from 'lucide-svelte';
import * as Tabs from '$lib/components/ui/tabs';
import { onMount, onDestroy } from 'svelte';
import { licenseStore } from '$lib/stores/license';
@@ -198,6 +198,7 @@
nodeVersion: string;
platform: string;
arch: string;
kernel: string;
memory: {
heapUsed: number;
heapTotal: number;
@@ -438,7 +439,7 @@
</div>
{/if}
{#if serverUptime !== null}
<div class="flex items-center gap-1">
<div class="flex items-center gap-1 min-w-[8.5rem]">
<Clock class="w-3 h-3 shrink-0" />
<span class="tabular-nums">Uptime {formatUptime(serverUptime)}</span>
</div>
@@ -558,10 +559,13 @@
</span>
{/if}
<span class="text-muted-foreground/50">|</span>
<span class="text-muted-foreground">Platform</span>
<Server class="w-3 h-3 text-muted-foreground" />
<span>{systemInfo.runtime.platform}/{systemInfo.runtime.arch}</span>
<span class="text-muted-foreground/50">|</span>
<span class="text-muted-foreground">Memory</span>
<Cpu class="w-3 h-3 text-muted-foreground" />
<span>{systemInfo.runtime.kernel}</span>
<span class="text-muted-foreground/50">|</span>
<MemoryStick class="w-3 h-3 text-muted-foreground" />
<span>{formatBytes(systemInfo.runtime.memory.rss)}</span>
</div>
{#if systemInfo.runtime.container.inContainer}
@@ -585,7 +589,7 @@
<!-- Database Info -->
<div class="space-y-1.5">
<div class="flex items-center gap-2 text-xs font-medium text-muted-foreground">
<HardDrive class="w-3.5 h-3.5" />
<Database class="w-3.5 h-3.5" />
Database
</div>
<div class="text-sm pl-5 space-y-1">
@@ -415,6 +415,10 @@
newLabelInput = '';
formPublicIp = environment.publicIp || '';
modalTab = 'general';
// Reset token state for this environment (important when switching between envs)
hawserToken = null;
generatedToken = null;
pendingToken = null;
// Load scanner settings, notifications, update check settings, and timezone
loadScannerSettings(environment.id);
loadEnvNotifications(environment.id);
@@ -825,6 +829,11 @@
}
}
// Reload only availability/versions without overwriting user's unsaved settings changes
async function reloadScannerAvailability(envId?: number) {
await loadScannerVersionsAsync(envId);
}
async function saveScannerSettings(envId?: number) {
try {
const response = await fetch('/api/settings/scanner', {
@@ -930,7 +939,8 @@
checkingGrypeUpdate = true;
grypeUpdateStatus = 'idle';
try {
const response = await fetch('/api/settings/scanner?checkUpdates=true');
const envParam = environment?.id ? `&env=${environment.id}` : '';
const response = await fetch(`/api/settings/scanner?checkUpdates=true${envParam}`);
const data = await response.json();
if (data.updates) {
grypeUpdateStatus = data.updates.grype?.hasUpdate ? 'update-available' : 'up-to-date';
@@ -947,7 +957,8 @@
checkingTrivyUpdate = true;
trivyUpdateStatus = 'idle';
try {
const response = await fetch('/api/settings/scanner?checkUpdates=true');
const envParam = environment?.id ? `&env=${environment.id}` : '';
const response = await fetch(`/api/settings/scanner?checkUpdates=true${envParam}`);
const data = await response.json();
if (data.updates) {
trivyUpdateStatus = data.updates.trivy?.hasUpdate ? 'update-available' : 'up-to-date';
@@ -1198,7 +1209,7 @@
</script>
<Dialog.Root bind:open onOpenChange={(o) => { if (o) focusFirstInput(); else onClose(); }}>
<Dialog.Content class="max-w-2xl min-h-[800px] max-h-[90vh] flex flex-col overflow-hidden">
<Dialog.Content class="max-w-2xl max-h-[90vh] flex flex-col overflow-hidden">
<Dialog.Header class="flex-shrink-0 border-b pb-4">
<Dialog.Title class="flex items-center gap-2">
{#if !isEditing}
@@ -1362,7 +1373,7 @@
<HelpCircle class="w-3.5 h-3.5" />
</button>
</Popover.Trigger>
<Popover.Content class="w-80 text-sm" side="right">
<Popover.Content class="w-80 text-sm z-[200]" side="right">
<div class="space-y-3">
<div class="flex items-start gap-2">
<Unplug class="w-4 h-4 mt-0.5 text-cyan-500 shrink-0" />
@@ -2112,7 +2123,7 @@
{/if}
{#if !loadingScannerVersions}
{#if !scannerAvailability.grype}
<ImagePullProgressPopover imageName="anchore/grype:latest" envId={environment?.id} onComplete={() => loadScannerSettings(environment?.id)}>
<ImagePullProgressPopover imageName="anchore/grype:latest" envId={environment?.id} onComplete={() => reloadScannerAvailability(environment?.id)}>
<button class="inline-flex items-center text-2xs px-1.5 py-0 h-4 rounded-full border bg-muted/50 hover:bg-muted text-muted-foreground hover:text-foreground transition-colors">
<Download class="w-2.5 h-2.5 mr-0.5" />
Pull
@@ -2189,7 +2200,7 @@
{/if}
{#if !loadingScannerVersions}
{#if !scannerAvailability.trivy}
<ImagePullProgressPopover imageName="aquasec/trivy:latest" envId={environment?.id} onComplete={() => loadScannerSettings(environment?.id)}>
<ImagePullProgressPopover imageName="aquasec/trivy:latest" envId={environment?.id} onComplete={() => reloadScannerAvailability(environment?.id)}>
<button class="inline-flex items-center text-2xs px-1.5 py-0 h-4 rounded-full border bg-muted/50 hover:bg-muted text-muted-foreground hover:text-foreground transition-colors">
<Download class="w-2.5 h-2.5 mr-0.5" />
Pull
+127 -3
View File
@@ -8,8 +8,8 @@
import { TogglePill, ToggleSwitch } from '$lib/components/ui/toggle-pill';
import CronEditor from '$lib/components/cron-editor.svelte';
import TimezoneSelector from '$lib/components/TimezoneSelector.svelte';
import { Eye, Bell, Database, Calendar, ShieldCheck, FileText, AlertTriangle, HelpCircle, Globe } from 'lucide-svelte';
import { appSettings, type DateFormat, type DownloadFormat } from '$lib/stores/settings';
import { Eye, Bell, Database, Calendar, ShieldCheck, FileText, AlertTriangle, HelpCircle, Globe, Activity, Clock } from 'lucide-svelte';
import { appSettings, type DateFormat, type DownloadFormat, type EventCollectionMode } from '$lib/stores/settings';
import { canAccess, authStore } from '$lib/stores/auth';
import { toast } from 'svelte-sonner';
import ThemeSelector from '$lib/components/ThemeSelector.svelte';
@@ -32,6 +32,9 @@
let eventCleanupEnabled = $derived($appSettings.eventCleanupEnabled);
let logBufferSizeKb = $derived($appSettings.logBufferSizeKb);
let defaultTimezone = $derived($appSettings.defaultTimezone);
let eventCollectionMode = $derived($appSettings.eventCollectionMode);
let eventPollInterval = $derived($appSettings.eventPollInterval);
let metricsCollectionInterval = $derived($appSettings.metricsCollectionInterval);
const dateFormatOptions: { value: DateFormat; label: string; example: string }[] = [
{ value: 'DD.MM.YYYY', label: 'DD.MM.YYYY', example: '31.12.2024' },
@@ -93,6 +96,27 @@
appSettings.setLogBufferSizeKb(value);
toast.success('Log buffer size updated');
}
function handleEventCollectionModeChange(value: string | undefined) {
if (value === 'stream' || value === 'poll') {
appSettings.setEventCollectionMode(value);
toast.success(`Event collection mode: ${value}`);
}
}
function handleEventPollIntervalChange(selected: { value: number } | undefined) {
if (selected?.value) {
appSettings.setEventPollInterval(selected.value);
toast.success(`Event poll interval: ${selected.value / 1000}s`);
}
}
function handleMetricsIntervalChange(selected: { value: number } | undefined) {
if (selected?.value) {
appSettings.setMetricsCollectionInterval(selected.value);
toast.success(`Metrics interval: ${selected.value / 1000}s`);
}
}
</script>
<div class="flex-1 min-h-0 overflow-y-auto">
@@ -362,7 +386,107 @@
</Card.Title>
</Card.Header>
<Card.Content class="space-y-4">
<div class="space-y-1">
<div class="space-y-3">
<div>
<div class="flex items-center gap-2">
<Label>Activity event collection mode</Label>
<Tooltip.Root>
<Tooltip.Trigger>
<HelpCircle class="w-3.5 h-3.5 text-muted-foreground" />
</Tooltip.Trigger>
<Tooltip.Content class="w-80">
<p class="text-xs">
<strong>Stream:</strong> Continuous event stream from Docker, instant notifications, higher CPU usage<br />
<strong>Poll:</strong> Periodic checks for new events, slight notification delay, lower CPU usage
</p>
</Tooltip.Content>
</Tooltip.Root>
</div>
<div class="flex items-center gap-4 mt-2">
<label class="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="eventCollectionMode"
value="stream"
checked={(eventCollectionMode || 'stream') === 'stream'}
onchange={() => handleEventCollectionModeChange('stream')}
disabled={!$canAccess('settings', 'edit')}
class="accent-primary w-4 h-4"
/>
<Activity class="w-3.5 h-3.5" />
<span class="text-sm">Stream</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="eventCollectionMode"
value="poll"
checked={(eventCollectionMode || 'stream') === 'poll'}
onchange={() => handleEventCollectionModeChange('poll')}
disabled={!$canAccess('settings', 'edit')}
class="accent-primary w-4 h-4"
/>
<Clock class="w-3.5 h-3.5" />
<span class="text-sm">Poll</span>
</label>
<span class="text-xs text-muted-foreground {(eventCollectionMode || 'stream') === 'poll' ? '' : 'invisible'}">every</span>
<Select.Root
type="single"
value={String(eventPollInterval || 60000)}
onValueChange={(v) => v && handleEventPollIntervalChange({ value: parseInt(v) })}
disabled={!$canAccess('settings', 'edit') || (eventCollectionMode || 'stream') !== 'poll'}
>
<Select.Trigger class="w-24 h-8 {(eventCollectionMode || 'stream') === 'poll' ? '' : 'invisible'}">
{(eventPollInterval || 60000) === 30000 ? '30s' : (eventPollInterval || 60000) === 60000 ? '60s' : (eventPollInterval || 60000) === 120000 ? '120s' : '300s'}
</Select.Trigger>
<Select.Content>
<Select.Item value="30000">30s</Select.Item>
<Select.Item value="60000">60s</Select.Item>
<Select.Item value="120000">120s</Select.Item>
<Select.Item value="300000">300s</Select.Item>
</Select.Content>
</Select.Root>
</div>
</div>
</div>
<div class="space-y-1 pt-2 border-t">
<div class="flex items-center gap-2">
<Label for="metrics-interval">Metrics collection interval</Label>
<Tooltip.Root>
<Tooltip.Trigger>
<HelpCircle class="w-3.5 h-3.5 text-muted-foreground" />
</Tooltip.Trigger>
<Tooltip.Content class="w-80">
<p class="text-xs">
How often to collect CPU/memory metrics from running containers. Lower intervals
provide more frequent updates but increase CPU usage.
</p>
</Tooltip.Content>
</Tooltip.Root>
</div>
<div class="flex items-center gap-2 mt-2">
<Select.Root
type="single"
value={String(metricsCollectionInterval || 30000)}
onValueChange={(v) => v && handleMetricsIntervalChange({ value: parseInt(v) })}
disabled={!$canAccess('settings', 'edit')}
>
<Select.Trigger class="w-24 h-8">
{(metricsCollectionInterval || 30000) === 10000 ? '10s' : (metricsCollectionInterval || 30000) === 30000 ? '30s' : (metricsCollectionInterval || 30000) === 60000 ? '60s' : '120s'}
</Select.Trigger>
<Select.Content>
<Select.Item value="10000">10s</Select.Item>
<Select.Item value="30000">30s</Select.Item>
<Select.Item value="60000">60s</Select.Item>
<Select.Item value="120000">120s</Select.Item>
</Select.Content>
</Select.Root>
</div>
</div>
<div class="space-y-1 pt-2 border-t">
<div class="flex items-center gap-3">
<Label for="schedule-retention">Schedule execution cleanup</Label>
<TogglePill