-
Events
+
System Logs
+
-
Activity Report
+
Audit Report
`);
@@ -23,5 +25,6 @@ export default AdminHOC(async function(render) {
componentLogViewer(createRender($page.querySelector(".component_logviewer")));
componentLogForm(createRender($page.querySelector(".component_logger")));
+ componentLogGraph(createRender($page.querySelector(".component_stats")));
componentAuditor(createRender($page.querySelector(".component_audit")));
});
diff --git a/public/assets/pages/adminpage/ctrl_activity_graph.css b/public/assets/pages/adminpage/ctrl_activity_graph.css
new file mode 100644
index 00000000..52f41f08
--- /dev/null
+++ b/public/assets/pages/adminpage/ctrl_activity_graph.css
@@ -0,0 +1,29 @@
+.component_stats {
+ clear: both;
+}
+.component_stats .chart {
+ display: flex;
+ align-items: flex-end;
+ height: 100px;
+}
+.component_stats .chart .bar {
+ display: flex;
+ flex: 1;
+ cursor: pointer;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ border-bottom: 1px solid var(--light);
+
+ background: var(--border);
+ border: 2px solid var(--border);
+}
+.component_stats .chart .bar[title="0"], .component_stats .chart .bar[title="1"], .component_stats .chart .bar[title="2"] {
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
+}
+.component_stats .legend {
+ display: flex;
+ justify-content: space-between;
+ color: var(--light);
+ font-size: 0.8rem;
+}
diff --git a/public/assets/pages/adminpage/ctrl_activity_graph.js b/public/assets/pages/adminpage/ctrl_activity_graph.js
new file mode 100644
index 00000000..684669e9
--- /dev/null
+++ b/public/assets/pages/adminpage/ctrl_activity_graph.js
@@ -0,0 +1,48 @@
+import { createElement } from "../../lib/skeleton/index.js";
+import rxjs, { effect } from "../../lib/rx.js";
+import { get as getLogs } from "./model_log.js";
+import { loadCSS } from "../../helpers/loader.js";
+
+export default async function(render) {
+ await loadCSS(import.meta.url, "./ctrl_activity_graph.css");
+
+ effect(getLogs().pipe(
+ rxjs.map((log) => {
+ const times = log.trim().split("\n").map((line) => new Date(line.substring(0, 19)).getTime());
+ const start = times[0];
+ const end = times[times.length - 1];
+
+ const size = 30
+ const bars = Array(size).fill(0);
+ const width = (end - start) / size;
+ for (const t of times) {
+ const idx = Math.min(size - 1, Math.max(0, Math.floor((t - start) / width)));
+ bars[idx] += 1;
+ }
+ return {
+ bars,
+ start: new Date(start).toLocaleTimeString(),
+ end: new Date(end).toLocaleTimeString(),
+ };
+ }),
+ rxjs.tap(({ bars, start, end }) => {
+ const max = Math.max(1, ...bars);
+ const $root = document.createDocumentFragment();
+ const $chart = createElement(`
`);
+ for (let i = 0; i < bars.length; i++) {
+ const $bar = createElement(`
`)
+ $bar.style.height = Math.sqrt(bars[i]) / Math.sqrt(max) * 100 + "%";
+ $chart.appendChild($bar);
+ }
+ $root.appendChild($chart);
+ $root.appendChild(createElement(`
+
+ ${start}
+ ${end}
+
+ `));
+ render($root);
+ }),
+ rxjs.catchError(() => rxjs.EMPTY),
+ ));
+}
diff --git a/public/assets/pages/adminpage/ctrl_activity_viewer.js b/public/assets/pages/adminpage/ctrl_activity_viewer.js
index 01057f35..25abe402 100644
--- a/public/assets/pages/adminpage/ctrl_activity_viewer.js
+++ b/public/assets/pages/adminpage/ctrl_activity_viewer.js
@@ -13,7 +13,7 @@ export default async function(render) {
Download
-
+
`);
const $log = qs($page, "pre");
diff --git a/public/assets/pages/adminpage/ctrl_workflow_list.js b/public/assets/pages/adminpage/ctrl_workflow_list.js
index 099a38b7..eaa993cd 100644
--- a/public/assets/pages/adminpage/ctrl_workflow_list.js
+++ b/public/assets/pages/adminpage/ctrl_workflow_list.js
@@ -24,7 +24,11 @@ export default async function(render, { workflows, triggers }) {
if (workflows.length === 0) $workflows.appendChild(createEmptyWorkflow());
effect(onClick(qs($page, "h2 > a")).pipe(
- rxjs.tap((a) => ctrlModal(createModal(), { triggers })),
+ rxjs.tap(($a) => animate($a, {
+ time: 300,
+ keyframes: [{ transform: "rotate(0)" }, { transform: `rotate(90deg)` }],
+ })),
+ rxjs.tap(() => ctrlModal(createModal(), { triggers })),
));
}
diff --git a/server/common/types.go b/server/common/types.go
index 5e4f5327..2f3c93bd 100644
--- a/server/common/types.go
+++ b/server/common/types.go
@@ -94,12 +94,12 @@ type ITriggerEvent interface {
}
type WorkflowSpecs struct {
- Name string `json:"name"`
- Title string `json:"title"`
- Subtitle string `json:"subtitle"`
- Icon string `json:"icon"`
- Specs map[string]FormElement `json:"specs"`
- Order int `json:"-"`
+ Name string `json:"name"`
+ Title string `json:"title"`
+ Subtitle string `json:"subtitle"`
+ Icon string `json:"icon"`
+ Specs Form `json:"specs"`
+ Order int `json:"-"`
}
type File struct {
diff --git a/server/workflow/actions/notify_email.go b/server/workflow/actions/notify_email.go
index 64d6ab06..f68c9854 100644
--- a/server/workflow/actions/notify_email.go
+++ b/server/workflow/actions/notify_email.go
@@ -17,15 +17,20 @@ func (this *ActionNotifyEmail) Manifest() WorkflowSpecs {
Name: "notify/email",
Title: "Notify",
Icon: `
`,
- Specs: map[string]FormElement{
- "email": {
- Type: "text",
- },
- "subject": {
- Type: "text",
- },
- "message": {
- Type: "long_text",
+ Specs: Form{
+ Elmnts: []FormElement{
+ {
+ Name: "email",
+ Type: "text",
+ },
+ {
+ Name: "subject",
+ Type: "text",
+ },
+ {
+ Name: "message",
+ Type: "long_text",
+ },
},
},
}
diff --git a/server/workflow/actions/run_api.go b/server/workflow/actions/run_api.go
index ec2caa9a..0637afda 100644
--- a/server/workflow/actions/run_api.go
+++ b/server/workflow/actions/run_api.go
@@ -22,19 +22,25 @@ func (this *RunApi) Manifest() WorkflowSpecs {
Name: "run/api",
Title: "Make API Call",
Icon: `
`,
- Specs: map[string]FormElement{
- "url": {
- Type: "text",
- },
- "method": {
- Type: "select",
- Opts: []string{"POST", "PUT", "GET", "PATCH"},
- },
- "headers": {
- Type: "long_text",
- },
- "body": {
- Type: "long_text",
+ Specs: Form{
+ Elmnts: []FormElement{
+ {
+ Name: "url",
+ Type: "text",
+ },
+ {
+ Name: "method",
+ Type: "select",
+ Opts: []string{"POST", "PUT", "GET", "PATCH"},
+ },
+ {
+ Name: "headers",
+ Type: "long_text",
+ },
+ {
+ Name: "body",
+ Type: "long_text",
+ },
},
},
}
diff --git a/server/workflow/actions/tools_debug.go b/server/workflow/actions/tools_debug.go
index 2e730b57..9ba68a8c 100644
--- a/server/workflow/actions/tools_debug.go
+++ b/server/workflow/actions/tools_debug.go
@@ -15,7 +15,7 @@ func (this *ToolsDebug) Manifest() WorkflowSpecs {
Name: "tools/debug",
Title: "Debug",
Icon: `
`,
- Specs: map[string]FormElement{},
+ Specs: Form{},
}
}
diff --git a/server/workflow/trigger/fileaction.go b/server/workflow/trigger/fileaction.go
index 51b65710..3d98fb4b 100644
--- a/server/workflow/trigger/fileaction.go
+++ b/server/workflow/trigger/fileaction.go
@@ -60,14 +60,18 @@ func (this *FileEventTrigger) Manifest() WorkflowSpecs {
Name: fileaction_name,
Title: "When Something Happen",
Icon: `
`,
- Specs: map[string]FormElement{
- "event": {
- Type: "text",
- Datalist: []string{"ls", "cat", "mkdir", "mv", "rm", "touch"},
- MultiValue: true,
- },
- "path": {
- Type: "text",
+ Specs: Form{
+ Elmnts: []FormElement{
+ {
+ Name: "event",
+ Type: "text",
+ Datalist: []string{"ls", "cat", "mkdir", "mv", "rm", "touch"},
+ MultiValue: true,
+ },
+ {
+ Name: "path",
+ Type: "text",
+ },
},
},
Order: 3,
diff --git a/server/workflow/trigger/filewatch.go b/server/workflow/trigger/filewatch.go
index f9450421..80fb40ba 100644
--- a/server/workflow/trigger/filewatch.go
+++ b/server/workflow/trigger/filewatch.go
@@ -28,12 +28,16 @@ func (this *WatchTrigger) Manifest() WorkflowSpecs {
Name: "watch",
Title: "When the Filesystem Changes",
Icon: `
`,
- Specs: map[string]FormElement{
- "token": {
- Type: "text",
- },
- "path": {
- Type: "text",
+ Specs: Form{
+ Elmnts: []FormElement{
+ {
+ Name: "token",
+ Type: "text",
+ },
+ {
+ Name: "path",
+ Type: "text",
+ },
},
},
Order: 4,
diff --git a/server/workflow/trigger/schedule.go b/server/workflow/trigger/schedule.go
index e364fb43..ae20a873 100644
--- a/server/workflow/trigger/schedule.go
+++ b/server/workflow/trigger/schedule.go
@@ -23,11 +23,14 @@ func (this *ScheduleTrigger) Manifest() WorkflowSpecs {
Title: "On a Schedule",
Subtitle: "frequency",
Icon: `
`,
- Specs: map[string]FormElement{
- "frequency": {
- Type: "select",
- Opts: []string{"per-minute", "hourly", "daily", "weekly", "monthly"},
- Value: "daily",
+ Specs: Form{
+ Elmnts: []FormElement{
+ {
+ Name: "frequency",
+ Type: "select",
+ Opts: []string{"per-minute", "hourly", "daily", "weekly", "monthly"},
+ Value: "daily",
+ },
},
},
Order: 1,
diff --git a/server/workflow/trigger/webhook.go b/server/workflow/trigger/webhook.go
index 29816fbd..e5f78e74 100644
--- a/server/workflow/trigger/webhook.go
+++ b/server/workflow/trigger/webhook.go
@@ -53,11 +53,14 @@ func (this *WebhookTrigger) Manifest() WorkflowSpecs {
Name: webhook_name,
Title: "From a WebHook",
Icon: `
`,
- Specs: map[string]FormElement{
- "url": {
- Type: "text",
- ReadOnly: true,
- Value: "/api/workflow/webhook",
+ Specs: Form{
+ Elmnts: []FormElement{
+ {
+ Name: "url",
+ Type: "text",
+ ReadOnly: true,
+ Value: "/api/workflow/webhook",
+ },
},
},
Order: 5,