event-storage-ui 1.0.0-alpha.0 → 1.0.0-alpha.2
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 +20 -6
- package/app/components/json.jsx +1 -1
- package/app/consumers.server.js +6 -1
- package/app/root.jsx +26 -8
- package/app/routes/_index.jsx +16 -20
- package/app/routes/{write-events.jsx → commit-events.jsx} +7 -7
- package/app/routes/consumers.$consumerIdentifier.jsx +34 -17
- package/app/routes/consumers._index.jsx +55 -299
- package/app/routes/consumers.create.jsx +320 -0
- package/app/routes/query.jsx +785 -0
- package/app/routes/streams.$streamName.$from.$direction.$amount.jsx +46 -50
- package/app/routes/streams.$streamName.jsx +44 -48
- package/app/routes/streams._index.jsx +15 -19
- package/eventstore.js +33 -30
- package/package.json +34 -10
- package/public/assets/css/material-overrides.css +135 -1
- package/public/logo_color.svg +13 -0
- package/public/logo_white.svg +13 -0
- package/public/assets/js/core/bootstrap-material-design.min.js +0 -1
- package/public/assets/js/core/jquery.min.js +0 -2
- package/public/assets/js/core/popper.min.js +0 -834
- package/public/assets/js/material-dashboard.js +0 -408
- package/public/assets/js/material-dashboard.js.map +0 -1
- package/public/assets/js/material-dashboard.min.js +0 -2
- package/public/assets/js/plugins/bootstrap-notify.js +0 -404
- package/public/assets/js/plugins/chartist.min.js +0 -9
- package/public/assets/js/plugins/perfect-scrollbar.1.5.0.min.js +0 -20
package/README.md
CHANGED
|
@@ -4,21 +4,29 @@ This is an admin dashboard for inspecting a running [node-event-storage](https:/
|
|
|
4
4
|
|
|
5
5
|
### Dashboard
|
|
6
6
|
|
|
7
|
-

|
|
7
|
+

|
|
8
8
|
|
|
9
9
|
### Event Stream
|
|
10
10
|
|
|
11
|
-

|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
### Event commit
|
|
14
|
+
|
|
15
|
+

|
|
12
16
|
|
|
13
17
|
### Consumers (create and list)
|
|
14
18
|
|
|
15
|
-

|
|
19
|
+

|
|
16
20
|
|
|
17
|
-
###
|
|
21
|
+
### Consumer Browser
|
|
22
|
+
|
|
23
|
+

|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
### Consumer Detail
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
### Event commit/write
|
|
22
30
|
|
|
23
31
|
```
|
|
24
32
|
git clone https://github.com/albe/node-event-storage-ui.git
|
|
@@ -51,3 +59,9 @@ Configuration is loaded at server startup, so restart the app after changing `ev
|
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
```
|
|
62
|
+
|
|
63
|
+
## Cypress test suites
|
|
64
|
+
|
|
65
|
+
- Functional tests (used in PR CI): `npm run cypress:functional`
|
|
66
|
+
- Screenshot tests (README images only, includes automatic sync): `npm run cypress:screenshots:readme`
|
|
67
|
+
- Manual sync only (optional): `npm run screenshots:sync`
|
package/app/components/json.jsx
CHANGED
|
@@ -5,7 +5,7 @@ import 'react18-json-view/src/dark.css';
|
|
|
5
5
|
const JsonView = JsonViewModule.default ?? JsonViewModule;
|
|
6
6
|
|
|
7
7
|
export default function Json({ data, collapsed = true, style = undefined, className = '' }) {
|
|
8
|
-
const normalizedCollapsed = collapsed ===
|
|
8
|
+
const normalizedCollapsed = collapsed === true ? true : collapsed === false ? false : collapsed;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
11
|
<JsonView
|
package/app/consumers.server.js
CHANGED
|
@@ -116,6 +116,11 @@ function replayConsumer({ stream, consumerLogic, initialState }) {
|
|
|
116
116
|
return state;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
function buildConsumerRouteIdentifier(streamName, consumerName) {
|
|
120
|
+
const indexName = streamName === '_all' ? '_all' : `stream-${streamName}`;
|
|
121
|
+
return `${indexName}.${consumerName}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
119
124
|
export async function previewConsumerState(
|
|
120
125
|
{ streamNames: streamNamesInput, consumerLogic, initialState = {} },
|
|
121
126
|
storeNameOverride
|
|
@@ -188,7 +193,7 @@ export async function createConsumer(
|
|
|
188
193
|
});
|
|
189
194
|
|
|
190
195
|
return {
|
|
191
|
-
consumerIdentifier:
|
|
196
|
+
consumerIdentifier: buildConsumerRouteIdentifier(normalizedStreamName, normalizedConsumerName),
|
|
192
197
|
streamName: normalizedStreamName,
|
|
193
198
|
consumerName: normalizedConsumerName
|
|
194
199
|
};
|
package/app/root.jsx
CHANGED
|
@@ -72,15 +72,18 @@ export default function App() {
|
|
|
72
72
|
<body>
|
|
73
73
|
<div className="shell">
|
|
74
74
|
<aside className="d-sidebar">
|
|
75
|
-
<div className="brand
|
|
75
|
+
<div className="brand">
|
|
76
76
|
<div className="brand-text">
|
|
77
77
|
<div className="brand-name">event-storage-ui</div>
|
|
78
78
|
<div className="brand-tag">node-event-storage</div>
|
|
79
79
|
</div>
|
|
80
|
+
<div className="brand-logo brand-logo-transparent">
|
|
81
|
+
<img alt="" src="/icon-hero-variant-3.svg" />
|
|
82
|
+
</div>
|
|
80
83
|
</div>
|
|
81
84
|
|
|
82
85
|
<nav className="nav-section">
|
|
83
|
-
|
|
86
|
+
<div className="nav-label">Workspace</div>
|
|
84
87
|
<NavLink
|
|
85
88
|
to={`/${storeSearch}`}
|
|
86
89
|
className={({ isActive }) => `nav-link${isActive ? ' is-active' : ''}`}
|
|
@@ -97,28 +100,43 @@ export default function App() {
|
|
|
97
100
|
<span>Stream Browser</span>
|
|
98
101
|
</NavLink>
|
|
99
102
|
<NavLink
|
|
100
|
-
to={`/
|
|
103
|
+
to={`/query${storeSearch}`}
|
|
101
104
|
className={({ isActive }) => `nav-link${isActive ? ' is-active' : ''}`}
|
|
102
105
|
>
|
|
103
|
-
<i className="material-icons">
|
|
104
|
-
<span>
|
|
106
|
+
<i className="material-icons">search</i>
|
|
107
|
+
<span>Query</span>
|
|
105
108
|
</NavLink>
|
|
106
109
|
{!storeLocked && (
|
|
107
110
|
<NavLink
|
|
108
|
-
to={`/
|
|
111
|
+
to={`/commit-events${storeSearch}`}
|
|
109
112
|
className={({ isActive }) => `nav-link${isActive ? ' is-active' : ''}`}
|
|
110
113
|
>
|
|
111
114
|
<i className="material-icons">edit</i>
|
|
112
|
-
<span>
|
|
115
|
+
<span>Commit Events</span>
|
|
113
116
|
</NavLink>
|
|
114
117
|
)}
|
|
118
|
+
<NavLink
|
|
119
|
+
to={`/consumers${storeSearch}`}
|
|
120
|
+
className={({ isActive }) => `nav-link${isActive ? ' is-active' : ''}`}
|
|
121
|
+
end
|
|
122
|
+
>
|
|
123
|
+
<i className="material-icons">manage_search</i>
|
|
124
|
+
<span>Consumer Browser</span>
|
|
125
|
+
</NavLink>
|
|
126
|
+
<NavLink
|
|
127
|
+
to={`/consumers/create${storeSearch}`}
|
|
128
|
+
className={({ isActive }) => `nav-link${isActive ? ' is-active' : ''}`}
|
|
129
|
+
>
|
|
130
|
+
<i className="material-icons">playlist_add</i>
|
|
131
|
+
<span>Create Consumer</span>
|
|
132
|
+
</NavLink>
|
|
115
133
|
</nav>
|
|
116
134
|
</aside>
|
|
117
135
|
|
|
118
136
|
<div className="main">
|
|
119
137
|
<header className="d-topbar">
|
|
120
138
|
<div className="crumbs">
|
|
121
|
-
<img src="/logo_white.
|
|
139
|
+
<img src="/logo_white.svg" className="topbar-logo" alt="* event-storage" />
|
|
122
140
|
</div>
|
|
123
141
|
<div className="topbar-actions">
|
|
124
142
|
<ul className="nav-right">
|
package/app/routes/_index.jsx
CHANGED
|
@@ -14,28 +14,24 @@ export async function loader({ request }) {
|
|
|
14
14
|
const storeNameOverride = url.searchParams.get('store') || undefined;
|
|
15
15
|
const { eventstore, storageStats } = await getEventStore({ readOnly: true }, storeNameOverride);
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
resolve(scannedConsumers);
|
|
25
|
-
});
|
|
17
|
+
const consumers = await new Promise((resolve, reject) => {
|
|
18
|
+
eventstore.scanConsumers((err, scannedConsumers) => {
|
|
19
|
+
if (err) {
|
|
20
|
+
reject(err);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
resolve(scannedConsumers);
|
|
26
24
|
});
|
|
25
|
+
});
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} finally {
|
|
37
|
-
eventstore.close();
|
|
38
|
-
}
|
|
27
|
+
return {
|
|
28
|
+
storeName: eventstore.storeName,
|
|
29
|
+
storageDirectory: eventstore.storageDirectory,
|
|
30
|
+
streamsCount: Object.keys(eventstore.streams).length,
|
|
31
|
+
eventsCount: eventstore.length,
|
|
32
|
+
consumersCount: consumers.length,
|
|
33
|
+
stats: storageStats ?? {}
|
|
34
|
+
};
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
const COLORS = { primary: '#00bcd4', success: '#32d48e', warning: '#f59e0b' };
|
|
@@ -5,7 +5,7 @@ import { Form } from 'react-router';
|
|
|
5
5
|
import { getStoreLockStatus, commitToEventStore } from '../../eventstore';
|
|
6
6
|
import Json from '../components/json';
|
|
7
7
|
|
|
8
|
-
export const meta = () => [{ title: 'event-storage:
|
|
8
|
+
export const meta = () => [{ title: 'event-storage: Commit Events' }];
|
|
9
9
|
|
|
10
10
|
export async function loader({ request }) {
|
|
11
11
|
const url = new URL(request.url);
|
|
@@ -97,10 +97,10 @@ export default function WriteEvents() {
|
|
|
97
97
|
<div className="page-stack">
|
|
98
98
|
<section className="page-hero hero">
|
|
99
99
|
<div className="hero-text">
|
|
100
|
-
<div className="page-eyebrow eyebrow">
|
|
101
|
-
<h2 className="page-title hero-title">
|
|
100
|
+
<div className="page-eyebrow eyebrow">Committer</div>
|
|
101
|
+
<h2 className="page-title hero-title">Commit Events ({storeName})</h2>
|
|
102
102
|
<p className="page-subtitle hero-sub">
|
|
103
|
-
|
|
103
|
+
Committing is disabled while this store is locked by an external process.
|
|
104
104
|
</p>
|
|
105
105
|
</div>
|
|
106
106
|
<div className="page-actions hero-actions">
|
|
@@ -116,7 +116,7 @@ export default function WriteEvents() {
|
|
|
116
116
|
<div className="status-banner" role="alert">
|
|
117
117
|
<span className="status-banner__icon">❗</span>
|
|
118
118
|
<div className="status-banner__text">
|
|
119
|
-
This Eventstore is currently locked by an external process.
|
|
119
|
+
This Eventstore is currently locked by an external process. Committing is not possible while the
|
|
120
120
|
store is locked.
|
|
121
121
|
</div>
|
|
122
122
|
</div>
|
|
@@ -130,8 +130,8 @@ export default function WriteEvents() {
|
|
|
130
130
|
<div className="page-stack">
|
|
131
131
|
<section className="page-hero hero">
|
|
132
132
|
<div className="hero-text">
|
|
133
|
-
<div className="page-eyebrow eyebrow">
|
|
134
|
-
<h2 className="page-title hero-title">
|
|
133
|
+
<div className="page-eyebrow eyebrow">Committer</div>
|
|
134
|
+
<h2 className="page-title hero-title">Commit Events ({storeName})</h2>
|
|
135
135
|
<p className="page-subtitle hero-sub">
|
|
136
136
|
Compose new event payloads, preview parsed JSON, and optionally attach metadata before committing.
|
|
137
137
|
</p>
|
|
@@ -6,29 +6,46 @@ export const meta = ({ params }) => [
|
|
|
6
6
|
{ title: `event-storage: Consumer ${params.consumerIdentifier}` }
|
|
7
7
|
];
|
|
8
8
|
|
|
9
|
+
function parseStreamFromIndexName(indexName) {
|
|
10
|
+
if (indexName === '_all') {
|
|
11
|
+
return '_all';
|
|
12
|
+
}
|
|
13
|
+
if (indexName.startsWith('stream-')) {
|
|
14
|
+
return indexName.slice(7);
|
|
15
|
+
}
|
|
16
|
+
return indexName;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseConsumerRouteIdentifier(consumerIdentifier) {
|
|
20
|
+
const splitIndex = consumerIdentifier.lastIndexOf('.');
|
|
21
|
+
if (splitIndex < 0) {
|
|
22
|
+
return { indexName: consumerIdentifier, consumerName: consumerIdentifier };
|
|
23
|
+
}
|
|
24
|
+
const indexName = consumerIdentifier.slice(0, splitIndex);
|
|
25
|
+
const consumerName = consumerIdentifier.slice(splitIndex + 1);
|
|
26
|
+
return { indexName, consumerName };
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
export async function loader({ params, request }) {
|
|
10
30
|
const consumerIdentifier = params.consumerIdentifier;
|
|
11
31
|
const url = new URL(request.url);
|
|
12
32
|
const storeNameOverride = url.searchParams.get('store') || undefined;
|
|
13
33
|
const { eventstore } = await getEventStore({ readOnly: true }, storeNameOverride);
|
|
14
34
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
const parsed = parseConsumerRouteIdentifier(consumerIdentifier);
|
|
36
|
+
const streamName = parseStreamFromIndexName(parsed.indexName);
|
|
37
|
+
const consumer = eventstore.getConsumer(streamName, parsed.consumerName);
|
|
38
|
+
const consumerPosition = consumer.position;
|
|
39
|
+
const consumerState = consumer.state;
|
|
40
|
+
const indexLength = consumer.index.length;
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} finally {
|
|
30
|
-
eventstore.close();
|
|
31
|
-
}
|
|
42
|
+
return {
|
|
43
|
+
indexName: streamName,
|
|
44
|
+
indexLength,
|
|
45
|
+
consumerName: parsed.consumerName,
|
|
46
|
+
consumerPosition,
|
|
47
|
+
consumerState
|
|
48
|
+
};
|
|
32
49
|
}
|
|
33
50
|
|
|
34
51
|
export default function Consumer() {
|
|
@@ -88,7 +105,7 @@ export default function Consumer() {
|
|
|
88
105
|
<div className="meta-list__item">
|
|
89
106
|
<div className="meta-list__label">State</div>
|
|
90
107
|
<div className="json-surface json-surface--short">
|
|
91
|
-
<Json data={consumerState} />
|
|
108
|
+
<Json data={consumerState} collapsed={3} />
|
|
92
109
|
</div>
|
|
93
110
|
</div>
|
|
94
111
|
</div>
|