1.0.18
This commit is contained in:
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
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user