ovtools 0.0.0-stage → 1.0.1

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.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -2
  3. package/assets/common/bond-inspect.sh +368 -0
  4. package/assets/common/config.sh +263 -0
  5. package/assets/common/extract-data.sh +197 -0
  6. package/assets/common/harden-host.sh +37 -0
  7. package/assets/common/install-deps.sh +93 -0
  8. package/assets/common/node-enroll.sh +65 -0
  9. package/assets/common/node-setup.sh +150 -0
  10. package/assets/common/node-start.sh +90 -0
  11. package/assets/common/node-stop.sh +18 -0
  12. package/assets/common/node-upgrade.sh +45 -0
  13. package/assets/common/pre-run.sh +497 -0
  14. package/assets/common/promote-validator.sh +104 -0
  15. package/assets/deploy/10-prepare-host.sh +98 -0
  16. package/assets/deploy/20-install-toolchain.sh +93 -0
  17. package/assets/deploy/30-setup-node.sh +82 -0
  18. package/assets/deploy/40-start-node.sh +75 -0
  19. package/assets/deploy/50-verify.sh +259 -0
  20. package/assets/deploy/60-enroll.sh +304 -0
  21. package/assets/deploy/70-promote.sh +152 -0
  22. package/assets/deploy/lib.sh +398 -0
  23. package/assets/manage/10-bond-increase.sh +149 -0
  24. package/assets/manage/10-diagnose-node.sh +169 -0
  25. package/assets/manage/20-exit-validator.sh +259 -0
  26. package/assets/manage/25-recover-node.sh +291 -0
  27. package/assets/manage/30-status.sh +64 -0
  28. package/assets/manage/40-exit-withdraw.sh +133 -0
  29. package/assets/manage/bond_inc.sh +318 -0
  30. package/assets/manage/dashboard_monitor/app.js +1544 -0
  31. package/assets/manage/dashboard_monitor/assets/v2.2/css/base.css +113 -0
  32. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/content.css +133 -0
  33. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/controls.css +229 -0
  34. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/data.css +374 -0
  35. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/icons.css +35 -0
  36. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/motion.css +57 -0
  37. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/shell.css +51 -0
  38. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/sidebar.css +242 -0
  39. package/assets/manage/dashboard_monitor/assets/v2.2/css/components/topbar.css +138 -0
  40. package/assets/manage/dashboard_monitor/assets/v2.2/css/fonts.css +30 -0
  41. package/assets/manage/dashboard_monitor/assets/v2.2/css/synteq-ui-v2.css +25 -0
  42. package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-dark.css +38 -0
  43. package/assets/manage/dashboard_monitor/assets/v2.2/css/theme-light.css +36 -0
  44. package/assets/manage/dashboard_monitor/assets/v2.2/css/tokens.css +61 -0
  45. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
  46. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
  47. package/assets/manage/dashboard_monitor/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
  48. package/assets/manage/dashboard_monitor/assets/v2.2/js/synteq-ui-v2.js +869 -0
  49. package/assets/manage/dashboard_monitor/deploy-dashboard.sh +230 -0
  50. package/assets/manage/dashboard_monitor/favicon.svg +13 -0
  51. package/assets/manage/dashboard_monitor/index.html +1197 -0
  52. package/assets/manage/dashboard_monitor/nginx-octra.conf +76 -0
  53. package/assets/manage/dashboard_monitor/octra-dashboard.service +29 -0
  54. package/assets/manage/dashboard_monitor/octra-node-prune.cron +12 -0
  55. package/assets/manage/dashboard_monitor/octra-node-prune.service +16 -0
  56. package/assets/manage/dashboard_monitor/octra-node-prune.timer +12 -0
  57. package/assets/manage/dashboard_monitor/prune-node-junk.sh +329 -0
  58. package/assets/manage/dashboard_monitor/server.py +1887 -0
  59. package/assets/manage/dashboard_monitor/ssl-certbot.sh +90 -0
  60. package/assets/manage/dashboard_monitor/styles.css +327 -0
  61. package/assets/manage/lib.sh +347 -0
  62. package/assets/manifest.json +143 -0
  63. package/assets/migrate/00-backup-identity.sh +67 -0
  64. package/assets/migrate/01-prepare-target.sh +145 -0
  65. package/assets/migrate/02-authorize-pull.sh +43 -0
  66. package/assets/migrate/03-pull-state.sh +102 -0
  67. package/assets/migrate/04-stop-source.sh +85 -0
  68. package/assets/migrate/05-finalize-target.sh +140 -0
  69. package/assets/migrate/06-start-target.sh +57 -0
  70. package/assets/migrate/07-verify.sh +237 -0
  71. package/assets/migrate/08-rollback.sh +54 -0
  72. package/assets/migrate/lib.sh +149 -0
  73. package/assets/upgrade/10-preflight.sh +120 -0
  74. package/assets/upgrade/20-upgrade.sh +142 -0
  75. package/assets/upgrade/30-manual-upgrade.sh +129 -0
  76. package/assets/upgrade/40-storage-prune.sh +65 -0
  77. package/assets/upgrade/50-status.sh +58 -0
  78. package/assets/upgrade/lib.sh +232 -0
  79. package/bin/ovtools.js +32 -0
  80. package/dist/api/checks.d.ts +6 -0
  81. package/dist/api/checks.d.ts.map +1 -0
  82. package/dist/api/checks.js +173 -0
  83. package/dist/api/checks.js.map +1 -0
  84. package/dist/api/hostkeys.d.ts +10 -0
  85. package/dist/api/hostkeys.d.ts.map +1 -0
  86. package/dist/api/hostkeys.js +58 -0
  87. package/dist/api/hostkeys.js.map +1 -0
  88. package/dist/api/jobs.d.ts +33 -0
  89. package/dist/api/jobs.d.ts.map +1 -0
  90. package/dist/api/jobs.js +486 -0
  91. package/dist/api/jobs.js.map +1 -0
  92. package/dist/api/nodes.d.ts +16 -0
  93. package/dist/api/nodes.d.ts.map +1 -0
  94. package/dist/api/nodes.js +126 -0
  95. package/dist/api/nodes.js.map +1 -0
  96. package/dist/api/server.d.ts +3 -0
  97. package/dist/api/server.d.ts.map +1 -0
  98. package/dist/api/server.js +443 -0
  99. package/dist/api/server.js.map +1 -0
  100. package/dist/cli.d.ts +3 -0
  101. package/dist/cli.d.ts.map +1 -0
  102. package/dist/cli.js +301 -0
  103. package/dist/cli.js.map +1 -0
  104. package/dist/constants.d.ts +76 -0
  105. package/dist/constants.d.ts.map +1 -0
  106. package/dist/constants.js +150 -0
  107. package/dist/constants.js.map +1 -0
  108. package/dist/index.d.ts +18 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +60 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/orchestrator/base.d.ts +52 -0
  113. package/dist/orchestrator/base.d.ts.map +1 -0
  114. package/dist/orchestrator/base.js +219 -0
  115. package/dist/orchestrator/base.js.map +1 -0
  116. package/dist/orchestrator/deploy.d.ts +33 -0
  117. package/dist/orchestrator/deploy.d.ts.map +1 -0
  118. package/dist/orchestrator/deploy.js +313 -0
  119. package/dist/orchestrator/deploy.js.map +1 -0
  120. package/dist/orchestrator/manage.d.ts +28 -0
  121. package/dist/orchestrator/manage.d.ts.map +1 -0
  122. package/dist/orchestrator/manage.js +267 -0
  123. package/dist/orchestrator/manage.js.map +1 -0
  124. package/dist/orchestrator/migrate.d.ts +31 -0
  125. package/dist/orchestrator/migrate.d.ts.map +1 -0
  126. package/dist/orchestrator/migrate.js +442 -0
  127. package/dist/orchestrator/migrate.js.map +1 -0
  128. package/dist/ssh/client.d.ts +39 -0
  129. package/dist/ssh/client.d.ts.map +1 -0
  130. package/dist/ssh/client.js +418 -0
  131. package/dist/ssh/client.js.map +1 -0
  132. package/dist/test/core.test.d.ts +2 -0
  133. package/dist/test/core.test.d.ts.map +1 -0
  134. package/dist/test/core.test.js +188 -0
  135. package/dist/test/core.test.js.map +1 -0
  136. package/dist/types.d.ts +267 -0
  137. package/dist/types.d.ts.map +1 -0
  138. package/dist/types.js +3 -0
  139. package/dist/types.js.map +1 -0
  140. package/dist/utils/assets.d.ts +28 -0
  141. package/dist/utils/assets.d.ts.map +1 -0
  142. package/dist/utils/assets.js +583 -0
  143. package/dist/utils/assets.js.map +1 -0
  144. package/dist/utils/browser.d.ts +2 -0
  145. package/dist/utils/browser.d.ts.map +1 -0
  146. package/dist/utils/browser.js +23 -0
  147. package/dist/utils/browser.js.map +1 -0
  148. package/dist/utils/dashboard.d.ts +16 -0
  149. package/dist/utils/dashboard.d.ts.map +1 -0
  150. package/dist/utils/dashboard.js +177 -0
  151. package/dist/utils/dashboard.js.map +1 -0
  152. package/dist/utils/helpers.d.ts +29 -0
  153. package/dist/utils/helpers.d.ts.map +1 -0
  154. package/dist/utils/helpers.js +204 -0
  155. package/dist/utils/helpers.js.map +1 -0
  156. package/dist/utils/scripts.d.ts +60 -0
  157. package/dist/utils/scripts.d.ts.map +1 -0
  158. package/dist/utils/scripts.js +174 -0
  159. package/dist/utils/scripts.js.map +1 -0
  160. package/dist/utils/state.d.ts +12 -0
  161. package/dist/utils/state.d.ts.map +1 -0
  162. package/dist/utils/state.js +44 -0
  163. package/dist/utils/state.js.map +1 -0
  164. package/dist/utils/tar.d.ts +11 -0
  165. package/dist/utils/tar.d.ts.map +1 -0
  166. package/dist/utils/tar.js +93 -0
  167. package/dist/utils/tar.js.map +1 -0
  168. package/dist/utils/update.d.ts +9 -0
  169. package/dist/utils/update.d.ts.map +1 -0
  170. package/dist/utils/update.js +134 -0
  171. package/dist/utils/update.js.map +1 -0
  172. package/dist/utils/websocket.d.ts +10 -0
  173. package/dist/utils/websocket.d.ts.map +1 -0
  174. package/dist/utils/websocket.js +203 -0
  175. package/dist/utils/websocket.js.map +1 -0
  176. package/package.json +55 -4
  177. package/public/app.js +2736 -0
  178. package/public/assets/v2.2/css/base.css +113 -0
  179. package/public/assets/v2.2/css/components/content.css +133 -0
  180. package/public/assets/v2.2/css/components/controls.css +229 -0
  181. package/public/assets/v2.2/css/components/data.css +373 -0
  182. package/public/assets/v2.2/css/components/icons.css +35 -0
  183. package/public/assets/v2.2/css/components/motion.css +57 -0
  184. package/public/assets/v2.2/css/components/shell.css +51 -0
  185. package/public/assets/v2.2/css/components/sidebar.css +242 -0
  186. package/public/assets/v2.2/css/components/topbar.css +138 -0
  187. package/public/assets/v2.2/css/fonts.css +30 -0
  188. package/public/assets/v2.2/css/synteq-ui-v2.css +25 -0
  189. package/public/assets/v2.2/css/theme-dark.css +38 -0
  190. package/public/assets/v2.2/css/theme-light.css +36 -0
  191. package/public/assets/v2.2/css/tokens.css +61 -0
  192. package/public/assets/v2.2/fonts/GoogleSansCode-Medium.ttf +0 -0
  193. package/public/assets/v2.2/fonts/GoogleSansCode-Regular.ttf +0 -0
  194. package/public/assets/v2.2/fonts/GoogleSansCode-SemiBold.ttf +0 -0
  195. package/public/assets/v2.2/js/synteq-ui-v2.js +869 -0
  196. package/public/favicon.svg +13 -0
  197. package/public/index.html +1083 -0
  198. package/public/styles.css +580 -0
