feat: refine project container detail panels
This commit is contained in:
1 parent
2815de9e14
commit
cb77dab219
12 files changed
+118
-441
No files matched your search
+18
@@ -2,6 +2,7 @@ import SearchField from "@/components/SearchField";
|
||||
import {
|
||||
Box,
|
||||
ChevronDown,
|
||||
CloudSyncIcon,
|
||||
Database,
|
||||
Loader2,
|
||||
Plus,
|
||||
@@ -163,6 +164,23 @@ export default function EnvironmentContainersToolbar({
|
||||
<Database size={14} style={{ color: "var(--accent-green)" }} />
|
||||
Deploy Database
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className="btn btn-ghost"
|
||||
onClick={handleDeployDatabase}
|
||||
style={{
|
||||
justifyContent: "flex-start",
|
||||
fontSize: 12,
|
||||
color: "var(--text-secondary)",
|
||||
}}
|
||||
>
|
||||
<CloudSyncIcon
|
||||
size={14}
|
||||
style={{ color: "var(--accent-red)" }}
|
||||
/>
|
||||
Import form Sync
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
+3
-1
@@ -12,7 +12,9 @@ interface AdvancedSummaryCardProps {
|
||||
item: AdvancedSummary;
|
||||
}
|
||||
|
||||
export default function AdvancedSummaryCard({ item }: AdvancedSummaryCardProps) {
|
||||
export default function AdvancedSummaryCard({
|
||||
item,
|
||||
}: AdvancedSummaryCardProps) {
|
||||
return (
|
||||
<section
|
||||
className="card"
|
||||
|
||||
+6
-93
@@ -1,15 +1,11 @@
|
||||
import {
|
||||
AlertTriangle,
|
||||
Clock3,
|
||||
Cpu,
|
||||
Database,
|
||||
Loader2,
|
||||
RefreshCcw,
|
||||
Settings2,
|
||||
ShieldCheck,
|
||||
ToggleLeft,
|
||||
ToggleRight,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import type {
|
||||
AdvancedAuditEvent,
|
||||
@@ -231,7 +227,7 @@ export default function AdvancedTabPanel({
|
||||
</PanelShell>
|
||||
</div>
|
||||
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
@@ -255,13 +251,13 @@ export default function AdvancedTabPanel({
|
||||
))}
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"minmax(min(620px, 100%), 1.2fr) minmax(min(360px, 100%), 0.8fr)",
|
||||
"minmax(min(620px, 100%), 1.2fr) minmax(min(360px, 100%), 1.2fr)",
|
||||
gap: 12,
|
||||
alignItems: "start",
|
||||
}}
|
||||
@@ -271,7 +267,7 @@ export default function AdvancedTabPanel({
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"repeat(auto-fit, minmax(min(180px, 100%), 1fr))",
|
||||
"repeat(auto-fit, minmax(min(280px, 100%), 1fr))",
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
@@ -355,90 +351,7 @@ export default function AdvancedTabPanel({
|
||||
))}
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div>
|
||||
|
||||
<PanelShell title="Danger Zone">
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto minmax(0, 1fr) auto",
|
||||
gap: 12,
|
||||
alignItems: "center",
|
||||
border: "1px solid rgba(239,68,68,0.35)",
|
||||
borderRadius: 7,
|
||||
background: "rgba(239,68,68,0.06)",
|
||||
padding: 13,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 10,
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "var(--text-danger)",
|
||||
background: "rgba(239,68,68,0.1)",
|
||||
}}
|
||||
>
|
||||
<AlertTriangle size={20} />
|
||||
</span>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<p
|
||||
style={{
|
||||
margin: 0,
|
||||
color: "var(--text-primary)",
|
||||
fontSize: 13,
|
||||
fontWeight: 800,
|
||||
}}
|
||||
>
|
||||
Destructive actions require confirmation
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
marginTop: 4,
|
||||
color: "var(--text-muted)",
|
||||
fontSize: 12,
|
||||
lineHeight: 1.45,
|
||||
}}
|
||||
>
|
||||
Remove, reset, and cleanup operations stay behind explicit
|
||||
confirmation dialogs before they execute.
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost"
|
||||
disabled={actionBusy}
|
||||
aria-busy={activeAction === "rebuild"}
|
||||
onClick={onReset}
|
||||
>
|
||||
{activeAction === "rebuild" ? (
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
) : (
|
||||
<Settings2 size={14} />
|
||||
)}
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger"
|
||||
disabled={actionBusy}
|
||||
aria-busy={activeAction === "remove"}
|
||||
onClick={onRemove}
|
||||
>
|
||||
{activeAction === "remove" ? (
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
) : (
|
||||
<Trash2 size={14} />
|
||||
)}
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div> */}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
-33
@@ -9,7 +9,6 @@ import {
|
||||
ExternalLink,
|
||||
GitBranch,
|
||||
GitCommit,
|
||||
Package,
|
||||
Rocket,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
@@ -242,7 +241,6 @@ export default function DeploymentsTabPanel({
|
||||
<th>Branch</th>
|
||||
<th>Duration</th>
|
||||
<th>Deployed At</th>
|
||||
<th style={{ textAlign: "right" }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -269,16 +267,6 @@ export default function DeploymentsTabPanel({
|
||||
</td>
|
||||
<td>{deployment.duration}</td>
|
||||
<td>{deployment.deployedAt}</td>
|
||||
<td style={{ textAlign: "right" }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost"
|
||||
style={{ padding: "4px 7px" }}
|
||||
aria-label={`Open deployment ${deployment.version}`}
|
||||
>
|
||||
<ExternalLink size={13} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -356,27 +344,6 @@ export default function DeploymentsTabPanel({
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div>
|
||||
|
||||
<PanelShell title="Release Notes">
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto minmax(0, 1fr)",
|
||||
gap: 10,
|
||||
alignItems: "start",
|
||||
color: "var(--text-secondary)",
|
||||
fontSize: 12,
|
||||
lineHeight: 1.6,
|
||||
}}
|
||||
>
|
||||
<Package size={16} style={{ color: "var(--accent-blue)" }} />
|
||||
<p style={{ margin: 0 }}>
|
||||
Deployment history is currently derived from the real container
|
||||
record. A permanent deployment history API is not available yet, so
|
||||
only the current deployment snapshot is shown here.
|
||||
</p>
|
||||
</div>
|
||||
</PanelShell>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+5
-246
@@ -1,14 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import TablePagination from "@/components/TablePagination";
|
||||
import { useTablePagination } from "@/lib/use-table-pagination";
|
||||
import {
|
||||
CheckCircle2,
|
||||
ClipboardCheck,
|
||||
KeyRound,
|
||||
Loader2,
|
||||
LockKeyhole,
|
||||
RotateCcw,
|
||||
Save,
|
||||
ShieldAlert,
|
||||
@@ -178,7 +175,7 @@ export default function EnvironmentTabPanel({
|
||||
|
||||
return (
|
||||
<section style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
@@ -189,7 +186,7 @@ export default function EnvironmentTabPanel({
|
||||
{environment.summaries.map((summary) => (
|
||||
<EnvironmentSummaryCard key={summary.label} item={summary} />
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<PanelShell title="Project .env Editor">
|
||||
<div
|
||||
@@ -215,7 +212,7 @@ export default function EnvironmentTabPanel({
|
||||
spellCheck={false}
|
||||
style={{
|
||||
width: "100%",
|
||||
minHeight: 260,
|
||||
minHeight: 480,
|
||||
resize: "vertical",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: 7,
|
||||
@@ -255,7 +252,8 @@ export default function EnvironmentTabPanel({
|
||||
});
|
||||
setEditorNotice({
|
||||
tone: "info",
|
||||
message: "Editor content has been reset to the last loaded .env.",
|
||||
message:
|
||||
"Editor content has been reset to the last loaded .env.",
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -444,245 +442,6 @@ export default function EnvironmentTabPanel({
|
||||
</div>
|
||||
</div>
|
||||
</PanelShell>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"minmax(min(660px, 100%), 1.35fr) minmax(min(340px, 100%), 0.65fr)",
|
||||
gap: 12,
|
||||
alignItems: "start",
|
||||
}}
|
||||
>
|
||||
<PanelShell title={`Variables (${environment.variables.length})`}>
|
||||
{environment.variables.length > 0 ? (
|
||||
<>
|
||||
<div style={{ overflowX: "auto" }}>
|
||||
<table className="data-table" style={{ minWidth: 760 }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
<th>Scope</th>
|
||||
<th>Source</th>
|
||||
<th>Required</th>
|
||||
<th>Sensitive</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{pagination.paginatedItems.map((variable) => (
|
||||
<tr key={variable.id}>
|
||||
<td style={{ fontFamily: "var(--font--code)" }}>
|
||||
<KeyRound size={13} />
|
||||
{variable.key}
|
||||
</td>
|
||||
<td style={{ fontFamily: "var(--font--code)" }}>
|
||||
{variable.value}
|
||||
</td>
|
||||
<td>{variable.scope}</td>
|
||||
<td>{variable.source}</td>
|
||||
<td>
|
||||
<span
|
||||
className={`ui-badge ${
|
||||
variable.required ? "badge-warning" : ""
|
||||
}`}
|
||||
>
|
||||
{variable.required ? "Required" : "Optional"}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className={`ui-badge ${
|
||||
variable.sensitive ? "badge-online" : ""
|
||||
}`}
|
||||
>
|
||||
{variable.sensitive ? "Masked" : "Plain"}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<TablePagination
|
||||
currentPage={pagination.currentPage}
|
||||
totalPages={pagination.totalPages}
|
||||
totalItems={pagination.totalItems}
|
||||
startItem={pagination.startItem}
|
||||
endItem={pagination.endItem}
|
||||
itemLabel="variables"
|
||||
onPageChange={pagination.setCurrentPage}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
minHeight: 180,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 10,
|
||||
textAlign: "center",
|
||||
color: "var(--text-muted)",
|
||||
border: "1px dashed var(--border)",
|
||||
borderRadius: 7,
|
||||
background: "var(--bg-input)",
|
||||
padding: 18,
|
||||
}}
|
||||
>
|
||||
<KeyRound size={22} style={{ color: "var(--accent-blue)" }} />
|
||||
<div>
|
||||
<p
|
||||
style={{
|
||||
margin: 0,
|
||||
color: "var(--text-primary)",
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
No environment variables detected
|
||||
</p>
|
||||
<p style={{ marginTop: 4, fontSize: 12 }}>
|
||||
This container has no stored deploy variables or runtime
|
||||
inspect variables available.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PanelShell>
|
||||
|
||||
<PanelShell title="Validation">
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
{environment.checks.map((check) => (
|
||||
<div
|
||||
key={check.label}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "auto minmax(0, 1fr)",
|
||||
gap: 10,
|
||||
alignItems: "start",
|
||||
padding: "10px 11px",
|
||||
border: "1px solid var(--border)",
|
||||
borderRadius: 7,
|
||||
background: "var(--bg-input)",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color:
|
||||
check.status === "Ready"
|
||||
? "var(--accent-green)"
|
||||
: check.status === "Missing"
|
||||
? "var(--text-danger)"
|
||||
: "var(--accent-yellow)",
|
||||
display: "inline-flex",
|
||||
marginTop: 1,
|
||||
}}
|
||||
>
|
||||
{check.status === "Ready" ? (
|
||||
<CheckCircle2 size={15} />
|
||||
) : check.status === "Missing" ? (
|
||||
<ShieldAlert size={15} />
|
||||
) : (
|
||||
<LockKeyhole size={15} />
|
||||
)}
|
||||
</span>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
margin: 0,
|
||||
color: "var(--text-primary)",
|
||||
fontSize: 12,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{check.label}
|
||||
</p>
|
||||
<span className={`ui-badge ${checkClass[check.status]}`}>
|
||||
{check.status}
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
marginTop: 4,
|
||||
color: "var(--text-muted)",
|
||||
fontSize: 11,
|
||||
lineHeight: 1.45,
|
||||
}}
|
||||
>
|
||||
{check.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"repeat(auto-fit, minmax(min(360px, 100%), 1fr))",
|
||||
gap: 12,
|
||||
alignItems: "start",
|
||||
}}
|
||||
>
|
||||
{environment.groups.map((group) => (
|
||||
<PanelShell key={group.title} title={group.title}>
|
||||
<p
|
||||
style={{
|
||||
marginBottom: 12,
|
||||
color: "var(--text-muted)",
|
||||
fontSize: 12,
|
||||
lineHeight: 1.5,
|
||||
}}
|
||||
>
|
||||
{group.description}
|
||||
</p>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
{group.items.map((item) => (
|
||||
<div
|
||||
key={item.label}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"minmax(120px, 0.45fr) minmax(0, 1fr)",
|
||||
gap: 12,
|
||||
alignItems: "center",
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
<span style={{ color: "var(--text-muted)" }}>
|
||||
{item.label}
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
color: "var(--text-primary)",
|
||||
fontFamily: "var(--font--code)",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
textAlign: "right",
|
||||
}}
|
||||
>
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PanelShell>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+26
-23
@@ -28,21 +28,25 @@ const levelColor: Record<LogsStreamItem["level"], string> = {
|
||||
DEBUG: "#38bdf8",
|
||||
};
|
||||
|
||||
const sourceStatusClass: Record<LogsTabData["sources"][number]["status"], string> =
|
||||
{
|
||||
Streaming: "badge-online",
|
||||
Paused: "badge-warning",
|
||||
Unavailable: "badge-danger",
|
||||
};
|
||||
const sourceStatusClass: Record<
|
||||
LogsTabData["sources"][number]["status"],
|
||||
string
|
||||
> = {
|
||||
Streaming: "badge-online",
|
||||
Paused: "badge-warning",
|
||||
Unavailable: "badge-danger",
|
||||
};
|
||||
|
||||
const summaryToneColor: Record<LogsTabData["summaries"][number]["tone"], string> =
|
||||
{
|
||||
blue: "var(--accent-blue)",
|
||||
green: "var(--accent-green)",
|
||||
purple: "var(--accent-purple)",
|
||||
amber: "var(--accent-yellow)",
|
||||
cyan: "var(--accent-cyan)",
|
||||
};
|
||||
const summaryToneColor: Record<
|
||||
LogsTabData["summaries"][number]["tone"],
|
||||
string
|
||||
> = {
|
||||
blue: "var(--accent-blue)",
|
||||
green: "var(--accent-green)",
|
||||
purple: "var(--accent-purple)",
|
||||
amber: "var(--accent-yellow)",
|
||||
cyan: "var(--accent-cyan)",
|
||||
};
|
||||
|
||||
export default function LogsTabPanel({
|
||||
logs,
|
||||
@@ -198,8 +202,7 @@ export default function LogsTabPanel({
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"minmax(min(360px, 100%), 1fr) auto auto auto",
|
||||
gridTemplateColumns: "minmax(min(360px, 100%), 1fr) auto auto auto",
|
||||
gap: 8,
|
||||
alignItems: "center",
|
||||
marginBottom: 10,
|
||||
@@ -295,7 +298,7 @@ export default function LogsTabPanel({
|
||||
key={line.id}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "72px 54px 110px minmax(360px, 1fr) auto",
|
||||
gridTemplateColumns: "72px 54px 180px minmax(360px, 1fr) auto",
|
||||
gap: 10,
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
@@ -306,9 +309,7 @@ export default function LogsTabPanel({
|
||||
<span style={{ color: levelColor[line.level], fontWeight: 800 }}>
|
||||
{line.level}
|
||||
</span>
|
||||
<span style={{ color: "var(--accent-cyan)" }}>
|
||||
{line.source}
|
||||
</span>
|
||||
<span style={{ color: "var(--accent-cyan)" }}>{line.source}</span>
|
||||
<span>{line.message}</span>
|
||||
<span style={{ color: "var(--text-muted)" }}>
|
||||
{line.traceId ?? ""}
|
||||
@@ -356,7 +357,7 @@ export default function LogsTabPanel({
|
||||
</div>
|
||||
</PanelShell>
|
||||
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
@@ -402,7 +403,9 @@ export default function LogsTabPanel({
|
||||
{source.type} - {source.lines} lines - {source.retention}
|
||||
</p>
|
||||
</div>
|
||||
<span className={`ui-badge ${sourceStatusClass[source.status]}`}>
|
||||
<span
|
||||
className={`ui-badge ${sourceStatusClass[source.status]}`}
|
||||
>
|
||||
{source.status}
|
||||
</span>
|
||||
</div>
|
||||
@@ -455,7 +458,7 @@ export default function LogsTabPanel({
|
||||
))}
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div>
|
||||
</div> */}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+17
-11
@@ -22,13 +22,19 @@ export default function AppOverview({
|
||||
<section
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"repeat(auto-fit, minmax(min(360px, 100%), 1fr))",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(min(360px, 100%), 1fr))",
|
||||
gap: 12,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12, minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 12,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
@@ -40,16 +46,16 @@ export default function AppOverview({
|
||||
<DeploymentPanel deployment={app.deployment} />
|
||||
<HealthPanel health={app.health} />
|
||||
</div>
|
||||
<RuntimeContainersPanel containers={app.runtimeContainers} />
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12, minWidth: 0 }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 12,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<DomainsPanel domains={app.domains} />
|
||||
<RecentLogsPanel
|
||||
logs={app.logs}
|
||||
autoRefresh={logsAutoRefresh}
|
||||
refreshing={logsRefreshing}
|
||||
onAutoRefreshChange={onLogsAutoRefreshChange}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
+2
-2
@@ -79,13 +79,13 @@ export default function RecentLogsPanel({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
{/* <button
|
||||
type="button"
|
||||
className="btn btn-ghost"
|
||||
style={{ width: "100%", marginTop: 10, fontSize: 12 }}
|
||||
>
|
||||
View all logs
|
||||
</button>
|
||||
</button> */}
|
||||
</PanelShell>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -181,7 +181,7 @@ export default function RuntimeTabPanel({ runtime }: RuntimeTabPanelProps) {
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
minHeight: 156,
|
||||
minHeight: 210,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
|
||||
+7
-15
@@ -1,15 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
AlertTriangle,
|
||||
History,
|
||||
Loader2,
|
||||
Play,
|
||||
RotateCw,
|
||||
ShieldAlert,
|
||||
TerminalSquare,
|
||||
} from "lucide-react";
|
||||
import { Loader2, RotateCw, TerminalSquare } from "lucide-react";
|
||||
import { terminal as terminalApi } from "@/lib/api";
|
||||
import { createClientId } from "@/lib/random-id";
|
||||
import { sanitizeTerminalStreamChunk } from "@/lib/terminal-output";
|
||||
@@ -541,7 +533,7 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) {
|
||||
|
||||
return (
|
||||
<section style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
@@ -608,7 +600,7 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<PanelShell
|
||||
title="Container Terminal"
|
||||
@@ -688,7 +680,7 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) {
|
||||
</div>
|
||||
</PanelShell>
|
||||
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
@@ -803,9 +795,9 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) {
|
||||
))}
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
@@ -884,7 +876,7 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) {
|
||||
))}
|
||||
</div>
|
||||
</PanelShell>
|
||||
</div>
|
||||
</div> */}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
+19
-12
@@ -40,7 +40,8 @@ function formatBytes(value: number | null) {
|
||||
}
|
||||
|
||||
function formatCpu(nanoCpus: number | null, cpuQuota: number | null) {
|
||||
if (nanoCpus && nanoCpus > 0) return `${(nanoCpus / 1_000_000_000).toFixed(2)} vCPU`;
|
||||
if (nanoCpus && nanoCpus > 0)
|
||||
return `${(nanoCpus / 1_000_000_000).toFixed(2)} vCPU`;
|
||||
if (cpuQuota && cpuQuota > 0) return `${cpuQuota} quota`;
|
||||
return "Unlimited";
|
||||
}
|
||||
@@ -105,16 +106,16 @@ export function createAdvancedData(
|
||||
tone: restartName === "no" ? "amber" : "green",
|
||||
},
|
||||
{
|
||||
label: "Runtime Guard",
|
||||
value: runtimeLoaded ? "Loaded" : "Unavailable",
|
||||
subvalue: runtimeLoaded ? "Docker inspect available" : "Using DB record",
|
||||
tone: runtimeLoaded ? "cyan" : "amber",
|
||||
label: "Root Filesystem",
|
||||
value: readOnlyRootfs ? "Read-only" : "Writable",
|
||||
subvalue: "Docker HostConfig.ReadonlyRootfs",
|
||||
tone: readOnlyRootfs ? "green" : "amber",
|
||||
},
|
||||
{
|
||||
label: "Danger Zone",
|
||||
value: "Protected",
|
||||
subvalue: "Confirmation required",
|
||||
tone: "purple",
|
||||
label: "Auto Remove",
|
||||
value: autoRemove ? "Enabled" : "Disabled",
|
||||
subvalue: "Docker HostConfig.AutoRemove",
|
||||
tone: autoRemove ? "amber" : "blue",
|
||||
},
|
||||
],
|
||||
metadata: [
|
||||
@@ -134,7 +135,9 @@ export function createAdvancedData(
|
||||
label: "CPU Limit",
|
||||
value: formatCpu(nanoCpus, cpuQuota),
|
||||
usage: detail ? Math.min(100, Math.round(detail.stats.cpuPercent)) : 0,
|
||||
helper: runtimeLoaded ? "Current Docker CPU sampling" : "Runtime unavailable",
|
||||
helper: runtimeLoaded
|
||||
? "Current Docker CPU sampling"
|
||||
: "Runtime unavailable",
|
||||
},
|
||||
{
|
||||
label: "Memory Limit",
|
||||
@@ -147,7 +150,10 @@ export function createAdvancedData(
|
||||
value: pidsLimit && pidsLimit > 0 ? String(pidsLimit) : "Unlimited",
|
||||
usage:
|
||||
pidsLimit && pidsLimit > 0
|
||||
? Math.min(100, Math.round((detail?.stats.pids ?? 0) / pidsLimit * 100))
|
||||
? Math.min(
|
||||
100,
|
||||
Math.round(((detail?.stats.pids ?? 0) / pidsLimit) * 100),
|
||||
)
|
||||
: 0,
|
||||
helper: `${detail?.stats.pids ?? 0} process ID(s) currently visible`,
|
||||
},
|
||||
@@ -202,7 +208,8 @@ export function createAdvancedData(
|
||||
{
|
||||
id: "reconcile-runtime",
|
||||
label: "Reconcile runtime state",
|
||||
description: "Stored metadata can be refreshed from Docker runtime data.",
|
||||
description:
|
||||
"Stored metadata can be refreshed from Docker runtime data.",
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
|
||||
+14
-4
@@ -53,6 +53,16 @@ function getBranch(container: Container) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
function getImageTag(image: string) {
|
||||
if (!image.trim()) return "-";
|
||||
if (image.includes("@")) return "digest";
|
||||
|
||||
const lastSegment = image.split("/").at(-1) ?? image;
|
||||
const tagIndex = lastSegment.lastIndexOf(":");
|
||||
|
||||
return tagIndex >= 0 ? lastSegment.slice(tagIndex + 1) || "latest" : "latest";
|
||||
}
|
||||
|
||||
export function createDeploymentsData(
|
||||
container: Container,
|
||||
detail: ContainerDetails | null,
|
||||
@@ -88,10 +98,10 @@ export function createDeploymentsData(
|
||||
tone: isSuccessful ? "green" : "amber",
|
||||
},
|
||||
{
|
||||
label: "Success Rate",
|
||||
value: isSuccessful ? "100%" : "0%",
|
||||
subvalue: "Based on current container record",
|
||||
tone: isSuccessful ? "green" : "amber",
|
||||
label: "Image Tag",
|
||||
value: getImageTag(container.image),
|
||||
subvalue: container.image,
|
||||
tone: "purple",
|
||||
},
|
||||
{
|
||||
label: "Deploy Mode",
|
||||
|
||||
Reference in new issue
Block a user