@jxa13/pm2ui 1.17.0
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/README.md +198 -0
- package/Services/pm2Service.js +91 -0
- package/Services/systemService.js +28 -0
- package/bin/pm2ui.js +3 -0
- package/changelog.md +250 -0
- package/frontend/dist/assets/index-BhYqcf4u.css +1 -0
- package/frontend/dist/assets/index-aVk9yHhV.js +224 -0
- package/frontend/dist/index.html +13 -0
- package/package.json +43 -0
- package/roadmap.md +170 -0
- package/server.js +889 -0
- package/user_manual.md +458 -0
package/README.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Node WebUI
|
|
4
|
+
|
|
5
|
+
A local web dashboard for monitoring and managing PM2 processes on macOS.
|
|
6
|
+
|
|
7
|
+
Node WebUI provides a clean browser-based interface for viewing running services, checking system usage, managing PM2 apps, and reading logs without needing to keep a Terminal window open.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- View all PM2-managed processes
|
|
14
|
+
- Start, stop, and restart individual processes
|
|
15
|
+
- Bulk start, stop, and restart actions
|
|
16
|
+
- View PM2 process ID, CPU usage, RAM usage, uptime, restart count, and start time
|
|
17
|
+
- Search and filter processes by name and status
|
|
18
|
+
- Sort processes by status, name, CPU, RAM, restarts, uptime, and started time
|
|
19
|
+
- Show or hide table columns
|
|
20
|
+
- Save table preferences in local storage
|
|
21
|
+
- Open the process folder in Finder
|
|
22
|
+
- View recent logs for each process
|
|
23
|
+
- Auto-refresh logs
|
|
24
|
+
- Search within logs
|
|
25
|
+
- Copy, clear, and download logs
|
|
26
|
+
- Toggle line wrapping in logs
|
|
27
|
+
- Differentiate stdout and stderr log lines
|
|
28
|
+
- View host CPU and RAM usage
|
|
29
|
+
- View service counts and restart totals
|
|
30
|
+
- React operations-console UI served locally by Express
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- macOS
|
|
37
|
+
- Node.js 18+
|
|
38
|
+
- One or more PM2-managed processes
|
|
39
|
+
|
|
40
|
+
Install PM2 globally if you want to use the `pm2` command directly:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g pm2
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Install the CLI globally from npm:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g @jxa13/pm2ui
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Start the dashboard:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pm2ui
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
By default, the dashboard runs locally on:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
http://127.0.0.1:3000
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Install a specific version:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install -g @jxa13/pm2ui@1.17.0
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Update to the latest version:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install -g @jxa13/pm2ui@latest
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Roll back to a previous version:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install -g @jxa13/pm2ui@1.16.4
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Install From Source
|
|
87
|
+
|
|
88
|
+
Clone the repository:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git clone <repo-url>
|
|
92
|
+
cd Node-WebUI
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Install dependencies:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Start the app:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm start
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`npm start` builds the React frontend and starts the local Express server.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## PM2 Setup
|
|
112
|
+
|
|
113
|
+
Start a sample process:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pm2 start app.js --name my-app
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
View PM2 processes:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pm2 list
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Save PM2 process state:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pm2 save
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Enable PM2 to start automatically after reboot:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pm2 startup
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Available Actions
|
|
140
|
+
|
|
141
|
+
### Per Process
|
|
142
|
+
|
|
143
|
+
- Start
|
|
144
|
+
- Stop
|
|
145
|
+
- Restart
|
|
146
|
+
- Open logs
|
|
147
|
+
- Open folder in Finder
|
|
148
|
+
|
|
149
|
+
### Bulk Actions
|
|
150
|
+
|
|
151
|
+
- Start all stopped processes
|
|
152
|
+
- Stop all running processes
|
|
153
|
+
- Restart all running processes
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Logs Viewer
|
|
158
|
+
|
|
159
|
+
The logs viewer supports:
|
|
160
|
+
|
|
161
|
+
- stdout and stderr lines
|
|
162
|
+
- Auto refresh
|
|
163
|
+
- Search within logs
|
|
164
|
+
- Copy logs
|
|
165
|
+
- Clear logs
|
|
166
|
+
- Download logs
|
|
167
|
+
- Toggle line wrapping
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Security
|
|
172
|
+
|
|
173
|
+
This app is designed for local use only.
|
|
174
|
+
|
|
175
|
+
Recommended:
|
|
176
|
+
|
|
177
|
+
- Keep it bound to localhost
|
|
178
|
+
- Do not expose it directly to the internet
|
|
179
|
+
- If remote access is needed, place it behind authentication and a reverse proxy
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Future Ideas
|
|
184
|
+
|
|
185
|
+
Potential future improvements:
|
|
186
|
+
|
|
187
|
+
- Real-time log streaming with WebSockets
|
|
188
|
+
- Process details modal
|
|
189
|
+
- Create new PM2 processes from the UI
|
|
190
|
+
- Historical CPU and RAM charts
|
|
191
|
+
- Authentication for LAN access
|
|
192
|
+
- Saved dashboard layouts
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const pm2 = require('pm2');
|
|
2
|
+
|
|
3
|
+
function connectPm2() {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
pm2.connect((error) => {
|
|
6
|
+
if (error) {
|
|
7
|
+
reject(error);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
resolve();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function disconnectPm2() {
|
|
17
|
+
try {
|
|
18
|
+
pm2.disconnect();
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error('PM2 disconnect error:', error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function withPm2(callback) {
|
|
25
|
+
await connectPm2();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return await callback();
|
|
29
|
+
} finally {
|
|
30
|
+
disconnectPm2();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function listProcesses() {
|
|
35
|
+
return withPm2(() => new Promise((resolve, reject) => {
|
|
36
|
+
pm2.list((error, list) => {
|
|
37
|
+
if (error) {
|
|
38
|
+
reject(error);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
resolve(list);
|
|
43
|
+
});
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function startProcess(target) {
|
|
48
|
+
return withPm2(() => new Promise((resolve, reject) => {
|
|
49
|
+
pm2.start(target, (error, result) => {
|
|
50
|
+
if (error) {
|
|
51
|
+
reject(error);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
resolve(result);
|
|
56
|
+
});
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function stopProcess(target) {
|
|
61
|
+
return withPm2(() => new Promise((resolve, reject) => {
|
|
62
|
+
pm2.stop(target, (error, result) => {
|
|
63
|
+
if (error) {
|
|
64
|
+
reject(error);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
resolve(result);
|
|
69
|
+
});
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function restartProcess(target) {
|
|
74
|
+
return withPm2(() => new Promise((resolve, reject) => {
|
|
75
|
+
pm2.restart(target, (error, result) => {
|
|
76
|
+
if (error) {
|
|
77
|
+
reject(error);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
resolve(result);
|
|
82
|
+
});
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
listProcesses,
|
|
88
|
+
startProcess,
|
|
89
|
+
stopProcess,
|
|
90
|
+
restartProcess
|
|
91
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const si = require('systeminformation');
|
|
4
|
+
|
|
5
|
+
async function getSystemStats() {
|
|
6
|
+
const [currentLoad, mem, time] = await Promise.all([
|
|
7
|
+
si.currentLoad(),
|
|
8
|
+
si.mem(),
|
|
9
|
+
si.time()
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
cpu: {
|
|
14
|
+
currentLoad: currentLoad.currentLoad || 0
|
|
15
|
+
},
|
|
16
|
+
memory: {
|
|
17
|
+
total: mem.total || 0,
|
|
18
|
+
used: mem.used || 0,
|
|
19
|
+
free: mem.free || 0,
|
|
20
|
+
usedPercent: mem.total ? (mem.used / mem.total) * 100 : 0
|
|
21
|
+
},
|
|
22
|
+
uptime: time.uptime || 0
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
getSystemStats
|
|
28
|
+
};
|
package/bin/pm2ui.js
ADDED
package/changelog.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Node WebUI are documented here.
|
|
4
|
+
|
|
5
|
+
Version numbers should be updated with each commit that changes app behavior, user-facing UI, or project documentation. Use semantic versioning:
|
|
6
|
+
|
|
7
|
+
- Patch versions, such as `1.0.1`, for fixes and small internal changes.
|
|
8
|
+
- Minor versions, such as `1.1.0`, for user-visible improvements that do not break existing behavior.
|
|
9
|
+
- Major versions, such as `2.0.0`, for breaking changes.
|
|
10
|
+
|
|
11
|
+
## 1.17.0 - 2026-06-07
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Made the React operations console the default UI served from `/`.
|
|
16
|
+
- Changed `npm start` to build the React frontend before starting Express.
|
|
17
|
+
- Kept `/react` as a compatibility route to the same React build.
|
|
18
|
+
- Updated the README, user manual, roadmap, and smoke checks for React as the only frontend.
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- Removed the old vanilla `public` UI assets after React feature parity.
|
|
23
|
+
- Removed the `NODE_WEBUI_REACT_DEFAULT` opt-in flag because React is now the default.
|
|
24
|
+
|
|
25
|
+
## 1.16.4 - 2026-06-07
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Changed protected-process badges in the React table and inspector to use a red padlock icon for stronger visual warning.
|
|
30
|
+
|
|
31
|
+
## 1.16.3 - 2026-06-07
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- Aligned the React process toolbar controls so the status filter icon, status selector, density toggle, and column menu sit on the same row.
|
|
36
|
+
|
|
37
|
+
## 1.16.2 - 2026-06-07
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Added a Protect process checkbox to the React process inspector Config tab for quickly adding or removing the selected process from the local protected-process list.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- Protected processes now keep Start available when applicable, while Stop, Restart, and Delete remain disabled in the React UI.
|
|
46
|
+
|
|
47
|
+
## 1.16.1 - 2026-06-07
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Reduced React Metric History chart card and canvas heights further so the graphs stay inside the section bounds in wide dashboard layouts.
|
|
52
|
+
|
|
53
|
+
## 1.16.0 - 2026-06-07
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- Added a Vite + React operations-console frontend beside the existing vanilla UI.
|
|
58
|
+
- Added React dependencies, build scripts, and smoke checks with `npm run react:build`, `npm run react:dev`, `npm run react:preview`, and `npm run react:smoke`.
|
|
59
|
+
- Added Express serving for the built React UI at `/react` while keeping the vanilla UI available at `/`.
|
|
60
|
+
- Added `NODE_WEBUI_REACT_DEFAULT=true` support to redirect `/` to `/react` when the React build exists.
|
|
61
|
+
- Added a three-region React layout with collapsible left sidebar, main process dashboard, and right process inspector.
|
|
62
|
+
- Added React dashboard summary metrics, PM2 metric history charts, process table, process inspector tabs, logs workspace, create-app modal, and settings view.
|
|
63
|
+
- Added a central Logs sidebar workspace that shares log target, line count, search, wrapping, auto-refresh, entries, and stream lifecycle with inspector logs.
|
|
64
|
+
- Added structured confirmation dialogs for stop, restart, delete, bulk stop/restart, clear logs, and local-storage reset actions.
|
|
65
|
+
- Added table density controls with persisted Compact and Comfortable modes.
|
|
66
|
+
- Added persistence for React navigation, selected process, inspector tab, log target, sidebar collapse state, inspector collapse state, and table density.
|
|
67
|
+
- Added a collapsible right process inspector with a persisted collapsed rail.
|
|
68
|
+
- Added collapsed-sidebar popover tooltips and brief native tooltips for visible React buttons.
|
|
69
|
+
- Added a Settings action to reset local browser preferences, including themes, filters, table columns, metric history, selected process, collapse states, protected names, and log settings.
|
|
70
|
+
- Added keyboard workflows for search focus, dashboard refresh, selected-process logs, row selection movement, and clearing selected rows.
|
|
71
|
+
- Added theme-aware chart strokes that use CSS variables.
|
|
72
|
+
- Added a user manual at `user_manual.md` with installation, usage, settings, troubleshooting, and security instructions.
|
|
73
|
+
- Added manual screenshots under `docs/images/`.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- Updated the roadmap to mark React migration work and the recent operations-console batch as completed or partially completed where appropriate.
|
|
78
|
+
- Changed the React protected-process editor to display saved process names one per line.
|
|
79
|
+
- Reduced metric chart card and canvas heights so charts fit inside the Metric History panel without overlapping the process table.
|
|
80
|
+
- Changed sidebar Logs behavior so it opens logs only in the central workspace and no longer duplicates the log viewer in the inspector.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Fixed full-app React theme application so theme choices affect the app shell, panels, controls, and charts instead of only isolated buttons.
|
|
85
|
+
- Fixed the sidebar Logs navigation item so it opens a real logs workspace.
|
|
86
|
+
- Fixed newline-separated protected process names so entries such as `node-webui` and `server` on separate lines are saved as separate protected names.
|
|
87
|
+
- Fixed protected process UI enforcement after saving newline-separated names, disabling protected process controls in the table and inspector.
|
|
88
|
+
|
|
89
|
+
## 1.15.0 - 2026-06-07
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
|
|
93
|
+
- Added a Pause Live/Resume Live dashboard control that stops automatic stream/polling updates while keeping manual refresh available.
|
|
94
|
+
|
|
95
|
+
## 1.14.0 - 2026-06-07
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- Added a process result counter that shows total processes and filtered result counts next to the process filters.
|
|
100
|
+
|
|
101
|
+
## 1.13.0 - 2026-06-07
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
|
|
105
|
+
- Persisted process search, process status filter, log line count, log auto-refresh, and log wrap preferences in local browser storage.
|
|
106
|
+
|
|
107
|
+
## 1.12.1 - 2026-06-07
|
|
108
|
+
|
|
109
|
+
### Changed
|
|
110
|
+
|
|
111
|
+
- Updated the roadmap to mark completed vanilla dashboard work and move the app to React-ready status.
|
|
112
|
+
|
|
113
|
+
## 1.12.0 - 2026-06-07
|
|
114
|
+
|
|
115
|
+
### Changed
|
|
116
|
+
|
|
117
|
+
- Changed the dashboard CPU/RAM cards and metric history charts to show PM2 process usage instead of host system usage.
|
|
118
|
+
- Changed PM2 RAM display to bytes and reset local metric history storage so older host-memory percentage samples do not mix with PM2 RAM samples.
|
|
119
|
+
|
|
120
|
+
## 1.11.0 - 2026-06-07
|
|
121
|
+
|
|
122
|
+
### Added
|
|
123
|
+
|
|
124
|
+
- Added local historical charts for CPU, RAM, restart count, and process status counts with a configurable 15/30/60 minute window.
|
|
125
|
+
|
|
126
|
+
## 1.10.0 - 2026-06-07
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
|
|
130
|
+
- Added live PM2 log streaming for the logs modal, with the existing log reload path retained as a fallback.
|
|
131
|
+
|
|
132
|
+
## 1.9.0 - 2026-06-07
|
|
133
|
+
|
|
134
|
+
### Added
|
|
135
|
+
|
|
136
|
+
- Added a Server-Sent Events dashboard stream for live process and metric updates while keeping polling as a fallback.
|
|
137
|
+
|
|
138
|
+
## 1.8.0 - 2026-06-07
|
|
139
|
+
|
|
140
|
+
### Added
|
|
141
|
+
|
|
142
|
+
- Added optional PM2 app creation fields for interpreter, Node args, NODE_ENV, instances, watch mode, max-memory restart, and output/error log paths.
|
|
143
|
+
|
|
144
|
+
## 1.7.0 - 2026-06-07
|
|
145
|
+
|
|
146
|
+
### Added
|
|
147
|
+
|
|
148
|
+
- Added a PM2 Runtime dashboard panel showing PM2 version, daemon PID and uptime, managed app count, saved state, and startup persistence status.
|
|
149
|
+
|
|
150
|
+
## 1.6.0 - 2026-06-07
|
|
151
|
+
|
|
152
|
+
### Added
|
|
153
|
+
|
|
154
|
+
- Expanded process details with runtime summaries, PM2 namespace, version, environment, instances, watch and autorestart settings, arguments, and PM2 file paths.
|
|
155
|
+
|
|
156
|
+
### Fixed
|
|
157
|
+
|
|
158
|
+
- Preserved zero values such as restart counts in the process details modal instead of treating them as missing.
|
|
159
|
+
|
|
160
|
+
## 1.5.0 - 2026-06-07
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
|
|
164
|
+
- Added Lagoon, Ruby, Amber, and Nord built-in theme families with System, Light, and Dark mode support.
|
|
165
|
+
|
|
166
|
+
## 1.4.1 - 2026-06-07
|
|
167
|
+
|
|
168
|
+
### Changed
|
|
169
|
+
|
|
170
|
+
- Migrated remaining scratch roadmap notes into the tracked roadmap.
|
|
171
|
+
- Replaced duplicate root-level system monitor scratch scripts with a single tracked example in `examples/sys-monitor.js`.
|
|
172
|
+
|
|
173
|
+
## 1.4.0 - 2026-06-07
|
|
174
|
+
|
|
175
|
+
### Changed
|
|
176
|
+
|
|
177
|
+
- Replaced custom color and saved-theme management with curated built-in theme families.
|
|
178
|
+
- Added Default, Graphite, Midnight, Forest, and High Contrast theme families, each supporting System, Light, and Dark modes.
|
|
179
|
+
- Clarified that protected process settings in the browser are local UI protections and that API-level protection is configured with `PROTECTED_PM2_PROCESSES`.
|
|
180
|
+
|
|
181
|
+
## 1.3.2 - 2026-06-07
|
|
182
|
+
|
|
183
|
+
### Changed
|
|
184
|
+
|
|
185
|
+
- Updated the roadmap to replace custom theme management with curated built-in theme families that each support System, Light, and Dark modes.
|
|
186
|
+
|
|
187
|
+
## 1.3.1 - 2026-06-07
|
|
188
|
+
|
|
189
|
+
### Fixed
|
|
190
|
+
|
|
191
|
+
- Improved PM2 app creation argument parsing so quoted values and escaped characters are preserved.
|
|
192
|
+
- Added validation for unmatched quotes in new process arguments.
|
|
193
|
+
|
|
194
|
+
## 1.3.0 - 2026-06-07
|
|
195
|
+
|
|
196
|
+
### Added
|
|
197
|
+
|
|
198
|
+
- Added PM2 runtime status data to the dashboard API, including PM2 home, saved dump status, dump timestamp, and macOS startup LaunchAgent detection.
|
|
199
|
+
- Added `/api/pm2/status` for retrieving PM2 persistence status without loading full dashboard process data.
|
|
200
|
+
|
|
201
|
+
## 1.2.0 - 2026-06-07
|
|
202
|
+
|
|
203
|
+
### Added
|
|
204
|
+
|
|
205
|
+
- Added richer PM2 process metadata to the dashboard API, including interpreter, exec mode, namespace, args, node args, log paths, PID path, PM2 home, watch state, unstable restarts, version, and safe environment labels.
|
|
206
|
+
- Populated existing process details modal fields for interpreter and exec mode when PM2 provides them.
|
|
207
|
+
|
|
208
|
+
## 1.1.4 - 2026-06-07
|
|
209
|
+
|
|
210
|
+
### Fixed
|
|
211
|
+
|
|
212
|
+
- Fixed service module import casing so the app can load on case-sensitive filesystems.
|
|
213
|
+
|
|
214
|
+
## 1.1.3 - 2026-06-07
|
|
215
|
+
|
|
216
|
+
### Fixed
|
|
217
|
+
|
|
218
|
+
- Added server-side protected process enforcement for stop, restart, and delete API routes.
|
|
219
|
+
- Added `PROTECTED_PM2_PROCESSES` support for configuring protected PM2 app names on the server.
|
|
220
|
+
|
|
221
|
+
## 1.1.2 - 2026-06-07
|
|
222
|
+
|
|
223
|
+
### Fixed
|
|
224
|
+
|
|
225
|
+
- Replaced shell-string PM2 log commands with argument-safe `execFile` calls.
|
|
226
|
+
- Clamped requested log line counts to a safe range before passing them to PM2.
|
|
227
|
+
|
|
228
|
+
## 1.1.1 - 2026-06-07
|
|
229
|
+
|
|
230
|
+
### Fixed
|
|
231
|
+
|
|
232
|
+
- Fixed dashboard CPU and RAM summary values so the UI reports host system usage instead of aggregate PM2 process usage.
|
|
233
|
+
- Preserved PM2 aggregate CPU and RAM usage in the dashboard API as `pm2Usage` for future UI use.
|
|
234
|
+
|
|
235
|
+
## 1.1.0 - 2026-06-07
|
|
236
|
+
|
|
237
|
+
### Added
|
|
238
|
+
|
|
239
|
+
- Added a project roadmap with prioritized work for safety, process data, monitoring, UX, UI refresh, React migration, and maintainability.
|
|
240
|
+
- Added a deferred React migration plan. React is recommended later when the frontend needs richer component structure, live logs, charts, settings tabs, and more reusable state.
|
|
241
|
+
- Added a more useful empty state for dashboards with no PM2 processes, including direct actions to create a PM2 app or refresh the dashboard.
|
|
242
|
+
|
|
243
|
+
### Changed
|
|
244
|
+
|
|
245
|
+
- Refreshed the dashboard styling to feel more like a compact operations console.
|
|
246
|
+
- Made summary cards tighter and easier to scan.
|
|
247
|
+
- Improved table readability with a sticky header, clearer hover states, and right-aligned numeric columns.
|
|
248
|
+
- Strengthened process status badges with consistent semantic styling and status dots.
|
|
249
|
+
- Replaced mixed action glyphs and emoji with consistent inline SVG icons while keeping accessible labels.
|
|
250
|
+
- Tuned modal and control styling for smaller radii, quieter surfaces, and consistent focus states.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{color-scheme:dark;--bg: #0b0f14;--bg-2: #0f141b;--panel: #131922;--panel-2: #171f29;--panel-3: #0f151d;--line: rgba(148, 163, 184, .15);--line-strong: rgba(148, 163, 184, .26);--text: #e5edf6;--muted: #8b99a9;--muted-2: #657284;--accent: #38bdf8;--accent-2: #22d3ee;--accent-contrast: #041017;--accent-faint: color-mix(in srgb, var(--accent) 6%, transparent);--accent-soft: color-mix(in srgb, var(--accent) 12%, transparent);--accent-selected: color-mix(in srgb, var(--accent) 9%, transparent);--accent-line: color-mix(in srgb, var(--accent) 38%, transparent);--success: #22c55e;--warning: #f59e0b;--danger: #ef4444;--shadow: 0 18px 50px rgba(0, 0, 0, .28);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}body[data-theme-mode=light]{color-scheme:light;--bg: #eef2f6;--bg-2: #f7f9fc;--panel: #ffffff;--panel-2: #f8fafc;--panel-3: #edf2f7;--line: rgba(15, 23, 42, .12);--line-strong: rgba(15, 23, 42, .22);--text: #111827;--muted: #526071;--muted-2: #7b8794;--accent-contrast: #ffffff;--shadow: 0 16px 40px rgba(15, 23, 42, .12)}body[data-theme-family=graphite][data-theme-mode=dark]{--bg: #0d0d0f;--bg-2: #131316;--panel: #19191d;--panel-2: #202026;--panel-3: #111114;--line: rgba(212, 212, 216, .14);--line-strong: rgba(212, 212, 216, .26);--text: #f4f4f5;--muted: #a1a1aa;--muted-2: #71717a;--accent: #a1a1aa;--accent-2: #d4d4d8}body[data-theme-family=graphite][data-theme-mode=light]{--bg: #f4f4f5;--bg-2: #fafafa;--panel: #ffffff;--panel-2: #f4f4f5;--panel-3: #e4e4e7;--line: rgba(39, 39, 42, .14);--line-strong: rgba(39, 39, 42, .25);--text: #18181b;--muted: #52525b;--muted-2: #71717a;--accent: #52525b;--accent-2: #27272a}body[data-theme-family=midnight][data-theme-mode=dark]{--bg: #07111f;--bg-2: #0b1627;--panel: #101d31;--panel-2: #14243a;--panel-3: #0b1828;--line: rgba(147, 197, 253, .15);--line-strong: rgba(147, 197, 253, .28);--text: #dbeafe;--muted: #93a4bb;--muted-2: #64748b;--accent: #60a5fa;--accent-2: #38bdf8}body[data-theme-family=midnight][data-theme-mode=light]{--bg: #eef2ff;--bg-2: #f8fbff;--panel: #ffffff;--panel-2: #eff6ff;--panel-3: #dbeafe;--line: rgba(37, 99, 235, .17);--line-strong: rgba(37, 99, 235, .3);--text: #172554;--muted: #475569;--muted-2: #64748b;--accent: #2563eb;--accent-2: #0891b2}body[data-theme-family=forest][data-theme-mode=dark]{--bg: #07130d;--bg-2: #0d1b14;--panel: #12241a;--panel-2: #172d20;--panel-3: #0b1811;--line: rgba(134, 239, 172, .14);--line-strong: rgba(134, 239, 172, .28);--text: #dcfce7;--muted: #9fc8aa;--muted-2: #75a681;--accent: #4ade80;--accent-2: #22c55e}body[data-theme-family=forest][data-theme-mode=light]{--bg: #f0fdf4;--bg-2: #f8fffb;--panel: #ffffff;--panel-2: #ecfdf5;--panel-3: #dcfce7;--line: rgba(21, 128, 61, .16);--line-strong: rgba(21, 128, 61, .28);--text: #14532d;--muted: #3f6212;--muted-2: #65a30d;--accent: #15803d;--accent-2: #16a34a}body[data-theme-family=lagoon][data-theme-mode=dark]{--bg: #061519;--bg-2: #0a1f25;--panel: #102a31;--panel-2: #153640;--panel-3: #0b2026;--line: rgba(103, 232, 249, .15);--line-strong: rgba(103, 232, 249, .28);--text: #cffafe;--muted: #8bcbd4;--muted-2: #5aa6b1;--accent: #22d3ee;--accent-2: #38bdf8}body[data-theme-family=lagoon][data-theme-mode=light]{--bg: #ecfeff;--bg-2: #f8ffff;--panel: #ffffff;--panel-2: #cffafe;--panel-3: #a5f3fc;--line: rgba(8, 145, 178, .18);--line-strong: rgba(8, 145, 178, .3);--text: #164e63;--muted: #155e75;--muted-2: #0e7490;--accent: #0891b2;--accent-2: #0e7490}body[data-theme-family=ruby][data-theme-mode=dark]{--bg: #19070d;--bg-2: #241018;--panel: #301522;--panel-2: #3a1827;--panel-3: #201018;--line: rgba(253, 164, 175, .15);--line-strong: rgba(253, 164, 175, .3);--text: #ffe4e6;--muted: #dda2ae;--muted-2: #b87783;--accent: #fb7185;--accent-2: #f43f5e}body[data-theme-family=ruby][data-theme-mode=light]{--bg: #fff1f2;--bg-2: #fff7f8;--panel: #ffffff;--panel-2: #ffe4e6;--panel-3: #fecdd3;--line: rgba(190, 18, 60, .17);--line-strong: rgba(190, 18, 60, .3);--text: #4c0519;--muted: #9f1239;--muted-2: #be123c;--accent: #be123c;--accent-2: #e11d48}body[data-theme-family=amber][data-theme-mode=dark]{--bg: #171006;--bg-2: #211707;--panel: #2b1d0a;--panel-2: #34240d;--panel-3: #1f1609;--line: rgba(252, 211, 77, .15);--line-strong: rgba(252, 211, 77, .3);--text: #fef3c7;--muted: #d6b979;--muted-2: #a8843b;--accent: #fbbf24;--accent-2: #f59e0b}body[data-theme-family=amber][data-theme-mode=light]{--bg: #fffbeb;--bg-2: #fffdf5;--panel: #ffffff;--panel-2: #fef3c7;--panel-3: #fde68a;--line: rgba(180, 83, 9, .18);--line-strong: rgba(180, 83, 9, .3);--text: #451a03;--muted: #92400e;--muted-2: #b45309;--accent: #b45309;--accent-2: #d97706}body[data-theme-family=nord][data-theme-mode=dark]{--bg: #111827;--bg-2: #172033;--panel: #1f2937;--panel-2: #273449;--panel-3: #162031;--line: rgba(203, 213, 225, .14);--line-strong: rgba(203, 213, 225, .27);--text: #f8fafc;--muted: #cbd5e1;--muted-2: #94a3b8;--accent: #93c5fd;--accent-2: #60a5fa}body[data-theme-family=nord][data-theme-mode=light]{--bg: #f8fafc;--bg-2: #ffffff;--panel: #ffffff;--panel-2: #f1f5f9;--panel-3: #e2e8f0;--line: rgba(71, 85, 105, .16);--line-strong: rgba(71, 85, 105, .28);--text: #1e293b;--muted: #475569;--muted-2: #64748b;--accent: #2563eb;--accent-2: #0ea5e9}body[data-theme-family=high-contrast][data-theme-mode=dark]{--bg: #000000;--bg-2: #000000;--panel: #000000;--panel-2: #111111;--panel-3: #000000;--line: #ffffff;--line-strong: #ffffff;--text: #ffffff;--muted: #d1d5db;--muted-2: #f3f4f6;--accent: #ffffff;--accent-2: #ffffff;--accent-contrast: #000000}body[data-theme-family=high-contrast][data-theme-mode=light]{--bg: #ffffff;--bg-2: #ffffff;--panel: #ffffff;--panel-2: #f3f4f6;--panel-3: #ffffff;--line: #000000;--line-strong: #000000;--text: #000000;--muted: #111827;--muted-2: #1f2937;--accent: #000000;--accent-2: #000000;--accent-contrast: #ffffff}*{box-sizing:border-box}html,body,#root{min-height:100%;margin:0}body{background:var(--bg);color:var(--text);overflow:hidden}button,input,select,textarea{font:inherit}button,select,input,textarea{color:var(--text)}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--accent);outline-offset:2px}button{cursor:pointer}button:disabled{cursor:not-allowed;opacity:.45}.app-shell{display:grid;grid-template-columns:auto minmax(620px,1fr) 340px;height:100vh;min-height:720px;background:linear-gradient(180deg,var(--accent-faint),transparent 34%),var(--bg)}.app-shell.inspector-collapsed-shell{grid-template-columns:auto minmax(620px,1fr) 44px}.sidebar{position:relative;z-index:60;width:232px;display:flex;flex-direction:column;gap:14px;padding:14px 12px;border-right:1px solid var(--line);background:var(--bg-2)}.sidebar-collapsed{width:68px;align-items:center}.brand-row{display:grid;grid-template-columns:30px 1fr 30px;align-items:center;gap:9px;min-height:38px}.sidebar-collapsed .brand-row{display:flex;flex-direction:column}.brand-mark{width:30px;height:30px;display:grid;place-items:center;border:1px solid var(--accent-line);border-radius:8px;color:var(--accent);background:var(--accent-soft)}.brand-row h1,.brand-row p,.panel-header h2,.inspector-header h2,.settings-block h3,.modal h2{margin:0;letter-spacing:0}.brand-row h1{font-size:15px;line-height:1.15}.brand-row p{color:var(--muted);font-size:11px;margin-top:2px}.connection-pill{min-height:30px;display:flex;align-items:center;gap:8px;padding:6px 8px;border:1px solid var(--line);border-radius:8px;color:var(--muted);background:var(--panel);font-size:12px}.connection-pill span{width:8px;height:8px;border-radius:999px;background:var(--muted-2)}.connection-pill.good span{background:var(--success)}.connection-pill.warn span{background:var(--warning)}.connection-pill.bad span{background:var(--danger)}.connection-pill strong{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.nav-stack{display:grid;gap:6px}.nav-button{position:relative;width:100%;display:flex;align-items:center;gap:9px;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--muted);padding:9px 10px;text-align:left;font-size:13px}.sidebar-collapsed .nav-button{justify-content:center;padding:9px}.sidebar-tooltip{position:absolute;left:calc(100% + 10px);top:50%;z-index:80;min-width:max-content;max-width:180px;transform:translateY(-50%) translate(-4px);border:1px solid var(--line-strong);border-radius:7px;background:var(--panel);box-shadow:var(--shadow);color:var(--text);padding:6px 8px;pointer-events:none;white-space:nowrap;font-size:12px;font-weight:700;transform:translateY(-50%) translate(0)}.nav-button.active,.nav-button:hover{border-color:var(--line);color:var(--text);background:var(--panel)}.sidebar-section{padding-top:12px;border-top:1px solid var(--line);display:grid;gap:9px}.section-title{color:var(--muted-2);font-size:10px;line-height:1;text-transform:uppercase;font-weight:800}.mini-meter{display:grid;gap:5px}.mini-meter div:first-child,.runtime-line{display:flex;justify-content:space-between;gap:10px;font-size:12px}.mini-meter span,.runtime-line span{color:var(--muted)}.mini-meter strong,.runtime-line strong{color:var(--text);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.runtime-line.good strong{color:var(--success)}.runtime-line.warn strong{color:var(--warning)}.meter-track{height:5px;overflow:hidden;border-radius:999px;background:#94a3b829}.meter-track span{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,var(--accent),var(--success))}.quick-settings label,.inline-field,.form-field{display:grid;gap:5px;color:var(--muted);font-size:11px;font-weight:700}.quick-settings select,.inline-field select,.form-field input,.form-field select,.form-field textarea,.search-field input,.logs-toolbar select{min-height:32px;width:100%;border:1px solid var(--line);border-radius:7px;background:var(--panel-3);color:var(--text);padding:0 9px;font-size:12px}.form-field textarea{min-height:88px;padding:9px;resize:vertical}.form-field.invalid input{border-color:#ef4444cc;background:#ef444414}.form-field em{color:var(--danger);font-style:normal;margin-left:2px}.segmented{display:grid;grid-template-columns:repeat(3,1fr);border:1px solid var(--line);border-radius:8px;overflow:hidden}.segmented button{height:30px;border:0;border-right:1px solid var(--line);color:var(--muted);background:var(--panel-3)}.segmented button:last-child{border-right:0}.segmented button.active{color:var(--accent);background:var(--accent-soft)}.main-region{min-width:0;display:flex;flex-direction:column;gap:12px;padding:14px;overflow:auto}.top-toolbar,.panel,.inspector,.modal,.metric-tile{border:1px solid var(--line);background:color-mix(in srgb,var(--panel) 92%,transparent);box-shadow:var(--shadow)}.top-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:54px;padding:9px 10px;border-radius:8px}.toolbar-caption,.panel-header p,.chart-header span,.metric-tile span,.logs-status,.modal p{color:var(--muted);font-size:11px}.top-toolbar strong{display:block;margin-top:2px;font-size:13px}.toolbar-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap;justify-content:flex-end}.button,.icon-button{display:inline-flex;align-items:center;justify-content:center;gap:7px;border:1px solid var(--line);border-radius:7px;color:var(--text);background:var(--panel-2);min-height:32px;padding:0 10px;font-size:12px;font-weight:700}.button:hover,.icon-button:hover{border-color:var(--line-strong);background:color-mix(in srgb,var(--panel-2) 78%,var(--accent))}.button.primary{color:var(--accent-contrast);border-color:transparent;background:var(--accent)}.button.danger-button{color:#fff;border-color:#ef444473;background:var(--danger)}.button.secondary{background:var(--panel-3)}.button.ghost,.icon-button.ghost{background:transparent}.icon-button{width:30px;min-width:30px;padding:0}.icon-button.danger:hover{color:#fecaca;border-color:#ef444473;background:#ef444424}.icon-button.active{color:var(--accent);border-color:var(--accent-line);background:var(--accent-soft)}.busy-chip{display:inline-flex;align-items:center;gap:6px;min-height:30px;border:1px solid rgba(245,158,11,.35);border-radius:999px;padding:0 9px;color:var(--warning);background:#f59e0b1a;font-size:12px}.summary-row{display:grid;grid-template-columns:repeat(7,minmax(88px,1fr));gap:8px}.metric-tile{min-height:76px;display:grid;align-content:space-between;border-radius:8px;padding:10px}.metric-tile div{display:flex;align-items:center;gap:7px;color:var(--muted)}.metric-tile strong{font-size:22px;line-height:1}.metric-tile.good strong{color:var(--success)}.metric-tile.warn strong{color:var(--warning)}.metric-tile.bad strong{color:var(--danger)}.panel{border-radius:8px;padding:12px}.panel-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.panel-header h2{font-size:15px}.panel-header p{margin:3px 0 0}.metric-history{min-height:176px}.chart-grid{display:grid;grid-template-columns:repeat(4,minmax(140px,1fr));gap:8px}.chart-panel{height:96px;min-height:0;border:1px solid var(--line);border-radius:8px;background:var(--panel-3);padding:7px}.chart-header{display:flex;justify-content:space-between;gap:10px;margin-bottom:4px}.chart-header strong{font-size:12px}.spark-chart{display:block;width:100%;height:52px}.process-panel{min-height:420px;padding-bottom:0}.process-header{align-items:center}.process-tools{display:flex;align-items:center;justify-content:flex-end;gap:7px;flex-wrap:wrap}.process-tools .status-filter{height:32px;min-width:144px;display:flex;align-items:center;gap:7px;border:1px solid var(--line);border-radius:7px;background:var(--panel-3);padding:0 8px;color:var(--muted)}.process-tools .status-filter select{min-height:0;border:0;background:transparent;padding:0;outline:0}.density-toggle{width:188px}.density-toggle button{padding:0 8px;font-size:11px}.search-field{min-width:220px;height:32px;display:flex;align-items:center;gap:7px;border:1px solid var(--line);border-radius:7px;background:var(--panel-3);padding:0 8px;color:var(--muted)}.search-field input{min-height:0;border:0;background:transparent;padding:0;outline:0}.search-field.mini{min-width:150px;flex:1}.bulk-bar{min-height:40px;display:flex;align-items:center;gap:7px;margin-bottom:10px;padding:7px 8px;border:1px solid var(--accent-line);border-radius:8px;background:var(--accent-selected)}.bulk-bar strong{margin-right:auto;font-size:12px}.table-scroll{height:min(47vh,540px);min-height:360px;overflow:auto;border-top:1px solid var(--line);margin:0 -12px}.process-table{width:100%;min-width:980px;border-collapse:separate;border-spacing:0;table-layout:fixed;font-size:12px}.process-table th:nth-child(2),.process-table td:nth-child(2){width:96px}.process-table th:nth-child(3),.process-table td:nth-child(3){width:52px}.process-table th:nth-child(4),.process-table td:nth-child(4){width:190px}.process-table th:nth-child(5),.process-table td:nth-child(5),.process-table th:nth-child(6),.process-table td:nth-child(6){width:76px}.process-table th:nth-child(7),.process-table td:nth-child(7){width:82px}.process-table th:nth-child(8),.process-table td:nth-child(8),.process-table th:nth-child(9),.process-table td:nth-child(9){width:118px}.process-table thead th{position:sticky;top:0;z-index:2;height:36px;border-bottom:1px solid var(--line);background:var(--panel-3);color:var(--muted);text-align:left;font-size:11px;font-weight:800;text-transform:uppercase}.process-table th,.process-table td{height:42px;padding:0 9px;border-bottom:1px solid rgba(148,163,184,.09);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.process-table-comfortable th,.process-table-comfortable td{height:52px}.process-table-comfortable{font-size:13px}.process-table tbody tr{background:transparent}.process-table tbody tr:hover,.process-table tbody tr.selected{background:var(--accent-selected)}.process-table tbody tr.busy{opacity:.7}.check-col{width:42px;text-align:center}.actions-col{width:184px}.sort-control{border:0;background:transparent;color:inherit;padding:0;font-size:inherit;font-weight:inherit;text-transform:inherit}.sort-control span{color:var(--accent);text-transform:none}.mono{font-family:SFMono-Regular,Consolas,monospace}.name-cell{display:flex;align-items:center;gap:7px}.name-cell strong{overflow:hidden;text-overflow:ellipsis}.protected-badge,.status-badge{display:inline-flex;align-items:center;gap:4px;height:22px;border:1px solid var(--line);border-radius:999px;padding:0 7px;font-size:10px;font-weight:800;text-transform:uppercase}.protected-badge{color:#f87171;border-color:#f8717180;background:#ef44441f}.protected-badge svg{color:#ef4444;stroke-width:2.7}.status-badge.online{color:#86efac;border-color:#22c55e66;background:#22c55e1f}.status-badge.stopped{color:#fcd34d;border-color:#f59e0b66;background:#f59e0b1f}.status-badge.errored{color:#fca5a5;border-color:#ef444466;background:#ef44441f}.status-badge.launching{color:var(--accent-2);border-color:var(--accent-line);background:var(--accent-soft)}.status-badge.unknown{color:var(--muted)}.row-actions{display:flex;align-items:center;gap:5px}.column-menu{position:relative}.column-popover{position:absolute;top:calc(100% + 7px);right:0;z-index:20;width:164px;display:grid;gap:6px;border:1px solid var(--line);border-radius:8px;background:var(--panel);box-shadow:var(--shadow);padding:8px}.column-popover label{display:flex;align-items:center;gap:8px;font-size:12px}.empty-cell{height:160px;text-align:center;color:var(--muted)}.empty-state{min-height:210px;display:grid;place-items:center;align-content:center;gap:8px;color:var(--muted)}.empty-state strong{color:var(--text)}.empty-state p{max-width:430px;margin:0;text-align:center}.empty-state div{display:flex;gap:8px}.logs-workspace{min-height:calc(100vh - 90px)}.logs-workspace-header{align-items:center}.logs-workspace-grid{display:grid;grid-template-columns:minmax(220px,280px) minmax(0,1fr);gap:12px;min-height:0}.logs-process-list{display:grid;align-content:start;gap:7px;min-height:0;max-height:calc(100vh - 180px);overflow:auto;padding-right:2px}.logs-process-list button{width:100%;min-height:58px;display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--line);border-radius:8px;color:var(--text);background:var(--panel-3);padding:8px;text-align:left}.logs-process-list button:hover,.logs-process-list button.active{border-color:var(--accent-line);background:var(--accent-selected)}.logs-process-list span{min-width:0;display:grid;gap:3px}.logs-process-list strong,.logs-process-list small{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.logs-process-list strong{font-size:13px}.logs-process-list small{color:var(--muted);font-size:11px}.logs-workspace-viewer{min-width:0;min-height:0;border:1px solid var(--line);border-radius:8px;background:var(--panel-3);padding:10px}.logs-workspace-viewer .log-output{height:calc(100vh - 250px);min-height:420px}.inspector{min-width:0;display:flex;flex-direction:column;border-width:0 0 0 1px;border-radius:0;box-shadow:none;padding:14px 12px;overflow:auto;background:var(--bg-2)}.inspector-collapsed{width:44px;min-width:44px;align-items:center;overflow:visible;padding:10px 6px}.inspector-rail-button{width:32px;min-height:180px;display:grid;place-items:center;align-content:center;gap:10px;border:1px solid var(--line);border-radius:8px;color:var(--muted);background:var(--panel-3);font-size:11px;font-weight:800}.inspector-rail-button:hover{color:var(--text);border-color:var(--accent-line);background:var(--accent-selected)}.inspector-rail-button span{writing-mode:vertical-rl;text-orientation:mixed}.inspector-header{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:10px}.inspector-header h2{font-size:18px;line-height:1.15;word-break:break-word}.badge-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.inspector-actions{padding-bottom:12px;border-bottom:1px solid var(--line)}.tabs{display:grid;grid-template-columns:repeat(4,1fr);gap:4px;margin:12px 0}.tabs button{height:30px;border:1px solid var(--line);border-radius:7px;color:var(--muted);background:var(--panel-3);font-size:11px;font-weight:800}.tabs button.active{color:var(--accent);border-color:var(--accent-line);background:var(--accent-soft)}.key-grid{display:grid;gap:7px}.protection-toggle{min-height:48px;display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:9px;margin-bottom:8px;padding:9px;border:1px solid var(--accent-line);border-radius:8px;background:var(--accent-selected)}.protection-toggle input{width:16px;height:16px}.protection-toggle span{min-width:0;display:grid;gap:2px}.protection-toggle strong{color:var(--text);font-size:12px}.protection-toggle small{color:var(--muted);font-size:11px;line-height:1.3}.key-row{display:grid;grid-template-columns:112px minmax(0,1fr);align-items:start;gap:8px;min-height:34px;padding:8px;border:1px solid var(--line);border-radius:8px;background:var(--panel)}.key-row span{color:var(--muted);font-size:11px}.key-row strong{min-width:0;overflow:hidden;color:var(--text);font-size:12px;text-overflow:ellipsis;white-space:nowrap}.inspector-empty{min-height:100%;display:grid;place-items:center;align-content:center;gap:9px;color:var(--muted);text-align:center}.inspector-empty strong{color:var(--text)}.inspector-empty p{max-width:260px;margin:0;font-size:12px}.logs-viewer{display:grid;gap:8px;min-height:0}.logs-toolbar{display:flex;align-items:center;gap:5px;flex-wrap:wrap}.logs-toolbar select{width:68px}.log-output{height:360px;overflow:auto;border:1px solid var(--line);border-radius:8px;background:#06090d;padding:8px;font-family:SFMono-Regular,Consolas,monospace;font-size:11px}body[data-theme-mode=light] .log-output{background:#111827;color:#e5edf6}.log-output.wrap .log-line p{white-space:pre-wrap}.log-output>p{margin:0;color:var(--muted)}.log-line{display:grid;grid-template-columns:48px 138px minmax(0,1fr);gap:8px;min-height:22px;align-items:start}.log-line span{color:var(--success);font-weight:800}.log-line.stderr span{color:var(--danger)}.log-line time{color:#8b99a9}.log-line p{margin:0;overflow:hidden;color:#dbeafe;text-overflow:ellipsis;white-space:pre}.settings-view{min-height:calc(100vh - 90px)}.settings-grid{display:grid;grid-template-columns:minmax(260px,360px) minmax(320px,1fr);gap:12px}.settings-block{display:grid;align-content:start;gap:12px;padding:12px;border:1px solid var(--line);border-radius:8px;background:var(--panel-3)}.settings-block h3{font-size:14px}.settings-block-wide{grid-column:1 / -1}.settings-description{max-width:760px;margin:0;color:var(--muted);font-size:12px;line-height:1.45}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:50;display:grid;place-items:center;padding:20px;background:#000000a3}.modal{width:min(860px,94vw);max-height:min(760px,92vh);display:flex;flex-direction:column;border-radius:8px;overflow:hidden}.modal-header,.modal-footer{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px;border-bottom:1px solid var(--line)}.modal-footer{border-top:1px solid var(--line);border-bottom:0;justify-content:flex-end}.modal h2{font-size:17px}.modal p{margin:3px 0 0}.modal-body{overflow:auto;padding:12px}.confirm-modal{width:min(520px,94vw)}.confirm-modal-danger{border-color:#ef444459}.confirm-modal-warning{border-color:#f59e0b59}.confirm-details{display:grid;gap:7px;padding:12px;border-bottom:1px solid var(--line)}.confirm-details div{display:grid;grid-template-columns:96px minmax(0,1fr);gap:10px;min-height:30px;align-items:center;border:1px solid var(--line);border-radius:8px;background:var(--panel-3);padding:7px 8px}.confirm-details span{color:var(--muted);font-size:11px}.confirm-details strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px}.form-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.check-field{grid-template-columns:auto 1fr;align-items:center;min-height:51px;padding:18px 10px 0}.check-field input{width:auto}.toast{position:fixed;right:18px;bottom:18px;z-index:70;max-width:min(360px,calc(100vw - 36px));border:1px solid var(--line-strong);border-radius:8px;background:var(--panel);box-shadow:var(--shadow);padding:11px 12px;color:var(--text);font-size:13px;font-weight:700}.toast-success{border-color:#22c55e66}.toast-error{border-color:#ef444466}@media (max-width: 1180px){.app-shell{grid-template-columns:auto minmax(0,1fr)}.app-shell.inspector-collapsed-shell{grid-template-columns:auto minmax(0,1fr) 44px}.inspector{position:fixed;top:0;right:0;bottom:0;z-index:30;width:min(360px,94vw);transform:translate(calc(100% - 42px));transition:transform .16s ease}.inspector:hover,.inspector:focus-within{transform:translate(0)}.inspector-collapsed{width:44px;transform:none}.inspector-collapsed:hover,.inspector-collapsed:focus-within{transform:none}.summary-row,.chart-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width: 760px){body{overflow:auto}.app-shell{height:auto;min-height:100vh;grid-template-columns:1fr}.sidebar{position:sticky;top:0;z-index:40;width:100%;border-right:0;border-bottom:1px solid var(--line)}.sidebar-collapsed{width:100%}.sidebar-tooltip{display:none}.sidebar-collapsed .brand-row{flex-direction:row;width:100%}.main-region{padding:10px}.top-toolbar,.panel-header,.process-header{align-items:stretch;flex-direction:column}.toolbar-actions,.process-tools{justify-content:stretch}.button{flex:1}.summary-row,.chart-grid,.settings-grid,.form-grid,.logs-workspace-grid{grid-template-columns:1fr}.logs-process-list{max-height:230px}.logs-workspace-viewer .log-output{height:420px;min-height:360px}.table-scroll{height:60vh}.inspector{position:static;width:auto;transform:none;border-top:1px solid var(--line);border-left:0}.inspector-collapsed{width:auto;min-width:0;padding:10px}.inspector-rail-button{width:100%;min-height:40px;grid-auto-flow:column}.inspector-rail-button span{writing-mode:horizontal-tb}}
|