@@ -0,0 +1,580 @@
1
+ /* ==========================================================================
2
+ Octra Migration Console - app layer on top of Synteq UI V2
3
+ Only layout for console-specific blocks; colours come from the tokens so
4
+ light/dark switching keeps working.
5
+ ========================================================================== */
6
+
7
+ /* An author `display` rule (flex/grid) beats the UA rule behind [hidden], so
8
+ panels styled with display:flex would stay visible when we hide them.
9
+ https://html.spec.whatwg.org/multipage/rendering.html#hidden-elements */
10
+ [hidden] { display: none !important; }
11
+
12
+ .mc-hero { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--v2-space-4); }
13
+ .mc-hero__title-row { display: flex; align-items: center; gap: var(--v2-space-3); flex-wrap: wrap; }
14
+ .mc-hero__title {
15
+ margin: 0;
16
+ font-size: var(--v2-text-2xl);
17
+ font-weight: var(--v2-fw-semibold);
18
+ letter-spacing: -0.01em;
19
+ color: var(--v2-text-strong);
20
+ }
21
+ .mc-hero__meta {
22
+ display: flex; align-items: center; gap: var(--v2-space-2);
23
+ margin-top: var(--v2-space-2); flex-wrap: wrap;
24
+ }
25
+ .mc-hero__sep { opacity: 0.4; }
26
+
27
+ /* ---- safety banner ------------------------------------------------------ */
28
+ .mc-alert {
29
+ display: flex; flex-direction: column; gap: 2px;
30
+ margin: var(--v2-space-3) 0 0;
31
+ padding-left: var(--v2-space-3);
32
+ border-left: 2px solid var(--v2-status-warning);
33
+ }
34
+ .mc-alert__row { font-size: var(--v2-text-xs); color: var(--v2-text); }
35
+ .mc-alert__row--err { color: var(--v2-status-error); }
36
+ .mc-alert__row--warn { color: var(--v2-status-warning); }
37
+
38
+ /* ---- endpoints: one row per field (host above, password below) --------- */
39
+ .mc-endpoints {
40
+ display: grid;
41
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
42
+ gap: var(--v2-space-5);
43
+ align-items: start;
44
+ margin: var(--v2-space-5) 0 var(--v2-space-4);
45
+ }
46
+ .mc-endpoint { display: flex; flex-direction: column; gap: var(--v2-space-2); min-width: 0; }
47
+ .mc-endpoint__role {
48
+ font-size: var(--v2-text-xs);
49
+ letter-spacing: 0.06em;
50
+ text-transform: uppercase;
51
+ color: var(--v2-text-subtle);
52
+ }
53
+ .mc-endpoint .v2-input { width: 100%; }
54
+ .mc-samepass {
55
+ display: inline-flex;
56
+ align-items: center;
57
+ gap: 8px;
58
+ margin-top: 2px;
59
+ font-size: var(--v2-text-xs);
60
+ color: var(--v2-text-subtle);
61
+ cursor: pointer;
62
+ user-select: none;
63
+ }
64
+ .mc-samepass input { width: 14px; height: 14px; accent-color: var(--v2-brand); cursor: pointer; }
65
+ .mc-samepass input:disabled + span { opacity: 0.6; }
66
+
67
+ /* passwords: masked input + a show/hide toggle (never rendered back by the app) */
68
+ .mc-pass { display: flex; align-items: center; gap: 6px; min-width: 0; }
69
+ .mc-pass .v2-input { flex: 1 1 auto; min-width: 0; }
70
+ .mc-pass__toggle { flex: 0 0 auto; }
71
+ .mc-pass__toggle .mc-pass__eye-off { display: none; }
72
+ .mc-pass__toggle[aria-pressed="true"] { color: var(--v2-brand); }
73
+ .mc-pass__toggle[aria-pressed="true"] .mc-pass__eye-open { display: none; }
74
+ .mc-pass__toggle[aria-pressed="true"] .mc-pass__eye-off { display: inline-flex; }
75
+ .mc-pass__toggle[disabled] { opacity: 0.4; cursor: not-allowed; }
76
+
77
+ /* ---- options row ------------------------------------------------------- */
78
+ .mc-form {
79
+ display: flex;
80
+ align-items: flex-end;
81
+ align-content: flex-start;
82
+ gap: var(--v2-space-3);
83
+ flex-wrap: wrap;
84
+ margin: 0 0 var(--v2-space-4);
85
+ }
86
+ .mc-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
87
+ .mc-field__label {
88
+ font-size: var(--v2-text-xs);
89
+ color: var(--v2-text-subtle);
90
+ text-transform: uppercase;
91
+ letter-spacing: 0.06em;
92
+ }
93
+ .mc-field--sm { width: 106px; flex: 0 0 auto; }
94
+ .mc-field--grow { flex: 1 1 220px; min-width: 180px; }
95
+ .mc-form .v2-input, .mc-modal .v2-input { width: 100%; }
96
+
97
+ /* ---- pipeline result (dashboard URL / failure notice) ------------------ */
98
+ .mc-result {
99
+ margin-top: var(--v2-space-4);
100
+ padding-left: var(--v2-space-3);
101
+ border-left: 2px solid var(--v2-line);
102
+ font-size: var(--v2-text-sm);
103
+ color: var(--v2-text);
104
+ word-break: break-word;
105
+ }
106
+ .mc-result--ok { border-left-color: var(--v2-brand); color: var(--v2-text-strong); }
107
+ .mc-result--err { border-left-color: var(--v2-status-error); color: var(--v2-status-error); }
108
+
109
+ /* ---- progress ---------------------------------------------------------- */
110
+ .mc-progress { display: flex; flex-direction: column; gap: var(--v2-space-2); margin-bottom: var(--v2-space-4); }
111
+ .mc-progress__meta { display: flex; gap: var(--v2-space-4); flex-wrap: wrap; }
112
+
113
+ /* ---- precondition check ------------------------------------------------ */
114
+ .mc-check { border-top: 1px solid var(--v2-line); border-bottom: 1px solid var(--v2-line); margin-bottom: var(--v2-space-4); }
115
+ .mc-check__head {
116
+ display: flex; align-items: center; gap: var(--v2-space-3); flex-wrap: wrap;
117
+ padding: var(--v2-space-3) 0;
118
+ }
119
+ .mc-check__list { display: flex; flex-direction: column; }
120
+ .mc-check__row {
121
+ display: grid;
122
+ grid-template-columns: 58px minmax(0, 1.1fr) minmax(0, 1.5fr);
123
+ gap: var(--v2-space-3);
124
+ align-items: baseline;
125
+ padding: 6px 0;
126
+ border-top: 1px solid var(--v2-line);
127
+ font-size: var(--v2-text-xs);
128
+ }
129
+ .mc-check__status {
130
+ font-size: 10px;
131
+ letter-spacing: 0.08em;
132
+ font-weight: var(--v2-fw-semibold);
133
+ text-transform: uppercase;
134
+ color: var(--v2-text-subtle);
135
+ }
136
+ .mc-check__row--ok .mc-check__status { color: var(--v2-brand); }
137
+ .mc-check__row--warn .mc-check__status { color: var(--v2-status-warning); }
138
+ .mc-check__row--fail .mc-check__status { color: var(--v2-status-error); }
139
+ .mc-check__label { color: var(--v2-text); }
140
+ .mc-check__detail {
141
+ color: var(--v2-text-subtle);
142
+ overflow: hidden;
143
+ text-overflow: ellipsis;
144
+ white-space: nowrap;
145
+ }
146
+ .mc-check__actions {
147
+ display: flex;
148
+ align-items: center;
149
+ gap: var(--v2-space-3);
150
+ flex-wrap: wrap;
151
+ padding: var(--v2-space-3) 0;
152
+ border-top: 1px solid var(--v2-line);
153
+ }
154
+ .mc-check__hint { font-size: var(--v2-text-xs); color: var(--v2-status-warning); }
155
+
156
+ /* ---- actions ----------------------------------------------------------- */
157
+ .mc-actions {
158
+ display: flex; align-items: center; gap: var(--v2-space-3); flex-wrap: wrap;
159
+ /* a wrapped row must not stretch its lines (the buttons then look like they
160
+ carry extra padding top and bottom) */
161
+ align-content: flex-start;
162
+ margin-top: var(--v2-space-5);
163
+ padding-top: var(--v2-space-4);
164
+ border-top: 1px solid var(--v2-line);
165
+ }
166
+ .mc-check-btn { display: inline-flex; align-items: center; gap: 6px; }
167
+ .mc-footnote { flex: 1 1 240px; min-width: 200px; }
168
+ /* an empty note takes no room at all (the migrate pipeline note, the modal notes) */
169
+ .mc-footnote:empty, .mc-enroll__note:empty { display: none; }
170
+ /* .mc-footnote is a *row* item: inside .mc-actions it takes a flexible width.
171
+ As a direct child of .mc-panel (column flex) the same `flex: 1 1 240px` means
172
+ 240px of HEIGHT - that was the empty gap under the Manage buttons. */
173
+ .mc-panel > .mc-footnote { flex: 0 0 auto; min-width: 0; }
174
+
175
+ /* ---- manage actions, inside the "Live node state" section ------------- */
176
+ /* They act on the node whose stats are on the left, so they live in the same
177
+ grid: two columns x two rows, centred both ways, and the note spans both
178
+ columns underneath (2026-11-05). Deploy/Migrate hide the whole block. */
179
+ .mc-nodeactions {
180
+ display: flex; align-items: center; justify-content: center;
181
+ padding: var(--v2-space-4) 0 var(--v2-space-4) var(--v2-space-4);
182
+ border-left: 1px solid var(--v2-line);
183
+ }
184
+ .mc-nodeactions__grid {
185
+ display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
186
+ gap: var(--v2-space-2); width: 100%;
187
+ }
188
+ .mc-nodeactions__grid .v2-btn {
189
+ justify-content: center; min-width: 0; font-weight: var(--v2-fw-medium);
190
+ }
191
+ .mc-nodeactions__grid .v2-btn svg { display: block; flex: 0 0 auto; }
192
+ .mc-nodeactions__wide { grid-column: 1 / -1; }
193
+ .mc-nodeactions__note { grid-column: 1 / -1; display: block; line-height: 1.45; }
194
+ /* the manage tool shows ONE node card, so it needs no pair divider */
195
+ .mc-nodes--manage > .mc-node { border-left: 0; padding-left: 0; }
196
+ @media (max-width: 900px) {
197
+ .mc-nodes { grid-template-columns: 1fr; }
198
+ .mc-node + .mc-node { border-left: 0; padding-left: 0; border-top: 1px solid var(--v2-line); }
199
+ .mc-nodeactions { border-left: 0; border-top: 1px solid var(--v2-line); padding: var(--v2-space-4) 0 0; }
200
+ }
201
+ /* an inline SVG sits on the text baseline and adds descender space under the
202
+ label: block layout keeps the button height exactly 32px */
203
+ .mc-actions .v2-btn svg, .mc-logbtn svg, .mc-settings__save svg { display: block; flex: 0 0 auto; }
204
+ /* a disabled manage action (Check not passed yet) keeps a plain border */
205
+ .mc-nodeactions__grid .v2-btn[disabled] { border-color: var(--v2-line); }
206
+ .mc-hero__main { min-width: 0; }
207
+ .mc-guide__col { min-width: 0; }
208
+
209
+ /* ---- node cards (flat, hairline separated) ----------------------------- */
210
+ .mc-nodes { display: grid; grid-template-columns: 1fr 1fr; gap: 0; border-top: 1px solid var(--v2-line); }
211
+ .mc-node { padding: var(--v2-space-4) var(--v2-space-3) var(--v2-space-4) 0; display: flex; flex-direction: column; gap: var(--v2-space-3); }
212
+ .mc-node + .mc-node { border-left: 1px solid var(--v2-line); padding-left: var(--v2-space-4); }
213
+ .mc-node__head { display: flex; align-items: center; gap: var(--v2-space-2); flex-wrap: wrap; }
214
+ .mc-node__ip { color: var(--v2-text-strong); font-size: var(--v2-text-md); }
215
+ .mc-node__foot { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
216
+
217
+ /* ---- console state label (sidebar footer) ------------------------------- */
218
+ /* One word, coloured by the run state: Idle / Running / Waiting / Watching /
219
+ Done / Failed / Offline. The phase detail lives in the topbar badge. The
220
+ label is plain text next to the dot - no pill wrapper. */
221
+ .mc-dockstatus {
222
+ font-size: var(--v2-text-xs);
223
+ padding: 0;
224
+ border-radius: 0;
225
+ background: transparent;
226
+ max-width: 100%;
227
+ white-space: nowrap;
228
+ overflow: hidden;
229
+ text-overflow: ellipsis;
230
+ }
231
+ .v2-aside__footer { gap: 6px; }
232
+
233
+ /* ---- brand mark: Octra ring + "vt" in the project colour ---------------- */
234
+ .v2-aside__brand-mark {
235
+ width: 22px;
236
+ height: 22px;
237
+ background: transparent;
238
+ color: var(--v2-brand);
239
+ }
240
+
241
+ /* ---- bottom navigation: active item colours icon + text only ------------ */
242
+ .v2-nav__item--active,
243
+ .v2-nav__item.is-active,
244
+ .v2-nav__item--active:hover,
245
+ .v2-nav__item.is-active:hover {
246
+ background: transparent;
247
+ box-shadow: none;
248
+ color: var(--v2-brand);
249
+ }
250
+ .v2-nav__item--active .v2-icon,
251
+ .v2-nav__item.is-active .v2-icon,
252
+ .v2-nav__item--active:hover .v2-icon,
253
+ .v2-nav__item.is-active:hover .v2-icon { color: var(--v2-brand); }
254
+
255
+ /* the data directory is a full path now: let it wrap instead of overflowing */
256
+ #hero-project { overflow-wrap: anywhere; }
257
+
258
+ /* ---- program tabs + the three dashboards -------------------------------- */
259
+ /* One row, three equal buttons; they stack only when the window is narrow. */
260
+ .mc-tabs {
261
+ display: grid;
262
+ grid-template-columns: repeat(3, minmax(0, 1fr));
263
+ gap: var(--v2-space-3);
264
+ margin: var(--v2-space-4) 0 var(--v2-space-4);
265
+ }
266
+ @media (max-width: 760px) {
267
+ .mc-tabs { grid-template-columns: minmax(0, 1fr); }
268
+ }
269
+ .mc-tab {
270
+ display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
271
+ padding: var(--v2-space-3) var(--v2-space-4); text-align: left; cursor: pointer;
272
+ border: 1px solid var(--v2-line-strong); border-radius: var(--v2-radius-control);
273
+ background: var(--v2-inset); color: var(--v2-text);
274
+ min-width: 0;
275
+ }
276
+ .mc-tab + .mc-tab { margin-left: 0; }
277
+ .mc-tab:hover { border-color: var(--v2-line-accent); }
278
+ .mc-tab--active {
279
+ border-color: var(--v2-brand);
280
+ background: color-mix(in srgb, var(--v2-brand) 8%, var(--v2-inset));
281
+ }
282
+ .mc-tab__title { font-size: var(--v2-text-md); font-weight: var(--v2-fw-semibold); color: var(--v2-text-strong); }
283
+ .mc-tab--active .mc-tab__title { color: var(--v2-brand); }
284
+ .mc-tab__hint { font-size: var(--v2-text-xs); color: var(--v2-text-subtle); }
285
+ .mc-panel { display: flex; flex-direction: column; gap: var(--v2-space-3); }
286
+ .mc-endpoints--single { grid-template-columns: minmax(0, 1fr); }
287
+ .mc-endpoints--single .mc-endpoint { max-width: 560px; }
288
+ .mc-program__head { display: flex; align-items: baseline; gap: var(--v2-space-3); flex-wrap: wrap; }
289
+ .mc-program__title { font-size: var(--v2-text-md); font-weight: var(--v2-fw-semibold); color: var(--v2-text-strong); }
290
+ .mc-pipeline { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
291
+ .mc-pipeline__step {
292
+ padding: 2px 8px; font-size: var(--v2-text-xs); font-family: var(--v2-font-mono);
293
+ border: 1px solid var(--v2-line); border-radius: var(--v2-radius-micro);
294
+ background: var(--v2-inset); color: var(--v2-text-subtle);
295
+ }
296
+ .mc-pipeline__step--main { border-color: var(--v2-brand); color: var(--v2-brand); }
297
+ .mc-pipeline__step--critical { border-color: color-mix(in srgb, var(--v2-status-warning) 60%, transparent); }
298
+ .mc-pipeline__sep { color: var(--v2-text-subtle); font-size: var(--v2-text-xs); }
299
+
300
+ /* ---- endpoints + per-host auth ----------------------------------------- */
301
+ /* The auth row is its own small grid: method on the left, the key (or the
302
+ password below it) on the right, so every host lines up the same way. */
303
+ .mc-auth {
304
+ display: grid;
305
+ grid-template-columns: minmax(96px, 0.32fr) minmax(0, 1fr);
306
+ align-items: start;
307
+ gap: 6px;
308
+ }
309
+ .mc-auth__method { width: 100%; min-width: 0; font-size: var(--v2-text-sm); }
310
+ .mc-auth__key { width: 100%; min-width: 0; font-size: var(--v2-text-sm); }
311
+ .mc-key { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
312
+ .mc-key[hidden] { display: none; }
313
+ .mc-key__row { display: flex; align-items: center; gap: 6px; min-width: 0; }
314
+ .mc-key__row .v2-input { flex: 1 1 auto; min-width: 0; }
315
+ .mc-key__pick { flex: 0 0 auto; white-space: nowrap; }
316
+ .mc-key__row .v2-btn svg { display: block; flex: 0 0 auto; }
317
+ .mc-key__text {
318
+ width: 100%; min-height: 46px; resize: vertical;
319
+ font-family: var(--v2-font-mono); font-size: var(--v2-text-xs);
320
+ }
321
+ .mc-endpoint .mc-auth + .mc-pass { margin-top: 2px; }
322
+ /* The node role moved into the action row (right before Start deploy): align the
323
+ label+select with the buttons instead of centring them. */
324
+ .mc-actions--deploy { align-items: flex-end; }
325
+ .mc-actions--deploy .mc-role-field { width: 116px; flex: 0 0 auto; }
326
+
327
+ /* Project settings: swap is the only thing to type; the save button must be
328
+ the obvious next step, and the dashboard-off row carries the two ways out. */
329
+ .mc-settings { margin-top: 0; align-items: flex-end; }
330
+ /* the swap input is the last thing in its label: aligning to the bottom (flex-end)
331
+ puts the Save button on the same line as the field the operator edits */
332
+ /* Project settings carry the swap size only: the dashboard checkbox that used to
333
+ sit here moved to the Manage active node tab (Deploy monitor), so its rule is
334
+ gone (2026-11-05). */
335
+ .mc-settings__save {
336
+ flex: 0 0 auto; min-width: 150px;
337
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--v2-brand) 35%, transparent),
338
+ 0 6px 18px -10px color-mix(in srgb, var(--v2-brand) 70%, transparent);
339
+ }
340
+ .mc-settings__off {
341
+ display: flex; align-items: center; gap: var(--v2-space-2); flex-wrap: wrap;
342
+ margin-top: var(--v2-space-2);
343
+ padding: var(--v2-space-2) var(--v2-space-3);
344
+ border-left: 2px solid var(--v2-status-warning);
345
+ background: color-mix(in srgb, var(--v2-status-warning) 6%, transparent);
346
+ border-radius: var(--v2-radius-control);
347
+ }
348
+ .mc-settings__off[hidden] { display: none; }
349
+
350
+ /* ---- log toolbar ------------------------------------------------------- */
351
+ .mc-logbar {
352
+ display: flex; align-items: center; gap: var(--v2-space-2); flex-wrap: wrap;
353
+ align-content: flex-start;
354
+ margin-bottom: var(--v2-space-3);
355
+ }
356
+ /* ---- disabled controls ------------------------------------------------- */
357
+ .v2-btn[disabled], .v2-seg__btn[disabled] { opacity: 0.45; cursor: not-allowed; }
358
+ .v2-btn[disabled]:hover { background: transparent; }
359
+
360
+ .mc-logbar__search { flex: 1 1 180px; min-width: 140px; }
361
+ /* log toolbar buttons (pause / clear / download): same weight as the filter
362
+ segments so the row reads as one control strip */
363
+ .mc-logbtn {
364
+ display: inline-flex; align-items: center; gap: 6px;
365
+ flex: 0 0 auto;
366
+ padding: 5px 10px;
367
+ border: 1px solid var(--v2-line);
368
+ background: var(--v2-inset);
369
+ font-size: var(--v2-text-xs);
370
+ color: var(--v2-text-subtle);
371
+ }
372
+ .mc-logbtn:hover:not(:disabled) { border-color: var(--v2-line-accent); color: var(--v2-text-strong); }
373
+ .mc-logbtn[aria-pressed="true"] {
374
+ border-color: var(--v2-brand);
375
+ color: var(--v2-brand);
376
+ background: color-mix(in srgb, var(--v2-brand) 10%, transparent);
377
+ }
378
+ .mc-logbar__hosts .v2-seg__btn { font-size: 10px; }
379
+ #log-view { --v2-log-h: min(46vh, 460px); }
380
+
381
+ /* per-host colouring in the log: the chip carries a SRC/DST role tag, so the
382
+ source and the target are distinguishable by shape as well as by colour */
383
+ .mc-hostchip {
384
+ display: inline-flex;
385
+ align-items: center;
386
+ gap: 6px;
387
+ min-width: 0;
388
+ overflow: hidden;
389
+ font-size: 10px;
390
+ opacity: 0.95;
391
+ }
392
+ .mc-hostchip__role {
393
+ flex: 0 0 auto;
394
+ padding: 0 3px;
395
+ border: 1px solid currentColor;
396
+ border-radius: var(--v2-radius-micro);
397
+ font-size: 9px;
398
+ font-weight: var(--v2-fw-medium);
399
+ letter-spacing: 0.06em;
400
+ line-height: 1.35;
401
+ opacity: 0.85;
402
+ }
403
+ .mc-hostchip__role--src { background: transparent; }
404
+ .mc-hostchip__role--dst { background: color-mix(in srgb, currentColor 18%, transparent); }
405
+ .mc-hostchip__ip { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
406
+
407
+ /* The chip colours the log uses for the host. --v2-tel-out was a rose/red token
408
+ and an operator read a healthy step as an error (2026-11-04). The two roles now
409
+ have explicit NON-red hues: emerald for the source, sky for the target. */
410
+ :root {
411
+ --mc-role-src: #34d399;
412
+ --mc-role-dst: #38bdf8;
413
+ }
414
+
415
+ /* A line the operator must not miss in a 1500-row log: the manage token, the
416
+ published URL, the wallet address, a completed milestone. Brand-tinted, never
417
+ red - red stays the error colour. */
418
+ .v2-log__row--key {
419
+ border-left-color: var(--v2-brand);
420
+ background: color-mix(in srgb, var(--v2-brand) 8%, transparent);
421
+ }
422
+ .v2-log__row--key .v2-log__msg { color: var(--v2-text-strong); font-weight: var(--v2-fw-medium); }
423
+ .v2-log__row--key .v2-log__level { color: var(--v2-brand); border-color: color-mix(in srgb, var(--v2-brand) 45%, transparent); }
424
+
425
+ /* log rows: a step marker opens a block, quiet progress lines recede, a clean
426
+ result gets a green edge (the same language the warn/error rows already use) */
427
+ .v2-log__row--ok { border-left-color: color-mix(in srgb, var(--v2-status-running) 45%, transparent); }
428
+ .v2-log__row--muted .v2-log__msg { color: var(--v2-text-subtle); }
429
+ .mc-row--step {
430
+ margin-top: var(--v2-space-2);
431
+ padding-top: var(--v2-space-2);
432
+ border-top: var(--v2-line-h) solid var(--v2-line);
433
+ }
434
+ .mc-row--step .v2-log__msg { color: var(--v2-text-strong); font-weight: var(--v2-fw-medium); }
435
+ .mc-row--step .v2-log__level { color: var(--v2-text-subtle); }
436
+ .mc-legend {
437
+ display: inline-flex;
438
+ align-items: center;
439
+ gap: var(--v2-space-3);
440
+ margin-left: auto;
441
+ }
442
+ .mc-legend__item {
443
+ display: inline-flex;
444
+ align-items: center;
445
+ gap: 6px;
446
+ font-size: var(--v2-text-xs);
447
+ color: var(--v2-text-subtle);
448
+ }
449
+ .mc-legend__dot {
450
+ width: 8px;
451
+ height: 8px;
452
+ border-radius: 50%;
453
+ display: inline-block;
454
+ }
455
+
456
+ /* ---- guide ------------------------------------------------------------- */
457
+ .mc-guide {
458
+ display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
459
+ gap: var(--v2-space-5); border-top: 1px solid var(--v2-line); padding-top: var(--v2-space-4);
460
+ }
461
+ .mc-guide__h {
462
+ margin: 0 0 var(--v2-space-2);
463
+ font-size: var(--v2-text-xs);
464
+ letter-spacing: 0.06em;
465
+ text-transform: uppercase;
466
+ color: var(--v2-text-subtle);
467
+ font-weight: var(--v2-fw-medium);
468
+ }
469
+ .mc-guide__list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
470
+ .mc-guide__list li { font-size: var(--v2-text-sm); color: var(--v2-text); line-height: 1.5; }
471
+ .mc-guide--three { align-items: start; }
472
+ .mc-guide__foot { margin: var(--v2-space-3) 0 0; }
473
+ @media (max-width: 980px) {
474
+ .mc-guide { grid-template-columns: minmax(0, 1fr); gap: var(--v2-space-4); }
475
+ }
476
+
477
+ /* ---- enroll popup (deploy: validator) ---------------------------------- */
478
+ .mc-enroll__addr { display: flex; flex-direction: column; gap: 4px; }
479
+ .mc-enroll__value {
480
+ padding: 6px 8px;
481
+ background: var(--v2-inset);
482
+ border: 1px solid var(--v2-line);
483
+ border-radius: var(--v2-radius-control);
484
+ color: var(--v2-text-strong);
485
+ overflow-wrap: anywhere;
486
+ user-select: all;
487
+ }
488
+ .mc-enroll__check {
489
+ display: inline-flex; align-items: center; gap: 8px;
490
+ font-size: var(--v2-text-xs); color: var(--v2-text);
491
+ cursor: pointer; user-select: none;
492
+ }
493
+ .mc-enroll__check input { width: 14px; height: 14px; accent-color: var(--v2-brand); cursor: pointer; }
494
+ .mc-enroll__note { color: var(--v2-status-warning); }
495
+ .mc-enroll__note:empty { display: none; }
496
+ .mc-err { color: var(--v2-status-error); }
497
+ .mc-ok { color: var(--v2-status-success); }
498
+
499
+ /* ---- confirm modal ----------------------------------------------------- */
500
+ .mc-modal {
501
+ position: fixed; inset: 0; z-index: 90;
502
+ display: grid; place-items: center;
503
+ padding: var(--v2-space-4);
504
+ background: rgba(0, 0, 0, 0.62);
505
+ backdrop-filter: blur(2px);
506
+ }
507
+ .mc-modal[hidden] { display: none; }
508
+ .mc-modal__body {
509
+ width: min(460px, 100%);
510
+ max-height: calc(100vh - 2rem);
511
+ max-height: calc(100dvh - 2rem);
512
+ overflow-y: auto;
513
+ background: var(--v2-canvas);
514
+ border: 1px solid var(--v2-line-strong);
515
+ border-radius: var(--v2-radius-control);
516
+ padding: var(--v2-space-5);
517
+ display: flex; flex-direction: column; gap: var(--v2-space-3);
518
+ }
519
+ .mc-modal__title { margin: 0; font-size: var(--v2-text-lg); color: var(--v2-text-strong); }
520
+ .mc-modal__text { margin: 0; font-size: var(--v2-text-sm); color: var(--v2-text-subtle); line-height: 1.55; }
521
+ .mc-modal__actions { display: flex; justify-content: flex-end; gap: var(--v2-space-2); margin-top: var(--v2-space-2); }
522
+
523
+ /* TLS choice inside the monitor modal: two radio cards, the picked one gets a
524
+ brand border and only its detail rows are shown */
525
+ .mc-ssl { margin: 0; padding: 0; border: 0; display: flex; flex-direction: column; gap: var(--v2-space-2); }
526
+ .mc-ssl > legend { padding: 0; margin-bottom: 6px; }
527
+ .mc-ssl__opt {
528
+ display: flex; align-items: flex-start; gap: 10px;
529
+ padding: var(--v2-space-3);
530
+ border: 1px solid var(--v2-line);
531
+ border-radius: var(--v2-radius-control);
532
+ background: var(--v2-inset);
533
+ cursor: pointer; user-select: none;
534
+ }
535
+ .mc-ssl__opt:has(input:checked) {
536
+ border-color: var(--v2-brand);
537
+ background: color-mix(in srgb, var(--v2-brand) 7%, var(--v2-inset));
538
+ }
539
+ .mc-ssl__opt input { width: 15px; height: 15px; margin-top: 2px; accent-color: var(--v2-brand); cursor: pointer; }
540
+ .mc-ssl__opt span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
541
+ .mc-ssl__opt strong { font-size: var(--v2-text-sm); color: var(--v2-text-strong); font-weight: var(--v2-fw-medium); }
542
+ .mc-ssl__opt em { font-size: var(--v2-text-xs); color: var(--v2-text-subtle); font-style: normal; line-height: 1.45; }
543
+ .mc-ssl__body { display: flex; flex-direction: column; gap: 6px; padding: 0 var(--v2-space-3) 0 34px; min-width: 0; }
544
+ .mc-ssl__body[hidden] { display: none; }
545
+ /* a stored path has no spaces: let it break instead of stretching the modal
546
+ (a long C:\...\tmp\certs\... line used to widen the dialog) */
547
+ .mc-ssl__body span, .mc-modal__text, .mc-enroll__note { overflow-wrap: anywhere; min-width: 0; }
548
+
549
+ /* ---- a field that failed validation ------------------------------------ */
550
+ /* The message alone was too easy to miss, and a button that "does nothing" is
551
+ the worst feedback: the field is outlined, focused and explained. */
552
+ .v2-input.mc-invalid,
553
+ .v2-input.mc-invalid:focus {
554
+ border-color: var(--v2-status-error);
555
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--v2-status-error) 45%, transparent);
556
+ }
557
+
558
+
559
+ /* ---- responsive -------------------------------------------------------- */
560
+ @media (max-width: 900px) {
561
+ .mc-endpoints { grid-template-columns: 1fr; gap: var(--v2-space-4); }
562
+ .mc-nodes { grid-template-columns: 1fr; }
563
+ .mc-node + .mc-node { border-left: 0; border-top: 1px solid var(--v2-line); padding-left: 0; }
564
+ .mc-guide { grid-template-columns: 1fr; gap: var(--v2-space-4); }
565
+ }
566
+ @media (max-width: 620px) {
567
+ .mc-hero__title { font-size: var(--v2-text-xl); }
568
+ .mc-field--sm { width: 100%; }
569
+ .mc-field--grow { width: 100%; }
570
+ }
571
+ @media (max-width: 440px) {
572
+ /* the folder input + its picker no longer fit on one line: stack them so the
573
+ icon and the caption are never clipped */
574
+ .mc-ssl__body .mc-key__row { flex-wrap: wrap; }
575
+ .mc-ssl__body .mc-key__pick { width: 100%; }
576
+ /* long manage labels ("Update node (auto)") do not fit two 174px columns */
577
+ .mc-nodeactions__grid { grid-template-columns: 1fr; }
578
+ .mc-nodeactions__wide { grid-column: auto; }
579
+ }
580
+