diff --git a/internal/http/static/app.css b/internal/http/static/app.css index a18c416..2012652 100644 --- a/internal/http/static/app.css +++ b/internal/http/static/app.css @@ -754,23 +754,24 @@ pre { /* Detail pages (DESIGN-SYSTEM §6) */ .breadcrumb { display: flex; align-items: center; gap: 6px; font-size: 12.8px; color: var(--muted); margin-bottom: 8px; } .page-head { margin-bottom: 12px; } -.page-head h1 { font-size: 22px; font-weight: 800; margin: 0 0 4px; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; } +.page-head h1 { font-size: 22px; font-weight: 800; margin: 0 0 2px; display: flex; align-items: center; flex-wrap: wrap; gap: 10px; } .page-head .meta { margin: 0; font-size: 12.8px; color: var(--muted); } -.alert { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--line); margin-bottom: 12px; } +.alert { display: flex; align-items: flex-start; gap: 10px; padding: 12px; border-radius: 12px; border: 1px solid var(--line); margin-bottom: 12px; font-size: 13px; } .alert .icon { margin-top: 1px; } -.alert-title { font-size: 14px; font-weight: 600; } +.alert-title { font-size: 13px; font-weight: 700; } .alert-sub { font-size: 12.8px; opacity: 0.9; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } .alert-text { min-width: 0; } .alert-ok { background: var(--ok-bg); color: var(--ok-text); border-color: color-mix(in srgb, var(--ok) 25%, transparent); } .alert-warn { background: var(--warn-bg); color: var(--warn-text); border-color: color-mix(in srgb, var(--warn) 25%, transparent); } .alert-danger { background: var(--danger-bg); color: var(--danger-text); border-color: color-mix(in srgb, var(--danger) 25%, transparent); } .alert-neutral { background: var(--sunk); color: var(--ink2); } -.action-bar { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; } -.action-bar form { display: contents; } -.action-bar .btn-primary { grid-column: 1 / -1; } +.actgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line); } +.actgrid form { display: contents; } +.actgrid .btn-primary { grid-column: 1 / -1; } +.actgrid .btn .icon { width: 17px; height: 17px; } @media (min-width: 700px) { - .action-bar { display: flex; flex-wrap: wrap; } - .action-bar .btn-primary { order: -1; } + .actgrid { display: flex; flex-wrap: wrap; } + .actgrid .btn-primary { order: -1; } } .seg { display: inline-flex; gap: 2px; padding: 3px; background: var(--sunk); border-radius: 12px; max-width: 100%; overflow-x: auto; } .seg-btn { min-height: 32px; padding: 0 12px; border: 0; border-radius: 10px; background: none; box-shadow: none; color: var(--ink2); font-size: 12.8px; font-weight: 600; white-space: nowrap; } @@ -794,11 +795,12 @@ div.bar-danger > span { background: var(--danger); } .stats-body { margin-top: 12px; } .muted-note { color: var(--muted); font-size: 12.8px; margin: 0 0 8px; } .muted { color: var(--muted); } -.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; margin: 0; } +.detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 14px; margin: 0; } .detail-grid > div { min-width: 0; } .detail-grid .wide { grid-column: 1 / -1; } -.detail-grid dt { font-size: 11px; font-weight: 800; text-transform: uppercase; color: var(--muted); letter-spacing: 0.04em; margin-bottom: 2px; } -.detail-grid dd { margin: 0; font-size: 14px; font-weight: 600; overflow-wrap: anywhere; } +.detail-grid dt { font-size: 11px; font-weight: 800; text-transform: uppercase; color: var(--muted); letter-spacing: 0.06em; margin-bottom: 2px; } +.detail-grid dd { margin: 0; font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.detail-grid dd.wrap { white-space: normal; overflow-wrap: anywhere; } .detail-grid dd.mono, .mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 13px; } .wrap { overflow-wrap: anywhere; word-break: break-all; } pre.code { margin: 0; padding: 12px; background: var(--sunk); border: 1px solid var(--line); border-radius: 12px; font-size: 12.5px; overflow-x: auto; white-space: pre-wrap; overflow-wrap: anywhere; color: var(--ink); } @@ -810,15 +812,12 @@ pre.code { margin: 0; padding: 12px; background: var(--sunk); border: 1px solid .file-list li { padding: 8px 0; border-bottom: 1px solid var(--line); } .file-list li:last-child { border-bottom: 0; } .log-cell { max-width: 420px; white-space: normal !important; overflow-wrap: anywhere; font-size: 12.5px; } -.danger-zone { border-top: 2px solid var(--danger); } -.danger-zone h3 { color: var(--danger-text); font-size: 12.8px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; } -.dz-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 0; border-top: 1px solid var(--line); } -.dz-title { font-size: 14px; font-weight: 600; } -.dz-desc { font-size: 12.8px; color: var(--muted); } -@media (max-width: 699px) { - .dz-row { flex-direction: column; align-items: stretch; } - .detail-grid { grid-template-columns: 1fr; } -} +.danger-zone { box-shadow: none; } +.danger-zone h3 { color: var(--danger); font-size: 12.8px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 2px; } +.dz-sub { font-size: 12.8px; color: var(--muted); margin: 0; } +.dz-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 8px; margin-top: 10px; } +.dz-grid .btn { padding: 0 8px; } +@media (min-width: 700px) { .dz-grid { display: flex; flex-wrap: wrap; } } .t-ok { color: var(--ok); } .t-info { color: var(--accent); } .t-warn { color: var(--warn-text); } diff --git a/internal/http/templates/apps_detail.html b/internal/http/templates/apps_detail.html index 801cfc6..4f53ccc 100644 --- a/internal/http/templates/apps_detail.html +++ b/internal/http/templates/apps_detail.html @@ -16,29 +16,45 @@

