@yarkivaev/simulation-web 1.0.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.
@@ -0,0 +1 @@
1
+ *{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background:#f4f6f9;color:#333}.app{display:flex;min-height:100vh}.sidebar{width:220px;background:#1e293b;color:#fff;padding:1.5rem 1rem;flex-shrink:0}.logo{font-size:1.2rem;margin-bottom:2rem;padding-bottom:1rem;border-bottom:1px solid #334155}.nav-list{list-style:none}.nav-list li{margin-bottom:.5rem}.nav-list a{color:#94a3b8;text-decoration:none;display:block;padding:.5rem .75rem;border-radius:6px;transition:background .2s,color .2s}.nav-list a:hover,.nav-list a.router-link-active{background:#334155;color:#fff}.content{flex:1;padding:2rem;overflow-y:auto}.editor-container[data-v-ac617ac0]{height:300px;border:1px solid #cbd5e1;border-radius:6px;overflow:hidden}.simulation-control[data-v-0b0ece24]{display:inline-flex;align-items:center;gap:.75rem}.status-badge[data-v-0b0ece24]{padding:.2rem .6rem;border-radius:12px;font-size:.8rem;font-weight:600}.status-badge.RUNNING[data-v-0b0ece24]{background:#dcfce7;color:#166534}.status-badge.STOPPED[data-v-0b0ece24]{background:#f1f5f9;color:#64748b}.btn-primary[data-v-0b0ece24]{background:#3b82f6;color:#fff;border:none;padding:.25rem .75rem;border-radius:6px;cursor:pointer;font-size:.85rem}.btn-danger[data-v-0b0ece24]{background:#ef4444;color:#fff;border:none;padding:.25rem .75rem;border-radius:6px;cursor:pointer;font-size:.85rem}.page-header[data-v-028dcd41]{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem}.data-table[data-v-028dcd41]{width:100%;border-collapse:collapse;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px #0000001a}.data-table th[data-v-028dcd41],.data-table td[data-v-028dcd41]{padding:.75rem 1rem;text-align:left;border-bottom:1px solid #e2e8f0}.data-table th[data-v-028dcd41]{background:#f8fafc;font-weight:600;color:#475569}.dialog-overlay[data-v-028dcd41]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:100}.dialog[data-v-028dcd41]{background:#fff;border-radius:12px;padding:2rem;min-width:400px;box-shadow:0 8px 30px #0000001f}.dialog.wide[data-v-028dcd41]{min-width:700px;max-width:900px}.dialog h2[data-v-028dcd41]{margin-bottom:1.5rem}.dialog label[data-v-028dcd41]{display:block;margin-bottom:1rem;color:#475569}.dialog input[data-v-028dcd41]{display:block;width:100%;margin-top:.25rem;padding:.5rem;border:1px solid #cbd5e1;border-radius:6px}.dialog-actions[data-v-028dcd41]{display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.5rem}.validation[data-v-028dcd41]{padding:.5rem;border-radius:4px;margin-top:.5rem;background:#f0fdf4;color:#15803d}.validation.error[data-v-028dcd41]{background:#fef2f2;color:#dc2626}.btn-primary[data-v-028dcd41]{background:#3b82f6;color:#fff;border:none;padding:.5rem 1.25rem;border-radius:6px;cursor:pointer}.btn-secondary[data-v-028dcd41]{background:#e2e8f0;color:#475569;border:none;padding:.5rem 1.25rem;border-radius:6px;cursor:pointer}.btn-danger[data-v-028dcd41]{background:#ef4444;color:#fff;border:none;padding:.25rem .75rem;border-radius:4px;cursor:pointer;font-size:.85rem;margin-left:.5rem}
@@ -0,0 +1,239 @@
1
+ import { defineComponent as _, resolveComponent as D, openBlock as i, createElementBlock as r, createElementVNode as e, toDisplayString as v, Fragment as b, renderList as g, createVNode as k, withCtx as P, createTextVNode as w, renderSlot as L, ref as p, onMounted as C, watch as O, onBeforeUnmount as x, normalizeClass as V, reactive as B, withModifiers as A, withDirectives as I, vModelText as M, createCommentVNode as $ } from "vue";
2
+ import * as U from "monaco-editor";
3
+ const j = { class: "app" }, z = { class: "sidebar" }, F = { class: "logo" }, G = { class: "nav-list" }, H = { class: "content" }, st = /* @__PURE__ */ _({
4
+ __name: "SimulationLayout",
5
+ props: {
6
+ title: {},
7
+ nav: {}
8
+ },
9
+ setup(t) {
10
+ return (l, a) => {
11
+ const u = D("router-link");
12
+ return i(), r("div", j, [
13
+ e("nav", z, [
14
+ e("h2", F, v(t.title), 1),
15
+ e("ul", G, [
16
+ (i(!0), r(b, null, g(t.nav, (s) => (i(), r("li", {
17
+ key: s.to
18
+ }, [
19
+ k(u, {
20
+ to: s.to
21
+ }, {
22
+ default: P(() => [
23
+ w(v(s.label), 1)
24
+ ]),
25
+ _: 2
26
+ }, 1032, ["to"])
27
+ ]))), 128))
28
+ ])
29
+ ]),
30
+ e("main", H, [
31
+ L(l.$slots, "default")
32
+ ])
33
+ ]);
34
+ };
35
+ }
36
+ }), J = /* @__PURE__ */ _({
37
+ __name: "DslEditor",
38
+ props: {
39
+ modelValue: {}
40
+ },
41
+ emits: ["update:modelValue"],
42
+ setup(t, { emit: l }) {
43
+ const a = t, u = l, s = p();
44
+ let n = null;
45
+ return C(() => {
46
+ s.value && (n = U.editor.create(s.value, {
47
+ value: a.modelValue,
48
+ language: "groovy",
49
+ theme: "vs",
50
+ minimap: { enabled: !1 },
51
+ fontSize: 14,
52
+ lineNumbers: "on",
53
+ scrollBeyondLastLine: !1,
54
+ automaticLayout: !0,
55
+ tabSize: 2
56
+ }), n.onDidChangeModelContent(() => {
57
+ u("update:modelValue", n.getValue());
58
+ }));
59
+ }), O(() => a.modelValue, (f) => {
60
+ n && n.getValue() !== f && n.setValue(f);
61
+ }), x(() => {
62
+ n == null || n.dispose();
63
+ }), (f, y) => (i(), r("div", {
64
+ ref_key: "container",
65
+ ref: s,
66
+ class: "editor-container"
67
+ }, null, 512));
68
+ }
69
+ }), h = (t, l) => {
70
+ const a = t.__vccOpts || t;
71
+ for (const [u, s] of l)
72
+ a[u] = s;
73
+ return a;
74
+ }, R = /* @__PURE__ */ h(J, [["__scopeId", "data-v-ac617ac0"]]), q = { class: "simulation-control" }, K = /* @__PURE__ */ _({
75
+ __name: "SimulationControl",
76
+ props: {
77
+ status: {}
78
+ },
79
+ emits: ["start", "stop"],
80
+ setup(t) {
81
+ return (l, a) => (i(), r("span", q, [
82
+ e("span", {
83
+ class: V(["status-badge", t.status])
84
+ }, v(t.status), 3),
85
+ t.status !== "RUNNING" ? (i(), r("button", {
86
+ key: 0,
87
+ class: "btn-primary btn-sm",
88
+ onClick: a[0] || (a[0] = (u) => l.$emit("start"))
89
+ }, "Start")) : (i(), r("button", {
90
+ key: 1,
91
+ class: "btn-danger btn-sm",
92
+ onClick: a[1] || (a[1] = (u) => l.$emit("stop"))
93
+ }, "Stop"))
94
+ ]));
95
+ }
96
+ }), lt = /* @__PURE__ */ h(K, [["__scopeId", "data-v-0b0ece24"]]), d = "/api";
97
+ async function S(t) {
98
+ if (!t.ok)
99
+ throw new Error(`HTTP ${t.status}: ${t.statusText}`);
100
+ return t.json();
101
+ }
102
+ async function Q() {
103
+ return S(await fetch(`${d}/scenarios`));
104
+ }
105
+ async function W(t) {
106
+ return S(await fetch(`${d}/scenarios`, {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/json" },
109
+ body: JSON.stringify(t)
110
+ }));
111
+ }
112
+ async function X(t) {
113
+ await fetch(`${d}/scenarios/${t}`, { method: "DELETE" });
114
+ }
115
+ async function it(t) {
116
+ return S(await fetch(`${d}/scenarios/${t}/validate`, { method: "POST" }));
117
+ }
118
+ async function rt() {
119
+ return S(await fetch(`${d}/simulations`));
120
+ }
121
+ async function ut(t) {
122
+ await fetch(`${d}/simulations/${t}/start`, { method: "POST" });
123
+ }
124
+ async function ct(t) {
125
+ await fetch(`${d}/simulations/${t}/stop`, { method: "POST" });
126
+ }
127
+ async function dt() {
128
+ await fetch(`${d}/simulations/start-all`, { method: "POST" });
129
+ }
130
+ async function mt() {
131
+ await fetch(`${d}/simulations/stop-all`, { method: "POST" });
132
+ }
133
+ const Y = { class: "page-header" }, Z = { class: "data-table" }, tt = ["onClick"], et = ["onClick"], nt = { class: "dialog wide" }, at = /* @__PURE__ */ _({
134
+ __name: "ScenariosPage",
135
+ setup(t) {
136
+ const l = p([]), a = p(!1), u = p(!1), s = p(null), n = B({ name: "", dsl: "" });
137
+ C(async () => {
138
+ l.value = await Q();
139
+ });
140
+ function f(m) {
141
+ n.name = m.name, n.dsl = m.dsl, u.value = !0, a.value = !0;
142
+ }
143
+ function y() {
144
+ a.value = !1, u.value = !1, s.value = null, n.name = "", n.dsl = "";
145
+ }
146
+ async function N() {
147
+ const m = await W({ name: n.name, dsl: n.dsl });
148
+ l.value.push(m), y();
149
+ }
150
+ async function T(m) {
151
+ await X(m), l.value = l.value.filter((o) => o.id !== m);
152
+ }
153
+ return (m, o) => (i(), r("div", null, [
154
+ e("div", Y, [
155
+ o[3] || (o[3] = e("h1", null, "Scenarios", -1)),
156
+ e("button", {
157
+ class: "btn-primary",
158
+ onClick: o[0] || (o[0] = (c) => a.value = !0)
159
+ }, "New Scenario")
160
+ ]),
161
+ e("table", Z, [
162
+ o[4] || (o[4] = e("thead", null, [
163
+ e("tr", null, [
164
+ e("th", null, "Name"),
165
+ e("th", null, "Actions")
166
+ ])
167
+ ], -1)),
168
+ e("tbody", null, [
169
+ (i(!0), r(b, null, g(l.value, (c) => (i(), r("tr", {
170
+ key: c.id
171
+ }, [
172
+ e("td", null, v(c.name), 1),
173
+ e("td", null, [
174
+ e("button", {
175
+ class: "btn-secondary",
176
+ onClick: (E) => f(c)
177
+ }, "Edit", 8, tt),
178
+ e("button", {
179
+ class: "btn-danger",
180
+ onClick: (E) => T(c.id)
181
+ }, "Delete", 8, et)
182
+ ])
183
+ ]))), 128))
184
+ ])
185
+ ]),
186
+ a.value ? (i(), r("div", {
187
+ key: 0,
188
+ class: "dialog-overlay",
189
+ onClick: A(y, ["self"])
190
+ }, [
191
+ e("div", nt, [
192
+ e("h2", null, v(u.value ? "Edit" : "New") + " Scenario", 1),
193
+ e("label", null, [
194
+ o[5] || (o[5] = w("Name", -1)),
195
+ I(e("input", {
196
+ "onUpdate:modelValue": o[1] || (o[1] = (c) => n.name = c)
197
+ }, null, 512), [
198
+ [M, n.name]
199
+ ])
200
+ ]),
201
+ o[6] || (o[6] = e("label", null, "DSL", -1)),
202
+ k(R, {
203
+ modelValue: n.dsl,
204
+ "onUpdate:modelValue": o[2] || (o[2] = (c) => n.dsl = c)
205
+ }, null, 8, ["modelValue"]),
206
+ s.value ? (i(), r("div", {
207
+ key: 0,
208
+ class: V(["validation", { error: !s.value.valid }])
209
+ }, v(s.value.valid ? "Valid" : s.value.error), 3)) : $("", !0),
210
+ e("div", { class: "dialog-actions" }, [
211
+ e("button", {
212
+ class: "btn-secondary",
213
+ onClick: y
214
+ }, "Cancel"),
215
+ e("button", {
216
+ class: "btn-primary",
217
+ onClick: N
218
+ }, "Save")
219
+ ])
220
+ ])
221
+ ])) : $("", !0)
222
+ ]));
223
+ }
224
+ }), vt = /* @__PURE__ */ h(at, [["__scopeId", "data-v-028dcd41"]]);
225
+ export {
226
+ R as DslEditor,
227
+ vt as ScenariosPage,
228
+ lt as SimulationControl,
229
+ st as SimulationLayout,
230
+ W as createScenario,
231
+ X as deleteScenario,
232
+ Q as fetchScenarios,
233
+ rt as fetchSimulations,
234
+ dt as startAll,
235
+ ut as startSimulation,
236
+ mt as stopAll,
237
+ ct as stopSimulation,
238
+ it as validateScenario
239
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@yarkivaev/simulation-web",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "dist/simulation-web.js",
6
+ "files": ["dist"],
7
+ "scripts": {
8
+ "build": "vue-tsc -b && vite build",
9
+ "test": "vitest run --passWithNoTests"
10
+ },
11
+ "peerDependencies": {
12
+ "vue": "^3.5.13",
13
+ "vue-router": "^4.5.0",
14
+ "monaco-editor": "^0.52.2"
15
+ },
16
+ "devDependencies": {
17
+ "@vitejs/plugin-vue": "^5.2.3",
18
+ "monaco-editor": "^0.52.2",
19
+ "typescript": "^5.7.3",
20
+ "vite": "^6.1.1",
21
+ "vitest": "^3.0.5",
22
+ "vue": "^3.5.13",
23
+ "vue-router": "^4.5.0",
24
+ "vue-tsc": "^2.2.8"
25
+ }
26
+ }