feat(console): render standalone terminal fullscreen

This commit is contained in:
2026-06-20 17:38:48 -05:00
parent de4f1485b1
commit 96439b43e2
3 changed files with 156 additions and 18 deletions

View File

@@ -0,0 +1,5 @@
{{define "shell"}}
<main class="console-fullscreen-shell">
{{template "content" .}}
</main>
{{end}}

View File

@@ -37,6 +37,10 @@ func NewRenderer() (*Renderer, error) {
"icon": icon,
"contains": strings.Contains,
"distroIconClass": distroIconClass,
"nodeIconClass": nodeIconClass,
"nodeIconPending": nodeIconPending,
"packageManagerIconClass": packageManagerIconClass,
"packageManagerLabel": packageManagerLabel,
"uptime": formatUptime,
"safeHTML": func(value string) template.HTML { return template.HTML(value) },
"nowYear": func() int { return time.Now().Year() },
@@ -68,6 +72,8 @@ func (r *Renderer) Render(w http.ResponseWriter, name string, data ViewData) {
layout := "layouts/app.gohtml"
if data.Shell == "auth" {
layout = "layouts/auth.gohtml"
} else if data.Shell == "console" {
layout = "layouts/console.gohtml"
}
parsed, err := template.New("base").Funcs(r.functions).ParseFS(
@@ -137,6 +143,57 @@ func distroIconClass(distro string) string {
}
}
func nodeIconClass(distro, packageManager string) string {
if className := distroIconClass(distro); className != "ti ti-server-2" {
return className
}
switch strings.ToLower(strings.TrimSpace(packageManager)) {
case "apt":
return "fa-brands fa-debian"
case "pacman":
return "ti ti-brand-archlinux"
default:
return "ti ti-server-2"
}
}
func nodeIconPending(distro, packageManager string) bool {
distroValue := strings.ToLower(strings.TrimSpace(distro))
packageValue := strings.ToLower(strings.TrimSpace(packageManager))
return packageValue == "" && (distroValue == "" || distroValue == "linux")
}
func packageManagerIconClass(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "apt":
return "fa-brands fa-debian"
case "pacman":
return "ti ti-brand-archlinux"
case "dnf", "yum", "zypper", "apk", "nix", "emerge":
return "ti ti-package"
default:
return "ti ti-package"
}
}
func packageManagerLabel(value string) string {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return "Unknown"
}
switch strings.ToLower(trimmed) {
case "apt":
return "APT"
case "dnf":
return "DNF"
case "yum":
return "YUM"
default:
return strings.ToUpper(trimmed[:1]) + strings.ToLower(trimmed[1:])
}
}
func icon(name string) template.HTML {
icons := map[string]string{
"dashboard": `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M4 13h7V4H4zm9 7h7v-9h-7zM4 20h7v-5H4zm9-9h7V4h-7z"/></svg>`,

View File

@@ -314,11 +314,24 @@ body[data-bs-theme="light"] .app-sidebar {
padding: 0.7rem 0.8rem 0.7rem 0.75rem;
min-width: 0;
position: relative;
display: grid;
grid-template-rows: auto auto 1fr auto;
align-items: start;
}
.node-tile-heading {
min-width: 0;
}
.node-tile-metrics {
display: grid;
gap: 0.45rem;
align-self: start;
}
.node-tile-meta {
align-self: end;
margin-top: 0.55rem;
}
.node-metric {
@@ -331,6 +344,8 @@ body[data-bs-theme="light"] .app-sidebar {
.node-metric .progress {
--bs-progress-height: 0.35rem;
display: flex;
align-items: center;
margin: 0;
}
@@ -340,6 +355,20 @@ body[data-bs-theme="light"] .app-sidebar {
font-size: 1rem;
}
.node-loading-icon {
animation: node-icon-spin 1s linear infinite;
}
@keyframes node-icon-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.node-tile-main .badge {
font-size: 0.65rem;
padding: 0.35rem 0.45rem;
@@ -386,6 +415,48 @@ body[data-bs-theme="light"] .app-sidebar {
justify-self: end;
}
.system-summary-item {
display: grid;
grid-template-columns: 3.25rem minmax(0, 1fr);
gap: 0.9rem;
align-items: center;
}
.system-summary-icon {
width: 3rem;
height: 3rem;
font-size: 1.2rem;
}
.system-summary-label {
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--bs-secondary-color);
margin-bottom: 0.15rem;
}
.system-summary-value {
font-size: 1rem;
font-weight: 700;
color: var(--bs-emphasis-color);
}
.system-summary-stats {
display: grid;
gap: 0.75rem;
padding-top: 0.25rem;
}
.system-summary-stat {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
font-size: 0.92rem;
}
.progress {
--bs-progress-height: 0.6rem;
background: color-mix(in srgb, black 18%, var(--ma-surface-1));
@@ -472,9 +543,11 @@ body[data-bs-theme="light"] .add-vm-panel {
}
.auth-toggle-option {
display: grid;
gap: 0.2rem;
padding: 0.85rem 0.95rem;
display: flex;
align-items: center;
justify-content: center;
min-height: 3rem;
padding: 0.6rem 0.9rem;
border: 1px solid var(--ma-border);
border-radius: 0.95rem;
cursor: pointer;
@@ -490,11 +563,6 @@ body[data-bs-theme="light"] .auth-toggle-option {
color: var(--bs-emphasis-color);
}
.auth-toggle-text {
font-size: 0.78rem;
color: var(--bs-secondary-color);
}
.btn-check:checked + .auth-toggle-option {
border-color: rgba(var(--color-primary-500), 0.7);
background: rgba(var(--color-primary-500), 0.12);
@@ -540,7 +608,7 @@ body[data-bs-theme="light"] .option-check {
min-height: 420px;
max-height: 60vh;
overflow: auto;
background: #151920;
background: #0b1220;
color: #dbeafe;
padding: 0;
}
@@ -556,19 +624,27 @@ body[data-bs-theme="light"] .option-check {
}
.console-panel {
min-height: calc(100vh - 12rem);
min-height: 100vh;
}
.console-panel .console-terminal {
height: calc(100vh - 12rem);
height: 100vh;
}
.console-fullscreen-shell {
height: 100vh;
width: 100vw;
overflow: hidden;
}
.console-terminal .xterm {
height: 100%;
background: #0b1220;
padding: 0.9rem 1rem;
}
.console-terminal .xterm-viewport {
background: #0b1220;
border-radius: 1.25rem;
}