@kazurolabs/psql-explorer 0.1.0 → 0.3.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 +229 -18
- package/RELEASES.md +87 -0
- package/dist/cli.js +57 -13
- package/dist/commands/configgen.js +25 -25
- package/dist/commands/explore.js +86 -4
- package/dist/config/load.js +11 -73
- package/dist/config/resolve-environment.js +10 -62
- package/dist/config/resolvers/index.js +1 -1
- package/dist/config/resolvers/prompt.js +2 -2
- package/dist/config/resolvers/vault.js +1 -83
- package/dist/db/store.js +57 -0
- package/dist/environments.js +5 -2
- package/dist/explorer/alter.js +493 -0
- package/dist/explorer/catalog.js +31 -0
- package/dist/explorer/connect-entry.js +29 -18
- package/dist/explorer/cypher-parse.js +478 -0
- package/dist/explorer/cypher.js +963 -0
- package/dist/explorer/ddl.js +502 -0
- package/dist/explorer/dependents.js +222 -0
- package/dist/explorer/design-load.js +254 -0
- package/dist/explorer/design.js +45 -0
- package/dist/explorer/edit.js +108 -0
- package/dist/explorer/explain.js +24 -0
- package/dist/explorer/filter.js +121 -0
- package/dist/explorer/graph.js +679 -0
- package/dist/explorer/gucs.js +31 -0
- package/dist/explorer/profile.js +102 -0
- package/dist/explorer/references.js +95 -0
- package/dist/explorer/rows.js +22 -8
- package/dist/explorer/schema-diff.js +360 -0
- package/dist/explorer/search.js +309 -0
- package/dist/explorer/security.js +152 -0
- package/dist/explorer/seed.js +1376 -0
- package/dist/explorer/session.js +17 -0
- package/dist/explorer/stats.js +135 -0
- package/dist/explorer/timeout.js +13 -0
- package/dist/screens/environments.js +6 -3
- package/dist/screens/explorer.js +0 -3
- package/dist/screens/splash.js +4 -2
- package/dist/web/server.js +654 -21
- package/package.json +3 -1
- package/web/dist/assets/antonio-latin-400-normal-B7iMKlzw.woff +0 -0
- package/web/dist/assets/antonio-latin-400-normal-CZjzwNAN.woff2 +0 -0
- package/web/dist/assets/antonio-latin-700-normal-BY3mCh47.woff +0 -0
- package/web/dist/assets/antonio-latin-700-normal-DSH-lEDG.woff2 +0 -0
- package/web/dist/assets/antonio-latin-ext-400-normal-nE82dizb.woff2 +0 -0
- package/web/dist/assets/antonio-latin-ext-400-normal-qA9Jb92z.woff +0 -0
- package/web/dist/assets/antonio-latin-ext-700-normal-BdSkr58W.woff +0 -0
- package/web/dist/assets/antonio-latin-ext-700-normal-D-dvgqjr.woff2 +0 -0
- package/web/dist/assets/button-ClA70PLR.js +1 -0
- package/web/dist/assets/code-xml-CXkpHeU9.js +1 -0
- package/web/dist/assets/compare-view-DKl5l_Jw.js +2 -0
- package/web/dist/assets/copy-CCXzeW-s.js +1 -0
- package/web/dist/assets/cypher-view-Pd0WFH8F.js +13 -0
- package/web/dist/assets/design-view-CIHSmH4u.js +9 -0
- package/web/dist/assets/erd-view-CV7mFGAN.js +1 -0
- package/web/dist/assets/format-sql-BLj5_RDZ.js +5 -0
- package/web/dist/assets/graph-force-yBLyErpS.js +1 -0
- package/web/dist/assets/graph-link-OCo_s0zO.js +1 -0
- package/web/dist/assets/graph-view-sM1OlByf.js +3 -0
- package/web/dist/assets/index-CDcnUacl.js +10 -0
- package/web/dist/assets/index-OzJqnJWG.css +2 -0
- package/web/dist/assets/key-round-Cv_7QeED.js +1 -0
- package/web/dist/assets/loader-circle-BK_GMdvN.js +1 -0
- package/web/dist/assets/query-builder-view-ChNFb51g.js +4 -0
- package/web/dist/assets/rotate-ccw-CdCaf7Q6.js +1 -0
- package/web/dist/assets/route-DO6IZ7of.js +15 -0
- package/web/dist/assets/seed-view-DFNB8WHE.js +7 -0
- package/web/dist/assets/shield-alert-CMS-3j4U.js +1 -0
- package/web/dist/assets/sliders-horizontal-Dg51yc-C.js +1 -0
- package/web/dist/assets/sql-tabs-B4jt6bQj.js +2 -0
- package/web/dist/assets/table-DfMz2f8M.js +1 -0
- package/web/dist/assets/triangle-alert-COGnHCD1.js +1 -0
- package/web/dist/assets/zone-D9dXk8tn.js +5 -0
- package/web/dist/index.html +9 -2
- package/web/dist/assets/index-BMLFCtFY.js +0 -17
- package/web/dist/assets/index-CMXJRL9O.css +0 -2
package/dist/explorer/session.js
CHANGED
|
@@ -100,6 +100,23 @@ export const openSession = async (params, options = {}) => {
|
|
|
100
100
|
};
|
|
101
101
|
}),
|
|
102
102
|
rows: (sql, values = []) => serial(async () => (await client.query(sql, [...values])).rows),
|
|
103
|
+
// From a connection of its own, since this one is busy with the statement to stop.
|
|
104
|
+
cancel: async () => {
|
|
105
|
+
const pid = client.processID;
|
|
106
|
+
if (!pid)
|
|
107
|
+
return;
|
|
108
|
+
const other = createClient(params, { ...options, statementTimeoutMs: 5000, readOnly: true });
|
|
109
|
+
try {
|
|
110
|
+
await other.connect();
|
|
111
|
+
await other.query('SELECT pg_catalog.pg_cancel_backend($1)', [pid]);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// The statement may have finished meanwhile, or the server cannot be reached.
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
await other.end().catch(() => undefined);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
103
120
|
close: () => serial(() => client.end().catch(() => undefined)),
|
|
104
121
|
};
|
|
105
122
|
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { qualified } from './catalog.js';
|
|
2
|
+
const utc = (column) => `to_char(${column} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"')`;
|
|
3
|
+
const kinds = {
|
|
4
|
+
r: 'table',
|
|
5
|
+
p: 'partitioned',
|
|
6
|
+
m: 'materialized',
|
|
7
|
+
};
|
|
8
|
+
const count = (value) => (value === null ? null : Number(value));
|
|
9
|
+
const flag = (value) => value === 't';
|
|
10
|
+
/**
|
|
11
|
+
* Tables and materialized views with their sizes and upkeep, biggest first. The schemas of the
|
|
12
|
+
* system are left out; `table` narrows it to one.
|
|
13
|
+
*/
|
|
14
|
+
export async function listTableStats(session, table) {
|
|
15
|
+
const rows = await session.rows(`WITH rel AS (
|
|
16
|
+
SELECT c.oid, n.nspname AS schema, c.relname AS name, c.relkind AS kind, c.reltuples,
|
|
17
|
+
c.relpages,
|
|
18
|
+
pg_catalog.pg_relation_size(c.oid) AS heap_bytes,
|
|
19
|
+
pg_catalog.pg_table_size(c.oid) AS table_and_toast_bytes,
|
|
20
|
+
pg_catalog.pg_indexes_size(c.oid) AS index_bytes,
|
|
21
|
+
CASE WHEN c.reltoastrelid = 0 THEN 0
|
|
22
|
+
ELSE pg_catalog.pg_total_relation_size(c.reltoastrelid) END AS toast_bytes,
|
|
23
|
+
pg_catalog.pg_total_relation_size(c.oid) AS total_bytes,
|
|
24
|
+
-- The bytes a row needs: its header, its pointer in the page, and its columns.
|
|
25
|
+
(SELECT 24 + 4 + sum(s.avg_width) FROM pg_catalog.pg_stats s
|
|
26
|
+
WHERE s.schemaname = n.nspname AND s.tablename = c.relname
|
|
27
|
+
AND NOT s.inherited) AS row_bytes,
|
|
28
|
+
coalesce((SELECT option_value::numeric
|
|
29
|
+
FROM pg_catalog.pg_options_to_table(c.reloptions)
|
|
30
|
+
WHERE option_name = 'fillfactor'), 100) AS fillfactor
|
|
31
|
+
FROM pg_catalog.pg_class c
|
|
32
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
33
|
+
WHERE c.relkind IN ('r', 'p', 'm')
|
|
34
|
+
AND n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
|
|
35
|
+
AND ($1::text IS NULL OR c.oid = $1::regclass)
|
|
36
|
+
)
|
|
37
|
+
SELECT r.schema, r.name, r.kind,
|
|
38
|
+
CASE WHEN r.reltuples < 0 THEN NULL ELSE r.reltuples::int8 END AS estimated_rows,
|
|
39
|
+
r.table_and_toast_bytes - r.toast_bytes AS table_bytes,
|
|
40
|
+
r.index_bytes, r.toast_bytes, r.total_bytes,
|
|
41
|
+
CASE WHEN r.row_bytes IS NULL OR r.reltuples < 0 OR r.kind = 'p' THEN NULL
|
|
42
|
+
ELSE greatest(r.heap_bytes - ceil(r.reltuples * r.row_bytes
|
|
43
|
+
/ ((current_setting('block_size')::int - 24) * r.fillfactor / 100))
|
|
44
|
+
* current_setting('block_size')::int, 0)::int8
|
|
45
|
+
END AS bloat_bytes,
|
|
46
|
+
s.n_live_tup AS live_rows, s.n_dead_tup AS dead_rows,
|
|
47
|
+
s.seq_scan AS seq_scans, s.idx_scan AS index_scans,
|
|
48
|
+
${utc('s.last_vacuum')} AS last_vacuum, ${utc('s.last_autovacuum')} AS last_autovacuum,
|
|
49
|
+
${utc('s.last_analyze')} AS last_analyze, ${utc('s.last_autoanalyze')} AS last_autoanalyze
|
|
50
|
+
FROM rel r
|
|
51
|
+
LEFT JOIN pg_catalog.pg_stat_all_tables s ON s.relid = r.oid
|
|
52
|
+
ORDER BY r.total_bytes DESC, r.schema, r.name`, [table ? qualified(table) : null]);
|
|
53
|
+
return rows.map((row) => ({
|
|
54
|
+
schema: row.schema ?? '',
|
|
55
|
+
name: row.name ?? '',
|
|
56
|
+
kind: kinds[row.kind ?? ''] ?? 'table',
|
|
57
|
+
estimatedRows: count(row.estimated_rows),
|
|
58
|
+
tableBytes: Number(row.table_bytes),
|
|
59
|
+
indexBytes: Number(row.index_bytes),
|
|
60
|
+
toastBytes: Number(row.toast_bytes),
|
|
61
|
+
totalBytes: Number(row.total_bytes),
|
|
62
|
+
bloatBytes: count(row.bloat_bytes),
|
|
63
|
+
liveRows: count(row.live_rows),
|
|
64
|
+
deadRows: count(row.dead_rows),
|
|
65
|
+
seqScans: count(row.seq_scans),
|
|
66
|
+
indexScans: count(row.index_scans),
|
|
67
|
+
lastVacuum: row.last_vacuum,
|
|
68
|
+
lastAutovacuum: row.last_autovacuum,
|
|
69
|
+
lastAnalyze: row.last_analyze,
|
|
70
|
+
lastAutoanalyze: row.last_autoanalyze,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Every index of the user's tables (or of one table) with its size, how often it was used, and the
|
|
75
|
+
* other indexes it duplicates: the same table, columns, operator classes, expressions and
|
|
76
|
+
* condition. Biggest first.
|
|
77
|
+
*/
|
|
78
|
+
export async function listIndexStats(session, table) {
|
|
79
|
+
const rows = await session.rows(`SELECT n.nspname AS schema, t.relname AS "table", i.relname AS name,
|
|
80
|
+
pg_catalog.pg_get_indexdef(x.indexrelid) AS definition,
|
|
81
|
+
pg_catalog.pg_relation_size(x.indexrelid) AS bytes,
|
|
82
|
+
x.indisprimary AS "primary", x.indisunique AS "unique", x.indisvalid AS valid,
|
|
83
|
+
EXISTS (SELECT 1 FROM pg_catalog.pg_constraint k WHERE k.conindid = x.indexrelid
|
|
84
|
+
AND k.conrelid = x.indrelid AND k.contype IN ('p', 'u', 'x')) AS "constraint",
|
|
85
|
+
coalesce(s.idx_scan, 0) AS scans,
|
|
86
|
+
array_to_json(ARRAY(SELECT o.relname FROM pg_catalog.pg_index y
|
|
87
|
+
JOIN pg_catalog.pg_class o ON o.oid = y.indexrelid
|
|
88
|
+
WHERE y.indrelid = x.indrelid AND y.indexrelid <> x.indexrelid
|
|
89
|
+
AND y.indkey::text = x.indkey::text AND y.indclass::text = x.indclass::text
|
|
90
|
+
AND y.indcollation::text = x.indcollation::text
|
|
91
|
+
AND o.relam = i.relam
|
|
92
|
+
AND coalesce(pg_catalog.pg_get_expr(y.indexprs, y.indrelid), '')
|
|
93
|
+
= coalesce(pg_catalog.pg_get_expr(x.indexprs, x.indrelid), '')
|
|
94
|
+
AND coalesce(pg_catalog.pg_get_expr(y.indpred, y.indrelid), '')
|
|
95
|
+
= coalesce(pg_catalog.pg_get_expr(x.indpred, x.indrelid), '')
|
|
96
|
+
ORDER BY o.relname))::text AS duplicate_of
|
|
97
|
+
FROM pg_catalog.pg_index x
|
|
98
|
+
JOIN pg_catalog.pg_class i ON i.oid = x.indexrelid
|
|
99
|
+
JOIN pg_catalog.pg_class t ON t.oid = x.indrelid
|
|
100
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.relnamespace
|
|
101
|
+
LEFT JOIN pg_catalog.pg_stat_all_indexes s ON s.indexrelid = x.indexrelid
|
|
102
|
+
WHERE t.relkind IN ('r', 'p', 'm')
|
|
103
|
+
AND n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
|
|
104
|
+
AND ($1::text IS NULL OR x.indrelid = $1::regclass)
|
|
105
|
+
ORDER BY bytes DESC, n.nspname, t.relname, i.relname`, [table ? qualified(table) : null]);
|
|
106
|
+
return rows.map((row) => ({
|
|
107
|
+
schema: row.schema ?? '',
|
|
108
|
+
table: row.table ?? '',
|
|
109
|
+
name: row.name ?? '',
|
|
110
|
+
definition: row.definition ?? '',
|
|
111
|
+
bytes: Number(row.bytes),
|
|
112
|
+
primary: flag(row.primary),
|
|
113
|
+
unique: flag(row.unique),
|
|
114
|
+
constraint: flag(row.constraint),
|
|
115
|
+
valid: flag(row.valid),
|
|
116
|
+
scans: Number(row.scans),
|
|
117
|
+
duplicateOf: JSON.parse(row.duplicate_of ?? '[]'),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
/** The sizes and upkeep of every table and index of the database, biggest first. */
|
|
121
|
+
export async function databaseStats(session) {
|
|
122
|
+
const [row] = await session.rows(`SELECT current_database() AS database,
|
|
123
|
+
pg_catalog.pg_database_size(current_database()) AS bytes,
|
|
124
|
+
(SELECT ${utc('stats_reset')} FROM pg_catalog.pg_stat_database
|
|
125
|
+
WHERE datname = current_database()) AS reset`);
|
|
126
|
+
const tables = await listTableStats(session);
|
|
127
|
+
const indexes = await listIndexStats(session);
|
|
128
|
+
return {
|
|
129
|
+
database: row?.database ?? '',
|
|
130
|
+
databaseBytes: Number(row?.bytes ?? 0),
|
|
131
|
+
statsReset: row?.reset ?? null,
|
|
132
|
+
tables,
|
|
133
|
+
indexes,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Runs `work` with the session's statement timeout set to `ms`, and puts the old one back. */
|
|
2
|
+
export async function withStatementTimeout(session, ms, work) {
|
|
3
|
+
const [before] = await session.rows("SELECT current_setting('statement_timeout') AS timeout");
|
|
4
|
+
await session.rows("SELECT pg_catalog.set_config('statement_timeout', $1, false)", [String(ms)]);
|
|
5
|
+
try {
|
|
6
|
+
return await work();
|
|
7
|
+
}
|
|
8
|
+
finally {
|
|
9
|
+
await session.rows("SELECT pg_catalog.set_config('statement_timeout', $1, false)", [
|
|
10
|
+
before?.timeout ?? '0',
|
|
11
|
+
]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -38,6 +38,11 @@ function Status({ entry, outcome }) {
|
|
|
38
38
|
return _jsx(Text, { color: "red", children: "\u2717" });
|
|
39
39
|
return _jsx(Text, { dimColor: true, children: "untested" });
|
|
40
40
|
}
|
|
41
|
+
function WebNote({ url }) {
|
|
42
|
+
if (!url)
|
|
43
|
+
return null;
|
|
44
|
+
return (_jsx(Box, { marginTop: 1, paddingX: 1, children: _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["Web UI is running on ", _jsx(Text, { color: "cyan", children: url })] }) }));
|
|
45
|
+
}
|
|
41
46
|
function Banner({ services }) {
|
|
42
47
|
const error = services.configError;
|
|
43
48
|
if (!error)
|
|
@@ -121,8 +126,6 @@ export default function Environments({ version, navigate, params, services, isIn
|
|
|
121
126
|
}
|
|
122
127
|
finally {
|
|
123
128
|
running.current.delete(entry.key);
|
|
124
|
-
// Credentials issued for the test are not needed any more.
|
|
125
|
-
void services.groups?.release(entry.name);
|
|
126
129
|
}
|
|
127
130
|
setOutcome(entry.key, outcome);
|
|
128
131
|
};
|
|
@@ -219,7 +222,7 @@ export default function Environments({ version, navigate, params, services, isIn
|
|
|
219
222
|
const below = entries.length - first - shown.length;
|
|
220
223
|
const current = entries[selected];
|
|
221
224
|
const currentOutcome = current ? outcomes.get(current.key) : undefined;
|
|
222
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingTop: 1, flexGrow: 1, children: [_jsx(Header, { version: version, title: "Environments", width: width }), _jsx(Banner, { services: services }), _jsx(Box, { flexDirection: "column", marginTop: 1, paddingX: 1, children: 'error' in loaded ? (_jsxs(Text, { color: "red", children: ["\u2717 Could not read connections: ", loaded.error] })) : entries.length === 0 ? (_jsxs(Text, { children: ["No environments yet. Press ", _jsx(Text, { color: "cyan", children: "n" }), " to create one."] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { width: rowWidth, children: [_jsx(Box, { width: 2, flexShrink: 0 }), _jsx(Box, { width: nameWidth, marginRight: 2, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "NAME" }) }), showTier ? (_jsx(Box, { width: tierWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "TIER" }) })) : null, _jsx(Box, { width: protectedWidth, flexShrink: 0 }), showSource ? (_jsx(Box, { width: sourceWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "SOURCE" }) })) : null, _jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, minWidth: 0, children: _jsx(Text, { bold: true, dimColor: true, wrap: "truncate-end", children: "ADDRESS" }) }), showAccess ? (_jsx(Box, { width: accessWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "ACCESS" }) })) : null, showTls ? (_jsx(Box, { width: tlsWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "TLS" }) })) : null, _jsx(Box, { width: statusWidth, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { bold: true, dimColor: true, children: "CONNECTION" }) })] }), _jsx(Text, { dimColor: true, children: above > 0 ? ` ↑ ${above} more` : ' ' }), shown.map((entry, index) => {
|
|
225
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingTop: 1, flexGrow: 1, children: [_jsx(Header, { version: version, title: "Environments", width: width }), _jsx(Banner, { services: services }), _jsx(WebNote, { url: services.webUrl }), _jsx(Box, { flexDirection: "column", marginTop: 1, paddingX: 1, children: 'error' in loaded ? (_jsxs(Text, { color: "red", children: ["\u2717 Could not read connections: ", loaded.error] })) : entries.length === 0 ? (_jsxs(Text, { children: ["No environments yet. Press ", _jsx(Text, { color: "cyan", children: "n" }), " to create one."] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { width: rowWidth, children: [_jsx(Box, { width: 2, flexShrink: 0 }), _jsx(Box, { width: nameWidth, marginRight: 2, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "NAME" }) }), showTier ? (_jsx(Box, { width: tierWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "TIER" }) })) : null, _jsx(Box, { width: protectedWidth, flexShrink: 0 }), showSource ? (_jsx(Box, { width: sourceWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "SOURCE" }) })) : null, _jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, minWidth: 0, children: _jsx(Text, { bold: true, dimColor: true, wrap: "truncate-end", children: "ADDRESS" }) }), showAccess ? (_jsx(Box, { width: accessWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "ACCESS" }) })) : null, showTls ? (_jsx(Box, { width: tlsWidth, flexShrink: 0, children: _jsx(Text, { bold: true, dimColor: true, children: "TLS" }) })) : null, _jsx(Box, { width: statusWidth, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Text, { bold: true, dimColor: true, children: "CONNECTION" }) })] }), _jsx(Text, { dimColor: true, children: above > 0 ? ` ↑ ${above} more` : ' ' }), shown.map((entry, index) => {
|
|
223
226
|
const isSelected = first + index === selected;
|
|
224
227
|
return (_jsxs(Box, { width: rowWidth, children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: "cyan", children: isSelected ? '›' : ' ' }) }), _jsx(Box, { width: nameWidth, marginRight: 2, flexShrink: 0, children: _jsx(Text, { bold: isSelected, color: locked.has(entry.key) ? 'red' : isSelected ? 'cyan' : undefined, wrap: "truncate-end", children: entry.name }) }), showTier ? (_jsx(Box, { width: tierWidth, flexShrink: 0, children: _jsx(Text, { color: tierColor[entry.tier], children: entry.tier }) })) : null, _jsx(Box, { width: protectedWidth, flexShrink: 0, children: entry.protected ? _jsx(Text, { color: "red", children: shield }) : null }), showSource ? (_jsx(Box, { width: sourceWidth, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: entry.source }) })) : null, _jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, minWidth: 0, children: _jsx(Text, { dimColor: !isSelected, wrap: "truncate-end", children: addressOf(entry) }) }), locked.has(entry.key) ? (_jsx(Box, { flexShrink: 0, marginRight: 2, children: _jsx(Text, { color: "red", children: "credentials can't be retrieved" }) })) : null, showAccess ? (_jsx(Box, { width: accessWidth, flexShrink: 0, children: entry.readOnly ? (_jsx(Text, { color: "yellow", children: "read only" })) : (_jsx(Text, { dimColor: true, children: "read write" })) })) : null, showTls ? (_jsx(Box, { width: tlsWidth, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: entry.tlsMode }) })) : null, _jsx(Box, { width: statusWidth, flexShrink: 0, justifyContent: "flex-end", children: _jsx(Status, { entry: entry, outcome: outcomes.get(entry.key) }) })] }, entry.key));
|
|
225
228
|
}), _jsx(Text, { dimColor: true, children: below > 0 ? ` ↓ ${below} more` : ' ' })] })) }), _jsx(Box, { marginTop: 1, paddingX: 1, width: width, children: deleting ? (_jsxs(Text, { color: "red", wrap: "truncate-end", children: ["Delete ", deleting.name, "? y to delete \u00B7 any other key cancels"] })) : note ? (_jsx(Text, { color: "yellow", wrap: "truncate-end", children: note })) : currentOutcome?.state === 'running' ? (_jsxs(Text, { dimColor: true, children: ["Testing ", current?.name, "\u2026"] })) : currentOutcome ? (_jsxs(Text, { color: currentOutcome.state === 'ok' ? 'green' : 'red', wrap: "truncate-end", children: [currentOutcome.state === 'ok' ? '✓' : '✗', " ", currentOutcome.message] })) : (_jsx(Text, { children: " " })) }), _jsx(Box, { flexGrow: 1 }), _jsx(Box, { marginTop: 1, paddingX: 1, width: width, flexWrap: "wrap", children: hints.map((hint, index) => (_jsxs(Text, { color: "gray", wrap: "truncate-end", children: [hint, index < hints.length - 1 ? ' · ' : ''] }, hint))) })] }));
|
package/dist/screens/explorer.js
CHANGED
|
@@ -68,9 +68,6 @@ export default function Explorer({ version, navigate, params, services, isInputA
|
|
|
68
68
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
69
69
|
generation.current++;
|
|
70
70
|
void session.current?.close();
|
|
71
|
-
// Credentials issued for this visit end with it.
|
|
72
|
-
if (entry)
|
|
73
|
-
void services.groups?.release(entry.name);
|
|
74
71
|
};
|
|
75
72
|
// Connects once, when the screen opens.
|
|
76
73
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
package/dist/screens/splash.js
CHANGED
|
@@ -69,8 +69,10 @@ export default function Splash({ version, navigate, services, isInputActive, })
|
|
|
69
69
|
const stepsWidth = Math.max(20, Math.min(columns - 4, maxStepsWidth));
|
|
70
70
|
useInput((input) => {
|
|
71
71
|
// `q` is the global quit key, handled by <App>.
|
|
72
|
-
if (input
|
|
72
|
+
if (input === 'o' && services.webUrl)
|
|
73
|
+
services.openUrl?.(services.webUrl);
|
|
74
|
+
else if (input !== 'q' && finished)
|
|
73
75
|
navigate('environments');
|
|
74
76
|
}, { isActive: isInputActive });
|
|
75
|
-
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: Math.max(1, rows - 1), paddingY: 1, children: [_jsx(Box, { "aria-label": "psql-explorer", marginBottom: columns >= tinyMinColumns ? 0 : 1, children: _jsx(Gradient, { name: "cristal", children: _jsx(Title, { columns: columns }) }) }), _jsx(Gradient, { name: "cristal", children: _jsx(Text, { children: rule(columns) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: tagline }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "by " }), _jsx(Gradient, { name: "vice", children: _jsx(Text, { bold: true, children: company }) })] }), columns >= highlightsMinColumns ? (_jsx(Box, { marginTop: 1, children: highlights.map((item, index) => (_jsxs(Text, { color: "gray", children: [index > 0 ? ' ' : '', _jsx(Text, { color: "cyan", children: "\u25C6" }), " ", item] }, item))) })) : null, _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["v", version] }) }), steps.length > 0 ? (_jsx(Box, { flexDirection: "column", marginTop: 1, width: stepsWidth, children: steps.map((step) => (_jsx(StepLine, { step: step, now: now }, step.id))) })) : null, _jsx(Box, { marginTop: 1, children: !finished ? (_jsx(Text, { color: "gray", children: "Starting\u2026 \u00B7 q to quit" })) : columns >= tinyMinColumns ? (_jsx(Text, { color: "gray", children: "Press any key to continue \u00B7 q to quit" })) : (_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [_jsx(Text, { color: "gray", children: "Press any key to continue" }), _jsx(Text, { color: "gray", children: "q to quit" })] })) })] }));
|
|
77
|
+
return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: Math.max(1, rows - 1), paddingY: 1, children: [_jsx(Box, { "aria-label": "psql-explorer", marginBottom: columns >= tinyMinColumns ? 0 : 1, children: _jsx(Gradient, { name: "cristal", children: _jsx(Title, { columns: columns }) }) }), _jsx(Gradient, { name: "cristal", children: _jsx(Text, { children: rule(columns) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: tagline }) }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "by " }), _jsx(Gradient, { name: "vice", children: _jsx(Text, { bold: true, children: company }) })] }), columns >= highlightsMinColumns ? (_jsx(Box, { marginTop: 1, children: highlights.map((item, index) => (_jsxs(Text, { color: "gray", children: [index > 0 ? ' ' : '', _jsx(Text, { color: "cyan", children: "\u25C6" }), " ", item] }, item))) })) : null, _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["v", version] }) }), services.webUrl ? (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Web UI " }), _jsx(Text, { color: "cyan", children: services.webUrl }), _jsx(Text, { dimColor: true, children: " \u00B7 press " }), _jsx(Text, { color: "cyan", children: "o" }), _jsx(Text, { dimColor: true, children: " to open" })] })) : null, steps.length > 0 ? (_jsx(Box, { flexDirection: "column", marginTop: 1, width: stepsWidth, children: steps.map((step) => (_jsx(StepLine, { step: step, now: now }, step.id))) })) : null, _jsx(Box, { marginTop: 1, children: !finished ? (_jsx(Text, { color: "gray", children: "Starting\u2026 \u00B7 q to quit" })) : columns >= tinyMinColumns ? (_jsx(Text, { color: "gray", children: "Press any key to continue \u00B7 q to quit" })) : (_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [_jsx(Text, { color: "gray", children: "Press any key to continue" }), _jsx(Text, { color: "gray", children: "q to quit" })] })) })] }));
|
|
76
78
|
}
|