Card layout for tables on phones and tablets; fit terminal bar

Sideways-scrolling tables only showed a few of the 11 columns at a time on
a phone or iPad. Below 1100px (where the nav already collapses) each row
now renders as a card: the device as the title and every other column as
a labelled line, one card per row on phones and two or more on tablets.

- web/mobile.js copies column names onto cells (data-label) for the cards
  and re-applies them as the dashboard re-renders; no-op on wide screens
- Column filters fold behind a "Show filters" button with an active count
- Custom View keeps its sort/filter header as a strip of chips
- Terminal: Follow and the "your actions (simulated)" legend move into the
  terminal window's title bar; tabs read "Server" / "Worker" on phones, so
  LIVE, the legend and Follow are all visible on small screens
This commit is contained in:
Johan Sandoval committed 2026-10-06 16:29:23 +00:00
1 parent 5a7fd4f2df
commit 2cbb03894b
5 files changed
+227 -24

No files matched your search

+8 -6
View File
@@ -42,9 +42,12 @@ body.usmt-term-open { padding-bottom: calc(var(--usmt-term-h, 340px) + 72px); }
.usmt-term-window { height: var(--usmt-term-h); display: flex; flex-direction: column; background: #012456; }
.usmt-term.collapsed .usmt-term-window { display: none; }
.usmt-term-titlebar {
height: 26px; flex: 0 0 26px; display: flex; align-items: center; padding: 0 12px;
height: 32px; flex: 0 0 32px; display: flex; align-items: center; gap: 10px; padding: 0 8px 0 12px;
background: #0c0c0c; color: #cccccc; font-size: 0.78em; border-bottom: 1px solid #1b1b1b;
}
.usmt-term-wtitle { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.usmt-term-titlebar .usmt-term-legend { font-size: 1em; }
.usmt-term-titlebar .usmt-term-follow { font-size: 1em; padding: 3px 10px; }
.usmt-term-screens { flex: 1; min-height: 0; position: relative; }
.usmt-term-body {
display: none; position: absolute; inset: 0; overflow: auto; padding: 8px 12px 14px 18px;
@@ -103,12 +106,11 @@ body.usmt-term-open { padding-bottom: calc(var(--usmt-term-h, 340px) + 72px); }
.usmt-demo-actions .usmt-demo-reset { color: #e74c3c; border-color: #e74c3c; margin-right: auto; }
@media (max-width: 700px) {
.usmt-term-legend, .usmt-term-follow { display: none; }
.usmt-term-bar { gap: 4px; padding: 0 6px; }
.t-long { display: none; } /* "Server" / "Worker", shorter titles */
.usmt-term-bar { gap: 4px; padding: 0 8px; }
.usmt-term-toggle { padding: 4px 6px; }
.usmt-term-tabs { flex: 1 1 auto; overflow: hidden; }
.usmt-term-tab { padding: 4px 6px; font-size: 0.72em; }
.usmt-term-live { font-size: 0; gap: 0; } /* just the pulsing dot */
.usmt-term-tabs { flex: 0 1 auto; min-width: 0; }
.usmt-term-tab { padding: 4px 10px; font-size: 0.8em; }
.usmt-term { --usmt-term-h: 45vh; }
.usmt-term-body { font-size: 11.5px; padding: 6px 8px 10px 14px; }
.usmt-term-titlebar { font-size: 0.7em; }
+14 -5
View File
@@ -89,11 +89,13 @@
</button>
<div class="usmt-term-tabs"></div>
<span class="usmt-term-live" title="Streaming the real PowerShell console output from the server"><i></i>LIVE</span>
${this.demo ? '<span class="usmt-term-legend" title="Lines marked with a dot were produced by your own actions. They are simulated in your browser and never reach the real services."><i class="usmt-term-legend-dot"></i>your actions (simulated)</span>' : ''}
<button class="usmt-term-btn usmt-term-follow active" type="button" title="Auto-scroll">Follow</button>
</div>
<div class="usmt-term-window">
<div class="usmt-term-titlebar"><span class="usmt-term-wtitle"></span></div>
<div class="usmt-term-titlebar">
<span class="usmt-term-wtitle"></span>
${this.demo ? '<span class="usmt-term-legend" title="Lines marked with a dot were produced by your own actions. They are simulated in your browser and never reach the real services."><i class="usmt-term-legend-dot"></i><span class="t-long">your actions&nbsp;</span>(simulated)</span>' : ''}
<button class="usmt-term-btn usmt-term-follow active" type="button" title="Keep scrolled to the newest output">Follow</button>
</div>
<div class="usmt-term-screens"></div>
</div>`;
document.body.appendChild(dock);
@@ -103,7 +105,11 @@
const screens = dock.querySelector('.usmt-term-screens');
TERMINALS.forEach(t => {
const b = document.createElement('button');
b.type = 'button'; b.className = 'usmt-term-tab'; b.dataset.id = t.id; b.textContent = t.title;
b.type = 'button'; b.className = 'usmt-term-tab'; b.dataset.id = t.id; b.title = t.title;
// "USMT-Server.ps1" on wide screens, just "Server" on phones
const m = t.title.match(/^(USMT-)(.+)(\.ps1)$/);
if (m) b.innerHTML = `<span class="t-long">${m[1]}</span>${m[2]}<span class="t-long">${m[3]}</span>`;
else b.textContent = t.title;
b.onclick = () => { this.select(t.id); if (!this.open) this.toggle(true); };
tabs.appendChild(b);
const body = document.createElement('div');
@@ -143,7 +149,10 @@
this.el.querySelectorAll('.usmt-term-tab').forEach(b => b.classList.toggle('active', b.dataset.id === id));
this.el.querySelectorAll('.usmt-term-body').forEach(b => b.classList.toggle('active', b.dataset.id === id));
const t = TERMINALS.find(x => x.id === id);
this.el.querySelector('.usmt-term-wtitle').textContent = (this.demo ? 'Administrator: PowerShell (demo container) - ' : 'Administrator: PowerShell - ') + t.title;
const wtitle = this.el.querySelector('.usmt-term-wtitle');
wtitle.innerHTML = '<span class="t-long"></span><span></span>';
wtitle.firstChild.textContent = this.demo ? 'Administrator: PowerShell (demo container) - ' : 'Administrator: PowerShell - ';
wtitle.lastChild.textContent = t.title;
this.el.querySelector(`.usmt-term-tab[data-id="${id}"]`).classList.remove('unread');
try { localStorage.setItem('usmt_term_tab', id); } catch (e) {}
this.poll(id);