landfall 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -1
- package/dist/index.html +159 -138
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -815,6 +815,6 @@ Runs on 127.0.0.1 only; the printed link logs the browser in.`,options:{port:{ty
|
|
|
815
815
|
|
|
816
816
|
Without --packages/--images both are updated. Package upgrades cannot be undone;
|
|
817
817
|
image updates can (\`lf undo\` puts the old image back). --compose replaces the
|
|
818
|
-
compose plugin setup installed with the newest release (undoable too).`,options:{server:F,packages:{type:`boolean`,desc:`upgrade OS packages`},security:{type:`boolean`,desc:`only security updates (apt, dnf/yum); implies --packages`},reboot:{type:`boolean`,desc:`reboot afterwards if the upgrade needs it`},images:{type:`boolean`,desc:`pull newer images and recreate their containers`},compose:{type:`boolean`,desc:`update the compose plugin landfall installed to the newest release`}},async run(e,t){let{name:n,server:r}=H(t.server);if(t.compose){(e.length||t.packages||t.images)&&k(`--compose goes alone`);let i=await hr(r,`updating docker compose`),a=await Nr(i),o=Pr(a);if(!o.composeUpdate)return void(o.composeNote?E(o.composeNote):T(`compose ${a.compose} is the newest`));await N(`Update compose ${a.compose} to ${a.latestCompose} on ${n}?`)&&(w(`downloading docker compose`),await Fr(i),T(`compose updated (lf undo puts the old one back)`));return}let i=!t.packages&&!t.images&&!t.security&&!t.reboot;e.length&&t.packages&&k(`sites only apply to --images`);let a=!e.length&&(t.packages||i||t.security),o=t.images||i||e.length>0;if(a){let e=await hr(r,`upgrading packages`),i=await vr(e),a=t.security?i.updates.filter(e=>e.security):i.updates;i.mgr===`none`?D(`no supported package manager, skipping packages`):a.length?(Io({...i,updates:a},!1),t.security&&(i.mgr===`apk`||i.mgr===`zypper`)&&k(`${i.mgr} has no security-only upgrades here; drop --security`),await N(`Upgrade ${a.length} package${a.length===1?``:`s`} on ${n}?`)&&(w(`upgrading packages on ${n}`),await br(e,{only:t.security?a.map(e=>e.name):void 0,what:t.security?`${a.length} security packages`:`${a.length} packages`,reboot:t.reboot}),T(`packages upgraded`),t.reboot&&E(`rebooting if required; give it a minute`))):T(t.security?`no security updates`:`packages up to date`)}if(o){let t=await Lr(r);if(e.length){let r=e.filter(e=>!t.some(t=>t.name===e));r.length&&k(`no site or service ${r.join(`, `)} on ${n}`)}let i=e.length?t.filter(t=>e.includes(t.name)):t;for(let e of i.filter(e=>e.state!==`update`&&e.state!==`current`&&e.state!==`unknown`))E(C.dim(`${e.name}: ${e.note??e.state}`));let a=i.filter(t=>t.state===`update`||e.includes(t.name)&&t.state===`unknown`);a.length?(Lo(a),await N(`Pull and recreate ${a.map(e=>e.name).join(`, `)}?`)&&(w(`updating images`),await Rr(r,a),T(`images updated (lf undo puts the old ones back)`))):T(`images up to date`)}}});var Vo=`0.5.
|
|
818
|
+
compose plugin setup installed with the newest release (undoable too).`,options:{server:F,packages:{type:`boolean`,desc:`upgrade OS packages`},security:{type:`boolean`,desc:`only security updates (apt, dnf/yum); implies --packages`},reboot:{type:`boolean`,desc:`reboot afterwards if the upgrade needs it`},images:{type:`boolean`,desc:`pull newer images and recreate their containers`},compose:{type:`boolean`,desc:`update the compose plugin landfall installed to the newest release`}},async run(e,t){let{name:n,server:r}=H(t.server);if(t.compose){(e.length||t.packages||t.images)&&k(`--compose goes alone`);let i=await hr(r,`updating docker compose`),a=await Nr(i),o=Pr(a);if(!o.composeUpdate)return void(o.composeNote?E(o.composeNote):T(`compose ${a.compose} is the newest`));await N(`Update compose ${a.compose} to ${a.latestCompose} on ${n}?`)&&(w(`downloading docker compose`),await Fr(i),T(`compose updated (lf undo puts the old one back)`));return}let i=!t.packages&&!t.images&&!t.security&&!t.reboot;e.length&&t.packages&&k(`sites only apply to --images`);let a=!e.length&&(t.packages||i||t.security),o=t.images||i||e.length>0;if(a){let e=await hr(r,`upgrading packages`),i=await vr(e),a=t.security?i.updates.filter(e=>e.security):i.updates;i.mgr===`none`?D(`no supported package manager, skipping packages`):a.length?(Io({...i,updates:a},!1),t.security&&(i.mgr===`apk`||i.mgr===`zypper`)&&k(`${i.mgr} has no security-only upgrades here; drop --security`),await N(`Upgrade ${a.length} package${a.length===1?``:`s`} on ${n}?`)&&(w(`upgrading packages on ${n}`),await br(e,{only:t.security?a.map(e=>e.name):void 0,what:t.security?`${a.length} security packages`:`${a.length} packages`,reboot:t.reboot}),T(`packages upgraded`),t.reboot&&E(`rebooting if required; give it a minute`))):T(t.security?`no security updates`:`packages up to date`)}if(o){let t=await Lr(r);if(e.length){let r=e.filter(e=>!t.some(t=>t.name===e));r.length&&k(`no site or service ${r.join(`, `)} on ${n}`)}let i=e.length?t.filter(t=>e.includes(t.name)):t;for(let e of i.filter(e=>e.state!==`update`&&e.state!==`current`&&e.state!==`unknown`))E(C.dim(`${e.name}: ${e.note??e.state}`));let a=i.filter(t=>t.state===`update`||e.includes(t.name)&&t.state===`unknown`);a.length?(Lo(a),await N(`Pull and recreate ${a.map(e=>e.name).join(`, `)}?`)&&(w(`updating images`),await Rr(r,a),T(`images updated (lf undo puts the old ones back)`))):T(`images up to date`)}}});var Vo=`0.5.2`;const Ho=[Eo,No,jo,Li,Ri,zi,qi,Wi,Vi,Hi,Ui,Ki,Gi,Ji,yo,bo,xo,zo,Bo,Xa,Za,ro,ao,lo,to,Qa,eo,no,Oi,ki,ji,Mi,Ni,Ai,Pi,ma,ha,ga,fa,pa,va,_a,Fa,Ia,So,Po],Uo={ls:`sites list`,sites:`sites list`,servers:`server list`,"server ls":`server list`,"server ssh":`ssh`,"sites logs":`logs`,"sites env":`env`,"sites rollback":`rollback`,"sites restart":`restart`,"sites stop":`stop`,"sites start":`start`,"sites ls":`sites list`,"sites remove":`sites rm`,"server remove":`server rm`,uninstall:`server uninstall`,"traefik remove":`traefik rm`,traefik:`traefik status`,firewall:`firewall status`,"firewall remove":`firewall rm`,log:`history`,revert:`undo`,web:`ui`,info:`server info`,"sites domains":`domains`,"sites auth":`auth`,"sites open":`open`,"sites tunnel":`tunnel`,upgrade:`update`},Wo=new Map(Ho.map(e=>[e.name,e]));function Go(e){for(let t of[2,1]){let n=e.slice(0,t).join(` `),r=Wo.get(Uo[n]??n);if(r&&e.length>=t)return{cmd:r,rest:e.slice(t)}}return{rest:e}}function Ko(){let e=[[`Servers`,[`setup`,`server add`,`server list`,`server info`,`server config`,`server domain`,`server use`,`server rm`,`server uninstall`,`ssh`,`keygen`]],[`Traefik`,[`traefik setup`,`traefik status`,`traefik dashboard`,`traefik logs`,`traefik restart`,`traefik rm`]],[`Firewall`,[`firewall status`,`firewall enable`,`firewall disable`,`firewall allow`,`firewall deny`,`firewall update`,`firewall rm`]],[`Sites`,[`deploy`,`sites list`,`sites info`,`domains`,`auth`,`open`,`tunnel`,`logs`,`env`,`rollback`,`restart`,`stop`,`start`,`sites rm`]],[`Updates`,[`updates`,`update`]],[`History`,[`history`,`undo`]],[`Dashboard`,[`ui`]]],t=[`${C.bold(`landfall`)} ${C.dim(Vo)} — set up servers and deploy sites behind Traefik + HTTPS`,``,`${C.bold(`Usage:`)} lf <command> [options] ${C.dim(`lf <command> --help for details`)}`,` lf ui ${C.dim(`web dashboard in your browser`)}`];for(let[n,r]of e){t.push(``,C.bold(n));for(let e of r){let n=Wo.get(e);t.push(` ${(e+(n.args?` `+n.args:``)).padEnd(32)} ${n.summary.split(`
|
|
819
819
|
`)[0]}`)}}t.push(``,C.bold(`Quick start`),` lf setup vps --check ${C.dim(`# what is on vps, what setup would change`)}`,` lf setup vps ${C.dim("# docker + traefik on a server you reach with `ssh vps`")}`,` lf setup box --host 203.0.113.7 ${C.dim("# fresh box (root password once) → `ssh box` works")}`,` lf deploy ./dist -d example.com -s vps ${C.dim(`# publish a static site with HTTPS`)}`,` lf deploy ${C.dim(`# redeploy using ./.landfall.json`)}`,` lf deploy ./dist -y ${C.dim(`# no domain yet: serve at <name>.<ip>.sslip.io`)}`,` lf auth my-site --user anna ${C.dim(`# put a password in front of a site`)}`,` lf updates -s vps && lf update -s vps ${C.dim(`# pending OS/image updates, then apply them`)}`,` lf ls ${C.dim(`# what runs where`)}`,` lf history -s vps && lf undo -s vps ${C.dim(`# what landfall changed, and revert the last of it`)}`,``,C.dim(`Servers are ssh targets: any ~/.ssh/config alias works as --server.`)),console.log(t.join(`
|
|
820
820
|
`))}async function qo(e){if(!e.length||e[0]===`help`||e[0]===`-h`||e[0]===`--help`)return Ko(),0;if(e[0]===`-v`||e[0]===`--version`||e[0]===`version`)return console.log(Vo),0;let{cmd:t,rest:n}=Go(e);if(!t)return console.error(`${C.red(`error:`)} unknown command "${e.join(` `)}"\n`),Ko(),2;let{values:r,positionals:i,rest:a}=me(t,n);if(r.help)return console.log(he(t)),0;j(!!r.yes),gt.cmd=ge(t,i,r);let o=await t.run(i,r,a);return typeof o==`number`?o:0}async function Jo(e){try{return await qo(e)}catch(e){return e instanceof O?(console.error(`${C.red(`error:`)} ${e.message}`),/--force\b/.test(e.message)&&oe({force:e.message})):console.error(e),1}}Jo(process.argv.slice(2)).then(e=>process.stdout.write(``,()=>process.exit(e)));export{H as A,ye as C,z as D,ve as E,C as M,pe as N,V as O,G as S,Te as T,Ge as _,uo as a,U as b,Ua as c,za as d,Yn as f,at as g,ht as h,po as i,L as j,Be as k,Ba as l,Nn as m,fo as n,mo as o,jn as p,go as r,ho as s,Vo as t,Ra as u,q as v,we as w,W as x,K as y};
|
package/dist/index.html
CHANGED
|
@@ -9,171 +9,179 @@
|
|
|
9
9
|
:root {
|
|
10
10
|
/* Three text sizes and three corner radii; headings set their own. */
|
|
11
11
|
--fs-xs: 11.5px; --fs-sm: 12.5px; --fs: 13px;
|
|
12
|
-
--r-sm:
|
|
13
|
-
--bg: #
|
|
14
|
-
--accent: #
|
|
15
|
-
--bad: #
|
|
12
|
+
--r-sm: 7px; --r: 10px; --r-lg: 14px;
|
|
13
|
+
--bg: #fafafa; --panel: #fff; --hover: #f4f4f5; --line: #ececee; --line-strong: #d4d4d8; --text: #18181b; --dim: #5d5d66; --faint: #71717a;
|
|
14
|
+
--accent: #4f46e5; --accent-bg: #eef0fe; --accent-solid: #4f46e5; --bad-solid: #dc2626; --ok: #15803d; --ok-bg: #eaf7ee; --warn: #b45309; --warn-bg: #fdf4e3;
|
|
15
|
+
--bad: #c42323; --bad-bg: #fcecec; --term: #0f0f12; --term-text: #dcdce1;
|
|
16
|
+
--shadow: 0 1px 2px rgb(0 0 0 / .04); --shadow-lg: 0 10px 30px -8px rgb(0 0 0 / .14), 0 2px 6px rgb(0 0 0 / .05);
|
|
17
|
+
--ring: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
|
|
16
18
|
--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
|
17
19
|
color-scheme: light;
|
|
18
20
|
}
|
|
19
21
|
@media (prefers-color-scheme: dark) {
|
|
20
22
|
:root {
|
|
21
|
-
--bg: #
|
|
22
|
-
--accent: #
|
|
23
|
-
--bad: #
|
|
23
|
+
--bg: #0c0c0e; --panel: #141417; --hover: #1c1c20; --line: #232328; --line-strong: #34343b; --text: #ececef; --dim: #a1a1aa; --faint: #8b8b94;
|
|
24
|
+
--accent: #8b93ff; --accent-bg: #1d1d3a; --accent-solid: #5b54e8; --bad-solid: #d33a3a; --ok: #4ade80; --ok-bg: #122a1b; --warn: #f5b34a; --warn-bg: #2b2212;
|
|
25
|
+
--bad: #ff7b7b; --bad-bg: #321819; --term: #08080a; --term-text: #dcdce1;
|
|
26
|
+
--shadow: 0 1px 2px rgb(0 0 0 / .4); --shadow-lg: 0 16px 40px -8px rgb(0 0 0 / .6), 0 2px 6px rgb(0 0 0 / .4);
|
|
24
27
|
color-scheme: dark;
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
* { box-sizing: border-box; }
|
|
28
31
|
html, body { margin: 0; height: 100%; }
|
|
29
|
-
body { background: var(--bg); color: var(--text); font: 14px/1.
|
|
32
|
+
body { background: var(--bg); color: var(--text); font: 14px/1.5 Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; -webkit-font-smoothing: antialiased; }
|
|
30
33
|
a { color: var(--accent); text-decoration: none; }
|
|
31
|
-
a:hover { text-decoration: underline; }
|
|
34
|
+
a:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
32
35
|
code, .mono { font-family: var(--mono); font-size: var(--fs-sm); }
|
|
33
36
|
button {
|
|
34
|
-
font: inherit; font-size: var(--fs); border: 1px solid var(--line); background: var(--panel); color: var(--text);
|
|
35
|
-
padding: 5px
|
|
37
|
+
font: inherit; font-size: var(--fs); font-weight: 500; border: 1px solid var(--line-strong); background: var(--panel); color: var(--text);
|
|
38
|
+
padding: 5px 12px; border-radius: var(--r-sm); cursor: pointer; white-space: nowrap; box-shadow: var(--shadow);
|
|
39
|
+
transition: background-color .12s, border-color .12s, color .12s, box-shadow .12s;
|
|
36
40
|
}
|
|
37
|
-
button:hover {
|
|
38
|
-
button:focus-visible, a:focus-visible { outline:
|
|
39
|
-
button.primary:hover { filter: brightness(1.08); }
|
|
41
|
+
button:hover { background: var(--hover); }
|
|
42
|
+
button:focus-visible, a:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
|
|
40
43
|
button:disabled { opacity: .5; cursor: default; }
|
|
41
44
|
button.primary { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
|
|
45
|
+
button.primary:hover, button.danger.solid:hover { filter: brightness(1.1); }
|
|
42
46
|
button.danger { color: var(--bad); }
|
|
47
|
+
button.danger:hover { background: var(--bad-bg); }
|
|
43
48
|
button.danger.solid { background: var(--bad-solid); border-color: var(--bad-solid); color: #fff; }
|
|
44
|
-
button.
|
|
45
|
-
button.ghost {
|
|
46
|
-
button.
|
|
47
|
-
button.sm { padding: 2px 8px; font-size: var(--fs-sm); }
|
|
49
|
+
button.ghost { border-color: transparent; background: transparent; color: var(--dim); box-shadow: none; }
|
|
50
|
+
button.ghost:hover { color: var(--text); background: var(--hover); }
|
|
51
|
+
button.sm { padding: 2px 9px; font-size: var(--fs-sm); }
|
|
48
52
|
input, select, textarea {
|
|
49
|
-
font: inherit; color: var(--text); background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--r-sm); padding:
|
|
53
|
+
font: inherit; color: var(--text); background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--r-sm); padding: 7px 10px; width: 100%;
|
|
54
|
+
transition: border-color .12s, box-shadow .12s;
|
|
50
55
|
}
|
|
51
56
|
input.secret { -webkit-text-security: disc; text-security: disc; }
|
|
52
|
-
input:focus, select:focus, textarea:focus { outline:
|
|
57
|
+
input:focus, select:focus, textarea:focus { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
|
|
53
58
|
textarea { font-family: var(--mono); font-size: var(--fs-sm); min-height: 90px; }
|
|
54
59
|
|
|
55
|
-
.app { display: grid; grid-template-columns:
|
|
56
|
-
.app.with-job { grid-template-columns:
|
|
57
|
-
header { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px; padding: 0
|
|
58
|
-
.brand { font-weight:
|
|
60
|
+
.app { display: grid; grid-template-columns: 248px minmax(0, 1fr); grid-template-rows: 56px minmax(0, 1fr); height: 100vh; }
|
|
61
|
+
.app.with-job { grid-template-columns: 248px minmax(0, 1fr) clamp(360px, 32vw, 520px); }
|
|
62
|
+
header { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px; padding: 0 20px; border-bottom: 1px solid var(--line); background: var(--panel); }
|
|
63
|
+
.brand { font-weight: 650; font-size: 15px; display: flex; align-items: center; gap: 9px; letter-spacing: -.015em; color: var(--text); }
|
|
59
64
|
.brand:hover { text-decoration: none; }
|
|
60
|
-
.brand svg { width:
|
|
61
|
-
.brand small { font-weight:
|
|
65
|
+
.brand svg { width: 24px; height: 24px; flex: none; }
|
|
66
|
+
.brand small { font-weight: 500; color: var(--faint); font-size: var(--fs-xs); letter-spacing: 0; padding: 1px 7px; border-radius: 99px; background: var(--hover); }
|
|
62
67
|
.project { margin-left: auto; display: flex; align-items: center; gap: 8px; color: var(--dim); font-size: var(--fs); min-width: 0; }
|
|
63
|
-
.project .path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 36vw; }
|
|
64
|
-
nav.servers { border-right: 1px solid var(--line); background: var(--
|
|
65
|
-
.nav-top { position: sticky; top: 0; z-index: 1; background: var(--
|
|
66
|
-
.nav-top input { padding:
|
|
67
|
-
.nav-top button { flex: none; }
|
|
68
|
-
#navlist { padding: 0 8px
|
|
69
|
-
nav h3 { margin: 10px
|
|
70
|
-
nav h3 span { font-weight: 500;
|
|
71
|
-
.srv { display: block; width: 100%; text-align: left; border: 0; background: none; padding: 6px 10px; border-radius: var(--r-sm); }
|
|
72
|
-
.srv:hover { background: var(--
|
|
73
|
-
.srv.on { background: var(--
|
|
74
|
-
.srv.on .n { color: var(--
|
|
75
|
-
.srv.on .t { color: var(--
|
|
76
|
-
.srv .n { font-weight:
|
|
77
|
-
.srv .t { color: var(--
|
|
68
|
+
.project .path { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 36vw; font-family: var(--mono); font-size: var(--fs-sm); }
|
|
69
|
+
nav.servers { border-right: 1px solid var(--line); background: var(--bg); overflow: auto; display: flex; flex-direction: column; }
|
|
70
|
+
.nav-top { position: sticky; top: 0; z-index: 1; background: var(--bg); display: flex; gap: 6px; padding: 14px 12px 8px; }
|
|
71
|
+
.nav-top input { padding: 6px 10px; font-size: var(--fs); }
|
|
72
|
+
.nav-top button { flex: none; padding-inline: 10px; }
|
|
73
|
+
#navlist { padding: 0 8px 16px; }
|
|
74
|
+
nav h3 { margin: 14px 10px 4px; font-size: var(--fs-xs); color: var(--faint); font-weight: 500; display: flex; justify-content: space-between; }
|
|
75
|
+
nav h3 span { font-weight: 500; }
|
|
76
|
+
.srv { display: block; width: 100%; text-align: left; border: 0; background: none; box-shadow: none; padding: 6px 10px; border-radius: var(--r-sm); }
|
|
77
|
+
.srv:hover { background: var(--hover); }
|
|
78
|
+
.srv.on { background: var(--panel); box-shadow: 0 0 0 1px var(--line), var(--shadow); }
|
|
79
|
+
.srv.on .n { color: var(--text); }
|
|
80
|
+
.srv.on .t { color: var(--dim); }
|
|
81
|
+
.srv .n { font-weight: 550; display: flex; align-items: center; gap: 6px; }
|
|
82
|
+
.srv .t { color: var(--faint); font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
78
83
|
.nav-none { color: var(--faint); font-size: var(--fs-sm); padding: 6px 10px; }
|
|
79
84
|
.srv .t .live { color: var(--ok); }
|
|
80
85
|
/* The open server's sections, under it in the list. */
|
|
81
|
-
.sections { display: grid; gap: 1px; margin:
|
|
82
|
-
.sections button { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; border: 0; background: none; padding:
|
|
83
|
-
.sections button:hover { background: var(--
|
|
84
|
-
.sections button.on { background: var(--accent-bg); color: var(--accent); font-weight:
|
|
86
|
+
.sections { display: grid; gap: 1px; margin: 4px 0 10px 12px; padding-left: 8px; border-left: 1px solid var(--line-strong); }
|
|
87
|
+
.sections button { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; border: 0; background: none; box-shadow: none; padding: 5px 10px; color: var(--dim); font-weight: 450; }
|
|
88
|
+
.sections button:hover { background: var(--hover); color: var(--text); }
|
|
89
|
+
.sections button.on { background: var(--accent-bg); color: var(--accent); font-weight: 550; }
|
|
85
90
|
.sections .badge { margin-left: auto; }
|
|
86
|
-
.head h1 .crumb { font-weight: 400; color: var(--
|
|
91
|
+
.head h1 .crumb { font-weight: 400; color: var(--faint); }
|
|
87
92
|
.tunnel-url { font-family: var(--mono); font-size: var(--fs-sm); }
|
|
88
93
|
.tunnel-lan::before { content: "· LAN "; color: var(--faint); font-size: var(--fs-sm); }
|
|
89
94
|
.star { color: var(--warn); font-size: var(--fs-xs); }
|
|
90
|
-
main { overflow: auto; padding:
|
|
95
|
+
main { overflow: auto; padding: 28px 32px 48px; min-width: 0; container-type: inline-size; }
|
|
91
96
|
.show-sm { display: none !important; }
|
|
92
97
|
/* By main's own width, so an open job panel squeezes the tables like a small screen does. */
|
|
93
98
|
@container (max-width: 720px) {
|
|
94
|
-
td, th { padding: 8px; }
|
|
99
|
+
td, th { padding: 9px 8px; }
|
|
95
100
|
.hide-sm { display: none; }
|
|
96
101
|
.show-sm { display: flex !important; }
|
|
97
102
|
}
|
|
98
|
-
.empty { color: var(--dim); text-align: center; padding:
|
|
99
|
-
.empty h2 { color: var(--text); font-size: 18px; margin: 0 0 6px; }
|
|
100
|
-
.head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px 20px; margin-bottom:
|
|
103
|
+
.empty { color: var(--dim); text-align: center; padding: 72px 20px; }
|
|
104
|
+
.empty h2 { color: var(--text); font-size: 18px; font-weight: 600; letter-spacing: -.01em; margin: 0 0 6px; }
|
|
105
|
+
.head { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 12px 20px; margin-bottom: 22px; }
|
|
101
106
|
.head > div:first-child { flex: 1 1 320px; min-width: 0; }
|
|
102
|
-
.head h1 { margin: 0; font-size:
|
|
103
|
-
.head .sub { color: var(--dim); font-size: var(--fs); margin-top:
|
|
107
|
+
.head h1 { margin: 0; font-size: 24px; font-weight: 650; letter-spacing: -.02em; line-height: 1.25; }
|
|
108
|
+
.head .sub { color: var(--dim); font-size: var(--fs); margin-top: 4px; }
|
|
104
109
|
.actions { display: flex; flex-wrap: wrap; gap: 6px; margin-left: auto; }
|
|
105
110
|
/* Phones only: elsewhere the sections sit under the server in the list. */
|
|
106
|
-
.tabs { display: none; gap:
|
|
111
|
+
.tabs { display: none; gap: 4px; padding: 4px; background: var(--hover); border-radius: var(--r); margin-bottom: 18px; overflow-x: auto; scrollbar-width: none; }
|
|
107
112
|
.tabs::-webkit-scrollbar { display: none; }
|
|
108
|
-
.tabs button { border: 0; border-
|
|
109
|
-
.tabs button:hover { color: var(--text); }
|
|
110
|
-
.tabs button.on { color: var(--text);
|
|
111
|
-
.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--shadow); }
|
|
112
|
-
.card + .card { margin-top:
|
|
113
|
-
.card > h2 { margin: 0; padding:
|
|
113
|
+
.tabs button { border: 0; border-radius: var(--r-sm); background: none; box-shadow: none; padding: 6px 12px; color: var(--dim); flex: none; }
|
|
114
|
+
.tabs button:hover { color: var(--text); background: none; }
|
|
115
|
+
.tabs button.on { color: var(--text); background: var(--panel); box-shadow: var(--shadow), 0 0 0 1px var(--line); }
|
|
116
|
+
.card { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--shadow); overflow: clip; }
|
|
117
|
+
.card + .card { margin-top: 20px; }
|
|
118
|
+
.card > h2 { margin: 0; padding: 14px 18px; font-size: 14px; font-weight: 600; border-bottom: 1px solid var(--line); display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
|
|
114
119
|
.card > h2 .actions { font-weight: 400; }
|
|
115
120
|
.card > h2 .sub { font-weight: 400; color: var(--dim); }
|
|
116
|
-
.card > .lede { padding: 10px
|
|
121
|
+
.card > .lede { padding: 10px 18px; color: var(--dim); font-size: var(--fs); border-bottom: 1px solid var(--line); }
|
|
117
122
|
|
|
118
123
|
/* "⋯" menus: one floating list at a time, placed by openMenu(). */
|
|
119
|
-
button.more { padding-inline:
|
|
120
|
-
.menu { position: fixed; z-index: 30; min-width:
|
|
121
|
-
.menu button { border: 0; background: none; text-align: left; padding: 6px 10px; border-radius: var(--r-sm); display: flex; justify-content: space-between; gap: 16px; }
|
|
122
|
-
.menu button:hover { background: var(--
|
|
123
|
-
.menu button:focus-visible { background: var(--accent-bg);
|
|
124
|
+
button.more { padding-inline: 8px; letter-spacing: .05em; }
|
|
125
|
+
.menu { position: fixed; z-index: 30; min-width: 190px; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); box-shadow: var(--shadow-lg); padding: 5px; display: grid; animation: pop .12s ease-out; }
|
|
126
|
+
.menu button { border: 0; background: none; box-shadow: none; font-weight: 450; text-align: left; padding: 6px 10px; border-radius: var(--r-sm); display: flex; justify-content: space-between; gap: 16px; }
|
|
127
|
+
.menu button:hover { background: var(--hover); }
|
|
128
|
+
.menu button:focus-visible { background: var(--accent-bg); box-shadow: none; }
|
|
124
129
|
.menu button.danger:hover { background: var(--bad-bg); }
|
|
125
130
|
.menu button .k { color: var(--faint); font-family: var(--mono); font-size: var(--fs-xs); }
|
|
126
131
|
.menu hr { border: 0; border-top: 1px solid var(--line); margin: 4px 2px; }
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.home
|
|
130
|
-
.home
|
|
131
|
-
.
|
|
132
|
-
.
|
|
133
|
-
.tile
|
|
132
|
+
@keyframes pop { from { opacity: 0; transform: translateY(-3px) scale(.98); } }
|
|
133
|
+
|
|
134
|
+
.home { max-width: 880px; margin: 0 auto; padding-top: 24px; }
|
|
135
|
+
.home h1 { margin: 0 0 6px; font-size: 28px; font-weight: 650; letter-spacing: -.025em; }
|
|
136
|
+
.home > p { color: var(--dim); margin: 0 0 24px; }
|
|
137
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
|
|
138
|
+
.tile { display: block; text-align: left; padding: 16px 18px; border-radius: var(--r-lg); background: var(--panel); border: 1px solid var(--line); box-shadow: var(--shadow); width: 100%; transition: border-color .12s, box-shadow .15s, transform .15s; }
|
|
139
|
+
.tile:hover { background: var(--panel); border-color: var(--line-strong); box-shadow: var(--shadow-lg); transform: translateY(-1px); }
|
|
134
140
|
.tile .n { font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 6px; }
|
|
135
|
-
.tile .t { color: var(--
|
|
136
|
-
.tile.add { border
|
|
137
|
-
.
|
|
138
|
-
.cli
|
|
141
|
+
.tile .t { color: var(--faint); font-size: var(--fs-sm); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
142
|
+
.tile.add { border: 1.5px dashed var(--line-strong); background: none; box-shadow: none; color: var(--dim); display: grid; place-items: center; min-height: 68px; }
|
|
143
|
+
.tile.add:hover { color: var(--accent); border-color: var(--accent); background: var(--accent-bg); transform: none; box-shadow: none; }
|
|
144
|
+
.cli { margin-top: 32px; padding: 16px 18px; border-radius: var(--r-lg); border: 1px solid var(--line); background: var(--hover); font-size: var(--fs); color: var(--dim); }
|
|
145
|
+
.cli pre { margin: 10px 0 0; font: 12.5px/1.7 var(--mono); color: var(--text); white-space: pre-wrap; }
|
|
139
146
|
.cli pre span { color: var(--faint); }
|
|
140
147
|
|
|
141
|
-
.drow { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px 16px; padding:
|
|
148
|
+
.drow { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px 16px; padding: 12px 0; border-bottom: 1px solid var(--line); }
|
|
142
149
|
.drow:first-child { padding-top: 0; }
|
|
150
|
+
.drow:last-child { border-bottom: 0; padding-bottom: 0; }
|
|
143
151
|
.drow > div:first-child { flex: 1 1 260px; min-width: 0; }
|
|
144
152
|
.drow .actions { flex: none; }
|
|
145
153
|
.site-name { display: flex; align-items: center; gap: 2px; white-space: nowrap; }
|
|
146
154
|
td .chips.show-sm { margin-top: 4px; }
|
|
147
|
-
.card .body { padding:
|
|
155
|
+
.card .body { padding: 16px 18px; }
|
|
148
156
|
table { border-collapse: collapse; width: 100%; }
|
|
149
|
-
th { text-align: left; font-weight:
|
|
150
|
-
td { padding:
|
|
157
|
+
th { text-align: left; font-weight: 500; font-size: var(--fs-sm); color: var(--faint); padding: 9px 14px; border-bottom: 1px solid var(--line); background: color-mix(in srgb, var(--hover) 55%, transparent); }
|
|
158
|
+
td { padding: 11px 14px; border-bottom: 1px solid var(--line); vertical-align: middle; }
|
|
151
159
|
tr:last-child td { border-bottom: 0; }
|
|
152
160
|
tr.click { cursor: pointer; }
|
|
153
|
-
tr.click:hover td { background: var(--
|
|
161
|
+
tr.click:hover td { background: var(--hover); }
|
|
154
162
|
td.acts { text-align: right; white-space: nowrap; }
|
|
155
163
|
.nowrap { white-space: nowrap; }
|
|
156
164
|
td.acts button { margin-left: 4px; }
|
|
157
165
|
.scroll { overflow-x: auto; }
|
|
158
|
-
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--faint); margin-right:
|
|
159
|
-
.dot.up { background: var(--ok); } .dot.down { background: var(--bad); } .dot.warn { background: var(--warn); }
|
|
160
|
-
.badge { display: inline-block; font-size: var(--fs-xs); padding:
|
|
166
|
+
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--faint); margin-right: 7px; vertical-align: 1px; }
|
|
167
|
+
.dot.up { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-bg); } .dot.down { background: var(--bad); box-shadow: 0 0 0 3px var(--bad-bg); } .dot.warn { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-bg); }
|
|
168
|
+
.badge { display: inline-block; font-size: var(--fs-xs); font-weight: 500; line-height: 1.6; padding: 0 8px; border-radius: 99px; background: var(--hover); color: var(--dim); border: 1px solid var(--line); }
|
|
161
169
|
.badge.ok { background: var(--ok-bg); color: var(--ok); border-color: transparent; }
|
|
162
170
|
.badge.warn { background: var(--warn-bg); color: var(--warn); border-color: transparent; }
|
|
163
171
|
.badge.bad { background: var(--bad-bg); color: var(--bad); border-color: transparent; }
|
|
164
172
|
.badge.accent { background: var(--accent-bg); color: var(--accent); border-color: transparent; }
|
|
165
173
|
.dim { color: var(--dim); } .faint { color: var(--faint); }
|
|
166
|
-
.note { padding:
|
|
174
|
+
.note { padding: 11px 14px; border-radius: var(--r); background: var(--warn-bg); color: var(--warn); margin-bottom: 16px; border: 1px solid color-mix(in srgb, currentColor 18%, transparent); }
|
|
167
175
|
.note.bad { background: var(--bad-bg); color: var(--bad); }
|
|
168
176
|
.note.info { background: var(--accent-bg); color: var(--accent); }
|
|
169
|
-
.kv { display: grid; grid-template-columns: max-content 1fr; gap:
|
|
170
|
-
.kv dt { color: var(--
|
|
171
|
-
pre.block { background: var(--
|
|
172
|
-
.detail td { background: var(--
|
|
177
|
+
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 6px 18px; font-size: var(--fs); margin: 0; }
|
|
178
|
+
.kv dt { color: var(--faint); } .kv dd { margin: 0; }
|
|
179
|
+
pre.block { background: var(--hover); border: 1px solid var(--line); padding: 10px 12px; border-radius: var(--r-sm); overflow-x: auto; font-size: var(--fs-sm); margin: 6px 0 0; }
|
|
180
|
+
.detail td { background: color-mix(in srgb, var(--hover) 60%, transparent); }
|
|
173
181
|
.releases, .services { margin: 0; padding: 0; list-style: none; }
|
|
174
|
-
.services li { display: flex; align-items: center; gap: 8px; padding:
|
|
175
|
-
.releases li { display: flex; align-items: center; gap: 10px; padding:
|
|
176
|
-
.run { padding:
|
|
182
|
+
.services li { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
|
|
183
|
+
.releases li { display: flex; align-items: center; gap: 10px; padding: 5px 0; }
|
|
184
|
+
.run { padding: 14px 18px; border-bottom: 1px solid var(--line); }
|
|
177
185
|
.run:last-child { border-bottom: 0; }
|
|
178
186
|
.run .top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
179
187
|
.run .cmd { font-family: var(--mono); font-weight: 600; font-size: var(--fs); }
|
|
@@ -184,49 +192,50 @@ pre.block { background: var(--bg); border: 1px solid var(--line); padding: 10px
|
|
|
184
192
|
.steps li.undone .what { text-decoration: line-through; }
|
|
185
193
|
.steps li button { opacity: 0; }
|
|
186
194
|
.steps li:hover button, .steps li:focus-within button { opacity: 1; }
|
|
187
|
-
.form-row { display: flex; gap:
|
|
195
|
+
.form-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: end; }
|
|
188
196
|
.form-row label { flex: 1 1 140px; }
|
|
189
|
-
label { display: block; font-size: var(--fs-sm); color: var(--dim); }
|
|
190
|
-
label > input, label > select, label > textarea { margin-top:
|
|
191
|
-
.check { display: flex; gap: 8px; align-items: flex-start; color: var(--text); font-size: var(--fs); }
|
|
192
|
-
.check input { width: auto; margin-top: 3px; }
|
|
193
|
-
.hint { font-size: var(--fs-sm); color: var(--faint); margin-top:
|
|
194
|
-
.spin { display: inline-block; width: 12px; height: 12px; border: 2px solid var(--line); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; vertical-align: -2px; }
|
|
197
|
+
label { display: block; font-size: var(--fs-sm); font-weight: 500; color: var(--dim); }
|
|
198
|
+
label > input, label > select, label > textarea { margin-top: 5px; font-weight: 400; }
|
|
199
|
+
.check { display: flex; gap: 8px; align-items: flex-start; color: var(--text); font-size: var(--fs); font-weight: 400; }
|
|
200
|
+
.check input { width: auto; margin-top: 3px; accent-color: var(--accent-solid); }
|
|
201
|
+
.hint { font-size: var(--fs-sm); font-weight: 400; color: var(--faint); margin-top: 4px; }
|
|
202
|
+
.spin { display: inline-block; width: 12px; height: 12px; border: 2px solid var(--line-strong); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite; vertical-align: -2px; }
|
|
195
203
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
196
|
-
@media (prefers-reduced-motion: reduce) { .spin { animation-duration: 3s; } }
|
|
197
|
-
.loading { color: var(--dim); padding:
|
|
204
|
+
@media (prefers-reduced-motion: reduce) { .spin { animation-duration: 3s; } .menu, dialog[open] { animation: none; } .tile:hover { transform: none; } }
|
|
205
|
+
.loading { color: var(--dim); padding: 40px 0; text-align: center; }
|
|
198
206
|
|
|
199
207
|
[hidden] { display: none !important; }
|
|
200
208
|
aside.job { border-left: 1px solid var(--line); background: var(--panel); display: flex; flex-direction: column; min-width: 0; }
|
|
201
|
-
.job-head { padding: 10px
|
|
202
|
-
.job-head .title { font-family: var(--mono); font-size: var(--fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
|
203
|
-
.job-out { flex: 1; overflow: auto; background: var(--term); color: var(--term-text); font: 12.5px/1.
|
|
204
|
-
.job-out .c-cyan { color: #67d3e8; } .job-out .c-green { color: #6ee7a0; } .job-out .c-yellow { color: #f5c76a; } .job-out .c-red { color: #ff8a8a; } .job-out .c-dim { color: #
|
|
205
|
-
.job-foot { padding: 10px
|
|
209
|
+
.job-head { padding: 10px 14px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 8px; min-height: 48px; }
|
|
210
|
+
.job-head .title { font-family: var(--mono); font-size: var(--fs-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
|
211
|
+
.job-out { flex: 1; overflow: auto; background: var(--term); color: var(--term-text); font: 12.5px/1.6 var(--mono); padding: 12px 14px; white-space: pre-wrap; word-break: break-word; }
|
|
212
|
+
.job-out .c-cyan { color: #67d3e8; } .job-out .c-green { color: #6ee7a0; } .job-out .c-yellow { color: #f5c76a; } .job-out .c-red { color: #ff8a8a; } .job-out .c-dim { color: #7b7b86; }
|
|
213
|
+
.job-foot { padding: 10px 14px; border-top: 1px solid var(--line); display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
|
206
214
|
.job-foot .status { margin-right: auto; font-size: var(--fs); }
|
|
207
|
-
.joblist { border-top: 1px solid var(--line); max-height: 28%; overflow: auto; }
|
|
208
|
-
.joblist button { display: flex; width: 100%; gap: 8px; border: 0; border-radius:
|
|
215
|
+
.joblist { border-top: 1px solid var(--line); max-height: 28%; overflow: auto; padding: 4px; }
|
|
216
|
+
.joblist button { display: flex; width: 100%; gap: 8px; border: 0; border-radius: var(--r-sm); box-shadow: none; text-align: left; background: none; padding: 6px 10px; font-family: var(--mono); font-size: var(--fs-sm); font-weight: 400; color: var(--dim); }
|
|
209
217
|
.joblist button span:last-child { overflow: hidden; text-overflow: ellipsis; }
|
|
210
|
-
.joblist button.on { background: var(--accent-bg); color: var(--text);
|
|
218
|
+
.joblist button.on { background: var(--accent-bg); color: var(--text); }
|
|
211
219
|
.joblist .ok { color: var(--ok); } .joblist .bad { color: var(--bad); }
|
|
212
220
|
|
|
213
|
-
dialog { border: 1px solid var(--line); border-radius: var(--r-lg); padding: 0; background: var(--panel); color: var(--text); box-shadow: var(--shadow); width: min(520px, calc(100vw - 32px)); }
|
|
214
|
-
dialog
|
|
215
|
-
dialog
|
|
216
|
-
dialog
|
|
221
|
+
dialog { border: 1px solid var(--line); border-radius: var(--r-lg); padding: 0; background: var(--panel); color: var(--text); box-shadow: var(--shadow-lg); width: min(520px, calc(100vw - 32px)); }
|
|
222
|
+
dialog[open] { animation: pop .15s ease-out; }
|
|
223
|
+
dialog::backdrop { background: rgb(9 9 11 / .4); backdrop-filter: blur(2px); }
|
|
224
|
+
dialog form { padding: 22px 24px; display: grid; gap: 14px; }
|
|
225
|
+
dialog h2 { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -.01em; }
|
|
217
226
|
dialog .text { color: var(--dim); white-space: pre-wrap; font-size: var(--fs); }
|
|
218
|
-
dialog .btns { display: flex; justify-content: flex-end; gap: 8px; margin-top:
|
|
219
|
-
fieldset { border: 1px solid var(--line); border-radius: var(--r); padding:
|
|
227
|
+
dialog .btns { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; }
|
|
228
|
+
fieldset { border: 1px solid var(--line); border-radius: var(--r); padding: 12px 14px; margin: 0; display: grid; gap: 10px; }
|
|
220
229
|
legend { font-size: var(--fs-sm); font-weight: 600; padding: 0 4px; }
|
|
221
|
-
.toast { position: fixed; inset: auto auto
|
|
222
|
-
.toast button { margin-left: 10px; background: none; border-color: color-mix(in srgb, var(--bg) 40%, transparent); color: inherit; }
|
|
230
|
+
.toast { position: fixed; inset: auto auto 20px 50%; transform: translateX(-50%); margin: 0; border: 0; background: var(--text); color: var(--bg); padding: 10px 16px; border-radius: var(--r); box-shadow: var(--shadow-lg); font-size: var(--fs); font-weight: 500; max-width: min(560px, calc(100vw - 32px)); }
|
|
231
|
+
.toast button { margin-left: 10px; background: none; box-shadow: none; border-color: color-mix(in srgb, var(--bg) 40%, transparent); color: inherit; }
|
|
223
232
|
.sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
224
|
-
.row-toggle { border: 0; background: none; padding: 2px 4px; margin-left: -4px; display: inline-flex; align-items: center; gap: 4px; color: inherit; font-weight: 600; }
|
|
225
|
-
.row-toggle:hover { background: none; text-decoration: underline; }
|
|
233
|
+
.row-toggle { border: 0; background: none; box-shadow: none; padding: 2px 4px; margin-left: -4px; display: inline-flex; align-items: center; gap: 4px; color: inherit; font-weight: 600; }
|
|
234
|
+
.row-toggle:hover { background: none; text-decoration: underline; text-underline-offset: 2px; }
|
|
226
235
|
.jobs-btn .spin { width: 10px; height: 10px; margin-right: 4px; }
|
|
227
236
|
.dlg-err { margin: 0; }
|
|
228
237
|
.server-pick { display: none; }
|
|
229
|
-
.facts { display: flex; flex-wrap: wrap; gap: 4px
|
|
238
|
+
.facts { display: flex; flex-wrap: wrap; gap: 4px 16px; color: var(--faint); font-size: var(--fs-sm); margin-top: 6px; }
|
|
230
239
|
.facts b { color: var(--text); font-weight: 500; }
|
|
231
240
|
.chips { display: flex; flex-direction: column; gap: 3px; align-items: flex-start; }
|
|
232
241
|
.chip { display: inline-flex; align-items: center; gap: 2px; max-width: 100%; }
|
|
@@ -236,14 +245,14 @@ legend { font-size: var(--fs-sm); font-weight: 600; padding: 0 4px; }
|
|
|
236
245
|
.lock { font-size: var(--fs-xs); margin-left: 4px; }
|
|
237
246
|
dialog.wide { width: min(680px, calc(100vw - 32px)); }
|
|
238
247
|
.list { display: grid; border: 1px solid var(--line); border-radius: var(--r); max-height: 260px; overflow: auto; }
|
|
239
|
-
.list .row { display: flex; gap: 10px; align-items: baseline; border: 0; border-radius: 0; border-bottom: 1px solid var(--line); text-align: left; background: none; padding:
|
|
248
|
+
.list .row { display: flex; gap: 10px; align-items: baseline; border: 0; border-radius: 0; box-shadow: none; font-weight: 400; border-bottom: 1px solid var(--line); text-align: left; background: none; padding: 7px 12px; min-width: 0; }
|
|
240
249
|
.list .row:last-child { border-bottom: 0; }
|
|
241
|
-
.list .row:hover { background: var(--
|
|
250
|
+
.list .row:hover { background: var(--hover); }
|
|
242
251
|
.list .row .mono { margin-left: auto; overflow: hidden; text-overflow: ellipsis; }
|
|
243
252
|
.crumbs { display: flex; flex-wrap: wrap; gap: 2px; align-items: center; font-size: var(--fs-sm); }
|
|
244
|
-
.crumbs button { padding: 1px
|
|
245
|
-
.detected { font-size: var(--fs); padding:
|
|
246
|
-
.drop { border: 1.5px dashed var(--line); border-radius: var(--r); padding:
|
|
253
|
+
.crumbs button { padding: 1px 6px; }
|
|
254
|
+
.detected { font-size: var(--fs); padding: 10px 12px; border-radius: var(--r); background: var(--hover); }
|
|
255
|
+
.drop { border: 1.5px dashed var(--line-strong); border-radius: var(--r); padding: 14px; text-align: center; color: var(--dim); font-size: var(--fs); }
|
|
247
256
|
.drop label { display: inline; color: var(--accent); cursor: pointer; font-size: var(--fs); }
|
|
248
257
|
body.dragging::after {
|
|
249
258
|
content: "Drop a folder or .zip to deploy it"; position: fixed; inset: 12px; z-index: 20; display: grid; place-items: center;
|
|
@@ -252,19 +261,20 @@ body.dragging::after {
|
|
|
252
261
|
|
|
253
262
|
@media (max-width: 1100px) {
|
|
254
263
|
.app.with-job { grid-template-columns: 220px minmax(0, 1fr); }
|
|
255
|
-
aside.job { position: fixed; inset:
|
|
264
|
+
aside.job { position: fixed; inset: 56px 0 0 auto; width: min(560px, 100vw); z-index: 5; box-shadow: var(--shadow-lg); }
|
|
256
265
|
}
|
|
257
266
|
@media (max-width: 760px) {
|
|
258
|
-
.app, .app.with-job { grid-template-columns: minmax(0, 1fr); grid-template-rows:
|
|
259
|
-
header { padding: 0
|
|
267
|
+
.app, .app.with-job { grid-template-columns: minmax(0, 1fr); grid-template-rows: 56px auto minmax(0, 1fr); }
|
|
268
|
+
header { padding: 0 14px; gap: 8px; }
|
|
260
269
|
.brand small { display: none; }
|
|
261
|
-
.project button { padding: 5px
|
|
270
|
+
.project button { padding: 5px 10px; font-size: var(--fs); white-space: nowrap; }
|
|
262
271
|
nav.servers { display: none; }
|
|
263
|
-
.server-pick { display: block; padding:
|
|
264
|
-
.tabs { display: flex; position: sticky; top: -14px; z-index: 2;
|
|
265
|
-
main { padding:
|
|
272
|
+
.server-pick { display: block; padding: 12px 16px 0; }
|
|
273
|
+
.tabs { display: flex; position: sticky; top: -14px; z-index: 2; }
|
|
274
|
+
main { padding: 16px 16px 40px; }
|
|
275
|
+
.home h1 { font-size: 24px; }
|
|
266
276
|
.project .path { display: none; }
|
|
267
|
-
aside.job { inset:
|
|
277
|
+
aside.job { inset: 56px 0 0 0; width: 100vw; }
|
|
268
278
|
td.acts { white-space: normal; }
|
|
269
279
|
td.acts button { margin: 2px 0 2px 4px; }
|
|
270
280
|
}
|
|
@@ -1012,7 +1022,6 @@ function deployDialog(det, o = {}) {
|
|
|
1012
1022
|
if (!n) return invalid("name", "Give the site a name");
|
|
1013
1023
|
if (!v.server) return invalid("server", "Pick a server");
|
|
1014
1024
|
const domains = v.domains.split(/[\s,]+/).filter(Boolean);
|
|
1015
|
-
if (v.noDomain && domains.length) return invalid("domains", "No domain is ticked below: clear these, or untick it");
|
|
1016
1025
|
const argv = ["deploy", "-y", ...sv(v.server), "-n", n, ...(v.noDomain ? ["--no-domain"] : domains.flatMap((d) => ["-d", d]))];
|
|
1017
1026
|
if (v.build?.trim() && v.build.trim() !== p?.build) argv.push("--build", v.build.trim());
|
|
1018
1027
|
if (v.spa && !p?.spa) argv.push("--spa");
|
|
@@ -1030,6 +1039,16 @@ function deployDialog(det, o = {}) {
|
|
|
1030
1039
|
run(argv, { server: v.server, cwd: det.path, after: openAfter });
|
|
1031
1040
|
},
|
|
1032
1041
|
});
|
|
1042
|
+
noDomainToggle();
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/** The open dialog's "no domain" box greys out its domains field: ticked, the site gets none. */
|
|
1046
|
+
function noDomainToggle() {
|
|
1047
|
+
const box = $("#f-noDomain"), input = $("#f-domains");
|
|
1048
|
+
if (!box || !input) return;
|
|
1049
|
+
const sync = () => (input.disabled = box.checked);
|
|
1050
|
+
box.addEventListener("change", sync);
|
|
1051
|
+
sync();
|
|
1033
1052
|
}
|
|
1034
1053
|
|
|
1035
1054
|
/** After a deploy: offer the address it printed. */
|
|
@@ -1169,20 +1188,22 @@ function domainsDialog(n, m) {
|
|
|
1169
1188
|
text: `Each domain needs an A record pointing at ${ips.join(", ") || "the server"}. Traefik fetches the certificate once it resolves.`,
|
|
1170
1189
|
fields: [
|
|
1171
1190
|
{ name: "domains", label: "Domains (space separated)", value: m.domains.join(" ") },
|
|
1191
|
+
{ name: "noDomain", label: "No domain: reach it through a tunnel only", type: "check", value: !m.domains.length, hint: "Nothing is served on the internet; the site keeps running. Open via tunnel reaches it." },
|
|
1172
1192
|
...(main && !m.domains.includes(main) ? [{ name: "apex", label: `Also serve ${main}`, type: "check", value: false, hint: servedByOther(n, m.name, main) ? `${servedByOther(n, m.name, main)} serves it now and hands it over.` : "The main domain itself." }] : []),
|
|
1173
1193
|
...(free && !m.domains.includes(free) ? [{ name: "free", label: main ? `Also add ${free}` : `Also add the free address ${free}`, type: "check", value: false, hint: main ? `Under the main domain: works once *.${main} points at the server.` : "Works right away, no DNS setup." }] : []),
|
|
1174
1194
|
],
|
|
1175
1195
|
ok: "Save",
|
|
1176
1196
|
onOk: (v) => {
|
|
1177
|
-
const domains = v.domains.split(/[\s,]+/).filter(Boolean);
|
|
1178
|
-
if (v.free) domains.push(free);
|
|
1179
|
-
if (v.apex) domains.push(main);
|
|
1197
|
+
const domains = v.noDomain ? [] : v.domains.split(/[\s,]+/).filter(Boolean);
|
|
1198
|
+
if (v.free && !v.noDomain) domains.push(free);
|
|
1199
|
+
if (v.apex && !v.noDomain) domains.push(main);
|
|
1180
1200
|
// None left: the site keeps running, unrouted.
|
|
1181
1201
|
const args = domains.length ? domains.flatMap((d) => ["--set", d]) : m.domains.flatMap((d) => ["--rm", d]);
|
|
1182
1202
|
if (!args.length) return;
|
|
1183
1203
|
run(["domains", m.name, ...sv(n), ...args, "-y"], { server: n });
|
|
1184
1204
|
},
|
|
1185
1205
|
});
|
|
1206
|
+
noDomainToggle();
|
|
1186
1207
|
}
|
|
1187
1208
|
|
|
1188
1209
|
function authDialog(n, m) {
|