{{.App.Name}} {{.App.Status}}{{if .Domains}}{{index .Domains 0}}{{if .ExtraDomainCount}} +{{.ExtraDomainCount}} more{{end}}:{{.App.Port}}{{else}}Internal service, port {{.App.Port}}{{end}}

- {{if eq .App.Status "running"}}{{template "ui_alert" (dict "Kind" "ok" "Icon" "check" "Title" (printf "Running on %s" .ServerName) "Sub" "")}} + {{if eq .App.Status "running"}}{{template "ui_alert" (dict "Kind" "ok" "Icon" "check" "Title" (printf "Running on %s" .ServerName) "Sub" (or (and .Deployments (printf "Last deploy %s · %s" ((index .Deployments 0).CreatedAt.Format "2006-01-02 15:04") (or (index .Deployments 0).Status "unknown"))) ""))}} {{else if eq .App.Status "stopped"}}{{template "ui_alert" (dict "Kind" "neutral" "Icon" "alert" "Title" "Stopped" "Sub" "The app is not running. Start it to serve traffic.")}} {{else if eq .App.Status "draft"}}{{template "ui_alert" (dict "Kind" "neutral" "Icon" "alert" "Title" "Draft" "Sub" (or (and (ne .App.ServerID 0) (printf "Draft on %s. Deploy when ready." .ServerName)) "Assign a server and deploy."))}} {{else if or (eq .App.Status "deploying") (eq .App.Status "pending")}}{{template "ui_alert" (dict "Kind" "warn" "Icon" "refresh" "Title" "Deploying" "Sub" "This page refreshes every 2 seconds.")}} {{else}}{{template "ui_alert" (dict "Kind" "danger" "Icon" "alert" "Title" (printf "Status: %s" .App.Status) "Sub" (or (and .Deployments (index .Deployments 0).Log) ""))}}{{end}} -
+
+
+
Server
{{if eq .App.ServerID 0}}Not assigned Select one{{else}}{{.ServerName}}{{end}}
+
Domain{{if gt (len .Domains) 1}}s{{end}}
{{range $i, $d := .Domains}}{{if $i}}, {{end}}{{$d}}{{end}}{{if not .Domains}}-{{end}}
+
Port
{{.App.Port}}
+
Internal
{{.ServiceName}}:{{.App.Port}}
+ {{if .App.Command}}
Command
{{.App.Command}}
{{end}} + {{if .App.Ports}}
Ports
{{.App.Ports | nl2br}}
{{end}} + {{if .App.MemoryLimit}}
Memory limit
{{.App.MemoryLimit}}
{{end}} + {{if .App.CPULimit}}
CPU limit
{{.App.CPULimit}}
{{end}} + {{if .App.UlimitsNofile}}
Ulimits (nofile)
{{.App.UlimitsNofile}}
{{end}} + {{if or .App.LogMaxSize .App.LogMaxFile}}
Log limits
{{if .App.LogMaxSize}}max-size={{.App.LogMaxSize}}{{end}}{{if and .App.LogMaxSize .App.LogMaxFile}}, {{end}}{{if .App.LogMaxFile}}max-file={{.App.LogMaxFile}}{{end}}
{{end}} + {{if .App.AuthUser}}
Auth
basic auth ({{.App.AuthUser}})
{{end}} + {{if .App.GitRepo}}
Git repo
{{.App.GitRepo}}
Branch
{{.App.GitBranch}}
{{end}} +
+
{{if eq .App.Status "draft"}}
- +
- Edit + {{template "ui_icon" "edit"}}Edit {{else}} {{if eq .App.Status "stopped"}} -
- Edit +
+ {{template "ui_icon" "edit"}}Edit {{else}} - Edit - {{if eq .App.Status "running"}}
{{end}} + {{template "ui_icon" "edit"}}Edit + {{if eq .App.Status "running"}}
{{end}} {{end}} - + {{end}}
+

