From f43df1b3fef79017b820d3794f55b2c58d52ee8c Mon Sep 17 00:00:00 2001 From: HoneyBearTech <31805425+HoneyBearTech@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:05:58 +0000 Subject: [PATCH 1/7] Optimize 2 SVG(s) --- Shoko/shoko.svg | 2 +- Verdaccio/verdaccio.svg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Shoko/shoko.svg b/Shoko/shoko.svg index f63e0b22c4..87ac7aa229 100644 --- a/Shoko/shoko.svg +++ b/Shoko/shoko.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Verdaccio/verdaccio.svg b/Verdaccio/verdaccio.svg index 8a98484a4a..37b7017f1e 100644 --- a/Verdaccio/verdaccio.svg +++ b/Verdaccio/verdaccio.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 56295249ffa775311f36f324b0fd1300447fcbed Mon Sep 17 00:00:00 2001 From: Sean <31805425+HoneyBearTech@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:07:56 -0500 Subject: [PATCH 2/7] Create app.json for Steerhouse configuration Added configuration for Steerhouse application. --- Steerhouse/app.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Steerhouse/app.json diff --git a/Steerhouse/app.json b/Steerhouse/app.json new file mode 100644 index 0000000000..eb5003ab91 --- /dev/null +++ b/Steerhouse/app.json @@ -0,0 +1,10 @@ +{ + "appid": "422c6bf3a19cca6e7c2e59a393b1838cca9bb6e9", + "name": "Steerhouse", + "website": "https://github.com/HoneyBearTech/Steerhouse", + "license": "MIT License", + "description": "Self-hosted web UI and API for running Ansible playbooks, with per-project access control, isolated workers and a full audit trail.", + "enhanced": true, + "tile_background": "dark", + "icon": "steerhouse.svg" +} From c3e40b3110e1da84a70cd20aa212535b4558e308 Mon Sep 17 00:00:00 2001 From: Sean <31805425+HoneyBearTech@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:08:42 -0500 Subject: [PATCH 3/7] Add files via upload --- Steerhouse/Steerhouse.php | 68 ++++++++++++++++++++++++++++++++++ Steerhouse/config.blade.php | 31 ++++++++++++++++ Steerhouse/livestats.blade.php | 14 +++++++ 3 files changed, 113 insertions(+) create mode 100644 Steerhouse/Steerhouse.php create mode 100644 Steerhouse/config.blade.php create mode 100644 Steerhouse/livestats.blade.php diff --git a/Steerhouse/Steerhouse.php b/Steerhouse/Steerhouse.php new file mode 100644 index 0000000000..6356dda4f3 --- /dev/null +++ b/Steerhouse/Steerhouse.php @@ -0,0 +1,68 @@ +url('api/stats'), $this->getAttrs()); + echo $test->status; + } + + public function livestats() + { + $status = 'inactive'; + $data = [ + 'running' => 0, + 'queued' => 0, + 'failed' => 0, + ]; + + // GET /api/stats returns counts only, e.g. {"queued": 0, "running": 2, "succeeded_24h": 14, + // "failed_24h": 1, "workers_online": 3}. execute() returns null when the connection fails; + // a bad key gets a 401 whose body has no counts, so the tile stays at zero and inactive. + $res = parent::execute($this->url('api/stats'), $this->getAttrs()); + if ($res !== null && $res->getStatusCode() === 200) { + $body = json_decode($res->getBody()); + if (isset($body->running, $body->queued, $body->failed_24h)) { + $data['running'] = (int) $body->running; + $data['queued'] = (int) $body->queued; + $data['failed'] = (int) $body->failed_24h; + // Refresh every 5 seconds while runs are waiting or in progress, else every 30. + if ($data['running'] > 0 || $data['queued'] > 0) { + $status = 'active'; + } + } + } + + return parent::getLiveStats($status, $data); + } + + private function getAttrs() + { + // A "stats" API key from Projects -> API keys in Steerhouse: it can read these counts only. + $attrs = [ + 'headers' => [ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . ($this->config->apikey ?? ''), + ], + ]; + if (!empty($this->config->ignore_tls)) { + $attrs['verify'] = false; + } + return $attrs; + } + + public function url($endpoint) + { + $api_url = parent::normaliseurl($this->config->url) . $endpoint; + return $api_url; + } +} diff --git a/Steerhouse/config.blade.php b/Steerhouse/config.blade.php new file mode 100644 index 0000000000..51a673a866 --- /dev/null +++ b/Steerhouse/config.blade.php @@ -0,0 +1,31 @@ +

{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')

+
+
+ + {!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!} +
+
+ + {!! Form::input('password', 'config[apikey]', isset($item) ? ($item->getconfig()->apikey ?? null) : null, ['placeholder' => __('app.apps.apikey'), 'data-config' => 'apikey', 'class' => 'form-control config-item']) !!} +
+
+ +
+ {!! Form::hidden('config[ignore_tls]', 0, ['class' => 'config-item', 'data-config' => 'ignore_tls']) !!} + +
+
+
+ +
+
diff --git a/Steerhouse/livestats.blade.php b/Steerhouse/livestats.blade.php new file mode 100644 index 0000000000..bb81f043fb --- /dev/null +++ b/Steerhouse/livestats.blade.php @@ -0,0 +1,14 @@ + From 3d69c0f227aaddad0edfb1971118d9edba8b7e0d Mon Sep 17 00:00:00 2001 From: Sean <31805425+HoneyBearTech@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:10:44 -0500 Subject: [PATCH 4/7] Add files via upload --- Steerhouse/steerhouse.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 Steerhouse/steerhouse.svg diff --git a/Steerhouse/steerhouse.svg b/Steerhouse/steerhouse.svg new file mode 100644 index 0000000000..af2ce05a46 --- /dev/null +++ b/Steerhouse/steerhouse.svg @@ -0,0 +1 @@ + \ No newline at end of file From e66d0d1b5bf23cdc79f297da61a1a30df71edca5 Mon Sep 17 00:00:00 2001 From: HoneyBearTech <31805425+HoneyBearTech@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:11:01 +0000 Subject: [PATCH 5/7] Optimize 1 SVG(s) --- Steerhouse/steerhouse.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Steerhouse/steerhouse.svg b/Steerhouse/steerhouse.svg index af2ce05a46..bc0cc5d3bd 100644 --- a/Steerhouse/steerhouse.svg +++ b/Steerhouse/steerhouse.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 89f88a90565296e17bfc9c24adb47e5ecd8a6e38 Mon Sep 17 00:00:00 2001 From: Sean <31805425+HoneyBearTech@users.noreply.github.com> Date: Fri, 23 Oct 2026 14:13:35 -0500 Subject: [PATCH 6/7] Delete Steerhouse directory --- Steerhouse/Steerhouse.php | 68 ---------------------------------- Steerhouse/app.json | 10 ----- Steerhouse/config.blade.php | 31 ---------------- Steerhouse/livestats.blade.php | 14 ------- Steerhouse/steerhouse.svg | 1 - 5 files changed, 124 deletions(-) delete mode 100644 Steerhouse/Steerhouse.php delete mode 100644 Steerhouse/app.json delete mode 100644 Steerhouse/config.blade.php delete mode 100644 Steerhouse/livestats.blade.php delete mode 100644 Steerhouse/steerhouse.svg diff --git a/Steerhouse/Steerhouse.php b/Steerhouse/Steerhouse.php deleted file mode 100644 index 6356dda4f3..0000000000 --- a/Steerhouse/Steerhouse.php +++ /dev/null @@ -1,68 +0,0 @@ -url('api/stats'), $this->getAttrs()); - echo $test->status; - } - - public function livestats() - { - $status = 'inactive'; - $data = [ - 'running' => 0, - 'queued' => 0, - 'failed' => 0, - ]; - - // GET /api/stats returns counts only, e.g. {"queued": 0, "running": 2, "succeeded_24h": 14, - // "failed_24h": 1, "workers_online": 3}. execute() returns null when the connection fails; - // a bad key gets a 401 whose body has no counts, so the tile stays at zero and inactive. - $res = parent::execute($this->url('api/stats'), $this->getAttrs()); - if ($res !== null && $res->getStatusCode() === 200) { - $body = json_decode($res->getBody()); - if (isset($body->running, $body->queued, $body->failed_24h)) { - $data['running'] = (int) $body->running; - $data['queued'] = (int) $body->queued; - $data['failed'] = (int) $body->failed_24h; - // Refresh every 5 seconds while runs are waiting or in progress, else every 30. - if ($data['running'] > 0 || $data['queued'] > 0) { - $status = 'active'; - } - } - } - - return parent::getLiveStats($status, $data); - } - - private function getAttrs() - { - // A "stats" API key from Projects -> API keys in Steerhouse: it can read these counts only. - $attrs = [ - 'headers' => [ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . ($this->config->apikey ?? ''), - ], - ]; - if (!empty($this->config->ignore_tls)) { - $attrs['verify'] = false; - } - return $attrs; - } - - public function url($endpoint) - { - $api_url = parent::normaliseurl($this->config->url) . $endpoint; - return $api_url; - } -} diff --git a/Steerhouse/app.json b/Steerhouse/app.json deleted file mode 100644 index eb5003ab91..0000000000 --- a/Steerhouse/app.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "appid": "422c6bf3a19cca6e7c2e59a393b1838cca9bb6e9", - "name": "Steerhouse", - "website": "https://github.com/HoneyBearTech/Steerhouse", - "license": "MIT License", - "description": "Self-hosted web UI and API for running Ansible playbooks, with per-project access control, isolated workers and a full audit trail.", - "enhanced": true, - "tile_background": "dark", - "icon": "steerhouse.svg" -} diff --git a/Steerhouse/config.blade.php b/Steerhouse/config.blade.php deleted file mode 100644 index 51a673a866..0000000000 --- a/Steerhouse/config.blade.php +++ /dev/null @@ -1,31 +0,0 @@ -

{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')

-
-
- - {!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!} -
-
- - {!! Form::input('password', 'config[apikey]', isset($item) ? ($item->getconfig()->apikey ?? null) : null, ['placeholder' => __('app.apps.apikey'), 'data-config' => 'apikey', 'class' => 'form-control config-item']) !!} -
-
- -
- {!! Form::hidden('config[ignore_tls]', 0, ['class' => 'config-item', 'data-config' => 'ignore_tls']) !!} - -
-
-
- -
-
diff --git a/Steerhouse/livestats.blade.php b/Steerhouse/livestats.blade.php deleted file mode 100644 index bb81f043fb..0000000000 --- a/Steerhouse/livestats.blade.php +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/Steerhouse/steerhouse.svg b/Steerhouse/steerhouse.svg deleted file mode 100644 index bc0cc5d3bd..0000000000 --- a/Steerhouse/steerhouse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From e9eed6717f128aafa7665c0c310f2537d7e2da83 Mon Sep 17 00:00:00 2001 From: Sean <31805425+HoneyBearTech@users.noreply.github.com> Date: Sun, 25 Oct 2026 19:47:09 -0500 Subject: [PATCH 7/7] Add files via upload --- USAS/USAS.php | 96 ++++++++++++++++++++++++++++++++++++++++ USAS/app.json | 10 +++++ USAS/config.blade.php | 15 +++++++ USAS/livestats.blade.php | 14 ++++++ USAS/usas.svg | 1 + 5 files changed, 136 insertions(+) create mode 100644 USAS/USAS.php create mode 100644 USAS/app.json create mode 100644 USAS/config.blade.php create mode 100644 USAS/livestats.blade.php create mode 100644 USAS/usas.svg diff --git a/USAS/USAS.php b/USAS/USAS.php new file mode 100644 index 0000000000..9700c35c98 --- /dev/null +++ b/USAS/USAS.php @@ -0,0 +1,96 @@ + "ISO", + "provision" => "Install", + "baseline" => "Baseline", + "site" => "Site", + "keys" => "Keys", + ]; + private const RESULTS = [ + "succeeded" => "OK", + "failed" => "Failed", + "stopped" => "Stopped", + "interrupted" => "Stopped", + ]; + + public function __construct() + { + } + + public function test() + { + $test = parent::appTest($this->url("api/status"), $this->getAttrs()); + echo $test->status; + } + + public function livestats() + { + $status = "inactive"; + $data = [ + "run_title" => "Run", + "run_value" => "-", + "vms" => "-", + "pending" => "-", + ]; + + // GET /api/status (docs/interfaces.md#the-status-api in the USAS repository). execute() returns + // null when the connection fails and never throws for an error status. + $res = parent::execute($this->url("api/status"), $this->getAttrs()); + if ($res === null || $res->getStatusCode() !== 200) { + return parent::getLiveStats($status, $data); + } + $body = json_decode($res->getBody()); + if (!is_object($body) || ($body->schema ?? null) !== 1) { + return parent::getLiveStats($status, $data); + } + + $running = $body->running ?? null; + $playbook = is_object($running) && is_string($running->playbook ?? null) ? $running->playbook : ""; + if (isset(self::PLAYBOOKS[$playbook])) { + // Heimdall asks every 5 seconds while the status is active, every 30 otherwise. + $status = "active"; + $data["run_title"] = "Now"; + $data["run_value"] = self::PLAYBOOKS[$playbook]; + } else { + $last = $body->last_run ?? null; + $result = is_object($last) && is_string($last->status ?? null) ? $last->status : ""; + $data["run_value"] = is_object($last) ? (self::RESULTS[$result] ?? "-") : "None"; + } + + $vms = $body->vms ?? null; + if (is_object($vms) && is_int($vms->running ?? null) && is_int($vms->total ?? null)) { + $data["vms"] = $vms->running . "/" . $vms->total; + } + $servers = $body->servers ?? null; + if (is_object($servers) && is_int($servers->pending ?? null)) { + $data["pending"] = (string) $servers->pending; + } + + return parent::getLiveStats($status, $data); + } + + public function url($endpoint) + { + return parent::normaliseurl($this->config->url) . $endpoint; + } + + private function getAttrs() + { + // The status token, made in USAS on the Account page. It goes only in this header, never in the URL. + return [ + "headers" => [ + "Accept" => "application/json", + "Authorization" => "Bearer " . trim($this->config->password ?? ""), + ], + ]; + } +} diff --git a/USAS/app.json b/USAS/app.json new file mode 100644 index 0000000000..5e3c98b735 --- /dev/null +++ b/USAS/app.json @@ -0,0 +1,10 @@ +{ + "appid": "a30b98c03a1facce720e0838722513881066a95f", + "name": "USAS", + "website": "https://github.com/HoneyBearTech/ubuntu-server-auto-setup", + "license": "MIT License", + "description": "USAS (Ubuntu Server Auto Setup) installs Ubuntu Server on new Proxmox VE VMs with nobody at the keyboard, loads the admin's GitHub SSH keys and applies a hardened baseline, from a web UI. The tile shows whether a run is going, how the last one ended, the cluster's running VMs and the servers still to install.", + "enhanced": true, + "tile_background": "dark", + "icon": "usas.svg" +} diff --git a/USAS/config.blade.php b/USAS/config.blade.php new file mode 100644 index 0000000000..977587e9fd --- /dev/null +++ b/USAS/config.blade.php @@ -0,0 +1,15 @@ +

{{ __('app.apps.config') }} ({{ __('app.optional') }}) @include('items.enable')

+
+
+ + {!! Form::text('config[override_url]', isset($item) ? $item->getconfig()->override_url : null, ['placeholder' => __('app.apps.override'), 'id' => 'override_url', 'class' => 'form-control']) !!} +
+
+ + {!! Form::input('password', 'config[password]', '', ['placeholder' => 'usas_st_...', 'data-config' => 'password', 'class' => 'form-control config-item']) !!} +

Make it in USAS on the Account page, under Status for dashboards. It only reads the status.

+
+
+ +
+
diff --git a/USAS/livestats.blade.php b/USAS/livestats.blade.php new file mode 100644 index 0000000000..208b03ef5f --- /dev/null +++ b/USAS/livestats.blade.php @@ -0,0 +1,14 @@ + diff --git a/USAS/usas.svg b/USAS/usas.svg new file mode 100644 index 0000000000..5275abd758 --- /dev/null +++ b/USAS/usas.svg @@ -0,0 +1 @@ + \ No newline at end of file