diff --git a/.prettierignore b/.prettierignore
index 9e0427c45..dad0d6013 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,3 @@
/build/
node_modules/
-/src/server_manager/messages/
+/src/shadowbox/prometheus/consoles/
diff --git a/package-lock.json b/package-lock.json
index 131eb326e..bb8f54faa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,6 +5,7 @@
"packages": {
"": {
"name": "outline-server",
+ "hasInstallScript": true,
"workspaces": [
"src/*"
],
diff --git a/src/shadowbox/Taskfile.yml b/src/shadowbox/Taskfile.yml
index a7a7daf9a..31b49bb7d 100644
--- a/src/shadowbox/Taskfile.yml
+++ b/src/shadowbox/Taskfile.yml
@@ -20,6 +20,8 @@ tasks:
- cp '{{joinPath .TASKFILE_DIR "package.json"}}' '{{.TARGET_DIR}}'
# Build Node.js app
- npx webpack --config='{{joinPath .TASKFILE_DIR "webpack.config.js"}}' --output-path='{{.NODE_DIR}}' ${BUILD_ENV:+--mode="${BUILD_ENV}"}
+ # Copy Prometheus Console files.
+ - cp -r '{{joinPath .TASKFILE_DIR "prometheus"}}' '{{.TARGET_DIR}}'
# Copy third_party dependencies
- task: ':third_party:prometheus:copy-{{.TARGET_OS}}-{{.GOARCH}}'
vars: {TARGET_DIR: '{{.BIN_DIR}}'}
diff --git a/src/shadowbox/prometheus/console_libraries/menu.lib b/src/shadowbox/prometheus/console_libraries/menu.lib
new file mode 100644
index 000000000..0eef1bd1e
--- /dev/null
+++ b/src/shadowbox/prometheus/console_libraries/menu.lib
@@ -0,0 +1,53 @@
+{{/* vim: set ft=html: */}}
+
+{{/* Navbar, should be passed . */}}
+{{ define "navbar" }}
+
+{{ end }}
+
+{{/* LHS menu, should be passed . */}}
+{{ define "menu" }}
+
+{{ end }}
+
+{{/* Helper, pass (args . path name) */}}
+{{ define "_menuItem" }}
+
+{{ end }}
+
diff --git a/src/shadowbox/prometheus/console_libraries/prom.lib b/src/shadowbox/prometheus/console_libraries/prom.lib
new file mode 100644
index 000000000..d7d436f94
--- /dev/null
+++ b/src/shadowbox/prometheus/console_libraries/prom.lib
@@ -0,0 +1,138 @@
+{{/* vim: set ft=html: */}}
+{{/* Load Prometheus console library JS/CSS. Should go in */}}
+{{ define "prom_console_head" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ end }}
+
+{{/* Top of all pages. */}}
+{{ define "head" -}}
+
+
+
+{{ template "prom_console_head" }}
+
+
+{{ template "navbar" . }}
+
+{{ template "menu" . }}
+{{ end }}
+
+{{ define "__prom_query_drilldown_noop" }}{{ . }}{{ end }}
+{{ define "humanize" }}{{ humanize . }}{{ end }}
+{{ define "humanizeNoSmallPrefix" }}{{ if and (lt . 1.0) (gt . -1.0) }}{{ printf "%.3g" . }}{{ else }}{{ humanize . }}{{ end }}{{ end }}
+{{ define "humanize1024" }}{{ humanize1024 . }}{{ end }}
+{{ define "humanizeDuration" }}{{ humanizeDuration . }}{{ end }}
+{{ define "humanizePercentage" }}{{ humanizePercentage . }}{{ end }}
+{{ define "humanizeTimestamp" }}{{ humanizeTimestamp . }}{{ end }}
+{{ define "printf.1f" }}{{ printf "%.1f" . }}{{ end }}
+{{ define "printf.3g" }}{{ printf "%.3g" . }}{{ end }}
+
+{{/* prom_query_drilldown (args expr suffix? renderTemplate?)
+Displays the result of the expression, with a link to /graph for it.
+
+renderTemplate is the name of the template to use to render the value.
+*/}}
+{{ define "prom_query_drilldown" }}
+{{ $expr := .arg0 }}{{ $suffix := (or .arg1 "") }}{{ $renderTemplate := (or .arg2 "__prom_query_drilldown_noop") }}
+{{ with query $expr }}{{tmpl $renderTemplate ( . | first | value )}}{{ $suffix }}{{ else }}-{{ end }}
+{{ end }}
+
+{{ define "prom_path" }}/consoles/{{ .Path }}?{{ range $param, $value := .Params }}{{ $param }}={{ $value }}&{{ end }}{{ end }}"
+
+{{ define "prom_right_table_head" }}
+
+
+{{ end }}
+{{ define "prom_right_table_tail" }}
+
+
+{{ end }}
+
+{{/* RHS table head, pass job name. Should be used after prom_right_table_head. */}}
+{{ define "prom_right_table_job_head" }}
+
+ {{ . }} |
+ {{ template "prom_query_drilldown" (args (printf "sum(up{job='%s'})" .)) }} / {{ template "prom_query_drilldown" (args (printf "count(up{job='%s'})" .)) }} |
+
+
+ CPU |
+ {{ template "prom_query_drilldown" (args (printf "avg by(job)(irate(process_cpu_seconds_total{job='%s'}[5m]))" .) "s/s" "humanizeNoSmallPrefix") }} |
+
+
+ Memory |
+ {{ template "prom_query_drilldown" (args (printf "avg by(job)(process_resident_memory_bytes{job='%s'})" .) "B" "humanize1024") }} |
+
+{{ end }}
+
+
+{{ define "prom_content_head" }}
+
+
+{{ template "prom_graph_timecontrol" . }}
+{{ end }}
+{{ define "prom_content_tail" }}
+
+
+{{ end }}
+
+{{ define "prom_graph_timecontrol" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ end }}
+
+{{/* Bottom of all pages. */}}
+{{ define "tail" }}
+
+
+{{ end }}
diff --git a/src/shadowbox/prometheus/consoles/index.html b/src/shadowbox/prometheus/consoles/index.html
new file mode 100644
index 000000000..71758476d
--- /dev/null
+++ b/src/shadowbox/prometheus/consoles/index.html
@@ -0,0 +1,5 @@
+{{ template "head" . }} {{ template "prom_right_table_head" }} {{ template "prom_right_table_tail"
+}} {{ template "prom_content_head" . }}
+Overview
+These are standard consoles for Outline metrics.
+{{ template "prom_content_tail" . }} {{ template "tail" }}
diff --git a/src/shadowbox/prometheus/consoles/outline-overview.html b/src/shadowbox/prometheus/consoles/outline-overview.html
new file mode 100644
index 000000000..f8f8d3ae5
--- /dev/null
+++ b/src/shadowbox/prometheus/consoles/outline-overview.html
@@ -0,0 +1,91 @@
+{{ template "head" . }}
+
+{{ template "prom_right_table_head" }}
+
+ Overview |
+
+
+ CPU |
+ {{ template "prom_query_drilldown" (args (printf "irate(process_cpu_seconds_total{job='outline-server-ss',instance='%s'}[5m])" .Params.instance) "s/s" "humanizeNoSmallPrefix") }} |
+
+
+ Memory |
+ {{ template "prom_query_drilldown" (args (printf "process_resident_memory_bytes{job='outline-server-ss',instance='%s'}" .Params.instance) "B" "humanize1024") }} |
+
+
+ Version |
+
+
+ {{ with query (printf "shadowsocks_build_info{job='outline-server-ss',instance='%s'}" .Params.instance) }}{{. | first | label "version"}}{{end}}
+
+ |
+
+
+
+ Shadowsocks |
+
+
+ Keys |
+ {{ template "prom_query_drilldown" (args (printf "shadowsocks_keys{job='outline-server-ss',instance='%s'}" .Params.instance) "" "humanize") }} |
+
+
+ Ports |
+ {{ template "prom_query_drilldown" (args (printf "shadowsocks_ports{job='outline-server-ss',instance='%s'}" .Params.instance) "" "humanize") }} |
+
+
+ Bytes transferred |
+ {{ template "prom_query_drilldown" (args (printf "sum by (instance) (shadowsocks_data_bytes{job='outline-server-ss',instance='%s'})" .Params.instance) "B" "humanize1024") }} |
+
+{{ template "prom_right_table_tail" }}
+
+{{ template "prom_content_head" . }}
+
+
Outline Overview - {{ .Params.instance }}
+
+
Data Bytes per Access Key
+
+
+
+
TCP Connections by Location
+
+
+
+
UDP Packets by Location
+
+
+
+{{ template "prom_content_tail" . }}
+
+{{ template "tail" }}
diff --git a/src/shadowbox/prometheus/consoles/outline.html b/src/shadowbox/prometheus/consoles/outline.html
new file mode 100644
index 000000000..2809523bb
--- /dev/null
+++ b/src/shadowbox/prometheus/consoles/outline.html
@@ -0,0 +1,36 @@
+{{ template "head" . }}
+
+{{ template "prom_right_table_head" }}
+
+ Outline Shadowsocks |
+ {{ template "prom_query_drilldown" (args "sum(up{job='outline-server-ss'})") }} / {{ template "prom_query_drilldown" (args "count(up{job='outline-server-ss'})") }} |
+
+{{ template "prom_right_table_tail" }}
+
+{{ template "prom_content_head" . }}
+Outline
+
+
+
+ Shadowsocks |
+ Up |
+ Uptime |
+ Memory |
+
+{{ range query "up{job='outline-server-ss'}" | sortByLabel "instance" }}
+
+ {{ .Labels.instance }} |
+ Yes{{ else }} class="alert-danger">No{{ end }} |
+
+ {{ template "prom_query_drilldown" (args (printf "round((time() - process_start_time_seconds{job='outline-server-ss',instance='%s'})/(60*60*24), 1)" .Labels.instance) " days" "humanizeNoSmallPrefix") }}
+ |
+ {{ template "prom_query_drilldown" (args (printf "process_resident_memory_bytes{job='outline-server-ss',instance='%s'}" .Labels.instance) "B" "humanize1024")}} |
+
+{{ else }}
+No jobs found. |
+{{ end }}
+
+
+{{ template "prom_content_tail" . }}
+
+{{ template "tail" }}
diff --git a/src/shadowbox/server/main.ts b/src/shadowbox/server/main.ts
index fe8dbcb53..7b6254ad7 100644
--- a/src/shadowbox/server/main.ts
+++ b/src/shadowbox/server/main.ts
@@ -190,6 +190,10 @@ async function main() {
prometheusTsdbFilename,
'--web.listen-address',
prometheusLocation,
+ '--web.console.libraries',
+ path.join(APP_BASE_DIR, 'prometheus', 'console_libraries'),
+ '--web.console.templates',
+ path.join(APP_BASE_DIR, 'prometheus', 'consoles'),
'--log.level',
verbose ? 'debug' : 'info',
];