Docker Compose

@@ -184,14 +182,13 @@

Logs

Danger zone

- {{if ne .App.Status "draft"}} -
Redeploy
Rebuild and restart the app. It is briefly unavailable.
-
Rollback
Restore the last successful deployment.
- {{end}} - {{if .Deployments}} -
Clear deployment history
Remove all deployment records. This cannot be undone.
- {{end}} -
{{if eq .App.Status "draft"}}Delete draft{{else}}Undeploy{{end}}
{{if eq .App.Status "draft"}}Permanently delete this draft.{{else}}Stop the app and remove it from {{.ServerName}}.{{end}}
+

Each action asks for confirmation first.

+
+ {{if ne .App.Status "draft"}} + {{end}} + {{if .Deployments}}{{end}} + +
diff --git a/internal/http/templates/servers_detail.html b/internal/http/templates/servers_detail.html index b083b83..19e7cf6 100644 --- a/internal/http/templates/servers_detail.html +++ b/internal/http/templates/servers_detail.html @@ -20,9 +20,19 @@

{{.Server.Name}} {{.Server.Stat {{else if or (eq .Server.Status "initializing") (eq .Server.Status "pending")}}{{template "ui_alert" (dict "Kind" "warn" "Icon" "refresh" "Title" (printf "Status: %s" .Server.Status) "Sub" "Initialization in progress. This page refreshes every 2 seconds.")}} {{else}}{{template "ui_alert" (dict "Kind" "danger" "Icon" "alert" "Title" (printf "Status: %s" .Server.Status) "Sub" "Check the SSH key and connection, then run Init worker.")}}{{end}} -
- Edit - +
+
+
Host
{{.Server.Host}}
+
Port
{{.Server.Port}}
+
User
{{.Server.User}}
+
SSH key
{{.Server.SSHKey}}
+
Host key
{{with .Server.HostKeyFingerprint}}{{.}}{{else}}not pinned yet{{end}}
+
Registry
{{if .Server.RegistryUser}}{{.Server.RegistryHost}} as {{.Server.RegistryUser}}{{else}}—{{end}}
+
+
+ {{template "ui_icon" "edit"}}Edit + +
@@ -62,18 +72,6 @@

Resource usage -

Connection

-
-
Host
{{.Server.Host}}
-
Port
{{.Server.Port}}
-
User
{{.Server.User}}
-
Status
{{.Server.Status}}
-
SSH key
{{.Server.SSHKey}}
-
Host key
{{with .Server.HostKeyFingerprint}}{{.}}{{else}}not pinned yet{{end}}
-
Registry
{{if .Server.RegistryUser}}{{.Server.RegistryHost}} as {{.Server.RegistryUser}}{{else}}—{{end}}
-
-

{{template "servers_resources_card" .}} @@ -114,9 +112,12 @@

Apps {{len .Apps}}

Danger zone

-
{{if eq .Server.Status "online"}}Re-run init{{else}}Init worker{{end}}
Installs Docker and Caddy, re-applies the worker config.
-
Prune disk
Remove stopped containers, unused images and build cache.
-
Remove server
Remove from Dockify and delete its SSH key. Cannot be undone.
+

Each action asks for confirmation first.

+
+ + + +
diff --git a/internal/http/templates/ui_icon.html b/internal/http/templates/ui_icon.html index bf966cb..581cbf5 100644 --- a/internal/http/templates/ui_icon.html +++ b/internal/http/templates/ui_icon.html @@ -1 +1 @@ -{{define "ui_icon"}}{{end}} +{{define "ui_icon"}}{{end}}