mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
Backend: - GET /services/:id/metrics with real Docker container stats - DELETE /git/providers/:id and GET repo branches endpoint Frontend: - Service detail shows real telemetry or honest empty states - Live status distinguishes idle/polling/reconnecting - Git providers section in Settings; repo+branch pickers in Add Service - Real logo, compact Projects header, canvas fitView fix - index.css deduplicated (1725 -> 1384 lines) - Fixed smoke tests, lint errors, ToastProvider/context unification Repo hygiene: - Added LICENSE (MIT), CONTRIBUTING.md, SECURITY.md; rewrote README - Untracked .env.prod and data/letsencrypt secrets; fixed .gitignore - Removed legacy root src/internal/cmd and stale configs/docs/artifacts - Moved guides and design references under docs/ Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
58 lines
1.4 KiB
TypeScript
58 lines
1.4 KiB
TypeScript
export function formatRelative(iso?: string): string {
|
|
if (!iso) {
|
|
return 'n/a';
|
|
}
|
|
|
|
const timestamp = Date.parse(iso);
|
|
if (Number.isNaN(timestamp)) {
|
|
return 'n/a';
|
|
}
|
|
|
|
const diffMs = Date.now() - timestamp;
|
|
const minute = 60_000;
|
|
const hour = 60 * minute;
|
|
const day = 24 * hour;
|
|
|
|
if (diffMs < minute) {
|
|
return 'just now';
|
|
}
|
|
if (diffMs < hour) {
|
|
return `${Math.floor(diffMs / minute)}m ago`;
|
|
}
|
|
if (diffMs < day) {
|
|
return `${Math.floor(diffMs / hour)}h ago`;
|
|
}
|
|
return `${Math.floor(diffMs / day)}d ago`;
|
|
}
|
|
|
|
export function formatDate(iso?: string): string {
|
|
if (!iso) {
|
|
return 'n/a';
|
|
}
|
|
const dt = new Date(iso);
|
|
if (Number.isNaN(dt.getTime())) {
|
|
return 'n/a';
|
|
}
|
|
return dt.toLocaleString();
|
|
}
|
|
|
|
export function formatBytes(bytes: number): string {
|
|
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
return '0 B';
|
|
}
|
|
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
|
|
const index = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
const value = bytes / Math.pow(1024, index);
|
|
return `${value >= 100 ? Math.round(value) : value.toFixed(1)} ${units[index]}`;
|
|
}
|
|
|
|
export function seededMetric(seed: string, min: number, max: number): number {
|
|
let hash = 0;
|
|
for (let idx = 0; idx < seed.length; idx += 1) {
|
|
hash = (hash * 31 + seed.charCodeAt(idx)) & 0xffffffff;
|
|
}
|
|
|
|
const ratio = Math.abs(hash) % 10_000 / 10_000;
|
|
return Math.round(min + (max - min) * ratio);
|
|
}
|