pgserve 2.2.1 → 2.2.3
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 +104 -0
- package/console/dist/app.js +121 -0
- package/console/dist/index.html +13 -0
- package/package.json +8 -4
- package/src/cli-install.cjs +345 -4
- package/src/cli-ui.cjs +81 -6
- package/console/README.md +0 -131
- package/console/api.js +0 -173
- package/console/app.jsx +0 -483
- package/console/components.jsx +0 -167
- package/console/data.jsx +0 -350
- package/console/index.html +0 -31
- package/console/screens/databases.jsx +0 -5
- package/console/screens/health.jsx +0 -5
- package/console/screens/ingress.jsx +0 -5
- package/console/screens/optimizer.jsx +0 -5
- package/console/screens/rlm-sim.jsx +0 -5
- package/console/screens/rlm-trace.jsx +0 -5
- package/console/screens/security.jsx +0 -5
- package/console/screens/settings.jsx +0 -611
- package/console/screens/sql.jsx +0 -5
- package/console/screens/sync.jsx +0 -5
- package/console/screens/tables.jsx +0 -5
- package/console/tweaks-panel.jsx +0 -425
- /package/console/{colors_and_type.css → dist/colors_and_type.css} +0 -0
- /package/console/{console.css → dist/console.css} +0 -0
package/console/README.md
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# `console/` — autopg console
|
|
2
|
-
|
|
3
|
-
Local web console served by `autopg ui`. React + Babel via CDN, no build
|
|
4
|
-
step. Single-user dev tool — binds 127.0.0.1 only, no auth, no TLS.
|
|
5
|
-
|
|
6
|
-
## Run
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
autopg ui # walks 8433–8533 picking the first free port
|
|
10
|
-
autopg ui --port 8500 # bind exactly 8500 or fail
|
|
11
|
-
autopg ui --no-open # skip browser launch (CI / headless)
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
`pgserve ui …` is a forever alias of the same command.
|
|
15
|
-
|
|
16
|
-
The server boots in-process via `node:http` and serves this directory as
|
|
17
|
-
its document root. Four helper endpoints are mounted alongside the static
|
|
18
|
-
assets — every mutation shells out to the CLI rather than calling the
|
|
19
|
-
daemon directly, so the console works with or without a running daemon.
|
|
20
|
-
|
|
21
|
-
| Endpoint | Backed by |
|
|
22
|
-
|----------|-----------|
|
|
23
|
-
| `GET /api/settings` | `loadEffectiveConfig()` → `{ settings, sources, etag }` |
|
|
24
|
-
| `PUT /api/settings` | `writeSettings(body, { ifMatch })` (409 on stale `If-Match`) |
|
|
25
|
-
| `POST /api/restart` | `cli-restart.cjs` (pm2-aware) |
|
|
26
|
-
| `GET /api/status` | shells out to `autopg status --json` |
|
|
27
|
-
|
|
28
|
-
## Layout
|
|
29
|
-
|
|
30
|
-
```
|
|
31
|
-
console/
|
|
32
|
-
├── README.md # this file
|
|
33
|
-
├── index.html # entry — pinned React + Babel CDN scripts
|
|
34
|
-
├── app.jsx # shell + sidebar router (11 routes)
|
|
35
|
-
├── api.js # fetch wrapper, holds latest etag, surfaces ETAG_MISMATCH
|
|
36
|
-
├── components.jsx # shared widgets (Seg, Toggle, Field, …)
|
|
37
|
-
├── data.jsx # demo data fixtures (used by placeholder screens)
|
|
38
|
-
├── tweaks-panel.jsx # theme/phosphor/density/CRT toggles (persists to settings.ui)
|
|
39
|
-
├── colors_and_type.css # design tokens
|
|
40
|
-
├── console.css # layout + screen styles
|
|
41
|
-
└── screens/
|
|
42
|
-
├── settings.jsx # ✅ functional — 6-section schema editor
|
|
43
|
-
├── databases.jsx # [ coming soon ]
|
|
44
|
-
├── tables.jsx # [ coming soon ]
|
|
45
|
-
├── sql.jsx # [ coming soon ]
|
|
46
|
-
├── optimizer.jsx # [ coming soon ]
|
|
47
|
-
├── security.jsx # [ coming soon ]
|
|
48
|
-
├── ingress.jsx # [ coming soon ]
|
|
49
|
-
├── health.jsx # [ coming soon ] — next wish
|
|
50
|
-
├── sync.jsx # [ coming soon ]
|
|
51
|
-
├── rlm-trace.jsx # [ coming soon ]
|
|
52
|
-
└── rlm-sim.jsx # [ coming soon ]
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Screen rollout
|
|
56
|
-
|
|
57
|
-
| Screen | Status | Notes |
|
|
58
|
-
|--------|--------|-------|
|
|
59
|
-
| Settings | ✅ functional | 6 sections, type-aware controls, raw GUC passthrough, etag concurrency, env-override chip |
|
|
60
|
-
| Health | 🟡 next | Live cluster health metrics — next wish |
|
|
61
|
-
| Databases | ⚪ placeholder | List + create + drop |
|
|
62
|
-
| Tables | ⚪ placeholder | Per-DB table inspector |
|
|
63
|
-
| SQL | ⚪ placeholder | Ad-hoc query runner |
|
|
64
|
-
| Optimizer | ⚪ placeholder | Plan inspector / GUC tuner suggestions |
|
|
65
|
-
| Security | ⚪ placeholder | Roles, RLS, audit log |
|
|
66
|
-
| Ingress | ⚪ placeholder | Listener / TLS / token surface |
|
|
67
|
-
| Sync | ⚪ placeholder | Replication-slot status |
|
|
68
|
-
| RLM-trace | ⚪ placeholder | RLM agent trace viewer (depends on rlmx) |
|
|
69
|
-
| RLM-sim | ⚪ placeholder | RLM scenario simulator (depends on rlmx) |
|
|
70
|
-
|
|
71
|
-
## Local dev loop
|
|
72
|
-
|
|
73
|
-
The console is shipped as static files — no build, no bundler. Edit the
|
|
74
|
-
`.jsx` files in place; refresh the browser tab to pick up the change
|
|
75
|
-
(Babel transpiles in the browser at load time). The CDN scripts are
|
|
76
|
-
pinned with SRI integrity hashes — bumping React or Babel requires
|
|
77
|
-
re-pinning the matching `integrity="sha384-…"` attribute in
|
|
78
|
-
[`index.html`](./index.html).
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
autopg ui --no-open --port 8500 &
|
|
82
|
-
open http://127.0.0.1:8500
|
|
83
|
-
# … edit screens/settings.jsx, refresh browser …
|
|
84
|
-
kill %1
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
The Settings screen reads live state from `~/.autopg/settings.json`
|
|
88
|
-
through the helper endpoints, so changes survive reload and round-trip
|
|
89
|
-
through `autopg config get` from another shell.
|
|
90
|
-
|
|
91
|
-
### Concurrency model
|
|
92
|
-
|
|
93
|
-
`api.js` stores the etag returned by every successful GET and sends it
|
|
94
|
-
back as `If-Match` on the next PUT. If a parallel `autopg config set` (or
|
|
95
|
-
another browser tab) drifts the file, the PUT comes back as
|
|
96
|
-
`409 ETAG_MISMATCH` with a fresh `currentEtag`. The Settings screen
|
|
97
|
-
catches this and shows a "settings changed, reload?" banner instead of
|
|
98
|
-
overwriting the operator's other changes.
|
|
99
|
-
|
|
100
|
-
### Env-override chip
|
|
101
|
-
|
|
102
|
-
`GET /api/settings` returns a `sources` map (one entry per leaf:
|
|
103
|
-
`'default' | 'file' | 'env:<NAME>'`). Rows whose source starts with
|
|
104
|
-
`env:` render a yellow `OVERRIDDEN BY ENV` chip — Save still writes the
|
|
105
|
-
file, but `loadEffectiveConfig()` will keep returning the env value
|
|
106
|
-
until the env var is unset or the daemon is restarted with a clean
|
|
107
|
-
environment.
|
|
108
|
-
|
|
109
|
-
## Design system
|
|
110
|
-
|
|
111
|
-
The console UI is derived from the `pgserve-console` design kit at
|
|
112
|
-
`namastex-design-system/ui_kits/pgserve-console`. The CSS files
|
|
113
|
-
(`colors_and_type.css`, `console.css`) and the shared widgets
|
|
114
|
-
(`components.jsx`, `tweaks-panel.jsx`) are copied verbatim — the
|
|
115
|
-
soft rename only touches the topbar identity (`pgserve` → `autopg`)
|
|
116
|
-
and the Settings screen, which was rewritten to match the
|
|
117
|
-
6-section schema documented in
|
|
118
|
-
[`docs/settings-schema.md`](../docs/settings-schema.md).
|
|
119
|
-
|
|
120
|
-
## What's deliberately not here
|
|
121
|
-
|
|
122
|
-
- **No build step.** Pre-bundling is a future optimization. The CDN +
|
|
123
|
-
Babel-in-browser path is intentional for v1 — zero infrastructure.
|
|
124
|
-
- **No daemon HTTP API.** The CLI is the source of truth; every UI
|
|
125
|
-
mutation shells out. This means the UI works ahead of `autopg
|
|
126
|
-
install` (you can configure before the daemon ever runs) and
|
|
127
|
-
cannot leak privileges through a long-lived listening socket.
|
|
128
|
-
- **No multi-user / multi-machine access.** 127.0.0.1 only, by
|
|
129
|
-
design.
|
|
130
|
-
- **No telemetry, no analytics.** Static page + four endpoints, all
|
|
131
|
-
local.
|
package/console/api.js
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/* autopg console · API client.
|
|
2
|
-
*
|
|
3
|
-
* Wraps the four helper endpoints exposed by `autopg ui`:
|
|
4
|
-
* GET /api/settings → { settings, sources, etag, path }
|
|
5
|
-
* PUT /api/settings → { ok, etag } | { error: { code, message, field? } }
|
|
6
|
-
* POST /api/restart → { ok } | { error }
|
|
7
|
-
* GET /api/status → whatever `pgserve status --json` returns
|
|
8
|
-
*
|
|
9
|
-
* The latest etag from a successful GET is cached on the module so PUTs can
|
|
10
|
-
* send `If-Match` without the caller threading it through manually. PUT
|
|
11
|
-
* replies update the cached etag too so successive saves chain cleanly.
|
|
12
|
-
*
|
|
13
|
-
* Errors from the server come back as `{ error: { code, message, field? } }`.
|
|
14
|
-
* The wrapper raises a structured `ApiError` (with `.code`, `.field`,
|
|
15
|
-
* `.message`, `.status`, `.currentEtag?`) so screens can branch on the code
|
|
16
|
-
* without parsing strings. ETAG_MISMATCH is surfaced as a normal rejection
|
|
17
|
-
* with `error.code === 'ETAG_MISMATCH'` plus `error.currentEtag` so the
|
|
18
|
-
* Settings screen can show a "settings changed, reload?" banner.
|
|
19
|
-
*/
|
|
20
|
-
(function (root) {
|
|
21
|
-
'use strict';
|
|
22
|
-
|
|
23
|
-
const STATE = { etag: null };
|
|
24
|
-
|
|
25
|
-
class ApiError extends Error {
|
|
26
|
-
constructor({ code, message, field, status, currentEtag }) {
|
|
27
|
-
super(message || code || 'api error');
|
|
28
|
-
this.name = 'ApiError';
|
|
29
|
-
this.code = code || 'UNKNOWN';
|
|
30
|
-
if (field) this.field = field;
|
|
31
|
-
if (typeof status === 'number') this.status = status;
|
|
32
|
-
if (currentEtag) this.currentEtag = currentEtag;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function parseJson(res) {
|
|
37
|
-
const text = await res.text();
|
|
38
|
-
if (!text) return {};
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(text);
|
|
41
|
-
} catch {
|
|
42
|
-
return { _raw: text };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function getSettings() {
|
|
47
|
-
const res = await fetch('/api/settings', {
|
|
48
|
-
method: 'GET',
|
|
49
|
-
headers: { accept: 'application/json' },
|
|
50
|
-
cache: 'no-store',
|
|
51
|
-
});
|
|
52
|
-
const body = await parseJson(res);
|
|
53
|
-
if (!res.ok) {
|
|
54
|
-
throw new ApiError({
|
|
55
|
-
code: body?.error?.code,
|
|
56
|
-
message: body?.error?.message,
|
|
57
|
-
field: body?.error?.field,
|
|
58
|
-
status: res.status,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
if (body && body.etag) STATE.etag = body.etag;
|
|
62
|
-
return body;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function putSettings(patch, { ifMatch } = {}) {
|
|
66
|
-
const etag = ifMatch ?? STATE.etag;
|
|
67
|
-
if (!etag) {
|
|
68
|
-
throw new ApiError({
|
|
69
|
-
code: 'PRECONDITION_REQUIRED',
|
|
70
|
-
message: 'no etag cached — call getSettings() before putSettings()',
|
|
71
|
-
status: 428,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
const res = await fetch('/api/settings', {
|
|
75
|
-
method: 'PUT',
|
|
76
|
-
headers: {
|
|
77
|
-
'content-type': 'application/json',
|
|
78
|
-
'if-match': etag,
|
|
79
|
-
accept: 'application/json',
|
|
80
|
-
},
|
|
81
|
-
body: JSON.stringify(patch ?? {}),
|
|
82
|
-
});
|
|
83
|
-
const body = await parseJson(res);
|
|
84
|
-
if (res.status === 409) {
|
|
85
|
-
// Update the cached etag so the next reload has the latest.
|
|
86
|
-
if (body && body.currentEtag) STATE.etag = body.currentEtag;
|
|
87
|
-
throw new ApiError({
|
|
88
|
-
code: body?.error?.code || 'ETAG_MISMATCH',
|
|
89
|
-
message: body?.error?.message || 'settings changed on disk',
|
|
90
|
-
status: 409,
|
|
91
|
-
currentEtag: body?.currentEtag,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
if (!res.ok) {
|
|
95
|
-
throw new ApiError({
|
|
96
|
-
code: body?.error?.code,
|
|
97
|
-
message: body?.error?.message,
|
|
98
|
-
field: body?.error?.field,
|
|
99
|
-
status: res.status,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
if (body && body.etag) STATE.etag = body.etag;
|
|
103
|
-
return body;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function restart() {
|
|
107
|
-
const res = await fetch('/api/restart', {
|
|
108
|
-
method: 'POST',
|
|
109
|
-
headers: { accept: 'application/json' },
|
|
110
|
-
});
|
|
111
|
-
const body = await parseJson(res);
|
|
112
|
-
if (!res.ok) {
|
|
113
|
-
throw new ApiError({
|
|
114
|
-
code: body?.error?.code,
|
|
115
|
-
message: body?.error?.message,
|
|
116
|
-
status: res.status,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return body;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function getStatus() {
|
|
123
|
-
const res = await fetch('/api/status', {
|
|
124
|
-
method: 'GET',
|
|
125
|
-
headers: { accept: 'application/json' },
|
|
126
|
-
cache: 'no-store',
|
|
127
|
-
});
|
|
128
|
-
const body = await parseJson(res);
|
|
129
|
-
if (!res.ok) {
|
|
130
|
-
throw new ApiError({
|
|
131
|
-
code: body?.error?.code,
|
|
132
|
-
message: body?.error?.message,
|
|
133
|
-
status: res.status,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
return body;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Live pgserve stats — connections + databases. Always returns a body
|
|
140
|
-
// (status 200) even when the daemon is unreachable; check `body.ok`.
|
|
141
|
-
// Safe to poll from the topbar without try/catch error handling.
|
|
142
|
-
async function getStats() {
|
|
143
|
-
try {
|
|
144
|
-
const res = await fetch('/api/stats', {
|
|
145
|
-
method: 'GET',
|
|
146
|
-
headers: { accept: 'application/json' },
|
|
147
|
-
cache: 'no-store',
|
|
148
|
-
});
|
|
149
|
-
return await parseJson(res);
|
|
150
|
-
} catch (err) {
|
|
151
|
-
return { ok: false, reason: 'fetch-failed', message: err.message };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function getCachedEtag() {
|
|
156
|
-
return STATE.etag;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function setCachedEtag(etag) {
|
|
160
|
-
STATE.etag = etag || null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
root.AutopgApi = {
|
|
164
|
-
getSettings,
|
|
165
|
-
putSettings,
|
|
166
|
-
restart,
|
|
167
|
-
getStatus,
|
|
168
|
-
getStats,
|
|
169
|
-
getCachedEtag,
|
|
170
|
-
setCachedEtag,
|
|
171
|
-
ApiError,
|
|
172
|
-
};
|
|
173
|
-
})(typeof window !== 'undefined' ? window : globalThis);
|