Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a37e101bba |
No files matched your search
@@ -30,9 +30,8 @@ needed to restore encrypted integration credentials.
|
|||||||
Select a Homarr backup ZIP. The browser previews entity counts, board names, and required database migrations before
|
Select a Homarr backup ZIP. The browser previews entity counts, board names, and required database migrations before
|
||||||
the archive is uploaded.
|
the archive is uploaded.
|
||||||
|
|
||||||
Restoring replaces the current SQLite database and cannot be undone. It also invalidates the current database session.
|
Restoring replaces the current SQLite database and cannot be undone. After a successful restore, the Next.js process
|
||||||
After a successful management restore, Homarr waits for the restarted server and sends the administrator to sign in
|
restarts and the page reloads when Homarr is ready.
|
||||||
again. The first-run onboarding restore keeps its existing board destination.
|
|
||||||
|
|
||||||
A backup can be restored on another Homarr instance. When its `SECRET_ENCRYPTION_KEY` differs, Homarr re-encrypts stored
|
A backup can be restored on another Homarr instance. When its `SECRET_ENCRYPTION_KEY` differs, Homarr re-encrypts stored
|
||||||
integration secrets for the target instance. Version metadata is used to apply compatible migrations.
|
integration secrets for the target instance. Version metadata is used to apply compatible migrations.
|
||||||
|
|||||||
@@ -204,12 +204,8 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat
|
|||||||
}
|
}
|
||||||
|
|
||||||
onRestoreComplete?.();
|
onRestoreComplete?.();
|
||||||
let destination: string | null = null;
|
const destination =
|
||||||
if (variant === "standalone" && homeBoardName) {
|
variant === "standalone" && homeBoardName ? `/boards/${encodeURIComponent(homeBoardName)}` : null;
|
||||||
destination = `/boards/${encodeURIComponent(homeBoardName)}`;
|
|
||||||
} else if (variant === "card") {
|
|
||||||
destination = "/auth/login";
|
|
||||||
}
|
|
||||||
startReadinessCheck(restartAfterMs, destination);
|
startReadinessCheck(restartAfterMs, destination);
|
||||||
}, [file, onRestoreComplete, startReadinessCheck, t, variant]);
|
}, [file, onRestoreComplete, startReadinessCheck, t, variant]);
|
||||||
|
|
||||||
@@ -321,7 +317,6 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat
|
|||||||
<RestoreProgressPanel
|
<RestoreProgressPanel
|
||||||
active
|
active
|
||||||
status={restoreStatus}
|
status={restoreStatus}
|
||||||
reloginRequired={variant === "card"}
|
|
||||||
onRetry={() => startReadinessCheck(0)}
|
onRetry={() => startReadinessCheck(0)}
|
||||||
onReload={() => window.location.reload()}
|
onReload={() => window.location.reload()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,18 +11,11 @@ export type RestoreProgressStatus = "restoring" | "restarting" | "timedOut";
|
|||||||
interface RestoreProgressPanelProps {
|
interface RestoreProgressPanelProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
status: RestoreProgressStatus;
|
status: RestoreProgressStatus;
|
||||||
reloginRequired?: boolean;
|
|
||||||
onRetry: () => void;
|
onRetry: () => void;
|
||||||
onReload: () => void;
|
onReload: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RestoreProgressPanel = ({
|
export const RestoreProgressPanel = ({ active, status, onRetry, onReload }: RestoreProgressPanelProps) => {
|
||||||
active,
|
|
||||||
status,
|
|
||||||
reloginRequired = false,
|
|
||||||
onRetry,
|
|
||||||
onReload,
|
|
||||||
}: RestoreProgressPanelProps) => {
|
|
||||||
const t = useI18n("management.page.tool.backup.restore");
|
const t = useI18n("management.page.tool.backup.restore");
|
||||||
const tCommon = useI18n("common");
|
const tCommon = useI18n("common");
|
||||||
const reduceMotion = useReducedMotion();
|
const reduceMotion = useReducedMotion();
|
||||||
@@ -70,12 +63,6 @@ export const RestoreProgressPanel = ({
|
|||||||
</div>
|
</div>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{reloginRequired && !isRestoring ? (
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{t("progress.relogin")}
|
|
||||||
</Text>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{isTimedOut ? (
|
{isTimedOut ? (
|
||||||
<Group>
|
<Group>
|
||||||
<Button variant="light" onClick={onRetry} leftSection={<IconRefresh size={16} />}>
|
<Button variant="light" onClick={onRetry} leftSection={<IconRefresh size={16} />}>
|
||||||
|
|||||||
@@ -481,13 +481,14 @@ export function calculateCpuUsage(stats: ContainerStats): number {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const numberOfCpus = stats.cpu_stats.online_cpus;
|
const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - (stats.precpu_stats?.cpu_usage?.total_usage ?? 0);
|
||||||
const usage = stats.cpu_stats.system_cpu_usage;
|
const systemDelta = (stats.cpu_stats.system_cpu_usage ?? 0) - (stats.precpu_stats?.system_cpu_usage ?? 0);
|
||||||
if (!usage || usage === 0) {
|
|
||||||
|
if (systemDelta <= 0 || cpuDelta < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (stats.cpu_stats.cpu_usage.total_usage / usage) * numberOfCpus * 100;
|
return (cpuDelta / systemDelta) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateMemoryUsage(stats: ContainerStats): number {
|
export function calculateMemoryUsage(stats: ContainerStats): number {
|
||||||
|
|||||||
@@ -72,16 +72,16 @@ describe("calculateCpuUsage", () => {
|
|||||||
const stats = createStats({
|
const stats = createStats({
|
||||||
cpu_stats: { online_cpus: 4, cpu_usage: { total_usage: 2000 }, system_cpu_usage: 10000 },
|
cpu_stats: { online_cpus: 4, cpu_usage: { total_usage: 2000 }, system_cpu_usage: 10000 },
|
||||||
});
|
});
|
||||||
// (2000 / 10000) * 4 * 100 = 80
|
// (2000 / 10000) * 100 = 20
|
||||||
expect(calculateCpuUsage(stats)).toBe(80);
|
expect(calculateCpuUsage(stats)).toBe(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle fractional CPU usage", () => {
|
test("should handle fractional CPU usage", () => {
|
||||||
const stats = createStats({
|
const stats = createStats({
|
||||||
cpu_stats: { online_cpus: 2, cpu_usage: { total_usage: 500 }, system_cpu_usage: 100000 },
|
cpu_stats: { online_cpus: 2, cpu_usage: { total_usage: 500 }, system_cpu_usage: 100000 },
|
||||||
});
|
});
|
||||||
// (500 / 100000) * 2 * 100 = 1
|
// (500 / 100000) * 100 = 0.5
|
||||||
expect(calculateCpuUsage(stats)).toBe(1);
|
expect(calculateCpuUsage(stats)).toBe(0.5);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6456,8 +6456,7 @@
|
|||||||
},
|
},
|
||||||
"progress": {
|
"progress": {
|
||||||
"title": "Restoring database...",
|
"title": "Restoring database...",
|
||||||
"restarting": "Restarting server",
|
"restarting": "Restarting server"
|
||||||
"relogin": "Restoring the database invalidated your current session. Homarr will send you to sign in when the server is ready."
|
|
||||||
},
|
},
|
||||||
"timeout": {
|
"timeout": {
|
||||||
"title": "Server did not come back online",
|
"title": "Server did not come back online",
|
||||||
|
|||||||
Reference in new issue
Block a user