@monster-orange/den 0.4.10 → 0.4.11
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/CHANGELOG.md +25 -0
- package/README.md +7 -7
- package/package.json +1 -1
- package/src/ui/server/public/header.js +28 -19
- package/src/ui/server/public/i18n/es.js +4 -5
- package/src/ui/server/public/i18n/ja.js +4 -5
- package/src/ui/server/public/local-dev.html +1 -1
- package/src/ui/server/public/shell.js +14 -20
- package/src/ui/server/public/workspace-picker.js +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to Den are documented here. Each release lists the
|
|
|
4
4
|
config and manifest changes a distribution must make to move to it under
|
|
5
5
|
**Migrations**.
|
|
6
6
|
|
|
7
|
+
## [0.4.11] - 2026-11-07
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **Picking a workspace in the top bar also makes it the CLI default.**
|
|
12
|
+
There is one "current workspace": choosing one in the Workspace menu
|
|
13
|
+
sets what pages show and where `<cli>` commands run (workspaces.yml, as
|
|
14
|
+
Local Dev's "Make CLI default" does). "All active (n)" changes only what
|
|
15
|
+
pages show; the CLI default stays. If the CLI default can't be set,
|
|
16
|
+
nothing changes and the error shows.
|
|
17
|
+
- **The menu's separate "Make <ws> the CLI default" item is gone**, since
|
|
18
|
+
picking a workspace does it. The "CLI default" tag stays on its row, and
|
|
19
|
+
Local Dev keeps its "Make CLI default" button, to set it without
|
|
20
|
+
changing what pages show. Each workspace row's tooltip says so.
|
|
21
|
+
- **Picking the workspace already in view makes it the CLI default** when
|
|
22
|
+
it isn't one (after Local Dev's "Make CLI default" or `<cli> workspace
|
|
23
|
+
focus` moved it). The view stays as it is.
|
|
24
|
+
- **Pages whose address names a workspace follow the pick.** Workspaces'
|
|
25
|
+
detail pane and breadcrumb now show the workspace the top bar picked,
|
|
26
|
+
not the one in the old `?ws=`.
|
|
27
|
+
|
|
28
|
+
### Migrations
|
|
29
|
+
|
|
30
|
+
None.
|
|
31
|
+
|
|
7
32
|
## [0.4.10] - 2026-11-07
|
|
8
33
|
|
|
9
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<picture>
|
|
13
|
-
<source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.
|
|
14
|
-
<img src="docs/images/0.4.
|
|
13
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/images/0.4.11/hero-dark.png">
|
|
14
|
+
<img src="docs/images/0.4.11/hero-light.png" width="100%" alt="Den's Graph page: the demo workspace's services grouped by application, all healthy, with the status chip and the left rail">
|
|
15
15
|
</picture>
|
|
16
16
|
|
|
17
17
|
## Set up by your agent
|
|
@@ -38,7 +38,7 @@ It doesn't replace your tools. Your repos, Docker, your test runners, your secre
|
|
|
38
38
|
yours. Den connects them and turns on good practice from the start.
|
|
39
39
|
|
|
40
40
|
<p align="center">
|
|
41
|
-
<img src="docs/images/0.4.
|
|
41
|
+
<img src="docs/images/0.4.11/up.gif" width="100%" alt="make up in the Graph page: every stack down, then demo-api, demo-web and demo-notes come up in dependency order until the status chip reads All 3 healthy">
|
|
42
42
|
</p>
|
|
43
43
|
|
|
44
44
|
## How it helps
|
|
@@ -68,10 +68,10 @@ yours. Den connects them and turns on good practice from the start.
|
|
|
68
68
|
lives in your own repo.
|
|
69
69
|
|
|
70
70
|
<p align="center">
|
|
71
|
-
<img src="docs/images/0.4.
|
|
72
|
-
<img src="docs/images/0.4.
|
|
73
|
-
<img src="docs/images/0.4.
|
|
74
|
-
<img src="docs/images/0.4.
|
|
71
|
+
<img src="docs/images/0.4.11/agents.png" width="49%" alt="The Agents tab: the demo workspace's sessions on the left, and one open in a terminal pane on the right, running demo-api's test and then the service">
|
|
72
|
+
<img src="docs/images/0.4.11/workspaces.png" width="49%" alt="The Workspaces page: the demo workspace's repos with their health, mode and Start, Stop, Restart and Logs actions">
|
|
73
|
+
<img src="docs/images/0.4.11/status-drawer.png" width="49%" alt="The status drawer: nothing needs attention, and every service in the demo workspace, by application, with its latency">
|
|
74
|
+
<img src="docs/images/0.4.11/edge.png" width="49%" alt="The Edge page: the edge container up, its ports and TLS, and every route it serves, Den's own and each stack's, with its upstream">
|
|
75
75
|
</p>
|
|
76
76
|
|
|
77
77
|
## Get started
|
package/package.json
CHANGED
|
@@ -792,10 +792,11 @@ export function groupReposByApplication(repos, apps) {
|
|
|
792
792
|
|
|
793
793
|
/**
|
|
794
794
|
* Feed the shell's Workspace part (shell.js #ws-pill, a menu): "All active"
|
|
795
|
-
* and each active workspace, the focused one marked. Choosing
|
|
796
|
-
*
|
|
797
|
-
*
|
|
798
|
-
*
|
|
795
|
+
* and each active workspace, the focused one marked. Choosing a workspace
|
|
796
|
+
* makes it the CLI default (POST /api/workspaces `focus`, workspaces.yml)
|
|
797
|
+
* and sets the view (POST /api/scope, a preference) for every page: one
|
|
798
|
+
* "current workspace" (Ward's decision, 0.4.11). "All active" sets only the
|
|
799
|
+
* view; the CLI default stays.
|
|
799
800
|
*
|
|
800
801
|
* The arguments are dev.17's (an env select, an onSwitch callback) and are
|
|
801
802
|
* no longer used: the view applies to every env.
|
|
@@ -822,7 +823,6 @@ async function _renderScopeMenu() {
|
|
|
822
823
|
sh.setWorkspaces({
|
|
823
824
|
...buildScopeMenuModel(pageScope(scope, data), data, t),
|
|
824
825
|
onSelect: _switchScope,
|
|
825
|
-
onFocus: _setFocus,
|
|
826
826
|
});
|
|
827
827
|
} catch {
|
|
828
828
|
sh.setWorkspaces(null);
|
|
@@ -852,25 +852,34 @@ function pageScope(scope, data) {
|
|
|
852
852
|
}
|
|
853
853
|
|
|
854
854
|
/**
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
*
|
|
855
|
+
* A choice replaces ?ws= (All active drops it, a workspace sets it) on the
|
|
856
|
+
* Graph and Agents pages, and on any page whose URL names a workspace
|
|
857
|
+
* (Workspaces' detail pane), so the reload shows the choice rather than
|
|
858
|
+
* the old ?ws=. The history entry is replaced, not pushed: Back does not
|
|
859
|
+
* return to a view the picker has left.
|
|
859
860
|
*/
|
|
860
861
|
function followScopeInUrl(value) {
|
|
861
|
-
if (!WS_URL_PAGES.has(location.pathname)) return;
|
|
862
862
|
const u = new URL(location.href);
|
|
863
|
+
if (!WS_URL_PAGES.has(location.pathname) && !u.searchParams.has("ws")) return;
|
|
863
864
|
if (value === "all") u.searchParams.delete("ws");
|
|
864
865
|
else u.searchParams.set("ws", value);
|
|
865
866
|
history.replaceState(history.state, "", u.pathname + u.search + u.hash);
|
|
866
867
|
}
|
|
867
868
|
|
|
868
869
|
/**
|
|
869
|
-
* A
|
|
870
|
-
*
|
|
871
|
-
*
|
|
870
|
+
* A workspace: first the CLI default (workspaces.yml), then the view. When
|
|
871
|
+
* the CLI default can't be set, nothing changes and the error shows. "All
|
|
872
|
+
* active": the view only. POST /api/scope stores the view (user.yml
|
|
873
|
+
* `view`), then the page reloads to show it whole. The workspace already
|
|
874
|
+
* in view (`inView`): the CLI default only, the view untouched. No
|
|
875
|
+
* service, container, daemon or route changes.
|
|
872
876
|
*/
|
|
873
|
-
async function _switchScope(value) {
|
|
877
|
+
async function _switchScope(value, focus, inView) {
|
|
878
|
+
if (value !== "all" && value !== focus && !(await _setFocus(value))) return;
|
|
879
|
+
if (inView) {
|
|
880
|
+
location.reload();
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
874
883
|
try {
|
|
875
884
|
await window.denShell.api("POST", "/api/scope", { scope: value });
|
|
876
885
|
} catch (err) {
|
|
@@ -885,9 +894,9 @@ async function _switchScope(value) {
|
|
|
885
894
|
}
|
|
886
895
|
|
|
887
896
|
/**
|
|
888
|
-
*
|
|
889
|
-
* `focus`, which writes workspaces.yml and retargets the
|
|
890
|
-
*
|
|
897
|
+
* Make `name` the CLI default: Local Dev's focus action (POST
|
|
898
|
+
* /api/workspaces `focus`, which writes workspaces.yml and retargets the
|
|
899
|
+
* CLI). False, with the error shown, when it couldn't.
|
|
891
900
|
*/
|
|
892
901
|
async function _setFocus(name) {
|
|
893
902
|
try {
|
|
@@ -900,9 +909,9 @@ async function _setFocus(name) {
|
|
|
900
909
|
err.message = t("ws.focusFailed", { msg: err.message });
|
|
901
910
|
window.denShell.errorToast(err);
|
|
902
911
|
} else window.denShell?.toast?.(t("ws.focusFailed", { msg: err.message }));
|
|
903
|
-
return;
|
|
912
|
+
return false;
|
|
904
913
|
}
|
|
905
|
-
|
|
914
|
+
return true;
|
|
906
915
|
}
|
|
907
916
|
|
|
908
917
|
// ── Health: the status chip and drawer (topbar.js, R6 and R15) ─────────────
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"ws.all": "Todos los activos",
|
|
30
30
|
"ws.focus": "predeterminado de la CLI",
|
|
31
31
|
"ws.focusTip":
|
|
32
|
-
"Los comandos de la CLI se ejecutan aquí si no indicas un espacio de trabajo. Elegir un espacio en la barra superior
|
|
32
|
+
"Los comandos de la CLI se ejecutan aquí si no indicas un espacio de trabajo. Elegir un espacio en la barra superior también lo convierte en el predeterminado de la CLI.",
|
|
33
|
+
"ws.pickTip":
|
|
34
|
+
"Elegir un espacio de trabajo aquí también lo convierte en el predeterminado de la CLI, así que los comandos de __DEN_CLI__ se ejecutan allí.",
|
|
33
35
|
"ws.none": "No hay espacios de trabajo activos",
|
|
34
36
|
"ws.manage": "Gestionar espacios de trabajo…",
|
|
35
37
|
"ws.allN": "Todos los activos ({n})",
|
|
@@ -39,10 +41,7 @@
|
|
|
39
41
|
"ws.viewTitle":
|
|
40
42
|
"Vista: lo que muestra cada página ({ws}). Es solo un filtro: no inicia, detiene ni enruta nada.",
|
|
41
43
|
"ws.viewTitleFocus":
|
|
42
|
-
"Vista: lo que muestra cada página ({ws}).
|
|
43
|
-
"ws.setFocus": "Hacer de {ws} el predeterminado de la CLI",
|
|
44
|
-
"ws.setFocusTip":
|
|
45
|
-
"Lo convierte en el destino por defecto de los comandos (escribe workspaces.yml)",
|
|
44
|
+
"Vista: lo que muestra cada página ({ws}). No inicia, detiene ni enruta nada. Predeterminado de la CLI: {focus}.",
|
|
46
45
|
"ws.viewFailed": "No se pudo cambiar la vista: {msg}",
|
|
47
46
|
"ws.focusFailed": "No se pudo poner el foco: {msg}",
|
|
48
47
|
"org.n": "{n} espacios de trabajo",
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"ws.all": "すべてのアクティブ",
|
|
30
30
|
"ws.focus": "CLI の既定",
|
|
31
31
|
"ws.focusTip":
|
|
32
|
-
"
|
|
32
|
+
"ワークスペースを指定しないと CLI のコマンドはここで実行されます。上部バーでワークスペースを選ぶと、それが CLI の既定にもなります。",
|
|
33
|
+
"ws.pickTip":
|
|
34
|
+
"ここでワークスペースを選ぶと CLI の既定にもなり、__DEN_CLI__ のコマンドはそこで実行されます。",
|
|
33
35
|
"ws.none": "アクティブなワークスペースはありません",
|
|
34
36
|
"ws.manage": "ワークスペースを管理…",
|
|
35
37
|
"ws.allN": "すべてのアクティブ ({n})",
|
|
@@ -39,10 +41,7 @@
|
|
|
39
41
|
"ws.viewTitle":
|
|
40
42
|
"表示: すべてのページに表示する範囲 ({ws})。表示の絞り込みだけで、何も起動・停止・ルーティングしません。",
|
|
41
43
|
"ws.viewTitleFocus":
|
|
42
|
-
"表示:
|
|
43
|
-
"ws.setFocus": "{ws} を CLI の既定にする",
|
|
44
|
-
"ws.setFocusTip":
|
|
45
|
-
"コマンドの既定の対象にします (workspaces.yml に書き込みます)",
|
|
44
|
+
"表示: 各ページに出るもの ({ws})。起動・停止・ルーティングは行いません。CLI の既定: {focus}。",
|
|
46
45
|
"ws.viewFailed": "表示を変更できませんでした: {msg}",
|
|
47
46
|
"ws.focusFailed": "フォーカスを設定できませんでした: {msg}",
|
|
48
47
|
"org.n": "{n} 個のワークスペース",
|
|
@@ -1613,7 +1613,7 @@
|
|
|
1613
1613
|
? `<button class="danger" data-action="delete">Delete</button>`
|
|
1614
1614
|
: "";
|
|
1615
1615
|
return `<div class="ws-item ${n === activeWs ? "active" : ""}" data-name="${escHtml(n)}" data-action="select">
|
|
1616
|
-
<span>${escHtml(n)}${isWsActive(n) ? ' <span style="font-size:10px;color:var(--muted)">(active)</span>' : ""}${n === focusName() ? ` <span class="ws-cli-default" title="${escHtml(wsT("ws.focusTip", "CLI commands run here when you don't name a workspace. Picking a workspace in the top bar
|
|
1616
|
+
<span>${escHtml(n)}${isWsActive(n) ? ' <span style="font-size:10px;color:var(--muted)">(active)</span>' : ""}${n === focusName() ? ` <span class="ws-cli-default" title="${escHtml(wsT("ws.focusTip", "CLI commands run here when you don't name a workspace. Picking a workspace in the top bar also makes it the CLI default."))}">${escHtml(wsT("ws.focus", "CLI default"))}</span>` : ""}</span>
|
|
1617
1617
|
<span class="ws-item-right">
|
|
1618
1618
|
<button class="ws-term-btn" title="Open in terminal" data-open-term="${escHtml(dirPath)}">>_</button>
|
|
1619
1619
|
<button class="ws-open-btn" title="Open in editor" data-open-path="${escHtml(wsData.codeWorkspacePaths?.[n] || dirPath)}">↗</button>
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
* setOrgs([{ id, title, workspaces? }], currentId, onSelect?)
|
|
63
63
|
* two or more: the Org ▾ menu; a pick
|
|
64
64
|
* calls onSelect(id), fires `den:org`
|
|
65
|
-
* setWorkspaces({ label, title, items,
|
|
65
|
+
* setWorkspaces({ label, title, items, focus, onSelect })
|
|
66
66
|
* the Workspace ▾ menu (header.js)
|
|
67
67
|
* syncEnv() repaint Env ▾ from #env-sel
|
|
68
68
|
* registerCommand({ id, label, keywords?, kind?, run })
|
|
@@ -134,7 +134,9 @@
|
|
|
134
134
|
"ws.all": "All active",
|
|
135
135
|
"ws.focus": "CLI default",
|
|
136
136
|
"ws.focusTip":
|
|
137
|
-
"CLI commands run here when you don't name a workspace. Picking a workspace in the top bar
|
|
137
|
+
"CLI commands run here when you don't name a workspace. Picking a workspace in the top bar also makes it the CLI default.",
|
|
138
|
+
"ws.pickTip":
|
|
139
|
+
"Picking a workspace here also makes it the CLI default, so __DEN_CLI__ commands run there.",
|
|
138
140
|
"ws.none": "No active workspaces",
|
|
139
141
|
"ws.manage": "Manage workspaces…",
|
|
140
142
|
"ws.allN": "All active ({n})",
|
|
@@ -144,10 +146,7 @@
|
|
|
144
146
|
"ws.viewTitle":
|
|
145
147
|
"View: what every page shows ({ws}). A view filter only: it starts, stops and routes nothing.",
|
|
146
148
|
"ws.viewTitleFocus":
|
|
147
|
-
"View: what every page shows ({ws}).
|
|
148
|
-
"ws.setFocus": "Make {ws} the CLI default",
|
|
149
|
-
"ws.setFocusTip":
|
|
150
|
-
"CLI commands run in it when you don't name a workspace (writes workspaces.yml)",
|
|
149
|
+
"View: what every page shows ({ws}). It starts, stops and routes nothing. CLI default: {focus}.",
|
|
151
150
|
"ws.viewFailed": "Could not change the view: {msg}",
|
|
152
151
|
"ws.focusFailed": "Could not change the CLI default: {msg}",
|
|
153
152
|
// A count's singular is its .one (0.3.0-dev.40: en had none, so the org
|
|
@@ -1722,8 +1721,9 @@
|
|
|
1722
1721
|
/**
|
|
1723
1722
|
* The Workspace part: { label, title, items: [{ value, label, checked,
|
|
1724
1723
|
* focused, title? }], onSelect(value) }. "All active" and the active
|
|
1725
|
-
* workspaces, the focused one
|
|
1726
|
-
*
|
|
1724
|
+
* workspaces, the focused one (model.focus, the CLI default) marked; then
|
|
1725
|
+
* a link to manage them. onSelect(value, focus): choosing a workspace
|
|
1726
|
+
* also makes it the CLI default (header.js).
|
|
1727
1727
|
*/
|
|
1728
1728
|
function setWorkspaces(model) {
|
|
1729
1729
|
var btn = document.getElementById("ws-pill");
|
|
@@ -1748,22 +1748,16 @@
|
|
|
1748
1748
|
tag: it.focused ? t("ws.focus") : null,
|
|
1749
1749
|
title: it.title || null,
|
|
1750
1750
|
onSelect: function () {
|
|
1751
|
-
if (
|
|
1752
|
-
|
|
1751
|
+
if (typeof model.onSelect !== "function") return;
|
|
1752
|
+
// The workspace in view still becomes the CLI default when it
|
|
1753
|
+
// isn't one; picking what is both already does nothing.
|
|
1754
|
+
if (it.checked && (it.value === "all" || it.value === model.focus))
|
|
1755
|
+
return;
|
|
1756
|
+
model.onSelect(it.value, model.focus || null, !!it.checked);
|
|
1753
1757
|
},
|
|
1754
1758
|
});
|
|
1755
1759
|
});
|
|
1756
1760
|
items.push({ hr: true });
|
|
1757
|
-
// Focus is a separate action (it writes workspaces.yml and retargets
|
|
1758
|
-
// the CLI); picking a view above never moves it.
|
|
1759
|
-
if (model.focusable && typeof model.onFocus === "function")
|
|
1760
|
-
items.push({
|
|
1761
|
-
label: t("ws.setFocus", { ws: model.focusable }),
|
|
1762
|
-
title: t("ws.setFocusTip"),
|
|
1763
|
-
onSelect: function () {
|
|
1764
|
-
model.onFocus(model.focusable);
|
|
1765
|
-
},
|
|
1766
|
-
});
|
|
1767
1761
|
items.push({ label: t("ws.manage"), href: navHref("local-dev") });
|
|
1768
1762
|
scopeMenus.ws = { label: t("k.ws"), items: items };
|
|
1769
1763
|
}
|
|
@@ -25,7 +25,9 @@ export const EN_FALLBACK = {
|
|
|
25
25
|
"ws.viewTitle":
|
|
26
26
|
"View: what every page shows ({ws}). A view filter only: it starts, stops and routes nothing.",
|
|
27
27
|
"ws.viewTitleFocus":
|
|
28
|
-
"View: what every page shows ({ws}).
|
|
28
|
+
"View: what every page shows ({ws}). It starts, stops and routes nothing. CLI default: {focus}.",
|
|
29
|
+
"ws.pickTip":
|
|
30
|
+
"Picking a workspace here also makes it the CLI default, so __DEN_CLI__ commands run there.",
|
|
29
31
|
};
|
|
30
32
|
function enT(key, vars) {
|
|
31
33
|
return String(EN_FALLBACK[key] ?? key).replace(/\{(\w+)\}/g, (m, k) =>
|
|
@@ -50,10 +52,10 @@ function focusName(data) {
|
|
|
50
52
|
/**
|
|
51
53
|
* The top bar's Workspace ▾ menu (R4, 0.3.0-dev.18), from the `/api/scope`
|
|
52
54
|
* and `/api/workspaces` payloads. It is the view scope: what every page,
|
|
53
|
-
* the health row and `status` show. Choosing
|
|
54
|
-
* focus (the default
|
|
55
|
-
*
|
|
56
|
-
*
|
|
55
|
+
* the health row and `status` show. Choosing a workspace also makes it the
|
|
56
|
+
* focus (the CLI default, Ward's decision in 0.4.11); "All active" leaves
|
|
57
|
+
* the focus where it is. The focused workspace is marked, and `focus`
|
|
58
|
+
* names it, so the caller can skip a choice that is already the focus.
|
|
57
59
|
*
|
|
58
60
|
* "All active" is offered with two or more active workspaces; with one,
|
|
59
61
|
* it and that workspace are the same view.
|
|
@@ -85,12 +87,13 @@ export function buildScopeMenuModel(scope, data, t = enT) {
|
|
|
85
87
|
label: n,
|
|
86
88
|
checked: n === one,
|
|
87
89
|
focused: n === focus,
|
|
90
|
+
title: t("ws.pickTip"),
|
|
88
91
|
})),
|
|
89
92
|
];
|
|
90
93
|
return {
|
|
91
94
|
label: active.length === 0 ? t("ws.noneLabel") : (one ?? t("ws.all")),
|
|
92
95
|
items,
|
|
93
|
-
|
|
96
|
+
focus: focus ?? null,
|
|
94
97
|
title: t(focus ? "ws.viewTitleFocus" : "ws.viewTitle", {
|
|
95
98
|
ws: inView.join(", ") || t("ws.noneLabel"),
|
|
96
99
|
focus,
|