This commit is contained in:
jarek committed 2026-02-16 12:59:42 +01:00
1 parent 3a7b856047
commit ae3aea2296
6 files changed
+233 -27

No files matched your search

@@ -247,6 +247,12 @@ async function checkEnvDiskSpace(env: { id: number; name: string; collectMetrics
return;
}
// Check if disk warnings are enabled for this environment
const diskWarningEnabled = (await getEnvSetting('disk_warning_enabled', env.id)) ?? true;
if (!diskWarningEnabled) {
return;
}
// Check if we're in cooldown for this environment
const lastWarningTime = lastDiskWarning.get(env.id);
if (lastWarningTime && Date.now() - lastWarningTime < DISK_WARNING_COOLDOWN) {
@@ -292,41 +298,48 @@ async function checkEnvDiskSpace(env: { id: number; name: string; collectMetrics
}
}
// If we found total disk space, calculate percentage
if (dataSpaceTotal > 0) {
diskPercentUsed = (totalUsed / dataSpaceTotal) * 100;
} else {
// Fallback: just report absolute usage if we can't determine percentage
const GB = 1024 * 1024 * 1024;
if (totalUsed > 50 * GB) {
// Determine warning mode
const diskWarningMode = (await getEnvSetting('disk_warning_mode', env.id)) ?? 'percentage';
const GB = 1024 * 1024 * 1024;
if (diskWarningMode === 'absolute') {
// Absolute mode: warn when usage exceeds GB threshold
const thresholdGb = (await getEnvSetting('disk_warning_threshold_gb', env.id)) ?? 50;
if (totalUsed > thresholdGb * GB) {
send({
type: 'disk_warning',
envId: env.id,
envName: env.name,
message: `Environment "${env.name}" is using ${formatSize(totalUsed)} of Docker disk space`
message: `Environment "${env.name}" is using ${formatSize(totalUsed)} of Docker disk space (threshold: ${thresholdGb} GB)`
});
lastDiskWarning.set(env.id, Date.now());
}
return;
}
} else {
// Percentage mode: need total disk space
if (dataSpaceTotal > 0) {
diskPercentUsed = (totalUsed / dataSpaceTotal) * 100;
} else {
// Can't determine percentage without total space — skip
return;
}
// Check against threshold
const threshold =
(await getEnvSetting('disk_warning_threshold', env.id)) || DEFAULT_DISK_THRESHOLD;
if (diskPercentUsed >= threshold) {
console.log(
`[MetricsSubprocess] Docker disk usage for ${env.name}: ${diskPercentUsed.toFixed(1)}% (threshold: ${threshold}%)`
);
const threshold =
(await getEnvSetting('disk_warning_threshold', env.id)) || DEFAULT_DISK_THRESHOLD;
if (diskPercentUsed >= threshold) {
console.log(
`[MetricsSubprocess] Docker disk usage for ${env.name}: ${diskPercentUsed.toFixed(1)}% (threshold: ${threshold}%)`
);
send({
type: 'disk_warning',
envId: env.id,
envName: env.name,
message: `Environment "${env.name}" Docker disk usage is at ${diskPercentUsed.toFixed(1)}% (${formatSize(totalUsed)} used)`,
diskPercent: diskPercentUsed
});
send({
type: 'disk_warning',
envId: env.id,
envName: env.name,
message: `Environment "${env.name}" Docker disk usage is at ${diskPercentUsed.toFixed(1)}% (${formatSize(totalUsed)} used)`,
diskPercent: diskPercentUsed
});
lastDiskWarning.set(env.id, Date.now());
lastDiskWarning.set(env.id, Date.now());
}
}
} catch (error) {
// Skip this environment if it fails
+5
View File
@@ -62,6 +62,11 @@ export const PUT: RequestHandler = async ({ request, cookies }) => {
}
}
// Enforce minimum session timeout of 1 hour
if (data.sessionTimeout !== undefined) {
data.sessionTimeout = Math.max(3600, Math.min(604800, parseInt(data.sessionTimeout) || 86400));
}
const settings = await updateAuthSettings(data);
return json(settings);
} catch (error) {
@@ -0,0 +1,64 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { authorize } from '$lib/server/authorize';
import { getEnvironment, getEnvSetting, setEnvSetting } from '$lib/server/db';
export const GET: RequestHandler = async ({ params, cookies }) => {
const auth = await authorize(cookies);
if (auth.authEnabled && !(await auth.can('environments', 'view'))) {
return json({ error: 'Permission denied' }, { status: 403 });
}
try {
const id = parseInt(params.id);
const env = await getEnvironment(id);
if (!env) {
return json({ error: 'Environment not found' }, { status: 404 });
}
const enabled = (await getEnvSetting('disk_warning_enabled', id)) ?? true;
const mode = (await getEnvSetting('disk_warning_mode', id)) ?? 'percentage';
const threshold = (await getEnvSetting('disk_warning_threshold', id)) ?? 80;
const thresholdGb = (await getEnvSetting('disk_warning_threshold_gb', id)) ?? 50;
return json({ enabled, mode, threshold, thresholdGb });
} catch (error) {
console.error('Failed to get disk warning settings:', error);
return json({ error: 'Failed to get disk warning settings' }, { status: 500 });
}
};
export const POST: RequestHandler = async ({ params, request, cookies }) => {
const auth = await authorize(cookies);
if (auth.authEnabled && !(await auth.can('environments', 'edit'))) {
return json({ error: 'Permission denied' }, { status: 403 });
}
try {
const id = parseInt(params.id);
const env = await getEnvironment(id);
if (!env) {
return json({ error: 'Environment not found' }, { status: 404 });
}
const data = await request.json();
if (typeof data.enabled === 'boolean') {
await setEnvSetting('disk_warning_enabled', data.enabled, id);
}
if (data.mode === 'percentage' || data.mode === 'absolute') {
await setEnvSetting('disk_warning_mode', data.mode, id);
}
if (typeof data.threshold === 'number' && data.threshold >= 1 && data.threshold <= 100) {
await setEnvSetting('disk_warning_threshold', data.threshold, id);
}
if (typeof data.thresholdGb === 'number' && data.thresholdGb >= 1) {
await setEnvSetting('disk_warning_threshold_gb', data.thresholdGb, id);
}
return json({ success: true });
} catch (error) {
console.error('Failed to save disk warning settings:', error);
return json({ error: 'Failed to save disk warning settings' }, { status: 500 });
}
};
+5 -1
View File
@@ -264,7 +264,11 @@
value={sessionTimeout}
min={3600}
max={604800}
onchange={(e) => (sessionTimeout = parseInt(e.currentTarget.value))}
onchange={(e) => {
const val = parseInt(e.currentTarget.value);
sessionTimeout = Math.max(3600, Math.min(604800, isNaN(val) ? 86400 : val));
e.currentTarget.value = String(sessionTimeout);
}}
class="w-32"
disabled={!$canAccess('settings', 'edit')}
/>
@@ -262,6 +262,10 @@
let formCollectActivity = $state(true);
let formCollectMetrics = $state(true);
let formHighlightChanges = $state(true);
let formDiskWarningEnabled = $state(true);
let formDiskWarningMode = $state<'percentage' | 'absolute'>('percentage');
let formDiskWarningThreshold = $state(80);
let formDiskWarningThresholdGb = $state(50);
let formConnectionType = $state<ConnectionType>('socket');
let formHawserToken = $state('');
let formLabels = $state<string[]>([]);
@@ -445,6 +449,7 @@
loadUpdateCheckSettings(environment.id);
loadImagePruneSettings(environment.id);
loadTimezone(environment.id);
loadDiskWarningSettings(environment.id);
// Load Hawser token if edge mode
if (formConnectionType === 'hawser-edge') {
loadHawserToken(environment.id);
@@ -463,6 +468,10 @@
formCollectActivity = true;
formCollectMetrics = true;
formHighlightChanges = true;
formDiskWarningEnabled = true;
formDiskWarningMode = 'percentage';
formDiskWarningThreshold = 80;
formDiskWarningThresholdGb = 50;
formConnectionType = 'socket';
formHawserToken = '';
formLabels = [];
@@ -701,6 +710,7 @@
// Save timezone if not default
if (newEnv?.id) {
await saveTimezone(newEnv.id);
await saveDiskWarningSettings(newEnv.id);
}
onSaved();
onClose();
@@ -774,6 +784,7 @@
await saveUpdateCheckSettings(environment.id);
await saveImagePruneSettings(environment.id);
await saveTimezone(environment.id);
await saveDiskWarningSettings(environment.id);
toast.success(`Updated environment: ${formName}`);
onSaved();
onClose();
@@ -788,6 +799,39 @@
}
}
// === Disk Warning Functions ===
async function loadDiskWarningSettings(envId: number) {
try {
const response = await fetch(`/api/environments/${envId}/disk-warning`);
if (response.ok) {
const data = await response.json();
formDiskWarningEnabled = data.enabled ?? true;
formDiskWarningMode = data.mode ?? 'percentage';
formDiskWarningThreshold = data.threshold ?? 80;
formDiskWarningThresholdGb = data.thresholdGb ?? 50;
}
} catch (error) {
console.error('Failed to load disk warning settings:', error);
}
}
async function saveDiskWarningSettings(envId: number) {
try {
await fetch(`/api/environments/${envId}/disk-warning`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
enabled: formDiskWarningEnabled,
mode: formDiskWarningMode,
threshold: formDiskWarningThreshold,
thresholdGb: formDiskWarningThresholdGb
})
});
} catch (error) {
console.error('Failed to save disk warning settings:', error);
}
}
// === Timezone Functions ===
async function loadTimezone(envId: number) {
try {
@@ -2063,6 +2107,10 @@
bind:collectActivity={formCollectActivity}
bind:collectMetrics={formCollectMetrics}
bind:highlightChanges={formHighlightChanges}
bind:diskWarningEnabled={formDiskWarningEnabled}
bind:diskWarningMode={formDiskWarningMode}
bind:diskWarningThreshold={formDiskWarningThreshold}
bind:diskWarningThresholdGb={formDiskWarningThresholdGb}
/>
</Tabs.Content>
@@ -1,17 +1,28 @@
<script lang="ts">
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { TogglePill } from '$lib/components/ui/toggle-pill';
import * as Select from '$lib/components/ui/select';
import { Percent, HardDrive } from 'lucide-svelte';
interface Props {
collectActivity: boolean;
collectMetrics: boolean;
highlightChanges: boolean;
diskWarningEnabled: boolean;
diskWarningMode: 'percentage' | 'absolute';
diskWarningThreshold: number;
diskWarningThresholdGb: number;
}
let {
collectActivity = $bindable(),
collectMetrics = $bindable(),
highlightChanges = $bindable()
highlightChanges = $bindable(),
diskWarningEnabled = $bindable(),
diskWarningMode = $bindable(),
diskWarningThreshold = $bindable(),
diskWarningThresholdGb = $bindable()
}: Props = $props();
</script>
@@ -36,3 +47,64 @@
</div>
<TogglePill bind:checked={highlightChanges} />
</div>
<div class="border-t pt-4 mt-2 space-y-3">
<div class="flex items-start gap-3">
<div class="flex-1">
<Label>Disk space warnings</Label>
<p class="text-xs text-muted-foreground">Send notifications when Docker disk usage exceeds the threshold</p>
</div>
<TogglePill bind:checked={diskWarningEnabled} />
</div>
{#if diskWarningEnabled}
<div class="flex items-center gap-3">
<Select.Root type="single" value={diskWarningMode} onValueChange={(v) => { if (v) diskWarningMode = v as 'percentage' | 'absolute'; }}>
<Select.Trigger class="w-48">
<div class="flex items-center gap-2">
{#if diskWarningMode === 'percentage'}
<Percent class="w-3.5 h-3.5" />
<span>Percentage</span>
{:else}
<HardDrive class="w-3.5 h-3.5" />
<span>Absolute (GB)</span>
{/if}
</div>
</Select.Trigger>
<Select.Content>
<Select.Item value="percentage">
<div class="flex items-center gap-2">
<Percent class="w-3.5 h-3.5" />
Percentage
</div>
</Select.Item>
<Select.Item value="absolute">
<div class="flex items-center gap-2">
<HardDrive class="w-3.5 h-3.5" />
Absolute (GB)
</div>
</Select.Item>
</Select.Content>
</Select.Root>
{#if diskWarningMode === 'percentage'}
<Input
type="number"
min={1}
max={100}
bind:value={diskWarningThreshold}
class="w-24"
/>
<span class="text-sm text-muted-foreground">%</span>
{:else}
<Input
type="number"
min={1}
bind:value={diskWarningThresholdGb}
class="w-24"
/>
<span class="text-sm text-muted-foreground">GB</span>
{/if}
</div>
{/if}
</div>