feat(console): render standalone terminal fullscreen
This commit is contained in:
5
internal/views/layouts/console.gohtml
Normal file
5
internal/views/layouts/console.gohtml
Normal file
@@ -0,0 +1,5 @@
|
||||
{{define "shell"}}
|
||||
<main class="console-fullscreen-shell">
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
{{end}}
|
||||
@@ -34,13 +34,17 @@ type ViewData struct {
|
||||
|
||||
func NewRenderer() (*Renderer, error) {
|
||||
functions := template.FuncMap{
|
||||
"icon": icon,
|
||||
"contains": strings.Contains,
|
||||
"distroIconClass": distroIconClass,
|
||||
"uptime": formatUptime,
|
||||
"safeHTML": func(value string) template.HTML { return template.HTML(value) },
|
||||
"nowYear": func() int { return time.Now().Year() },
|
||||
"lower": strings.ToLower,
|
||||
"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() },
|
||||
"lower": strings.ToLower,
|
||||
"splitLines": func(value string) []string {
|
||||
var lines []string
|
||||
for _, line := range strings.Split(value, "\n") {
|
||||
@@ -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>`,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user