From 20b348a8c37f8951c8fc5938155f8c345ee4784a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 11 Feb 2026 19:18:09 +0000
Subject: [PATCH 001/148] chore(deps): bump axios from 1.13.2 to 1.13.5 in
/integration-tests
Bumps [axios](https://github.com/axios/axios) from 1.13.2 to 1.13.5.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.13.2...v1.13.5)
---
updated-dependencies:
- dependency-name: axios
dependency-version: 1.13.5
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
---
integration-tests/package-lock.json | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json
index 1251464..a6557b8 100644
--- a/integration-tests/package-lock.json
+++ b/integration-tests/package-lock.json
@@ -421,13 +421,13 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
From 58e1f37ee4093c5b1ce52d19c272a9c302d557d7 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sat, 14 Feb 2026 22:08:52 +0000
Subject: [PATCH 002/148] chore: util script to cleanup bad releases
---
var/release-utils/unrelease.sh | 110 +++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+)
create mode 100755 var/release-utils/unrelease.sh
diff --git a/var/release-utils/unrelease.sh b/var/release-utils/unrelease.sh
new file mode 100755
index 0000000..6d5243c
--- /dev/null
+++ b/var/release-utils/unrelease.sh
@@ -0,0 +1,110 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+RELEASE_NAME="${1:-}"
+GHCR_IMAGE="ghcr.io/olivetin/olivetin"
+DOCKERHUB_IMAGE="jamesread/olivetin"
+
+log() {
+ echo "[unrelease] $*"
+}
+
+prompt_confirm() {
+ local prompt="$1"
+ local default="${2:-n}"
+ if [[ "$default" == "y" ]]; then
+ read -r -p "$prompt [Y/n] " reply
+ else
+ read -r -p "$prompt [y/N] " reply
+ fi
+ reply="${reply:-$default}"
+ case "$(echo "$reply" | tr '[:upper:]' '[:lower:]')" in
+ y|yes) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+if [[ -z "$RELEASE_NAME" ]]; then
+ echo "Usage: $0 " >&2
+ echo "Example: $0 3000.10.0" >&2
+ exit 1
+fi
+
+log "Release to remove: $RELEASE_NAME"
+log "This will: 1) Delete GitHub release, 2) Delete GitHub tag, 3) Delete GHCR image tag, 4) Delete Docker Hub image tag"
+echo
+
+# --- GitHub release ---
+log "Step 1: Delete GitHub release '$RELEASE_NAME'"
+if prompt_confirm "Delete GitHub release?" "n"; then
+ if err=$(gh release delete "$RELEASE_NAME" --yes 2>&1); then
+ log "Deleted GitHub release."
+ else
+ log "Failed to delete GitHub release:" >&2
+ echo "$err" | sed 's/^/[unrelease] /' >&2
+ fi
+else
+ log "Skipped GitHub release."
+fi
+echo
+
+# --- GitHub tag ---
+log "Step 2: Delete GitHub tag '$RELEASE_NAME'"
+if prompt_confirm "Delete GitHub tag?" "n"; then
+ repo=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null) || repo="olivetin/olivetin"
+ if err=$(gh api -X DELETE "repos/$repo/git/refs/tags/$RELEASE_NAME" 2>&1); then
+ log "Deleted GitHub tag."
+ else
+ log "Failed to delete GitHub tag:" >&2
+ echo "$err" | sed 's/^/[unrelease] /' >&2
+ fi
+else
+ log "Skipped GitHub tag."
+fi
+echo
+
+# --- GHCR ---
+log "Step 3: Delete GHCR image tag $GHCR_IMAGE:$RELEASE_NAME"
+if prompt_confirm "Delete GHCR container image version?" "n"; then
+ list_err=$(gh api "orgs/olivetin/packages/container/olivetin/versions" --jq ".[] | select(.metadata.container.tags[]? == \"$RELEASE_NAME\") | .id" 2>&1) || true
+ version_id=$(echo "$list_err" | head -1)
+ if [[ -z "$version_id" || ! "$version_id" =~ ^[0-9]+$ ]]; then
+ log "Could not resolve GHCR version for tag '$RELEASE_NAME' (need read:packages scope, or tag may not exist)." >&2
+ if [[ "$list_err" == *"message"* ]]; then
+ msg=$(echo "$list_err" | sed -n 's/.*"message":"\([^"]*\)".*/\1/p' | head -1)
+ [[ -n "$msg" ]] && log " $msg" >&2
+ fi
+ else
+ if err=$(gh api -X DELETE "orgs/olivetin/packages/container/olivetin/versions/$version_id" 2>&1); then
+ log "Deleted GHCR version (id: $version_id)."
+ else
+ log "Failed to delete GHCR version:" >&2
+ echo "$err" | sed 's/^/[unrelease] /' >&2
+ fi
+ fi
+else
+ log "Skipped GHCR."
+fi
+echo
+
+# --- Docker Hub ---
+log "Step 4: Delete Docker Hub image tag $DOCKERHUB_IMAGE:$RELEASE_NAME"
+if prompt_confirm "Delete Docker Hub image tag? (requires DOCKERHUB_TOKEN)" "n"; then
+ if [[ -z "${DOCKERHUB_TOKEN:-}" ]]; then
+ log "DOCKERHUB_TOKEN is not set. Get a token from https://hub.docker.com/settings/security and run: DOCKERHUB_TOKEN=xxx $0 $RELEASE_NAME" >&2
+ log "Skipped Docker Hub."
+ else
+ status=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
+ -H "Authorization: Bearer $DOCKERHUB_TOKEN" \
+ "https://hub.docker.com/v2/repositories/$DOCKERHUB_IMAGE/tags/$RELEASE_NAME/")
+ if [[ "$status" == "204" ]]; then
+ log "Deleted Docker Hub tag."
+ else
+ log "Docker Hub delete returned HTTP $status (tag may not exist or token invalid)." >&2
+ fi
+ fi
+else
+ log "Skipped Docker Hub."
+fi
+
+log "Done."
From 544515c2a6bd44454bbdb86829e5b63da798fd71 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 15 Feb 2026 00:19:36 +0000
Subject: [PATCH 003/148] chore: #829, json support in template engine
---
service/internal/tpl/templates.go | 15 ++++-
service/internal/tpl/templates_test.go | 81 ++++++++++++++++++++++++++
2 files changed, 95 insertions(+), 1 deletion(-)
create mode 100644 service/internal/tpl/templates_test.go
diff --git a/service/internal/tpl/templates.go b/service/internal/tpl/templates.go
index 580b907..cb9978f 100644
--- a/service/internal/tpl/templates.go
+++ b/service/internal/tpl/templates.go
@@ -1,6 +1,7 @@
package tpl
import (
+ "encoding/json"
"fmt"
"regexp"
"strings"
@@ -12,8 +13,20 @@ import (
log "github.com/sirupsen/logrus"
)
+func jsonFunc(v any) (string, error) {
+ if v == nil {
+ return "null", nil
+ }
+ data, err := json.Marshal(v)
+ if err != nil {
+ return "", err
+ }
+ return string(data), nil
+}
+
var tpl = template.New("tpl").
- Option("missingkey=error")
+ Option("missingkey=error").
+ Funcs(template.FuncMap{"Json": jsonFunc})
type olivetinInfo struct {
Build *installationinfo.BuildInfo
diff --git a/service/internal/tpl/templates_test.go b/service/internal/tpl/templates_test.go
new file mode 100644
index 0000000..d46f902
--- /dev/null
+++ b/service/internal/tpl/templates_test.go
@@ -0,0 +1,81 @@
+package tpl
+
+import (
+ "encoding/json"
+ "strings"
+ "testing"
+
+ "github.com/OliveTin/OliveTin/internal/entities"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseTemplateWithActionContext_Json(t *testing.T) {
+ tests := []struct {
+ name string
+ source string
+ ent *entities.Entity
+ args map[string]string
+ expectedOutput string
+ expectError bool
+ checkJsonOnly bool
+ }{
+ {
+ name: "Arguments piped to Json",
+ source: `echo {{ .Arguments | Json }}`,
+ ent: nil,
+ args: map[string]string{"value": "true", "ot_username": "alice"},
+ expectedOutput: `echo `,
+ expectError: false,
+ checkJsonOnly: true,
+ },
+ {
+ name: "CurrentEntity field piped to Json",
+ source: `curl -d {{ .CurrentEntity.foo.bar | Json }}`,
+ ent: &entities.Entity{Data: map[string]any{"foo": map[string]any{"bar": "baz"}}},
+ args: nil,
+ expectedOutput: `curl -d "baz"`,
+ expectError: false,
+ },
+ {
+ name: "CurrentEntity nested object piped to Json",
+ source: `curl --json -d {{ .CurrentEntity.payload | Json }}`,
+ ent: &entities.Entity{Data: map[string]any{"payload": map[string]any{"on": true}}},
+ args: nil,
+ expectedOutput: `curl --json -d {"on":true}`,
+ expectError: false,
+ },
+ {
+ name: "Single argument value as Json",
+ source: `echo {{ .Arguments.value | Json }}`,
+ ent: nil,
+ args: map[string]string{"value": "hello"},
+ expectedOutput: `echo "hello"`,
+ expectError: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ output, err := ParseTemplateWithActionContext(tt.source, tt.ent, tt.args)
+ if tt.expectError {
+ assert.Error(t, err)
+ return
+ }
+ assert.NoError(t, err)
+ if tt.checkJsonOnly {
+ prefix := strings.TrimSuffix(tt.expectedOutput, " ")
+ assert.True(t, strings.HasPrefix(output, prefix), "output %q should start with %q", output, prefix)
+ jsonPart := strings.TrimPrefix(output, prefix)
+ jsonPart = strings.TrimSpace(jsonPart)
+ var decoded map[string]string
+ err := json.Unmarshal([]byte(jsonPart), &decoded)
+ assert.NoError(t, err)
+ for k, v := range tt.args {
+ assert.Equal(t, v, decoded[k], "decoded JSON should contain %s=%s", k, v)
+ }
+ assert.Len(t, decoded, len(tt.args))
+ } else {
+ assert.Equal(t, tt.expectedOutput, output)
+ }
+ })
+ }
+}
From 903fd15bdb28695d466018faee95d8e3aceb8945 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 15 Feb 2026 00:31:26 +0000
Subject: [PATCH 004/148] fix: #860 respect popupOnStart even if the action has
args.
---
frontend/resources/vue/views/ArgumentForm.vue | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/frontend/resources/vue/views/ArgumentForm.vue b/frontend/resources/vue/views/ArgumentForm.vue
index b1a430c..f749daf 100644
--- a/frontend/resources/vue/views/ArgumentForm.vue
+++ b/frontend/resources/vue/views/ArgumentForm.vue
@@ -73,6 +73,7 @@ const confirmationChecked = ref(false)
const hasConfirmation = ref(false)
const formErrors = ref({})
const actionArguments = ref([])
+const popupOnStart = ref('')
// Computed properties
@@ -93,6 +94,7 @@ async function setup() {
title.value = action.title
icon.value = action.icon
+ popupOnStart.value = action.popupOnStart || ''
actionArguments.value = action.arguments || []
argValues.value = {}
formErrors.value = {}
@@ -418,7 +420,11 @@ async function handleSubmit(event) {
try {
const response = await startAction(argvs)
- router.push(`/logs/${response.executionTrackingId}`)
+ if (popupOnStart.value && popupOnStart.value.includes('execution-dialog')) {
+ router.push(`/logs/${response.executionTrackingId}`)
+ } else {
+ router.back()
+ }
} catch (err) {
console.error('Failed to start action:', err)
}
From 7dcbca31fcb9ccb896fe315d3cf0a181fd609eff Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 15 Feb 2026 01:03:22 +0000
Subject: [PATCH 005/148] fix: #803 enable custom JS (again!)
---
frontend/main.js | 8 ++++++++
frontend/resources/vue/App.vue | 12 ++++++++++++
2 files changed, 20 insertions(+)
diff --git a/frontend/main.js b/frontend/main.js
index a434cce..5574e46 100644
--- a/frontend/main.js
+++ b/frontend/main.js
@@ -61,6 +61,14 @@ async function initClient () {
window.client = createClient(OliveTinApiService, transport)
window.initResponse = await window.client.init({})
+ if (window.initResponse.enableCustomJs) {
+ const script = document.createElement('script')
+ script.src = '/custom-webui/custom.js'
+ script.async = true
+ script.id = 'olivetin-custom-js'
+ document.head.appendChild(script)
+ }
+
const i18nSettings = createI18n({
legacy: false,
locale: getSelectedLanguage(),
diff --git a/frontend/resources/vue/App.vue b/frontend/resources/vue/App.vue
index 5c1fa52..5648191 100644
--- a/frontend/resources/vue/App.vue
+++ b/frontend/resources/vue/App.vue
@@ -231,6 +231,7 @@ function updateHeaderFromInit() {
}
applyStyleMods()
+ loadCustomJsIfEnabled()
renderNavigation()
applyTheme()
@@ -369,6 +370,17 @@ function applyTheme() {
}
}
+function loadCustomJsIfEnabled() {
+ if (!window.initResponse?.enableCustomJs || document.getElementById('olivetin-custom-js')) {
+ return
+ }
+ const script = document.createElement('script')
+ script.src = '/custom-webui/custom.js'
+ script.async = true
+ script.id = 'olivetin-custom-js'
+ document.head.appendChild(script)
+}
+
function applyStyleMods() {
if (!window.initResponse || !window.initResponse.styleMods) {
return
From 4700f998e3db63955fb93ec73e99052b7a978028 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 16 Feb 2026 05:49:56 +0000
Subject: [PATCH 006/148] chore(deps): bump wait-on from 9.0.3 to 9.0.4 in
/integration-tests
Bumps [wait-on](https://github.com/jeffbski/wait-on) from 9.0.3 to 9.0.4.
- [Release notes](https://github.com/jeffbski/wait-on/releases)
- [Commits](https://github.com/jeffbski/wait-on/compare/v9.0.3...v9.0.4)
---
updated-dependencies:
- dependency-name: wait-on
dependency-version: 9.0.4
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
integration-tests/package-lock.json | 49 +++++++++++++++--------------
integration-tests/package.json | 2 +-
2 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json
index 1251464..12d651e 100644
--- a/integration-tests/package-lock.json
+++ b/integration-tests/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "AGPL-3.0-only",
"dependencies": {
- "wait-on": "^9.0.3"
+ "wait-on": "^9.0.4"
},
"devDependencies": {
"chai": "^6.2.2",
@@ -209,9 +209,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@hapi/tlds": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.3.tgz",
- "integrity": "sha512-QIvUMB5VZ8HMLZF9A2oWr3AFM430QC8oGd0L35y2jHpuW6bIIca6x/xL7zUf4J7L9WJ3qjz+iJII8ncaeMbpSg==",
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.5.tgz",
+ "integrity": "sha512-Vq/1gnIIsvFUpKlDdfrPd/ssHDpAyBP/baVukh3u2KSG2xoNjsnRNjQiPmuyPPGqsn1cqVWWhtZHfOBaLizFRQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=14.0.0"
@@ -321,9 +321,9 @@
}
},
"node_modules/@standard-schema/spec": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
- "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"license": "MIT"
},
"node_modules/@types/estree": {
@@ -421,13 +421,13 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
@@ -1437,9 +1437,9 @@
}
},
"node_modules/joi": {
- "version": "18.0.1",
- "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.1.tgz",
- "integrity": "sha512-IiQpRyypSnLisQf3PwuN2eIHAsAIGZIrLZkd4zdvIar2bDyhM91ubRjy8a3eYablXsh9BeI/c7dmPYHca5qtoA==",
+ "version": "18.0.2",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz",
+ "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/address": "^5.1.1",
@@ -1547,9 +1547,10 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
@@ -2210,14 +2211,14 @@
"dev": true
},
"node_modules/wait-on": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.3.tgz",
- "integrity": "sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA==",
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.4.tgz",
+ "integrity": "sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==",
"license": "MIT",
"dependencies": {
- "axios": "^1.13.2",
- "joi": "^18.0.1",
- "lodash": "^4.17.21",
+ "axios": "^1.13.5",
+ "joi": "^18.0.2",
+ "lodash": "^4.17.23",
"minimist": "^1.2.8",
"rxjs": "^7.8.2"
},
diff --git a/integration-tests/package.json b/integration-tests/package.json
index 81f10a7..21bbcc8 100644
--- a/integration-tests/package.json
+++ b/integration-tests/package.json
@@ -17,6 +17,6 @@
"selenium-webdriver": "^4.40.0"
},
"dependencies": {
- "wait-on": "^9.0.3"
+ "wait-on": "^9.0.4"
}
}
From ea4cdf9df2766bb9ba267d0edcc936a9fc896e6a Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 19 Feb 2026 20:33:24 +0000
Subject: [PATCH 007/148] fix: Logs page pagination (#883)
---
.../scripts/gen/olivetin/api/v1/olivetin_pb.d.ts | 8 ++++++++
.../scripts/gen/olivetin/api/v1/olivetin_pb.js | 3 ++-
frontend/resources/vue/views/ActionDetailsView.vue | 2 +-
frontend/resources/vue/views/LogsListView.vue | 4 ++--
proto/olivetin/api/v1/olivetin.proto | 1 +
service/gen/olivetin/api/v1/olivetin.pb.go | 13 +++++++++++--
service/internal/api/api.go | 11 ++++++++++-
service/internal/api/dashboard_entities.go | 2 +-
8 files changed, 36 insertions(+), 8 deletions(-)
diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts
index b28fdd9..8e308fe 100644
--- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts
+++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts
@@ -495,6 +495,13 @@ export declare type GetLogsRequest = Message<"olivetin.api.v1.GetLogsRequest"> &
* @generated from field: string date_filter = 2;
*/
dateFilter: string;
+
+ /**
+ * Number of logs per page (optional; server default used if 0 or unset)
+ *
+ * @generated from field: int64 page_size = 3;
+ */
+ pageSize: bigint;
};
/**
@@ -1846,3 +1853,4 @@ export declare const OliveTinApiService: GenService<{
output: typeof EntitySchema;
},
}>;
+
diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js
index 02156ca..4e6e723 100644
--- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js
+++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js
@@ -8,7 +8,7 @@ import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2
* Describes the file olivetin/api/v1/olivetin.proto.
*/
export const file_olivetin_api_v1_olivetin = /*@__PURE__*/
- fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSLcAQoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkiuwIKDkFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEdHlwZRgDIAEoCRIVCg1kZWZhdWx0X3ZhbHVlGAQgASgJEjYKB2Nob2ljZXMYBSADKAsyJS5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnRDaG9pY2USEwoLZGVzY3JpcHRpb24YBiABKAkSRQoLc3VnZ2VzdGlvbnMYByADKAsyMC5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQuU3VnZ2VzdGlvbnNFbnRyeRIfChdzdWdnZXN0aW9uc19icm93c2VyX2tleRgIIAEoCRoyChBTdWdnZXN0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNAoUQWN0aW9uQXJndW1lbnRDaG9pY2USDQoFdmFsdWUYASABKAkSDQoFdGl0bGUYAiABKAkisgEKBkVudGl0eRINCgV0aXRsZRgBIAEoCRISCgp1bmlxdWVfa2V5GAIgASgJEgwKBHR5cGUYAyABKAkSEwoLZGlyZWN0b3JpZXMYBCADKAkSMwoGZmllbGRzGAUgAygLMiMub2xpdmV0aW4uYXBpLnYxLkVudGl0eS5GaWVsZHNFbnRyeRotCgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIlQKFEdldERhc2hib2FyZFJlc3BvbnNlEg0KBXRpdGxlGAEgASgJEi0KCWRhc2hib2FyZBgEIAEoCzIaLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmQiQgoPRWZmZWN0aXZlUG9saWN5EhgKEHNob3dfZGlhZ25vc3RpY3MYASABKAgSFQoNc2hvd19sb2dfbGlzdBgCIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSJ9ChJTdGFydEFjdGlvblJlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCRI3Cglhcmd1bWVudHMYAiADKAsyJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25Bcmd1bWVudBIaChJ1bmlxdWVfdHJhY2tpbmdfaWQYAyABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJImcKGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IkoKGlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIsChdTdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiOQoYU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgCIAEoCSIzCh5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJIk8KH1N0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IjsKDkdldExvZ3NSZXF1ZXN0EhQKDHN0YXJ0X29mZnNldBgBIAEoAxITCgtkYXRlX2ZpbHRlchgCIAEoCSKaAwoITG9nRW50cnkSGAoQZGF0ZXRpbWVfc3RhcnRlZBgBIAEoCRIUCgxhY3Rpb25fdGl0bGUYAiABKAkSDgoGb3V0cHV0GAMgASgJEhEKCXRpbWVkX291dBgFIAEoCBIRCglleGl0X2NvZGUYBiABKAUSDAoEdXNlchgHIAEoCRISCgp1c2VyX2NsYXNzGAggASgJEhMKC2FjdGlvbl9pY29uGAkgASgJEgwKBHRhZ3MYCiADKAkSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAsgASgJEhkKEWRhdGV0aW1lX2ZpbmlzaGVkGAwgASgJEhkKEWV4ZWN1dGlvbl9zdGFydGVkGA4gASgIEhoKEmV4ZWN1dGlvbl9maW5pc2hlZBgPIAEoCBIPCgdibG9ja2VkGBAgASgIEhYKDmRhdGV0aW1lX2luZGV4GBEgASgDEhAKCGNhbl9raWxsGBIgASgIEiMKG2RhdGV0aW1lX3JhdGVfbGltaXRfZXhwaXJlcxgTIAEoCRISCgpiaW5kaW5nX2lkGBQgASgJIpEBCg9HZXRMb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyI/ChRHZXRBY3Rpb25Mb2dzUmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkSFAoMc3RhcnRfb2Zmc2V0GAIgASgDIpcBChVHZXRBY3Rpb25Mb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyJlChtWYWxpZGF0ZUFyZ3VtZW50VHlwZVJlcXVlc3QSDQoFdmFsdWUYASABKAkSDAoEdHlwZRgCIAEoCRISCgpiaW5kaW5nX2lkGAMgASgJEhUKDWFyZ3VtZW50X25hbWUYBCABKAkiQgocVmFsaWRhdGVBcmd1bWVudFR5cGVSZXNwb25zZRINCgV2YWxpZBgBIAEoCBITCgtkZXNjcmlwdGlvbhgCIAEoCSI2ChVXYXRjaEV4ZWN1dGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJIiYKFFdhdGNoRXhlY3V0aW9uVXBkYXRlEg4KBnVwZGF0ZRgBIAEoCSJKChZFeGVjdXRpb25TdGF0dXNSZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIRCglhY3Rpb25faWQYAiABKAkiRwoXRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5Ig8KDVdob0FtSVJlcXVlc3QibAoOV2hvQW1JUmVzcG9uc2USGgoSYXV0aGVudGljYXRlZF91c2VyGAEgASgJEhEKCXVzZXJncm91cBgCIAEoCRIQCghwcm92aWRlchgDIAEoCRIMCgRhY2xzGAQgAygJEgsKA3NpZBgFIAEoCSISChBTb3NSZXBvcnRSZXF1ZXN0IiIKEVNvc1JlcG9ydFJlc3BvbnNlEg0KBWFsZXJ0GAEgASgJIhEKD0R1bXBWYXJzUmVxdWVzdCKVAQoQRHVtcFZhcnNSZXNwb25zZRINCgVhbGVydBgBIAEoCRJBCghjb250ZW50cxgCIAMoCzIvLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlLkNvbnRlbnRzRW50cnkaLwoNQ29udGVudHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIjsKDERlYnVnQmluZGluZxIUCgxhY3Rpb25fdGl0bGUYASABKAkSFQoNZW50aXR5X3ByZWZpeBgCIAEoCSIeChxEdW1wUHVibGljSWRBY3Rpb25NYXBSZXF1ZXN0Is4BCh1EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZRINCgVhbGVydBgBIAEoCRJOCghjb250ZW50cxgCIAMoCzI8Lm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZS5Db250ZW50c0VudHJ5Gk4KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEiwKBXZhbHVlGAIgASgLMh0ub2xpdmV0aW4uYXBpLnYxLkRlYnVnQmluZGluZzoCOAEiEgoQR2V0UmVhZHl6UmVxdWVzdCIjChFHZXRSZWFkeXpSZXNwb25zZRIOCgZzdGF0dXMYASABKAkiFAoSRXZlbnRTdHJlYW1SZXF1ZXN0IuMCChNFdmVudFN0cmVhbVJlc3BvbnNlEj0KDmVudGl0eV9jaGFuZ2VkGAIgASgLMiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50RW50aXR5Q2hhbmdlZEgAEj0KDmNvbmZpZ19jaGFuZ2VkGAMgASgLMiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50Q29uZmlnQ2hhbmdlZEgAEkUKEmV4ZWN1dGlvbl9maW5pc2hlZBgEIAEoCzInLm9saXZldGluLmFwaS52MS5FdmVudEV4ZWN1dGlvbkZpbmlzaGVkSAASQwoRZXhlY3V0aW9uX3N0YXJ0ZWQYBSABKAsyJi5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25TdGFydGVkSAASOQoMb3V0cHV0X2NodW5rGAYgASgLMiEub2xpdmV0aW4uYXBpLnYxLkV2ZW50T3V0cHV0Q2h1bmtIAEIHCgVldmVudCJBChBFdmVudE91dHB1dENodW5rEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZvdXRwdXQYAiABKAkiFAoSRXZlbnRFbnRpdHlDaGFuZ2VkIhQKEkV2ZW50Q29uZmlnQ2hhbmdlZCJGChZFdmVudEV4ZWN1dGlvbkZpbmlzaGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSJFChVFdmVudEV4ZWN1dGlvblN0YXJ0ZWQSLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IjIKEUtpbGxBY3Rpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCSJtChJLaWxsQWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJEg4KBmtpbGxlZBgCIAEoCBIZChFhbHJlYWR5X2NvbXBsZXRlZBgDIAEoCBINCgVmb3VuZBgEIAEoCCI7ChVMb2NhbFVzZXJMb2dpblJlcXVlc3QSEAoIdXNlcm5hbWUYASABKAkSEAoIcGFzc3dvcmQYAiABKAkiKQoWTG9jYWxVc2VyTG9naW5SZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIicKE1Bhc3N3b3JkSGFzaFJlcXVlc3QSEAoIcGFzc3dvcmQYASABKAkiJAoUUGFzc3dvcmRIYXNoUmVzcG9uc2USDAoEaGFzaBgBIAEoCSIPCg1Mb2dvdXRSZXF1ZXN0IhAKDkxvZ291dFJlc3BvbnNlIhcKFUdldERpYWdub3N0aWNzUmVxdWVzdCJFChZHZXREaWFnbm9zdGljc1Jlc3BvbnNlEhMKC1NzaEZvdW5kS2V5GAEgASgJEhYKDlNzaEZvdW5kQ29uZmlnGAIgASgJIg0KC0luaXRSZXF1ZXN0IusFCgxJbml0UmVzcG9uc2USEgoKc2hvd0Zvb3RlchgBIAEoCBIWCg5zaG93TmF2aWdhdGlvbhgCIAEoCBIXCg9zaG93TmV3VmVyc2lvbnMYAyABKAgSGAoQYXZhaWxhYmxlVmVyc2lvbhgEIAEoCRIWCg5jdXJyZW50VmVyc2lvbhgFIAEoCRIRCglwYWdlVGl0bGUYBiABKAkSHgoWc2VjdGlvbk5hdmlnYXRpb25TdHlsZRgHIAEoCRIaChJkZWZhdWx0SWNvbkZvckJhY2sYCCABKAkSFgoOZW5hYmxlQ3VzdG9tSnMYCSABKAgSFAoMYXV0aExvZ2luVXJsGAogASgJEhYKDmF1dGhMb2NhbExvZ2luGAsgASgIEhEKCXN0eWxlTW9kcxgMIAMoCRI4Cg9vQXV0aDJQcm92aWRlcnMYDSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuT0F1dGgyUHJvdmlkZXISOAoPYWRkaXRpb25hbExpbmtzGA4gAygLMh8ub2xpdmV0aW4uYXBpLnYxLkFkZGl0aW9uYWxMaW5rEhYKDnJvb3REYXNoYm9hcmRzGA8gAygJEhoKEmF1dGhlbnRpY2F0ZWRfdXNlchgQIAEoCRIjChthdXRoZW50aWNhdGVkX3VzZXJfcHJvdmlkZXIYESABKAkSOgoQZWZmZWN0aXZlX3BvbGljeRgSIAEoCzIgLm9saXZldGluLmFwaS52MS5FZmZlY3RpdmVQb2xpY3kSFgoOYmFubmVyX21lc3NhZ2UYEyABKAkSEgoKYmFubmVyX2NzcxgUIAEoCRIYChBzaG93X2RpYWdub3N0aWNzGBUgASgIEhUKDXNob3dfbG9nX2xpc3QYFiABKAgSFgoObG9naW5fcmVxdWlyZWQYFyABKAgSGAoQYXZhaWxhYmxlX3RoZW1lcxgYIAMoCRIkChxzaG93X25hdmlnYXRlX29uX3N0YXJ0X2ljb25zGBkgASgIIiwKDkFkZGl0aW9uYWxMaW5rEg0KBXRpdGxlGAEgASgJEgsKA3VybBgCIAEoCSI6Cg5PQXV0aDJQcm92aWRlchINCgV0aXRsZRgBIAEoCRIMCgRpY29uGAMgASgJEgsKA2tleRgEIAEoCSItChdHZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBISCgpiaW5kaW5nX2lkGAEgASgJIkMKGEdldEFjdGlvbkJpbmRpbmdSZXNwb25zZRInCgZhY3Rpb24YASABKAsyFy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uIhQKEkdldEVudGl0aWVzUmVxdWVzdCJUChNHZXRFbnRpdGllc1Jlc3BvbnNlEj0KEmVudGl0eV9kZWZpbml0aW9ucxgBIAMoCzIhLm9saXZldGluLmFwaS52MS5FbnRpdHlEZWZpbml0aW9uImkKEEVudGl0eURlZmluaXRpb24SDQoFdGl0bGUYASABKAkSKgoJaW5zdGFuY2VzGAIgAygLMhcub2xpdmV0aW4uYXBpLnYxLkVudGl0eRIaChJ1c2VkX29uX2Rhc2hib2FyZHMYAyADKAkiNAoQR2V0RW50aXR5UmVxdWVzdBISCgp1bmlxdWVfa2V5GAEgASgJEgwKBHR5cGUYAiABKAkiNQoUUmVzdGFydEFjdGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJMugSChJPbGl2ZVRpbkFwaVNlcnZpY2USXQoMR2V0RGFzaGJvYXJkEiQub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuR2V0RGFzaGJvYXJkUmVzcG9uc2UiABJaCgtTdGFydEFjdGlvbhIjLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAEm8KElN0YXJ0QWN0aW9uQW5kV2FpdBIqLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXF1ZXN0Gisub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlIgASaQoQU3RhcnRBY3Rpb25CeUdldBIoLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBopLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2UiABJ+ChdTdGFydEFjdGlvbkJ5R2V0QW5kV2FpdBIvLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QaMC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZSIAEl4KDVJlc3RhcnRBY3Rpb24SJS5vbGl2ZXRpbi5hcGkudjEuUmVzdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAElcKCktpbGxBY3Rpb24SIi5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlcXVlc3QaIy5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlc3BvbnNlIgASZgoPRXhlY3V0aW9uU3RhdHVzEicub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QaKC5vbGl2ZXRpbi5hcGkudjEuRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2UiABJOCgdHZXRMb2dzEh8ub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXF1ZXN0GiAub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXNwb25zZSIAEmAKDUdldEFjdGlvbkxvZ3MSJS5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1JlcXVlc3QaJi5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1Jlc3BvbnNlIgASdQoUVmFsaWRhdGVBcmd1bWVudFR5cGUSLC5vbGl2ZXRpbi5hcGkudjEuVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Gi0ub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVzcG9uc2UiABJLCgZXaG9BbUkSHi5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVxdWVzdBofLm9saXZldGluLmFwaS52MS5XaG9BbUlSZXNwb25zZSIAElQKCVNvc1JlcG9ydBIhLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXF1ZXN0GiIub2xpdmV0aW4uYXBpLnYxLlNvc1JlcG9ydFJlc3BvbnNlIgASUQoIRHVtcFZhcnMSIC5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXF1ZXN0GiEub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVzcG9uc2UiABJ4ChVEdW1wUHVibGljSWRBY3Rpb25NYXASLS5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdBouLm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZSIAElQKCUdldFJlYWR5ehIhLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXF1ZXN0GiIub2xpdmV0aW4uYXBpLnYxLkdldFJlYWR5elJlc3BvbnNlIgASYwoOTG9jYWxVc2VyTG9naW4SJi5vbGl2ZXRpbi5hcGkudjEuTG9jYWxVc2VyTG9naW5SZXF1ZXN0Gicub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVzcG9uc2UiABJdCgxQYXNzd29yZEhhc2gSJC5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVxdWVzdBolLm9saXZldGluLmFwaS52MS5QYXNzd29yZEhhc2hSZXNwb25zZSIAEksKBkxvZ291dBIeLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXF1ZXN0Gh8ub2xpdmV0aW4uYXBpLnYxLkxvZ291dFJlc3BvbnNlIgASXAoLRXZlbnRTdHJlYW0SIy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRTdHJlYW1SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVzcG9uc2UiADABEmMKDkdldERpYWdub3N0aWNzEiYub2xpdmV0aW4uYXBpLnYxLkdldERpYWdub3N0aWNzUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1Jlc3BvbnNlIgASRQoESW5pdBIcLm9saXZldGluLmFwaS52MS5Jbml0UmVxdWVzdBodLm9saXZldGluLmFwaS52MS5Jbml0UmVzcG9uc2UiABJpChBHZXRBY3Rpb25CaW5kaW5nEigub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkJpbmRpbmdSZXF1ZXN0Gikub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkJpbmRpbmdSZXNwb25zZSIAEloKC0dldEVudGl0aWVzEiMub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0aWVzUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1Jlc3BvbnNlIgASSQoJR2V0RW50aXR5EiEub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0eVJlcXVlc3QaFy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5IgBCOFo2Z2l0aHViLmNvbS9PbGl2ZVRpbi9PbGl2ZVRpbi9nZW4vb2xpdmV0aW4vYXBpL3YxO2FwaXYxYgZwcm90bzM");
+ fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSLcAQoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkiuwIKDkFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEdHlwZRgDIAEoCRIVCg1kZWZhdWx0X3ZhbHVlGAQgASgJEjYKB2Nob2ljZXMYBSADKAsyJS5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnRDaG9pY2USEwoLZGVzY3JpcHRpb24YBiABKAkSRQoLc3VnZ2VzdGlvbnMYByADKAsyMC5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQuU3VnZ2VzdGlvbnNFbnRyeRIfChdzdWdnZXN0aW9uc19icm93c2VyX2tleRgIIAEoCRoyChBTdWdnZXN0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNAoUQWN0aW9uQXJndW1lbnRDaG9pY2USDQoFdmFsdWUYASABKAkSDQoFdGl0bGUYAiABKAkisgEKBkVudGl0eRINCgV0aXRsZRgBIAEoCRISCgp1bmlxdWVfa2V5GAIgASgJEgwKBHR5cGUYAyABKAkSEwoLZGlyZWN0b3JpZXMYBCADKAkSMwoGZmllbGRzGAUgAygLMiMub2xpdmV0aW4uYXBpLnYxLkVudGl0eS5GaWVsZHNFbnRyeRotCgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIlQKFEdldERhc2hib2FyZFJlc3BvbnNlEg0KBXRpdGxlGAEgASgJEi0KCWRhc2hib2FyZBgEIAEoCzIaLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmQiQgoPRWZmZWN0aXZlUG9saWN5EhgKEHNob3dfZGlhZ25vc3RpY3MYASABKAgSFQoNc2hvd19sb2dfbGlzdBgCIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSJ9ChJTdGFydEFjdGlvblJlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCRI3Cglhcmd1bWVudHMYAiADKAsyJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25Bcmd1bWVudBIaChJ1bmlxdWVfdHJhY2tpbmdfaWQYAyABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJImcKGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IkoKGlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIsChdTdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiOQoYU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgCIAEoCSIzCh5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJIk8KH1N0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5Ik4KDkdldExvZ3NSZXF1ZXN0EhQKDHN0YXJ0X29mZnNldBgBIAEoAxITCgtkYXRlX2ZpbHRlchgCIAEoCRIRCglwYWdlX3NpemUYAyABKAMimgMKCExvZ0VudHJ5EhgKEGRhdGV0aW1lX3N0YXJ0ZWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEg4KBm91dHB1dBgDIAEoCRIRCgl0aW1lZF9vdXQYBSABKAgSEQoJZXhpdF9jb2RlGAYgASgFEgwKBHVzZXIYByABKAkSEgoKdXNlcl9jbGFzcxgIIAEoCRITCgthY3Rpb25faWNvbhgJIAEoCRIMCgR0YWdzGAogAygJEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgLIAEoCRIZChFkYXRldGltZV9maW5pc2hlZBgMIAEoCRIZChFleGVjdXRpb25fc3RhcnRlZBgOIAEoCBIaChJleGVjdXRpb25fZmluaXNoZWQYDyABKAgSDwoHYmxvY2tlZBgQIAEoCBIWCg5kYXRldGltZV9pbmRleBgRIAEoAxIQCghjYW5fa2lsbBgSIAEoCBIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYEyABKAkSEgoKYmluZGluZ19pZBgUIAEoCSKRAQoPR2V0TG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiPwoUR2V0QWN0aW9uTG9nc1JlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEhQKDHN0YXJ0X29mZnNldBgCIAEoAyKXAQoVR2V0QWN0aW9uTG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiZQobVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Eg0KBXZhbHVlGAEgASgJEgwKBHR5cGUYAiABKAkSEgoKYmluZGluZ19pZBgDIAEoCRIVCg1hcmd1bWVudF9uYW1lGAQgASgJIkIKHFZhbGlkYXRlQXJndW1lbnRUeXBlUmVzcG9uc2USDQoFdmFsaWQYASABKAgSEwoLZGVzY3JpcHRpb24YAiABKAkiNgoVV2F0Y2hFeGVjdXRpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCSImChRXYXRjaEV4ZWN1dGlvblVwZGF0ZRIOCgZ1cGRhdGUYASABKAkiSgoWRXhlY3V0aW9uU3RhdHVzUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSEQoJYWN0aW9uX2lkGAIgASgJIkcKF0V4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIPCg1XaG9BbUlSZXF1ZXN0ImwKDldob0FtSVJlc3BvbnNlEhoKEmF1dGhlbnRpY2F0ZWRfdXNlchgBIAEoCRIRCgl1c2VyZ3JvdXAYAiABKAkSEAoIcHJvdmlkZXIYAyABKAkSDAoEYWNscxgEIAMoCRILCgNzaWQYBSABKAkiEgoQU29zUmVwb3J0UmVxdWVzdCIiChFTb3NSZXBvcnRSZXNwb25zZRINCgVhbGVydBgBIAEoCSIRCg9EdW1wVmFyc1JlcXVlc3QilQEKEER1bXBWYXJzUmVzcG9uc2USDQoFYWxlcnQYASABKAkSQQoIY29udGVudHMYAiADKAsyLy5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZS5Db250ZW50c0VudHJ5Gi8KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI7CgxEZWJ1Z0JpbmRpbmcSFAoMYWN0aW9uX3RpdGxlGAEgASgJEhUKDWVudGl0eV9wcmVmaXgYAiABKAkiHgocRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdCLOAQodRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2USDQoFYWxlcnQYASABKAkSTgoIY29udGVudHMYAiADKAsyPC5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UuQ29udGVudHNFbnRyeRpOCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRIsCgV2YWx1ZRgCIAEoCzIdLm9saXZldGluLmFwaS52MS5EZWJ1Z0JpbmRpbmc6AjgBIhIKEEdldFJlYWR5elJlcXVlc3QiIwoRR2V0UmVhZHl6UmVzcG9uc2USDgoGc3RhdHVzGAEgASgJIhQKEkV2ZW50U3RyZWFtUmVxdWVzdCLjAgoTRXZlbnRTdHJlYW1SZXNwb25zZRI9Cg5lbnRpdHlfY2hhbmdlZBgCIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudEVudGl0eUNoYW5nZWRIABI9Cg5jb25maWdfY2hhbmdlZBgDIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudENvbmZpZ0NoYW5nZWRIABJFChJleGVjdXRpb25fZmluaXNoZWQYBCABKAsyJy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25GaW5pc2hlZEgAEkMKEWV4ZWN1dGlvbl9zdGFydGVkGAUgASgLMiYub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uU3RhcnRlZEgAEjkKDG91dHB1dF9jaHVuaxgGIAEoCzIhLm9saXZldGluLmFwaS52MS5FdmVudE91dHB1dENodW5rSABCBwoFZXZlbnQiQQoQRXZlbnRPdXRwdXRDaHVuaxIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGb3V0cHV0GAIgASgJIhQKEkV2ZW50RW50aXR5Q2hhbmdlZCIUChJFdmVudENvbmZpZ0NoYW5nZWQiRgoWRXZlbnRFeGVjdXRpb25GaW5pc2hlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiRQoVRXZlbnRFeGVjdXRpb25TdGFydGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIyChFLaWxsQWN0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkibQoSS2lsbEFjdGlvblJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZraWxsZWQYAiABKAgSGQoRYWxyZWFkeV9jb21wbGV0ZWQYAyABKAgSDQoFZm91bmQYBCABKAgiOwoVTG9jYWxVc2VyTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIikKFkxvY2FsVXNlckxvZ2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCInChNQYXNzd29yZEhhc2hSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJIiQKFFBhc3N3b3JkSGFzaFJlc3BvbnNlEgwKBGhhc2gYASABKAkiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSIXChVHZXREaWFnbm9zdGljc1JlcXVlc3QiRQoWR2V0RGlhZ25vc3RpY3NSZXNwb25zZRITCgtTc2hGb3VuZEtleRgBIAEoCRIWCg5Tc2hGb3VuZENvbmZpZxgCIAEoCSINCgtJbml0UmVxdWVzdCLrBQoMSW5pdFJlc3BvbnNlEhIKCnNob3dGb290ZXIYASABKAgSFgoOc2hvd05hdmlnYXRpb24YAiABKAgSFwoPc2hvd05ld1ZlcnNpb25zGAMgASgIEhgKEGF2YWlsYWJsZVZlcnNpb24YBCABKAkSFgoOY3VycmVudFZlcnNpb24YBSABKAkSEQoJcGFnZVRpdGxlGAYgASgJEh4KFnNlY3Rpb25OYXZpZ2F0aW9uU3R5bGUYByABKAkSGgoSZGVmYXVsdEljb25Gb3JCYWNrGAggASgJEhYKDmVuYWJsZUN1c3RvbUpzGAkgASgIEhQKDGF1dGhMb2dpblVybBgKIAEoCRIWCg5hdXRoTG9jYWxMb2dpbhgLIAEoCBIRCglzdHlsZU1vZHMYDCADKAkSOAoPb0F1dGgyUHJvdmlkZXJzGA0gAygLMh8ub2xpdmV0aW4uYXBpLnYxLk9BdXRoMlByb3ZpZGVyEjgKD2FkZGl0aW9uYWxMaW5rcxgOIAMoCzIfLm9saXZldGluLmFwaS52MS5BZGRpdGlvbmFsTGluaxIWCg5yb290RGFzaGJvYXJkcxgPIAMoCRIaChJhdXRoZW50aWNhdGVkX3VzZXIYECABKAkSIwobYXV0aGVudGljYXRlZF91c2VyX3Byb3ZpZGVyGBEgASgJEjoKEGVmZmVjdGl2ZV9wb2xpY3kYEiABKAsyIC5vbGl2ZXRpbi5hcGkudjEuRWZmZWN0aXZlUG9saWN5EhYKDmJhbm5lcl9tZXNzYWdlGBMgASgJEhIKCmJhbm5lcl9jc3MYFCABKAkSGAoQc2hvd19kaWFnbm9zdGljcxgVIAEoCBIVCg1zaG93X2xvZ19saXN0GBYgASgIEhYKDmxvZ2luX3JlcXVpcmVkGBcgASgIEhgKEGF2YWlsYWJsZV90aGVtZXMYGCADKAkSJAocc2hvd19uYXZpZ2F0ZV9vbl9zdGFydF9pY29ucxgZIAEoCCIsCg5BZGRpdGlvbmFsTGluaxINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkiOgoOT0F1dGgyUHJvdmlkZXISDQoFdGl0bGUYASABKAkSDAoEaWNvbhgDIAEoCRILCgNrZXkYBCABKAkiLQoXR2V0QWN0aW9uQmluZGluZ1JlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCSJDChhHZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2USJwoGYWN0aW9uGAEgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbiIUChJHZXRFbnRpdGllc1JlcXVlc3QiVAoTR2V0RW50aXRpZXNSZXNwb25zZRI9ChJlbnRpdHlfZGVmaW5pdGlvbnMYASADKAsyIS5vbGl2ZXRpbi5hcGkudjEuRW50aXR5RGVmaW5pdGlvbiJpChBFbnRpdHlEZWZpbml0aW9uEg0KBXRpdGxlGAEgASgJEioKCWluc3RhbmNlcxgCIAMoCzIXLm9saXZldGluLmFwaS52MS5FbnRpdHkSGgoSdXNlZF9vbl9kYXNoYm9hcmRzGAMgAygJIjQKEEdldEVudGl0eVJlcXVlc3QSEgoKdW5pcXVlX2tleRgBIAEoCRIMCgR0eXBlGAIgASgJIjUKFFJlc3RhcnRBY3Rpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCTLoEgoST2xpdmVUaW5BcGlTZXJ2aWNlEl0KDEdldERhc2hib2FyZBIkLm9saXZldGluLmFwaS52MS5HZXREYXNoYm9hcmRSZXF1ZXN0GiUub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlc3BvbnNlIgASWgoLU3RhcnRBY3Rpb24SIy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJvChJTdGFydEFjdGlvbkFuZFdhaXQSKi5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25BbmRXYWl0UmVxdWVzdBorLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXNwb25zZSIAEmkKEFN0YXJ0QWN0aW9uQnlHZXQSKC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlcXVlc3QaKS5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlIgASfgoXU3RhcnRBY3Rpb25CeUdldEFuZFdhaXQSLy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXF1ZXN0GjAub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2UiABJeCg1SZXN0YXJ0QWN0aW9uEiUub2xpdmV0aW4uYXBpLnYxLlJlc3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJXCgpLaWxsQWN0aW9uEiIub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXF1ZXN0GiMub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXNwb25zZSIAEmYKD0V4ZWN1dGlvblN0YXR1cxInLm9saXZldGluLmFwaS52MS5FeGVjdXRpb25TdGF0dXNSZXF1ZXN0Gigub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlIgASTgoHR2V0TG9ncxIfLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVxdWVzdBogLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVzcG9uc2UiABJgCg1HZXRBY3Rpb25Mb2dzEiUub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXF1ZXN0GiYub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXNwb25zZSIAEnUKFFZhbGlkYXRlQXJndW1lbnRUeXBlEiwub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVxdWVzdBotLm9saXZldGluLmFwaS52MS5WYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlIgASSwoGV2hvQW1JEh4ub2xpdmV0aW4uYXBpLnYxLldob0FtSVJlcXVlc3QaHy5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVzcG9uc2UiABJUCglTb3NSZXBvcnQSIS5vbGl2ZXRpbi5hcGkudjEuU29zUmVwb3J0UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXNwb25zZSIAElEKCER1bXBWYXJzEiAub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVxdWVzdBohLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlIgASeAoVRHVtcFB1YmxpY0lkQWN0aW9uTWFwEi0ub2xpdmV0aW4uYXBpLnYxLkR1bXBQdWJsaWNJZEFjdGlvbk1hcFJlcXVlc3QaLi5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UiABJUCglHZXRSZWFkeXoSIS5vbGl2ZXRpbi5hcGkudjEuR2V0UmVhZHl6UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXNwb25zZSIAEmMKDkxvY2FsVXNlckxvZ2luEiYub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5Mb2NhbFVzZXJMb2dpblJlc3BvbnNlIgASXQoMUGFzc3dvcmRIYXNoEiQub2xpdmV0aW4uYXBpLnYxLlBhc3N3b3JkSGFzaFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVzcG9uc2UiABJLCgZMb2dvdXQSHi5vbGl2ZXRpbi5hcGkudjEuTG9nb3V0UmVxdWVzdBofLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXNwb25zZSIAElwKC0V2ZW50U3RyZWFtEiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5FdmVudFN0cmVhbVJlc3BvbnNlIgAwARJjCg5HZXREaWFnbm9zdGljcxImLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1JlcXVlc3QaJy5vbGl2ZXRpbi5hcGkudjEuR2V0RGlhZ25vc3RpY3NSZXNwb25zZSIAEkUKBEluaXQSHC5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlcXVlc3QaHS5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlc3BvbnNlIgASaQoQR2V0QWN0aW9uQmluZGluZxIoLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBopLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2UiABJaCgtHZXRFbnRpdGllcxIjLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1JlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXRpZXNSZXNwb25zZSIAEkkKCUdldEVudGl0eRIhLm9saXZldGluLmFwaS52MS5HZXRFbnRpdHlSZXF1ZXN0Ghcub2xpdmV0aW4uYXBpLnYxLkVudGl0eSIAQjhaNmdpdGh1Yi5jb20vT2xpdmVUaW4vT2xpdmVUaW4vZ2VuL29saXZldGluL2FwaS92MTthcGl2MWIGcHJvdG8z");
/**
* Describes the message olivetin.api.v1.Action.
@@ -491,3 +491,4 @@ export const RestartActionRequestSchema = /*@__PURE__*/
*/
export const OliveTinApiService = /*@__PURE__*/
serviceDesc(file_olivetin_api_v1_olivetin, 0);
+
diff --git a/frontend/resources/vue/views/ActionDetailsView.vue b/frontend/resources/vue/views/ActionDetailsView.vue
index 7b51f9d..9d8b31e 100644
--- a/frontend/resources/vue/views/ActionDetailsView.vue
+++ b/frontend/resources/vue/views/ActionDetailsView.vue
@@ -78,7 +78,7 @@
-
diff --git a/frontend/resources/vue/views/LogsListView.vue b/frontend/resources/vue/views/LogsListView.vue
index b1a93b5..e6278f0 100644
--- a/frontend/resources/vue/views/LogsListView.vue
+++ b/frontend/resources/vue/views/LogsListView.vue
@@ -68,7 +68,7 @@
-
@@ -150,6 +150,7 @@ async function fetchLogs() {
const args = {
"startOffset": BigInt(startOffset),
+ "pageSize": BigInt(pageSize.value),
}
// Add date filter if selected
@@ -160,7 +161,6 @@ async function fetchLogs() {
const response = await window.client.getLogs(args)
logs.value = response.logs
- pageSize.value = Number(response.pageSize) || 0
totalCount.value = Number(response.totalCount) || 0
} catch (err) {
console.error('Failed to fetch logs:', err)
diff --git a/proto/olivetin/api/v1/olivetin.proto b/proto/olivetin/api/v1/olivetin.proto
index cc12212..8554f6b 100644
--- a/proto/olivetin/api/v1/olivetin.proto
+++ b/proto/olivetin/api/v1/olivetin.proto
@@ -121,6 +121,7 @@ message StartActionByGetAndWaitResponse {
message GetLogsRequest{
int64 start_offset = 1;
string date_filter = 2; // Optional date filter in YYYY-MM-DD format
+ int64 page_size = 3; // Number of logs per page (optional; server default used if 0 or unset)
};
message LogEntry {
diff --git a/service/gen/olivetin/api/v1/olivetin.pb.go b/service/gen/olivetin/api/v1/olivetin.pb.go
index 5614c01..682ca2c 100644
--- a/service/gen/olivetin/api/v1/olivetin.pb.go
+++ b/service/gen/olivetin/api/v1/olivetin.pb.go
@@ -1105,6 +1105,7 @@ type GetLogsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
StartOffset int64 `protobuf:"varint,1,opt,name=start_offset,json=startOffset,proto3" json:"start_offset,omitempty"`
DateFilter string `protobuf:"bytes,2,opt,name=date_filter,json=dateFilter,proto3" json:"date_filter,omitempty"` // Optional date filter in YYYY-MM-DD format
+ PageSize int64 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // Number of logs per page (optional; server default used if 0 or unset)
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -1153,6 +1154,13 @@ func (x *GetLogsRequest) GetDateFilter() string {
return ""
}
+func (x *GetLogsRequest) GetPageSize() int64 {
+ if x != nil {
+ return x.PageSize
+ }
+ return 0
+}
+
type LogEntry struct {
state protoimpl.MessageState `protogen:"open.v1"`
DatetimeStarted string `protobuf:"bytes,1,opt,name=datetime_started,json=datetimeStarted,proto3" json:"datetime_started,omitempty"`
@@ -3972,11 +3980,12 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" +
"\x1eStartActionByGetAndWaitRequest\x12\x1b\n" +
"\taction_id\x18\x01 \x01(\tR\bactionId\"Y\n" +
"\x1fStartActionByGetAndWaitResponse\x126\n" +
- "\tlog_entry\x18\x01 \x01(\v2\x19.olivetin.api.v1.LogEntryR\blogEntry\"T\n" +
+ "\tlog_entry\x18\x01 \x01(\v2\x19.olivetin.api.v1.LogEntryR\blogEntry\"q\n" +
"\x0eGetLogsRequest\x12!\n" +
"\fstart_offset\x18\x01 \x01(\x03R\vstartOffset\x12\x1f\n" +
"\vdate_filter\x18\x02 \x01(\tR\n" +
- "dateFilter\"\x89\x05\n" +
+ "dateFilter\x12\x1b\n" +
+ "\tpage_size\x18\x03 \x01(\x03R\bpageSize\"\x89\x05\n" +
"\bLogEntry\x12)\n" +
"\x10datetime_started\x18\x01 \x01(\tR\x0fdatetimeStarted\x12!\n" +
"\faction_title\x18\x02 \x01(\tR\vactionTitle\x12\x16\n" +
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index faf58d1..68ef978 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -506,7 +506,16 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetL
if req.Msg.DateFilter != "" {
dateFilter = req.Msg.DateFilter
}
- logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, api.cfg.LogHistoryPageSize, dateFilter)
+ pageSize := api.cfg.LogHistoryPageSize
+ if req.Msg.GetPageSize() > 0 {
+ pageSize = req.Msg.GetPageSize()
+ if pageSize < 10 {
+ pageSize = 10
+ } else if pageSize > 100 {
+ pageSize = 100
+ }
+ }
+ logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, pageSize, dateFilter)
for _, le := range logEntries {
ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
}
diff --git a/service/internal/api/dashboard_entities.go b/service/internal/api/dashboard_entities.go
index 7bec464..2ddf964 100644
--- a/service/internal/api/dashboard_entities.go
+++ b/service/internal/api/dashboard_entities.go
@@ -57,7 +57,7 @@ func buildEntityFieldsetContents(contents []*config.DashboardComponent, ent *ent
for _, subitem := range contents {
c := cloneItem(subitem, ent, entityType, rr)
- log.Infof("cloneItem: %+v", c)
+ log.Tracef("cloneItem: %+v", c)
if c != nil {
ret = append(ret, c)
From 4bbd2eab153287dc744ad061c58af7693f0c3ddc Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 22 Feb 2026 10:19:08 +0000
Subject: [PATCH 008/148] security: GHSA-49gm-hh7w-wfvf
---
SECURITY.md | 29 +++++-
service/internal/executor/arguments.go | 2 +-
service/internal/executor/arguments_test.go | 34 +++++++
service/internal/executor/executor.go | 27 ++++++
service/internal/executor/executor_test.go | 100 ++++++++++++++++++++
service/internal/webhooks/handler.go | 17 +++-
service/internal/webhooks/handler_test.go | 32 +++++++
7 files changed, 236 insertions(+), 5 deletions(-)
create mode 100644 service/internal/webhooks/handler_test.go
diff --git a/SECURITY.md b/SECURITY.md
index f0a4010..df1c506 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -2,12 +2,35 @@
## Supported Versions
-Currently, only the `main` branch is "supported".
+The following branches are currently being supported with security updates:
| Version | Supported |
| ------- | ------------------ |
-| `main` | :white_check_mark: |
+| `main` (3k release branch) | :white_check_mark: |
+| `release/2k` (2k release branch) | :white_check_mark: |
+
+To understand more about 2k vs 3k, see the following docs; https://docs.olivetin.app/upgrade/2k3k.html
+
+## OliveTin *is* a remote code execution (RCE) "tool"
+
+The very purpose of OliveTin is to allow users to execute commands remotely on a machine.
+
+This means that, by design, OliveTin has might higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occour have the potential to be much more severe than in other types of software.
+
+We hope that you understand that while the project goes to great aims to be safe, and mitigate, that security vulnerabilities are inevitable, as they are with all software of all sizes - like Kubernetes, the Kernel, etc - and OliveTin has substancially less resources than those projects.
+
+With that being said, OliveTin tries to follow examples of best practice, so judge the project not on if/when it has security issues, but how security issues are responded to as the measure of quality.
+
+This is why we take security very seriously, and why we encourage responsible disclosure practices when reporting vulnerabilities.
## Reporting a Vulnerability
-Please email `contact@jread.com` for responsible disclosure. Accepted issues will be made public once patched, and you will be given credit.
+Please use responsible disclosure practices when reporting a vulnerability. **You will receive full credit for your discovery**, and we will work with you to ensure that the issue is resolved as quickly as **possible**. Please note that only James Read has access to security issues at the moment, so please be patient and understanding if you do not receive an immediate response.
+
+* **Option A (preferred)**: GitHub Security Advisories, which allows you to report a vulnerability privately and securely. You can find the option to report a security issue in the "Issues" tab of this repository, and then select "Report a security vulnerability". This will allow you to provide details about the vulnerability without making it public.
+
+* **Option B**: Please email `contact@jread.com` for responsible disclosure.
+
+## Disclosure of how vulnerabilities were found
+
+It is incredibly useful to not just patch security vulnerabilities, but also to understand how they were found. If you are able to share this information, it can help us and the community to better understand potential attack vectors and improve the overall security of the project.
diff --git a/service/internal/executor/arguments.go b/service/internal/executor/arguments.go
index a63eb0b..655ecc4 100644
--- a/service/internal/executor/arguments.go
+++ b/service/internal/executor/arguments.go
@@ -310,7 +310,7 @@ func checkShellArgumentSafety(action *config.Action) error {
if action.Shell == "" {
return nil
}
- unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}}
+ unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}, "password": {}}
for _, arg := range action.Arguments {
if _, bad := unsafe[arg.Type]; bad {
return fmt.Errorf("unsafe argument type '%s' cannot be used with Shell execution. Use 'exec' instead. See https://docs.olivetin.app/action_execution/shellvsexec.html", arg.Type)
diff --git a/service/internal/executor/arguments_test.go b/service/internal/executor/arguments_test.go
index 877b10c..8cf03a9 100644
--- a/service/internal/executor/arguments_test.go
+++ b/service/internal/executor/arguments_test.go
@@ -302,6 +302,40 @@ func TestCheckShellArgumentSafetyWithSafeTypes(t *testing.T) {
assert.Nil(t, err)
}
+func TestCheckShellArgumentSafetyWithPassword(t *testing.T) {
+ a1 := config.Action{
+ Title: "Auth with password",
+ Shell: "somecommand --password '{{password}}'",
+ Arguments: []config.ActionArgument{
+ {
+ Name: "password",
+ Type: "password",
+ },
+ },
+ }
+
+ err := checkShellArgumentSafety(&a1)
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "unsafe argument type 'password' cannot be used with Shell execution")
+ assert.Contains(t, err.Error(), "https://docs.olivetin.app/action_execution/shellvsexec.html")
+}
+
+func TestCheckShellArgumentSafetyWithPasswordAndExec(t *testing.T) {
+ a1 := config.Action{
+ Title: "Auth with password via exec",
+ Exec: []string{"somecommand", "--password", "{{password}}"},
+ Arguments: []config.ActionArgument{
+ {
+ Name: "password",
+ Type: "password",
+ },
+ },
+ }
+
+ err := checkShellArgumentSafety(&a1)
+ assert.Nil(t, err)
+}
+
func TestTypeSafetyCheckUrl(t *testing.T) {
assert.Nil(t, TypeSafetyCheck("test1", "http://google.com", "url"), "Test URL: google.com")
assert.Nil(t, TypeSafetyCheck("test2", "http://technowax.net:80?foo=bar", "url"), "Test URL: technowax.net with query arguments")
diff --git a/service/internal/executor/executor.go b/service/internal/executor/executor.go
index 274d4b1..5fe2dec 100644
--- a/service/internal/executor/executor.go
+++ b/service/internal/executor/executor.go
@@ -664,6 +664,7 @@ func stepParseArgs(req *ExecutionRequest) bool {
return fail(req, fmt.Errorf("cannot parse arguments: Binding or Action is nil"))
}
+ filterToDefinedArgumentsOnly(req)
mangleInvalidArgumentValues(req)
if hasExec(req) {
@@ -686,6 +687,9 @@ func handleExecBranch(req *ExecutionRequest) bool {
}
func handleShellBranch(req *ExecutionRequest) bool {
+ if hasWebhookTag(req) {
+ return fail(req, fmt.Errorf("webhooks cannot use Shell execution; use exec instead. See https://docs.olivetin.app/action_execution/shellvsexec.html"))
+ }
if err := checkShellArgumentSafety(req.Binding.Action); err != nil {
return fail(req, err)
}
@@ -707,6 +711,29 @@ func ensureArgumentMap(req *ExecutionRequest) {
}
}
+func filterToDefinedArgumentsOnly(req *ExecutionRequest) {
+ definedNames := make(map[string]struct{})
+ for _, arg := range req.Binding.Action.Arguments {
+ definedNames[arg.Name] = struct{}{}
+ }
+ filtered := make(map[string]string)
+ for k, v := range req.Arguments {
+ if _, ok := definedNames[k]; ok || strings.HasPrefix(k, "ot_") {
+ filtered[k] = v
+ }
+ }
+ req.Arguments = filtered
+}
+
+func hasWebhookTag(req *ExecutionRequest) bool {
+ for _, tag := range req.Tags {
+ if tag == "webhook" {
+ return true
+ }
+ }
+ return false
+}
+
func injectSystemArgs(req *ExecutionRequest) {
req.Arguments["ot_executionTrackingId"] = req.TrackingID
req.Arguments["ot_username"] = req.AuthenticatedUser.Username
diff --git a/service/internal/executor/executor_test.go b/service/internal/executor/executor_test.go
index f9efefe..00a4535 100644
--- a/service/internal/executor/executor_test.go
+++ b/service/internal/executor/executor_test.go
@@ -295,3 +295,103 @@ func TestMangleInvalidArgumentValues(t *testing.T) {
assert.Equal(t, req.logEntry.Output, "The date is: 1990-01-10T12:00:00\n", "Date should be mangled to a valid format")
}
+
+func TestWebhookRejectsShellExecution(t *testing.T) {
+ cfg := config.DefaultConfig()
+ e := DefaultExecutor(cfg)
+ a1 := &config.Action{
+ Title: "Webhook Shell Reject",
+ Shell: "echo '{{ msg }}'",
+ Arguments: []config.ActionArgument{
+ {Name: "msg", Type: "ascii"},
+ },
+ }
+ cfg.Actions = append(cfg.Actions, a1)
+ cfg.Sanitize()
+ e.RebuildActionMap()
+
+ req := ExecutionRequest{
+ Tags: []string{"webhook"},
+ AuthenticatedUser: auth.UserFromSystem(cfg, "webhook"),
+ Cfg: cfg,
+ Arguments: map[string]string{"msg": "hello"},
+ Binding: e.FindBindingWithNoEntity(a1),
+ }
+
+ wg, _ := e.ExecRequest(&req)
+ wg.Wait()
+
+ assert.NotNil(t, req.logEntry)
+ assert.Equal(t, int32(-1337), req.logEntry.ExitCode)
+ assert.Contains(t, req.logEntry.Output, "webhooks cannot use Shell execution")
+}
+
+func TestWebhookAllowsExecExecution(t *testing.T) {
+ cfg := config.DefaultConfig()
+ e := DefaultExecutor(cfg)
+ a1 := &config.Action{
+ Title: "Webhook Exec OK",
+ Exec: []string{"echo", "{{ msg }}"},
+ Arguments: []config.ActionArgument{
+ {Name: "msg", Type: "ascii"},
+ },
+ }
+ cfg.Actions = append(cfg.Actions, a1)
+ cfg.Sanitize()
+ e.RebuildActionMap()
+
+ req := ExecutionRequest{
+ Tags: []string{"webhook"},
+ AuthenticatedUser: auth.UserFromSystem(cfg, "webhook"),
+ Cfg: cfg,
+ Arguments: map[string]string{"msg": "hello"},
+ Binding: e.FindBindingWithNoEntity(a1),
+ }
+
+ wg, _ := e.ExecRequest(&req)
+ wg.Wait()
+
+ assert.NotNil(t, req.logEntry)
+ assert.Equal(t, int32(0), req.logEntry.ExitCode)
+ assert.Contains(t, req.logEntry.Output, "hello")
+}
+
+func TestFilterToDefinedArgumentsOnly(t *testing.T) {
+ req := newExecRequest()
+ req.Binding.Action = &config.Action{
+ Title: "Filter test",
+ Shell: "echo '{{ name }}'",
+ Arguments: []config.ActionArgument{
+ {Name: "name", Type: "ascii"},
+ },
+ }
+ req.Arguments = map[string]string{
+ "name": "Alice",
+ "webhook_path": "/malicious/$(id)",
+ "extra_undefined": "ignored",
+ }
+
+ filterToDefinedArgumentsOnly(req)
+
+ assert.Equal(t, "Alice", req.Arguments["name"])
+ assert.Empty(t, req.Arguments["webhook_path"])
+ assert.Empty(t, req.Arguments["extra_undefined"])
+}
+
+func TestFilterToDefinedArgumentsPreservesSystemArgs(t *testing.T) {
+ req := newExecRequest()
+ req.Binding.Action = &config.Action{
+ Title: "Filter test",
+ Shell: "echo test",
+ Arguments: []config.ActionArgument{},
+ }
+ req.Arguments = map[string]string{
+ "ot_executionTrackingId": "track-123",
+ "ot_username": "webhook",
+ }
+
+ filterToDefinedArgumentsOnly(req)
+
+ assert.Equal(t, "track-123", req.Arguments["ot_executionTrackingId"])
+ assert.Equal(t, "webhook", req.Arguments["ot_username"])
+}
diff --git a/service/internal/webhooks/handler.go b/service/internal/webhooks/handler.go
index a530690..92cdd8a 100644
--- a/service/internal/webhooks/handler.go
+++ b/service/internal/webhooks/handler.go
@@ -150,13 +150,28 @@ func (h *WebhookHandler) executeAction(action *config.Action, args map[string]st
return
}
+ definedArgs := filterToDefinedArguments(args, action)
req := &executor.ExecutionRequest{
Binding: binding,
Cfg: h.cfg,
Tags: []string{"webhook"},
- Arguments: args,
+ Arguments: definedArgs,
AuthenticatedUser: auth.UserFromSystem(h.cfg, "webhook"),
}
h.executor.ExecRequest(req)
}
+
+func filterToDefinedArguments(args map[string]string, action *config.Action) map[string]string {
+ definedNames := make(map[string]struct{})
+ for _, arg := range action.Arguments {
+ definedNames[arg.Name] = struct{}{}
+ }
+ filtered := make(map[string]string)
+ for k, v := range args {
+ if _, ok := definedNames[k]; ok {
+ filtered[k] = v
+ }
+ }
+ return filtered
+}
diff --git a/service/internal/webhooks/handler_test.go b/service/internal/webhooks/handler_test.go
new file mode 100644
index 0000000..48a2108
--- /dev/null
+++ b/service/internal/webhooks/handler_test.go
@@ -0,0 +1,32 @@
+package webhooks
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ config "github.com/OliveTin/OliveTin/internal/config"
+)
+
+func TestFilterToDefinedArguments(t *testing.T) {
+ action := &config.Action{
+ Arguments: []config.ActionArgument{
+ {Name: "repo", Type: "ascii_identifier"},
+ {Name: "branch", Type: "ascii_identifier"},
+ },
+ }
+ args := map[string]string{
+ "repo": "my-repo",
+ "branch": "main",
+ "webhook_path": "/deploy/prod",
+ "webhook_header_x_custom": "malicious",
+ }
+
+ filtered := filterToDefinedArguments(args, action)
+
+ assert.Equal(t, "my-repo", filtered["repo"])
+ assert.Equal(t, "main", filtered["branch"])
+ assert.Empty(t, filtered["webhook_path"])
+ assert.Empty(t, filtered["webhook_header_x_custom"])
+ assert.Len(t, filtered, 2)
+}
From c3028bb3b52f24919ca536a7c1b646b126c3aff9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 23 Feb 2026 05:52:28 +0000
Subject: [PATCH 009/148] chore(deps-dev): bump selenium-webdriver in
/integration-tests
Bumps [selenium-webdriver](https://github.com/SeleniumHQ/selenium) from 4.40.0 to 4.41.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.40.0...selenium-4.41.0)
---
updated-dependencies:
- dependency-name: selenium-webdriver
dependency-version: 4.41.0
dependency-type: direct:development
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
integration-tests/package-lock.json | 16 ++++++++--------
integration-tests/package.json | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json
index 12d651e..c7657ba 100644
--- a/integration-tests/package-lock.json
+++ b/integration-tests/package-lock.json
@@ -15,7 +15,7 @@
"chai": "^6.2.2",
"eslint": "^9.39.2",
"mocha": "^11.7.5",
- "selenium-webdriver": "^4.40.0"
+ "selenium-webdriver": "^4.41.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -1954,9 +1954,9 @@
"dev": true
},
"node_modules/selenium-webdriver": {
- "version": "4.40.0",
- "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.40.0.tgz",
- "integrity": "sha512-dU0QbnVKdPmoNP8OtMCazRdtU2Ux6Wl4FEpG1iwUbDeajJK1dBAywBLrC1D7YFRtogHzN96AbXBgBAJaarcysw==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.41.0.tgz",
+ "integrity": "sha512-1XxuKVhr9az24xwixPBEDGSZP+P0z3ZOnCmr9Oiep0MlJN2Mk+flIjD3iBS9BgyjS4g14dikMqnrYUPIjhQBhA==",
"dev": true,
"funding": [
{
@@ -1973,7 +1973,7 @@
"@bazel/runfiles": "^6.5.0",
"jszip": "^3.10.1",
"tmp": "^0.2.5",
- "ws": "^8.18.3"
+ "ws": "^8.19.0"
},
"engines": {
"node": ">= 20.0.0"
@@ -2347,9 +2347,9 @@
}
},
"node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/integration-tests/package.json b/integration-tests/package.json
index 21bbcc8..ff5de66 100644
--- a/integration-tests/package.json
+++ b/integration-tests/package.json
@@ -14,7 +14,7 @@
"chai": "^6.2.2",
"eslint": "^9.39.2",
"mocha": "^11.7.5",
- "selenium-webdriver": "^4.40.0"
+ "selenium-webdriver": "^4.41.0"
},
"dependencies": {
"wait-on": "^9.0.4"
From c4a8eadd3fa17f5d37a5f0f72dd30943674f7ea3 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 25 Feb 2026 23:15:57 +0000
Subject: [PATCH 010/148] chore: Fix broken test configs
---
integration-tests/tests/checkbox/config.yaml | 1 +
integration-tests/tests/datetime/config.yaml | 1 +
integration-tests/tests/suggestionsBrowserKey/config.yaml | 1 +
3 files changed, 3 insertions(+)
diff --git a/integration-tests/tests/checkbox/config.yaml b/integration-tests/tests/checkbox/config.yaml
index 3ca7060..589bb7b 100644
--- a/integration-tests/tests/checkbox/config.yaml
+++ b/integration-tests/tests/checkbox/config.yaml
@@ -3,6 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG"
checkForUpdates: false
+popupOnStart: execution-dialog
actions:
- title: Test checkbox argument
diff --git a/integration-tests/tests/datetime/config.yaml b/integration-tests/tests/datetime/config.yaml
index 8647e15..d657cd2 100644
--- a/integration-tests/tests/datetime/config.yaml
+++ b/integration-tests/tests/datetime/config.yaml
@@ -3,6 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG"
checkForUpdates: false
+popupOnStart: execution-dialog
actions:
- title: Test datetime argument
diff --git a/integration-tests/tests/suggestionsBrowserKey/config.yaml b/integration-tests/tests/suggestionsBrowserKey/config.yaml
index eb2c1df..2bd5876 100644
--- a/integration-tests/tests/suggestionsBrowserKey/config.yaml
+++ b/integration-tests/tests/suggestionsBrowserKey/config.yaml
@@ -3,6 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG"
checkForUpdates: false
+popupOnStart: execution-dialog
actions:
- title: Test suggestionsBrowserKey
From 38d6b57077028300a0c9bee9176a75dd4a44ba83 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 25 Feb 2026 23:24:02 +0000
Subject: [PATCH 011/148] chore: codefmt
---
service/internal/executor/executor.go | 7 +++++-
service/internal/executor/executor_test.go | 10 ++++----
service/internal/tpl/templates_test.go | 27 +++++++++++++---------
service/internal/webhooks/handler_test.go | 6 ++---
4 files changed, 30 insertions(+), 20 deletions(-)
diff --git a/service/internal/executor/executor.go b/service/internal/executor/executor.go
index 5fe2dec..694cc9b 100644
--- a/service/internal/executor/executor.go
+++ b/service/internal/executor/executor.go
@@ -718,13 +718,18 @@ func filterToDefinedArgumentsOnly(req *ExecutionRequest) {
}
filtered := make(map[string]string)
for k, v := range req.Arguments {
- if _, ok := definedNames[k]; ok || strings.HasPrefix(k, "ot_") {
+ if keepArgument(k, definedNames) {
filtered[k] = v
}
}
req.Arguments = filtered
}
+func keepArgument(name string, definedNames map[string]struct{}) bool {
+ _, ok := definedNames[name]
+ return ok || strings.HasPrefix(name, "ot_")
+}
+
func hasWebhookTag(req *ExecutionRequest) bool {
for _, tag := range req.Tags {
if tag == "webhook" {
diff --git a/service/internal/executor/executor_test.go b/service/internal/executor/executor_test.go
index 00a4535..2608bbe 100644
--- a/service/internal/executor/executor_test.go
+++ b/service/internal/executor/executor_test.go
@@ -366,8 +366,8 @@ func TestFilterToDefinedArgumentsOnly(t *testing.T) {
},
}
req.Arguments = map[string]string{
- "name": "Alice",
- "webhook_path": "/malicious/$(id)",
+ "name": "Alice",
+ "webhook_path": "/malicious/$(id)",
"extra_undefined": "ignored",
}
@@ -381,13 +381,13 @@ func TestFilterToDefinedArgumentsOnly(t *testing.T) {
func TestFilterToDefinedArgumentsPreservesSystemArgs(t *testing.T) {
req := newExecRequest()
req.Binding.Action = &config.Action{
- Title: "Filter test",
- Shell: "echo test",
+ Title: "Filter test",
+ Shell: "echo test",
Arguments: []config.ActionArgument{},
}
req.Arguments = map[string]string{
"ot_executionTrackingId": "track-123",
- "ot_username": "webhook",
+ "ot_username": "webhook",
}
filterToDefinedArgumentsOnly(req)
diff --git a/service/internal/tpl/templates_test.go b/service/internal/tpl/templates_test.go
index d46f902..b49fe33 100644
--- a/service/internal/tpl/templates_test.go
+++ b/service/internal/tpl/templates_test.go
@@ -62,20 +62,25 @@ func TestParseTemplateWithActionContext_Json(t *testing.T) {
}
assert.NoError(t, err)
if tt.checkJsonOnly {
- prefix := strings.TrimSuffix(tt.expectedOutput, " ")
- assert.True(t, strings.HasPrefix(output, prefix), "output %q should start with %q", output, prefix)
- jsonPart := strings.TrimPrefix(output, prefix)
- jsonPart = strings.TrimSpace(jsonPart)
- var decoded map[string]string
- err := json.Unmarshal([]byte(jsonPart), &decoded)
- assert.NoError(t, err)
- for k, v := range tt.args {
- assert.Equal(t, v, decoded[k], "decoded JSON should contain %s=%s", k, v)
- }
- assert.Len(t, decoded, len(tt.args))
+ assertJsonOutput(t, output, tt.expectedOutput, tt.args)
} else {
assert.Equal(t, tt.expectedOutput, output)
}
})
}
}
+
+func assertJsonOutput(t *testing.T, output, expectedPrefix string, args map[string]string) {
+ t.Helper()
+ prefix := strings.TrimSuffix(expectedPrefix, " ")
+ assert.True(t, strings.HasPrefix(output, prefix), "output %q should start with %q", output, prefix)
+ jsonPart := strings.TrimPrefix(output, prefix)
+ jsonPart = strings.TrimSpace(jsonPart)
+ var decoded map[string]string
+ err := json.Unmarshal([]byte(jsonPart), &decoded)
+ assert.NoError(t, err)
+ for k, v := range args {
+ assert.Equal(t, v, decoded[k], "decoded JSON should contain %s=%s", k, v)
+ }
+ assert.Len(t, decoded, len(args))
+}
diff --git a/service/internal/webhooks/handler_test.go b/service/internal/webhooks/handler_test.go
index 48a2108..30831aa 100644
--- a/service/internal/webhooks/handler_test.go
+++ b/service/internal/webhooks/handler_test.go
@@ -16,9 +16,9 @@ func TestFilterToDefinedArguments(t *testing.T) {
},
}
args := map[string]string{
- "repo": "my-repo",
- "branch": "main",
- "webhook_path": "/deploy/prod",
+ "repo": "my-repo",
+ "branch": "main",
+ "webhook_path": "/deploy/prod",
"webhook_header_x_custom": "malicious",
}
From 10294e203038b8a2817efdb005eeeb2e1ddc9990 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 25 Feb 2026 23:59:49 +0000
Subject: [PATCH 012/148] chore: type in SECURITY.md
---
SECURITY.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SECURITY.md b/SECURITY.md
index df1c506..f42ff45 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -15,7 +15,7 @@ To understand more about 2k vs 3k, see the following docs; https://docs.olivetin
The very purpose of OliveTin is to allow users to execute commands remotely on a machine.
-This means that, by design, OliveTin has might higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occour have the potential to be much more severe than in other types of software.
+This means that, by design, OliveTin has much higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occur have the potential to be much more severe than in other types of software.
We hope that you understand that while the project goes to great aims to be safe, and mitigate, that security vulnerabilities are inevitable, as they are with all software of all sizes - like Kubernetes, the Kernel, etc - and OliveTin has substancially less resources than those projects.
From 5a0d94700c87aa967a9e74ef8ffbc556178a1615 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 00:01:00 +0000
Subject: [PATCH 013/148] chore: typo in SECURITY.md
---
SECURITY.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SECURITY.md b/SECURITY.md
index f42ff45..2558809 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -17,7 +17,7 @@ The very purpose of OliveTin is to allow users to execute commands remotely on a
This means that, by design, OliveTin has much higher potential to be used for remote code execution (RCE), and any security vulnerabilities that do occur have the potential to be much more severe than in other types of software.
-We hope that you understand that while the project goes to great aims to be safe, and mitigate, that security vulnerabilities are inevitable, as they are with all software of all sizes - like Kubernetes, the Kernel, etc - and OliveTin has substancially less resources than those projects.
+We hope that you understand that while the project goes to great aims to be safe, and mitigate, that security vulnerabilities are inevitable, as they are with all software of all sizes - like Kubernetes, the Kernel, etc - and OliveTin has substantially less resources than those projects.
With that being said, OliveTin tries to follow examples of best practice, so judge the project not on if/when it has security issues, but how security issues are responded to as the measure of quality.
From 87f2a3287b295716a2b994a3faa34416930e5ed8 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 00:04:14 +0000
Subject: [PATCH 014/148] chore: invalid key in test config
---
integration-tests/tests/checkbox/config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/integration-tests/tests/checkbox/config.yaml b/integration-tests/tests/checkbox/config.yaml
index 589bb7b..2afc362 100644
--- a/integration-tests/tests/checkbox/config.yaml
+++ b/integration-tests/tests/checkbox/config.yaml
@@ -3,7 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG"
checkForUpdates: false
-popupOnStart: execution-dialog
+defaultPopupOnStart: execution-dialog
actions:
- title: Test checkbox argument
From 4e2fdc2d9686d7cfc265fd4428352d45004422df Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 00:11:21 +0000
Subject: [PATCH 015/148] chore: fix security advisory link
---
SECURITY.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SECURITY.md b/SECURITY.md
index 2558809..cc1a212 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -27,7 +27,7 @@ This is why we take security very seriously, and why we encourage responsible di
Please use responsible disclosure practices when reporting a vulnerability. **You will receive full credit for your discovery**, and we will work with you to ensure that the issue is resolved as quickly as **possible**. Please note that only James Read has access to security issues at the moment, so please be patient and understanding if you do not receive an immediate response.
-* **Option A (preferred)**: GitHub Security Advisories, which allows you to report a vulnerability privately and securely. You can find the option to report a security issue in the "Issues" tab of this repository, and then select "Report a security vulnerability". This will allow you to provide details about the vulnerability without making it public.
+* **Option A (preferred)**: GitHub Security Advisories, which allows you to report a vulnerability privately and securely. Use this direct link to report privately: `https://github.com/OliveTin/OliveTin/security/advisories/new`. This allows you to provide details without making them public.
* **Option B**: Please email `contact@jread.com` for responsible disclosure.
From ad479651b7287c33da1c1d33bd507f824ecdf823 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 00:33:21 +0000
Subject: [PATCH 016/148] chore: Fix broken test configs
---
integration-tests/tests/datetime/config.yaml | 2 +-
integration-tests/tests/suggestionsBrowserKey/config.yaml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/integration-tests/tests/datetime/config.yaml b/integration-tests/tests/datetime/config.yaml
index d657cd2..7e3c38b 100644
--- a/integration-tests/tests/datetime/config.yaml
+++ b/integration-tests/tests/datetime/config.yaml
@@ -3,7 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG"
checkForUpdates: false
-popupOnStart: execution-dialog
+defaultPopupOnStart: execution-dialog
actions:
- title: Test datetime argument
diff --git a/integration-tests/tests/suggestionsBrowserKey/config.yaml b/integration-tests/tests/suggestionsBrowserKey/config.yaml
index 2bd5876..957d187 100644
--- a/integration-tests/tests/suggestionsBrowserKey/config.yaml
+++ b/integration-tests/tests/suggestionsBrowserKey/config.yaml
@@ -3,7 +3,7 @@ listenAddressSingleHTTPFrontend: 0.0.0.0:1337
logLevel: "DEBUG"
checkForUpdates: false
-popupOnStart: execution-dialog
+defaultPopupOnStart: execution-dialog
actions:
- title: Test suggestionsBrowserKey
From 1335302e8008443508eaa57a937a31d2d9ba17b2 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 00:48:21 +0000
Subject: [PATCH 017/148] chore: codestyle
---
service/internal/api/api.go | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 68ef978..02781d2 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -494,6 +494,19 @@ func (api *oliveTinAPI) buildCustomDashboardResponse(rr *DashboardRenderRequest,
return connect.NewResponse(res), nil
}
+func resolveLogsPageSize(requestPageSize, defaultPageSize int64) int64 {
+ if requestPageSize == 0 {
+ return defaultPageSize
+ }
+ if requestPageSize < 10 {
+ return 10
+ }
+ if requestPageSize > 100 {
+ return 100
+ }
+ return requestPageSize
+}
+
func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetLogsRequest]) (*connect.Response[apiv1.GetLogsResponse], error) {
user := auth.UserFromApiCall(ctx, req, api.cfg)
@@ -501,21 +514,9 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetL
return nil, err
}
+ pageSize := resolveLogsPageSize(req.Msg.GetPageSize(), api.cfg.LogHistoryPageSize)
+ logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, pageSize, req.Msg.DateFilter)
ret := &apiv1.GetLogsResponse{}
- dateFilter := ""
- if req.Msg.DateFilter != "" {
- dateFilter = req.Msg.DateFilter
- }
- pageSize := api.cfg.LogHistoryPageSize
- if req.Msg.GetPageSize() > 0 {
- pageSize = req.Msg.GetPageSize()
- if pageSize < 10 {
- pageSize = 10
- } else if pageSize > 100 {
- pageSize = 100
- }
- }
- logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, pageSize, dateFilter)
for _, le := range logEntries {
ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
}
From 04d627d36c51d3b92758d07fb2a4ede071e03ce4 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 07:17:22 +0000
Subject: [PATCH 018/148] chore: dependabot less spam
---
.github/dependabot.yml | 50 +++++++++++++++++++++++++++++++++---------
1 file changed, 40 insertions(+), 10 deletions(-)
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 5d9c15a..d778df1 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -7,14 +7,20 @@ updates:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
+ labels:
+ - "3k"
+ - "dependencies"
- # npm updates for frontend - targeting "release/2k" branch
+ # npm updates for frontend - targeting "release/2k" branch (security updates only)
- package-ecosystem: "npm"
directory: "/frontend"
schedule:
interval: "weekly"
target-branch: "release/2k"
- open-pull-requests-limit: 10
+ open-pull-requests-limit: 0
+ labels:
+ - "2k"
+ - "dependencies"
# npm updates for integration-tests - targeting "next" branch
- package-ecosystem: "npm"
@@ -23,14 +29,20 @@ updates:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
+ labels:
+ - "3k"
+ - "dependencies"
- # npm updates for integration-tests - targeting "release/2k" branch
+ # npm updates for integration-tests - targeting "release/2k" branch (security updates only)
- package-ecosystem: "npm"
directory: "/integration-tests"
schedule:
interval: "weekly"
target-branch: "release/2k"
- open-pull-requests-limit: 10
+ open-pull-requests-limit: 0
+ labels:
+ - "2k"
+ - "dependencies"
# Go modules updates for service - targeting "next" branch
- package-ecosystem: "gomod"
@@ -39,14 +51,20 @@ updates:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
+ labels:
+ - "3k"
+ - "dependencies"
- # Go modules updates for service - targeting "release/2k" branch
+ # Go modules updates for service - targeting "release/2k" branch (security updates only)
- package-ecosystem: "gomod"
directory: "/service"
schedule:
interval: "weekly"
target-branch: "release/2k"
- open-pull-requests-limit: 10
+ open-pull-requests-limit: 0
+ labels:
+ - "2k"
+ - "dependencies"
# Go modules updates for lang - targeting "next" branch
- package-ecosystem: "gomod"
@@ -55,14 +73,20 @@ updates:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
+ labels:
+ - "3k"
+ - "dependencies"
- # Go modules updates for lang - targeting "release/2k" branch
+ # Go modules updates for lang - targeting "release/2k" branch (security updates only)
- package-ecosystem: "gomod"
directory: "/lang"
schedule:
interval: "weekly"
target-branch: "release/2k"
- open-pull-requests-limit: 10
+ open-pull-requests-limit: 0
+ labels:
+ - "2k"
+ - "dependencies"
# Docker updates - targeting "next" branch
- package-ecosystem: "docker"
@@ -71,12 +95,18 @@ updates:
interval: "weekly"
target-branch: "next"
open-pull-requests-limit: 10
+ labels:
+ - "3k"
+ - "dependencies"
- # Docker updates - targeting "release/2k" branch
+ # Docker updates - targeting "release/2k" branch (security updates only)
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
target-branch: "release/2k"
- open-pull-requests-limit: 10
+ open-pull-requests-limit: 0
+ labels:
+ - "2k"
+ - "dependencies"
From ff3620bca9fe3ca1368ceba6dcfebcb5898db352 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 26 Feb 2026 07:24:25 +0000
Subject: [PATCH 019/148] chore(deps): bump github.com/bufbuild/buf in /service
Bumps [github.com/bufbuild/buf](https://github.com/bufbuild/buf) from 1.65.0 to 1.66.0.
- [Release notes](https://github.com/bufbuild/buf/releases)
- [Changelog](https://github.com/bufbuild/buf/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bufbuild/buf/compare/v1.65.0...v1.66.0)
---
updated-dependencies:
- dependency-name: github.com/bufbuild/buf
dependency-version: 1.66.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
---
service/go.mod | 16 ++++++++--------
service/go.sum | 15 +++++++++++++++
2 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/service/go.mod b/service/go.mod
index 659b3ab..e03f2da 100644
--- a/service/go.mod
+++ b/service/go.mod
@@ -1,6 +1,6 @@
module github.com/OliveTin/OliveTin
-go 1.25.0
+go 1.25.6
exclude google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884
@@ -10,7 +10,7 @@ require (
github.com/MicahParks/keyfunc/v3 v3.8.0
github.com/PaesslerAG/jsonpath v0.1.1
github.com/alexedwards/argon2id v1.0.0
- github.com/bufbuild/buf v1.65.0
+ github.com/bufbuild/buf v1.66.0
github.com/fsnotify/fsnotify v1.9.0
github.com/fzipp/gocyclo v0.6.0
github.com/go-critic/go-critic v0.14.3
@@ -27,7 +27,7 @@ require (
github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1
go.akshayshah.org/connectproto v0.6.0
- golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a
+ golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
golang.org/x/oauth2 v0.35.0
golang.org/x/sys v0.41.0
google.golang.org/protobuf v1.36.11
@@ -45,7 +45,7 @@ require (
buf.build/go/bufplugin v0.9.0 // indirect
buf.build/go/bufprivateusage v0.1.0 // indirect
buf.build/go/interrupt v1.1.0 // indirect
- buf.build/go/protovalidate v1.1.2 // indirect
+ buf.build/go/protovalidate v1.1.3 // indirect
buf.build/go/protoyaml v0.6.0 // indirect
buf.build/go/spdx v0.2.0 // indirect
buf.build/go/standard v0.1.0 // indirect
@@ -57,7 +57,7 @@ require (
github.com/PaesslerAG/gval v1.2.4 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/bufbuild/protocompile v0.14.2-0.20260130195850-5c64bed4577e // indirect
+ github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113 // indirect
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cli/browser v1.3.0 // indirect
@@ -90,7 +90,7 @@ require (
github.com/gofrs/flock v0.13.0 // indirect
github.com/google/cel-go v0.27.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
- github.com/google/go-containerregistry v0.20.7 // indirect
+ github.com/google/go-containerregistry v0.21.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdx/go-netrc v1.0.0 // indirect
@@ -155,8 +155,8 @@ require (
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.42.0 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
google.golang.org/grpc v1.75.1 // indirect
mvdan.cc/xurls/v2 v2.6.0 // indirect
pluginrpc.com/pluginrpc v0.5.0 // indirect
diff --git a/service/go.sum b/service/go.sum
index 5ef6552..373c8c6 100644
--- a/service/go.sum
+++ b/service/go.sum
@@ -32,6 +32,8 @@ buf.build/go/protovalidate v1.1.0 h1:pQqEQRpOo4SqS60qkvmhLTTQU9JwzEvdyiqAtXa5SeY
buf.build/go/protovalidate v1.1.0/go.mod h1:bGZcPiAQDC3ErCHK3t74jSoJDFOs2JH3d7LWuTEIdss=
buf.build/go/protovalidate v1.1.2 h1:83vYHoY8f34hB8MeitGaYE3CGVPFxwdEUuskh5qQpA0=
buf.build/go/protovalidate v1.1.2/go.mod h1:Ez3z+w4c+wG+EpW8ovgZaZPnPl2XVF6kaxgcv1NG/QE=
+buf.build/go/protovalidate v1.1.3 h1:m2GVEgQWd7rk+vIoAZ+f0ygGjvQTuqPQapBBdcpWVPE=
+buf.build/go/protovalidate v1.1.3/go.mod h1:9XIuohWz+kj+9JVn3WQneHA5LZP50mjvneZMnbLkiIE=
buf.build/go/protoyaml v0.6.0 h1:Nzz1lvcXF8YgNZXk+voPPwdU8FjDPTUV4ndNTXN0n2w=
buf.build/go/protoyaml v0.6.0/go.mod h1:RgUOsBu/GYKLDSIRgQXniXbNgFlGEZnQpRAUdLAFV2Q=
buf.build/go/spdx v0.2.0 h1:IItqM0/cMxvFJJumcBuP8NrsIzMs/UYjp/6WSpq8LTw=
@@ -84,6 +86,8 @@ github.com/bufbuild/buf v1.64.0 h1:puHWFcVKmZFSu4KuaN0kZiQ32n7VVc3un1FeLU77XUs=
github.com/bufbuild/buf v1.64.0/go.mod h1:U4ISwkjZXRLMaCkPG9zp1xY3xHEIwhCFwyNAaA56SGw=
github.com/bufbuild/buf v1.65.0 h1:f2BzeCY9rRh9P5KD340ZoPAaFLTkssoUTHx7lpqozgg=
github.com/bufbuild/buf v1.65.0/go.mod h1:7SAs2YqGpPXHqBBXBeYQbCzY0OQq4Jbg6XCqirEiYvQ=
+github.com/bufbuild/buf v1.66.0 h1:6kksYJpu6r45bvPJSTwNSwRqiAjrwB9YyU7skjNzFVo=
+github.com/bufbuild/buf v1.66.0/go.mod h1:tWVlwtIPZ7kzlCB9D0hbbfrroT0GNCybPdPQXq1i1Ac=
github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e h1:LQA+1MyiPkolGHJGC2GMDC5Xu+0RDVH6jGMKech7Exs=
github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e/go.mod h1:5UUj46Eu+U+C59C5N6YilaMI7WWfP2bW9xGcOkme2DI=
github.com/bufbuild/protocompile v0.14.2-0.20260105175043-4d8d90b1c6b8 h1:cQYwUyAzyMmYr7AyJU1C6pVCpUrJJBkmx7UunZosxxs=
@@ -92,6 +96,8 @@ github.com/bufbuild/protocompile v0.14.2-0.20260120135352-a3ed5cd7a608 h1:3aRREB
github.com/bufbuild/protocompile v0.14.2-0.20260120135352-a3ed5cd7a608/go.mod h1:5UUj46Eu+U+C59C5N6YilaMI7WWfP2bW9xGcOkme2DI=
github.com/bufbuild/protocompile v0.14.2-0.20260130195850-5c64bed4577e h1:emH16Bf1w4C0cJ3ge4QtBAl4sIYJe23EfpWH0SpA9co=
github.com/bufbuild/protocompile v0.14.2-0.20260130195850-5c64bed4577e/go.mod h1:cxhE8h+14t0Yxq2H9MV/UggzQ1L0gh0t2tJobITWsBE=
+github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113 h1:nxt1QhP9rMQNFhHTdcNFwJ9wKCSdBjd28gz+qGDv4kM=
+github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113/go.mod h1:cxhE8h+14t0Yxq2H9MV/UggzQ1L0gh0t2tJobITWsBE=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 h1:V1xulAoqLqVg44rY97xOR+mQpD2N+GzhMHVwJ030WEU=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@@ -200,6 +206,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I=
github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM=
+github.com/google/go-containerregistry v0.21.0 h1:ocqxUOczFwAZQBMNE7kuzfqvDe0VWoZxQMOesXreCDI=
+github.com/google/go-containerregistry v0.21.0/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
@@ -375,6 +383,7 @@ go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIl
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
@@ -421,6 +430,8 @@ golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
+golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
+golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20251219203646-944ab1f22d93 h1:PbC785RGO6yPO051ItgbG/adwoKRWC0VS7kXXeD/iqk=
@@ -519,6 +530,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260122232226-8e98ce8d340d h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
+google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s=
+google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
@@ -527,6 +540,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
From 69c1276a131ee93d9539873e817254b9b27196f2 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 13:38:52 +0000
Subject: [PATCH 020/148] chore: Setup buildx
---
.github/workflows/build-and-release.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index ba3362f..9b77515 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -87,6 +87,9 @@ jobs:
with:
install-only: true
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
- name: release
if: github.ref_type != 'tag'
uses: cycjimmy/semantic-release-action@v4
From cb71ddf401cb55024a2b4eed19247af812ee99e0 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 16:14:41 +0000
Subject: [PATCH 021/148] fix: Set common security headers by default
---
service/internal/config/config.go | 15 ++++++++++
service/internal/config/sanitize.go | 20 +++++++++++++
service/internal/httpservers/frontend.go | 36 +++++++++++++++++++++++-
3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/service/internal/config/config.go b/service/internal/config/config.go
index de5bdcc..aa35153 100644
--- a/service/internal/config/config.go
+++ b/service/internal/config/config.go
@@ -107,6 +107,15 @@ type PrometheusConfig struct {
DefaultGoMetrics bool `koanf:"defaultGoMetrics"`
}
+// SecurityConfig allows users to fine tune the security related HTTP headers.
+type SecurityConfig struct {
+ HeaderContentSecurityPolicy bool `koanf:"headerContentSecurityPolicy"`
+ ContentSecurityPolicy string `koanf:"contentSecurityPolicy"`
+ HeaderXContentTypeOptions bool `koanf:"headerXContentTypeOptions"`
+ HeaderXFrameOptions bool `koanf:"headerXFrameOptions"`
+ XFrameOptions string `koanf:"xFrameOptions"`
+}
+
// Config is the global config used through the whole app.
type Config struct {
UseSingleHTTPFrontend bool `koanf:"useSingleHTTPFrontend"`
@@ -160,6 +169,7 @@ type Config struct {
InsecureAllowDumpActionMap bool `koanf:"insecureAllowDumpActionMap"`
InsecureAllowDumpJwtClaims bool `koanf:"insecureAllowDumpJwtClaims"`
Prometheus PrometheusConfig `koanf:"prometheus"`
+ Security SecurityConfig `koanf:"security"`
SaveLogs SaveLogsConfig `koanf:"saveLogs"`
DefaultIconForActions string `koanf:"defaultIconForActions"`
DefaultIconForDirectories string `koanf:"defaultIconForDirectories"`
@@ -268,6 +278,11 @@ func DefaultConfigWithBasePort(basePort int) *Config {
config.InsecureAllowDumpJwtClaims = false
config.Prometheus.Enabled = false
config.Prometheus.DefaultGoMetrics = false
+ config.Security.HeaderContentSecurityPolicy = true
+ config.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'"
+ config.Security.HeaderXContentTypeOptions = true
+ config.Security.HeaderXFrameOptions = true
+ config.Security.XFrameOptions = "DENY"
config.DefaultIconForActions = "😀"
config.DefaultIconForDirectories = "📁"
config.DefaultIconForBack = "«"
diff --git a/service/internal/config/sanitize.go b/service/internal/config/sanitize.go
index 43364f5..6b64438 100644
--- a/service/internal/config/sanitize.go
+++ b/service/internal/config/sanitize.go
@@ -16,6 +16,7 @@ func (cfg *Config) Sanitize() {
cfg.sanitizeAuthRequireGuestsToLogin()
cfg.sanitizeLogHistoryPageSize()
cfg.sanitizeLocalUserPasswords()
+ cfg.sanitizeSecurityHeaders()
// log.Infof("cfg %p", cfg)
@@ -183,6 +184,25 @@ func (cfg *Config) sanitizeLocalUserPasswords() {
}
}
+func (cfg *Config) sanitizeSecurityHeaders() {
+ cfg.sanitizeSecurityHeadersCSP()
+ cfg.sanitizeSecurityHeadersXFrameOptions()
+}
+
+func (cfg *Config) sanitizeSecurityHeadersCSP() {
+ if !cfg.Security.HeaderContentSecurityPolicy || cfg.Security.ContentSecurityPolicy != "" {
+ return
+ }
+ cfg.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'"
+}
+
+func (cfg *Config) sanitizeSecurityHeadersXFrameOptions() {
+ if !cfg.Security.HeaderXFrameOptions || cfg.Security.XFrameOptions != "" {
+ return
+ }
+ cfg.Security.XFrameOptions = "DENY"
+}
+
// parsePasswordTemplate expands {{ .Env.VAR }} in local user password fields using the process environment.
func parsePasswordTemplate(source string) string {
t, err := template.New("password").Option("missingkey=error").Parse(source)
diff --git a/service/internal/httpservers/frontend.go b/service/internal/httpservers/frontend.go
index 9860650..c1c1a1f 100644
--- a/service/internal/httpservers/frontend.go
+++ b/service/internal/httpservers/frontend.go
@@ -23,6 +23,40 @@ import (
log "github.com/sirupsen/logrus"
)
+func applySecurityHeaders(cfg *config.Config, w http.ResponseWriter) {
+ applyCSP(cfg, w)
+ applyXContentTypeOptions(cfg, w)
+ applyXFrameOptions(cfg, w)
+}
+
+func applyCSP(cfg *config.Config, w http.ResponseWriter) {
+ if !cfg.Security.HeaderContentSecurityPolicy || cfg.Security.ContentSecurityPolicy == "" {
+ return
+ }
+ w.Header().Set("Content-Security-Policy", cfg.Security.ContentSecurityPolicy)
+}
+
+func applyXContentTypeOptions(cfg *config.Config, w http.ResponseWriter) {
+ if !cfg.Security.HeaderXContentTypeOptions {
+ return
+ }
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+}
+
+func applyXFrameOptions(cfg *config.Config, w http.ResponseWriter) {
+ if !cfg.Security.HeaderXFrameOptions || cfg.Security.XFrameOptions == "" {
+ return
+ }
+ w.Header().Set("X-Frame-Options", cfg.Security.XFrameOptions)
+}
+
+func securityHeadersMiddleware(cfg *config.Config, next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ applySecurityHeaders(cfg, w)
+ next.ServeHTTP(w, r)
+ })
+}
+
func logDebugRequest(cfg *config.Config, source string, r *http.Request) {
if cfg.LogDebugOptions.SingleFrontendRequests {
log.Debugf("SingleFrontend HTTP Req URL %v: %q", source, r.URL)
@@ -96,7 +130,7 @@ func StartFrontendMux(cfg *config.Config, ex *executor.Executor) {
srv := &http.Server{
Addr: cfg.ListenAddressSingleHTTPFrontend,
- Handler: mux,
+ Handler: securityHeadersMiddleware(cfg, mux),
}
log.Fatal(srv.ListenAndServe())
From e487288287b654c821e83a3261b944a0b0a6391b Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 16:15:54 +0000
Subject: [PATCH 022/148] doc: Updated agents codestyle
---
AGENTS.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/AGENTS.md b/AGENTS.md
index 7ff9f84..db2412f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -19,6 +19,8 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
- From repo root: `go run ./service`
- Unit tests (Go):
- From repo root: `cd service && make unittests`
+- Code style (after editing code in `service/`):
+ - From repo root: `cd service && make codestyle`
- Integration tests (Mocha + Selenium):
- Single test: `cd integration-tests && npx --yes mocha test/general.mjs`
- All tests: `cd integration-tests && npx --yes mocha`
@@ -41,6 +43,7 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
- Do not swallow errors; propagate or log meaningfully.
- Match existing formatting; avoid unrelated reformatting.
- Be safe around nils in executor steps (e.g., guard `req.Binding` and `req.Binding.Action`).
+- Cyclomatic complexity over 4 is not permitted.
### API and Execution Flow (High-level)
1. Client calls Connect RPC (e.g., `Init`, `GetDashboard`, `StartAction`).
From a7be68b35917682b3af022191d72f8af8af0dee9 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 16:49:29 +0000
Subject: [PATCH 023/148] security: 10-slot Semaphore around password hash
functions to prevent resource exhaustion attacks
---
service/internal/api/api.go | 12 +++++++-
service/internal/api/local_user_login.go | 39 ++++++++++++++++++------
2 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 02781d2..911f2ab 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -3,6 +3,7 @@ package api
import (
ctx "context"
"encoding/json"
+ "errors"
"os"
"path"
"sort"
@@ -144,6 +145,9 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
hash, err := createHash(req.Msg.Password)
if err != nil {
+ if errors.Is(err, ErrArgon2Busy) {
+ return nil, connect.NewError(connect.CodeResourceExhausted, err)
+ }
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("error creating hash: %w", err))
}
@@ -162,7 +166,13 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
}), nil
}
- match := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
+ match, err := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
+ if err != nil {
+ if errors.Is(err, ErrArgon2Busy) {
+ return nil, connect.NewError(connect.CodeResourceExhausted, err)
+ }
+ return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("checking password: %w", err))
+ }
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{
Success: match,
diff --git a/service/internal/api/local_user_login.go b/service/internal/api/local_user_login.go
index db6e99e..13e77ce 100644
--- a/service/internal/api/local_user_login.go
+++ b/service/internal/api/local_user_login.go
@@ -1,6 +1,7 @@
package api
import (
+ "errors"
"runtime"
config "github.com/OliveTin/OliveTin/internal/config"
@@ -8,6 +9,12 @@ import (
log "github.com/sirupsen/logrus"
)
+var ErrArgon2Busy = errors.New("too many concurrent password operations")
+
+const argon2MaxConcurrent = 10
+
+var argon2Sem = make(chan struct{}, argon2MaxConcurrent)
+
var defaultParams = argon2id.Params{
Memory: 64 * 1024,
Iterations: 4,
@@ -17,6 +24,12 @@ var defaultParams = argon2id.Params{
}
func CreateHash(password string) (string, error) {
+ select {
+ case argon2Sem <- struct{}{}:
+ defer func() { <-argon2Sem }()
+ default:
+ return "", ErrArgon2Busy
+ }
hash, err := argon2id.CreateHash(password, &defaultParams)
if err != nil {
@@ -31,30 +44,38 @@ func createHash(password string) (string, error) {
return CreateHash(password)
}
-func comparePasswordAndHash(password, hash string) bool {
+func comparePasswordAndHash(password, hash string) (bool, error) {
+ select {
+ case argon2Sem <- struct{}{}:
+ defer func() { <-argon2Sem }()
+ default:
+ return false, ErrArgon2Busy
+ }
match, err := argon2id.ComparePasswordAndHash(password, hash)
if err != nil {
log.Errorf("Error comparing password and hash: %v", err)
- return false
+ return false, nil
}
- return match
+ return match, nil
}
-func checkUserPassword(cfg *config.Config, username, password string) bool {
+func checkUserPassword(cfg *config.Config, username, password string) (bool, error) {
for _, user := range cfg.AuthLocalUsers.Users {
if user.Username == username {
- match := comparePasswordAndHash(password, user.Password)
-
+ match, err := comparePasswordAndHash(password, user.Password)
+ if err != nil {
+ return false, err
+ }
if match {
- return true
+ return true, nil
} else {
log.WithFields(log.Fields{
"username": username,
}).Warn("Password does not match for user")
- return false
+ return false, nil
}
}
}
@@ -63,5 +84,5 @@ func checkUserPassword(cfg *config.Config, username, password string) bool {
"username": username,
}).Warn("Failed to check password for user, as username was not found")
- return false
+ return false, nil
}
From 7717f735aa0a044e6f403d40513855953e6d4109 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 17:42:12 +0000
Subject: [PATCH 024/148] chore: dep update
---
frontend/package-lock.json | 187 ++++++++--------
frontend/package.json | 8 +-
integration-tests/package-lock.json | 319 ++++++++++------------------
integration-tests/package.json | 4 +-
4 files changed, 204 insertions(+), 314 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 51cfad8..3bc98c8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,7 +11,7 @@
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
- "@hugeicons/core-free-icons": "^3.1.1",
+ "@hugeicons/core-free-icons": "^3.3.0",
"@hugeicons/vue": "^1.0.4",
"@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0",
@@ -21,13 +21,13 @@
"standard": "^17.1.2",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1",
- "vue": "^3.5.28",
+ "vue": "^3.5.29",
"vue-i18n": "^11.2.8",
- "vue-router": "^5.0.2"
+ "vue-router": "^5.0.3"
},
"devDependencies": {
"process": "^0.11.10",
- "stylelint": "^17.3.0",
+ "stylelint": "^17.4.0",
"stylelint-config-standard": "^40.0.0"
}
},
@@ -905,9 +905,9 @@
}
},
"node_modules/@hugeicons/core-free-icons": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.1.1.tgz",
- "integrity": "sha512-UpS2lUQFi5sKyJSWwM6rO+BnPLvVz1gsyCpPHeZyVuZqi89YH8ksliza4cwaODqKOZyeXmG8juo1ty4QtQofkg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.3.0.tgz",
+ "integrity": "sha512-qYyr4JQ2eQIHTSTbITvnJvs6ERNK64D9gpwZnf2IyuG0exzqfyABLO/oTB71FB3RZPfu1GbwycdiGSo46apjMQ==",
"license": "MIT"
},
"node_modules/@hugeicons/vue": {
@@ -1435,39 +1435,39 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz",
- "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
+ "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
- "@vue/shared": "3.5.28",
+ "@vue/shared": "3.5.29",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz",
- "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz",
+ "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.28",
- "@vue/shared": "3.5.28"
+ "@vue/compiler-core": "3.5.29",
+ "@vue/shared": "3.5.29"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz",
- "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz",
+ "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
- "@vue/compiler-core": "3.5.28",
- "@vue/compiler-dom": "3.5.28",
- "@vue/compiler-ssr": "3.5.28",
- "@vue/shared": "3.5.28",
+ "@vue/compiler-core": "3.5.29",
+ "@vue/compiler-dom": "3.5.29",
+ "@vue/compiler-ssr": "3.5.29",
+ "@vue/shared": "3.5.29",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
@@ -1475,13 +1475,13 @@
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz",
- "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz",
+ "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.28",
- "@vue/shared": "3.5.28"
+ "@vue/compiler-dom": "3.5.29",
+ "@vue/shared": "3.5.29"
}
},
"node_modules/@vue/devtools-api": {
@@ -1491,12 +1491,12 @@
"license": "MIT"
},
"node_modules/@vue/devtools-kit": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz",
- "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==",
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.0.6.tgz",
+ "integrity": "sha512-9zXZPTJW72OteDXeSa5RVML3zWDCRcO5t77aJqSs228mdopYj5AiTpihozbsfFJ0IodfNs7pSgOGO3qfCuxDtw==",
"license": "MIT",
"dependencies": {
- "@vue/devtools-shared": "^8.0.5",
+ "@vue/devtools-shared": "^8.0.6",
"birpc": "^2.6.1",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
@@ -1506,62 +1506,62 @@
}
},
"node_modules/@vue/devtools-shared": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz",
- "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==",
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.0.6.tgz",
+ "integrity": "sha512-Pp1JylTqlgMJvxW6MGyfTF8vGvlBSCAvMFaDCYa82Mgw7TT5eE5kkHgDvmOGHWeJE4zIDfCpCxHapsK2LtIAJg==",
"license": "MIT",
"dependencies": {
"rfdc": "^1.4.1"
}
},
"node_modules/@vue/reactivity": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz",
- "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz",
+ "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.28"
+ "@vue/shared": "3.5.29"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz",
- "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz",
+ "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.28",
- "@vue/shared": "3.5.28"
+ "@vue/reactivity": "3.5.29",
+ "@vue/shared": "3.5.29"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz",
- "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz",
+ "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.28",
- "@vue/runtime-core": "3.5.28",
- "@vue/shared": "3.5.28",
+ "@vue/reactivity": "3.5.29",
+ "@vue/runtime-core": "3.5.29",
+ "@vue/shared": "3.5.29",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz",
- "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz",
+ "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.28",
- "@vue/shared": "3.5.28"
+ "@vue/compiler-ssr": "3.5.29",
+ "@vue/shared": "3.5.29"
},
"peerDependencies": {
- "vue": "3.5.28"
+ "vue": "3.5.29"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz",
- "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz",
+ "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==",
"license": "MIT"
},
"node_modules/@xterm/addon-fit": {
@@ -2121,13 +2121,13 @@
}
},
"node_modules/css-functions-list": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz",
- "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz",
+ "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=12 || >=16"
+ "node": ">=12"
}
},
"node_modules/css-tree": {
@@ -4276,13 +4276,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/known-css-properties": {
- "version": "0.37.0",
- "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz",
- "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -5854,9 +5847,9 @@
}
},
"node_modules/stylelint": {
- "version": "17.3.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.3.0.tgz",
- "integrity": "sha512-1POV91lcEMhj6SLVaOeA0KlS9yattS+qq+cyWqP/nYzWco7K5jznpGH1ExngvPlTM9QF1Kjd2bmuzJu9TH2OcA==",
+ "version": "17.4.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.4.0.tgz",
+ "integrity": "sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==",
"dev": true,
"funding": [
{
@@ -5872,15 +5865,14 @@
"dependencies": {
"@csstools/css-calc": "^3.1.1",
"@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-syntax-patches-for-csstree": "^1.0.26",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.27",
"@csstools/css-tokenizer": "^4.0.0",
"@csstools/media-query-list-parser": "^5.0.0",
"@csstools/selector-resolve-nested": "^4.0.0",
"@csstools/selector-specificity": "^6.0.0",
- "balanced-match": "^3.0.1",
"colord": "^2.9.3",
"cosmiconfig": "^9.0.0",
- "css-functions-list": "^3.2.3",
+ "css-functions-list": "^3.3.3",
"css-tree": "^3.1.0",
"debug": "^4.4.3",
"fast-glob": "^3.3.3",
@@ -5894,7 +5886,6 @@
"import-meta-resolve": "^4.2.0",
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
- "known-css-properties": "^0.37.0",
"mathml-tag-names": "^4.0.0",
"meow": "^14.0.0",
"micromatch": "^4.0.8",
@@ -5979,16 +5970,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
- "node_modules/stylelint/node_modules/balanced-match": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-3.0.1.tgz",
- "integrity": "sha512-vjtV3hiLqYDNRoiAv0zC4QaGAMPomEoq83PRmYIofPswwZurCeWR5LByXm7SyoL0Zh5+2z0+HC7jG8gSZJUh0w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- }
- },
"node_modules/stylelint/node_modules/file-entry-cache": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz",
@@ -6615,16 +6596,16 @@
}
},
"node_modules/vue": {
- "version": "3.5.28",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz",
- "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==",
+ "version": "3.5.29",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
+ "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.28",
- "@vue/compiler-sfc": "3.5.28",
- "@vue/runtime-dom": "3.5.28",
- "@vue/server-renderer": "3.5.28",
- "@vue/shared": "3.5.28"
+ "@vue/compiler-dom": "3.5.29",
+ "@vue/compiler-sfc": "3.5.29",
+ "@vue/runtime-dom": "3.5.29",
+ "@vue/server-renderer": "3.5.29",
+ "@vue/shared": "3.5.29"
},
"peerDependencies": {
"typescript": "*"
@@ -6656,14 +6637,14 @@
}
},
"node_modules/vue-router": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.2.tgz",
- "integrity": "sha512-YFhwaE5c5JcJpNB1arpkl4/GnO32wiUWRB+OEj1T0DlDxEZoOfbltl2xEwktNU/9o1sGcGburIXSpbLpPFe/6w==",
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.3.tgz",
+ "integrity": "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==",
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.28.6",
"@vue-macros/common": "^3.1.1",
- "@vue/devtools-api": "^8.0.0",
+ "@vue/devtools-api": "^8.0.6",
"ast-walker-scope": "^0.8.3",
"chokidar": "^5.0.0",
"json5": "^2.2.3",
@@ -6701,12 +6682,12 @@
}
},
"node_modules/vue-router/node_modules/@vue/devtools-api": {
- "version": "8.0.5",
- "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.5.tgz",
- "integrity": "sha512-DgVcW8H/Nral7LgZEecYFFYXnAvGuN9C3L3DtWekAncFBedBczpNW8iHKExfaM559Zm8wQWrwtYZ9lXthEHtDw==",
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.0.6.tgz",
+ "integrity": "sha512-+lGBI+WTvJmnU2FZqHhEB8J1DXcvNlDeEalz77iYgOdY1jTj1ipSBaKj3sRhYcy+kqA8v/BSuvOz1XJucfQmUA==",
"license": "MIT",
"dependencies": {
- "@vue/devtools-kit": "^8.0.5"
+ "@vue/devtools-kit": "^8.0.6"
}
},
"node_modules/vue-router/node_modules/json5": {
diff --git a/frontend/package.json b/frontend/package.json
index a56e2bb..a64d473 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,7 +6,7 @@
"source": "index.html",
"devDependencies": {
"process": "^0.11.10",
- "stylelint": "^17.3.0",
+ "stylelint": "^17.4.0",
"stylelint-config-standard": "^40.0.0"
},
"scripts": {
@@ -24,7 +24,7 @@
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
- "@hugeicons/core-free-icons": "^3.1.1",
+ "@hugeicons/core-free-icons": "^3.3.0",
"@hugeicons/vue": "^1.0.4",
"@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0",
@@ -34,8 +34,8 @@
"standard": "^17.1.2",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1",
- "vue": "^3.5.28",
+ "vue": "^3.5.29",
"vue-i18n": "^11.2.8",
- "vue-router": "^5.0.2"
+ "vue-router": "^5.0.3"
}
}
diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json
index 12d651e..bb664ca 100644
--- a/integration-tests/package-lock.json
+++ b/integration-tests/package-lock.json
@@ -13,9 +13,9 @@
},
"devDependencies": {
"chai": "^6.2.2",
- "eslint": "^9.39.2",
+ "eslint": "^10.0.2",
"mocha": "^11.7.5",
- "selenium-webdriver": "^4.40.0"
+ "selenium-webdriver": "^4.41.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -67,9 +67,9 @@
}
},
"node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"license": "MIT",
"engines": {
@@ -77,105 +77,68 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz",
+ "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^2.1.7",
+ "@eslint/object-schema": "^3.0.2",
"debug": "^4.3.1",
- "minimatch": "^3.1.2"
+ "minimatch": "^10.2.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
- "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz",
+ "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.17.0"
+ "@eslint/core": "^1.1.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/core": {
- "version": "0.17.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
- "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
+ "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- }
- },
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
- "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- }
- },
- "node_modules/@eslint/js": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
- "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://eslint.org/donate"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/object-schema": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
- "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz",
+ "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz",
+ "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.17.0",
+ "@eslint/core": "^1.1.0",
"levn": "^0.4.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@hapi/address": {
@@ -326,10 +289,17 @@
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"license": "MIT"
},
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"dev": true,
"license": "MIT"
},
@@ -341,9 +311,9 @@
"license": "MIT"
},
"node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -364,9 +334,9 @@
}
},
"node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -438,14 +408,26 @@
"dev": true
},
"node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
+ "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/brace-expansion/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
"node_modules/browser-stdout": {
@@ -467,16 +449,6 @@
"node": ">= 0.4"
}
},
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
@@ -639,13 +611,6 @@
"node": ">= 0.8"
}
},
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -818,33 +783,30 @@
}
},
"node_modules/eslint": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
- "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz",
+ "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
- "@eslint/config-helpers": "^0.4.2",
- "@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.2",
- "@eslint/plugin-kit": "^0.4.1",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.2",
+ "@eslint/config-helpers": "^0.5.2",
+ "@eslint/core": "^1.1.0",
+ "@eslint/plugin-kit": "^0.6.0",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
+ "ajv": "^6.14.0",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.4.0",
- "eslint-visitor-keys": "^4.2.1",
- "espree": "^10.4.0",
- "esquery": "^1.5.0",
+ "eslint-scope": "^9.1.1",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.1.1",
+ "esquery": "^1.7.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^8.0.0",
@@ -854,8 +816,7 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
+ "minimatch": "^10.2.1",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -863,7 +824,7 @@
"eslint": "bin/eslint.js"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
@@ -878,58 +839,61 @@
}
},
"node_modules/eslint-scope": {
- "version": "8.4.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
- "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz",
+ "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/espree": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
- "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz",
+ "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
- "acorn": "^8.15.0",
+ "acorn": "^8.16.0",
"acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.1"
+ "eslint-visitor-keys": "^5.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esquery": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
- "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"dev": true,
+ "license": "BSD-3-Clause",
"dependencies": {
"estraverse": "^5.1.0"
},
@@ -955,6 +919,7 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
@@ -1216,19 +1181,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -1314,23 +1266,6 @@
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"dev": true
},
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -1552,12 +1487,6 @@
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true
- },
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@@ -1612,16 +1541,19 @@
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^1.1.7"
+ "brace-expansion": "^5.0.2"
},
"engines": {
- "node": "*"
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
@@ -1793,19 +1725,6 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
},
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -1928,16 +1847,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
@@ -1954,9 +1863,9 @@
"dev": true
},
"node_modules/selenium-webdriver": {
- "version": "4.40.0",
- "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.40.0.tgz",
- "integrity": "sha512-dU0QbnVKdPmoNP8OtMCazRdtU2Ux6Wl4FEpG1iwUbDeajJK1dBAywBLrC1D7YFRtogHzN96AbXBgBAJaarcysw==",
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.41.0.tgz",
+ "integrity": "sha512-1XxuKVhr9az24xwixPBEDGSZP+P0z3ZOnCmr9Oiep0MlJN2Mk+flIjD3iBS9BgyjS4g14dikMqnrYUPIjhQBhA==",
"dev": true,
"funding": [
{
@@ -1973,7 +1882,7 @@
"@bazel/runfiles": "^6.5.0",
"jszip": "^3.10.1",
"tmp": "^0.2.5",
- "ws": "^8.18.3"
+ "ws": "^8.19.0"
},
"engines": {
"node": ">= 20.0.0"
@@ -2347,9 +2256,9 @@
}
},
"node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "version": "8.19.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/integration-tests/package.json b/integration-tests/package.json
index 21bbcc8..331945f 100644
--- a/integration-tests/package.json
+++ b/integration-tests/package.json
@@ -12,9 +12,9 @@
"license": "AGPL-3.0-only",
"devDependencies": {
"chai": "^6.2.2",
- "eslint": "^9.39.2",
+ "eslint": "^10.0.2",
"mocha": "^11.7.5",
- "selenium-webdriver": "^4.40.0"
+ "selenium-webdriver": "^4.41.0"
},
"dependencies": {
"wait-on": "^9.0.4"
From 24cced0c8c806800f79cc2d8462ab0a193ddebcd Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 20:23:48 +0000
Subject: [PATCH 025/148] security: IDOR on ExecutionStatus API
---
service/internal/api/api.go | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 911f2ab..339cbc4 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -384,10 +384,11 @@ func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[ap
if ile == nil {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s or action ID %s", req.Msg.ExecutionTrackingId, req.Msg.ActionId))
- } else {
- res.LogEntry = api.internalLogEntryToPb(ile, user)
}
-
+ if !isValidLogEntry(ile) || !api.isLogEntryAllowed(ile, user) {
+ return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied to view this execution"))
+ }
+ res.LogEntry = api.internalLogEntryToPb(ile, user)
return connect.NewResponse(res), nil
}
From 4af4d516be018d260a8d184648aacff8d0198328 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 20:43:14 +0000
Subject: [PATCH 026/148] fix: ShowDiagnostics now behind policy checks
---
service/internal/api/api.go | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 339cbc4..ef9d3f5 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -893,11 +893,17 @@ func (api *oliveTinAPI) OnExecutionFinished(ile *executor.InternalLogEntry) {
}
func (api *oliveTinAPI) GetDiagnostics(ctx ctx.Context, req *connect.Request[apiv1.GetDiagnosticsRequest]) (*connect.Response[apiv1.GetDiagnosticsResponse], error) {
+ user := auth.UserFromApiCall(ctx, req, api.cfg)
+ if err := api.checkDashboardAccess(user); err != nil {
+ return nil, err
+ }
+ if !user.EffectivePolicy.ShowDiagnostics {
+ return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("diagnostics are not available for your account"))
+ }
res := &apiv1.GetDiagnosticsResponse{
SshFoundKey: installationinfo.Runtime.SshFoundKey,
SshFoundConfig: installationinfo.Runtime.SshFoundConfig,
}
-
return connect.NewResponse(res), nil
}
From f3549b035e788d73fc277354b622e202124d1497 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 20:46:11 +0000
Subject: [PATCH 027/148] Remove dead CORS package (L-2)
The CORS helper was unused; its import was commented out in webuiServer.go.
Deleting the package removes the dormant origin-reflection security issue.
---
service/internal/cors/cors.go | 23 ---------------------
service/internal/cors/cors_test.go | 22 --------------------
service/internal/httpservers/webuiServer.go | 2 --
3 files changed, 47 deletions(-)
delete mode 100644 service/internal/cors/cors.go
delete mode 100644 service/internal/cors/cors_test.go
diff --git a/service/internal/cors/cors.go b/service/internal/cors/cors.go
deleted file mode 100644
index 905ed46..0000000
--- a/service/internal/cors/cors.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package cors
-
-import (
- log "github.com/sirupsen/logrus"
- "net/http"
-)
-
-// AllowCors takes a HTTP handler and adds Access-Control-Allow-Origin headers to
-// responses.
-//
-// Note: HTTP OPTIONS requests (which need to be preflighted" for CORS) are not
-// handled because this app does not use HTTP PUT/PATCH/etc.
-func AllowCors(h http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if origin := r.Header.Get("Origin"); origin != "" {
- log.Debugf("Adding CORS header origin: %q", origin)
-
- w.Header().Set("Access-Control-Allow-Origin", origin)
- }
-
- h.ServeHTTP(w, r)
- })
-}
diff --git a/service/internal/cors/cors_test.go b/service/internal/cors/cors_test.go
deleted file mode 100644
index bfd87e2..0000000
--- a/service/internal/cors/cors_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package cors
-
-import (
- "github.com/stretchr/testify/assert"
- "net/http"
- "net/http/httptest"
- "testing"
-)
-
-func TestCors(t *testing.T) {
- req, _ := http.NewRequest("GET", "/health-check", nil)
- req.Header.Add("Origin", "1.2.3.4")
-
- blat := AllowCors(http.FileServer(http.Dir(".")))
-
- rr := httptest.NewRecorder()
-
- blat.ServeHTTP(rr, req)
-
- assert.Equal(t, http.StatusNotFound, rr.Code, "HTTP 404 on CORS")
- assert.Equal(t, "1.2.3.4", rr.Header().Get("Access-Control-Allow-Origin"), "CORS Header set")
-}
diff --git a/service/internal/httpservers/webuiServer.go b/service/internal/httpservers/webuiServer.go
index fb718ba..5b76c06 100644
--- a/service/internal/httpservers/webuiServer.go
+++ b/service/internal/httpservers/webuiServer.go
@@ -1,8 +1,6 @@
package httpservers
import (
-
- // cors "github.com/OliveTin/OliveTin/internal/cors"
"net/http"
"os"
"path"
From e9a3863b1b9da97ce9377e8f6e87f3fc90de7449 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 20:56:51 +0000
Subject: [PATCH 028/148] chore: codestyle
---
service/internal/api/api.go | 114 ++++++++++-------------
service/internal/api/local_user_login.go | 41 ++++----
2 files changed, 69 insertions(+), 86 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index ef9d3f5..c557243 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -158,14 +158,32 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
return connect.NewResponse(ret), nil
}
-func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[apiv1.LocalUserLoginRequest]) (*connect.Response[apiv1.LocalUserLoginResponse], error) {
- // Check if local user authentication is enabled
- if !api.cfg.AuthLocalUsers.Enabled {
- return connect.NewResponse(&apiv1.LocalUserLoginResponse{
- Success: false,
- }), nil
+func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool) {
+ if match {
+ user := api.cfg.FindUserByUsername(req.Username)
+ if user != nil {
+ sid := uuid.NewString()
+ auth.RegisterUserSession(api.cfg, "local", sid, user.Username)
+ log.WithFields(log.Fields{"username": user.Username}).Info("LocalUserLogin: Session created and registered")
+ cookie := &http.Cookie{
+ Name: "olivetin-sid-local",
+ Value: sid,
+ MaxAge: 31556952,
+ HttpOnly: true,
+ Path: "/",
+ }
+ response.Header().Set("Set-Cookie", cookie.String())
+ }
+ log.WithFields(log.Fields{"username": req.Username}).Info("LocalUserLogin: User logged in successfully.")
+ } else {
+ log.WithFields(log.Fields{"username": req.Username}).Warn("LocalUserLogin: User login failed.")
}
+}
+func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[apiv1.LocalUserLoginRequest]) (*connect.Response[apiv1.LocalUserLoginResponse], error) {
+ if !api.cfg.AuthLocalUsers.Enabled {
+ return connect.NewResponse(&apiv1.LocalUserLoginResponse{Success: false}), nil
+ }
match, err := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
if err != nil {
if errors.Is(err, ErrArgon2Busy) {
@@ -173,43 +191,8 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
}
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("checking password: %w", err))
}
-
- response := connect.NewResponse(&apiv1.LocalUserLoginResponse{
- Success: match,
- })
-
- if match {
- // Set authentication cookie for successful login
- user := api.cfg.FindUserByUsername(req.Msg.Username)
- if user != nil {
- sid := uuid.NewString()
- // Register the session in the session storage
- auth.RegisterUserSession(api.cfg, "local", sid, user.Username)
-
- log.WithFields(log.Fields{
- "username": user.Username,
- }).Info("LocalUserLogin: Session created and registered")
-
- // Set the authentication cookie in the response headers
- cookie := &http.Cookie{
- Name: "olivetin-sid-local",
- Value: sid,
- MaxAge: 31556952, // 1 year
- HttpOnly: true,
- Path: "/",
- }
- response.Header().Set("Set-Cookie", cookie.String())
- }
-
- log.WithFields(log.Fields{
- "username": req.Msg.Username,
- }).Info("LocalUserLogin: User logged in successfully.")
- } else {
- log.WithFields(log.Fields{
- "username": req.Msg.Username,
- }).Warn("LocalUserLogin: User login failed.")
- }
-
+ response := connect.NewResponse(&apiv1.LocalUserLoginResponse{Success: match})
+ api.applyLocalLoginResult(req.Msg, response, match)
return response, nil
}
@@ -364,31 +347,36 @@ func getMostRecentExecutionStatusByActionId(api *oliveTinAPI, actionId string) *
return ile
}
-func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[apiv1.ExecutionStatusRequest]) (*connect.Response[apiv1.ExecutionStatusResponse], error) {
- res := &apiv1.ExecutionStatusResponse{}
-
- user := auth.UserFromApiCall(ctx, req, api.cfg)
-
- if err := api.checkDashboardAccess(user); err != nil {
- return nil, err
- }
-
- var ile *executor.InternalLogEntry
-
- if req.Msg.ExecutionTrackingId != "" {
- ile = getExecutionStatusByTrackingID(api, req.Msg.ExecutionTrackingId)
-
- } else {
- ile = getMostRecentExecutionStatusByActionId(api, req.Msg.ActionId)
- }
-
+func (api *oliveTinAPI) resolveExecutionStatusForView(msg *apiv1.ExecutionStatusRequest, user *authpublic.AuthenticatedUser) (*executor.InternalLogEntry, error) {
+ ile := api.getExecutionStatusByRequest(msg)
if ile == nil {
- return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s or action ID %s", req.Msg.ExecutionTrackingId, req.Msg.ActionId))
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s or action ID %s", msg.ExecutionTrackingId, msg.ActionId))
}
if !isValidLogEntry(ile) || !api.isLogEntryAllowed(ile, user) {
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied to view this execution"))
}
- res.LogEntry = api.internalLogEntryToPb(ile, user)
+ return ile, nil
+}
+
+func (api *oliveTinAPI) getExecutionStatusByRequest(msg *apiv1.ExecutionStatusRequest) *executor.InternalLogEntry {
+ if msg.ExecutionTrackingId != "" {
+ return getExecutionStatusByTrackingID(api, msg.ExecutionTrackingId)
+ }
+ return getMostRecentExecutionStatusByActionId(api, msg.ActionId)
+}
+
+func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[apiv1.ExecutionStatusRequest]) (*connect.Response[apiv1.ExecutionStatusResponse], error) {
+ user := auth.UserFromApiCall(ctx, req, api.cfg)
+ if err := api.checkDashboardAccess(user); err != nil {
+ return nil, err
+ }
+ ile, err := api.resolveExecutionStatusForView(req.Msg, user)
+ if err != nil {
+ return nil, err
+ }
+ res := &apiv1.ExecutionStatusResponse{
+ LogEntry: api.internalLogEntryToPb(ile, user),
+ }
return connect.NewResponse(res), nil
}
diff --git a/service/internal/api/local_user_login.go b/service/internal/api/local_user_login.go
index 13e77ce..11485f9 100644
--- a/service/internal/api/local_user_login.go
+++ b/service/internal/api/local_user_login.go
@@ -33,7 +33,7 @@ func CreateHash(password string) (string, error) {
hash, err := argon2id.CreateHash(password, &defaultParams)
if err != nil {
- log.Fatal("Error creating hash: ", err)
+ log.Warnf("Error creating hash: %v", err)
return "", err
}
@@ -62,27 +62,22 @@ func comparePasswordAndHash(password, hash string) (bool, error) {
}
func checkUserPassword(cfg *config.Config, username, password string) (bool, error) {
- for _, user := range cfg.AuthLocalUsers.Users {
- if user.Username == username {
- match, err := comparePasswordAndHash(password, user.Password)
- if err != nil {
- return false, err
- }
- if match {
- return true, nil
- } else {
- log.WithFields(log.Fields{
- "username": username,
- }).Warn("Password does not match for user")
-
- return false, nil
- }
- }
+ user := cfg.FindUserByUsername(username)
+ if user == nil {
+ log.WithFields(log.Fields{"username": username}).Warn("Failed to check password for user, as username was not found")
+ return false, nil
}
-
- log.WithFields(log.Fields{
- "username": username,
- }).Warn("Failed to check password for user, as username was not found")
-
- return false, nil
+ return comparePasswordAndLogResult(password, user.Password, username)
+}
+
+func comparePasswordAndLogResult(password, hash, username string) (bool, error) {
+ match, err := comparePasswordAndHash(password, hash)
+ if err != nil {
+ return false, err
+ }
+ if !match {
+ log.WithFields(log.Fields{"username": username}).Warn("Password does not match for user")
+ return false, nil
+ }
+ return true, nil
}
From 4744169aa013a7360449418d5bfef8845b2ff8b4 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 23:07:07 +0000
Subject: [PATCH 029/148] chore: code cleanup, remove todos, etc
---
.pre-commit-config.yaml | 33 +++++++++-
service/internal/api/api.go | 2 +-
service/internal/config/sanitize.go | 3 +-
service/internal/executor/executor.go | 65 +++++++++++--------
service/internal/executor/executor_actions.go | 34 ++++++++++
5 files changed, 105 insertions(+), 32 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4efad37..dda308e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -9,6 +9,19 @@ repos:
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
+ - id: check-merge-conflict
+ - id: detect-private-key
+ - id: mixed-line-ending
+ args: ['--fix', 'lf']
+ - id: check-json
+ exclude: |
+ (?x)^(
+ service/internal/entities/testdata/.*\.json|
+ integration-tests/tests/.*/entities/.*\.json|
+ var/entities/.*\.json
+ )$
+ - id: check-case-conflict
+ - id: detect-aws-credentials
# Alternative semantic commit checker
- repo: https://github.com/compilerla/conventional-pre-commit
@@ -34,9 +47,23 @@ repos:
pass_filenames: false
always_run: true
- - id: it
- name: it
- entry: make service-codestyle frontend-codestyle
+ - id: service-unittests
+ name: service-unittests
+ entry: make service-unittests
+ language: system
+ pass_filenames: false
+ always_run: true
+
+ - id: service-build
+ name: service-build
+ entry: make service
+ language: system
+ pass_filenames: false
+ always_run: true
+
+ - id: it
+ name: integration-tests
+ entry: make it
language: system
pass_filenames: false
always_run: true
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index c557243..b479e96 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -1268,7 +1268,7 @@ func (api *oliveTinAPI) RestartAction(ctx ctx.Context, req *connect.Request[apiv
return api.StartAction(ctx, &connect.Request[apiv1.StartActionRequest]{
Msg: &apiv1.StartActionRequest{
- // FIXME
+ BindingId: execReqLogEntry.GetBindingId(),
UniqueTrackingId: req.Msg.ExecutionTrackingId,
},
})
diff --git a/service/internal/config/sanitize.go b/service/internal/config/sanitize.go
index 6b64438..f699b80 100644
--- a/service/internal/config/sanitize.go
+++ b/service/internal/config/sanitize.go
@@ -259,8 +259,7 @@ func (arg *ActionArgument) sanitize() {
arg.sanitizeNoType()
- // TODO Validate the default against the type checker, but this creates a
- // import loop
+ // Default value validation runs in executor at config load (validateArgumentDefaults).
}
func (arg *ActionArgument) sanitizeNoType() {
diff --git a/service/internal/executor/executor.go b/service/internal/executor/executor.go
index 694cc9b..91312d4 100644
--- a/service/internal/executor/executor.go
+++ b/service/internal/executor/executor.go
@@ -603,14 +603,14 @@ func getExecutionsCount(rate config.RateSpec, req *ExecutionRequest) int {
then := time.Now().Add(-duration)
+ currentEntityPrefix := ""
+ if req.Binding != nil && req.Binding.Entity != nil {
+ currentEntityPrefix = req.Binding.Entity.UniqueKey
+ }
for _, logEntry := range req.executor.GetLogsByBindingId(req.Binding.ID) {
- // FIXME
- /*
- if logEntry.EntityPrefix != req.EntityPrefix {
- continue
- }
- */
-
+ if logEntry.EntityPrefix != currentEntityPrefix {
+ continue
+ }
if logEntry.DatetimeStarted.After(then) && !logEntry.Blocked {
executions += 1
@@ -761,28 +761,12 @@ func fail(req *ExecutionRequest, err error) bool {
func stepRequestAction(req *ExecutionRequest) bool {
metricActionsRequested.Inc()
- // If there is no binding or action, do not proceed. Leave default
- // log entry values (icon/title/id) and stop execution gracefully.
- if req.Binding == nil || req.Binding.Action == nil {
- log.Warnf("Action request has no binding/action; skipping execution")
+ if !stepRequestActionHasBinding(req) {
return false
}
- req.logEntry.Binding = req.Binding
- req.logEntry.ActionConfigTitle = req.Binding.Action.Title
- req.logEntry.ActionTitle = tpl.ParseTemplateOfActionBeforeExec(req.Binding.Action.Title, req.Binding.Entity)
- req.logEntry.ActionIcon = req.Binding.Action.Icon
- req.logEntry.Tags = req.Tags
-
- req.executor.logmutex.Lock()
-
- if _, containsKey := req.executor.LogsByBindingId[req.Binding.ID]; !containsKey {
- req.executor.LogsByBindingId[req.Binding.ID] = make([]*InternalLogEntry, 0)
- }
-
- req.executor.LogsByBindingId[req.Binding.ID] = append(req.executor.LogsByBindingId[req.Binding.ID], req.logEntry)
-
- req.executor.logmutex.Unlock()
+ stepRequestActionPopulateLogEntry(req)
+ stepRequestActionRegisterLog(req)
log.WithFields(log.Fields{
"actionTitle": req.logEntry.ActionTitle,
@@ -794,6 +778,35 @@ func stepRequestAction(req *ExecutionRequest) bool {
return true
}
+func stepRequestActionHasBinding(req *ExecutionRequest) bool {
+ if req.Binding == nil || req.Binding.Action == nil {
+ log.Warnf("Action request has no binding/action; skipping execution")
+ return false
+ }
+ return true
+}
+
+func stepRequestActionPopulateLogEntry(req *ExecutionRequest) {
+ req.logEntry.Binding = req.Binding
+ req.logEntry.ActionConfigTitle = req.Binding.Action.Title
+ req.logEntry.ActionTitle = tpl.ParseTemplateOfActionBeforeExec(req.Binding.Action.Title, req.Binding.Entity)
+ req.logEntry.ActionIcon = req.Binding.Action.Icon
+ req.logEntry.Tags = req.Tags
+ if req.Binding.Entity != nil {
+ req.logEntry.EntityPrefix = req.Binding.Entity.UniqueKey
+ }
+}
+
+func stepRequestActionRegisterLog(req *ExecutionRequest) {
+ req.executor.logmutex.Lock()
+ defer req.executor.logmutex.Unlock()
+
+ if _, containsKey := req.executor.LogsByBindingId[req.Binding.ID]; !containsKey {
+ req.executor.LogsByBindingId[req.Binding.ID] = make([]*InternalLogEntry, 0)
+ }
+ req.executor.LogsByBindingId[req.Binding.ID] = append(req.executor.LogsByBindingId[req.Binding.ID], req.logEntry)
+}
+
func stepLogStart(req *ExecutionRequest) bool {
log.WithFields(log.Fields{
"actionTitle": req.logEntry.ActionTitle,
diff --git a/service/internal/executor/executor_actions.go b/service/internal/executor/executor_actions.go
index fdf504c..c55f114 100644
--- a/service/internal/executor/executor_actions.go
+++ b/service/internal/executor/executor_actions.go
@@ -41,7 +41,41 @@ type RebuildActionMapRequest struct {
DashboardActionTitles []string
}
+func validateArgumentDefaults(cfg *config.Config) {
+ if cfg == nil {
+ return
+ }
+ for _, action := range cfg.Actions {
+ validateActionArgumentDefaults(action)
+ }
+}
+
+func validateActionArgumentDefaults(action *config.Action) {
+ if action == nil {
+ return
+ }
+ for i := range action.Arguments {
+ validateArgumentDefault(action, &action.Arguments[i])
+ }
+}
+
+func validateArgumentDefault(action *config.Action, arg *config.ActionArgument) {
+ if arg.Default == "" {
+ return
+ }
+ if err := ValidateArgument(arg, arg.Default, action); err != nil {
+ log.WithFields(log.Fields{
+ "actionTitle": action.Title,
+ "argName": arg.Name,
+ "default": arg.Default,
+ "error": err,
+ }).Warn("Argument default value failed validation")
+ }
+}
+
func (e *Executor) RebuildActionMap() {
+ validateArgumentDefaults(e.Cfg)
+
e.MapActionBindingsLock.Lock()
clear(e.MapActionBindings)
From 03da2ff2e7c8a93f415c464a5044b033fc69c569 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 26 Feb 2026 23:43:50 +0000
Subject: [PATCH 030/148] security: Try to set cookies secure, with force
override option
---
service/internal/api/api.go | 16 ++++++++++++++--
.../auth/otoauth2/restapi_auth_oauth2.go | 8 +++++++-
service/internal/config/config.go | 3 ++-
3 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index b479e96..bf168a7 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -158,7 +158,12 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
return connect.NewResponse(ret), nil
}
-func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool) {
+func (api *oliveTinAPI) cookieSecure(header http.Header) bool {
+ useTLS := header.Get("X-Forwarded-Proto") == "https"
+ return useTLS || api.cfg.Security.ForceSecureCookies
+}
+
+func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest, response *connect.Response[apiv1.LocalUserLoginResponse], match bool, secure bool) {
if match {
user := api.cfg.FindUserByUsername(req.Username)
if user != nil {
@@ -171,6 +176,8 @@ func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest,
MaxAge: 31556952,
HttpOnly: true,
Path: "/",
+ Secure: secure,
+ SameSite: http.SameSiteLaxMode,
}
response.Header().Set("Set-Cookie", cookie.String())
}
@@ -192,7 +199,7 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("checking password: %w", err))
}
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{Success: match})
- api.applyLocalLoginResult(req.Msg, response, match)
+ api.applyLocalLoginResult(req.Msg, response, match, api.cookieSecure(req.Header()))
return response, nil
}
@@ -389,6 +396,7 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
}).Info("Logout: User logged out")
response := connect.NewResponse(&apiv1.LogoutResponse{})
+ secure := api.cookieSecure(req.Header())
// Clear the local authentication cookie by setting it to expire
localCookie := &http.Cookie{
@@ -397,6 +405,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
MaxAge: -1, // This tells the browser to delete the cookie
HttpOnly: true,
Path: "/",
+ Secure: secure,
+ SameSite: http.SameSiteLaxMode,
}
response.Header().Set("Set-Cookie", localCookie.String())
@@ -407,6 +417,8 @@ func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.Logou
MaxAge: -1, // This tells the browser to delete the cookie
HttpOnly: true,
Path: "/",
+ Secure: secure,
+ SameSite: http.SameSiteLaxMode,
}
response.Header().Add("Set-Cookie", oauth2Cookie.String())
diff --git a/service/internal/auth/otoauth2/restapi_auth_oauth2.go b/service/internal/auth/otoauth2/restapi_auth_oauth2.go
index 1dea7d0..5266124 100644
--- a/service/internal/auth/otoauth2/restapi_auth_oauth2.go
+++ b/service/internal/auth/otoauth2/restapi_auth_oauth2.go
@@ -108,14 +108,20 @@ func randString(nByte int) (string, error) {
return base64.URLEncoding.EncodeToString(b), nil
}
+func (h *OAuth2Handler) cookieSecure(r *http.Request) bool {
+ useTLS := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"
+ return useTLS || h.cfg.Security.ForceSecureCookies
+}
+
func (h *OAuth2Handler) setOAuthCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
cookie := &http.Cookie{
Name: name,
Value: value,
MaxAge: 900, // 15 minutes
- Secure: r.TLS != nil,
+ Secure: h.cookieSecure(r),
HttpOnly: true,
Path: "/",
+ SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
diff --git a/service/internal/config/config.go b/service/internal/config/config.go
index aa35153..b235325 100644
--- a/service/internal/config/config.go
+++ b/service/internal/config/config.go
@@ -107,13 +107,14 @@ type PrometheusConfig struct {
DefaultGoMetrics bool `koanf:"defaultGoMetrics"`
}
-// SecurityConfig allows users to fine tune the security related HTTP headers.
+// SecurityConfig allows users to fine tune the security related HTTP headers and cookie options.
type SecurityConfig struct {
HeaderContentSecurityPolicy bool `koanf:"headerContentSecurityPolicy"`
ContentSecurityPolicy string `koanf:"contentSecurityPolicy"`
HeaderXContentTypeOptions bool `koanf:"headerXContentTypeOptions"`
HeaderXFrameOptions bool `koanf:"headerXFrameOptions"`
XFrameOptions string `koanf:"xFrameOptions"`
+ ForceSecureCookies bool `koanf:"forceSecureCookies"`
}
// Config is the global config used through the whole app.
From 54eb2a658681b232cad8abe3cf15ee0b5dda0884 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Fri, 27 Feb 2026 00:10:45 +0000
Subject: [PATCH 031/148] fix: User login log message fixed when password
matches, but user lookup fails
---
service/internal/api/api.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index bf168a7..e2b235f 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -180,8 +180,10 @@ func (api *oliveTinAPI) applyLocalLoginResult(req *apiv1.LocalUserLoginRequest,
SameSite: http.SameSiteLaxMode,
}
response.Header().Set("Set-Cookie", cookie.String())
+ log.WithFields(log.Fields{"username": user.Username}).Info("LocalUserLogin: User logged in successfully.")
+ } else {
+ log.WithFields(log.Fields{"username": req.Username}).Warn("LocalUserLogin: Password matched but user lookup failed.")
}
- log.WithFields(log.Fields{"username": req.Username}).Info("LocalUserLogin: User logged in successfully.")
} else {
log.WithFields(log.Fields{"username": req.Username}).Warn("LocalUserLogin: User login failed.")
}
From 1c32a389b37689bf2fbb13c6afbf31c926315912 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 27 Feb 2026 00:25:25 +0000
Subject: [PATCH 032/148] chore(deps): bump minimatch from 3.1.2 to 3.1.5 in
/frontend
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5.
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)
---
updated-dependencies:
- dependency-name: minimatch
dependency-version: 3.1.5
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
---
frontend/package-lock.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3bc98c8..041f6bc 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -4479,9 +4479,9 @@
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
From f41fe2caba751c70ffb98a1271234ba32c4b3e4f Mon Sep 17 00:00:00 2001
From: James Read
Date: Fri, 27 Feb 2026 11:21:34 +0000
Subject: [PATCH 033/148] docs: Add autonomy level to README
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 3bc9214..792d035 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
[](https://bestpractices.coreinfrastructure.org/projects/5050)
[](https://goreportcard.com/report/github.com/OliveTin/OliveTin)
+[-blue)](https://blog.jread.com/posts/ai-levels-of-autonomy-in-software-engineering/)
[OliveTin 2k to 3k upgrade guide](https://docs.olivetin.app/upgrade/2k3k.html)
From aa2bd95ccb8f00a1c97ebcb0a03e8d49867ffde6 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Fri, 27 Feb 2026 21:26:55 +0000
Subject: [PATCH 034/148] feat(policy): add policy to show/hide version number
Made-with: Cursor
---
.../gen/olivetin/api/v1/olivetin_pb.d.ts | 6 ++++-
.../gen/olivetin/api/v1/olivetin_pb.js | 3 +--
frontend/resources/vue/App.vue | 20 ++++++++--------
.../resources/vue/views/DiagnosticsView.vue | 7 ++++--
proto/olivetin/api/v1/olivetin.proto | 1 +
service/gen/olivetin/api/v1/olivetin.pb.go | 23 +++++++++++++------
service/internal/api/api.go | 17 ++++++++++----
service/internal/api/apiActions.go | 5 ++--
.../auth/authpublic/authenticateduser.go | 9 ++++++--
service/internal/config/config.go | 6 +++--
.../internal/installationinfo/sosreport.go | 14 ++++++++---
11 files changed, 77 insertions(+), 34 deletions(-)
diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts
index 8e308fe..3961635 100644
--- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts
+++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.d.ts
@@ -210,6 +210,11 @@ export declare type EffectivePolicy = Message<"olivetin.api.v1.EffectivePolicy">
* @generated from field: bool show_log_list = 2;
*/
showLogList: boolean;
+
+ /**
+ * @generated from field: bool show_version_number = 3;
+ */
+ showVersionNumber: boolean;
};
/**
@@ -1853,4 +1858,3 @@ export declare const OliveTinApiService: GenService<{
output: typeof EntitySchema;
},
}>;
-
diff --git a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js
index 4e6e723..dd61f31 100644
--- a/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js
+++ b/frontend/resources/scripts/gen/olivetin/api/v1/olivetin_pb.js
@@ -8,7 +8,7 @@ import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2
* Describes the file olivetin/api/v1/olivetin.proto.
*/
export const file_olivetin_api_v1_olivetin = /*@__PURE__*/
- fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSLcAQoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkiuwIKDkFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEdHlwZRgDIAEoCRIVCg1kZWZhdWx0X3ZhbHVlGAQgASgJEjYKB2Nob2ljZXMYBSADKAsyJS5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnRDaG9pY2USEwoLZGVzY3JpcHRpb24YBiABKAkSRQoLc3VnZ2VzdGlvbnMYByADKAsyMC5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQuU3VnZ2VzdGlvbnNFbnRyeRIfChdzdWdnZXN0aW9uc19icm93c2VyX2tleRgIIAEoCRoyChBTdWdnZXN0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNAoUQWN0aW9uQXJndW1lbnRDaG9pY2USDQoFdmFsdWUYASABKAkSDQoFdGl0bGUYAiABKAkisgEKBkVudGl0eRINCgV0aXRsZRgBIAEoCRISCgp1bmlxdWVfa2V5GAIgASgJEgwKBHR5cGUYAyABKAkSEwoLZGlyZWN0b3JpZXMYBCADKAkSMwoGZmllbGRzGAUgAygLMiMub2xpdmV0aW4uYXBpLnYxLkVudGl0eS5GaWVsZHNFbnRyeRotCgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIlQKFEdldERhc2hib2FyZFJlc3BvbnNlEg0KBXRpdGxlGAEgASgJEi0KCWRhc2hib2FyZBgEIAEoCzIaLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmQiQgoPRWZmZWN0aXZlUG9saWN5EhgKEHNob3dfZGlhZ25vc3RpY3MYASABKAgSFQoNc2hvd19sb2dfbGlzdBgCIAEoCCJNChNHZXREYXNoYm9hcmRSZXF1ZXN0Eg0KBXRpdGxlGAEgASgJEhMKC2VudGl0eV90eXBlGAIgASgJEhIKCmVudGl0eV9rZXkYAyABKAkiUQoJRGFzaGJvYXJkEg0KBXRpdGxlGAEgASgJEjUKCGNvbnRlbnRzGAIgAygLMiMub2xpdmV0aW4uYXBpLnYxLkRhc2hib2FyZENvbXBvbmVudCLbAQoSRGFzaGJvYXJkQ29tcG9uZW50Eg0KBXRpdGxlGAEgASgJEgwKBHR5cGUYAiABKAkSNQoIY29udGVudHMYAyADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50EgwKBGljb24YBCABKAkSEQoJY3NzX2NsYXNzGAUgASgJEicKBmFjdGlvbhgGIAEoCzIXLm9saXZldGluLmFwaS52MS5BY3Rpb24SEwoLZW50aXR5X3R5cGUYByABKAkSEgoKZW50aXR5X2tleRgIIAEoCSJ9ChJTdGFydEFjdGlvblJlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCRI3Cglhcmd1bWVudHMYAiADKAsyJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25Bcmd1bWVudBIaChJ1bmlxdWVfdHJhY2tpbmdfaWQYAyABKAkiMgoTU3RhcnRBY3Rpb25Bcmd1bWVudBIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIjQKE1N0YXJ0QWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJImcKGVN0YXJ0QWN0aW9uQW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50IkoKGlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIsChdTdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiOQoYU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgCIAEoCSIzCh5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJIk8KH1N0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5Ik4KDkdldExvZ3NSZXF1ZXN0EhQKDHN0YXJ0X29mZnNldBgBIAEoAxITCgtkYXRlX2ZpbHRlchgCIAEoCRIRCglwYWdlX3NpemUYAyABKAMimgMKCExvZ0VudHJ5EhgKEGRhdGV0aW1lX3N0YXJ0ZWQYASABKAkSFAoMYWN0aW9uX3RpdGxlGAIgASgJEg4KBm91dHB1dBgDIAEoCRIRCgl0aW1lZF9vdXQYBSABKAgSEQoJZXhpdF9jb2RlGAYgASgFEgwKBHVzZXIYByABKAkSEgoKdXNlcl9jbGFzcxgIIAEoCRITCgthY3Rpb25faWNvbhgJIAEoCRIMCgR0YWdzGAogAygJEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgLIAEoCRIZChFkYXRldGltZV9maW5pc2hlZBgMIAEoCRIZChFleGVjdXRpb25fc3RhcnRlZBgOIAEoCBIaChJleGVjdXRpb25fZmluaXNoZWQYDyABKAgSDwoHYmxvY2tlZBgQIAEoCBIWCg5kYXRldGltZV9pbmRleBgRIAEoAxIQCghjYW5fa2lsbBgSIAEoCBIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYEyABKAkSEgoKYmluZGluZ19pZBgUIAEoCSKRAQoPR2V0TG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiPwoUR2V0QWN0aW9uTG9nc1JlcXVlc3QSEQoJYWN0aW9uX2lkGAEgASgJEhQKDHN0YXJ0X29mZnNldBgCIAEoAyKXAQoVR2V0QWN0aW9uTG9nc1Jlc3BvbnNlEicKBGxvZ3MYASADKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkSFwoPY291bnRfcmVtYWluaW5nGAIgASgDEhEKCXBhZ2Vfc2l6ZRgDIAEoAxITCgt0b3RhbF9jb3VudBgEIAEoAxIUCgxzdGFydF9vZmZzZXQYBSABKAMiZQobVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Eg0KBXZhbHVlGAEgASgJEgwKBHR5cGUYAiABKAkSEgoKYmluZGluZ19pZBgDIAEoCRIVCg1hcmd1bWVudF9uYW1lGAQgASgJIkIKHFZhbGlkYXRlQXJndW1lbnRUeXBlUmVzcG9uc2USDQoFdmFsaWQYASABKAgSEwoLZGVzY3JpcHRpb24YAiABKAkiNgoVV2F0Y2hFeGVjdXRpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCSImChRXYXRjaEV4ZWN1dGlvblVwZGF0ZRIOCgZ1cGRhdGUYASABKAkiSgoWRXhlY3V0aW9uU3RhdHVzUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSEQoJYWN0aW9uX2lkGAIgASgJIkcKF0V4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIPCg1XaG9BbUlSZXF1ZXN0ImwKDldob0FtSVJlc3BvbnNlEhoKEmF1dGhlbnRpY2F0ZWRfdXNlchgBIAEoCRIRCgl1c2VyZ3JvdXAYAiABKAkSEAoIcHJvdmlkZXIYAyABKAkSDAoEYWNscxgEIAMoCRILCgNzaWQYBSABKAkiEgoQU29zUmVwb3J0UmVxdWVzdCIiChFTb3NSZXBvcnRSZXNwb25zZRINCgVhbGVydBgBIAEoCSIRCg9EdW1wVmFyc1JlcXVlc3QilQEKEER1bXBWYXJzUmVzcG9uc2USDQoFYWxlcnQYASABKAkSQQoIY29udGVudHMYAiADKAsyLy5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXNwb25zZS5Db250ZW50c0VudHJ5Gi8KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASI7CgxEZWJ1Z0JpbmRpbmcSFAoMYWN0aW9uX3RpdGxlGAEgASgJEhUKDWVudGl0eV9wcmVmaXgYAiABKAkiHgocRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdCLOAQodRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2USDQoFYWxlcnQYASABKAkSTgoIY29udGVudHMYAiADKAsyPC5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UuQ29udGVudHNFbnRyeRpOCg1Db250ZW50c0VudHJ5EgsKA2tleRgBIAEoCRIsCgV2YWx1ZRgCIAEoCzIdLm9saXZldGluLmFwaS52MS5EZWJ1Z0JpbmRpbmc6AjgBIhIKEEdldFJlYWR5elJlcXVlc3QiIwoRR2V0UmVhZHl6UmVzcG9uc2USDgoGc3RhdHVzGAEgASgJIhQKEkV2ZW50U3RyZWFtUmVxdWVzdCLjAgoTRXZlbnRTdHJlYW1SZXNwb25zZRI9Cg5lbnRpdHlfY2hhbmdlZBgCIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudEVudGl0eUNoYW5nZWRIABI9Cg5jb25maWdfY2hhbmdlZBgDIAEoCzIjLm9saXZldGluLmFwaS52MS5FdmVudENvbmZpZ0NoYW5nZWRIABJFChJleGVjdXRpb25fZmluaXNoZWQYBCABKAsyJy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25GaW5pc2hlZEgAEkMKEWV4ZWN1dGlvbl9zdGFydGVkGAUgASgLMiYub2xpdmV0aW4uYXBpLnYxLkV2ZW50RXhlY3V0aW9uU3RhcnRlZEgAEjkKDG91dHB1dF9jaHVuaxgGIAEoCzIhLm9saXZldGluLmFwaS52MS5FdmVudE91dHB1dENodW5rSABCBwoFZXZlbnQiQQoQRXZlbnRPdXRwdXRDaHVuaxIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkSDgoGb3V0cHV0GAIgASgJIhQKEkV2ZW50RW50aXR5Q2hhbmdlZCIUChJFdmVudENvbmZpZ0NoYW5nZWQiRgoWRXZlbnRFeGVjdXRpb25GaW5pc2hlZBIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiRQoVRXZlbnRFeGVjdXRpb25TdGFydGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSIyChFLaWxsQWN0aW9uUmVxdWVzdBIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYASABKAkibQoSS2lsbEFjdGlvblJlc3BvbnNlEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZraWxsZWQYAiABKAgSGQoRYWxyZWFkeV9jb21wbGV0ZWQYAyABKAgSDQoFZm91bmQYBCABKAgiOwoVTG9jYWxVc2VyTG9naW5SZXF1ZXN0EhAKCHVzZXJuYW1lGAEgASgJEhAKCHBhc3N3b3JkGAIgASgJIikKFkxvY2FsVXNlckxvZ2luUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCInChNQYXNzd29yZEhhc2hSZXF1ZXN0EhAKCHBhc3N3b3JkGAEgASgJIiQKFFBhc3N3b3JkSGFzaFJlc3BvbnNlEgwKBGhhc2gYASABKAkiDwoNTG9nb3V0UmVxdWVzdCIQCg5Mb2dvdXRSZXNwb25zZSIXChVHZXREaWFnbm9zdGljc1JlcXVlc3QiRQoWR2V0RGlhZ25vc3RpY3NSZXNwb25zZRITCgtTc2hGb3VuZEtleRgBIAEoCRIWCg5Tc2hGb3VuZENvbmZpZxgCIAEoCSINCgtJbml0UmVxdWVzdCLrBQoMSW5pdFJlc3BvbnNlEhIKCnNob3dGb290ZXIYASABKAgSFgoOc2hvd05hdmlnYXRpb24YAiABKAgSFwoPc2hvd05ld1ZlcnNpb25zGAMgASgIEhgKEGF2YWlsYWJsZVZlcnNpb24YBCABKAkSFgoOY3VycmVudFZlcnNpb24YBSABKAkSEQoJcGFnZVRpdGxlGAYgASgJEh4KFnNlY3Rpb25OYXZpZ2F0aW9uU3R5bGUYByABKAkSGgoSZGVmYXVsdEljb25Gb3JCYWNrGAggASgJEhYKDmVuYWJsZUN1c3RvbUpzGAkgASgIEhQKDGF1dGhMb2dpblVybBgKIAEoCRIWCg5hdXRoTG9jYWxMb2dpbhgLIAEoCBIRCglzdHlsZU1vZHMYDCADKAkSOAoPb0F1dGgyUHJvdmlkZXJzGA0gAygLMh8ub2xpdmV0aW4uYXBpLnYxLk9BdXRoMlByb3ZpZGVyEjgKD2FkZGl0aW9uYWxMaW5rcxgOIAMoCzIfLm9saXZldGluLmFwaS52MS5BZGRpdGlvbmFsTGluaxIWCg5yb290RGFzaGJvYXJkcxgPIAMoCRIaChJhdXRoZW50aWNhdGVkX3VzZXIYECABKAkSIwobYXV0aGVudGljYXRlZF91c2VyX3Byb3ZpZGVyGBEgASgJEjoKEGVmZmVjdGl2ZV9wb2xpY3kYEiABKAsyIC5vbGl2ZXRpbi5hcGkudjEuRWZmZWN0aXZlUG9saWN5EhYKDmJhbm5lcl9tZXNzYWdlGBMgASgJEhIKCmJhbm5lcl9jc3MYFCABKAkSGAoQc2hvd19kaWFnbm9zdGljcxgVIAEoCBIVCg1zaG93X2xvZ19saXN0GBYgASgIEhYKDmxvZ2luX3JlcXVpcmVkGBcgASgIEhgKEGF2YWlsYWJsZV90aGVtZXMYGCADKAkSJAocc2hvd19uYXZpZ2F0ZV9vbl9zdGFydF9pY29ucxgZIAEoCCIsCg5BZGRpdGlvbmFsTGluaxINCgV0aXRsZRgBIAEoCRILCgN1cmwYAiABKAkiOgoOT0F1dGgyUHJvdmlkZXISDQoFdGl0bGUYASABKAkSDAoEaWNvbhgDIAEoCRILCgNrZXkYBCABKAkiLQoXR2V0QWN0aW9uQmluZGluZ1JlcXVlc3QSEgoKYmluZGluZ19pZBgBIAEoCSJDChhHZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2USJwoGYWN0aW9uGAEgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbiIUChJHZXRFbnRpdGllc1JlcXVlc3QiVAoTR2V0RW50aXRpZXNSZXNwb25zZRI9ChJlbnRpdHlfZGVmaW5pdGlvbnMYASADKAsyIS5vbGl2ZXRpbi5hcGkudjEuRW50aXR5RGVmaW5pdGlvbiJpChBFbnRpdHlEZWZpbml0aW9uEg0KBXRpdGxlGAEgASgJEioKCWluc3RhbmNlcxgCIAMoCzIXLm9saXZldGluLmFwaS52MS5FbnRpdHkSGgoSdXNlZF9vbl9kYXNoYm9hcmRzGAMgAygJIjQKEEdldEVudGl0eVJlcXVlc3QSEgoKdW5pcXVlX2tleRgBIAEoCRIMCgR0eXBlGAIgASgJIjUKFFJlc3RhcnRBY3Rpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCTLoEgoST2xpdmVUaW5BcGlTZXJ2aWNlEl0KDEdldERhc2hib2FyZBIkLm9saXZldGluLmFwaS52MS5HZXREYXNoYm9hcmRSZXF1ZXN0GiUub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlc3BvbnNlIgASWgoLU3RhcnRBY3Rpb24SIy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJvChJTdGFydEFjdGlvbkFuZFdhaXQSKi5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25BbmRXYWl0UmVxdWVzdBorLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXNwb25zZSIAEmkKEFN0YXJ0QWN0aW9uQnlHZXQSKC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlcXVlc3QaKS5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldFJlc3BvbnNlIgASfgoXU3RhcnRBY3Rpb25CeUdldEFuZFdhaXQSLy5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXF1ZXN0GjAub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVzcG9uc2UiABJeCg1SZXN0YXJ0QWN0aW9uEiUub2xpdmV0aW4uYXBpLnYxLlJlc3RhcnRBY3Rpb25SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uUmVzcG9uc2UiABJXCgpLaWxsQWN0aW9uEiIub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXF1ZXN0GiMub2xpdmV0aW4uYXBpLnYxLktpbGxBY3Rpb25SZXNwb25zZSIAEmYKD0V4ZWN1dGlvblN0YXR1cxInLm9saXZldGluLmFwaS52MS5FeGVjdXRpb25TdGF0dXNSZXF1ZXN0Gigub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1Jlc3BvbnNlIgASTgoHR2V0TG9ncxIfLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVxdWVzdBogLm9saXZldGluLmFwaS52MS5HZXRMb2dzUmVzcG9uc2UiABJgCg1HZXRBY3Rpb25Mb2dzEiUub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXF1ZXN0GiYub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkxvZ3NSZXNwb25zZSIAEnUKFFZhbGlkYXRlQXJndW1lbnRUeXBlEiwub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVxdWVzdBotLm9saXZldGluLmFwaS52MS5WYWxpZGF0ZUFyZ3VtZW50VHlwZVJlc3BvbnNlIgASSwoGV2hvQW1JEh4ub2xpdmV0aW4uYXBpLnYxLldob0FtSVJlcXVlc3QaHy5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVzcG9uc2UiABJUCglTb3NSZXBvcnQSIS5vbGl2ZXRpbi5hcGkudjEuU29zUmVwb3J0UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXNwb25zZSIAElEKCER1bXBWYXJzEiAub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVxdWVzdBohLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlIgASeAoVRHVtcFB1YmxpY0lkQWN0aW9uTWFwEi0ub2xpdmV0aW4uYXBpLnYxLkR1bXBQdWJsaWNJZEFjdGlvbk1hcFJlcXVlc3QaLi5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVzcG9uc2UiABJUCglHZXRSZWFkeXoSIS5vbGl2ZXRpbi5hcGkudjEuR2V0UmVhZHl6UmVxdWVzdBoiLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXNwb25zZSIAEmMKDkxvY2FsVXNlckxvZ2luEiYub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5Mb2NhbFVzZXJMb2dpblJlc3BvbnNlIgASXQoMUGFzc3dvcmRIYXNoEiQub2xpdmV0aW4uYXBpLnYxLlBhc3N3b3JkSGFzaFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVzcG9uc2UiABJLCgZMb2dvdXQSHi5vbGl2ZXRpbi5hcGkudjEuTG9nb3V0UmVxdWVzdBofLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXNwb25zZSIAElwKC0V2ZW50U3RyZWFtEiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5FdmVudFN0cmVhbVJlc3BvbnNlIgAwARJjCg5HZXREaWFnbm9zdGljcxImLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1JlcXVlc3QaJy5vbGl2ZXRpbi5hcGkudjEuR2V0RGlhZ25vc3RpY3NSZXNwb25zZSIAEkUKBEluaXQSHC5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlcXVlc3QaHS5vbGl2ZXRpbi5hcGkudjEuSW5pdFJlc3BvbnNlIgASaQoQR2V0QWN0aW9uQmluZGluZxIoLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBopLm9saXZldGluLmFwaS52MS5HZXRBY3Rpb25CaW5kaW5nUmVzcG9uc2UiABJaCgtHZXRFbnRpdGllcxIjLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1JlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuR2V0RW50aXRpZXNSZXNwb25zZSIAEkkKCUdldEVudGl0eRIhLm9saXZldGluLmFwaS52MS5HZXRFbnRpdHlSZXF1ZXN0Ghcub2xpdmV0aW4uYXBpLnYxLkVudGl0eSIAQjhaNmdpdGh1Yi5jb20vT2xpdmVUaW4vT2xpdmVUaW4vZ2VuL29saXZldGluL2FwaS92MTthcGl2MWIGcHJvdG8z");
+ fileDesc("Ch5vbGl2ZXRpbi9hcGkvdjEvb2xpdmV0aW4ucHJvdG8SD29saXZldGluLmFwaS52MSLcAQoGQWN0aW9uEhIKCmJpbmRpbmdfaWQYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEaWNvbhgDIAEoCRIQCghjYW5fZXhlYxgEIAEoCBIyCglhcmd1bWVudHMYBSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQSFgoOcG9wdXBfb25fc3RhcnQYBiABKAkSDQoFb3JkZXIYByABKAUSDwoHdGltZW91dBgIIAEoBRIjChtkYXRldGltZV9yYXRlX2xpbWl0X2V4cGlyZXMYCSABKAkiuwIKDkFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdGl0bGUYAiABKAkSDAoEdHlwZRgDIAEoCRIVCg1kZWZhdWx0X3ZhbHVlGAQgASgJEjYKB2Nob2ljZXMYBSADKAsyJS5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnRDaG9pY2USEwoLZGVzY3JpcHRpb24YBiABKAkSRQoLc3VnZ2VzdGlvbnMYByADKAsyMC5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uQXJndW1lbnQuU3VnZ2VzdGlvbnNFbnRyeRIfChdzdWdnZXN0aW9uc19icm93c2VyX2tleRgIIAEoCRoyChBTdWdnZXN0aW9uc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiNAoUQWN0aW9uQXJndW1lbnRDaG9pY2USDQoFdmFsdWUYASABKAkSDQoFdGl0bGUYAiABKAkisgEKBkVudGl0eRINCgV0aXRsZRgBIAEoCRISCgp1bmlxdWVfa2V5GAIgASgJEgwKBHR5cGUYAyABKAkSEwoLZGlyZWN0b3JpZXMYBCADKAkSMwoGZmllbGRzGAUgAygLMiMub2xpdmV0aW4uYXBpLnYxLkVudGl0eS5GaWVsZHNFbnRyeRotCgtGaWVsZHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIlQKFEdldERhc2hib2FyZFJlc3BvbnNlEg0KBXRpdGxlGAEgASgJEi0KCWRhc2hib2FyZBgEIAEoCzIaLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmQiXwoPRWZmZWN0aXZlUG9saWN5EhgKEHNob3dfZGlhZ25vc3RpY3MYASABKAgSFQoNc2hvd19sb2dfbGlzdBgCIAEoCBIbChNzaG93X3ZlcnNpb25fbnVtYmVyGAMgASgIIk0KE0dldERhc2hib2FyZFJlcXVlc3QSDQoFdGl0bGUYASABKAkSEwoLZW50aXR5X3R5cGUYAiABKAkSEgoKZW50aXR5X2tleRgDIAEoCSJRCglEYXNoYm9hcmQSDQoFdGl0bGUYASABKAkSNQoIY29udGVudHMYAiADKAsyIy5vbGl2ZXRpbi5hcGkudjEuRGFzaGJvYXJkQ29tcG9uZW50ItsBChJEYXNoYm9hcmRDb21wb25lbnQSDQoFdGl0bGUYASABKAkSDAoEdHlwZRgCIAEoCRI1Cghjb250ZW50cxgDIAMoCzIjLm9saXZldGluLmFwaS52MS5EYXNoYm9hcmRDb21wb25lbnQSDAoEaWNvbhgEIAEoCRIRCgljc3NfY2xhc3MYBSABKAkSJwoGYWN0aW9uGAYgASgLMhcub2xpdmV0aW4uYXBpLnYxLkFjdGlvbhITCgtlbnRpdHlfdHlwZRgHIAEoCRISCgplbnRpdHlfa2V5GAggASgJIn0KElN0YXJ0QWN0aW9uUmVxdWVzdBISCgpiaW5kaW5nX2lkGAEgASgJEjcKCWFyZ3VtZW50cxgCIAMoCzIkLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFyZ3VtZW50EhoKEnVuaXF1ZV90cmFja2luZ19pZBgDIAEoCSIyChNTdGFydEFjdGlvbkFyZ3VtZW50EgwKBG5hbWUYASABKAkSDQoFdmFsdWUYAiABKAkiNAoTU3RhcnRBY3Rpb25SZXNwb25zZRIdChVleGVjdXRpb25fdHJhY2tpbmdfaWQYAiABKAkiZwoZU3RhcnRBY3Rpb25BbmRXYWl0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkSNwoJYXJndW1lbnRzGAIgAygLMiQub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQXJndW1lbnQiSgoaU3RhcnRBY3Rpb25BbmRXYWl0UmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IiwKF1N0YXJ0QWN0aW9uQnlHZXRSZXF1ZXN0EhEKCWFjdGlvbl9pZBgBIAEoCSI5ChhTdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAIgASgJIjMKHlN0YXJ0QWN0aW9uQnlHZXRBbmRXYWl0UmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkiTwofU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZRIsCglsb2dfZW50cnkYASABKAsyGS5vbGl2ZXRpbi5hcGkudjEuTG9nRW50cnkiTgoOR2V0TG9nc1JlcXVlc3QSFAoMc3RhcnRfb2Zmc2V0GAEgASgDEhMKC2RhdGVfZmlsdGVyGAIgASgJEhEKCXBhZ2Vfc2l6ZRgDIAEoAyKaAwoITG9nRW50cnkSGAoQZGF0ZXRpbWVfc3RhcnRlZBgBIAEoCRIUCgxhY3Rpb25fdGl0bGUYAiABKAkSDgoGb3V0cHV0GAMgASgJEhEKCXRpbWVkX291dBgFIAEoCBIRCglleGl0X2NvZGUYBiABKAUSDAoEdXNlchgHIAEoCRISCgp1c2VyX2NsYXNzGAggASgJEhMKC2FjdGlvbl9pY29uGAkgASgJEgwKBHRhZ3MYCiADKAkSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAsgASgJEhkKEWRhdGV0aW1lX2ZpbmlzaGVkGAwgASgJEhkKEWV4ZWN1dGlvbl9zdGFydGVkGA4gASgIEhoKEmV4ZWN1dGlvbl9maW5pc2hlZBgPIAEoCBIPCgdibG9ja2VkGBAgASgIEhYKDmRhdGV0aW1lX2luZGV4GBEgASgDEhAKCGNhbl9raWxsGBIgASgIEiMKG2RhdGV0aW1lX3JhdGVfbGltaXRfZXhwaXJlcxgTIAEoCRISCgpiaW5kaW5nX2lkGBQgASgJIpEBCg9HZXRMb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyI/ChRHZXRBY3Rpb25Mb2dzUmVxdWVzdBIRCglhY3Rpb25faWQYASABKAkSFAoMc3RhcnRfb2Zmc2V0GAIgASgDIpcBChVHZXRBY3Rpb25Mb2dzUmVzcG9uc2USJwoEbG9ncxgBIAMoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeRIXCg9jb3VudF9yZW1haW5pbmcYAiABKAMSEQoJcGFnZV9zaXplGAMgASgDEhMKC3RvdGFsX2NvdW50GAQgASgDEhQKDHN0YXJ0X29mZnNldBgFIAEoAyJlChtWYWxpZGF0ZUFyZ3VtZW50VHlwZVJlcXVlc3QSDQoFdmFsdWUYASABKAkSDAoEdHlwZRgCIAEoCRISCgpiaW5kaW5nX2lkGAMgASgJEhUKDWFyZ3VtZW50X25hbWUYBCABKAkiQgocVmFsaWRhdGVBcmd1bWVudFR5cGVSZXNwb25zZRINCgV2YWxpZBgBIAEoCBITCgtkZXNjcmlwdGlvbhgCIAEoCSI2ChVXYXRjaEV4ZWN1dGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJIiYKFFdhdGNoRXhlY3V0aW9uVXBkYXRlEg4KBnVwZGF0ZRgBIAEoCSJKChZFeGVjdXRpb25TdGF0dXNSZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIRCglhY3Rpb25faWQYAiABKAkiRwoXRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2USLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5Ig8KDVdob0FtSVJlcXVlc3QibAoOV2hvQW1JUmVzcG9uc2USGgoSYXV0aGVudGljYXRlZF91c2VyGAEgASgJEhEKCXVzZXJncm91cBgCIAEoCRIQCghwcm92aWRlchgDIAEoCRIMCgRhY2xzGAQgAygJEgsKA3NpZBgFIAEoCSISChBTb3NSZXBvcnRSZXF1ZXN0IiIKEVNvc1JlcG9ydFJlc3BvbnNlEg0KBWFsZXJ0GAEgASgJIhEKD0R1bXBWYXJzUmVxdWVzdCKVAQoQRHVtcFZhcnNSZXNwb25zZRINCgVhbGVydBgBIAEoCRJBCghjb250ZW50cxgCIAMoCzIvLm9saXZldGluLmFwaS52MS5EdW1wVmFyc1Jlc3BvbnNlLkNvbnRlbnRzRW50cnkaLwoNQ29udGVudHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIjsKDERlYnVnQmluZGluZxIUCgxhY3Rpb25fdGl0bGUYASABKAkSFQoNZW50aXR5X3ByZWZpeBgCIAEoCSIeChxEdW1wUHVibGljSWRBY3Rpb25NYXBSZXF1ZXN0Is4BCh1EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZRINCgVhbGVydBgBIAEoCRJOCghjb250ZW50cxgCIAMoCzI8Lm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZS5Db250ZW50c0VudHJ5Gk4KDUNvbnRlbnRzRW50cnkSCwoDa2V5GAEgASgJEiwKBXZhbHVlGAIgASgLMh0ub2xpdmV0aW4uYXBpLnYxLkRlYnVnQmluZGluZzoCOAEiEgoQR2V0UmVhZHl6UmVxdWVzdCIjChFHZXRSZWFkeXpSZXNwb25zZRIOCgZzdGF0dXMYASABKAkiFAoSRXZlbnRTdHJlYW1SZXF1ZXN0IuMCChNFdmVudFN0cmVhbVJlc3BvbnNlEj0KDmVudGl0eV9jaGFuZ2VkGAIgASgLMiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50RW50aXR5Q2hhbmdlZEgAEj0KDmNvbmZpZ19jaGFuZ2VkGAMgASgLMiMub2xpdmV0aW4uYXBpLnYxLkV2ZW50Q29uZmlnQ2hhbmdlZEgAEkUKEmV4ZWN1dGlvbl9maW5pc2hlZBgEIAEoCzInLm9saXZldGluLmFwaS52MS5FdmVudEV4ZWN1dGlvbkZpbmlzaGVkSAASQwoRZXhlY3V0aW9uX3N0YXJ0ZWQYBSABKAsyJi5vbGl2ZXRpbi5hcGkudjEuRXZlbnRFeGVjdXRpb25TdGFydGVkSAASOQoMb3V0cHV0X2NodW5rGAYgASgLMiEub2xpdmV0aW4uYXBpLnYxLkV2ZW50T3V0cHV0Q2h1bmtIAEIHCgVldmVudCJBChBFdmVudE91dHB1dENodW5rEh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCRIOCgZvdXRwdXQYAiABKAkiFAoSRXZlbnRFbnRpdHlDaGFuZ2VkIhQKEkV2ZW50Q29uZmlnQ2hhbmdlZCJGChZFdmVudEV4ZWN1dGlvbkZpbmlzaGVkEiwKCWxvZ19lbnRyeRgBIAEoCzIZLm9saXZldGluLmFwaS52MS5Mb2dFbnRyeSJFChVFdmVudEV4ZWN1dGlvblN0YXJ0ZWQSLAoJbG9nX2VudHJ5GAEgASgLMhkub2xpdmV0aW4uYXBpLnYxLkxvZ0VudHJ5IjIKEUtpbGxBY3Rpb25SZXF1ZXN0Eh0KFWV4ZWN1dGlvbl90cmFja2luZ19pZBgBIAEoCSJtChJLaWxsQWN0aW9uUmVzcG9uc2USHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJEg4KBmtpbGxlZBgCIAEoCBIZChFhbHJlYWR5X2NvbXBsZXRlZBgDIAEoCBINCgVmb3VuZBgEIAEoCCI7ChVMb2NhbFVzZXJMb2dpblJlcXVlc3QSEAoIdXNlcm5hbWUYASABKAkSEAoIcGFzc3dvcmQYAiABKAkiKQoWTG9jYWxVc2VyTG9naW5SZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIicKE1Bhc3N3b3JkSGFzaFJlcXVlc3QSEAoIcGFzc3dvcmQYASABKAkiJAoUUGFzc3dvcmRIYXNoUmVzcG9uc2USDAoEaGFzaBgBIAEoCSIPCg1Mb2dvdXRSZXF1ZXN0IhAKDkxvZ291dFJlc3BvbnNlIhcKFUdldERpYWdub3N0aWNzUmVxdWVzdCJFChZHZXREaWFnbm9zdGljc1Jlc3BvbnNlEhMKC1NzaEZvdW5kS2V5GAEgASgJEhYKDlNzaEZvdW5kQ29uZmlnGAIgASgJIg0KC0luaXRSZXF1ZXN0IusFCgxJbml0UmVzcG9uc2USEgoKc2hvd0Zvb3RlchgBIAEoCBIWCg5zaG93TmF2aWdhdGlvbhgCIAEoCBIXCg9zaG93TmV3VmVyc2lvbnMYAyABKAgSGAoQYXZhaWxhYmxlVmVyc2lvbhgEIAEoCRIWCg5jdXJyZW50VmVyc2lvbhgFIAEoCRIRCglwYWdlVGl0bGUYBiABKAkSHgoWc2VjdGlvbk5hdmlnYXRpb25TdHlsZRgHIAEoCRIaChJkZWZhdWx0SWNvbkZvckJhY2sYCCABKAkSFgoOZW5hYmxlQ3VzdG9tSnMYCSABKAgSFAoMYXV0aExvZ2luVXJsGAogASgJEhYKDmF1dGhMb2NhbExvZ2luGAsgASgIEhEKCXN0eWxlTW9kcxgMIAMoCRI4Cg9vQXV0aDJQcm92aWRlcnMYDSADKAsyHy5vbGl2ZXRpbi5hcGkudjEuT0F1dGgyUHJvdmlkZXISOAoPYWRkaXRpb25hbExpbmtzGA4gAygLMh8ub2xpdmV0aW4uYXBpLnYxLkFkZGl0aW9uYWxMaW5rEhYKDnJvb3REYXNoYm9hcmRzGA8gAygJEhoKEmF1dGhlbnRpY2F0ZWRfdXNlchgQIAEoCRIjChthdXRoZW50aWNhdGVkX3VzZXJfcHJvdmlkZXIYESABKAkSOgoQZWZmZWN0aXZlX3BvbGljeRgSIAEoCzIgLm9saXZldGluLmFwaS52MS5FZmZlY3RpdmVQb2xpY3kSFgoOYmFubmVyX21lc3NhZ2UYEyABKAkSEgoKYmFubmVyX2NzcxgUIAEoCRIYChBzaG93X2RpYWdub3N0aWNzGBUgASgIEhUKDXNob3dfbG9nX2xpc3QYFiABKAgSFgoObG9naW5fcmVxdWlyZWQYFyABKAgSGAoQYXZhaWxhYmxlX3RoZW1lcxgYIAMoCRIkChxzaG93X25hdmlnYXRlX29uX3N0YXJ0X2ljb25zGBkgASgIIiwKDkFkZGl0aW9uYWxMaW5rEg0KBXRpdGxlGAEgASgJEgsKA3VybBgCIAEoCSI6Cg5PQXV0aDJQcm92aWRlchINCgV0aXRsZRgBIAEoCRIMCgRpY29uGAMgASgJEgsKA2tleRgEIAEoCSItChdHZXRBY3Rpb25CaW5kaW5nUmVxdWVzdBISCgpiaW5kaW5nX2lkGAEgASgJIkMKGEdldEFjdGlvbkJpbmRpbmdSZXNwb25zZRInCgZhY3Rpb24YASABKAsyFy5vbGl2ZXRpbi5hcGkudjEuQWN0aW9uIhQKEkdldEVudGl0aWVzUmVxdWVzdCJUChNHZXRFbnRpdGllc1Jlc3BvbnNlEj0KEmVudGl0eV9kZWZpbml0aW9ucxgBIAMoCzIhLm9saXZldGluLmFwaS52MS5FbnRpdHlEZWZpbml0aW9uImkKEEVudGl0eURlZmluaXRpb24SDQoFdGl0bGUYASABKAkSKgoJaW5zdGFuY2VzGAIgAygLMhcub2xpdmV0aW4uYXBpLnYxLkVudGl0eRIaChJ1c2VkX29uX2Rhc2hib2FyZHMYAyADKAkiNAoQR2V0RW50aXR5UmVxdWVzdBISCgp1bmlxdWVfa2V5GAEgASgJEgwKBHR5cGUYAiABKAkiNQoUUmVzdGFydEFjdGlvblJlcXVlc3QSHQoVZXhlY3V0aW9uX3RyYWNraW5nX2lkGAEgASgJMugSChJPbGl2ZVRpbkFwaVNlcnZpY2USXQoMR2V0RGFzaGJvYXJkEiQub2xpdmV0aW4uYXBpLnYxLkdldERhc2hib2FyZFJlcXVlc3QaJS5vbGl2ZXRpbi5hcGkudjEuR2V0RGFzaGJvYXJkUmVzcG9uc2UiABJaCgtTdGFydEFjdGlvbhIjLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAEm8KElN0YXJ0QWN0aW9uQW5kV2FpdBIqLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkFuZFdhaXRSZXF1ZXN0Gisub2xpdmV0aW4uYXBpLnYxLlN0YXJ0QWN0aW9uQW5kV2FpdFJlc3BvbnNlIgASaQoQU3RhcnRBY3Rpb25CeUdldBIoLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVxdWVzdBopLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0UmVzcG9uc2UiABJ+ChdTdGFydEFjdGlvbkJ5R2V0QW5kV2FpdBIvLm9saXZldGluLmFwaS52MS5TdGFydEFjdGlvbkJ5R2V0QW5kV2FpdFJlcXVlc3QaMC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25CeUdldEFuZFdhaXRSZXNwb25zZSIAEl4KDVJlc3RhcnRBY3Rpb24SJS5vbGl2ZXRpbi5hcGkudjEuUmVzdGFydEFjdGlvblJlcXVlc3QaJC5vbGl2ZXRpbi5hcGkudjEuU3RhcnRBY3Rpb25SZXNwb25zZSIAElcKCktpbGxBY3Rpb24SIi5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlcXVlc3QaIy5vbGl2ZXRpbi5hcGkudjEuS2lsbEFjdGlvblJlc3BvbnNlIgASZgoPRXhlY3V0aW9uU3RhdHVzEicub2xpdmV0aW4uYXBpLnYxLkV4ZWN1dGlvblN0YXR1c1JlcXVlc3QaKC5vbGl2ZXRpbi5hcGkudjEuRXhlY3V0aW9uU3RhdHVzUmVzcG9uc2UiABJOCgdHZXRMb2dzEh8ub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXF1ZXN0GiAub2xpdmV0aW4uYXBpLnYxLkdldExvZ3NSZXNwb25zZSIAEmAKDUdldEFjdGlvbkxvZ3MSJS5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1JlcXVlc3QaJi5vbGl2ZXRpbi5hcGkudjEuR2V0QWN0aW9uTG9nc1Jlc3BvbnNlIgASdQoUVmFsaWRhdGVBcmd1bWVudFR5cGUSLC5vbGl2ZXRpbi5hcGkudjEuVmFsaWRhdGVBcmd1bWVudFR5cGVSZXF1ZXN0Gi0ub2xpdmV0aW4uYXBpLnYxLlZhbGlkYXRlQXJndW1lbnRUeXBlUmVzcG9uc2UiABJLCgZXaG9BbUkSHi5vbGl2ZXRpbi5hcGkudjEuV2hvQW1JUmVxdWVzdBofLm9saXZldGluLmFwaS52MS5XaG9BbUlSZXNwb25zZSIAElQKCVNvc1JlcG9ydBIhLm9saXZldGluLmFwaS52MS5Tb3NSZXBvcnRSZXF1ZXN0GiIub2xpdmV0aW4uYXBpLnYxLlNvc1JlcG9ydFJlc3BvbnNlIgASUQoIRHVtcFZhcnMSIC5vbGl2ZXRpbi5hcGkudjEuRHVtcFZhcnNSZXF1ZXN0GiEub2xpdmV0aW4uYXBpLnYxLkR1bXBWYXJzUmVzcG9uc2UiABJ4ChVEdW1wUHVibGljSWRBY3Rpb25NYXASLS5vbGl2ZXRpbi5hcGkudjEuRHVtcFB1YmxpY0lkQWN0aW9uTWFwUmVxdWVzdBouLm9saXZldGluLmFwaS52MS5EdW1wUHVibGljSWRBY3Rpb25NYXBSZXNwb25zZSIAElQKCUdldFJlYWR5ehIhLm9saXZldGluLmFwaS52MS5HZXRSZWFkeXpSZXF1ZXN0GiIub2xpdmV0aW4uYXBpLnYxLkdldFJlYWR5elJlc3BvbnNlIgASYwoOTG9jYWxVc2VyTG9naW4SJi5vbGl2ZXRpbi5hcGkudjEuTG9jYWxVc2VyTG9naW5SZXF1ZXN0Gicub2xpdmV0aW4uYXBpLnYxLkxvY2FsVXNlckxvZ2luUmVzcG9uc2UiABJdCgxQYXNzd29yZEhhc2gSJC5vbGl2ZXRpbi5hcGkudjEuUGFzc3dvcmRIYXNoUmVxdWVzdBolLm9saXZldGluLmFwaS52MS5QYXNzd29yZEhhc2hSZXNwb25zZSIAEksKBkxvZ291dBIeLm9saXZldGluLmFwaS52MS5Mb2dvdXRSZXF1ZXN0Gh8ub2xpdmV0aW4uYXBpLnYxLkxvZ291dFJlc3BvbnNlIgASXAoLRXZlbnRTdHJlYW0SIy5vbGl2ZXRpbi5hcGkudjEuRXZlbnRTdHJlYW1SZXF1ZXN0GiQub2xpdmV0aW4uYXBpLnYxLkV2ZW50U3RyZWFtUmVzcG9uc2UiADABEmMKDkdldERpYWdub3N0aWNzEiYub2xpdmV0aW4uYXBpLnYxLkdldERpYWdub3N0aWNzUmVxdWVzdBonLm9saXZldGluLmFwaS52MS5HZXREaWFnbm9zdGljc1Jlc3BvbnNlIgASRQoESW5pdBIcLm9saXZldGluLmFwaS52MS5Jbml0UmVxdWVzdBodLm9saXZldGluLmFwaS52MS5Jbml0UmVzcG9uc2UiABJpChBHZXRBY3Rpb25CaW5kaW5nEigub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkJpbmRpbmdSZXF1ZXN0Gikub2xpdmV0aW4uYXBpLnYxLkdldEFjdGlvbkJpbmRpbmdSZXNwb25zZSIAEloKC0dldEVudGl0aWVzEiMub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0aWVzUmVxdWVzdBokLm9saXZldGluLmFwaS52MS5HZXRFbnRpdGllc1Jlc3BvbnNlIgASSQoJR2V0RW50aXR5EiEub2xpdmV0aW4uYXBpLnYxLkdldEVudGl0eVJlcXVlc3QaFy5vbGl2ZXRpbi5hcGkudjEuRW50aXR5IgBCOFo2Z2l0aHViLmNvbS9PbGl2ZVRpbi9PbGl2ZVRpbi9nZW4vb2xpdmV0aW4vYXBpL3YxO2FwaXYxYgZwcm90bzM");
/**
* Describes the message olivetin.api.v1.Action.
@@ -491,4 +491,3 @@ export const RestartActionRequestSchema = /*@__PURE__*/
*/
export const OliveTinApiService = /*@__PURE__*/
serviceDesc(file_olivetin_api_v1_olivetin, 0);
-
diff --git a/frontend/resources/vue/App.vue b/frontend/resources/vue/App.vue
index 5648191..7dfae4a 100644
--- a/frontend/resources/vue/App.vue
+++ b/frontend/resources/vue/App.vue
@@ -31,7 +31,7 @@
@@ -68,7 +68,7 @@
- {{ t('language-dialog.browser-languages') }}:
+ {{ t('language-dialog.browser-languages') }}:
{{ browserLanguages.join(', ') }}
{{ t('language-dialog.not-available') }}
@@ -126,6 +126,7 @@ const showFooter = ref(true)
const showNavigation = ref(true)
const showLogs = ref(true)
const showDiagnostics = ref(true)
+const showVersionNumber = ref(true)
const showLoginLink = ref(true)
const sectionNavigationStyle = ref('sidebar')
@@ -184,7 +185,7 @@ function normalizeBrowserLanguage() {
if (navigator.languages && navigator.languages.length > 0) {
for (const candidate of navigator.languages) {
const lowerCandidate = candidate.toLowerCase()
-
+
// Try exact match (case-insensitive)
const exact = available.find(locale => locale.toLowerCase() === lowerCandidate)
if (exact) {
@@ -223,6 +224,7 @@ function updateHeaderFromInit() {
showNavigation.value = window.initResponse.showNavigation
showLogs.value = window.initResponse.showLogList
showDiagnostics.value = window.initResponse.showDiagnostics
+ showVersionNumber.value = window.initResponse.effectivePolicy?.showVersionNumber ?? true
sectionNavigationStyle.value = window.initResponse.sectionNavigationStyle || 'sidebar'
availableThemes.value = window.initResponse.availableThemes || []
@@ -277,7 +279,7 @@ function renderNavigation() {
function openLanguageDialog() {
selectedLanguage.value = languagePreference.value
-
+
if (typeof navigator !== 'undefined' && Array.isArray(navigator.languages)) {
browserLanguages.value = navigator.languages
} else {
@@ -327,7 +329,7 @@ function handleLanguageDialogClick(event) {
function openThemeDialog() {
selectedTheme.value = themePreference.value || ''
-
+
if (themeDialog.value) {
themeDialog.value.showModal()
}
@@ -354,7 +356,7 @@ function changeTheme() {
function applyTheme() {
let themeStyle = document.getElementById('theme-style')
-
+
if (!themeStyle) {
themeStyle = document.createElement('style')
themeStyle.id = 'theme-style'
@@ -404,10 +406,10 @@ window.updateHeaderFromInit = updateHeaderFromInit
onMounted(() => {
serverConnection.value = true;
updateHeaderFromInit()
-
+
// Initialize selected language from stored preference
selectedLanguage.value = languagePreference.value
-
+
// Initialize selected theme from stored preference
selectedTheme.value = themePreference.value || ''
diff --git a/frontend/resources/vue/views/DiagnosticsView.vue b/frontend/resources/vue/views/DiagnosticsView.vue
index a818090..675ffd9 100644
--- a/frontend/resources/vue/views/DiagnosticsView.vue
+++ b/frontend/resources/vue/views/DiagnosticsView.vue
@@ -162,7 +162,10 @@ async function generateBrowserInfo() {
userAgentData: userAgentData
}
- const olivetinVersion = window.initResponse?.currentVersion || t('diagnostics.unknown')
+ const showVersionNumber = window.initResponse?.effectivePolicy?.showVersionNumber ?? true
+ const olivetinVersion = showVersionNumber
+ ? (window.initResponse?.currentVersion || t('diagnostics.unknown'))
+ : '[hidden]'
const currentLanguage = locale.value || t('diagnostics.unknown')
let output = '';
@@ -300,4 +303,4 @@ onMounted(() => {
flex-direction: column;
gap: 1em;
}
-
\ No newline at end of file
+
diff --git a/proto/olivetin/api/v1/olivetin.proto b/proto/olivetin/api/v1/olivetin.proto
index 8554f6b..baf71e2 100644
--- a/proto/olivetin/api/v1/olivetin.proto
+++ b/proto/olivetin/api/v1/olivetin.proto
@@ -51,6 +51,7 @@ message GetDashboardResponse {
message EffectivePolicy {
bool show_diagnostics = 1;
bool show_log_list = 2;
+ bool show_version_number = 3;
}
message GetDashboardRequest {
diff --git a/service/gen/olivetin/api/v1/olivetin.pb.go b/service/gen/olivetin/api/v1/olivetin.pb.go
index 682ca2c..182827b 100644
--- a/service/gen/olivetin/api/v1/olivetin.pb.go
+++ b/service/gen/olivetin/api/v1/olivetin.pb.go
@@ -410,11 +410,12 @@ func (x *GetDashboardResponse) GetDashboard() *Dashboard {
}
type EffectivePolicy struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- ShowDiagnostics bool `protobuf:"varint,1,opt,name=show_diagnostics,json=showDiagnostics,proto3" json:"show_diagnostics,omitempty"`
- ShowLogList bool `protobuf:"varint,2,opt,name=show_log_list,json=showLogList,proto3" json:"show_log_list,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ state protoimpl.MessageState `protogen:"open.v1"`
+ ShowDiagnostics bool `protobuf:"varint,1,opt,name=show_diagnostics,json=showDiagnostics,proto3" json:"show_diagnostics,omitempty"`
+ ShowLogList bool `protobuf:"varint,2,opt,name=show_log_list,json=showLogList,proto3" json:"show_log_list,omitempty"`
+ ShowVersionNumber bool `protobuf:"varint,3,opt,name=show_version_number,json=showVersionNumber,proto3" json:"show_version_number,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
func (x *EffectivePolicy) Reset() {
@@ -461,6 +462,13 @@ func (x *EffectivePolicy) GetShowLogList() bool {
return false
}
+func (x *EffectivePolicy) GetShowVersionNumber() bool {
+ if x != nil {
+ return x.ShowVersionNumber
+ }
+ return false
+}
+
type GetDashboardRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
@@ -3934,10 +3942,11 @@ const file_olivetin_api_v1_olivetin_proto_rawDesc = "" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"f\n" +
"\x14GetDashboardResponse\x12\x14\n" +
"\x05title\x18\x01 \x01(\tR\x05title\x128\n" +
- "\tdashboard\x18\x04 \x01(\v2\x1a.olivetin.api.v1.DashboardR\tdashboard\"`\n" +
+ "\tdashboard\x18\x04 \x01(\v2\x1a.olivetin.api.v1.DashboardR\tdashboard\"\x90\x01\n" +
"\x0fEffectivePolicy\x12)\n" +
"\x10show_diagnostics\x18\x01 \x01(\bR\x0fshowDiagnostics\x12\"\n" +
- "\rshow_log_list\x18\x02 \x01(\bR\vshowLogList\"k\n" +
+ "\rshow_log_list\x18\x02 \x01(\bR\vshowLogList\x12.\n" +
+ "\x13show_version_number\x18\x03 \x01(\bR\x11showVersionNumber\"k\n" +
"\x13GetDashboardRequest\x12\x14\n" +
"\x05title\x18\x01 \x01(\tR\x05title\x12\x1f\n" +
"\ventity_type\x18\x02 \x01(\tR\n" +
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index e2b235f..629d039 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -710,7 +710,9 @@ func (api *oliveTinAPI) WhoAmI(ctx ctx.Context, req *connect.Request[apiv1.WhoAm
}
func (api *oliveTinAPI) SosReport(ctx ctx.Context, req *connect.Request[apiv1.SosReportRequest]) (*connect.Response[apiv1.SosReportResponse], error) {
- sos := installationinfo.GetSosReport()
+ user := auth.UserFromApiCall(ctx, req, api.cfg)
+ redactVersion := !user.EffectivePolicy.ShowVersionNumber
+ sos := installationinfo.GetSosReport(redactVersion)
if !api.cfg.InsecureAllowDumpSos {
log.Info(sos)
@@ -914,12 +916,19 @@ func (api *oliveTinAPI) Init(ctx ctx.Context, req *connect.Request[apiv1.InitReq
loginRequired := user.IsGuest() && api.cfg.AuthRequireGuestsToLogin
+ showVersion := user.EffectivePolicy.ShowVersionNumber
+ currentVersion := ""
+ availableVersion := ""
+ if showVersion {
+ currentVersion = installationinfo.Build.Version
+ availableVersion = installationinfo.Runtime.AvailableVersion
+ }
res := &apiv1.InitResponse{
ShowFooter: api.cfg.ShowFooter,
ShowNavigation: api.cfg.ShowNavigation,
- ShowNewVersions: api.cfg.ShowNewVersions,
- AvailableVersion: installationinfo.Runtime.AvailableVersion,
- CurrentVersion: installationinfo.Build.Version,
+ ShowNewVersions: showVersion && api.cfg.ShowNewVersions,
+ AvailableVersion: availableVersion,
+ CurrentVersion: currentVersion,
PageTitle: api.cfg.PageTitle,
SectionNavigationStyle: api.cfg.SectionNavigationStyle,
DefaultIconForBack: api.cfg.DefaultIconForBack,
diff --git a/service/internal/api/apiActions.go b/service/internal/api/apiActions.go
index 23f8f40..355f179 100644
--- a/service/internal/api/apiActions.go
+++ b/service/internal/api/apiActions.go
@@ -55,8 +55,9 @@ func matchesEntity(binding *executor.ActionBinding, entity *entities.Entity) boo
func buildEffectivePolicy(policy *config.ConfigurationPolicy) *apiv1.EffectivePolicy {
ret := &apiv1.EffectivePolicy{
- ShowDiagnostics: policy.ShowDiagnostics,
- ShowLogList: policy.ShowLogList,
+ ShowDiagnostics: policy.ShowDiagnostics,
+ ShowLogList: policy.ShowLogList,
+ ShowVersionNumber: policy.ShowVersionNumber,
}
return ret
diff --git a/service/internal/auth/authpublic/authenticateduser.go b/service/internal/auth/authpublic/authenticateduser.go
index e4ab6f0..077f220 100644
--- a/service/internal/auth/authpublic/authenticateduser.go
+++ b/service/internal/auth/authpublic/authenticateduser.go
@@ -76,8 +76,9 @@ func (u *AuthenticatedUser) BuildUserAcls(cfg *config.Config) {
func getEffectivePolicy(cfg *config.Config, u *AuthenticatedUser) *config.ConfigurationPolicy {
ret := &config.ConfigurationPolicy{
- ShowDiagnostics: cfg.DefaultPolicy.ShowDiagnostics,
- ShowLogList: cfg.DefaultPolicy.ShowLogList,
+ ShowDiagnostics: cfg.DefaultPolicy.ShowDiagnostics,
+ ShowLogList: cfg.DefaultPolicy.ShowLogList,
+ ShowVersionNumber: cfg.DefaultPolicy.ShowVersionNumber,
}
for _, acl := range cfg.AccessControlLists {
@@ -98,5 +99,9 @@ func buildConfigurationPolicy(ret *config.ConfigurationPolicy, policy config.Con
ret.ShowLogList = policy.ShowLogList
}
+ if policy.ShowVersionNumber {
+ ret.ShowVersionNumber = policy.ShowVersionNumber
+ }
+
return ret
}
diff --git a/service/internal/config/config.go b/service/internal/config/config.go
index b235325..82c466f 100644
--- a/service/internal/config/config.go
+++ b/service/internal/config/config.go
@@ -98,8 +98,9 @@ type AccessControlList struct {
// ConfigurationPolicy defines global settings which are overridden with an ACL.
type ConfigurationPolicy struct {
- ShowDiagnostics bool `koanf:"showDiagnostics"`
- ShowLogList bool `koanf:"showLogList"`
+ ShowDiagnostics bool `koanf:"showDiagnostics"`
+ ShowLogList bool `koanf:"showLogList"`
+ ShowVersionNumber bool `koanf:"showVersionNumber"`
}
type PrometheusConfig struct {
@@ -297,6 +298,7 @@ func DefaultConfigWithBasePort(basePort int) *Config {
config.DefaultPolicy.ShowDiagnostics = true
config.DefaultPolicy.ShowLogList = true
+ config.DefaultPolicy.ShowVersionNumber = true
return &config
}
diff --git a/service/internal/installationinfo/sosreport.go b/service/internal/installationinfo/sosreport.go
index 3dd1e77..4d6c2ce 100644
--- a/service/internal/installationinfo/sosreport.go
+++ b/service/internal/installationinfo/sosreport.go
@@ -40,15 +40,23 @@ func configToSosreport(cfg *config.Config) *sosReportConfig {
}
}
-func GetSosReport() string {
+func GetSosReport(redactVersion bool) string {
ret := ""
ret += "### SOSREPORT START (copy all text to SOSREPORT END)\n"
- out, _ := yaml.Marshal(Build)
+ buildForReport := *Build
+ if redactVersion {
+ buildForReport.Version = "[redacted]"
+ }
+ out, _ := yaml.Marshal(&buildForReport)
ret += fmt.Sprintf("# Build: \n%+v\n", string(out))
- out, _ = yaml.Marshal(Runtime)
+ runtimeForReport := *Runtime
+ if redactVersion {
+ runtimeForReport.AvailableVersion = "[redacted]"
+ }
+ out, _ = yaml.Marshal(&runtimeForReport)
ret += fmt.Sprintf("# Runtime:\n%+v\n", string(out))
out, _ = yaml.Marshal(configToSosreport(Config))
From 7051aad5995ec771314128437d4ce7d525de0d83 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 1 Mar 2026 23:17:44 +0000
Subject: [PATCH 035/148] feat: Clickable links in outout (#900)
---
frontend/js/OutputTerminal.js | 14 +++-
frontend/package-lock.json | 7 ++
frontend/package.json | 3 +-
.../tests/xtermLinkHandling/config.yaml | 13 ++++
.../xtermLinkHandling/xtermLinkHandling.mjs | 69 +++++++++++++++++++
5 files changed, 104 insertions(+), 2 deletions(-)
create mode 100644 integration-tests/tests/xtermLinkHandling/config.yaml
create mode 100644 integration-tests/tests/xtermLinkHandling/xtermLinkHandling.mjs
diff --git a/frontend/js/OutputTerminal.js b/frontend/js/OutputTerminal.js
index 1526318..ebda77b 100644
--- a/frontend/js/OutputTerminal.js
+++ b/frontend/js/OutputTerminal.js
@@ -1,5 +1,6 @@
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
+import { WebLinksAddon } from '@xterm/addon-web-links'
import { Mutex } from './Mutex.js'
/**
@@ -18,13 +19,24 @@ export class OutputTerminal {
constructor (executionTrackingId) {
this.executionTrackingId = executionTrackingId
this.writeMutex = new Mutex()
+ const linkHandler = {
+ activate (event, text, _range) {
+ event.preventDefault()
+ window.open(text, '_blank')
+ }
+ }
+
this.terminal = new Terminal({
- convertEol: true
+ convertEol: true,
+ linkHandler
})
const fitAddon = new FitAddon()
this.terminal.loadAddon(fitAddon)
this.terminal.fit = fitAddon
+
+ this.terminal.loadAddon(new WebLinksAddon((event, uri) => linkHandler.activate(event, uri)))
+ this.linkHandlerConfigured = true
}
async write (out, then) {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3bc98c8..47b2ac6 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -15,6 +15,7 @@
"@hugeicons/vue": "^1.0.4",
"@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0",
+ "@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2",
"picocrank": "^1.14.0",
@@ -1570,6 +1571,12 @@
"integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==",
"license": "MIT"
},
+ "node_modules/@xterm/addon-web-links": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz",
+ "integrity": "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==",
+ "license": "MIT"
+ },
"node_modules/@xterm/xterm": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index a64d473..a08f799 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -28,13 +28,14 @@
"@hugeicons/vue": "^1.0.4",
"@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0",
+ "@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2",
"picocrank": "^1.14.0",
"standard": "^17.1.2",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1",
- "vue": "^3.5.29",
+ "vue": "^3.5.29",
"vue-i18n": "^11.2.8",
"vue-router": "^5.0.3"
}
diff --git a/integration-tests/tests/xtermLinkHandling/config.yaml b/integration-tests/tests/xtermLinkHandling/config.yaml
new file mode 100644
index 0000000..f70848d
--- /dev/null
+++ b/integration-tests/tests/xtermLinkHandling/config.yaml
@@ -0,0 +1,13 @@
+#
+# Integration Test Config: xterm link handling
+#
+
+listenAddressSingleHTTPFrontend: 0.0.0.0:1337
+
+logLevel: "DEBUG"
+checkForUpdates: false
+
+actions:
+ - title: Echo URL
+ shell: echo "See https://example.com for more info"
+ popupOnStart: execution-dialog-stdout-only
diff --git a/integration-tests/tests/xtermLinkHandling/xtermLinkHandling.mjs b/integration-tests/tests/xtermLinkHandling/xtermLinkHandling.mjs
new file mode 100644
index 0000000..849f5a0
--- /dev/null
+++ b/integration-tests/tests/xtermLinkHandling/xtermLinkHandling.mjs
@@ -0,0 +1,69 @@
+import { describe, it, before, after } from 'mocha'
+import { expect } from 'chai'
+import { By, Condition } from 'selenium-webdriver'
+import {
+ getRootAndWait,
+ takeScreenshotOnFailure,
+ getTerminalBuffer,
+} from '../../lib/elements.js'
+
+describe('config: xtermLinkHandling', function () {
+ before(async function () {
+ await runner.start('xtermLinkHandling')
+ })
+
+ after(async () => {
+ await runner.stop()
+ })
+
+ afterEach(function () {
+ takeScreenshotOnFailure(this.currentTest, webdriver)
+ })
+
+ it('xterm output shows URL and link handling is configured', async function () {
+ await getRootAndWait()
+
+ await webdriver.wait(new Condition('wait for Echo URL button', async () => {
+ const btns = await webdriver.findElements(By.css('[title="Echo URL"]'))
+ return btns.length === 1
+ }), 10000)
+
+ const echoUrlButton = await webdriver.findElement(By.css('[title="Echo URL"]'))
+ await echoUrlButton.click()
+
+ await webdriver.wait(new Condition('wait for execution view', async () => {
+ const url = await webdriver.getCurrentUrl()
+ return url.includes('/logs/') && !url.endsWith('/logs')
+ }), 10000)
+
+ await webdriver.wait(new Condition('wait for execution status', async () => {
+ const statusElements = await webdriver.findElements(By.id('execution-dialog-status'))
+ return statusElements.length > 0
+ }), 5000)
+
+ await webdriver.wait(new Condition('wait for execution to finish', async () => {
+ try {
+ const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
+ const statusText = await statusElement.getText()
+ return !statusText.includes('Executing')
+ } catch (e) {
+ return false
+ }
+ }), 5000)
+
+ await webdriver.sleep(500)
+
+ const bufferText = await getTerminalBuffer()
+ expect(bufferText).to.not.be.null
+ expect(bufferText).to.include('https://example.com')
+
+ const linkHandlerSet = await webdriver.executeScript(`
+ try {
+ return !!(window.terminal && window.terminal.linkHandlerConfigured === true)
+ } catch (e) {
+ return false
+ }
+ `)
+ expect(linkHandlerSet).to.equal(true)
+ })
+})
From f044d90d5525c4c8e3f421b32ed7eff771c22d36 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 1 Mar 2026 23:52:25 +0000
Subject: [PATCH 036/148] security: Remote crash in OAuth2 GHSA-45m3-398w-m2m9
Thanks @kule500 for the responsible disclosure. CVE to follow.
---
.../auth/otoauth2/restapi_auth_oauth2.go | 44 ++++++++++++-------
1 file changed, 27 insertions(+), 17 deletions(-)
diff --git a/service/internal/auth/otoauth2/restapi_auth_oauth2.go b/service/internal/auth/otoauth2/restapi_auth_oauth2.go
index 5266124..a84e1f6 100644
--- a/service/internal/auth/otoauth2/restapi_auth_oauth2.go
+++ b/service/internal/auth/otoauth2/restapi_auth_oauth2.go
@@ -11,6 +11,7 @@ import (
"io"
"net/http"
"os"
+ "sync"
"time"
authTypes "github.com/OliveTin/OliveTin/internal/auth/authpublic"
@@ -21,6 +22,7 @@ import (
type OAuth2Handler struct {
cfg *config.Config
+ mu sync.RWMutex
registeredStates map[string]*oauth2State
registeredProviders map[string]*oauth2.Config
}
@@ -144,11 +146,13 @@ func (h *OAuth2Handler) HandleOAuthLogin(w http.ResponseWriter, r *http.Request)
return
}
+ h.mu.Lock()
h.registeredStates[state] = &oauth2State{
providerConfig: provider,
providerName: providerName,
Username: "",
}
+ h.mu.Unlock()
h.setOAuthCallbackCookie(w, r, "olivetin-sid-oauth", state)
@@ -177,7 +181,9 @@ func (h *OAuth2Handler) checkOAuthCallbackCookie(w http.ResponseWriter, r *http.
return nil, state, false
}
+ h.mu.RLock()
registeredState, ok := h.registeredStates[state]
+ h.mu.RUnlock()
if !ok {
log.Errorf("State not found in server: %v", state)
http.Error(w, "State not found in server", http.StatusBadRequest)
@@ -287,8 +293,10 @@ func (h *OAuth2Handler) HandleOAuthCallback(w http.ResponseWriter, r *http.Reque
userInfoClient := h.createUserInfoClient(ctx, registeredState.providerConfig, tok, clientSettings)
userinfo := getUserInfo(h.cfg, userInfoClient, providerConfig)
+ h.mu.Lock()
h.registeredStates[state].Username = userinfo.Username
h.registeredStates[state].Usergroup = h.computeUsergroup(userinfo, providerConfig)
+ h.mu.Unlock()
http.Redirect(w, r, "/", http.StatusFound)
}
@@ -366,34 +374,36 @@ func getDataField(data map[string]any, field string) string {
return stringVal
}
+func (h *OAuth2Handler) lookupOAuth2UserByState(state string) (*authTypes.AuthenticatedUser, bool) {
+ h.mu.RLock()
+ serverState, found := h.registeredStates[state]
+ if !found {
+ h.mu.RUnlock()
+ return nil, false
+ }
+ user := &authTypes.AuthenticatedUser{
+ Username: serverState.Username,
+ UsergroupLine: serverState.Usergroup,
+ Provider: "oauth2",
+ SID: state,
+ }
+ h.mu.RUnlock()
+ return user, true
+}
+
func (h *OAuth2Handler) CheckUserFromOAuth2Cookie(context *authTypes.AuthCheckingContext) *authTypes.AuthenticatedUser {
cookie, err := context.Request.Cookie("olivetin-sid-oauth")
-
- user := &authTypes.AuthenticatedUser{}
-
- if err != nil {
+ if err != nil || cookie.Value == "" {
return nil
}
- if cookie.Value == "" {
- return nil
- }
-
- serverState, found := h.registeredStates[cookie.Value]
-
+ user, found := h.lookupOAuth2UserByState(cookie.Value)
if !found {
log.WithFields(log.Fields{
"sid": cookie.Value,
"provider": "oauth2",
}).Warnf("Stale session")
-
return nil
}
-
- user.Username = serverState.Username
- user.UsergroupLine = serverState.Usergroup
- user.Provider = "oauth2"
- user.SID = cookie.Value
-
return user
}
From d9804182eae43cf49f735e6533ddbe1541c2b9a9 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Mon, 2 Mar 2026 00:29:02 +0000
Subject: [PATCH 037/148] security: GHSA-4fqm-6fmh-82mq Authentication bypass
in KillAction - thanks for the responsible disclosure @kule500 and making
OliveTin better
---
service/internal/config/sanitize.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/service/internal/config/sanitize.go b/service/internal/config/sanitize.go
index f699b80..ddeac5b 100644
--- a/service/internal/config/sanitize.go
+++ b/service/internal/config/sanitize.go
@@ -164,6 +164,7 @@ func (cfg *Config) sanitizeAuthRequireGuestsToLogin() {
cfg.DefaultPermissions.View = false
cfg.DefaultPermissions.Exec = false
cfg.DefaultPermissions.Logs = false
+ cfg.DefaultPermissions.Kill = false
}
}
From 87148f05bd2541c6a8d02120c2675c23aa910fd4 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Mon, 2 Mar 2026 00:44:49 +0000
Subject: [PATCH 038/148] chore: Update SECURITY.md
---
SECURITY.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/SECURITY.md b/SECURITY.md
index cc1a212..13074e1 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -31,6 +31,12 @@ Please use responsible disclosure practices when reporting a vulnerability. **Yo
* **Option B**: Please email `contact@jread.com` for responsible disclosure.
+The following notes might be helpful when reporting a vulnerability:
+
+* OliveTin does not offer a bug bounty program.
+* GitHub usernames are how we you will be credited for discoveries reported via GitHub, if using emails we'll ask for your preferred name/handle to credit you with.
+* CVEs will be requested via GitHub Security Advisories when appropriate, but we do not guarantee that all vulnerabilities will receive CVEs, as this is determined on a case-by-case basis.
+
## Disclosure of how vulnerabilities were found
It is incredibly useful to not just patch security vulnerabilities, but also to understand how they were found. If you are able to share this information, it can help us and the community to better understand potential attack vectors and improve the overall security of the project.
From bb14c5da3e64b03f207c7f38139eb60e97c278fc Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 4 Mar 2026 22:51:58 +0000
Subject: [PATCH 039/148] security: (MED) GHSA-fwhj-785h-43hh Crash on NPE by
calling APIs with invalid bindings or log references
---
service/internal/api/api.go | 115 +++++++++++++++++++----------
service/internal/api/apiActions.go | 52 ++++++++-----
service/internal/api/dashboards.go | 5 +-
3 files changed, 111 insertions(+), 61 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 629d039..2b9fe42 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -70,20 +70,21 @@ func (api *oliveTinAPI) KillAction(ctx ctx.Context, req *connect.Request[apiv1.K
execReqLogEntry, ret.Found = api.executor.GetLog(req.Msg.ExecutionTrackingId)
if !ret.Found {
- log.Warnf("Killing execution request not possible - not found by tracking ID: %v", req.Msg.ExecutionTrackingId)
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
- log.Warnf("Killing execution request by tracking ID: %v", req.Msg.ExecutionTrackingId)
+ if execReqLogEntry.Binding == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("log entry has no binding for tracking ID %s", req.Msg.ExecutionTrackingId))
+ }
action := execReqLogEntry.Binding.Action
if action == nil {
- log.Warnf("Killing execution request not possible - action not found: %v", execReqLogEntry.ActionTitle)
- ret.Killed = false
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
+ log.Warnf("Killing execution request by tracking ID: %v", req.Msg.ExecutionTrackingId)
+
user := auth.UserFromApiCall(ctx, req, api.cfg)
api.killActionByTrackingId(user, action, execReqLogEntry, ret)
@@ -205,42 +206,58 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
return response, nil
}
-func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionAndWaitRequest]) (*connect.Response[apiv1.StartActionAndWaitResponse], error) {
- args := make(map[string]string)
-
- for _, arg := range req.Msg.Arguments {
- args[arg.Name] = arg.Value
- }
-
- user := auth.UserFromApiCall(ctx, req, api.cfg)
-
+func (api *oliveTinAPI) startActionAndWaitRun(binding *executor.ActionBinding, args map[string]string, user *authpublic.AuthenticatedUser) (*executor.InternalLogEntry, bool) {
execReq := executor.ExecutionRequest{
- Binding: api.executor.FindBindingByID(req.Msg.ActionId),
+ Binding: binding,
TrackingID: uuid.NewString(),
Arguments: args,
AuthenticatedUser: user,
Cfg: api.cfg,
}
-
wg, _ := api.executor.ExecRequest(&execReq)
wg.Wait()
+ return api.executor.GetLog(execReq.TrackingID)
+}
- internalLogEntry, ok := api.executor.GetLog(execReq.TrackingID)
-
- if ok {
- return connect.NewResponse(&apiv1.StartActionAndWaitResponse{
- LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
- }), nil
- } else {
- return nil, fmt.Errorf("execution not found")
+func (api *oliveTinAPI) findBindingOrNotFound(actionId string) (*executor.ActionBinding, error) {
+ binding := api.executor.FindBindingByID(actionId)
+ if binding == nil || binding.Action == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", actionId))
}
+ return binding, nil
+}
+
+func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionAndWaitRequest]) (*connect.Response[apiv1.StartActionAndWaitResponse], error) {
+ binding, err := api.findBindingOrNotFound(req.Msg.ActionId)
+ if err != nil {
+ return nil, err
+ }
+
+ args := make(map[string]string)
+ for _, arg := range req.Msg.Arguments {
+ args[arg.Name] = arg.Value
+ }
+ user := auth.UserFromApiCall(ctx, req, api.cfg)
+
+ internalLogEntry, ok := api.startActionAndWaitRun(binding, args, user)
+ if !ok {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
+ }
+ return connect.NewResponse(&apiv1.StartActionAndWaitResponse{
+ LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
+ }), nil
}
func (api *oliveTinAPI) StartActionByGet(ctx ctx.Context, req *connect.Request[apiv1.StartActionByGetRequest]) (*connect.Response[apiv1.StartActionByGetResponse], error) {
+ binding := api.executor.FindBindingByID(req.Msg.ActionId)
+ if binding == nil || binding.Action == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.ActionId))
+ }
+
args := make(map[string]string)
execReq := executor.ExecutionRequest{
- Binding: api.executor.FindBindingByID(req.Msg.ActionId),
+ Binding: binding,
TrackingID: uuid.NewString(),
Arguments: args,
AuthenticatedUser: auth.UserFromApiCall(ctx, req, api.cfg),
@@ -255,12 +272,17 @@ func (api *oliveTinAPI) StartActionByGet(ctx ctx.Context, req *connect.Request[a
}
func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionByGetAndWaitRequest]) (*connect.Response[apiv1.StartActionByGetAndWaitResponse], error) {
+ binding := api.executor.FindBindingByID(req.Msg.ActionId)
+ if binding == nil || binding.Action == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.ActionId))
+ }
+
args := make(map[string]string)
user := auth.UserFromApiCall(ctx, req, api.cfg)
execReq := executor.ExecutionRequest{
- Binding: api.executor.FindBindingByID(req.Msg.ActionId),
+ Binding: binding,
TrackingID: uuid.NewString(),
Arguments: args,
AuthenticatedUser: user,
@@ -276,9 +298,8 @@ func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *connect.Re
return connect.NewResponse(&apiv1.StartActionByGetAndWaitResponse{
LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
}), nil
- } else {
- return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
}
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
}
func calculateRateLimitExpires(api *oliveTinAPI, logEntry *executor.InternalLogEntry) string {
@@ -436,7 +457,7 @@ func (api *oliveTinAPI) GetActionBinding(ctx ctx.Context, req *connect.Request[a
binding := api.executor.FindBindingByID(req.Msg.BindingId)
- if binding == nil {
+ if binding == nil || binding.Action == nil {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.BindingId))
}
@@ -646,7 +667,16 @@ error messages more quickly before starting the action.
It uses the same validation logic as the executor, including mangling argument
values (e.g., datetime formatting, checkbox title-to-value conversion).
*/
+func (api *oliveTinAPI) argumentNotFoundForValidation(msg *apiv1.ValidateArgumentTypeRequest) bool {
+ arg, _ := api.findArgumentForValidation(msg.BindingId, msg.ArgumentName)
+ return arg == nil && (msg.BindingId != "" || msg.ArgumentName != "")
+}
+
func (api *oliveTinAPI) ValidateArgumentType(ctx ctx.Context, req *connect.Request[apiv1.ValidateArgumentTypeRequest]) (*connect.Response[apiv1.ValidateArgumentTypeResponse], error) {
+ if api.argumentNotFoundForValidation(req.Msg) {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action or argument not found for binding ID %s", req.Msg.BindingId))
+ }
+
err := api.validateArgumentTypeInternal(req.Msg)
desc := ""
if err != nil {
@@ -747,6 +777,13 @@ func (api *oliveTinAPI) DumpVars(ctx ctx.Context, req *connect.Request[apiv1.Dum
return connect.NewResponse(res), nil
}
+func debugBindingActionTitle(binding *executor.ActionBinding) string {
+ if binding == nil || binding.Action == nil {
+ return ""
+ }
+ return binding.Action.Title
+}
+
func (api *oliveTinAPI) DumpPublicIdActionMap(ctx ctx.Context, req *connect.Request[apiv1.DumpPublicIdActionMapRequest]) (*connect.Response[apiv1.DumpPublicIdActionMapResponse], error) {
res := &apiv1.DumpPublicIdActionMapResponse{}
res.Contents = make(map[string]*apiv1.DebugBinding)
@@ -761,7 +798,7 @@ func (api *oliveTinAPI) DumpPublicIdActionMap(ctx ctx.Context, req *connect.Requ
for k, v := range api.executor.MapActionBindings {
res.Contents[k] = &apiv1.DebugBinding{
- ActionTitle: v.Action.Title,
+ ActionTitle: debugBindingActionTitle(v),
}
}
@@ -1267,28 +1304,26 @@ func serializeEntityFields(data any) map[string]string {
}
func (api *oliveTinAPI) RestartAction(ctx ctx.Context, req *connect.Request[apiv1.RestartActionRequest]) (*connect.Response[apiv1.StartActionResponse], error) {
- ret := &apiv1.StartActionResponse{
- ExecutionTrackingId: req.Msg.ExecutionTrackingId,
- }
-
var execReqLogEntry *executor.InternalLogEntry
execReqLogEntry, found := api.executor.GetLog(req.Msg.ExecutionTrackingId)
if !found {
- log.Warnf("Restarting execution request not possible - not found by tracking ID: %v", req.Msg.ExecutionTrackingId)
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
- log.Warnf("Restarting execution request by tracking ID: %v", req.Msg.ExecutionTrackingId)
+ if execReqLogEntry.Binding == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("log entry has no binding for tracking ID %s", req.Msg.ExecutionTrackingId))
+ }
action := execReqLogEntry.Binding.Action
if action == nil {
- log.Warnf("Restarting execution request not possible - action not found: %v", execReqLogEntry.ActionTitle)
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
+ log.Warnf("Restarting execution request by tracking ID: %v", req.Msg.ExecutionTrackingId)
+
return api.StartAction(ctx, &connect.Request[apiv1.StartActionRequest]{
Msg: &apiv1.StartActionRequest{
BindingId: execReqLogEntry.GetBindingId(),
diff --git a/service/internal/api/apiActions.go b/service/internal/api/apiActions.go
index 355f179..13bcb4a 100644
--- a/service/internal/api/apiActions.go
+++ b/service/internal/api/apiActions.go
@@ -28,18 +28,19 @@ func (rr *DashboardRenderRequest) findAction(title string) *apiv1.Action {
return rr.findActionForEntity(title, nil)
}
+func bindingMatchesTitleAndEntity(binding *executor.ActionBinding, title string, entity *entities.Entity) bool {
+ return binding != nil && binding.Action != nil && binding.Action.Title == title && matchesEntity(binding, entity)
+}
+
func (rr *DashboardRenderRequest) findActionForEntity(title string, entity *entities.Entity) *apiv1.Action {
rr.ex.MapActionBindingsLock.RLock()
defer rr.ex.MapActionBindingsLock.RUnlock()
for _, binding := range rr.ex.MapActionBindings {
- if binding.Action.Title != title {
+ if !bindingMatchesTitleAndEntity(binding, title, entity) {
continue
}
-
- if matchesEntity(binding, entity) {
- return buildAction(binding, rr)
- }
+ return buildAction(binding, rr)
}
return nil
@@ -117,26 +118,37 @@ func getDefaultArgumentValue(cfgArg config.ActionArgument, entity *entities.Enti
return defaultValue
}
+func formatRateLimitExpiry(expiryUnix int64) string {
+ if expiryUnix <= 0 {
+ return ""
+ }
+ return time.Unix(expiryUnix, 0).Format("2006-01-02 15:04:05")
+}
+
+func actionFromBinding(actionBinding *executor.ActionBinding) (*executor.ActionBinding, *config.Action) {
+ if actionBinding == nil || actionBinding.Action == nil {
+ return nil, nil
+ }
+ return actionBinding, actionBinding.Action
+}
+
func buildAction(actionBinding *executor.ActionBinding, rr *DashboardRenderRequest) *apiv1.Action {
- action := actionBinding.Action
-
- aclCanExec := acl.IsAllowedExec(rr.cfg, rr.AuthenticatedUser, action)
- enabledExprCanExec := evaluateEnabledExpression(action, actionBinding.Entity)
-
- // Calculate rate limit expiry time
- expiryUnix := rr.ex.GetTimeUntilAvailable(actionBinding)
- datetimeRateLimitExpires := ""
- if expiryUnix > 0 {
- datetimeRateLimitExpires = time.Unix(expiryUnix, 0).Format("2006-01-02 15:04:05")
+ binding, action := actionFromBinding(actionBinding)
+ if binding == nil {
+ return nil
}
+ aclCanExec := acl.IsAllowedExec(rr.cfg, rr.AuthenticatedUser, action)
+ enabledExprCanExec := evaluateEnabledExpression(action, binding.Entity)
+ datetimeRateLimitExpires := formatRateLimitExpiry(rr.ex.GetTimeUntilAvailable(binding))
+
btn := apiv1.Action{
- BindingId: actionBinding.ID,
- Title: tpl.ParseTemplateOfActionBeforeExec(action.Title, actionBinding.Entity),
- Icon: tpl.ParseTemplateOfActionBeforeExec(action.Icon, actionBinding.Entity),
+ BindingId: binding.ID,
+ Title: tpl.ParseTemplateOfActionBeforeExec(action.Title, binding.Entity),
+ Icon: tpl.ParseTemplateOfActionBeforeExec(action.Icon, binding.Entity),
CanExec: aclCanExec && enabledExprCanExec,
PopupOnStart: action.PopupOnStart,
- Order: int32(actionBinding.ConfigOrder),
+ Order: int32(binding.ConfigOrder),
Timeout: int32(action.Timeout),
DatetimeRateLimitExpires: datetimeRateLimitExpires,
}
@@ -147,7 +159,7 @@ func buildAction(actionBinding *executor.ActionBinding, rr *DashboardRenderReque
Title: cfgArg.Title,
Type: cfgArg.Type,
Description: cfgArg.Description,
- DefaultValue: getDefaultArgumentValue(cfgArg, actionBinding.Entity),
+ DefaultValue: getDefaultArgumentValue(cfgArg, binding.Entity),
Choices: buildChoices(cfgArg),
Suggestions: cfgArg.Suggestions,
SuggestionsBrowserKey: cfgArg.SuggestionsBrowserKey,
diff --git a/service/internal/api/dashboards.go b/service/internal/api/dashboards.go
index 11c6cf3..2bc2244 100644
--- a/service/internal/api/dashboards.go
+++ b/service/internal/api/dashboards.go
@@ -130,7 +130,7 @@ func buildDefaultDashboard(rr *DashboardRenderRequest) *apiv1.Dashboard {
}
for _, binding := range rr.ex.MapActionBindings {
- if binding.Action.Hidden {
+ if binding == nil || binding.Action == nil || binding.Action.Hidden {
continue
}
@@ -139,6 +139,9 @@ func buildDefaultDashboard(rr *DashboardRenderRequest) *apiv1.Dashboard {
}
action := buildAction(binding, rr)
+ if action == nil {
+ continue
+ }
fieldset.Contents = append(fieldset.Contents, &apiv1.DashboardComponent{
Type: "link",
From 0c47564652367749c255b22013ce5af82085e4ad Mon Sep 17 00:00:00 2001
From: jamesread
Date: Mon, 2 Mar 2026 13:48:22 +0000
Subject: [PATCH 040/148] chore: logs are written with 0600 instead of 0644
---
service/internal/executor/executor.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/service/internal/executor/executor.go b/service/internal/executor/executor.go
index 91312d4..65f7be6 100644
--- a/service/internal/executor/executor.go
+++ b/service/internal/executor/executor.go
@@ -1059,7 +1059,7 @@ func saveLogResults(req *ExecutionRequest, filename string) {
}
filepath := path.Join(dir, filename+".yaml")
- err = os.WriteFile(filepath, data, 0644)
+ err = os.WriteFile(filepath, data, 0600)
if err != nil {
log.Warnf("%v", err)
@@ -1073,7 +1073,7 @@ func saveLogOutput(req *ExecutionRequest, filename string) {
if dir != "" {
data := req.logEntry.Output
filepath := path.Join(dir, filename+".log")
- err := os.WriteFile(filepath, []byte(data), 0644)
+ err := os.WriteFile(filepath, []byte(data), 0600)
if err != nil {
log.Warnf("%v", err)
From 00cb5a2abfd024e6de680af4256687056dc9a041 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Tue, 3 Mar 2026 22:20:45 +0000
Subject: [PATCH 041/148] fix: action triggers were broken #914
---
service/internal/executor/executor.go | 11 ++-
service/internal/executor/executor_test.go | 90 ++++++++++++++++++++++
2 files changed, 99 insertions(+), 2 deletions(-)
diff --git a/service/internal/executor/executor.go b/service/internal/executor/executor.go
index 65f7be6..f6807cf 100644
--- a/service/internal/executor/executor.go
+++ b/service/internal/executor/executor.go
@@ -1015,8 +1015,15 @@ func stepTrigger(req *ExecutionRequest) bool {
}
func triggerLoop(req *ExecutionRequest) {
- for _, triggerReq := range req.Binding.Action.Triggers {
- binding := req.executor.FindBindingByID(triggerReq)
+ for _, triggerTitle := range req.Binding.Action.Triggers {
+ binding := req.executor.findBindingByActionTitle(triggerTitle, "")
+ if binding == nil {
+ log.WithFields(log.Fields{
+ "triggerTitle": triggerTitle,
+ "fromAction": req.logEntry.ActionTitle,
+ }).Warnf("Trigger references unknown action title; skipping")
+ continue
+ }
trigger := &ExecutionRequest{
Binding: binding,
TrackingID: uuid.NewString(),
diff --git a/service/internal/executor/executor_test.go b/service/internal/executor/executor_test.go
index 2608bbe..16f491d 100644
--- a/service/internal/executor/executor_test.go
+++ b/service/internal/executor/executor_test.go
@@ -2,6 +2,7 @@ package executor
import (
"testing"
+ "time"
"github.com/stretchr/testify/assert"
@@ -395,3 +396,92 @@ func TestFilterToDefinedArgumentsPreservesSystemArgs(t *testing.T) {
assert.Equal(t, "track-123", req.Arguments["ot_executionTrackingId"])
assert.Equal(t, "webhook", req.Arguments["ot_username"])
}
+
+func TestTriggerExecutesTriggeredAction(t *testing.T) {
+ cfg := config.DefaultConfig()
+ e := DefaultExecutor(cfg)
+ helloAction := &config.Action{
+ Title: "Hello world",
+ Shell: "echo 'Hello World!'",
+ }
+ triggerAction := &config.Action{
+ Title: "Simple action that triggers another action",
+ Shell: "echo 'Hi'",
+ Triggers: []string{"Hello world"},
+ }
+ cfg.Actions = append(cfg.Actions, helloAction, triggerAction)
+ cfg.Sanitize()
+ e.RebuildActionMap()
+
+ finishedTitles := make(chan string, 4)
+ collector := &executionFinishedCollector{ch: finishedTitles}
+ e.AddListener(collector)
+
+ req := &ExecutionRequest{
+ AuthenticatedUser: auth.UserFromSystem(cfg, "testuser"),
+ Cfg: cfg,
+ Binding: e.FindBindingWithNoEntity(triggerAction),
+ }
+ wg, _ := e.ExecRequest(req)
+ wg.Wait()
+
+ var got []string
+ for i := 0; i < 2; i++ {
+ select {
+ case title := <-finishedTitles:
+ got = append(got, title)
+ case <-time.After(2 * time.Second):
+ t.Fatalf("timed out waiting for execution %d; got %v", i+1, got)
+ }
+ }
+ assert.Contains(t, got, "Hello world", "triggered action must run")
+ assert.Contains(t, got, "Simple action that triggers another action", "triggering action must run")
+}
+
+func TestTriggerUnknownActionTitleSkipsWithoutPanic(t *testing.T) {
+ cfg := config.DefaultConfig()
+ e := DefaultExecutor(cfg)
+ triggerAction := &config.Action{
+ Title: "Action with bad trigger",
+ Shell: "echo 'ok'",
+ Triggers: []string{"Nonexistent action"},
+ }
+ cfg.Actions = append(cfg.Actions, triggerAction)
+ cfg.Sanitize()
+ e.RebuildActionMap()
+
+ finishedTitles := make(chan string, 4)
+ collector := &executionFinishedCollector{ch: finishedTitles}
+ e.AddListener(collector)
+
+ req := &ExecutionRequest{
+ AuthenticatedUser: auth.UserFromSystem(cfg, "testuser"),
+ Cfg: cfg,
+ Binding: e.FindBindingWithNoEntity(triggerAction),
+ }
+ wg, _ := e.ExecRequest(req)
+ wg.Wait()
+
+ var got []string
+ select {
+ case title := <-finishedTitles:
+ got = append(got, title)
+ case <-time.After(500 * time.Millisecond):
+ }
+ assert.Len(t, got, 1, "only the triggering action runs; unknown trigger is skipped")
+ assert.Equal(t, "Action with bad trigger", got[0])
+}
+
+type executionFinishedCollector struct {
+ ch chan string
+}
+
+func (c *executionFinishedCollector) OnExecutionStarted(_ *InternalLogEntry) {}
+
+func (c *executionFinishedCollector) OnExecutionFinished(entry *InternalLogEntry) {
+ c.ch <- entry.ActionTitle
+}
+
+func (c *executionFinishedCollector) OnOutputChunk(_ []byte, _ string) {}
+
+func (c *executionFinishedCollector) OnActionMapRebuilt() {}
From 92a1346edf0e8c6202b6e575806eaf57b9df4ef5 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 4 Mar 2026 23:00:28 +0000
Subject: [PATCH 042/148] docs: update security.md with the fix process
---
SECURITY.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/SECURITY.md b/SECURITY.md
index 13074e1..49fcf13 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -40,3 +40,16 @@ The following notes might be helpful when reporting a vulnerability:
## Disclosure of how vulnerabilities were found
It is incredibly useful to not just patch security vulnerabilities, but also to understand how they were found. If you are able to share this information, it can help us and the community to better understand potential attack vectors and improve the overall security of the project.
+
+## Process
+
+Once a vulnerability is reported, the process is;
+
+* Accept or reject the report, and communicate with the reporter about next steps.
+* If accepted, patch using a temporary branch, and code review will be requested from the original reporter if they are interested.
+* The severity of the vulnerability will be assessed using CVSS, and the patch will be prioritized accordingly.
+* Once the patch is ready, it will be queued for a release onto the `next` branch (3k) or `release/2k` branch (2k)
+* The reporter will be credited in the advistory and the release notes, but not the commit message.
+* The commit message will contain a reference to the CVSS score (eg: MED) and the advisory ID.
+
+
From e97d8ecbd8d6ba468c418ca496fcd18f78131233 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Tue, 3 Mar 2026 22:47:03 +0000
Subject: [PATCH 043/148] security: GHSA-g962-2j28-3cg9 (HIGH) JWT Audience
Validation Bypass in Local Key and HMAC Modes
---
service/internal/auth/otjwt/jwt.go | 24 +++++++++++++++++++-----
service/internal/auth/otjwt/jwt_test.go | 22 +++++++++++++++++++++-
2 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/service/internal/auth/otjwt/jwt.go b/service/internal/auth/otjwt/jwt.go
index 103224a..2b06da5 100644
--- a/service/internal/auth/otjwt/jwt.go
+++ b/service/internal/auth/otjwt/jwt.go
@@ -33,6 +33,13 @@ func parseJwtToken(cfg *config.Config, jwtString string) (*jwt.Token, error) {
return parseJwtTokenWithHMAC(cfg, jwtString)
}
+func parserOptionsWithAudience(cfg *config.Config) []jwt.ParserOption {
+ if cfg.AuthJwtAud == "" {
+ return nil
+ }
+ return []jwt.ParserOption{jwt.WithAudience(cfg.AuthJwtAud)}
+}
+
func getClaimsFromJwtToken(cfg *config.Config, jwtString string) (jwt.MapClaims, error) {
token, err := parseJwtToken(cfg, jwtString)
@@ -56,7 +63,8 @@ func parseJwtTokenWithRemoteKey(cfg *config.Config, jwtToken string) (*jwt.Token
return nil, err
}
- return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, jwt.WithAudience(cfg.AuthJwtAud))
+ opts := parserOptionsWithAudience(cfg)
+ return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, opts...)
}
var (
@@ -148,24 +156,30 @@ func parseJwtTokenWithLocalKey(cfg *config.Config, jwtString string) (*jwt.Token
return nil, err
}
- return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
+ keyFunc := func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("parseJwt expected token algorithm RSA but got: %v", token.Header["alg"])
}
return pubKey, nil
- })
+ }
+
+ opts := parserOptionsWithAudience(cfg)
+ return jwt.Parse(jwtString, keyFunc, opts...)
}
// Hash-based Message Authentication Code
func parseJwtTokenWithHMAC(cfg *config.Config, jwtString string) (*jwt.Token, error) {
- return jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
+ keyFunc := func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("parseJwt expected token algorithm HMAC but got: %v", token.Header["alg"])
}
return []byte(cfg.AuthJwtHmacSecret), nil
- })
+ }
+
+ opts := parserOptionsWithAudience(cfg)
+ return jwt.Parse(jwtString, keyFunc, opts...)
}
func lookupClaimValueOrDefault(claims jwt.MapClaims, key string, def string) string {
diff --git a/service/internal/auth/otjwt/jwt_test.go b/service/internal/auth/otjwt/jwt_test.go
index d3f48a8..ed4e3af 100644
--- a/service/internal/auth/otjwt/jwt_test.go
+++ b/service/internal/auth/otjwt/jwt_test.go
@@ -66,12 +66,19 @@ func newMux() *http.ServeMux {
}
func createJWTTokenWithExpiration(t *testing.T, privateKey *rsa.PrivateKey, expire int64) string {
+ return createJWTTokenWithExpirationAndAudience(t, privateKey, expire, "")
+}
+
+func createJWTTokenWithExpirationAndAudience(t *testing.T, privateKey *rsa.PrivateKey, expire int64, audience string) string {
token := jwt.New(jwt.SigningMethodRS256)
claims := token.Claims.(jwt.MapClaims)
claims["nbf"] = time.Now().Unix() - 1000
claims["exp"] = time.Now().Unix() + expire
claims["sub"] = "test"
claims["olivetinGroup"] = "test"
+ if audience != "" {
+ claims["aud"] = audience
+ }
tokenStr, err := token.SignedString(privateKey)
if err != nil {
@@ -108,6 +115,10 @@ func verifyJWTResponse(t *testing.T, res *http.Response, expectCode int) {
}
func testJwkValidation(t *testing.T, expire int64, expectCode int) {
+ testJwkValidationWithAudience(t, expire, expectCode, "", "")
+}
+
+func testJwkValidationWithAudience(t *testing.T, expire int64, expectCode int, configAudience, tokenAudience string) {
privateKey, publicKeyPath := createKeys(t)
defer os.Remove(publicKeyPath)
@@ -116,8 +127,9 @@ func testJwkValidation(t *testing.T, expire int64, expectCode int) {
cfg.AuthJwtClaimUsername = "sub"
cfg.AuthJwtClaimUserGroup = "olivetinGroup"
cfg.AuthJwtHeader = "Authorization"
+ cfg.AuthJwtAud = configAudience
- tokenStr := createJWTTokenWithExpiration(t, privateKey, expire)
+ tokenStr := createJWTTokenWithExpirationAndAudience(t, privateKey, expire, tokenAudience)
handler := setupJWTTestHandler(t, cfg)
srv := httptest.NewServer(handler)
@@ -135,6 +147,14 @@ func TestJWTSignatureVerificationFails(t *testing.T) {
testJwkValidation(t, -500, 403)
}
+func TestJWTAudienceValidationRejectsWrongAudience(t *testing.T) {
+ testJwkValidationWithAudience(t, 1000, 403, "expected-audience", "wrong-audience")
+}
+
+func TestJWTAudienceValidationAcceptsCorrectAudience(t *testing.T) {
+ testJwkValidationWithAudience(t, 1000, 200, "expected-audience", "expected-audience")
+}
+
func createJWTTokenWithGroups(t *testing.T, privateKey *rsa.PrivateKey, groups interface{}) string {
token := jwt.New(jwt.SigningMethodRS256)
claims := token.Claims.(jwt.MapClaims)
From d6a0abc3755d43107be1939567c52953bcbec3d5 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 4 Mar 2026 23:31:15 +0000
Subject: [PATCH 044/148] security: GHSA-gq2m-77hf-vwgh (MODERATE) Session
Fixation: Logout Fails to Invalidate Server-Side Session
---
service/internal/api/api.go | 2 +
.../auth/otoauth2/restapi_auth_oauth2.go | 6 +++
service/internal/auth/sessions.go | 37 ++++++++++++++++++-
service/internal/httpservers/frontend.go | 1 +
4 files changed, 44 insertions(+), 2 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 629d039..f2c81da 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -392,6 +392,8 @@ func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[ap
func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.LogoutRequest]) (*connect.Response[apiv1.LogoutResponse], error) {
user := auth.UserFromApiCall(ctx, req, api.cfg)
+ auth.RevokeSessionForProvider(api.cfg, user.Provider, user.SID)
+
log.WithFields(log.Fields{
"username": user.Username,
"provider": user.Provider,
diff --git a/service/internal/auth/otoauth2/restapi_auth_oauth2.go b/service/internal/auth/otoauth2/restapi_auth_oauth2.go
index a84e1f6..351a13b 100644
--- a/service/internal/auth/otoauth2/restapi_auth_oauth2.go
+++ b/service/internal/auth/otoauth2/restapi_auth_oauth2.go
@@ -391,6 +391,12 @@ func (h *OAuth2Handler) lookupOAuth2UserByState(state string) (*authTypes.Authen
return user, true
}
+func (h *OAuth2Handler) RevokeSession(sid string) {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+ delete(h.registeredStates, sid)
+}
+
func (h *OAuth2Handler) CheckUserFromOAuth2Cookie(context *authTypes.AuthCheckingContext) *authTypes.AuthenticatedUser {
cookie, err := context.Request.Cookie("olivetin-sid-oauth")
if err != nil || cookie.Value == "" {
diff --git a/service/internal/auth/sessions.go b/service/internal/auth/sessions.go
index f11fc8a..c8d7416 100644
--- a/service/internal/auth/sessions.go
+++ b/service/internal/auth/sessions.go
@@ -25,8 +25,9 @@ type SessionStorage struct {
}
var (
- sessionStorage *SessionStorage
- sessionStorageMutex sync.RWMutex
+ sessionStorage *SessionStorage
+ sessionStorageMutex sync.RWMutex
+ oauth2SessionRevoker func(sid string)
)
func init() {
@@ -58,6 +59,38 @@ func RegisterUserSession(cfg *config.Config, provider string, sid string, userna
saveUserSessions(cfg)
}
+// RegisterOAuth2SessionRevoker registers a callback to revoke OAuth2 sessions on logout.
+// OAuth2 uses its own session storage; the API calls this when provider is oauth2.
+func RegisterOAuth2SessionRevoker(fn func(sid string)) {
+ oauth2SessionRevoker = fn
+}
+
+// RevokeSessionForProvider invalidates the session for the given provider and SID (e.g. on logout).
+// Local auth uses shared SessionStorage; OAuth2 uses a separate storage and revoker.
+func RevokeSessionForProvider(cfg *config.Config, provider string, sid string) {
+ if sid == "" {
+ return
+ }
+ if provider == "oauth2" && oauth2SessionRevoker != nil {
+ oauth2SessionRevoker(sid)
+ return
+ }
+ RevokeUserSession(cfg, provider, sid)
+}
+
+// RevokeUserSession removes a session from storage so it can no longer be used (e.g. on logout).
+func RevokeUserSession(cfg *config.Config, provider string, sid string) {
+ sessionStorageMutex.Lock()
+ defer sessionStorageMutex.Unlock()
+
+ if sessionStorage.Providers[provider] != nil {
+ delete(sessionStorage.Providers[provider].Sessions, sid)
+ if cfg != nil {
+ saveUserSessions(cfg)
+ }
+ }
+}
+
// GetUserSession retrieves a user session
func GetUserSession(provider string, sid string) *UserSession {
sessionStorageMutex.Lock()
diff --git a/service/internal/httpservers/frontend.go b/service/internal/httpservers/frontend.go
index c1c1a1f..399c6fd 100644
--- a/service/internal/httpservers/frontend.go
+++ b/service/internal/httpservers/frontend.go
@@ -101,6 +101,7 @@ func StartFrontendMux(cfg *config.Config, ex *executor.Executor) {
oauth2handler := otoauth2.NewOAuth2Handler(cfg)
auth.AddAuthChainFunction(oauth2handler.CheckUserFromOAuth2Cookie)
+ auth.RegisterOAuth2SessionRevoker(oauth2handler.RevokeSession)
mux.HandleFunc("/oauth/login", oauth2handler.HandleOAuthLogin)
mux.HandleFunc("/oauth/callback", oauth2handler.HandleOAuthCallback)
From cb46a597b2465235839ed58cf034b5e7b70ef911 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 5 Mar 2026 00:04:58 +0000
Subject: [PATCH 045/148] security: GHSA-p443-p7w5-2f7f (MODERATE)
RestartAction always runs actions as guest
---
service/internal/api/api.go | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 629d039..4e0b1db 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -1271,8 +1271,6 @@ func (api *oliveTinAPI) RestartAction(ctx ctx.Context, req *connect.Request[apiv
ExecutionTrackingId: req.Msg.ExecutionTrackingId,
}
- var execReqLogEntry *executor.InternalLogEntry
-
execReqLogEntry, found := api.executor.GetLog(req.Msg.ExecutionTrackingId)
if !found {
@@ -1289,12 +1287,21 @@ func (api *oliveTinAPI) RestartAction(ctx ctx.Context, req *connect.Request[apiv
return connect.NewResponse(ret), nil
}
- return api.StartAction(ctx, &connect.Request[apiv1.StartActionRequest]{
- Msg: &apiv1.StartActionRequest{
- BindingId: execReqLogEntry.GetBindingId(),
- UniqueTrackingId: req.Msg.ExecutionTrackingId,
- },
- })
+ authenticatedUser := auth.UserFromApiCall(ctx, req, api.cfg)
+
+ // TrackingID is deliberately not passed to the executor, so that it generates a new one for the restarted execution.
+ // This is because the old execution (identified by the old TrackingID) is already used.
+ execReq := executor.ExecutionRequest{
+ Binding: execReqLogEntry.Binding,
+ Arguments: make(map[string]string),
+ AuthenticatedUser: authenticatedUser,
+ Cfg: api.cfg,
+ }
+
+ api.executor.ExecRequest(&execReq)
+
+ ret.ExecutionTrackingId = execReq.TrackingID
+ return connect.NewResponse(ret), nil
}
func newServer(ex *executor.Executor) *oliveTinAPI {
From 9d55d4a178573749ba93106748adf4c33269d10f Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 5 Mar 2026 00:24:00 +0000
Subject: [PATCH 046/148] docs: Policy change, 2k will receive security updates
much slower
---
SECURITY.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/SECURITY.md b/SECURITY.md
index 49fcf13..9b1ec7f 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,8 +6,8 @@ The following branches are currently being supported with security updates:
| Version | Supported |
| ------- | ------------------ |
-| `main` (3k release branch) | :white_check_mark: |
-| `release/2k` (2k release branch) | :white_check_mark: |
+| `main` (3k release branch) | :white_check_mark: - advisories will be published when patched in this branch |
+| `release/2k` (2k release branch) | :white_check_mark: - receives security updates, but much slower |
To understand more about 2k vs 3k, see the following docs; https://docs.olivetin.app/upgrade/2k3k.html
From 10f5ba62a261190efa472651b409cccd79b08d98 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 5 Mar 2026 08:07:56 +0000
Subject: [PATCH 047/148] docs: typos in SECURITY.md
---
SECURITY.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/SECURITY.md b/SECURITY.md
index 9b1ec7f..f4268df 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -47,9 +47,9 @@ Once a vulnerability is reported, the process is;
* Accept or reject the report, and communicate with the reporter about next steps.
* If accepted, patch using a temporary branch, and code review will be requested from the original reporter if they are interested.
-* The severity of the vulnerability will be assessed using CVSS, and the patch will be prioritized accordingly.
+* The severity of the vulnerability will be assessed using CVSS, and the patch will be prioritised accordingly.
* Once the patch is ready, it will be queued for a release onto the `next` branch (3k) or `release/2k` branch (2k)
-* The reporter will be credited in the advistory and the release notes, but not the commit message.
+* The reporter will be credited in the advisory and the release notes, but not the commit message.
* The commit message will contain a reference to the CVSS score (eg: MED) and the advisory ID.
From 9080577f2b7921add35dee70df8b6c24cfd6a184 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 5 Mar 2026 08:10:56 +0000
Subject: [PATCH 048/148] chore: potential crash in unit tests
---
service/internal/executor/executor_test.go | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/service/internal/executor/executor_test.go b/service/internal/executor/executor_test.go
index 16f491d..a36fc10 100644
--- a/service/internal/executor/executor_test.go
+++ b/service/internal/executor/executor_test.go
@@ -469,7 +469,10 @@ func TestTriggerUnknownActionTitleSkipsWithoutPanic(t *testing.T) {
case <-time.After(500 * time.Millisecond):
}
assert.Len(t, got, 1, "only the triggering action runs; unknown trigger is skipped")
- assert.Equal(t, "Action with bad trigger", got[0])
+
+ if len(got) > 0 {
+ assert.Equal(t, "Action with bad trigger", got[0])
+ }
}
type executionFinishedCollector struct {
From d7962710e7c46f6bdda4188b5b0cdbde4be665a0 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 5 Mar 2026 08:20:02 +0000
Subject: [PATCH 049/148] security: GHSA-jf73-858c-54pg (MODERATE) View
permission not being checked when returning dashboards
---
service/internal/api/api.go | 163 ++++++++++++++++++++---------
service/internal/api/apiActions.go | 53 ++++++----
service/internal/api/api_test.go | 115 ++++++++++++++++++++
service/internal/api/dashboards.go | 10 +-
4 files changed, 272 insertions(+), 69 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 629d039..a9e8c8a 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -70,20 +70,21 @@ func (api *oliveTinAPI) KillAction(ctx ctx.Context, req *connect.Request[apiv1.K
execReqLogEntry, ret.Found = api.executor.GetLog(req.Msg.ExecutionTrackingId)
if !ret.Found {
- log.Warnf("Killing execution request not possible - not found by tracking ID: %v", req.Msg.ExecutionTrackingId)
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
- log.Warnf("Killing execution request by tracking ID: %v", req.Msg.ExecutionTrackingId)
+ if execReqLogEntry.Binding == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("log entry has no binding for tracking ID %s", req.Msg.ExecutionTrackingId))
+ }
action := execReqLogEntry.Binding.Action
if action == nil {
- log.Warnf("Killing execution request not possible - action not found: %v", execReqLogEntry.ActionTitle)
- ret.Killed = false
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
+ log.Warnf("Killing execution request by tracking ID: %v", req.Msg.ExecutionTrackingId)
+
user := auth.UserFromApiCall(ctx, req, api.cfg)
api.killActionByTrackingId(user, action, execReqLogEntry, ret)
@@ -205,42 +206,58 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
return response, nil
}
-func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionAndWaitRequest]) (*connect.Response[apiv1.StartActionAndWaitResponse], error) {
- args := make(map[string]string)
-
- for _, arg := range req.Msg.Arguments {
- args[arg.Name] = arg.Value
- }
-
- user := auth.UserFromApiCall(ctx, req, api.cfg)
-
+func (api *oliveTinAPI) startActionAndWaitRun(binding *executor.ActionBinding, args map[string]string, user *authpublic.AuthenticatedUser) (*executor.InternalLogEntry, bool) {
execReq := executor.ExecutionRequest{
- Binding: api.executor.FindBindingByID(req.Msg.ActionId),
+ Binding: binding,
TrackingID: uuid.NewString(),
Arguments: args,
AuthenticatedUser: user,
Cfg: api.cfg,
}
-
wg, _ := api.executor.ExecRequest(&execReq)
wg.Wait()
+ return api.executor.GetLog(execReq.TrackingID)
+}
- internalLogEntry, ok := api.executor.GetLog(execReq.TrackingID)
-
- if ok {
- return connect.NewResponse(&apiv1.StartActionAndWaitResponse{
- LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
- }), nil
- } else {
- return nil, fmt.Errorf("execution not found")
+func (api *oliveTinAPI) findBindingOrNotFound(actionId string) (*executor.ActionBinding, error) {
+ binding := api.executor.FindBindingByID(actionId)
+ if binding == nil || binding.Action == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", actionId))
}
+ return binding, nil
+}
+
+func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionAndWaitRequest]) (*connect.Response[apiv1.StartActionAndWaitResponse], error) {
+ binding, err := api.findBindingOrNotFound(req.Msg.ActionId)
+ if err != nil {
+ return nil, err
+ }
+
+ args := make(map[string]string)
+ for _, arg := range req.Msg.Arguments {
+ args[arg.Name] = arg.Value
+ }
+ user := auth.UserFromApiCall(ctx, req, api.cfg)
+
+ internalLogEntry, ok := api.startActionAndWaitRun(binding, args, user)
+ if !ok {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
+ }
+ return connect.NewResponse(&apiv1.StartActionAndWaitResponse{
+ LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
+ }), nil
}
func (api *oliveTinAPI) StartActionByGet(ctx ctx.Context, req *connect.Request[apiv1.StartActionByGetRequest]) (*connect.Response[apiv1.StartActionByGetResponse], error) {
+ binding := api.executor.FindBindingByID(req.Msg.ActionId)
+ if binding == nil || binding.Action == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.ActionId))
+ }
+
args := make(map[string]string)
execReq := executor.ExecutionRequest{
- Binding: api.executor.FindBindingByID(req.Msg.ActionId),
+ Binding: binding,
TrackingID: uuid.NewString(),
Arguments: args,
AuthenticatedUser: auth.UserFromApiCall(ctx, req, api.cfg),
@@ -255,12 +272,17 @@ func (api *oliveTinAPI) StartActionByGet(ctx ctx.Context, req *connect.Request[a
}
func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionByGetAndWaitRequest]) (*connect.Response[apiv1.StartActionByGetAndWaitResponse], error) {
+ binding := api.executor.FindBindingByID(req.Msg.ActionId)
+ if binding == nil || binding.Action == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.ActionId))
+ }
+
args := make(map[string]string)
user := auth.UserFromApiCall(ctx, req, api.cfg)
execReq := executor.ExecutionRequest{
- Binding: api.executor.FindBindingByID(req.Msg.ActionId),
+ Binding: binding,
TrackingID: uuid.NewString(),
Arguments: args,
AuthenticatedUser: user,
@@ -276,9 +298,8 @@ func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *connect.Re
return connect.NewResponse(&apiv1.StartActionByGetAndWaitResponse{
LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
}), nil
- } else {
- return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
}
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
}
func calculateRateLimitExpires(api *oliveTinAPI, logEntry *executor.InternalLogEntry) string {
@@ -392,6 +413,8 @@ func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[ap
func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.LogoutRequest]) (*connect.Response[apiv1.LogoutResponse], error) {
user := auth.UserFromApiCall(ctx, req, api.cfg)
+ auth.RevokeSessionForProvider(api.cfg, user.Provider, user.SID)
+
log.WithFields(log.Fields{
"username": user.Username,
"provider": user.Provider,
@@ -434,19 +457,38 @@ func (api *oliveTinAPI) GetActionBinding(ctx ctx.Context, req *connect.Request[a
return nil, err
}
- binding := api.executor.FindBindingByID(req.Msg.BindingId)
-
- if binding == nil {
- return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.BindingId))
+ resp, err := api.getActionBindingResponse(user, req.Msg.BindingId)
+ if err != nil {
+ return nil, err
}
+ return connect.NewResponse(resp), nil
+}
- return connect.NewResponse(&apiv1.GetActionBindingResponse{
+func (api *oliveTinAPI) getActionBindingResponse(user *authpublic.AuthenticatedUser, bindingId string) (*apiv1.GetActionBindingResponse, error) {
+ binding := api.executor.FindBindingByID(bindingId)
+ if binding == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", bindingId))
+ }
+ if binding.Action == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", bindingId))
+ }
+ if !api.userCanViewAction(user, binding.Action) {
+ return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
+ }
+ return &apiv1.GetActionBindingResponse{
Action: buildAction(binding, &DashboardRenderRequest{
cfg: api.cfg,
AuthenticatedUser: user,
ex: api.executor,
}),
- }), nil
+ }, nil
+}
+
+func (api *oliveTinAPI) userCanViewAction(user *authpublic.AuthenticatedUser, action *config.Action) bool {
+ if user == nil {
+ return true
+ }
+ return acl.IsAllowedView(api.cfg, user, action)
}
func (api *oliveTinAPI) GetDashboard(ctx ctx.Context, req *connect.Request[apiv1.GetDashboardRequest]) (*connect.Response[apiv1.GetDashboardResponse], error) {
@@ -646,7 +688,16 @@ error messages more quickly before starting the action.
It uses the same validation logic as the executor, including mangling argument
values (e.g., datetime formatting, checkbox title-to-value conversion).
*/
+func (api *oliveTinAPI) argumentNotFoundForValidation(msg *apiv1.ValidateArgumentTypeRequest) bool {
+ arg, _ := api.findArgumentForValidation(msg.BindingId, msg.ArgumentName)
+ return arg == nil && (msg.BindingId != "" || msg.ArgumentName != "")
+}
+
func (api *oliveTinAPI) ValidateArgumentType(ctx ctx.Context, req *connect.Request[apiv1.ValidateArgumentTypeRequest]) (*connect.Response[apiv1.ValidateArgumentTypeResponse], error) {
+ if api.argumentNotFoundForValidation(req.Msg) {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action or argument not found for binding ID %s", req.Msg.BindingId))
+ }
+
err := api.validateArgumentTypeInternal(req.Msg)
desc := ""
if err != nil {
@@ -747,6 +798,13 @@ func (api *oliveTinAPI) DumpVars(ctx ctx.Context, req *connect.Request[apiv1.Dum
return connect.NewResponse(res), nil
}
+func debugBindingActionTitle(binding *executor.ActionBinding) string {
+ if binding == nil || binding.Action == nil {
+ return ""
+ }
+ return binding.Action.Title
+}
+
func (api *oliveTinAPI) DumpPublicIdActionMap(ctx ctx.Context, req *connect.Request[apiv1.DumpPublicIdActionMapRequest]) (*connect.Response[apiv1.DumpPublicIdActionMapResponse], error) {
res := &apiv1.DumpPublicIdActionMapResponse{}
res.Contents = make(map[string]*apiv1.DebugBinding)
@@ -761,7 +819,7 @@ func (api *oliveTinAPI) DumpPublicIdActionMap(ctx ctx.Context, req *connect.Requ
for k, v := range api.executor.MapActionBindings {
res.Contents[k] = &apiv1.DebugBinding{
- ActionTitle: v.Action.Title,
+ ActionTitle: debugBindingActionTitle(v),
}
}
@@ -1271,30 +1329,37 @@ func (api *oliveTinAPI) RestartAction(ctx ctx.Context, req *connect.Request[apiv
ExecutionTrackingId: req.Msg.ExecutionTrackingId,
}
- var execReqLogEntry *executor.InternalLogEntry
-
execReqLogEntry, found := api.executor.GetLog(req.Msg.ExecutionTrackingId)
if !found {
- log.Warnf("Restarting execution request not possible - not found by tracking ID: %v", req.Msg.ExecutionTrackingId)
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
- log.Warnf("Restarting execution request by tracking ID: %v", req.Msg.ExecutionTrackingId)
+ if execReqLogEntry.Binding == nil {
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("log entry has no binding for tracking ID %s", req.Msg.ExecutionTrackingId))
+ }
action := execReqLogEntry.Binding.Action
if action == nil {
- log.Warnf("Restarting execution request not possible - action not found: %v", execReqLogEntry.ActionTitle)
- return connect.NewResponse(ret), nil
+ return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action not found for tracking ID %s", req.Msg.ExecutionTrackingId))
}
- return api.StartAction(ctx, &connect.Request[apiv1.StartActionRequest]{
- Msg: &apiv1.StartActionRequest{
- BindingId: execReqLogEntry.GetBindingId(),
- UniqueTrackingId: req.Msg.ExecutionTrackingId,
- },
- })
+ authenticatedUser := auth.UserFromApiCall(ctx, req, api.cfg)
+
+ // TrackingID is deliberately not passed to the executor, so that it generates a new one for the restarted execution.
+ // This is because the old execution (identified by the old TrackingID) is already used.
+ execReq := executor.ExecutionRequest{
+ Binding: execReqLogEntry.Binding,
+ Arguments: make(map[string]string),
+ AuthenticatedUser: authenticatedUser,
+ Cfg: api.cfg,
+ }
+
+ api.executor.ExecRequest(&execReq)
+
+ ret.ExecutionTrackingId = execReq.TrackingID
+ return connect.NewResponse(ret), nil
}
func newServer(ex *executor.Executor) *oliveTinAPI {
diff --git a/service/internal/api/apiActions.go b/service/internal/api/apiActions.go
index 355f179..4a23125 100644
--- a/service/internal/api/apiActions.go
+++ b/service/internal/api/apiActions.go
@@ -28,18 +28,22 @@ func (rr *DashboardRenderRequest) findAction(title string) *apiv1.Action {
return rr.findActionForEntity(title, nil)
}
+func bindingMatchesTitleAndEntity(binding *executor.ActionBinding, title string, entity *entities.Entity) bool {
+ return binding != nil && binding.Action != nil && binding.Action.Title == title && matchesEntity(binding, entity)
+}
+
func (rr *DashboardRenderRequest) findActionForEntity(title string, entity *entities.Entity) *apiv1.Action {
rr.ex.MapActionBindingsLock.RLock()
defer rr.ex.MapActionBindingsLock.RUnlock()
for _, binding := range rr.ex.MapActionBindings {
- if binding.Action.Title != title {
+ if !bindingMatchesTitleAndEntity(binding, title, entity) {
continue
}
-
- if matchesEntity(binding, entity) {
- return buildAction(binding, rr)
+ if !acl.IsAllowedView(rr.cfg, rr.AuthenticatedUser, binding.Action) {
+ return nil
}
+ return buildAction(binding, rr)
}
return nil
@@ -117,26 +121,37 @@ func getDefaultArgumentValue(cfgArg config.ActionArgument, entity *entities.Enti
return defaultValue
}
+func formatRateLimitExpiry(expiryUnix int64) string {
+ if expiryUnix <= 0 {
+ return ""
+ }
+ return time.Unix(expiryUnix, 0).Format("2006-01-02 15:04:05")
+}
+
+func actionFromBinding(actionBinding *executor.ActionBinding) (*executor.ActionBinding, *config.Action) {
+ if actionBinding == nil || actionBinding.Action == nil {
+ return nil, nil
+ }
+ return actionBinding, actionBinding.Action
+}
+
func buildAction(actionBinding *executor.ActionBinding, rr *DashboardRenderRequest) *apiv1.Action {
- action := actionBinding.Action
-
- aclCanExec := acl.IsAllowedExec(rr.cfg, rr.AuthenticatedUser, action)
- enabledExprCanExec := evaluateEnabledExpression(action, actionBinding.Entity)
-
- // Calculate rate limit expiry time
- expiryUnix := rr.ex.GetTimeUntilAvailable(actionBinding)
- datetimeRateLimitExpires := ""
- if expiryUnix > 0 {
- datetimeRateLimitExpires = time.Unix(expiryUnix, 0).Format("2006-01-02 15:04:05")
+ binding, action := actionFromBinding(actionBinding)
+ if binding == nil {
+ return nil
}
+ aclCanExec := acl.IsAllowedExec(rr.cfg, rr.AuthenticatedUser, action)
+ enabledExprCanExec := evaluateEnabledExpression(action, binding.Entity)
+ datetimeRateLimitExpires := formatRateLimitExpiry(rr.ex.GetTimeUntilAvailable(binding))
+
btn := apiv1.Action{
- BindingId: actionBinding.ID,
- Title: tpl.ParseTemplateOfActionBeforeExec(action.Title, actionBinding.Entity),
- Icon: tpl.ParseTemplateOfActionBeforeExec(action.Icon, actionBinding.Entity),
+ BindingId: binding.ID,
+ Title: tpl.ParseTemplateOfActionBeforeExec(action.Title, binding.Entity),
+ Icon: tpl.ParseTemplateOfActionBeforeExec(action.Icon, binding.Entity),
CanExec: aclCanExec && enabledExprCanExec,
PopupOnStart: action.PopupOnStart,
- Order: int32(actionBinding.ConfigOrder),
+ Order: int32(binding.ConfigOrder),
Timeout: int32(action.Timeout),
DatetimeRateLimitExpires: datetimeRateLimitExpires,
}
@@ -147,7 +162,7 @@ func buildAction(actionBinding *executor.ActionBinding, rr *DashboardRenderReque
Title: cfgArg.Title,
Type: cfgArg.Type,
Description: cfgArg.Description,
- DefaultValue: getDefaultArgumentValue(cfgArg, actionBinding.Entity),
+ DefaultValue: getDefaultArgumentValue(cfgArg, binding.Entity),
Choices: buildChoices(cfgArg),
Suggestions: cfgArg.Suggestions,
SuggestionsBrowserKey: cfgArg.SuggestionsBrowserKey,
diff --git a/service/internal/api/api_test.go b/service/internal/api/api_test.go
index 790e315..6e24d33 100644
--- a/service/internal/api/api_test.go
+++ b/service/internal/api/api_test.go
@@ -6,6 +6,7 @@ import (
"connectrpc.com/connect"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
log "github.com/sirupsen/logrus"
@@ -335,3 +336,117 @@ func testWithEntity(t *testing.T, binding *executor.ActionBinding, rr *Dashboard
actionResult := buildAction(binding, rr)
assert.Equal(t, expectedCanExec, actionResult.CanExec, message)
}
+
+// buildViewPermissionTestConfig returns config and users for GHSA view-permission tests:
+// one action "secret_action", ACL "restricted" (view:false) for user "low", ACL "full" (view:true) for user "admin".
+func buildViewPermissionTestConfig(t *testing.T) (*config.Config, *authpublic.AuthenticatedUser, *authpublic.AuthenticatedUser) {
+ t.Helper()
+ cfg := config.DefaultConfig()
+ cfg.DefaultPermissions.View = false
+ cfg.DefaultPermissions.Exec = false
+
+ cfg.Actions = append(cfg.Actions, &config.Action{
+ ID: "secret_action",
+ Title: "Secret Action",
+ Shell: "echo sensitive",
+ Icon: "🔒",
+ })
+
+ cfg.AccessControlLists = append(cfg.AccessControlLists,
+ &config.AccessControlList{
+ Name: "restricted",
+ MatchUsernames: []string{"low"},
+ AddToEveryAction: true,
+ Permissions: config.PermissionsList{View: false, Exec: false, Logs: false, Kill: false},
+ },
+ &config.AccessControlList{
+ Name: "full",
+ MatchUsernames: []string{"admin"},
+ AddToEveryAction: true,
+ Permissions: config.PermissionsList{View: true, Exec: true, Logs: true, Kill: true},
+ },
+ )
+
+ lowUser := &authpublic.AuthenticatedUser{Username: "low"}
+ lowUser.BuildUserAcls(cfg)
+ adminUser := &authpublic.AuthenticatedUser{Username: "admin"}
+ adminUser.BuildUserAcls(cfg)
+ return cfg, lowUser, adminUser
+}
+
+// TestViewPermissionExcludedFromDashboard (GHSA: view permission) asserts that when a user has view: false,
+// the default dashboard must not include that action. Covers GetDashboard not leaking action metadata.
+func TestViewPermissionExcludedFromDashboard(t *testing.T) {
+ cfg, lowUser, _ := buildViewPermissionTestConfig(t)
+ ex := executor.DefaultExecutor(cfg)
+ ex.RebuildActionMap()
+
+ rr := &DashboardRenderRequest{
+ AuthenticatedUser: lowUser,
+ cfg: cfg,
+ ex: ex,
+ }
+ db := buildDefaultDashboard(rr)
+
+ bindingIdsInDashboard := bindingIdsInDashboardContents(db.Contents)
+ assert.NotContains(t, bindingIdsInDashboard, "secret_action",
+ "user with view:false must not see action in dashboard; got bindingIds: %v", bindingIdsInDashboard)
+}
+
+// TestGetActionBindingDeniedWhenNoViewPermission (GHSA: view permission) asserts that GetActionBinding
+// returns permission denied for a user with view: false. Covers GetActionBinding not exposing action details.
+func TestGetActionBindingDeniedWhenNoViewPermission(t *testing.T) {
+ cfg, lowUser, _ := buildViewPermissionTestConfig(t)
+ ex := executor.DefaultExecutor(cfg)
+ ex.RebuildActionMap()
+ api := newServer(ex)
+
+ _, err := api.getActionBindingResponse(lowUser, "secret_action")
+ require.Error(t, err)
+ assert.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err),
+ "user with view:false must get permission denied from GetActionBinding")
+}
+
+// TestViewPermissionAllowedSeesAction (GHSA: view permission) asserts that a user with view: true
+// still sees the action in the dashboard and can fetch it via GetActionBinding.
+func TestViewPermissionAllowedSeesAction(t *testing.T) {
+ cfg, _, adminUser := buildViewPermissionTestConfig(t)
+ ex := executor.DefaultExecutor(cfg)
+ ex.RebuildActionMap()
+ api := newServer(ex)
+
+ rr := &DashboardRenderRequest{
+ AuthenticatedUser: adminUser,
+ cfg: cfg,
+ ex: ex,
+ }
+ db := buildDefaultDashboard(rr)
+ bindingIdsInDashboard := bindingIdsInDashboardContents(db.Contents)
+ assert.Contains(t, bindingIdsInDashboard, "secret_action",
+ "user with view:true must see action in dashboard; got bindingIds: %v", bindingIdsInDashboard)
+
+ resp, err := api.getActionBindingResponse(adminUser, "secret_action")
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+ require.NotNil(t, resp.Action)
+ assert.Equal(t, "secret_action", resp.Action.BindingId)
+}
+
+func bindingIdsInDashboardContents(contents []*apiv1.DashboardComponent) []string {
+ var ids []string
+ for _, c := range contents {
+ ids = append(ids, bindingIdsFromComponent(c)...)
+ }
+ return ids
+}
+
+func bindingIdsFromComponent(c *apiv1.DashboardComponent) []string {
+ if c == nil {
+ return nil
+ }
+ var ids []string
+ if c.Action != nil && c.Action.BindingId != "" {
+ ids = append(ids, c.Action.BindingId)
+ }
+ return append(ids, bindingIdsInDashboardContents(c.Contents)...)
+}
diff --git a/service/internal/api/dashboards.go b/service/internal/api/dashboards.go
index 11c6cf3..575994c 100644
--- a/service/internal/api/dashboards.go
+++ b/service/internal/api/dashboards.go
@@ -4,6 +4,7 @@ import (
"sort"
apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
+ acl "github.com/OliveTin/OliveTin/internal/acl"
config "github.com/OliveTin/OliveTin/internal/config"
entities "github.com/OliveTin/OliveTin/internal/entities"
"github.com/OliveTin/OliveTin/internal/tpl"
@@ -130,7 +131,7 @@ func buildDefaultDashboard(rr *DashboardRenderRequest) *apiv1.Dashboard {
}
for _, binding := range rr.ex.MapActionBindings {
- if binding.Action.Hidden {
+ if binding == nil || binding.Action == nil || binding.Action.Hidden {
continue
}
@@ -138,7 +139,14 @@ func buildDefaultDashboard(rr *DashboardRenderRequest) *apiv1.Dashboard {
continue
}
+ if !acl.IsAllowedView(rr.cfg, rr.AuthenticatedUser, binding.Action) {
+ continue
+ }
+
action := buildAction(binding, rr)
+ if action == nil {
+ continue
+ }
fieldset.Contents = append(fieldset.Contents, &apiv1.DashboardComponent{
Type: "link",
From b032ae5e5e3fc5f9a0d3c8f66b9dce7bd276c9ef Mon Sep 17 00:00:00 2001
From: jamesread
Date: Thu, 5 Mar 2026 08:29:30 +0000
Subject: [PATCH 050/148] chore: fix regression on arguments not being found
---
service/internal/api/api.go | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 20e41dd..83465aa 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -466,15 +466,15 @@ func (api *oliveTinAPI) GetActionBinding(ctx ctx.Context, req *connect.Request[a
func (api *oliveTinAPI) getActionBindingResponse(user *authpublic.AuthenticatedUser, bindingId string) (*apiv1.GetActionBindingResponse, error) {
binding := api.executor.FindBindingByID(bindingId)
-
+
if binding == nil || binding.Action == nil {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", bindingId))
}
-
+
if !api.userCanViewAction(user, binding.Action) {
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
}
-
+
return &apiv1.GetActionBindingResponse{
Action: buildAction(binding, &DashboardRenderRequest{
cfg: api.cfg,
@@ -689,8 +689,13 @@ It uses the same validation logic as the executor, including mangling argument
values (e.g., datetime formatting, checkbox title-to-value conversion).
*/
func (api *oliveTinAPI) argumentNotFoundForValidation(msg *apiv1.ValidateArgumentTypeRequest) bool {
+ if msg.BindingId == "" || msg.ArgumentName == "" {
+ return false
+ }
+
arg, _ := api.findArgumentForValidation(msg.BindingId, msg.ArgumentName)
- return arg == nil && (msg.BindingId != "" || msg.ArgumentName != "")
+
+ return arg == nil
}
func (api *oliveTinAPI) ValidateArgumentType(ctx ctx.Context, req *connect.Request[apiv1.ValidateArgumentTypeRequest]) (*connect.Response[apiv1.ValidateArgumentTypeResponse], error) {
From 3f46007281769e09704c339231fc260fd4a41e72 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 8 Mar 2026 20:46:52 +0000
Subject: [PATCH 051/148] fix: Relax default CSP to allow iconify to work
---
service/internal/config/config.go | 2 +-
service/internal/config/sanitize.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/service/internal/config/config.go b/service/internal/config/config.go
index 82c466f..742f6bf 100644
--- a/service/internal/config/config.go
+++ b/service/internal/config/config.go
@@ -281,7 +281,7 @@ func DefaultConfigWithBasePort(basePort int) *Config {
config.Prometheus.Enabled = false
config.Prometheus.DefaultGoMetrics = false
config.Security.HeaderContentSecurityPolicy = true
- config.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'"
+ config.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'"
config.Security.HeaderXContentTypeOptions = true
config.Security.HeaderXFrameOptions = true
config.Security.XFrameOptions = "DENY"
diff --git a/service/internal/config/sanitize.go b/service/internal/config/sanitize.go
index ddeac5b..f612edb 100644
--- a/service/internal/config/sanitize.go
+++ b/service/internal/config/sanitize.go
@@ -194,7 +194,7 @@ func (cfg *Config) sanitizeSecurityHeadersCSP() {
if !cfg.Security.HeaderContentSecurityPolicy || cfg.Security.ContentSecurityPolicy != "" {
return
}
- cfg.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'"
+ cfg.Security.ContentSecurityPolicy = "default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'"
}
func (cfg *Config) sanitizeSecurityHeadersXFrameOptions() {
From 0fee24089fba815a219a158d178a7428c53f64e9 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 8 Mar 2026 21:11:27 +0000
Subject: [PATCH 052/148] fix: Restart action button was not working
---
frontend/resources/vue/views/ExecutionView.vue | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/frontend/resources/vue/views/ExecutionView.vue b/frontend/resources/vue/views/ExecutionView.vue
index 169572d..a8de151 100644
--- a/frontend/resources/vue/views/ExecutionView.vue
+++ b/frontend/resources/vue/views/ExecutionView.vue
@@ -169,14 +169,15 @@ function show(actionButton) {
}
async function rerunAction() {
- if (!logEntry.value || !logEntry.value.actionId) {
+ const bindingId = logEntry.value?.bindingId
+ if (!logEntry.value || !bindingId) {
console.error('Cannot rerun: no action ID available')
return
}
try {
const startActionArgs = {
- "bindingId": logEntry.value.actionId,
+ "bindingId": bindingId,
"arguments": []
}
@@ -281,13 +282,13 @@ async function renderExecutionResult(res) {
}
executionTrackingId.value = res.logEntry.executionTrackingId
- canRerun.value = res.logEntry.executionFinished
+ canRerun.value = res.logEntry.executionFinished && !!res.logEntry.bindingId
canKill.value = res.logEntry.canKill
icon.value = res.logEntry.actionIcon
title.value = res.logEntry.actionTitle
- titleTooltip.value = 'Action ID: ' + res.logEntry.actionId + '\nExecution ID: ' + res.logEntry.executionTrackingId
- actionId.value = res.logEntry.actionId
+ titleTooltip.value = 'Action ID: ' + res.logEntry.bindingId + '\nExecution ID: ' + res.logEntry.executionTrackingId
+ actionId.value = res.logEntry.bindingId
updateDuration(res.logEntry)
From 5ff6b5d08083ba8f362964d9a6793a58e5327bce Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 8 Mar 2026 22:16:24 +0000
Subject: [PATCH 053/148] fix: Entity ordering (#886, #762)
---
AGENTS.md | 6 +-
.../entityFilesWithLongIntsUseStandardForm.js | 10 +--
service/internal/api/apiActions.go | 4 +-
service/internal/api/api_test.go | 26 ++++++
service/internal/api/dashboard_entities.go | 7 +-
service/internal/api/dashboards.go | 80 +++++++++++++++++--
service/internal/entities/entities_test.go | 45 ++++++++++-
service/internal/entities/storage.go | 45 +++++++++++
service/internal/executor/executor_actions.go | 2 +-
specs/dashboard-component-ordering.md | 52 ++++++++++++
10 files changed, 252 insertions(+), 25 deletions(-)
create mode 100644 specs/dashboard-component-ordering.md
diff --git a/AGENTS.md b/AGENTS.md
index db2412f..40dd89a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -13,6 +13,7 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
- **Frontend (Vue 3)**: `frontend/` (served by the service)
- **Integration tests**: `integration-tests/`
- **Protos/Generated**: `proto/`, `service/gen/...`
+- **Specs**: `specs/` — Markdown specs that define how code should behave in human-readable form. When changing behavior in a spec-covered area, keep implementation and tests aligned with the spec; do not reference code or symbols in specs (English only).
### How to Run
- Run the server (dev):
@@ -62,11 +63,10 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
### Contributing Checklist
- Review the contributing guidelines at `CONTRIBUTING.adoc`.
- Review the AI guidance in `AI.md`.
-- Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`.
+- Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`.
+- When changing behaviour covered by a spec in `specs/`, ensure implementation and tests match the spec.
### Troubleshooting
- API tests failing with content-type errors: ensure Connect handler is served under `/api/` and the client targets that base URL.
- Executor panics: check for nil `Binding/Action` and add guards in step functions.
- Integration timeouts: wait for `loaded-dashboard` and use selectors matching the Vue UI.
-
-
diff --git a/integration-tests/tests/entityFilesWithLongIntsUseStandardForm/entityFilesWithLongIntsUseStandardForm.js b/integration-tests/tests/entityFilesWithLongIntsUseStandardForm/entityFilesWithLongIntsUseStandardForm.js
index eb92a07..cc3a320 100644
--- a/integration-tests/tests/entityFilesWithLongIntsUseStandardForm/entityFilesWithLongIntsUseStandardForm.js
+++ b/integration-tests/tests/entityFilesWithLongIntsUseStandardForm/entityFilesWithLongIntsUseStandardForm.js
@@ -2,8 +2,8 @@
import { describe, it, before, after } from 'mocha'
import { expect } from 'chai'
import { By, until, Condition } from 'selenium-webdriver'
-import {
- getRootAndWait,
+import {
+ getRootAndWait,
getActionButtons,
takeScreenshotOnFailure,
} from '../../lib/elements.js'
@@ -29,8 +29,8 @@ describe('config: entities', function () {
expect(buttons).to.not.be.null
expect(buttons).to.have.length(5)
- // Test INT with 10 numbers
- const buttonInt10 = await buttons[2]
+ // Entity buttons are in numeric key order (0,1,2,3,4); first row is "INT with 10 numbers"
+ const buttonInt10 = await buttons[0]
expect(await buttonInt10.getAttribute('title')).to.be.equal('Test me INT with 10 numbers')
await buttonInt10.click()
@@ -49,7 +49,7 @@ describe('config: entities', function () {
// Check that the execution completed successfully by looking at the status
const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
const statusText = await statusElement.getText()
-
+
// The status should indicate success (not "Executing..." or "Failed")
expect(statusText).to.not.include('Executing')
expect(statusText).to.not.include('Failed')
diff --git a/service/internal/api/apiActions.go b/service/internal/api/apiActions.go
index 4a23125..45fcad1 100644
--- a/service/internal/api/apiActions.go
+++ b/service/internal/api/apiActions.go
@@ -185,9 +185,7 @@ func buildChoices(arg config.ActionArgument) []*apiv1.ActionArgumentChoice {
func buildChoicesEntity(firstChoice config.ActionArgumentChoice, entityTitle string) []*apiv1.ActionArgumentChoice {
ret := []*apiv1.ActionArgumentChoice{}
- entList := entities.GetEntityInstances(entityTitle)
-
- for _, ent := range entList {
+ for _, ent := range entities.GetEntityInstancesOrdered(entityTitle) {
ret = append(ret, &apiv1.ActionArgumentChoice{
Value: tpl.ParseTemplateOfActionBeforeExec(firstChoice.Value, ent),
Title: tpl.ParseTemplateOfActionBeforeExec(firstChoice.Title, ent),
diff --git a/service/internal/api/api_test.go b/service/internal/api/api_test.go
index 6e24d33..7bca057 100644
--- a/service/internal/api/api_test.go
+++ b/service/internal/api/api_test.go
@@ -450,3 +450,29 @@ func bindingIdsFromComponent(c *apiv1.DashboardComponent) []string {
}
return append(ids, bindingIdsInDashboardContents(c.Contents)...)
}
+
+func TestOrderTopLevelDashboardComponents_RegularFieldsetsPreserveConfigOrder(t *testing.T) {
+ zebra := &apiv1.DashboardComponent{Title: "Zebra", Type: "fieldset", EntityType: ""}
+ alpha := &apiv1.DashboardComponent{Title: "Alpha", Type: "fieldset", EntityType: ""}
+ root := &apiv1.DashboardComponent{Title: "Actions", Type: "fieldset", EntityType: ""}
+ components := []*apiv1.DashboardComponent{zebra, alpha, root}
+
+ out := orderTopLevelDashboardComponents(components)
+
+ require.Len(t, out, 3)
+ assert.Same(t, zebra, out[0], "first must be Zebra (config order)")
+ assert.Same(t, alpha, out[1], "second must be Alpha (config order)")
+ assert.Same(t, root, out[2], "third must be root Actions fieldset")
+}
+
+func TestOrderTopLevelDashboardComponents_SortablesSorted(t *testing.T) {
+ entityBeta := &apiv1.DashboardComponent{Title: "Beta", Type: "fieldset", EntityType: "server"}
+ entityAlpha := &apiv1.DashboardComponent{Title: "Alpha", Type: "fieldset", EntityType: "server"}
+ components := []*apiv1.DashboardComponent{entityBeta, entityAlpha}
+
+ out := orderTopLevelDashboardComponents(components)
+
+ require.Len(t, out, 2)
+ assert.Equal(t, "Alpha", out[0].Title, "sortables ordered by title")
+ assert.Equal(t, "Beta", out[1].Title)
+}
diff --git a/service/internal/api/dashboard_entities.go b/service/internal/api/dashboard_entities.go
index 2ddf964..d9a12d6 100644
--- a/service/internal/api/dashboard_entities.go
+++ b/service/internal/api/dashboard_entities.go
@@ -11,9 +11,8 @@ import (
func buildEntityFieldsets(entityTitle string, tpl *config.DashboardComponent, rr *DashboardRenderRequest) []*apiv1.DashboardComponent {
ret := make([]*apiv1.DashboardComponent, 0)
- entities := entities.GetEntityInstances(entityTitle)
-
- for _, ent := range entities {
+ orderedEntities := entities.GetEntityInstancesOrdered(entityTitle)
+ for _, ent := range orderedEntities {
fs := buildEntityFieldset(tpl, ent, rr)
if len(fs.Contents) > 0 {
@@ -30,7 +29,7 @@ func buildEntityFieldset(component *config.DashboardComponent, ent *entities.Ent
Type: "fieldset",
Contents: removeFieldsetIfHasNoLinks(buildEntityFieldsetContents(component.Contents, ent, component.Entity, rr)),
CssClass: tpl.ParseTemplateOfActionBeforeExec(component.CssClass, ent),
- Action: rr.findAction(component.Title),
+ Action: rr.findActionForEntity(component.Title, ent),
EntityType: component.Entity,
EntityKey: ent.UniqueKey,
}
diff --git a/service/internal/api/dashboards.go b/service/internal/api/dashboards.go
index 575994c..4e98fde 100644
--- a/service/internal/api/dashboards.go
+++ b/service/internal/api/dashboards.go
@@ -2,6 +2,7 @@ package api
import (
"sort"
+ "strconv"
apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
acl "github.com/OliveTin/OliveTin/internal/acl"
@@ -113,7 +114,7 @@ func buildDashboardFromConfig(dashboard *config.DashboardComponent, rr *Dashboar
func buildDashboardFromConfigWithEntity(dashboard *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.Dashboard {
return &apiv1.Dashboard{
Title: dashboard.Title,
- Contents: sortActions(removeNulls(getDashboardComponentContentsWithEntity(dashboard, rr, entity))),
+ Contents: orderTopLevelDashboardComponents(removeNulls(getDashboardComponentContentsWithEntity(dashboard, rr, entity))),
}
}
@@ -148,33 +149,49 @@ func buildDefaultDashboard(rr *DashboardRenderRequest) *apiv1.Dashboard {
continue
}
- fieldset.Contents = append(fieldset.Contents, &apiv1.DashboardComponent{
+ comp := &apiv1.DashboardComponent{
Type: "link",
Title: action.Title,
Icon: action.Icon,
Action: action,
- })
+ }
+ if binding.Entity != nil {
+ comp.EntityKey = binding.Entity.UniqueKey
+ }
+ fieldset.Contents = append(fieldset.Contents, comp)
}
if len(fieldset.Contents) > 0 {
- fieldset.Contents = sortActions(fieldset.Contents)
+ fieldset.Contents = sortDashboardComponents(fieldset.Contents)
db.Contents = append(db.Contents, fieldset)
}
return db
}
-func sortActions(components []*apiv1.DashboardComponent) []*apiv1.DashboardComponent {
+func entityKeyLess(a, b string) bool {
+ ai, errA := strconv.ParseInt(a, 10, 64)
+ bi, errB := strconv.ParseInt(b, 10, 64)
+ if errA == nil && errB == nil {
+ return ai < bi
+ }
+ return a < b
+}
+
+//gocyclo:ignore
+func sortDashboardComponents(components []*apiv1.DashboardComponent) []*apiv1.DashboardComponent {
sort.Slice(components, func(i, j int) bool {
if components[i].Action == nil || components[j].Action == nil {
return components[i].Title < components[j].Title
}
- if components[i].Action.Order == components[j].Action.Order {
- return components[i].Action.Title < components[j].Action.Title
- } else {
+ if components[i].Action.Order != components[j].Action.Order {
return components[i].Action.Order < components[j].Action.Order
}
+ if components[i].EntityKey != components[j].EntityKey {
+ return entityKeyLess(components[i].EntityKey, components[j].EntityKey)
+ }
+ return components[i].Action.Title < components[j].Action.Title
})
return components
@@ -194,6 +211,53 @@ func removeNulls(components []*apiv1.DashboardComponent) []*apiv1.DashboardCompo
return ret
}
+func isRegularFieldset(component *apiv1.DashboardComponent, index int, totalLen int) bool {
+ if component == nil || component.Type != "fieldset" || component.EntityType != "" {
+ return false
+ }
+ return index != totalLen-1
+}
+
+func partitionTopLevelComponents(components []*apiv1.DashboardComponent) (regular, sortables []*apiv1.DashboardComponent, isRegular []bool) {
+ regular = make([]*apiv1.DashboardComponent, 0)
+ sortables = make([]*apiv1.DashboardComponent, 0)
+ isRegular = make([]bool, len(components))
+ for i, c := range components {
+ anchor := isRegularFieldset(c, i, len(components))
+ isRegular[i] = anchor
+ if anchor {
+ regular = append(regular, c)
+ } else {
+ sortables = append(sortables, c)
+ }
+ }
+ return regular, sortables, isRegular
+}
+
+func mergeOrderedTopLevelComponents(regular, sortables []*apiv1.DashboardComponent, isRegular []bool) []*apiv1.DashboardComponent {
+ out := make([]*apiv1.DashboardComponent, 0, len(isRegular))
+ regIdx, sortIdx := 0, 0
+ for _, anchor := range isRegular {
+ if anchor {
+ out = append(out, regular[regIdx])
+ regIdx++
+ } else {
+ out = append(out, sortables[sortIdx])
+ sortIdx++
+ }
+ }
+ return out
+}
+
+func orderTopLevelDashboardComponents(components []*apiv1.DashboardComponent) []*apiv1.DashboardComponent {
+ if len(components) == 0 {
+ return components
+ }
+ regular, sortables, isRegular := partitionTopLevelComponents(components)
+ sortDashboardComponents(sortables)
+ return mergeOrderedTopLevelComponents(regular, sortables, isRegular)
+}
+
func getDashboardComponentContentsWithEntity(dashboard *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) []*apiv1.DashboardComponent {
ret := make([]*apiv1.DashboardComponent, 0)
rootFieldset := createRootFieldset()
diff --git a/service/internal/entities/entities_test.go b/service/internal/entities/entities_test.go
index ab135d3..b13fd08 100644
--- a/service/internal/entities/entities_test.go
+++ b/service/internal/entities/entities_test.go
@@ -1,8 +1,10 @@
package entities
import (
- // "github.com/stretchr/testify/assert"
"testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestLoadObjectPerLineJsonFile(t *testing.T) {
@@ -16,3 +18,44 @@ func TestLoadObjectPerLineJsonFile(t *testing.T) {
assert.Equal(t, "1234567890", GetEntity("testrow", "0"), "Value should match expected value")
*/
}
+
+func TestGetEntityInstancesOrdered_numericKeys(t *testing.T) {
+ ClearEntitiesOfType("order_test")
+ defer ClearEntitiesOfType("order_test")
+
+ AddEntity("order_test", "2", map[string]any{"title": "Second"})
+ AddEntity("order_test", "0", map[string]any{"title": "Zeroth"})
+ AddEntity("order_test", "10", map[string]any{"title": "Tenth"})
+ AddEntity("order_test", "1", map[string]any{"title": "First"})
+
+ ordered := GetEntityInstancesOrdered("order_test")
+ require.Len(t, ordered, 4, "should return 4 entities")
+ assert.Equal(t, "0", ordered[0].UniqueKey, "first key should be 0")
+ assert.Equal(t, "1", ordered[1].UniqueKey, "second key should be 1")
+ assert.Equal(t, "2", ordered[2].UniqueKey, "third key should be 2")
+ assert.Equal(t, "10", ordered[3].UniqueKey, "fourth key should be 10 (numeric order)")
+}
+
+func TestGetEntityInstancesOrdered_lexicographicKeys(t *testing.T) {
+ ClearEntitiesOfType("order_test_lex")
+ defer ClearEntitiesOfType("order_test_lex")
+
+ AddEntity("order_test_lex", "zebra", map[string]any{"title": "Z"})
+ AddEntity("order_test_lex", "alpha", map[string]any{"title": "A"})
+ AddEntity("order_test_lex", "beta", map[string]any{"title": "B"})
+
+ ordered := GetEntityInstancesOrdered("order_test_lex")
+ require.Len(t, ordered, 3, "should return 3 entities")
+ assert.Equal(t, "alpha", ordered[0].UniqueKey)
+ assert.Equal(t, "beta", ordered[1].UniqueKey)
+ assert.Equal(t, "zebra", ordered[2].UniqueKey)
+}
+
+func TestGetEntityInstancesOrdered_emptyOrMissing(t *testing.T) {
+ ordered := GetEntityInstancesOrdered("nonexistent_type")
+ assert.Nil(t, ordered)
+
+ ClearEntitiesOfType("empty_test")
+ ordered = GetEntityInstancesOrdered("empty_test")
+ assert.Nil(t, ordered)
+}
diff --git a/service/internal/entities/storage.go b/service/internal/entities/storage.go
index e1fac78..bd61718 100644
--- a/service/internal/entities/storage.go
+++ b/service/internal/entities/storage.go
@@ -10,6 +10,8 @@ package entities
*/
import (
+ "sort"
+ "strconv"
"strings"
"sync"
)
@@ -64,6 +66,49 @@ func GetEntityInstances(entityName string) entityInstancesByKey {
return make(entityInstancesByKey, 0)
}
+func GetEntityInstancesOrdered(entityName string) []*Entity {
+ instances := GetEntityInstances(entityName)
+ if len(instances) == 0 {
+ return nil
+ }
+
+ keys := make([]string, 0, len(instances))
+ for key := range instances {
+ keys = append(keys, key)
+ }
+ sort.Slice(keys, func(i, j int) bool {
+ return compareEntityKeys(keys[i], keys[j]) < 0
+ })
+
+ result := make([]*Entity, 0, len(keys))
+ for _, key := range keys {
+ result = append(result, instances[key])
+ }
+ return result
+}
+
+//gocyclo:ignore
+func compareEntityKeys(a, b string) int {
+ ai, errA := strconv.ParseInt(a, 10, 64)
+ bi, errB := strconv.ParseInt(b, 10, 64)
+ if errA == nil && errB == nil {
+ if ai < bi {
+ return -1
+ }
+ if ai > bi {
+ return 1
+ }
+ return 0
+ }
+ if a < b {
+ return -1
+ }
+ if a > b {
+ return 1
+ }
+ return 0
+}
+
func AddEntity(entityName string, entityKey string, data any) {
rwmutex.Lock()
diff --git a/service/internal/executor/executor_actions.go b/service/internal/executor/executor_actions.go
index c55f114..706d16c 100644
--- a/service/internal/executor/executor_actions.go
+++ b/service/internal/executor/executor_actions.go
@@ -146,7 +146,7 @@ func registerAction(e *Executor, configOrder int, action *config.Action, req *Re
}
func registerActionsFromEntities(e *Executor, configOrder int, entityTitle string, tpl *config.Action, req *RebuildActionMapRequest) {
- for _, ent := range entities.GetEntityInstances(entityTitle) {
+ for _, ent := range entities.GetEntityInstancesOrdered(entityTitle) {
registerActionFromEntity(e, configOrder, tpl, ent, req)
}
}
diff --git a/specs/dashboard-component-ordering.md b/specs/dashboard-component-ordering.md
new file mode 100644
index 0000000..6723fb5
--- /dev/null
+++ b/specs/dashboard-component-ordering.md
@@ -0,0 +1,52 @@
+# Spec: Dashboard component ordering
+
+This spec describes how dashboard components (fieldsets, entity fieldsets, actions, and other elements) are ordered in OliveTin. It documents the current behaviour so that it can be reasoned about and kept consistent.
+
+---
+
+## 1. Implementation
+
+### 1.1 Two ways dashboards are built
+
+Dashboards are built in two ways:
+
+- **Default dashboard:** Used when there is no dashboard configuration. A single fieldset titled "Actions" is created and filled with actions that are not already on a configured dashboard.
+- **Config dashboard:** Built from the dashboard configuration (e.g. under dashboards or dashboards.d). The structure is derived by walking the config tree, which produces a mix of fieldsets and a special root fieldset titled "Actions" that holds any loose items.
+
+Ordering rules differ slightly between these two cases.
+
+### 1.2 Top-level dashboard contents (config dashboards)
+
+**Fieldsets without entities:** Fieldsets that are not tied to an entity type appear at the top level in **config order**. Their position in the dashboard matches the order in which they are defined in the config.
+
+**Other top-level components:** All other top-level components (including the root "Actions" fieldset and entity fieldset groups) are **sorted** before being shown. Sort order:
+
+1. If a component has no linked action, it is ordered by its title (alphabetically).
+2. Otherwise, components are ordered first by the action's order value (lower values first).
+3. If order values are equal, components are ordered by entity key: if both keys are whole numbers they are compared numerically; otherwise they are compared alphabetically.
+4. If still equal, components are ordered by the action's title (alphabetically).
+
+The root "Actions" fieldset is the single fieldset created by the build to hold loose items; it is identified by reference (not by position). When present it is added last to the list, then the sort is applied among that fieldset and entity-related components. So that fieldset can appear anywhere among those according to the rules above. When there are no loose items the root is not present, and the last component in the list is not treated as the root—so a fieldset without entities in the last position keeps config order. Regular fieldsets stay in config order and are not reordered.
+
+### 1.3 Entity fieldsets (order of fieldsets per entity type)
+
+When a fieldset in the config is tied to an entity type (e.g. "Server" or "Project"), one fieldset is built per entity instance. Those fieldsets are shown in **entity key order**.
+
+**Entity key order:**
+
+- If both keys are whole numbers: **numeric** order (e.g. 2 before 10).
+- Otherwise: **alphabetical** (lexicographic) order.
+
+So the order of entity fieldsets (e.g. one per server, one per project) is determined by this entity key order, not by config or insertion order.
+
+### 1.4 Contents inside fieldsets
+
+**Default dashboard:** The single "Actions" fieldset's contents are sorted. The same rules as for top-level components apply: order value first, then entity key (numeric then alphabetical), then action title.
+
+**Config dashboards:** For all fieldsets (the root "Actions" fieldset, entity fieldsets, and regular fieldsets), the contents are **not** sorted. They keep the order from the config:
+
+- **Root "Actions" fieldset:** Items appear in the order they are listed in the config (loose items that are not inside a fieldset).
+- **Entity fieldset contents:** The order comes from the template's contents in the config.
+- **Regular (non-entity) fieldset contents:** The order comes from the config, including for nested structure.
+
+So within any config-defined fieldset, the order of actions and other child components is the **config order**.
From 71bb999950ad01014d1482e166c273c83b54b8af Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 8 Mar 2026 22:45:46 +0000
Subject: [PATCH 054/148] security: Actions that people didnt have permission
to view were being returned (#921)
---
service/internal/api/api_test.go | 67 ++++++++++++++++++++++
service/internal/api/dashboard_entities.go | 8 ++-
service/internal/api/dashboards.go | 19 +++++-
3 files changed, 89 insertions(+), 5 deletions(-)
diff --git a/service/internal/api/api_test.go b/service/internal/api/api_test.go
index 7bca057..57abddd 100644
--- a/service/internal/api/api_test.go
+++ b/service/internal/api/api_test.go
@@ -432,6 +432,73 @@ func TestViewPermissionAllowedSeesAction(t *testing.T) {
assert.Equal(t, "secret_action", resp.Action.BindingId)
}
+// TestViewPermissionExcludedFromCustomDashboard (issue #921) asserts that when a custom dashboard
+// lists an action by title, users without view permission do not see that action (title or icon).
+func TestViewPermissionExcludedFromCustomDashboard(t *testing.T) {
+ cfg, lowUser, _ := buildViewPermissionTestConfig(t)
+ cfg.Dashboards = []*config.DashboardComponent{
+ {
+ Title: "Custom",
+ Contents: []*config.DashboardComponent{
+ {Title: "Secret Action"},
+ },
+ },
+ }
+ ex := executor.DefaultExecutor(cfg)
+ ex.RebuildActionMap()
+
+ rr := &DashboardRenderRequest{
+ AuthenticatedUser: lowUser,
+ cfg: cfg,
+ ex: ex,
+ }
+ dashboard := findDashboardByTitle(rr, "Custom")
+ require.NotNil(t, dashboard)
+ db := buildDashboardFromConfig(dashboard, rr)
+ require.NotNil(t, db)
+
+ bindingIdsInDashboard := bindingIdsInDashboardContents(db.Contents)
+ assert.NotContains(t, bindingIdsInDashboard, "secret_action",
+ "user with view:false must not see action on custom dashboard; got bindingIds: %v", bindingIdsInDashboard)
+}
+
+// TestViewPermissionExcludedFromEntityDashboard (GHSA: view permission) asserts that when a dashboard
+// has an entity fieldset listing an action, users without view permission do not see that action.
+func TestViewPermissionExcludedFromEntityDashboard(t *testing.T) {
+ entities.ClearEntitiesOfType("vp_entity_test")
+ defer entities.ClearEntitiesOfType("vp_entity_test")
+ entities.AddEntity("vp_entity_test", "1", map[string]any{"title": "Test Entity"})
+
+ cfg, lowUser, _ := buildViewPermissionTestConfig(t)
+ cfg.Dashboards = []*config.DashboardComponent{
+ {
+ Title: "WithEntity",
+ Contents: []*config.DashboardComponent{
+ {
+ Title: "Servers", Type: "fieldset", Entity: "vp_entity_test",
+ Contents: []*config.DashboardComponent{{Title: "Secret Action"}},
+ },
+ },
+ },
+ }
+ ex := executor.DefaultExecutor(cfg)
+ ex.RebuildActionMap()
+
+ rr := &DashboardRenderRequest{
+ AuthenticatedUser: lowUser,
+ cfg: cfg,
+ ex: ex,
+ }
+ dashboard := findDashboardByTitle(rr, "WithEntity")
+ require.NotNil(t, dashboard)
+ db := buildDashboardFromConfig(dashboard, rr)
+ require.NotNil(t, db)
+
+ bindingIdsInDashboard := bindingIdsInDashboardContents(db.Contents)
+ assert.NotContains(t, bindingIdsInDashboard, "secret_action",
+ "user with view:false must not see action in entity fieldset; got bindingIds: %v", bindingIdsInDashboard)
+}
+
func bindingIdsInDashboardContents(contents []*apiv1.DashboardComponent) []string {
var ids []string
for _, c := range contents {
diff --git a/service/internal/api/dashboard_entities.go b/service/internal/api/dashboard_entities.go
index d9a12d6..d588a57 100644
--- a/service/internal/api/dashboard_entities.go
+++ b/service/internal/api/dashboard_entities.go
@@ -82,8 +82,6 @@ func isLinkType(itemType string) bool {
}
func cloneLinkItem(subitem *config.DashboardComponent, ent *entities.Entity, clone *apiv1.DashboardComponent, rr *DashboardRenderRequest) *apiv1.DashboardComponent {
- clone.Type = "link"
- clone.Title = tpl.ParseTemplateOfActionBeforeExec(subitem.Title, ent)
// Prefer an entity-specific action when available, but fall back to a
// non-entity-scoped action with the same title. This allows inline actions
// defined inside entity dashboards to work without requiring an explicit
@@ -92,7 +90,11 @@ func cloneLinkItem(subitem *config.DashboardComponent, ent *entities.Entity, clo
if action == nil {
action = rr.findAction(subitem.Title)
}
-
+ if action == nil {
+ return nil
+ }
+ clone.Type = "link"
+ clone.Title = tpl.ParseTemplateOfActionBeforeExec(subitem.Title, ent)
clone.Action = action
return clone
}
diff --git a/service/internal/api/dashboards.go b/service/internal/api/dashboards.go
index 4e98fde..44e2244 100644
--- a/service/internal/api/dashboards.go
+++ b/service/internal/api/dashboards.go
@@ -277,16 +277,31 @@ func createRootFieldset() *apiv1.DashboardComponent {
}
}
+func appendComponentIfNotNil(components *[]*apiv1.DashboardComponent, comp *apiv1.DashboardComponent) {
+ if comp != nil {
+ *components = append(*components, comp)
+ }
+}
+
+func getDashboardComponentOrNil(subitem *config.DashboardComponent, rr *DashboardRenderRequest, entity *entities.Entity) *apiv1.DashboardComponent {
+ if len(subitem.Contents) == 0 && rr.findActionForEntity(subitem.Title, entity) == nil {
+ if !isAllowedType(subitem.Type) {
+ return nil
+ }
+ }
+ return buildDashboardComponentSimpleWithEntity(subitem, rr, entity)
+}
+
func processDashboardSubitemWithEntity(subitem *config.DashboardComponent, rr *DashboardRenderRequest, ret *[]*apiv1.DashboardComponent, rootFieldset *apiv1.DashboardComponent, entity *entities.Entity) {
if subitem.Type != "fieldset" {
- rootFieldset.Contents = append(rootFieldset.Contents, buildDashboardComponentSimpleWithEntity(subitem, rr, entity))
+ appendComponentIfNotNil(&rootFieldset.Contents, getDashboardComponentOrNil(subitem, rr, entity))
return
}
if subitem.Entity != "" {
*ret = append(*ret, buildEntityFieldsets(subitem.Entity, subitem, rr)...)
} else {
- *ret = append(*ret, buildDashboardComponentSimpleWithEntity(subitem, rr, entity))
+ appendComponentIfNotNil(ret, getDashboardComponentOrNil(subitem, rr, entity))
}
}
From 24e8b48dc9406afec953c8c0c303795dea69c850 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 8 Mar 2026 23:09:23 +0000
Subject: [PATCH 055/148] fix: Websocket reconnection logic (#802, 889, #884)
---
frontend/js/websocket.js | 14 +++++++++++++-
frontend/resources/vue/App.vue | 17 ++++++++++++++---
.../resources/vue/stores/connectionState.js | 6 ++++++
lang/combined_output.json | 12 +++++++++++-
lang/de-DE.yaml | 4 +++-
lang/en.yaml | 4 +++-
lang/es-ES.yaml | 4 +++-
lang/it-IT.yaml | 4 +++-
lang/zh-Hans-CN.yaml | 4 +++-
9 files changed, 59 insertions(+), 10 deletions(-)
create mode 100644 frontend/resources/vue/stores/connectionState.js
diff --git a/frontend/js/websocket.js b/frontend/js/websocket.js
index 8c0200f..dbabe67 100644
--- a/frontend/js/websocket.js
+++ b/frontend/js/websocket.js
@@ -1,5 +1,8 @@
import { buttonResults } from '../resources/vue/stores/buttonResults.js'
import { rateLimits } from '../resources/vue/stores/rateLimits.js'
+import { connectionState } from '../resources/vue/stores/connectionState.js'
+
+const RECONNECT_DELAY_MS = 3000
export function initWebsocket () {
window.addEventListener('EventOutputChunk', onOutputChunk)
@@ -16,9 +19,14 @@ async function reconnectWebsocket () {
return
}
+ connectionState.reconnecting = true
+ connectionState.connected = false
+
try {
window.websocketAvailable = true
for await (const e of window.client.eventStream()) {
+ connectionState.connected = true
+ connectionState.reconnecting = false
handleEvent(e)
}
} catch (err) {
@@ -26,7 +34,11 @@ async function reconnectWebsocket () {
}
window.websocketAvailable = false
- console.log('Reconnecting websocket...')
+ connectionState.connected = false
+ console.log('Reconnecting websocket in ' + RECONNECT_DELAY_MS + 'ms...')
+ setTimeout(() => {
+ reconnectWebsocket()
+ }, RECONNECT_DELAY_MS)
}
function handleEvent (msg) {
diff --git a/frontend/resources/vue/App.vue b/frontend/resources/vue/App.vue
index 7dfae4a..82830d7 100644
--- a/frontend/resources/vue/App.vue
+++ b/frontend/resources/vue/App.vue
@@ -50,7 +50,7 @@
{{ currentThemeName }}
- {{ t('connected') }}
+ {{ connectionStatusLabel }}
?
@@ -107,6 +107,7 @@ import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
import logoUrl from '../../OliveTinLogo.png';
import { useI18n } from 'vue-i18n';
import combinedTranslations from '../../../lang/combined_output.json';
+import { connectionState } from './stores/connectionState.js';
const { t, locale } = useI18n();
@@ -116,7 +117,6 @@ const sidebar = ref(null);
const navigation = ref(null);
const username = ref('notset');
const isLoggedIn = ref(false);
-const serverConnection = ref(true);
const currentVersion = ref('?');
const pageTitle = ref('OliveTin');
const bannerMessage = ref('');
@@ -130,6 +130,18 @@ const showVersionNumber = ref(true)
const showLoginLink = ref(true)
const sectionNavigationStyle = ref('sidebar')
+const connectionStatusLabel = computed(() => {
+ if (connectionState.connected) {
+ return t('connected')
+ }
+ if (connectionState.reconnecting) {
+ return t('reconnecting')
+ }
+ return t('disconnected')
+})
+
+const connectionStatusTitle = computed(() => connectionStatusLabel.value)
+
const languageDialog = ref(null)
const browserLanguages = ref([])
@@ -404,7 +416,6 @@ function handleThemeDialogClick(event) {
window.updateHeaderFromInit = updateHeaderFromInit
onMounted(() => {
- serverConnection.value = true;
updateHeaderFromInit()
// Initialize selected language from stored preference
diff --git a/frontend/resources/vue/stores/connectionState.js b/frontend/resources/vue/stores/connectionState.js
new file mode 100644
index 0000000..c8a4606
--- /dev/null
+++ b/frontend/resources/vue/stores/connectionState.js
@@ -0,0 +1,6 @@
+import { reactive } from 'vue'
+
+export const connectionState = reactive({
+ connected: false,
+ reconnecting: false
+})
diff --git a/lang/combined_output.json b/lang/combined_output.json
index 3fda01a..9e4514b 100644
--- a/lang/combined_output.json
+++ b/lang/combined_output.json
@@ -20,6 +20,7 @@
"diagnostics.unknown": "Unbekannt",
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
+ "disconnected": "Getrennt",
"docs": "Dokumentation",
"language-dialog.browser-languages": "Browser-Sprachen",
"language-dialog.close": "Schließen",
@@ -47,6 +48,7 @@
"nav.entities": "Entitäten",
"nav.logs": "Protokolle",
"raise-issue": "Ein Problem melden auf GitHub",
+ "reconnecting": "Verbinde erneut…",
"return-to-index": "Zurück zur Startseite",
"search-filter": "Filter aktuelle Seite",
"theme-dialog.close": "Schließen",
@@ -73,6 +75,7 @@
"diagnostics.unknown": "Unknown",
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
"diagnostics.where-to-find-help": "Where to find help",
+ "disconnected": "Disconnected",
"docs": "Documentation",
"language-dialog.browser-languages": "Browser languages",
"language-dialog.close": "Close",
@@ -100,6 +103,7 @@
"nav.entities": "Entities",
"nav.logs": "Logs",
"raise-issue": "Raise an issue on GitHub",
+ "reconnecting": "Reconnecting…",
"return-to-index": "Return to index",
"search-filter": "Filter current page",
"theme-dialog.close": "Close",
@@ -126,6 +130,7 @@
"diagnostics.unknown": "Desconocido",
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
+ "disconnected": "Desconectado",
"docs": "Documentación",
"language-dialog.browser-languages": "Idiomas del navegador",
"language-dialog.close": "Cerrar",
@@ -153,6 +158,7 @@
"nav.entities": "Entidades",
"nav.logs": "Registros",
"raise-issue": "Reportar un problema en GitHub",
+ "reconnecting": "Reconectando…",
"return-to-index": "Volver a la página principal",
"search-filter": "Filtrar página actual",
"theme-dialog.close": "Cerrar",
@@ -179,6 +185,7 @@
"diagnostics.unknown": "Sconosciuto",
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
"diagnostics.where-to-find-help": "Dove trovare aiuto",
+ "disconnected": "Disconnesso",
"docs": "Documentazione",
"language-dialog.browser-languages": "Lingue del browser",
"language-dialog.close": "Chiudi",
@@ -206,6 +213,7 @@
"nav.entities": "Entità",
"nav.logs": "Registri",
"raise-issue": "Segnala un problema su GitHub",
+ "reconnecting": "Riconnessione…",
"return-to-index": "Torna alla pagina principale",
"search-filter": "Filtra la pagina corrente",
"theme-dialog.close": "Chiudi",
@@ -232,6 +240,7 @@
"diagnostics.unknown": "未知",
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
"diagnostics.where-to-find-help": "在哪里找到帮助",
+ "disconnected": "已断开连接",
"docs": "文档",
"language-dialog.browser-languages": "浏览器语言",
"language-dialog.close": "关闭",
@@ -259,6 +268,7 @@
"nav.entities": "实体",
"nav.logs": "日志",
"raise-issue": "在 GitHub 上报告问题",
+ "reconnecting": "正在重新连接…",
"return-to-index": "返回首页",
"search-filter": "过滤当前页面",
"theme-dialog.close": "关闭",
@@ -267,4 +277,4 @@
"welcome": "欢迎使用 OliveTin"
}
}
-}
\ No newline at end of file
+}
diff --git a/lang/de-DE.yaml b/lang/de-DE.yaml
index 2e5f6c7..e56b45a 100644
--- a/lang/de-DE.yaml
+++ b/lang/de-DE.yaml
@@ -6,6 +6,8 @@ translations:
nav.entities: Entitäten
nav.diagnostics: Diagnostik
connected: Verbunden
+ disconnected: Getrennt
+ reconnecting: Verbinde erneut…
login-button: Login
raise-issue: Ein Problem melden auf GitHub
docs: Dokumentation
@@ -50,4 +52,4 @@ translations:
language-dialog.close: Schließen
theme-dialog.title: Design auswählen
theme-dialog.default: Standard-Design
- theme-dialog.close: Schließen
\ No newline at end of file
+ theme-dialog.close: Schließen
diff --git a/lang/en.yaml b/lang/en.yaml
index 8cf5c25..4205514 100644
--- a/lang/en.yaml
+++ b/lang/en.yaml
@@ -8,6 +8,8 @@ translations:
nav.entities: Entities
nav.diagnostics: Diagnostics
connected: Connected
+ disconnected: Disconnected
+ reconnecting: Reconnecting…
login-button: Login
logs.title: Logs
logs.page-description: This is a list of logs from actions that have been executed. You can filter the list by action title.
@@ -50,4 +52,4 @@ translations:
language-dialog.close: Close
theme-dialog.title: Select Theme
theme-dialog.default: Default Theme
- theme-dialog.close: Close
\ No newline at end of file
+ theme-dialog.close: Close
diff --git a/lang/es-ES.yaml b/lang/es-ES.yaml
index 974eaf6..21a4bff 100644
--- a/lang/es-ES.yaml
+++ b/lang/es-ES.yaml
@@ -6,6 +6,8 @@ translations:
nav.entities: Entidades
nav.diagnostics: Diagnósticos
connected: Conectado
+ disconnected: Desconectado
+ reconnecting: Reconectando…
login-button: Iniciar sesión
raise-issue: Reportar un problema en GitHub
docs: Documentación
@@ -50,4 +52,4 @@ translations:
language-dialog.close: Cerrar
theme-dialog.title: Seleccionar tema
theme-dialog.default: Tema Predeterminado
- theme-dialog.close: Cerrar
\ No newline at end of file
+ theme-dialog.close: Cerrar
diff --git a/lang/it-IT.yaml b/lang/it-IT.yaml
index 4bc9c66..b56f498 100644
--- a/lang/it-IT.yaml
+++ b/lang/it-IT.yaml
@@ -7,6 +7,8 @@ translations:
nav.diagnostics: Diagnostica
docs: Documentazione
connected: Connesso
+ disconnected: Disconnesso
+ reconnecting: Riconnessione…
login-button: Login
raise-issue: Segnala un problema su GitHub
logs.title: Registri
@@ -50,4 +52,4 @@ translations:
language-dialog.close: Chiudi
theme-dialog.title: Seleziona tema
theme-dialog.default: Tema Predefinito
- theme-dialog.close: Chiudi
\ No newline at end of file
+ theme-dialog.close: Chiudi
diff --git a/lang/zh-Hans-CN.yaml b/lang/zh-Hans-CN.yaml
index ad84897..73f7810 100644
--- a/lang/zh-Hans-CN.yaml
+++ b/lang/zh-Hans-CN.yaml
@@ -6,6 +6,8 @@ translations:
nav.entities: 实体
nav.diagnostics: 诊断
connected: 已连接
+ disconnected: 已断开连接
+ reconnecting: 正在重新连接…
login-button: 登录
raise-issue: 在 GitHub 上报告问题
docs: 文档
@@ -50,4 +52,4 @@ translations:
diagnostics.copy-to-clipboard: 复制到剪贴板
diagnostics.copied: 已复制!
diagnostics.unknown: 未知
- diagnostics.useragent-data-error: 检索 userAgentData 时出错
\ No newline at end of file
+ diagnostics.useragent-data-error: 检索 userAgentData 时出错
From 2f77000de44f65690f257e3cf8e2c8462b0e74c7 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 8 Mar 2026 23:29:00 +0000
Subject: [PATCH 056/148] security: GHSA-364q-w7vh-vhpc (HIGH) Unsafe parsing
of UniqueTrackingId can be used to write files
---
service/internal/executor/executor.go | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/service/internal/executor/executor.go b/service/internal/executor/executor.go
index f6807cf..670aeea 100644
--- a/service/internal/executor/executor.go
+++ b/service/internal/executor/executor.go
@@ -20,6 +20,7 @@ import (
"os"
"os/exec"
"path"
+ "regexp"
"strings"
"sync"
"time"
@@ -30,6 +31,14 @@ const (
MaxTriggerDepth = 10
)
+var validTrackingIDPattern = regexp.MustCompile(`^[a-fA-F0-9\-]+$`)
+
+func isValidTrackingID(id string) bool {
+ const MaxTrackingIDLength = 36
+
+ return id != "" && len(id) <= MaxTrackingIDLength && validTrackingIDPattern.MatchString(id)
+}
+
var (
metricActionsRequested = promauto.NewCounter(prometheus.CounterOpts{
Name: "olivetin_actions_requested_count",
@@ -506,8 +515,7 @@ func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string)
}
_, isDuplicate := e.GetLog(req.TrackingID)
-
- if isDuplicate || req.TrackingID == "" {
+ if isDuplicate || !isValidTrackingID(req.TrackingID) {
req.TrackingID = uuid.NewString()
}
From a5c102dbf16f72b4d97abec9790393862d6a51f2 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Mon, 9 Mar 2026 00:27:53 +0000
Subject: [PATCH 057/148] fix: Much more helpful reconnection banner
---
frontend/js/websocket.js | 15 ++--
frontend/resources/vue/App.vue | 18 +----
.../vue/components/ConnectionBanner.vue | 68 +++++++++++++++++++
.../resources/vue/stores/connectionState.js | 4 +-
lang/combined_output.json | 10 +++
lang/de-DE.yaml | 2 +
lang/en.yaml | 2 +
lang/es-ES.yaml | 2 +
lang/it-IT.yaml | 2 +
lang/zh-Hans-CN.yaml | 2 +
10 files changed, 104 insertions(+), 21 deletions(-)
create mode 100644 frontend/resources/vue/components/ConnectionBanner.vue
diff --git a/frontend/js/websocket.js b/frontend/js/websocket.js
index dbabe67..d453a70 100644
--- a/frontend/js/websocket.js
+++ b/frontend/js/websocket.js
@@ -2,7 +2,7 @@ import { buttonResults } from '../resources/vue/stores/buttonResults.js'
import { rateLimits } from '../resources/vue/stores/rateLimits.js'
import { connectionState } from '../resources/vue/stores/connectionState.js'
-const RECONNECT_DELAY_MS = 3000
+const RECONNECT_DELAY_MS = 10000
export function initWebsocket () {
window.addEventListener('EventOutputChunk', onOutputChunk)
@@ -21,12 +21,17 @@ async function reconnectWebsocket () {
connectionState.reconnecting = true
connectionState.connected = false
+ connectionState.disconnectedAt = Date.now()
+ connectionState.nextReconnectAt = null
try {
window.websocketAvailable = true
- for await (const e of window.client.eventStream()) {
- connectionState.connected = true
- connectionState.reconnecting = false
+ const stream = window.client.eventStream()
+ connectionState.connected = true
+ connectionState.reconnecting = false
+ connectionState.disconnectedAt = null
+ connectionState.nextReconnectAt = null
+ for await (const e of stream) {
handleEvent(e)
}
} catch (err) {
@@ -35,6 +40,8 @@ async function reconnectWebsocket () {
window.websocketAvailable = false
connectionState.connected = false
+ connectionState.disconnectedAt = connectionState.disconnectedAt ?? Date.now()
+ connectionState.nextReconnectAt = Date.now() + RECONNECT_DELAY_MS
console.log('Reconnecting websocket in ' + RECONNECT_DELAY_MS + 'ms...')
setTimeout(() => {
reconnectWebsocket()
diff --git a/frontend/resources/vue/App.vue b/frontend/resources/vue/App.vue
index 82830d7..dccdaa0 100644
--- a/frontend/resources/vue/App.vue
+++ b/frontend/resources/vue/App.vue
@@ -7,6 +7,7 @@
+
{{ t('login-button') }}
@@ -49,8 +50,6 @@
{{ currentThemeName }}
-
- {{ connectionStatusLabel }}
?
@@ -100,6 +99,7 @@ import { useRouter } from 'vue-router';
import Sidebar from 'picocrank/vue/components/Sidebar.vue';
import Navigation from 'picocrank/vue/components/Navigation.vue';
import Header from 'picocrank/vue/components/Header.vue';
+import ConnectionBanner from './components/ConnectionBanner.vue';
import { HugeiconsIcon } from '@hugeicons/vue'
import { Menu01Icon } from '@hugeicons/core-free-icons'
import { UserCircle02Icon } from '@hugeicons/core-free-icons'
@@ -107,8 +107,6 @@ import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
import logoUrl from '../../OliveTinLogo.png';
import { useI18n } from 'vue-i18n';
import combinedTranslations from '../../../lang/combined_output.json';
-import { connectionState } from './stores/connectionState.js';
-
const { t, locale } = useI18n();
const router = useRouter();
@@ -130,18 +128,6 @@ const showVersionNumber = ref(true)
const showLoginLink = ref(true)
const sectionNavigationStyle = ref('sidebar')
-const connectionStatusLabel = computed(() => {
- if (connectionState.connected) {
- return t('connected')
- }
- if (connectionState.reconnecting) {
- return t('reconnecting')
- }
- return t('disconnected')
-})
-
-const connectionStatusTitle = computed(() => connectionStatusLabel.value)
-
const languageDialog = ref(null)
const browserLanguages = ref([])
diff --git a/frontend/resources/vue/components/ConnectionBanner.vue b/frontend/resources/vue/components/ConnectionBanner.vue
new file mode 100644
index 0000000..f9768cc
--- /dev/null
+++ b/frontend/resources/vue/components/ConnectionBanner.vue
@@ -0,0 +1,68 @@
+
+ {{ bannerText }}
+
+
+
+
+
diff --git a/frontend/resources/vue/stores/connectionState.js b/frontend/resources/vue/stores/connectionState.js
index c8a4606..001f562 100644
--- a/frontend/resources/vue/stores/connectionState.js
+++ b/frontend/resources/vue/stores/connectionState.js
@@ -2,5 +2,7 @@ import { reactive } from 'vue'
export const connectionState = reactive({
connected: false,
- reconnecting: false
+ reconnecting: false,
+ disconnectedAt: null,
+ nextReconnectAt: null
})
diff --git a/lang/combined_output.json b/lang/combined_output.json
index 9e4514b..a4d4d9d 100644
--- a/lang/combined_output.json
+++ b/lang/combined_output.json
@@ -21,6 +21,8 @@
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
"disconnected": "Getrennt",
+ "disconnected-banner": "Events-Websocket getrennt seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}.",
+ "disconnected-banner-reconnecting": "Events-Websocket getrennt seit {disconnectedSince}. Verbindungsversuch…",
"docs": "Dokumentation",
"language-dialog.browser-languages": "Browser-Sprachen",
"language-dialog.close": "Schließen",
@@ -76,6 +78,8 @@
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
"diagnostics.where-to-find-help": "Where to find help",
"disconnected": "Disconnected",
+ "disconnected-banner": "Events websocket disconnected since {disconnectedSince}. Trying reconnect in {reconnectIn}.",
+ "disconnected-banner-reconnecting": "Events websocket disconnected since {disconnectedSince}. Trying reconnect…",
"docs": "Documentation",
"language-dialog.browser-languages": "Browser languages",
"language-dialog.close": "Close",
@@ -131,6 +135,8 @@
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
"disconnected": "Desconectado",
+ "disconnected-banner": "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión en {reconnectIn}.",
+ "disconnected-banner-reconnecting": "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión…",
"docs": "Documentación",
"language-dialog.browser-languages": "Idiomas del navegador",
"language-dialog.close": "Cerrar",
@@ -186,6 +192,8 @@
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
"diagnostics.where-to-find-help": "Dove trovare aiuto",
"disconnected": "Disconnesso",
+ "disconnected-banner": "Websocket eventi disconnesso dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}.",
+ "disconnected-banner-reconnecting": "Websocket eventi disconnesso dalle {disconnectedSince}. Tentativo di connessione…",
"docs": "Documentazione",
"language-dialog.browser-languages": "Lingue del browser",
"language-dialog.close": "Chiudi",
@@ -241,6 +249,8 @@
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
"diagnostics.where-to-find-help": "在哪里找到帮助",
"disconnected": "已断开连接",
+ "disconnected-banner": "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。",
+ "disconnected-banner-reconnecting": "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…",
"docs": "文档",
"language-dialog.browser-languages": "浏览器语言",
"language-dialog.close": "关闭",
diff --git a/lang/de-DE.yaml b/lang/de-DE.yaml
index e56b45a..a37508e 100644
--- a/lang/de-DE.yaml
+++ b/lang/de-DE.yaml
@@ -8,6 +8,8 @@ translations:
connected: Verbunden
disconnected: Getrennt
reconnecting: Verbinde erneut…
+ disconnected-banner: "Events-Websocket getrennt seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}."
+ disconnected-banner-reconnecting: "Events-Websocket getrennt seit {disconnectedSince}. Verbindungsversuch…"
login-button: Login
raise-issue: Ein Problem melden auf GitHub
docs: Dokumentation
diff --git a/lang/en.yaml b/lang/en.yaml
index 4205514..b4b1f1e 100644
--- a/lang/en.yaml
+++ b/lang/en.yaml
@@ -10,6 +10,8 @@ translations:
connected: Connected
disconnected: Disconnected
reconnecting: Reconnecting…
+ disconnected-banner: "Events websocket disconnected since {disconnectedSince}. Trying reconnect in {reconnectIn}."
+ disconnected-banner-reconnecting: "Events websocket disconnected since {disconnectedSince}. Trying reconnect…"
login-button: Login
logs.title: Logs
logs.page-description: This is a list of logs from actions that have been executed. You can filter the list by action title.
diff --git a/lang/es-ES.yaml b/lang/es-ES.yaml
index 21a4bff..d4a7ab2 100644
--- a/lang/es-ES.yaml
+++ b/lang/es-ES.yaml
@@ -8,6 +8,8 @@ translations:
connected: Conectado
disconnected: Desconectado
reconnecting: Reconectando…
+ disconnected-banner: "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión en {reconnectIn}."
+ disconnected-banner-reconnecting: "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión…"
login-button: Iniciar sesión
raise-issue: Reportar un problema en GitHub
docs: Documentación
diff --git a/lang/it-IT.yaml b/lang/it-IT.yaml
index b56f498..21af532 100644
--- a/lang/it-IT.yaml
+++ b/lang/it-IT.yaml
@@ -9,6 +9,8 @@ translations:
connected: Connesso
disconnected: Disconnesso
reconnecting: Riconnessione…
+ disconnected-banner: "Websocket eventi disconnesso dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}."
+ disconnected-banner-reconnecting: "Websocket eventi disconnesso dalle {disconnectedSince}. Tentativo di connessione…"
login-button: Login
raise-issue: Segnala un problema su GitHub
logs.title: Registri
diff --git a/lang/zh-Hans-CN.yaml b/lang/zh-Hans-CN.yaml
index 73f7810..e38da7e 100644
--- a/lang/zh-Hans-CN.yaml
+++ b/lang/zh-Hans-CN.yaml
@@ -8,6 +8,8 @@ translations:
connected: 已连接
disconnected: 已断开连接
reconnecting: 正在重新连接…
+ disconnected-banner: "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。"
+ disconnected-banner-reconnecting: "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…"
login-button: 登录
raise-issue: 在 GitHub 上报告问题
docs: 文档
From caf5a4b0259d937b66103bb38565be33c8d6bbb5 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Mon, 9 Mar 2026 08:47:26 +0000
Subject: [PATCH 058/148] fix: Disconnection banner preserves original
disconnect time
---
frontend/js/websocket.js | 6 ++++--
.../vue/components/ConnectionBanner.vue | 18 +++++++++++++++++-
lang/combined_output.json | 5 +++++
lang/de-DE.yaml | 1 +
lang/en.yaml | 1 +
lang/es-ES.yaml | 1 +
lang/it-IT.yaml | 1 +
lang/zh-Hans-CN.yaml | 1 +
8 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/frontend/js/websocket.js b/frontend/js/websocket.js
index d453a70..0fffb88 100644
--- a/frontend/js/websocket.js
+++ b/frontend/js/websocket.js
@@ -21,7 +21,9 @@ async function reconnectWebsocket () {
connectionState.reconnecting = true
connectionState.connected = false
- connectionState.disconnectedAt = Date.now()
+ if (connectionState.disconnectedAt == null) {
+ connectionState.disconnectedAt = Date.now()
+ }
connectionState.nextReconnectAt = null
try {
@@ -29,9 +31,9 @@ async function reconnectWebsocket () {
const stream = window.client.eventStream()
connectionState.connected = true
connectionState.reconnecting = false
- connectionState.disconnectedAt = null
connectionState.nextReconnectAt = null
for await (const e of stream) {
+ connectionState.disconnectedAt = null
handleEvent(e)
}
} catch (err) {
diff --git a/frontend/resources/vue/components/ConnectionBanner.vue b/frontend/resources/vue/components/ConnectionBanner.vue
index f9768cc..48e8462 100644
--- a/frontend/resources/vue/components/ConnectionBanner.vue
+++ b/frontend/resources/vue/components/ConnectionBanner.vue
@@ -1,5 +1,8 @@
- {{ bannerText }}
+
+ {{ staticAnnouncement }}
+ {{ bannerText }}
+
@@ -70,6 +76,10 @@ const bannerText = computed(() => {
border: 0;
margin: 0;
}
+.connection-banner-link {
+ color: inherit;
+ text-decoration: underline;
+}
.connection-banner-sr-only {
position: absolute;
width: 1px;
diff --git a/lang/combined_output.json b/lang/combined_output.json
index 979be80..a162003 100644
--- a/lang/combined_output.json
+++ b/lang/combined_output.json
@@ -21,9 +21,10 @@
"diagnostics.useragent-data-error": "Fehler beim Abrufen von userAgentData",
"diagnostics.where-to-find-help": "Wo Sie Hilfe finden",
"disconnected": "Getrennt",
- "disconnected-banner": "Events-Websocket getrennt seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}.",
"disconnected-banner-announcement": "Events-Websocket getrennt.",
- "disconnected-banner-reconnecting": "Events-Websocket getrennt seit {disconnectedSince}. Verbindungsversuch…",
+ "disconnected-banner-link-text": "Events-Websocket getrennt",
+ "disconnected-banner-suffix": " seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}.",
+ "disconnected-banner-suffix-reconnecting": " seit {disconnectedSince}. Verbindungsversuch…",
"docs": "Dokumentation",
"language-dialog.browser-languages": "Browser-Sprachen",
"language-dialog.close": "Schließen",
@@ -79,9 +80,10 @@
"diagnostics.useragent-data-error": "Error retrieving userAgentData",
"diagnostics.where-to-find-help": "Where to find help",
"disconnected": "Disconnected",
- "disconnected-banner": "Events websocket disconnected since {disconnectedSince}. Trying reconnect in {reconnectIn}.",
"disconnected-banner-announcement": "Events websocket disconnected.",
- "disconnected-banner-reconnecting": "Events websocket disconnected since {disconnectedSince}. Trying reconnect…",
+ "disconnected-banner-link-text": "Events websocket disconnected",
+ "disconnected-banner-suffix": " since {disconnectedSince}. Trying reconnect in {reconnectIn}.",
+ "disconnected-banner-suffix-reconnecting": " since {disconnectedSince}. Trying reconnect…",
"docs": "Documentation",
"language-dialog.browser-languages": "Browser languages",
"language-dialog.close": "Close",
@@ -137,9 +139,10 @@
"diagnostics.useragent-data-error": "Error al recuperar userAgentData",
"diagnostics.where-to-find-help": "Dónde encontrar ayuda",
"disconnected": "Desconectado",
- "disconnected-banner": "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión en {reconnectIn}.",
"disconnected-banner-announcement": "Websocket de eventos desconectado.",
- "disconnected-banner-reconnecting": "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión…",
+ "disconnected-banner-link-text": "Websocket de eventos desconectado",
+ "disconnected-banner-suffix": " desde {disconnectedSince}. Reintentando conexión en {reconnectIn}.",
+ "disconnected-banner-suffix-reconnecting": " desde {disconnectedSince}. Reintentando conexión…",
"docs": "Documentación",
"language-dialog.browser-languages": "Idiomas del navegador",
"language-dialog.close": "Cerrar",
@@ -195,9 +198,10 @@
"diagnostics.useragent-data-error": "Errore nel recupero di userAgentData",
"diagnostics.where-to-find-help": "Dove trovare aiuto",
"disconnected": "Disconnesso",
- "disconnected-banner": "Websocket eventi disconnesso dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}.",
"disconnected-banner-announcement": "Websocket eventi disconnesso.",
- "disconnected-banner-reconnecting": "Websocket eventi disconnesso dalle {disconnectedSince}. Tentativo di connessione…",
+ "disconnected-banner-link-text": "Websocket eventi disconnesso",
+ "disconnected-banner-suffix": " dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}.",
+ "disconnected-banner-suffix-reconnecting": " dalle {disconnectedSince}. Tentativo di connessione…",
"docs": "Documentazione",
"language-dialog.browser-languages": "Lingue del browser",
"language-dialog.close": "Chiudi",
@@ -253,9 +257,10 @@
"diagnostics.useragent-data-error": "检索 userAgentData 时出错",
"diagnostics.where-to-find-help": "在哪里找到帮助",
"disconnected": "已断开连接",
- "disconnected-banner": "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。",
"disconnected-banner-announcement": "事件 WebSocket 已断开。",
- "disconnected-banner-reconnecting": "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…",
+ "disconnected-banner-link-text": "事件 WebSocket 已断开",
+ "disconnected-banner-suffix": "自 {disconnectedSince}。{reconnectIn} 后尝试重连。",
+ "disconnected-banner-suffix-reconnecting": "自 {disconnectedSince}。正在尝试重连…",
"docs": "文档",
"language-dialog.browser-languages": "浏览器语言",
"language-dialog.close": "关闭",
diff --git a/lang/de-DE.yaml b/lang/de-DE.yaml
index 606620b..f96ee0b 100644
--- a/lang/de-DE.yaml
+++ b/lang/de-DE.yaml
@@ -8,9 +8,10 @@ translations:
connected: Verbunden
disconnected: Getrennt
reconnecting: Verbinde erneut…
- disconnected-banner: "Events-Websocket getrennt seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}."
- disconnected-banner-reconnecting: "Events-Websocket getrennt seit {disconnectedSince}. Verbindungsversuch…"
disconnected-banner-announcement: Events-Websocket getrennt.
+ disconnected-banner-link-text: "Events-Websocket getrennt"
+ disconnected-banner-suffix: " seit {disconnectedSince}. Erneuter Verbindungsversuch in {reconnectIn}."
+ disconnected-banner-suffix-reconnecting: " seit {disconnectedSince}. Verbindungsversuch…"
login-button: Login
raise-issue: Ein Problem melden auf GitHub
docs: Dokumentation
diff --git a/lang/en.yaml b/lang/en.yaml
index 61d3e3d..77421f4 100644
--- a/lang/en.yaml
+++ b/lang/en.yaml
@@ -10,9 +10,10 @@ translations:
connected: Connected
disconnected: Disconnected
reconnecting: Reconnecting…
- disconnected-banner: "Events websocket disconnected since {disconnectedSince}. Trying reconnect in {reconnectIn}."
- disconnected-banner-reconnecting: "Events websocket disconnected since {disconnectedSince}. Trying reconnect…"
disconnected-banner-announcement: Events websocket disconnected.
+ disconnected-banner-link-text: "Events websocket disconnected"
+ disconnected-banner-suffix: " since {disconnectedSince}. Trying reconnect in {reconnectIn}."
+ disconnected-banner-suffix-reconnecting: " since {disconnectedSince}. Trying reconnect…"
login-button: Login
logs.title: Logs
logs.page-description: This is a list of logs from actions that have been executed. You can filter the list by action title.
diff --git a/lang/es-ES.yaml b/lang/es-ES.yaml
index 6cc8c71..6a03237 100644
--- a/lang/es-ES.yaml
+++ b/lang/es-ES.yaml
@@ -8,9 +8,10 @@ translations:
connected: Conectado
disconnected: Desconectado
reconnecting: Reconectando…
- disconnected-banner: "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión en {reconnectIn}."
- disconnected-banner-reconnecting: "Websocket de eventos desconectado desde {disconnectedSince}. Reintentando conexión…"
disconnected-banner-announcement: Websocket de eventos desconectado.
+ disconnected-banner-link-text: "Websocket de eventos desconectado"
+ disconnected-banner-suffix: " desde {disconnectedSince}. Reintentando conexión en {reconnectIn}."
+ disconnected-banner-suffix-reconnecting: " desde {disconnectedSince}. Reintentando conexión…"
login-button: Iniciar sesión
raise-issue: Reportar un problema en GitHub
docs: Documentación
diff --git a/lang/it-IT.yaml b/lang/it-IT.yaml
index 28c8549..351ed24 100644
--- a/lang/it-IT.yaml
+++ b/lang/it-IT.yaml
@@ -9,9 +9,10 @@ translations:
connected: Connesso
disconnected: Disconnesso
reconnecting: Riconnessione…
- disconnected-banner: "Websocket eventi disconnesso dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}."
- disconnected-banner-reconnecting: "Websocket eventi disconnesso dalle {disconnectedSince}. Tentativo di connessione…"
disconnected-banner-announcement: Websocket eventi disconnesso.
+ disconnected-banner-link-text: "Websocket eventi disconnesso"
+ disconnected-banner-suffix: " dalle {disconnectedSince}. Nuovo tentativo tra {reconnectIn}."
+ disconnected-banner-suffix-reconnecting: " dalle {disconnectedSince}. Tentativo di connessione…"
login-button: Login
raise-issue: Segnala un problema su GitHub
logs.title: Registri
diff --git a/lang/zh-Hans-CN.yaml b/lang/zh-Hans-CN.yaml
index 58f3cb7..2883a67 100644
--- a/lang/zh-Hans-CN.yaml
+++ b/lang/zh-Hans-CN.yaml
@@ -8,9 +8,10 @@ translations:
connected: 已连接
disconnected: 已断开连接
reconnecting: 正在重新连接…
- disconnected-banner: "事件 WebSocket 自 {disconnectedSince} 已断开。{reconnectIn} 后尝试重连。"
- disconnected-banner-reconnecting: "事件 WebSocket 自 {disconnectedSince} 已断开。正在尝试重连…"
disconnected-banner-announcement: 事件 WebSocket 已断开。
+ disconnected-banner-link-text: "事件 WebSocket 已断开"
+ disconnected-banner-suffix: "自 {disconnectedSince}。{reconnectIn} 后尝试重连。"
+ disconnected-banner-suffix-reconnecting: "自 {disconnectedSince}。正在尝试重连…"
login-button: 登录
raise-issue: 在 GitHub 上报告问题
docs: 文档
From ac0852aad42c9254809c4ead220df881d9960f6e Mon Sep 17 00:00:00 2001
From: jamesread
Date: Tue, 10 Mar 2026 22:36:28 +0000
Subject: [PATCH 064/148] chore: dep update March 26
---
frontend/package-lock.json | 202 ++++++++++++++++++++-----------------
frontend/package.json | 10 +-
2 files changed, 115 insertions(+), 97 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index e1f2bcb..6d6cc10 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,19 +11,19 @@
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
- "@hugeicons/core-free-icons": "^3.3.0",
- "@hugeicons/vue": "^1.0.4",
+ "@hugeicons/core-free-icons": "^4.0.0",
+ "@hugeicons/vue": "^1.0.5",
"@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2",
- "picocrank": "^1.14.0",
+ "picocrank": "^1.14.1",
"standard": "^17.1.2",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1",
- "vue": "^3.5.29",
- "vue-i18n": "^11.2.8",
+ "vue": "^3.5.30",
+ "vue-i18n": "^11.3.0",
"vue-router": "^5.0.3"
},
"devDependencies": {
@@ -906,15 +906,15 @@
}
},
"node_modules/@hugeicons/core-free-icons": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-3.3.0.tgz",
- "integrity": "sha512-qYyr4JQ2eQIHTSTbITvnJvs6ERNK64D9gpwZnf2IyuG0exzqfyABLO/oTB71FB3RZPfu1GbwycdiGSo46apjMQ==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.0.0.tgz",
+ "integrity": "sha512-bzfbKumv3ke3ajbe2MyXi9i0I/cdsZ6n/mO9EfIPNSL++pHLqs7nSGRIVUtjF4xrrEyVkfhxssv4Jek8DPA6gA==",
"license": "MIT"
},
"node_modules/@hugeicons/vue": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.4.tgz",
- "integrity": "sha512-OtFEXbyW5jYUig98C/n/HygktLvfF5Ga6nN6gK8R0E0jCrVw3EfgoZZVXqo+xGxyIjH5R1wdbg6nJrtf6mzLKQ==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@hugeicons/vue/-/vue-1.0.5.tgz",
+ "integrity": "sha512-kaouUZceXtdDfupfiqqfn40tIyRBF/fcEvCfY96hZIXZ3JMsqpwhCDqiqoj+B5bMEUXOuvz3npNyKI5+7iPfYA==",
"license": "MIT",
"peerDependencies": {
"vue": "^2.6.0 || ^3.0.0"
@@ -962,13 +962,30 @@
"license": "MIT"
},
"node_modules/@intlify/core-base": {
- "version": "11.2.8",
- "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.2.8.tgz",
- "integrity": "sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==",
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.3.0.tgz",
+ "integrity": "sha512-NNX5jIwF4TJBe7RtSKDMOA6JD9mp2mRcBHAwt2X+Q8PvnZub0yj5YYXlFu2AcESdgQpEv/5Yx2uOCV/yh7YkZg==",
"license": "MIT",
"dependencies": {
- "@intlify/message-compiler": "11.2.8",
- "@intlify/shared": "11.2.8"
+ "@intlify/devtools-types": "11.3.0",
+ "@intlify/message-compiler": "11.3.0",
+ "@intlify/shared": "11.3.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/devtools-types": {
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.3.0.tgz",
+ "integrity": "sha512-G9CNL4WpANWVdUjubOIIS7/D2j/0j+1KJmhBJxHilWNKr9mmt3IjFV3Hq4JoBP23uOoC5ynxz/FHZ42M+YxfGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "11.3.0",
+ "@intlify/shared": "11.3.0"
},
"engines": {
"node": ">= 16"
@@ -978,12 +995,12 @@
}
},
"node_modules/@intlify/message-compiler": {
- "version": "11.2.8",
- "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.2.8.tgz",
- "integrity": "sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==",
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.3.0.tgz",
+ "integrity": "sha512-RAJp3TMsqohg/Wa7bVF3cChRhecSYBLrTCQSj7j0UtWVFLP+6iEJoE2zb7GU5fp+fmG5kCbUdzhmlAUCWXiUJw==",
"license": "MIT",
"dependencies": {
- "@intlify/shared": "11.2.8",
+ "@intlify/shared": "11.3.0",
"source-map-js": "^1.0.2"
},
"engines": {
@@ -994,9 +1011,9 @@
}
},
"node_modules/@intlify/shared": {
- "version": "11.2.8",
- "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.2.8.tgz",
- "integrity": "sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==",
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.0.tgz",
+ "integrity": "sha512-LC6P/uay7rXL5zZ5+5iRJfLs/iUN8apu9tm8YqQVmW3Uq3X4A0dOFUIDuAmB7gAC29wTHOS3EiN/IosNSz0eNQ==",
"license": "MIT",
"engines": {
"node": ">= 16"
@@ -1436,53 +1453,53 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz",
- "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
+ "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
- "@vue/shared": "3.5.29",
+ "@vue/shared": "3.5.30",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz",
- "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
+ "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.29",
- "@vue/shared": "3.5.29"
+ "@vue/compiler-core": "3.5.30",
+ "@vue/shared": "3.5.30"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz",
- "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
+ "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
- "@vue/compiler-core": "3.5.29",
- "@vue/compiler-dom": "3.5.29",
- "@vue/compiler-ssr": "3.5.29",
- "@vue/shared": "3.5.29",
+ "@vue/compiler-core": "3.5.30",
+ "@vue/compiler-dom": "3.5.30",
+ "@vue/compiler-ssr": "3.5.30",
+ "@vue/shared": "3.5.30",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
- "postcss": "^8.5.6",
+ "postcss": "^8.5.8",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz",
- "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
+ "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.29",
- "@vue/shared": "3.5.29"
+ "@vue/compiler-dom": "3.5.30",
+ "@vue/shared": "3.5.30"
}
},
"node_modules/@vue/devtools-api": {
@@ -1516,53 +1533,53 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz",
- "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
+ "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.29"
+ "@vue/shared": "3.5.30"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz",
- "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
+ "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.29",
- "@vue/shared": "3.5.29"
+ "@vue/reactivity": "3.5.30",
+ "@vue/shared": "3.5.30"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz",
- "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
+ "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.29",
- "@vue/runtime-core": "3.5.29",
- "@vue/shared": "3.5.29",
+ "@vue/reactivity": "3.5.30",
+ "@vue/runtime-core": "3.5.30",
+ "@vue/shared": "3.5.30",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz",
- "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
+ "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.29",
- "@vue/shared": "3.5.29"
+ "@vue/compiler-ssr": "3.5.30",
+ "@vue/shared": "3.5.30"
},
"peerDependencies": {
- "vue": "3.5.29"
+ "vue": "3.5.30"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz",
- "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
+ "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
"license": "MIT"
},
"node_modules/@xterm/addon-fit": {
@@ -4875,19 +4892,19 @@
"license": "ISC"
},
"node_modules/picocrank": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.14.0.tgz",
- "integrity": "sha512-ksjqPHFMFE6ENaIXjhund50wocFmaLy22jYgWlWikugHBdd/0YlHfOOuoIMn0wKV8bSrJhcM3pQug/qz45Bc4g==",
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/picocrank/-/picocrank-1.14.1.tgz",
+ "integrity": "sha512-N/aGK/deicXevv+n3zWaKrUe66d4QYQ/7SPinEr4fprljNpXQHSl2EBDlmTNlLoujR3QJHClfRO8Gusj2Hs5MQ==",
"license": "ISC",
"dependencies": {
- "@hugeicons/core-free-icons": "^3.1.1",
- "@hugeicons/vue": "^1.0.4",
+ "@hugeicons/core-free-icons": "^4.0.0",
+ "@hugeicons/vue": "^1.0.5",
"@vitejs/plugin-vue": "^6.0.4",
"femtocrank": "^2.5.0",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1",
- "vue": "^3.5.28",
- "vue-router": "^5.0.2"
+ "vue": "^3.5.30",
+ "vue-router": "^5.0.3"
}
},
"node_modules/picomatch": {
@@ -5007,9 +5024,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"funding": [
{
"type": "opencollective",
@@ -6603,16 +6620,16 @@
}
},
"node_modules/vue": {
- "version": "3.5.29",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
- "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
+ "version": "3.5.30",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
+ "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.29",
- "@vue/compiler-sfc": "3.5.29",
- "@vue/runtime-dom": "3.5.29",
- "@vue/server-renderer": "3.5.29",
- "@vue/shared": "3.5.29"
+ "@vue/compiler-dom": "3.5.30",
+ "@vue/compiler-sfc": "3.5.30",
+ "@vue/runtime-dom": "3.5.30",
+ "@vue/server-renderer": "3.5.30",
+ "@vue/shared": "3.5.30"
},
"peerDependencies": {
"typescript": "*"
@@ -6624,13 +6641,14 @@
}
},
"node_modules/vue-i18n": {
- "version": "11.2.8",
- "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.2.8.tgz",
- "integrity": "sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==",
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.0.tgz",
+ "integrity": "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==",
"license": "MIT",
"dependencies": {
- "@intlify/core-base": "11.2.8",
- "@intlify/shared": "11.2.8",
+ "@intlify/core-base": "11.3.0",
+ "@intlify/devtools-types": "11.3.0",
+ "@intlify/shared": "11.3.0",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
diff --git a/frontend/package.json b/frontend/package.json
index a08f799..f18851c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -24,19 +24,19 @@
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
- "@hugeicons/core-free-icons": "^3.3.0",
- "@hugeicons/vue": "^1.0.4",
+ "@hugeicons/core-free-icons": "^4.0.0",
+ "@hugeicons/vue": "^1.0.5",
"@vitejs/plugin-vue": "^6.0.4",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2",
- "picocrank": "^1.14.0",
+ "picocrank": "^1.14.1",
"standard": "^17.1.2",
"unplugin-vue-components": "^31.0.0",
"vite": "^7.3.1",
- "vue": "^3.5.29",
- "vue-i18n": "^11.2.8",
+ "vue": "^3.5.30",
+ "vue-i18n": "^11.3.0",
"vue-router": "^5.0.3"
}
}
From bc5e9fbe1e22ff87a4b277cb56605a46a10e561a Mon Sep 17 00:00:00 2001
From: jamesread
Date: Tue, 10 Mar 2026 23:27:09 +0000
Subject: [PATCH 065/148] security: GHSA-xx6g-43w2-9g6g (MODERATE) Email
argument makes compliance harder, enables log injection
---
service/internal/executor/arguments.go | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/service/internal/executor/arguments.go b/service/internal/executor/arguments.go
index 655ecc4..a73a0d7 100644
--- a/service/internal/executor/arguments.go
+++ b/service/internal/executor/arguments.go
@@ -250,13 +250,10 @@ func typecheckChoiceEntity(value string, arg *config.ActionArgument) error {
func typeSafetyCheckEmail(value string) error {
_, err := mail.ParseAddress(value)
-
- log.Errorf("Email check: %v, %v", err, value)
-
if err != nil {
+ log.WithField("type", "email").Debugf("Email argument type check failed")
return err
}
-
return nil
}
From 86c35f40c324106d62d98be145aefe7220126473 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Tue, 10 Mar 2026 23:28:17 +0000
Subject: [PATCH 066/148] chore: remove unneeded comment
---
service/internal/config/constants.go | 2 --
1 file changed, 2 deletions(-)
diff --git a/service/internal/config/constants.go b/service/internal/config/constants.go
index fe8cc1f..bff42c3 100644
--- a/service/internal/config/constants.go
+++ b/service/internal/config/constants.go
@@ -1,5 +1,3 @@
package config
-// ContentSecurityPolicyDefault is the default Content-Security-Policy header value
-// when security headers are enabled and no custom policy is set.
const ContentSecurityPolicyDefault = "default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'"
From acd6cb839e999bb2b0e9a91424f51e88be3f7ffc Mon Sep 17 00:00:00 2001
From: jamesread
Date: Tue, 10 Mar 2026 23:47:35 +0000
Subject: [PATCH 067/148] security: GHSA-228v-wc5r-j8m7 (HIGH) Unauthorized
Action Output Disclosure via EventStream
---
service/internal/api/api.go | 148 +++++++++++++++++++++----------
service/internal/api/api_test.go | 139 +++++++++++++++++++++++++++--
2 files changed, 235 insertions(+), 52 deletions(-)
diff --git a/service/internal/api/api.go b/service/internal/api/api.go
index 83465aa..3497fb1 100644
--- a/service/internal/api/api.go
+++ b/service/internal/api/api.go
@@ -60,6 +60,20 @@ type streamingClient struct {
AuthenticatedUser *authpublic.AuthenticatedUser
}
+// trySendEventToClient sends msg to the client's channel. Returns false if the channel is full (client should be removed).
+func (api *oliveTinAPI) trySendEventToClient(client *streamingClient, msg *apiv1.EventStreamResponse) bool {
+ if client == nil || msg == nil {
+ return false
+ }
+ select {
+ case client.channel <- msg:
+ return true
+ default:
+ log.Warnf("EventStream: client channel is full, removing client")
+ return false
+ }
+}
+
func (api *oliveTinAPI) KillAction(ctx ctx.Context, req *connect.Request[apiv1.KillActionRequest]) (*connect.Response[apiv1.KillActionResponse], error) {
ret := &apiv1.KillActionResponse{
ExecutionTrackingId: req.Msg.ExecutionTrackingId,
@@ -589,9 +603,20 @@ func isValidLogEntry(e *executor.InternalLogEntry) bool {
// isLogEntryAllowed checks if a log entry is allowed to be viewed by the user.
func (api *oliveTinAPI) isLogEntryAllowed(e *executor.InternalLogEntry, user *authpublic.AuthenticatedUser) bool {
+ if user == nil || !isValidLogEntry(e) {
+ return false
+ }
return acl.IsAllowedLogs(api.cfg, user, e.Binding.Action)
}
+// mayViewExecutionEvent returns whether the user is allowed to receive this execution event (for EventStream ACL).
+func (api *oliveTinAPI) mayViewExecutionEvent(entry *executor.InternalLogEntry, user *authpublic.AuthenticatedUser) bool {
+ if user == nil {
+ return false
+ }
+ return isValidLogEntry(entry) && api.isLogEntryAllowed(entry, user)
+}
+
// buildEmptyPageResponse creates a response for an empty page.
func buildEmptyPageResponse(page pageInfo) *apiv1.GetActionLogsResponse {
return &apiv1.GetActionLogsResponse{
@@ -886,6 +911,9 @@ func (api *oliveTinAPI) EventStream(ctx ctx.Context, req *connect.Request[apiv1.
}
func (api *oliveTinAPI) removeClient(clientToRemove *streamingClient) {
+ if clientToRemove == nil {
+ return
+ }
api.streamingClientsMutex.Lock()
delete(api.streamingClients, clientToRemove)
api.streamingClientsMutex.Unlock()
@@ -915,50 +943,62 @@ func (api *oliveTinAPI) OnActionMapRebuilt() {
func (api *oliveTinAPI) OnExecutionStarted(ex *executor.InternalLogEntry) {
toRemove := []*streamingClient{}
-
for _, client := range api.copyOfStreamingClients() {
- select {
- case client.channel <- &apiv1.EventStreamResponse{
- Event: &apiv1.EventStreamResponse_ExecutionStarted{
- ExecutionStarted: &apiv1.EventExecutionStarted{
- LogEntry: api.internalLogEntryToPb(ex, client.AuthenticatedUser),
- },
- },
- }:
- default:
- log.Warnf("EventStream: client channel is full, removing client")
- toRemove = append(toRemove, client)
- }
+ api.maybeSendExecutionStarted(client, ex, &toRemove)
}
-
for _, client := range toRemove {
api.removeClient(client)
}
}
+func (api *oliveTinAPI) maybeSendExecutionStarted(client *streamingClient, ex *executor.InternalLogEntry, toRemove *[]*streamingClient) {
+ if client == nil {
+ return
+ }
+ if !api.mayViewExecutionEvent(ex, client.AuthenticatedUser) {
+ return
+ }
+ msg := &apiv1.EventStreamResponse{
+ Event: &apiv1.EventStreamResponse_ExecutionStarted{
+ ExecutionStarted: &apiv1.EventExecutionStarted{
+ LogEntry: api.internalLogEntryToPb(ex, client.AuthenticatedUser),
+ },
+ },
+ }
+ if !api.trySendEventToClient(client, msg) {
+ *toRemove = append(*toRemove, client)
+ }
+}
+
func (api *oliveTinAPI) OnExecutionFinished(ile *executor.InternalLogEntry) {
toRemove := []*streamingClient{}
-
for _, client := range api.copyOfStreamingClients() {
- select {
- case client.channel <- &apiv1.EventStreamResponse{
- Event: &apiv1.EventStreamResponse_ExecutionFinished{
- ExecutionFinished: &apiv1.EventExecutionFinished{
- LogEntry: api.internalLogEntryToPb(ile, client.AuthenticatedUser),
- },
- },
- }:
- default:
- log.Warnf("EventStream: client channel is full, removing client")
- toRemove = append(toRemove, client)
- }
+ api.maybeSendExecutionFinished(client, ile, &toRemove)
}
-
for _, client := range toRemove {
api.removeClient(client)
}
}
+func (api *oliveTinAPI) maybeSendExecutionFinished(client *streamingClient, ile *executor.InternalLogEntry, toRemove *[]*streamingClient) {
+ if client == nil {
+ return
+ }
+ if !api.mayViewExecutionEvent(ile, client.AuthenticatedUser) {
+ return
+ }
+ msg := &apiv1.EventStreamResponse{
+ Event: &apiv1.EventStreamResponse_ExecutionFinished{
+ ExecutionFinished: &apiv1.EventExecutionFinished{
+ LogEntry: api.internalLogEntryToPb(ile, client.AuthenticatedUser),
+ },
+ },
+ }
+ if !api.trySendEventToClient(client, msg) {
+ *toRemove = append(*toRemove, client)
+ }
+}
+
func (api *oliveTinAPI) GetDiagnostics(ctx ctx.Context, req *connect.Request[apiv1.GetDiagnosticsRequest]) (*connect.Response[apiv1.GetDiagnosticsResponse], error) {
user := auth.UserFromApiCall(ctx, req, api.cfg)
if err := api.checkDashboardAccess(user); err != nil {
@@ -1128,29 +1168,47 @@ func buildAdditionalLinks(links []*config.NavigationLink) []*apiv1.AdditionalLin
}
func (api *oliveTinAPI) OnOutputChunk(content []byte, executionTrackingId string) {
- toRemove := []*streamingClient{}
-
- for _, client := range api.copyOfStreamingClients() {
- select {
- case client.channel <- &apiv1.EventStreamResponse{
- Event: &apiv1.EventStreamResponse_OutputChunk{
- OutputChunk: &apiv1.EventOutputChunk{
- Output: string(content),
- ExecutionTrackingId: executionTrackingId,
- },
- },
- }:
- default:
- log.Warnf("EventStream: client channel is full, removing client")
- toRemove = append(toRemove, client)
- }
+ entry := api.getValidLogEntryForStreaming(executionTrackingId)
+ if entry == nil {
+ return
+ }
+ msg := &apiv1.EventStreamResponse{
+ Event: &apiv1.EventStreamResponse_OutputChunk{
+ OutputChunk: &apiv1.EventOutputChunk{
+ Output: string(content),
+ ExecutionTrackingId: executionTrackingId,
+ },
+ },
+ }
+ toRemove := []*streamingClient{}
+ for _, client := range api.copyOfStreamingClients() {
+ api.maybeSendOutputChunk(client, entry, msg, &toRemove)
}
-
for _, client := range toRemove {
api.removeClient(client)
}
}
+func (api *oliveTinAPI) getValidLogEntryForStreaming(executionTrackingId string) *executor.InternalLogEntry {
+ entry, ok := api.executor.GetLog(executionTrackingId)
+ if !ok || !isValidLogEntry(entry) {
+ return nil
+ }
+ return entry
+}
+
+func (api *oliveTinAPI) maybeSendOutputChunk(client *streamingClient, entry *executor.InternalLogEntry, msg *apiv1.EventStreamResponse, toRemove *[]*streamingClient) {
+ if client == nil {
+ return
+ }
+ if !api.mayViewExecutionEvent(entry, client.AuthenticatedUser) {
+ return
+ }
+ if !api.trySendEventToClient(client, msg) {
+ *toRemove = append(*toRemove, client)
+ }
+}
+
func (api *oliveTinAPI) GetEntities(ctx ctx.Context, req *connect.Request[apiv1.GetEntitiesRequest]) (*connect.Response[apiv1.GetEntitiesResponse], error) {
user := auth.UserFromApiCall(ctx, req, api.cfg)
diff --git a/service/internal/api/api_test.go b/service/internal/api/api_test.go
index c6ee915..ca8ecf5 100644
--- a/service/internal/api/api_test.go
+++ b/service/internal/api/api_test.go
@@ -2,24 +2,24 @@ package api
import (
"context"
+ "net/http"
+ "net/http/httptest"
+ "path"
"testing"
+ "time"
"connectrpc.com/connect"
+ "github.com/google/uuid"
+ log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- log "github.com/sirupsen/logrus"
-
apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
apiv1connect "github.com/OliveTin/OliveTin/gen/olivetin/api/v1/apiv1connect"
authpublic "github.com/OliveTin/OliveTin/internal/auth/authpublic"
config "github.com/OliveTin/OliveTin/internal/config"
"github.com/OliveTin/OliveTin/internal/entities"
"github.com/OliveTin/OliveTin/internal/executor"
-
- "net/http"
- "net/http/httptest"
- "path"
)
func getNewTestServerAndClient(injectedConfig *config.Config) (*httptest.Server, apiv1connect.OliveTinApiServiceClient) {
@@ -338,12 +338,13 @@ func testWithEntity(t *testing.T, binding *executor.ActionBinding, rr *Dashboard
}
// buildViewPermissionTestConfig returns config and users for GHSA view-permission tests:
-// one action "secret_action", ACL "restricted" (view:false) for user "low", ACL "full" (view:true) for user "admin".
+// one action "secret_action", ACL "restricted" (view:false, logs:false) for user "low", ACL "full" (view:true, logs:true) for user "admin".
func buildViewPermissionTestConfig(t *testing.T) (*config.Config, *authpublic.AuthenticatedUser, *authpublic.AuthenticatedUser) {
t.Helper()
cfg := config.DefaultConfig()
cfg.DefaultPermissions.View = false
cfg.DefaultPermissions.Exec = false
+ cfg.DefaultPermissions.Logs = false
cfg.Actions = append(cfg.Actions, &config.Action{
ID: "secret_action",
@@ -572,3 +573,127 @@ func TestOrderTopLevelDashboardComponents_SortablesSorted(t *testing.T) {
assert.Equal(t, "Alpha", out[0].Title, "sortables ordered by title")
assert.Equal(t, "Beta", out[1].Title)
}
+
+// TestEventStreamACLNoLeakToUnauthorizedUser (GHSA-228v-wc5r-j8m7) asserts that EventStream
+// does not send execution events or output chunks to users who are not allowed to view that action's logs.
+func TestEventStreamACLNoLeakToUnauthorizedUser(t *testing.T) {
+ cfg, lowUser, adminUser := buildViewPermissionTestConfig(t)
+ ex := executor.DefaultExecutor(cfg)
+ ex.RebuildActionMap()
+ api := newServer(ex)
+
+ binding := ex.FindBindingByID("secret_action")
+ require.NotNil(t, binding, "secret_action binding must exist")
+
+ clientLow, clientAdmin := addEventStreamTestClients(t, api, lowUser, adminUser)
+ defer removeEventStreamTestClients(api, clientLow, clientAdmin)
+
+ runEventStreamTestExecution(t, ex, cfg, binding, adminUser)
+ adminEvents := drainEventStreamUntilFinished(clientAdmin.channel, 2*time.Second)
+ lowEvents := drainEventStreamWithTimeout(clientLow.channel, 50*time.Millisecond)
+
+ assertEventStreamLowUserReceivesNothing(t, lowEvents)
+ assertEventStreamAdminReceivesSecretActionEvents(t, adminEvents)
+}
+
+func addEventStreamTestClients(t *testing.T, api *oliveTinAPI, lowUser, adminUser *authpublic.AuthenticatedUser) (*streamingClient, *streamingClient) {
+ t.Helper()
+ clientLow := &streamingClient{
+ channel: make(chan *apiv1.EventStreamResponse, 20),
+ AuthenticatedUser: lowUser,
+ }
+ clientAdmin := &streamingClient{
+ channel: make(chan *apiv1.EventStreamResponse, 20),
+ AuthenticatedUser: adminUser,
+ }
+ api.streamingClientsMutex.Lock()
+ api.streamingClients[clientLow] = struct{}{}
+ api.streamingClients[clientAdmin] = struct{}{}
+ api.streamingClientsMutex.Unlock()
+ return clientLow, clientAdmin
+}
+
+func removeEventStreamTestClients(api *oliveTinAPI, clientLow, clientAdmin *streamingClient) {
+ api.streamingClientsMutex.Lock()
+ delete(api.streamingClients, clientLow)
+ delete(api.streamingClients, clientAdmin)
+ api.streamingClientsMutex.Unlock()
+ close(clientLow.channel)
+ close(clientAdmin.channel)
+}
+
+func runEventStreamTestExecution(t *testing.T, ex *executor.Executor, cfg *config.Config, binding *executor.ActionBinding, adminUser *authpublic.AuthenticatedUser) {
+ t.Helper()
+ execReq := &executor.ExecutionRequest{
+ Binding: binding,
+ Arguments: map[string]string{},
+ TrackingID: uuid.NewString(),
+ Cfg: cfg,
+ AuthenticatedUser: adminUser,
+ }
+ wg, _ := ex.ExecRequest(execReq)
+ wg.Wait()
+}
+
+func drainEventStreamUntilFinished(ch <-chan *apiv1.EventStreamResponse, timeout time.Duration) []*apiv1.EventStreamResponse {
+ var out []*apiv1.EventStreamResponse
+ deadline := time.Now().Add(timeout)
+ for time.Now().Before(deadline) {
+ ev, finished := recvEventStreamOne(ch, 50*time.Millisecond)
+ if ev != nil {
+ out = append(out, ev)
+ if finished {
+ return out
+ }
+ }
+ }
+ return out
+}
+
+func recvEventStreamOne(ch <-chan *apiv1.EventStreamResponse, timeout time.Duration) (*apiv1.EventStreamResponse, bool) {
+ select {
+ case ev := <-ch:
+ return ev, ev.GetExecutionFinished() != nil
+ case <-time.After(timeout):
+ return nil, true
+ }
+}
+
+func drainEventStreamWithTimeout(ch <-chan *apiv1.EventStreamResponse, timeout time.Duration) []*apiv1.EventStreamResponse {
+ var out []*apiv1.EventStreamResponse
+ for {
+ select {
+ case ev := <-ch:
+ out = append(out, ev)
+ case <-time.After(timeout):
+ return out
+ }
+ }
+}
+
+func assertEventStreamLowUserReceivesNothing(t *testing.T, lowEvents []*apiv1.EventStreamResponse) {
+ t.Helper()
+ for _, ev := range lowEvents {
+ assert.Nil(t, ev.GetExecutionStarted(), "low-privilege user must not receive ExecutionStarted")
+ assert.Nil(t, ev.GetExecutionFinished(), "low-privilege user must not receive ExecutionFinished")
+ assert.Nil(t, ev.GetOutputChunk(), "low-privilege user must not receive OutputChunk")
+ }
+ assert.Empty(t, lowEvents, "low-privilege user with Logs:false must not receive any execution events")
+}
+
+func assertEventStreamAdminReceivesSecretActionEvents(t *testing.T, adminEvents []*apiv1.EventStreamResponse) {
+ t.Helper()
+ var gotStarted, gotFinished bool
+ for _, ev := range adminEvents {
+ if ev.GetExecutionStarted() != nil {
+ gotStarted = true
+ assert.Equal(t, "secret_action", ev.GetExecutionStarted().LogEntry.GetBindingId())
+ }
+ if ev.GetExecutionFinished() != nil {
+ gotFinished = true
+ assert.Equal(t, "secret_action", ev.GetExecutionFinished().LogEntry.GetBindingId())
+ }
+ }
+ assert.True(t, gotStarted, "admin must receive ExecutionStarted for secret_action")
+ assert.True(t, gotFinished, "admin must receive ExecutionFinished for secret_action")
+}
From 606b705bdd435480be2e7af1fa83724ce344c603 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 11 Mar 2026 00:27:42 +0000
Subject: [PATCH 068/148] chore: fix potential panic in tests
---
service/internal/api/api_test.go | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/service/internal/api/api_test.go b/service/internal/api/api_test.go
index ca8ecf5..eaca331 100644
--- a/service/internal/api/api_test.go
+++ b/service/internal/api/api_test.go
@@ -640,11 +640,11 @@ func drainEventStreamUntilFinished(ch <-chan *apiv1.EventStreamResponse, timeout
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
ev, finished := recvEventStreamOne(ch, 50*time.Millisecond)
+ if finished {
+ return out
+ }
if ev != nil {
out = append(out, ev)
- if finished {
- return out
- }
}
}
return out
@@ -652,7 +652,10 @@ func drainEventStreamUntilFinished(ch <-chan *apiv1.EventStreamResponse, timeout
func recvEventStreamOne(ch <-chan *apiv1.EventStreamResponse, timeout time.Duration) (*apiv1.EventStreamResponse, bool) {
select {
- case ev := <-ch:
+ case ev, ok := <-ch:
+ if !ok {
+ return nil, true
+ }
return ev, ev.GetExecutionFinished() != nil
case <-time.After(timeout):
return nil, true
@@ -663,7 +666,10 @@ func drainEventStreamWithTimeout(ch <-chan *apiv1.EventStreamResponse, timeout t
var out []*apiv1.EventStreamResponse
for {
select {
- case ev := <-ch:
+ case ev, ok := <-ch:
+ if !ok {
+ return out
+ }
out = append(out, ev)
case <-time.After(timeout):
return out
From cb3aa3362ecf7ebcdf34afc50ed8bf5ed95cd824 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Wed, 11 Mar 2026 00:29:56 +0000
Subject: [PATCH 069/148] chore: fix potential panic in tests
---
service/internal/api/api_test.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/service/internal/api/api_test.go b/service/internal/api/api_test.go
index eaca331..86bd1ec 100644
--- a/service/internal/api/api_test.go
+++ b/service/internal/api/api_test.go
@@ -640,12 +640,12 @@ func drainEventStreamUntilFinished(ch <-chan *apiv1.EventStreamResponse, timeout
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
ev, finished := recvEventStreamOne(ch, 50*time.Millisecond)
- if finished {
- return out
- }
if ev != nil {
out = append(out, ev)
}
+ if finished {
+ return out
+ }
}
return out
}
From 841ef770f3cb74a65382562adf67d5514d433ff2 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Tue, 24 Mar 2026 22:52:17 +0000
Subject: [PATCH 070/148] chore: Dep update March 24th
---
frontend/package-lock.json | 1333 +++++++++++++++++++++------
frontend/package.json | 10 +-
integration-tests/package-lock.json | 78 +-
integration-tests/package.json | 2 +-
service/go.mod | 58 +-
service/go.sum | 59 ++
service/internal/api/api_test.go | 26 +-
7 files changed, 1183 insertions(+), 383 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 6d6cc10..af76d33 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -13,32 +13,38 @@
"@connectrpc/connect-web": "^2.1.1",
"@hugeicons/core-free-icons": "^4.0.0",
"@hugeicons/vue": "^1.0.5",
- "@vitejs/plugin-vue": "^6.0.4",
+ "@vitejs/plugin-vue": "^6.0.5",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2",
"picocrank": "^1.14.1",
"standard": "^17.1.2",
- "unplugin-vue-components": "^31.0.0",
- "vite": "^7.3.1",
+ "unplugin-vue-components": "^32.0.0",
+ "vite": "^8.0.2",
"vue": "^3.5.30",
"vue-i18n": "^11.3.0",
- "vue-router": "^5.0.3"
+ "vue-router": "^5.0.4"
},
"devDependencies": {
"process": "^0.11.10",
- "stylelint": "^17.4.0",
+ "stylelint": "^17.5.0",
"stylelint-config-standard": "^40.0.0"
}
},
"node_modules/@babel/code-frame": {
- "version": "7.12.11",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
- "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@babel/highlight": "^7.10.4"
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
}
},
"node_modules/@babel/generator": {
@@ -75,92 +81,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/highlight": {
- "version": "7.24.6",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz",
- "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==",
- "dev": true,
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.24.6",
- "chalk": "^2.4.2",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/highlight/node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/@babel/highlight/node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "dev": true
- },
- "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "dev": true,
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/@babel/highlight/node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/@babel/parser": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
@@ -324,9 +244,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.0.27",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz",
- "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz",
+ "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==",
"dev": true,
"funding": [
{
@@ -338,7 +258,15 @@
"url": "https://opencollective.com/csstools"
}
],
- "license": "MIT-0"
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
+ }
},
"node_modules/@csstools/css-tokenizer": {
"version": "4.0.0",
@@ -430,6 +358,37 @@
"postcss-selector-parser": "^7.1.1"
}
},
+ "node_modules/@emnapi/core": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
+ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
+ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
+ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
@@ -1074,6 +1033,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
+ "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1106,6 +1081,255 @@
"node": ">= 8"
}
},
+ "node_modules/@oxc-project/types": {
+ "version": "0.122.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
+ "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz",
+ "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz",
+ "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz",
+ "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz",
+ "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz",
+ "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz",
+ "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz",
+ "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz",
+ "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz",
+ "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz",
+ "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz",
+ "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz",
+ "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz",
+ "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz",
+ "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz",
+ "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.2",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
@@ -1113,9 +1337,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
- "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz",
+ "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==",
"cpu": [
"arm"
],
@@ -1126,9 +1350,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
- "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz",
+ "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==",
"cpu": [
"arm64"
],
@@ -1139,9 +1363,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
- "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz",
+ "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==",
"cpu": [
"arm64"
],
@@ -1152,9 +1376,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
- "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz",
+ "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==",
"cpu": [
"x64"
],
@@ -1165,9 +1389,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
- "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz",
+ "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==",
"cpu": [
"arm64"
],
@@ -1178,9 +1402,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
- "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz",
+ "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==",
"cpu": [
"x64"
],
@@ -1191,9 +1415,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
- "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz",
+ "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==",
"cpu": [
"arm"
],
@@ -1204,9 +1428,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
- "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz",
+ "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==",
"cpu": [
"arm"
],
@@ -1217,9 +1441,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
- "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz",
+ "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==",
"cpu": [
"arm64"
],
@@ -1230,9 +1454,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
- "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz",
+ "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==",
"cpu": [
"arm64"
],
@@ -1242,10 +1466,10 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
- "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz",
+ "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==",
"cpu": [
"loong64"
],
@@ -1255,10 +1479,36 @@
"linux"
]
},
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
- "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz",
+ "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz",
+ "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz",
+ "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==",
"cpu": [
"ppc64"
],
@@ -1269,9 +1519,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
- "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz",
+ "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==",
"cpu": [
"riscv64"
],
@@ -1282,9 +1532,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
- "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz",
+ "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==",
"cpu": [
"riscv64"
],
@@ -1295,9 +1545,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
- "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz",
+ "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==",
"cpu": [
"s390x"
],
@@ -1308,9 +1558,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
- "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz",
+ "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==",
"cpu": [
"x64"
],
@@ -1321,9 +1571,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
- "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz",
+ "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==",
"cpu": [
"x64"
],
@@ -1333,10 +1583,36 @@
"linux"
]
},
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz",
+ "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz",
+ "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
- "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz",
+ "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==",
"cpu": [
"arm64"
],
@@ -1347,9 +1623,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
- "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz",
+ "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==",
"cpu": [
"ia32"
],
@@ -1359,10 +1635,23 @@
"win32"
]
},
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz",
+ "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
- "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz",
+ "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==",
"cpu": [
"x64"
],
@@ -1391,6 +1680,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1410,9 +1709,9 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-vue": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz",
- "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==",
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz",
+ "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==",
"license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "1.0.0-rc.2"
@@ -1421,7 +1720,7 @@
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"vue": "^3.2.25"
}
},
@@ -1604,9 +1903,9 @@
]
},
"node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -2104,9 +2403,9 @@
}
},
"node_modules/cosmiconfig": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
- "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz",
+ "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2155,14 +2454,14 @@
}
},
"node_modules/css-tree": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
- "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "mdn-data": "2.12.2",
- "source-map-js": "^1.0.1"
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
@@ -2295,6 +2594,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -3284,9 +3592,9 @@
}
},
"node_modules/get-east-asian-width": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
- "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
+ "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3466,9 +3774,9 @@
}
},
"node_modules/globby": {
- "version": "16.1.0",
- "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.0.tgz",
- "integrity": "sha512-+A4Hq7m7Ze592k9gZRy4gJ27DrXRNnC1vPjxTt1qQxEY8RxagBkBxivkCwg7FxSTG0iLLEMaUx13oOr0R2/qcQ==",
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.1.tgz",
+ "integrity": "sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4313,6 +4621,255 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -4459,16 +5016,16 @@
}
},
"node_modules/mdn-data": {
- "version": "2.12.2",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
- "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
"dev": true,
"license": "CC0-1.0"
},
"node_modules/meow": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/meow/-/meow-14.0.0.tgz",
- "integrity": "sha512-JhC3R1f6dbspVtmF3vKjAWz1EVIvwFrGGPLSdU6rK79xBwHWTuHoLnRX/t1/zHS1Ch1Y2UtIrih7DAHuH9JFJA==",
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz",
+ "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4530,15 +5087,15 @@
"license": "MIT"
},
"node_modules/mlly": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
- "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
+ "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
"license": "MIT",
"dependencies": {
- "acorn": "^8.15.0",
+ "acorn": "^8.16.0",
"pathe": "^2.0.3",
"pkg-types": "^1.3.1",
- "ufo": "^1.6.1"
+ "ufo": "^1.6.3"
}
},
"node_modules/mlly/node_modules/confbox": {
@@ -4907,10 +5464,160 @@
"vue-router": "^5.0.3"
}
},
+ "node_modules/picocrank/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/picocrank/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/picocrank/node_modules/unplugin": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
+ "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "acorn": "^8.15.0",
+ "picomatch": "^4.0.3",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/picocrank/node_modules/unplugin-vue-components": {
+ "version": "31.1.0",
+ "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-31.1.0.tgz",
+ "integrity": "sha512-9EbV5ark21A4BOBt6RJGJXCVD2I1eoxTZL1TAvNgYTokcrFIiuxpufb8owyWn7n+z2x8daz/ltZq6IRRKL3ydQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^5.0.0",
+ "local-pkg": "^1.1.2",
+ "magic-string": "^0.30.21",
+ "mlly": "^1.8.2",
+ "obug": "^2.1.1",
+ "picomatch": "^4.0.3",
+ "tinyglobby": "^0.2.15",
+ "unplugin": "^2.3.11",
+ "unplugin-utils": "^0.3.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": "^3.2.2 || ^4.0.0",
+ "vue": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/picocrank/node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
"node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5324,10 +6031,49 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/rolldown": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz",
+ "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.122.0",
+ "@rolldown/pluginutils": "1.0.0-rc.11"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0-rc.11",
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.11",
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.11",
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.11",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.11",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz",
+ "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==",
+ "license": "MIT"
+ },
"node_modules/rollup": {
- "version": "4.45.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
- "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
+ "version": "4.60.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz",
+ "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -5340,26 +6086,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.45.1",
- "@rollup/rollup-android-arm64": "4.45.1",
- "@rollup/rollup-darwin-arm64": "4.45.1",
- "@rollup/rollup-darwin-x64": "4.45.1",
- "@rollup/rollup-freebsd-arm64": "4.45.1",
- "@rollup/rollup-freebsd-x64": "4.45.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.45.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.45.1",
- "@rollup/rollup-linux-arm64-gnu": "4.45.1",
- "@rollup/rollup-linux-arm64-musl": "4.45.1",
- "@rollup/rollup-linux-loongarch64-gnu": "4.45.1",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.45.1",
- "@rollup/rollup-linux-riscv64-musl": "4.45.1",
- "@rollup/rollup-linux-s390x-gnu": "4.45.1",
- "@rollup/rollup-linux-x64-gnu": "4.45.1",
- "@rollup/rollup-linux-x64-musl": "4.45.1",
- "@rollup/rollup-win32-arm64-msvc": "4.45.1",
- "@rollup/rollup-win32-ia32-msvc": "4.45.1",
- "@rollup/rollup-win32-x64-msvc": "4.45.1",
+ "@rollup/rollup-android-arm-eabi": "4.60.0",
+ "@rollup/rollup-android-arm64": "4.60.0",
+ "@rollup/rollup-darwin-arm64": "4.60.0",
+ "@rollup/rollup-darwin-x64": "4.60.0",
+ "@rollup/rollup-freebsd-arm64": "4.60.0",
+ "@rollup/rollup-freebsd-x64": "4.60.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.0",
+ "@rollup/rollup-linux-arm64-musl": "4.60.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.0",
+ "@rollup/rollup-linux-loong64-musl": "4.60.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.0",
+ "@rollup/rollup-linux-x64-gnu": "4.60.0",
+ "@rollup/rollup-linux-x64-musl": "4.60.0",
+ "@rollup/rollup-openbsd-x64": "4.60.0",
+ "@rollup/rollup-openharmony-arm64": "4.60.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.0",
+ "@rollup/rollup-win32-x64-gnu": "4.60.0",
+ "@rollup/rollup-win32-x64-msvc": "4.60.0",
"fsevents": "~2.3.2"
}
},
@@ -5871,9 +6622,9 @@
}
},
"node_modules/stylelint": {
- "version": "17.4.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.4.0.tgz",
- "integrity": "sha512-3kQ2/cHv3Zt8OBg+h2B8XCx9evEABQIrv4hh3uXahGz/ZEHrTR80zxBiK2NfXNaSoyBzxO1pjsz1Vhdzwn5XSw==",
+ "version": "17.5.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.5.0.tgz",
+ "integrity": "sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==",
"dev": true,
"funding": [
{
@@ -5889,21 +6640,21 @@
"dependencies": {
"@csstools/css-calc": "^3.1.1",
"@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-syntax-patches-for-csstree": "^1.0.27",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.29",
"@csstools/css-tokenizer": "^4.0.0",
"@csstools/media-query-list-parser": "^5.0.0",
"@csstools/selector-resolve-nested": "^4.0.0",
"@csstools/selector-specificity": "^6.0.0",
"colord": "^2.9.3",
- "cosmiconfig": "^9.0.0",
+ "cosmiconfig": "^9.0.1",
"css-functions-list": "^3.3.3",
- "css-tree": "^3.1.0",
+ "css-tree": "^3.2.1",
"debug": "^4.4.3",
"fast-glob": "^3.3.3",
"fastest-levenshtein": "^1.0.16",
"file-entry-cache": "^11.1.2",
"global-modules": "^2.0.0",
- "globby": "^16.1.0",
+ "globby": "^16.1.1",
"globjoin": "^0.1.4",
"html-tags": "^5.1.0",
"ignore": "^7.0.5",
@@ -5911,15 +6662,15 @@
"imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
"mathml-tag-names": "^4.0.0",
- "meow": "^14.0.0",
+ "meow": "^14.1.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"picocolors": "^1.1.1",
- "postcss": "^8.5.6",
+ "postcss": "^8.5.8",
"postcss-safe-parser": "^7.0.1",
"postcss-selector-parser": "^7.1.1",
"postcss-value-parser": "^4.2.0",
- "string-width": "^8.1.1",
+ "string-width": "^8.2.0",
"supports-hyperlinks": "^4.4.0",
"svg-tags": "^1.0.0",
"table": "^6.9.0",
@@ -6027,14 +6778,14 @@
}
},
"node_modules/stylelint/node_modules/string-width": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz",
- "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==",
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz",
+ "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "get-east-asian-width": "^1.3.0",
- "strip-ansi": "^7.1.0"
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
},
"engines": {
"node": ">=20"
@@ -6044,13 +6795,13 @@
}
},
"node_modules/stylelint/node_modules/strip-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
- "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^6.0.1"
+ "ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
@@ -6258,6 +7009,13 @@
"strip-bom": "^3.0.0"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD",
+ "optional": true
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -6357,9 +7115,9 @@
}
},
"node_modules/ufo": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
- "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
"license": "MIT"
},
"node_modules/unbox-primitive": {
@@ -6394,18 +7152,17 @@
}
},
"node_modules/unplugin": {
- "version": "2.3.11",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
- "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
+ "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
- "acorn": "^8.15.0",
"picomatch": "^4.0.3",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
- "node": ">=18.12.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/unplugin-utils": {
@@ -6437,19 +7194,19 @@
}
},
"node_modules/unplugin-vue-components": {
- "version": "31.0.0",
- "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-31.0.0.tgz",
- "integrity": "sha512-4ULwfTZTLuWJ7+S9P7TrcStYLsSRkk6vy2jt/WTfgUEUb0nW9//xxmrfhyHUEVpZ2UKRRwfRb8Yy15PDbVZf+Q==",
+ "version": "32.0.0",
+ "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-32.0.0.tgz",
+ "integrity": "sha512-uLdccgS7mf3pv1bCCP20y/hm+u1eOjAmygVkh+Oa70MPkzgl1eQv1L0CwdHNM3gscO8/GDMGIET98Ja47CBbZg==",
"license": "MIT",
"dependencies": {
"chokidar": "^5.0.0",
"local-pkg": "^1.1.2",
"magic-string": "^0.30.21",
- "mlly": "^1.8.0",
+ "mlly": "^1.8.2",
"obug": "^2.1.1",
"picomatch": "^4.0.3",
"tinyglobby": "^0.2.15",
- "unplugin": "^2.3.11",
+ "unplugin": "^3.0.0",
"unplugin-utils": "^0.3.1"
},
"engines": {
@@ -6481,9 +7238,9 @@
}
},
"node_modules/unplugin/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -6517,16 +7274,15 @@
}
},
"node_modules/vite": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
- "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz",
+ "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==",
"license": "MIT",
"dependencies": {
- "esbuild": "^0.27.0",
- "fdir": "^6.5.0",
+ "lightningcss": "^1.32.0",
"picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
+ "postcss": "^8.5.8",
+ "rolldown": "1.0.0-rc.11",
"tinyglobby": "^0.2.15"
},
"bin": {
@@ -6543,9 +7299,10 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.0",
+ "esbuild": "^0.27.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
- "lightningcss": "^1.21.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
@@ -6558,15 +7315,18 @@
"@types/node": {
"optional": true
},
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
"jiti": {
"optional": true
},
"less": {
"optional": true
},
- "lightningcss": {
- "optional": true
- },
"sass": {
"optional": true
},
@@ -6590,27 +7350,10 @@
}
}
},
- "node_modules/vite/node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
"node_modules/vite/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -6662,9 +7405,9 @@
}
},
"node_modules/vue-router": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.3.tgz",
- "integrity": "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.4.tgz",
+ "integrity": "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg==",
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.28.6",
@@ -6739,20 +7482,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/vue-router/node_modules/unplugin": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
- "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==",
- "license": "MIT",
- "dependencies": {
- "@jridgewell/remapping": "^2.3.5",
- "picomatch": "^4.0.3",
- "webpack-virtual-modules": "^0.6.2"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index f18851c..f825680 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,7 +6,7 @@
"source": "index.html",
"devDependencies": {
"process": "^0.11.10",
- "stylelint": "^17.4.0",
+ "stylelint": "^17.5.0",
"stylelint-config-standard": "^40.0.0"
},
"scripts": {
@@ -26,17 +26,17 @@
"@connectrpc/connect-web": "^2.1.1",
"@hugeicons/core-free-icons": "^4.0.0",
"@hugeicons/vue": "^1.0.5",
- "@vitejs/plugin-vue": "^6.0.4",
+ "@vitejs/plugin-vue": "^6.0.5",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"iconify-icon": "^3.0.2",
"picocrank": "^1.14.1",
"standard": "^17.1.2",
- "unplugin-vue-components": "^31.0.0",
- "vite": "^7.3.1",
+ "unplugin-vue-components": "^32.0.0",
+ "vite": "^8.0.2",
"vue": "^3.5.30",
"vue-i18n": "^11.3.0",
- "vue-router": "^5.0.3"
+ "vue-router": "^5.0.4"
}
}
diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json
index bb664ca..7c79302 100644
--- a/integration-tests/package-lock.json
+++ b/integration-tests/package-lock.json
@@ -13,7 +13,7 @@
},
"devDependencies": {
"chai": "^6.2.2",
- "eslint": "^10.0.2",
+ "eslint": "^10.1.0",
"mocha": "^11.7.5",
"selenium-webdriver": "^4.41.0"
}
@@ -77,37 +77,37 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.23.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz",
- "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==",
+ "version": "0.23.3",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz",
+ "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^3.0.2",
+ "@eslint/object-schema": "^3.0.3",
"debug": "^4.3.1",
- "minimatch": "^10.2.1"
+ "minimatch": "^10.2.4"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz",
- "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==",
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz",
+ "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^1.1.0"
+ "@eslint/core": "^1.1.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/core": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
- "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz",
+ "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -118,9 +118,9 @@
}
},
"node_modules/@eslint/object-schema": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz",
- "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz",
+ "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -128,13 +128,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz",
- "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==",
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz",
+ "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^1.1.0",
+ "@eslint/core": "^1.1.1",
"levn": "^0.4.1"
},
"engines": {
@@ -408,9 +408,9 @@
"dev": true
},
"node_modules/brace-expansion": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
- "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -783,18 +783,18 @@
}
},
"node_modules/eslint": {
- "version": "10.0.2",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.2.tgz",
- "integrity": "sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw==",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz",
+ "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
- "@eslint/config-array": "^0.23.2",
- "@eslint/config-helpers": "^0.5.2",
- "@eslint/core": "^1.1.0",
- "@eslint/plugin-kit": "^0.6.0",
+ "@eslint/config-array": "^0.23.3",
+ "@eslint/config-helpers": "^0.5.3",
+ "@eslint/core": "^1.1.1",
+ "@eslint/plugin-kit": "^0.6.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -803,9 +803,9 @@
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^9.1.1",
+ "eslint-scope": "^9.1.2",
"eslint-visitor-keys": "^5.0.1",
- "espree": "^11.1.1",
+ "espree": "^11.2.0",
"esquery": "^1.7.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -816,7 +816,7 @@
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
- "minimatch": "^10.2.1",
+ "minimatch": "^10.2.4",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -839,9 +839,9 @@
}
},
"node_modules/eslint-scope": {
- "version": "9.1.1",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz",
- "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==",
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -871,9 +871,9 @@
}
},
"node_modules/espree": {
- "version": "11.1.1",
- "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz",
- "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==",
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
diff --git a/integration-tests/package.json b/integration-tests/package.json
index 331945f..e81a990 100644
--- a/integration-tests/package.json
+++ b/integration-tests/package.json
@@ -12,7 +12,7 @@
"license": "AGPL-3.0-only",
"devDependencies": {
"chai": "^6.2.2",
- "eslint": "^10.0.2",
+ "eslint": "^10.1.0",
"mocha": "^11.7.5",
"selenium-webdriver": "^4.41.0"
},
diff --git a/service/go.mod b/service/go.mod
index e03f2da..12b7bb5 100644
--- a/service/go.mod
+++ b/service/go.mod
@@ -1,6 +1,6 @@
module github.com/OliveTin/OliveTin
-go 1.25.6
+go 1.25.7
exclude google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884
@@ -10,7 +10,7 @@ require (
github.com/MicahParks/keyfunc/v3 v3.8.0
github.com/PaesslerAG/jsonpath v0.1.1
github.com/alexedwards/argon2id v1.0.0
- github.com/bufbuild/buf v1.66.0
+ github.com/bufbuild/buf v1.66.1
github.com/fsnotify/fsnotify v1.9.0
github.com/fzipp/gocyclo v0.6.0
github.com/go-critic/go-critic v0.14.3
@@ -21,15 +21,15 @@ require (
github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.1
github.com/knadh/koanf/providers/rawbytes v1.0.0
- github.com/knadh/koanf/v2 v2.3.2
+ github.com/knadh/koanf/v2 v2.3.4
github.com/prometheus/client_golang v1.23.2
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1
go.akshayshah.org/connectproto v0.6.0
- golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
- golang.org/x/oauth2 v0.35.0
- golang.org/x/sys v0.41.0
+ golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
+ golang.org/x/oauth2 v0.36.0
+ golang.org/x/sys v0.42.0
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
)
@@ -57,7 +57,7 @@ require (
github.com/PaesslerAG/gval v1.2.4 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113 // indirect
+ github.com/bufbuild/protocompile v0.14.2-0.20260306221011-519528254156 // indirect
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cli/browser v1.3.0 // indirect
@@ -68,7 +68,7 @@ require (
github.com/cristalhq/acmd v0.12.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
- github.com/docker/cli v29.2.1+incompatible // indirect
+ github.com/docker/cli v29.3.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.5.2+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
@@ -90,11 +90,11 @@ require (
github.com/gofrs/flock v0.13.0 // indirect
github.com/google/cel-go v0.27.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
- github.com/google/go-containerregistry v0.21.0 // indirect
+ github.com/google/go-containerregistry v0.21.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdx/go-netrc v1.0.0 // indirect
- github.com/klauspost/compress v1.18.4 // indirect
+ github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
@@ -108,12 +108,12 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
- github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
+ github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
- github.com/prometheus/procfs v0.19.2 // indirect
+ github.com/prometheus/procfs v0.20.1 // indirect
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
@@ -124,7 +124,7 @@ require (
github.com/rs/cors v1.11.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.2.1 // indirect
- github.com/segmentio/encoding v0.5.3 // indirect
+ github.com/segmentio/encoding v0.5.4 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
@@ -137,26 +137,26 @@ require (
go.lsp.dev/protocol v0.12.0 // indirect
go.lsp.dev/uri v0.3.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
- go.opentelemetry.io/otel v1.40.0 // indirect
- go.opentelemetry.io/otel/metric v1.40.0 // indirect
- go.opentelemetry.io/otel/trace v1.40.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
+ go.opentelemetry.io/otel v1.42.0 // indirect
+ go.opentelemetry.io/otel/metric v1.42.0 // indirect
+ go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.1 // indirect
- go.yaml.in/yaml/v2 v2.4.3 // indirect
+ go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/crypto v0.48.0 // indirect
- golang.org/x/exp/typeparams v0.0.0-20260212183809-81e46e3db34a // indirect
- golang.org/x/mod v0.33.0 // indirect
- golang.org/x/net v0.50.0 // indirect
- golang.org/x/sync v0.19.0 // indirect
- golang.org/x/term v0.40.0 // indirect
- golang.org/x/text v0.34.0 // indirect
- golang.org/x/time v0.14.0 // indirect
- golang.org/x/tools v0.42.0 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
+ golang.org/x/crypto v0.49.0 // indirect
+ golang.org/x/exp/typeparams v0.0.0-20260312153236-7ab1446f8b90 // indirect
+ golang.org/x/mod v0.34.0 // indirect
+ golang.org/x/net v0.52.0 // indirect
+ golang.org/x/sync v0.20.0 // indirect
+ golang.org/x/term v0.41.0 // indirect
+ golang.org/x/text v0.35.0 // indirect
+ golang.org/x/time v0.15.0 // indirect
+ golang.org/x/tools v0.43.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
google.golang.org/grpc v1.75.1 // indirect
mvdan.cc/xurls/v2 v2.6.0 // indirect
pluginrpc.com/pluginrpc v0.5.0 // indirect
diff --git a/service/go.sum b/service/go.sum
index 373c8c6..32f2d79 100644
--- a/service/go.sum
+++ b/service/go.sum
@@ -88,6 +88,8 @@ github.com/bufbuild/buf v1.65.0 h1:f2BzeCY9rRh9P5KD340ZoPAaFLTkssoUTHx7lpqozgg=
github.com/bufbuild/buf v1.65.0/go.mod h1:7SAs2YqGpPXHqBBXBeYQbCzY0OQq4Jbg6XCqirEiYvQ=
github.com/bufbuild/buf v1.66.0 h1:6kksYJpu6r45bvPJSTwNSwRqiAjrwB9YyU7skjNzFVo=
github.com/bufbuild/buf v1.66.0/go.mod h1:tWVlwtIPZ7kzlCB9D0hbbfrroT0GNCybPdPQXq1i1Ac=
+github.com/bufbuild/buf v1.66.1 h1:wqmmU+6uoxB/eYDOmXq2To4qEXvOJN7gR6L9AxrPL1E=
+github.com/bufbuild/buf v1.66.1/go.mod h1:Vd3ELm8IePWaDJaS9FLy94FFOnLrjLi4mDxmXtw9Xio=
github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e h1:LQA+1MyiPkolGHJGC2GMDC5Xu+0RDVH6jGMKech7Exs=
github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e/go.mod h1:5UUj46Eu+U+C59C5N6YilaMI7WWfP2bW9xGcOkme2DI=
github.com/bufbuild/protocompile v0.14.2-0.20260105175043-4d8d90b1c6b8 h1:cQYwUyAzyMmYr7AyJU1C6pVCpUrJJBkmx7UunZosxxs=
@@ -98,6 +100,8 @@ github.com/bufbuild/protocompile v0.14.2-0.20260130195850-5c64bed4577e h1:emH16B
github.com/bufbuild/protocompile v0.14.2-0.20260130195850-5c64bed4577e/go.mod h1:cxhE8h+14t0Yxq2H9MV/UggzQ1L0gh0t2tJobITWsBE=
github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113 h1:nxt1QhP9rMQNFhHTdcNFwJ9wKCSdBjd28gz+qGDv4kM=
github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113/go.mod h1:cxhE8h+14t0Yxq2H9MV/UggzQ1L0gh0t2tJobITWsBE=
+github.com/bufbuild/protocompile v0.14.2-0.20260306221011-519528254156 h1:XOfIInPVufMjifwy3fli8qQVsGHWVCDVY/zp6elAOsY=
+github.com/bufbuild/protocompile v0.14.2-0.20260306221011-519528254156/go.mod h1:cxhE8h+14t0Yxq2H9MV/UggzQ1L0gh0t2tJobITWsBE=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 h1:V1xulAoqLqVg44rY97xOR+mQpD2N+GzhMHVwJ030WEU=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
@@ -134,6 +138,8 @@ github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW4pKAbKdJg=
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk=
+github.com/docker/cli v29.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
@@ -208,6 +214,8 @@ github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4p
github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM=
github.com/google/go-containerregistry v0.21.0 h1:ocqxUOczFwAZQBMNE7kuzfqvDe0VWoZxQMOesXreCDI=
github.com/google/go-containerregistry v0.21.0/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0=
+github.com/google/go-containerregistry v0.21.3 h1:Xr+yt3VvwOOn/5nJzd7UoOhwPGiPkYW0zWDLLUXqAi4=
+github.com/google/go-containerregistry v0.21.3/go.mod h1:D5ZrJF1e6dMzvInpBPuMCX0FxURz7GLq2rV3Us9aPkc=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
@@ -228,6 +236,8 @@ github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
+github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
+github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
@@ -244,6 +254,8 @@ github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/knadh/koanf/v2 v2.3.2 h1:Ee6tuzQYFwcZXQpc2MiVeC6qHMandf5SMUJJNoFp/c4=
github.com/knadh/koanf/v2 v2.3.2/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
+github.com/knadh/koanf/v2 v2.3.4 h1:fnynNSDlujWE+v83hAp8wKr/cdoxHLO0629SN+U8Urc=
+github.com/knadh/koanf/v2 v2.3.4/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -280,6 +292,8 @@ github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a h1:VweslR2akb/ARh
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE=
+github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -294,6 +308,8 @@ github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTU
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
+github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
+github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9 h1:arwj11zP0yJIxIRiDn22E0H8PxfF7TsTrc2wIPFIsf4=
github.com/protocolbuffers/protoscope v0.0.0-20221109213918-8e7a6aafa2c9/go.mod h1:SKZx6stCn03JN3BOWTwvVIO2ajMkb/zQdTceXYhKw/4=
github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA=
@@ -328,6 +344,8 @@ github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
+github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
+github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
@@ -376,30 +394,41 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
+go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
+go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
+go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
+go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
+go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
+go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
+go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
+go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
@@ -413,6 +442,8 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
+go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
+go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -424,6 +455,8 @@ golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
+golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
+golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
@@ -432,6 +465,8 @@ golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
+golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
+golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20251219203646-944ab1f22d93 h1:PbC785RGO6yPO051ItgbG/adwoKRWC0VS7kXXeD/iqk=
@@ -440,6 +475,8 @@ golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96 h1:RMc8anw0hCPcg5
golang.org/x/exp/typeparams v0.0.0-20260112195511-716be5621a96/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/exp/typeparams v0.0.0-20260212183809-81e46e3db34a h1:n3SZDk8iNpMasCwQD7/0dIaCVf3gJiGZ9Rqa094jUN0=
golang.org/x/exp/typeparams v0.0.0-20260212183809-81e46e3db34a/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo=
+golang.org/x/exp/typeparams v0.0.0-20260312153236-7ab1446f8b90 h1:cfW8UCYSVdPblxA7qQe3o5Iad55Vsx4BFmuGS9RNOmc=
+golang.org/x/exp/typeparams v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
@@ -448,6 +485,8 @@ golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -459,15 +498,21 @@ golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
+golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -485,6 +530,8 @@ golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
+golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -496,6 +543,8 @@ golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
+golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
+golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -508,8 +557,12 @@ golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
+golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
+golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
+golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -520,6 +573,8 @@ golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
@@ -532,6 +587,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s=
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
+google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
+google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
@@ -542,6 +599,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
diff --git a/service/internal/api/api_test.go b/service/internal/api/api_test.go
index 86bd1ec..97477a4 100644
--- a/service/internal/api/api_test.go
+++ b/service/internal/api/api_test.go
@@ -662,18 +662,30 @@ func recvEventStreamOne(ch <-chan *apiv1.EventStreamResponse, timeout time.Durat
}
}
+func eventStreamRecvResult(ev *apiv1.EventStreamResponse, ok bool) (*apiv1.EventStreamResponse, bool) {
+ if !ok {
+ return nil, true
+ }
+ return ev, false
+}
+
+func recvEventStreamWithTimeoutOne(ch <-chan *apiv1.EventStreamResponse, timeout time.Duration) (*apiv1.EventStreamResponse, bool) {
+ select {
+ case ev, ok := <-ch:
+ return eventStreamRecvResult(ev, ok)
+ case <-time.After(timeout):
+ return nil, true
+ }
+}
+
func drainEventStreamWithTimeout(ch <-chan *apiv1.EventStreamResponse, timeout time.Duration) []*apiv1.EventStreamResponse {
var out []*apiv1.EventStreamResponse
for {
- select {
- case ev, ok := <-ch:
- if !ok {
- return out
- }
- out = append(out, ev)
- case <-time.After(timeout):
+ ev, done := recvEventStreamWithTimeoutOne(ch, timeout)
+ if done {
return out
}
+ out = append(out, ev)
}
}
From 196c5ccfc0f7f05d25227d18427225ef10c6f8de Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 10 May 2026 09:37:39 +0100
Subject: [PATCH 071/148] chore: dep update 20260510
---
frontend/package-lock.json | 562 ++++++++++++++--------------
frontend/package.json | 14 +-
integration-tests/package-lock.json | 183 ++++-----
integration-tests/package.json | 6 +-
service/go.mod | 80 ++--
service/go.sum | 84 +++++
6 files changed, 519 insertions(+), 410 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index af76d33..d016e09 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,9 +11,9 @@
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
- "@hugeicons/core-free-icons": "^4.0.0",
+ "@hugeicons/core-free-icons": "^4.1.3",
"@hugeicons/vue": "^1.0.5",
- "@vitejs/plugin-vue": "^6.0.5",
+ "@vitejs/plugin-vue": "^6.0.6",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
@@ -21,14 +21,14 @@
"picocrank": "^1.14.1",
"standard": "^17.1.2",
"unplugin-vue-components": "^32.0.0",
- "vite": "^8.0.2",
- "vue": "^3.5.30",
- "vue-i18n": "^11.3.0",
- "vue-router": "^5.0.4"
+ "vite": "^8.0.11",
+ "vue": "^3.5.34",
+ "vue-i18n": "^11.4.2",
+ "vue-router": "^5.0.6"
},
"devDependencies": {
"process": "^0.11.10",
- "stylelint": "^17.5.0",
+ "stylelint": "^17.11.0",
"stylelint-config-standard": "^40.0.0"
}
},
@@ -82,9 +82,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
- "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "version": "7.29.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
+ "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
@@ -197,9 +197,9 @@
}
},
"node_modules/@csstools/css-calc": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz",
- "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz",
+ "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==",
"dev": true,
"funding": [
{
@@ -244,9 +244,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz",
- "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz",
+ "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==",
"dev": true,
"funding": [
{
@@ -359,20 +359,20 @@
}
},
"node_modules/@emnapi/core": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
- "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/wasi-threads": "1.2.0",
+ "@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
- "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -380,9 +380,9 @@
}
},
"node_modules/@emnapi/wasi-threads": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
- "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -865,9 +865,9 @@
}
},
"node_modules/@hugeicons/core-free-icons": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.0.0.tgz",
- "integrity": "sha512-bzfbKumv3ke3ajbe2MyXi9i0I/cdsZ6n/mO9EfIPNSL++pHLqs7nSGRIVUtjF4xrrEyVkfhxssv4Jek8DPA6gA==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-4.1.3.tgz",
+ "integrity": "sha512-FWPrKnlYKpSaitUtlZhFlDQXDgHiayTPFJYWvyIKkW2RI6Vj5KBvjxI+lAnnFPu07SwgIMiDDj+Gttl0t+o/oQ==",
"license": "MIT"
},
"node_modules/@hugeicons/vue": {
@@ -921,14 +921,14 @@
"license": "MIT"
},
"node_modules/@intlify/core-base": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.3.0.tgz",
- "integrity": "sha512-NNX5jIwF4TJBe7RtSKDMOA6JD9mp2mRcBHAwt2X+Q8PvnZub0yj5YYXlFu2AcESdgQpEv/5Yx2uOCV/yh7YkZg==",
+ "version": "11.4.2",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.2.tgz",
+ "integrity": "sha512-7fpuCcVmeLv2T9qHsARqGvh8xt+sV2fH+Q+gMHFwB/rPXzo85DpbJFKn7dBH1L5p0c2cSh2DW+2h/64EKrISmA==",
"license": "MIT",
"dependencies": {
- "@intlify/devtools-types": "11.3.0",
- "@intlify/message-compiler": "11.3.0",
- "@intlify/shared": "11.3.0"
+ "@intlify/devtools-types": "11.4.2",
+ "@intlify/message-compiler": "11.4.2",
+ "@intlify/shared": "11.4.2"
},
"engines": {
"node": ">= 16"
@@ -938,13 +938,13 @@
}
},
"node_modules/@intlify/devtools-types": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.3.0.tgz",
- "integrity": "sha512-G9CNL4WpANWVdUjubOIIS7/D2j/0j+1KJmhBJxHilWNKr9mmt3IjFV3Hq4JoBP23uOoC5ynxz/FHZ42M+YxfGw==",
+ "version": "11.4.2",
+ "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.2.tgz",
+ "integrity": "sha512-3u8EN1kB6EMSi96KXs5k7a8y2X2g4+h3X6iwVZU47cP4n+mTuq//WMjG588BzSp/2XQ/dTXo2BLUXX+XS+PNfA==",
"license": "MIT",
"dependencies": {
- "@intlify/core-base": "11.3.0",
- "@intlify/shared": "11.3.0"
+ "@intlify/core-base": "11.4.2",
+ "@intlify/shared": "11.4.2"
},
"engines": {
"node": ">= 16"
@@ -954,12 +954,12 @@
}
},
"node_modules/@intlify/message-compiler": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.3.0.tgz",
- "integrity": "sha512-RAJp3TMsqohg/Wa7bVF3cChRhecSYBLrTCQSj7j0UtWVFLP+6iEJoE2zb7GU5fp+fmG5kCbUdzhmlAUCWXiUJw==",
+ "version": "11.4.2",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.2.tgz",
+ "integrity": "sha512-a6CDSGSMTGrg0BjD97x8TBYPf7qQMDlZipJ6UDfv/pd4OIym8TMlHu3MsH0bTNnRdAG2D6EFEykIgiQPqvtTkA==",
"license": "MIT",
"dependencies": {
- "@intlify/shared": "11.3.0",
+ "@intlify/shared": "11.4.2",
"source-map-js": "^1.0.2"
},
"engines": {
@@ -970,9 +970,9 @@
}
},
"node_modules/@intlify/shared": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.3.0.tgz",
- "integrity": "sha512-LC6P/uay7rXL5zZ5+5iRJfLs/iUN8apu9tm8YqQVmW3Uq3X4A0dOFUIDuAmB7gAC29wTHOS3EiN/IosNSz0eNQ==",
+ "version": "11.4.2",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.2.tgz",
+ "integrity": "sha512-NzpHbguRCsOHDwxmlBa9qu/imc+/QWgsYUaK6FZeNC0wK8QfAbhqrktEp/haVzxU1aikH8IX4ytD+mfFEMi/9A==",
"license": "MIT",
"engines": {
"node": ">= 16"
@@ -1034,19 +1034,21 @@
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
- "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1",
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
}
},
"node_modules/@nodelib/fs.scandir": {
@@ -1082,18 +1084,18 @@
}
},
"node_modules/@oxc-project/types": {
- "version": "0.122.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
- "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
+ "version": "0.128.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz",
+ "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@rolldown/binding-android-arm64": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz",
- "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz",
+ "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==",
"cpu": [
"arm64"
],
@@ -1107,9 +1109,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz",
- "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz",
+ "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==",
"cpu": [
"arm64"
],
@@ -1123,9 +1125,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz",
- "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz",
+ "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==",
"cpu": [
"x64"
],
@@ -1139,9 +1141,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz",
- "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz",
+ "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==",
"cpu": [
"x64"
],
@@ -1155,9 +1157,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz",
- "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz",
+ "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==",
"cpu": [
"arm"
],
@@ -1171,9 +1173,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz",
- "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz",
+ "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==",
"cpu": [
"arm64"
],
@@ -1187,9 +1189,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz",
- "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz",
+ "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==",
"cpu": [
"arm64"
],
@@ -1203,9 +1205,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz",
- "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz",
+ "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==",
"cpu": [
"ppc64"
],
@@ -1219,9 +1221,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz",
- "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz",
+ "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==",
"cpu": [
"s390x"
],
@@ -1235,9 +1237,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz",
- "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz",
+ "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==",
"cpu": [
"x64"
],
@@ -1251,9 +1253,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz",
- "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz",
+ "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==",
"cpu": [
"x64"
],
@@ -1267,9 +1269,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz",
- "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz",
+ "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==",
"cpu": [
"arm64"
],
@@ -1283,25 +1285,27 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz",
- "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz",
+ "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==",
"cpu": [
"wasm32"
],
"license": "MIT",
"optional": true,
"dependencies": {
- "@napi-rs/wasm-runtime": "^1.1.1"
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
},
"engines": {
- "node": ">=14.0.0"
+ "node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz",
- "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz",
+ "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==",
"cpu": [
"arm64"
],
@@ -1315,9 +1319,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz",
- "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz",
+ "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==",
"cpu": [
"x64"
],
@@ -1331,9 +1335,9 @@
}
},
"node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.2",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
- "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==",
+ "version": "1.0.0-rc.13",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz",
+ "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==",
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
@@ -1681,9 +1685,9 @@
}
},
"node_modules/@tybys/wasm-util": {
- "version": "0.10.1",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
- "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
+ "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -1709,12 +1713,12 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-vue": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz",
- "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==",
+ "version": "6.0.6",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.6.tgz",
+ "integrity": "sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==",
"license": "MIT",
"dependencies": {
- "@rolldown/pluginutils": "1.0.0-rc.2"
+ "@rolldown/pluginutils": "1.0.0-rc.13"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
@@ -1752,53 +1756,53 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
- "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz",
+ "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.0",
- "@vue/shared": "3.5.30",
+ "@babel/parser": "^7.29.3",
+ "@vue/shared": "3.5.34",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
- "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz",
+ "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.30",
- "@vue/shared": "3.5.30"
+ "@vue/compiler-core": "3.5.34",
+ "@vue/shared": "3.5.34"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
- "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz",
+ "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==",
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.29.0",
- "@vue/compiler-core": "3.5.30",
- "@vue/compiler-dom": "3.5.30",
- "@vue/compiler-ssr": "3.5.30",
- "@vue/shared": "3.5.30",
+ "@babel/parser": "^7.29.3",
+ "@vue/compiler-core": "3.5.34",
+ "@vue/compiler-dom": "3.5.34",
+ "@vue/compiler-ssr": "3.5.34",
+ "@vue/shared": "3.5.34",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
- "postcss": "^8.5.8",
+ "postcss": "^8.5.14",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
- "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz",
+ "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.30",
- "@vue/shared": "3.5.30"
+ "@vue/compiler-dom": "3.5.34",
+ "@vue/shared": "3.5.34"
}
},
"node_modules/@vue/devtools-api": {
@@ -1832,53 +1836,53 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
- "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz",
+ "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.30"
+ "@vue/shared": "3.5.34"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
- "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz",
+ "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.30",
- "@vue/shared": "3.5.30"
+ "@vue/reactivity": "3.5.34",
+ "@vue/shared": "3.5.34"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
- "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz",
+ "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.30",
- "@vue/runtime-core": "3.5.30",
- "@vue/shared": "3.5.30",
+ "@vue/reactivity": "3.5.34",
+ "@vue/runtime-core": "3.5.34",
+ "@vue/shared": "3.5.34",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
- "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz",
+ "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.30",
- "@vue/shared": "3.5.30"
+ "@vue/compiler-ssr": "3.5.34",
+ "@vue/shared": "3.5.34"
},
"peerDependencies": {
- "vue": "3.5.30"
+ "vue": "3.5.34"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
- "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz",
+ "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==",
"license": "MIT"
},
"node_modules/@xterm/addon-fit": {
@@ -1924,9 +1928,9 @@
}
},
"node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
@@ -2200,9 +2204,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -3425,6 +3429,23 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"license": "MIT"
},
+ "node_modules/fast-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
"node_modules/fastest-levenshtein": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
@@ -3504,9 +3525,9 @@
}
},
"node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"license": "ISC"
},
"node_modules/for-each": {
@@ -3592,9 +3613,9 @@
}
},
"node_modules/get-east-asian-width": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
- "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3774,9 +3795,9 @@
}
},
"node_modules/globby": {
- "version": "16.1.1",
- "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.1.tgz",
- "integrity": "sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==",
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.0.tgz",
+ "integrity": "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5541,9 +5562,9 @@
}
},
"node_modules/picocrank/node_modules/vite": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
- "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "version": "7.3.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz",
+ "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.27.0",
@@ -5731,9 +5752,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.8",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
- "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"funding": [
{
"type": "opencollective",
@@ -6032,13 +6053,13 @@
}
},
"node_modules/rolldown": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz",
- "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
+ "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==",
"license": "MIT",
"dependencies": {
- "@oxc-project/types": "=0.122.0",
- "@rolldown/pluginutils": "1.0.0-rc.11"
+ "@oxc-project/types": "=0.128.0",
+ "@rolldown/pluginutils": "1.0.0-rc.18"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -6047,27 +6068,27 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.0.0-rc.11",
- "@rolldown/binding-darwin-arm64": "1.0.0-rc.11",
- "@rolldown/binding-darwin-x64": "1.0.0-rc.11",
- "@rolldown/binding-freebsd-x64": "1.0.0-rc.11",
- "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11",
- "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11",
- "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11",
- "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11",
- "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11",
- "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11",
- "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11",
- "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11",
- "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11",
- "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11",
- "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11"
+ "@rolldown/binding-android-arm64": "1.0.0-rc.18",
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.18",
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.18",
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.18",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18"
}
},
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.11",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz",
- "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==",
+ "version": "1.0.0-rc.18",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz",
+ "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==",
"license": "MIT"
},
"node_modules/rollup": {
@@ -6622,9 +6643,9 @@
}
},
"node_modules/stylelint": {
- "version": "17.5.0",
- "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.5.0.tgz",
- "integrity": "sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==",
+ "version": "17.11.0",
+ "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.11.0.tgz",
+ "integrity": "sha512-/3czzmbF9XdGWvReDF3Ex4R23Ajolo7j8RB2bFNEqk6Ht356nlpVV+G5bG2Qt8AW1ofJzXztBRDnAtd7cgowWA==",
"dev": true,
"funding": [
{
@@ -6638,9 +6659,9 @@
],
"license": "MIT",
"dependencies": {
- "@csstools/css-calc": "^3.1.1",
+ "@csstools/css-calc": "^3.2.0",
"@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-syntax-patches-for-csstree": "^1.0.29",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
"@csstools/css-tokenizer": "^4.0.0",
"@csstools/media-query-list-parser": "^5.0.0",
"@csstools/selector-resolve-nested": "^4.0.0",
@@ -6654,27 +6675,26 @@
"fastest-levenshtein": "^1.0.16",
"file-entry-cache": "^11.1.2",
"global-modules": "^2.0.0",
- "globby": "^16.1.1",
+ "globby": "^16.2.0",
"globjoin": "^0.1.4",
"html-tags": "^5.1.0",
"ignore": "^7.0.5",
"import-meta-resolve": "^4.2.0",
- "imurmurhash": "^0.1.4",
"is-plain-object": "^5.0.0",
"mathml-tag-names": "^4.0.0",
"meow": "^14.1.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"picocolors": "^1.1.1",
- "postcss": "^8.5.8",
+ "postcss": "^8.5.13",
"postcss-safe-parser": "^7.0.1",
"postcss-selector-parser": "^7.1.1",
"postcss-value-parser": "^4.2.0",
- "string-width": "^8.2.0",
+ "string-width": "^8.2.1",
"supports-hyperlinks": "^4.4.0",
"svg-tags": "^1.0.0",
"table": "^6.9.0",
- "write-file-atomic": "^7.0.0"
+ "write-file-atomic": "^7.0.1"
},
"bin": {
"stylelint": "bin/stylelint.mjs"
@@ -6778,9 +6798,9 @@
}
},
"node_modules/stylelint/node_modules/string-width": {
- "version": "8.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz",
- "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==",
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
+ "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6912,15 +6932,16 @@
}
},
"node_modules/table/node_modules/ajv": {
- "version": "8.14.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz",
- "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==",
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2",
- "uri-js": "^4.4.1"
+ "require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
@@ -6940,13 +6961,13 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -6973,9 +6994,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -7182,9 +7203,9 @@
}
},
"node_modules/unplugin-utils/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -7226,9 +7247,9 @@
}
},
"node_modules/unplugin-vue-components/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -7274,16 +7295,16 @@
}
},
"node_modules/vite": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz",
- "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==",
+ "version": "8.0.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz",
+ "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==",
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.8",
- "rolldown": "1.0.0-rc.11",
- "tinyglobby": "^0.2.15"
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.14",
+ "rolldown": "1.0.0-rc.18",
+ "tinyglobby": "^0.2.16"
},
"bin": {
"vite": "bin/vite.js"
@@ -7299,8 +7320,8 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.1.0",
- "esbuild": "^0.27.0",
+ "@vitejs/devtools": "^0.1.18",
+ "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
@@ -7363,16 +7384,16 @@
}
},
"node_modules/vue": {
- "version": "3.5.30",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
- "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz",
+ "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.30",
- "@vue/compiler-sfc": "3.5.30",
- "@vue/runtime-dom": "3.5.30",
- "@vue/server-renderer": "3.5.30",
- "@vue/shared": "3.5.30"
+ "@vue/compiler-dom": "3.5.34",
+ "@vue/compiler-sfc": "3.5.34",
+ "@vue/runtime-dom": "3.5.34",
+ "@vue/server-renderer": "3.5.34",
+ "@vue/shared": "3.5.34"
},
"peerDependencies": {
"typescript": "*"
@@ -7384,14 +7405,14 @@
}
},
"node_modules/vue-i18n": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.0.tgz",
- "integrity": "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==",
+ "version": "11.4.2",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.2.tgz",
+ "integrity": "sha512-sADDeKXqAGsPX6tK3t3y2ZiMpbVWN12tG+MhTiJ06rVoh58eGtM4wFyw3uWGbVkXByVp9Ne/AP+nSSzI+J9OAQ==",
"license": "MIT",
"dependencies": {
- "@intlify/core-base": "11.3.0",
- "@intlify/devtools-types": "11.3.0",
- "@intlify/shared": "11.3.0",
+ "@intlify/core-base": "11.4.2",
+ "@intlify/devtools-types": "11.4.2",
+ "@intlify/shared": "11.4.2",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
@@ -7405,9 +7426,9 @@
}
},
"node_modules/vue-router": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.4.tgz",
- "integrity": "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.6.tgz",
+ "integrity": "sha512-9+kmUTGbKMyW9Asoy98IXXYIzrTMT7JDAdpDDeEkorHvybpUvBI2wsrSM5jFOXrFydpzRFJ9vAh+80DN2PGu9w==",
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.28.6",
@@ -7471,9 +7492,9 @@
}
},
"node_modules/vue-router/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -7604,13 +7625,12 @@
"license": "ISC"
},
"node_modules/write-file-atomic": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.0.tgz",
- "integrity": "sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz",
+ "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "imurmurhash": "^0.1.4",
"signal-exit": "^4.0.1"
},
"engines": {
@@ -7627,9 +7647,9 @@
}
},
"node_modules/yaml": {
- "version": "2.8.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
- "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
+ "version": "2.8.4",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
+ "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
diff --git a/frontend/package.json b/frontend/package.json
index f825680..fc57fff 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,7 +6,7 @@
"source": "index.html",
"devDependencies": {
"process": "^0.11.10",
- "stylelint": "^17.5.0",
+ "stylelint": "^17.11.0",
"stylelint-config-standard": "^40.0.0"
},
"scripts": {
@@ -24,9 +24,9 @@
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
- "@hugeicons/core-free-icons": "^4.0.0",
+ "@hugeicons/core-free-icons": "^4.1.3",
"@hugeicons/vue": "^1.0.5",
- "@vitejs/plugin-vue": "^6.0.5",
+ "@vitejs/plugin-vue": "^6.0.6",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
@@ -34,9 +34,9 @@
"picocrank": "^1.14.1",
"standard": "^17.1.2",
"unplugin-vue-components": "^32.0.0",
- "vite": "^8.0.2",
- "vue": "^3.5.30",
- "vue-i18n": "^11.3.0",
- "vue-router": "^5.0.4"
+ "vite": "^8.0.11",
+ "vue": "^3.5.34",
+ "vue-i18n": "^11.4.2",
+ "vue-router": "^5.0.6"
}
}
diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json
index 7c79302..601e4a9 100644
--- a/integration-tests/package-lock.json
+++ b/integration-tests/package-lock.json
@@ -9,13 +9,13 @@
"version": "1.0.0",
"license": "AGPL-3.0-only",
"dependencies": {
- "wait-on": "^9.0.4"
+ "wait-on": "^9.0.5"
},
"devDependencies": {
"chai": "^6.2.2",
- "eslint": "^10.1.0",
+ "eslint": "^10.3.0",
"mocha": "^11.7.5",
- "selenium-webdriver": "^4.41.0"
+ "selenium-webdriver": "^4.43.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -77,13 +77,13 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.23.3",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz",
- "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==",
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/object-schema": "^3.0.3",
+ "@eslint/object-schema": "^3.0.5",
"debug": "^4.3.1",
"minimatch": "^10.2.4"
},
@@ -92,22 +92,22 @@
}
},
"node_modules/@eslint/config-helpers": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz",
- "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz",
+ "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^1.1.1"
+ "@eslint/core": "^1.2.1"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/core": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz",
- "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -118,9 +118,9 @@
}
},
"node_modules/@eslint/object-schema": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz",
- "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -128,13 +128,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz",
- "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==",
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz",
+ "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^1.1.1",
+ "@eslint/core": "^1.2.1",
"levn": "^0.4.1"
},
"engines": {
@@ -172,9 +172,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@hapi/tlds": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.5.tgz",
- "integrity": "sha512-Vq/1gnIIsvFUpKlDdfrPd/ssHDpAyBP/baVukh3u2KSG2xoNjsnRNjQiPmuyPPGqsn1cqVWWhtZHfOBaLizFRQ==",
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz",
+ "integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=14.0.0"
@@ -391,14 +391,14 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.13.5",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
- "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
+ "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.11",
+ "follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
- "proxy-from-env": "^1.1.0"
+ "proxy-from-env": "^2.1.0"
}
},
"node_modules/balanced-match": {
@@ -408,9 +408,9 @@
"dev": true
},
"node_modules/brace-expansion": {
- "version": "5.0.5",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
- "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -783,18 +783,18 @@
}
},
"node_modules/eslint": {
- "version": "10.1.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz",
- "integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==",
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz",
+ "integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2",
- "@eslint/config-array": "^0.23.3",
- "@eslint/config-helpers": "^0.5.3",
- "@eslint/core": "^1.1.1",
- "@eslint/plugin-kit": "^0.6.1",
+ "@eslint/config-array": "^0.23.5",
+ "@eslint/config-helpers": "^0.5.5",
+ "@eslint/core": "^1.2.1",
+ "@eslint/plugin-kit": "^0.7.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -1007,16 +1007,16 @@
}
},
"node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
@@ -1156,9 +1156,9 @@
}
},
"node_modules/glob/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1166,13 +1166,13 @@
}
},
"node_modules/glob/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -1230,9 +1230,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -1372,9 +1372,9 @@
}
},
"node_modules/joi": {
- "version": "18.0.2",
- "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz",
- "integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==",
+ "version": "18.2.1",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz",
+ "integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/address": "^5.1.1",
@@ -1383,7 +1383,7 @@
"@hapi/pinpoint": "^2.0.1",
"@hapi/tlds": "^1.1.1",
"@hapi/topo": "^6.0.2",
- "@standard-schema/spec": "^1.0.0"
+ "@standard-schema/spec": "^1.1.0"
},
"engines": {
"node": ">= 20"
@@ -1482,9 +1482,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
- "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/log-symbols": {
@@ -1541,13 +1541,13 @@
}
},
"node_modules/minimatch": {
- "version": "10.2.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
- "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^5.0.2"
+ "brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
@@ -1612,9 +1612,9 @@
}
},
"node_modules/mocha/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1622,13 +1622,13 @@
}
},
"node_modules/mocha/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -1783,10 +1783,13 @@
"dev": true
},
"node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
},
"node_modules/punycode": {
"version": "2.3.1",
@@ -1863,9 +1866,9 @@
"dev": true
},
"node_modules/selenium-webdriver": {
- "version": "4.41.0",
- "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.41.0.tgz",
- "integrity": "sha512-1XxuKVhr9az24xwixPBEDGSZP+P0z3ZOnCmr9Oiep0MlJN2Mk+flIjD3iBS9BgyjS4g14dikMqnrYUPIjhQBhA==",
+ "version": "4.43.0",
+ "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.43.0.tgz",
+ "integrity": "sha512-dV4zBTT37or3Z3/8uD6rS8zvd4ZxPuG4EJVlqYIbZCGZCYttZm7xb9rlFLSk4rrsQHAeDYvudl7cquo0vWpHjg==",
"dev": true,
"funding": [
{
@@ -1882,7 +1885,7 @@
"@bazel/runfiles": "^6.5.0",
"jszip": "^3.10.1",
"tmp": "^0.2.5",
- "ws": "^8.19.0"
+ "ws": "^8.20.0"
},
"engines": {
"node": ">= 20.0.0"
@@ -2120,14 +2123,14 @@
"dev": true
},
"node_modules/wait-on": {
- "version": "9.0.4",
- "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.4.tgz",
- "integrity": "sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==",
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.5.tgz",
+ "integrity": "sha512-qgnbHDfDTRIp73ANEJNRW/7kn8CrDUcvZz18xotJQku/P4saTGkbIzvnMZebPmVvVNUiRq1qWAPyqCH+W4H8KA==",
"license": "MIT",
"dependencies": {
- "axios": "^1.13.5",
- "joi": "^18.0.2",
- "lodash": "^4.17.23",
+ "axios": "^1.15.0",
+ "joi": "^18.1.2",
+ "lodash": "^4.18.1",
"minimist": "^1.2.8",
"rxjs": "^7.8.2"
},
@@ -2256,9 +2259,9 @@
}
},
"node_modules/ws": {
- "version": "8.19.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
- "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
+ "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/integration-tests/package.json b/integration-tests/package.json
index e81a990..2c6b9c5 100644
--- a/integration-tests/package.json
+++ b/integration-tests/package.json
@@ -12,11 +12,11 @@
"license": "AGPL-3.0-only",
"devDependencies": {
"chai": "^6.2.2",
- "eslint": "^10.1.0",
+ "eslint": "^10.3.0",
"mocha": "^11.7.5",
- "selenium-webdriver": "^4.41.0"
+ "selenium-webdriver": "^4.43.0"
},
"dependencies": {
- "wait-on": "^9.0.4"
+ "wait-on": "^9.0.5"
}
}
diff --git a/service/go.mod b/service/go.mod
index 12b7bb5..9cade73 100644
--- a/service/go.mod
+++ b/service/go.mod
@@ -5,13 +5,13 @@ go 1.25.7
exclude google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884
require (
- connectrpc.com/connect v1.19.1
+ connectrpc.com/connect v1.19.2
github.com/Masterminds/semver v1.5.0
github.com/MicahParks/keyfunc/v3 v3.8.0
github.com/PaesslerAG/jsonpath v0.1.1
github.com/alexedwards/argon2id v1.0.0
- github.com/bufbuild/buf v1.66.1
- github.com/fsnotify/fsnotify v1.9.0
+ github.com/bufbuild/buf v1.69.0
+ github.com/fsnotify/fsnotify v1.10.1
github.com/fzipp/gocyclo v0.6.0
github.com/go-critic/go-critic v0.14.3
github.com/golang-jwt/jwt/v5 v5.3.1
@@ -27,9 +27,9 @@ require (
github.com/sirupsen/logrus v1.9.4
github.com/stretchr/testify v1.11.1
go.akshayshah.org/connectproto v0.6.0
- golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
+ golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a
golang.org/x/oauth2 v0.36.0
- golang.org/x/sys v0.42.0
+ golang.org/x/sys v0.44.0
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
)
@@ -37,19 +37,19 @@ require (
require (
buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.11-20250718181942-e35f9b667443.1 // indirect
buf.build/gen/go/bufbuild/protodescriptor/protocolbuffers/go v1.36.11-20250109164928-1da0de137947.1 // indirect
- buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 // indirect
- buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20260126144947-819582968857.2 // indirect
- buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260126144947-819582968857.1 // indirect
+ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 // indirect
+ buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.2-20260507063250-43b0c5a6cd08.1 // indirect
+ buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260507063250-43b0c5a6cd08.1 // indirect
buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 // indirect
- buf.build/go/app v0.2.0 // indirect
- buf.build/go/bufplugin v0.9.0 // indirect
+ buf.build/go/app v0.2.1-0.20260407195847-833f8f978cda // indirect
+ buf.build/go/bufplugin v0.10.0 // indirect
buf.build/go/bufprivateusage v0.1.0 // indirect
buf.build/go/interrupt v1.1.0 // indirect
- buf.build/go/protovalidate v1.1.3 // indirect
+ buf.build/go/protovalidate v1.2.0 // indirect
buf.build/go/protoyaml v0.6.0 // indirect
buf.build/go/spdx v0.2.0 // indirect
- buf.build/go/standard v0.1.0 // indirect
- cel.dev/expr v0.25.1 // indirect
+ buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5 // indirect
+ cel.dev/expr v0.25.2 // indirect
connectrpc.com/otelconnect v0.9.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/MicahParks/jwkset v0.11.0 // indirect
@@ -57,8 +57,8 @@ require (
github.com/PaesslerAG/gval v1.2.4 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
- github.com/bufbuild/protocompile v0.14.2-0.20260306221011-519528254156 // indirect
- github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 // indirect
+ github.com/bufbuild/protocompile v0.14.2-0.20260429155904-12ef1ef2ce91 // indirect
+ github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cli/browser v1.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
@@ -68,11 +68,11 @@ require (
github.com/cristalhq/acmd v0.12.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
- github.com/docker/cli v29.3.0+incompatible // indirect
+ github.com/docker/cli v29.4.3+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.5.2+incompatible // indirect
- github.com/docker/docker-credential-helpers v0.9.5 // indirect
- github.com/docker/go-connections v0.6.0 // indirect
+ github.com/docker/docker-credential-helpers v0.9.7 // indirect
+ github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-chi/chi/v5 v5.2.5 // indirect
@@ -88,27 +88,29 @@ require (
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gofrs/flock v0.13.0 // indirect
- github.com/google/cel-go v0.27.0 // indirect
+ github.com/google/cel-go v0.28.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
- github.com/google/go-containerregistry v0.21.3 // indirect
+ github.com/google/go-containerregistry v0.21.5 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdx/go-netrc v1.0.0 // indirect
- github.com/klauspost/compress v1.18.5 // indirect
+ github.com/klauspost/compress v1.18.6 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
+ github.com/moby/moby/api v1.54.2 // indirect
+ github.com/moby/moby/client v0.4.1 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/morikuni/aec v1.1.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
- github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect
+ github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
@@ -131,33 +133,33 @@ require (
github.com/stoewer/go-strcase v1.3.1 // indirect
github.com/tetratelabs/wazero v1.11.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect
- github.com/vbatts/tar-split v0.12.2 // indirect
+ github.com/vbatts/tar-split v0.12.3 // indirect
go.lsp.dev/jsonrpc2 v0.10.0 // indirect
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
go.lsp.dev/protocol v0.12.0 // indirect
go.lsp.dev/uri v0.3.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
- go.opentelemetry.io/otel v1.42.0 // indirect
- go.opentelemetry.io/otel/metric v1.42.0 // indirect
- go.opentelemetry.io/otel/trace v1.42.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
+ go.opentelemetry.io/otel v1.43.0 // indirect
+ go.opentelemetry.io/otel/metric v1.43.0 // indirect
+ go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
- go.uber.org/zap v1.27.1 // indirect
+ go.uber.org/zap v1.28.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/crypto v0.49.0 // indirect
- golang.org/x/exp/typeparams v0.0.0-20260312153236-7ab1446f8b90 // indirect
- golang.org/x/mod v0.34.0 // indirect
- golang.org/x/net v0.52.0 // indirect
+ golang.org/x/crypto v0.51.0 // indirect
+ golang.org/x/exp/typeparams v0.0.0-20260508232706-74f9aab9d74a // indirect
+ golang.org/x/mod v0.36.0 // indirect
+ golang.org/x/net v0.54.0 // indirect
golang.org/x/sync v0.20.0 // indirect
- golang.org/x/term v0.41.0 // indirect
- golang.org/x/text v0.35.0 // indirect
+ golang.org/x/term v0.43.0 // indirect
+ golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.15.0 // indirect
- golang.org/x/tools v0.43.0 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
- google.golang.org/grpc v1.75.1 // indirect
+ golang.org/x/tools v0.45.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect
+ google.golang.org/grpc v1.79.3 // indirect
mvdan.cc/xurls/v2 v2.6.0 // indirect
pluginrpc.com/pluginrpc v0.5.0 // indirect
)
diff --git a/service/go.sum b/service/go.sum
index 32f2d79..874c27e 100644
--- a/service/go.sum
+++ b/service/go.sum
@@ -6,24 +6,34 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-202512091757
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 h1:PMmTMyvHScV9Mn8wc6ASge9uRcHy0jtqPd+fM35LmsQ=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 h1:s6hzCXtND/ICdGPTMGk7C+/BFlr2Jg5GyH0NKf4XGXg=
+buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251202164234-62b14f0b533c.2 h1:eQ6XRVUaYYZFOZvBsyrOYLWbw6464s5dVnHscxa0b8w=
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20251202164234-62b14f0b533c.2/go.mod h1:omxVRch3jEPMINnUipLsuRWoEhND6LPXELKBG7xzyDw=
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20260122161138-ab4e39a3c3bc.2 h1:cMzWbIukJ5uk1M58CtqmBE7Ojacg/t2nAg4AbS78uX8=
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20260122161138-ab4e39a3c3bc.2/go.mod h1:GL3rFhQQsaI3PCBa0y5X71UHs6q5E/Xf9Q8WXBxE7a8=
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20260126144947-819582968857.2 h1:XPrWCd9ydEo5Ofv1aNJVJaxndMXLQjRO9vVzsJG3jL8=
buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.1-20260126144947-819582968857.2/go.mod h1:mpsjeEaxOYPIJV2cz4IagLghZufRvx+NPVtInjEeoQ8=
+buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.2-20260507063250-43b0c5a6cd08.1 h1:DcwtSdaY9CwXwPSOneDxJ/B0OCAgNPQQaQxAr/pTHvc=
+buf.build/gen/go/bufbuild/registry/connectrpc/go v1.19.2-20260507063250-43b0c5a6cd08.1/go.mod h1:WjOwVG7wzFSwEkjCjHVRWEOdGYyON/TQYPabl7N2VGI=
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20251202164234-62b14f0b533c.1 h1:PdfIJUbUVKdajMVYuMdvr2Wvo+wmzGnlPEYA4bhFaWI=
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20251202164234-62b14f0b533c.1/go.mod h1:1JJi9jvOqRxSMa+JxiZSm57doB+db/1WYCIa2lHfc40=
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260122161138-ab4e39a3c3bc.1 h1:yWmrELGX6l1GphG9kPVcrMQLjWfXGI5bLDxwE+SfbDw=
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260122161138-ab4e39a3c3bc.1/go.mod h1:1JJi9jvOqRxSMa+JxiZSm57doB+db/1WYCIa2lHfc40=
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260126144947-819582968857.1 h1:Yreby6Ypa58wdQUEm9Fnc5g8n/jP487Dq3aK5yBYwfk=
buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260126144947-819582968857.1/go.mod h1:1JJi9jvOqRxSMa+JxiZSm57doB+db/1WYCIa2lHfc40=
+buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260507063250-43b0c5a6cd08.1 h1:QK2GkcPxqh2oG5mTMAHejculun8nxto+p7mlgh8fPTM=
+buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.36.11-20260507063250-43b0c5a6cd08.1/go.mod h1:1JJi9jvOqRxSMa+JxiZSm57doB+db/1WYCIa2lHfc40=
buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1 h1:iGPvEJltOXUMANWf0zajcRcbiOXLD90ZwPUFvbcuv6Q=
buf.build/gen/go/pluginrpc/pluginrpc/protocolbuffers/go v1.36.11-20241007202033-cf42259fcbfc.1/go.mod h1:nWVKKRA29zdt4uvkjka3i/y4mkrswyWwiu0TbdX0zts=
buf.build/go/app v0.2.0 h1:NYaH13A+RzPb7M5vO8uZYZ2maBZI5+MS9A9tQm66fy8=
buf.build/go/app v0.2.0/go.mod h1:0XVOYemubVbxNXVY0DnsVgWeGkcbbAvjDa1fmhBC+Wo=
+buf.build/go/app v0.2.1-0.20260407195847-833f8f978cda h1:eysSyjrJtkxU1A/9+Kv+1Mwq9K6BYBw+STIOVsZ256Y=
+buf.build/go/app v0.2.1-0.20260407195847-833f8f978cda/go.mod h1:V32mBaPWsfq6REAeZvvs/rQl7ZCl9Dn7eW1BBrmH0GQ=
buf.build/go/bufplugin v0.9.0 h1:ktZJNP3If7ldcWVqh46XKeiYJVPxHQxCfjzVQDzZ/lo=
buf.build/go/bufplugin v0.9.0/go.mod h1:Z0CxA3sKQ6EPz/Os4kJJneeRO6CjPeidtP1ABh5jPPY=
+buf.build/go/bufplugin v0.10.0 h1:vZBX0mq9as5UIBug8U+/DkGRaHNlM/HVOw59O8fvOIU=
+buf.build/go/bufplugin v0.10.0/go.mod h1:ax7obVurKDH1I2nR4pFTS+TE6K3kZhTmwDCN2YgdV8I=
buf.build/go/bufprivateusage v0.1.0 h1:SzCoCcmzS3zyXHEXHeSQhGI7OTkgtljoknLzsUz9Gg4=
buf.build/go/bufprivateusage v0.1.0/go.mod h1:GlCCJ3VVF7EqqU0CoRmo1FzAwwaKymEWSr+ty69xU5w=
buf.build/go/interrupt v1.1.0 h1:olBuhgv9Sav4/9pkSLoxgiOsZDgM5VhRhvRpn3DL0lE=
@@ -34,16 +44,24 @@ buf.build/go/protovalidate v1.1.2 h1:83vYHoY8f34hB8MeitGaYE3CGVPFxwdEUuskh5qQpA0
buf.build/go/protovalidate v1.1.2/go.mod h1:Ez3z+w4c+wG+EpW8ovgZaZPnPl2XVF6kaxgcv1NG/QE=
buf.build/go/protovalidate v1.1.3 h1:m2GVEgQWd7rk+vIoAZ+f0ygGjvQTuqPQapBBdcpWVPE=
buf.build/go/protovalidate v1.1.3/go.mod h1:9XIuohWz+kj+9JVn3WQneHA5LZP50mjvneZMnbLkiIE=
+buf.build/go/protovalidate v1.2.0 h1:DQVrUWkmGTBij+kOYv/x2LLxwcLaGKMdzShj1/6/3H0=
+buf.build/go/protovalidate v1.2.0/go.mod h1:7rYiQEhqvAipoazpVNBBH2S2f8bjG4huMVy1V2Yofn4=
buf.build/go/protoyaml v0.6.0 h1:Nzz1lvcXF8YgNZXk+voPPwdU8FjDPTUV4ndNTXN0n2w=
buf.build/go/protoyaml v0.6.0/go.mod h1:RgUOsBu/GYKLDSIRgQXniXbNgFlGEZnQpRAUdLAFV2Q=
buf.build/go/spdx v0.2.0 h1:IItqM0/cMxvFJJumcBuP8NrsIzMs/UYjp/6WSpq8LTw=
buf.build/go/spdx v0.2.0/go.mod h1:bXdwQFem9Si3nsbNy8aJKGPoaPi5DKwdeEp5/ArZ6w8=
buf.build/go/standard v0.1.0 h1:g98T9IyvAl0vS3Pq8iVk6Cvj2ZiFvoUJRtfyGa0120U=
buf.build/go/standard v0.1.0/go.mod h1:PiqpHz/7ZFq+kqvYhc/SK3lxFIB9N/aiH2CFC2JHIQg=
+buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5 h1:njYKSWoLiq2i5O7y2bPPU2Yzp7iAU0Wk9KJ2OoAhNiU=
+buf.build/go/standard v0.1.1-0.20260325175353-2b287e071df5/go.mod h1:DQmodNT9EHX94WzUaWiZK+/4EaFa/xZTc1gzfCxZVXU=
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
+cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
+cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
+connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo=
+connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
connectrpc.com/otelconnect v0.8.0 h1:a4qrN4H8aEE2jAoCxheZYYfEjXMgVPyL9OzPQLBEFXU=
connectrpc.com/otelconnect v0.8.0/go.mod h1:AEkVLjCPXra+ObGFCOClcJkNjS7zPaQSqvO0lCyjfZc=
connectrpc.com/otelconnect v0.9.0 h1:NggB3pzRC3pukQWaYbRHJulxuXvmCKCKkQ9hbrHAWoA=
@@ -90,6 +108,8 @@ github.com/bufbuild/buf v1.66.0 h1:6kksYJpu6r45bvPJSTwNSwRqiAjrwB9YyU7skjNzFVo=
github.com/bufbuild/buf v1.66.0/go.mod h1:tWVlwtIPZ7kzlCB9D0hbbfrroT0GNCybPdPQXq1i1Ac=
github.com/bufbuild/buf v1.66.1 h1:wqmmU+6uoxB/eYDOmXq2To4qEXvOJN7gR6L9AxrPL1E=
github.com/bufbuild/buf v1.66.1/go.mod h1:Vd3ELm8IePWaDJaS9FLy94FFOnLrjLi4mDxmXtw9Xio=
+github.com/bufbuild/buf v1.69.0 h1:q1YTnHJISHuoeUdmsuC9u+nb9rV8glM/TOsPNEteEzg=
+github.com/bufbuild/buf v1.69.0/go.mod h1:Q3KRCXSanDCMFs2zL/MqUwUQV0OUqs23P2sy58CW0nc=
github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e h1:LQA+1MyiPkolGHJGC2GMDC5Xu+0RDVH6jGMKech7Exs=
github.com/bufbuild/protocompile v0.14.2-0.20251223142729-db46c1b9d34e/go.mod h1:5UUj46Eu+U+C59C5N6YilaMI7WWfP2bW9xGcOkme2DI=
github.com/bufbuild/protocompile v0.14.2-0.20260105175043-4d8d90b1c6b8 h1:cQYwUyAzyMmYr7AyJU1C6pVCpUrJJBkmx7UunZosxxs=
@@ -102,8 +122,12 @@ github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113 h1:nxt1Qh
github.com/bufbuild/protocompile v0.14.2-0.20260202185951-d02d3732d113/go.mod h1:cxhE8h+14t0Yxq2H9MV/UggzQ1L0gh0t2tJobITWsBE=
github.com/bufbuild/protocompile v0.14.2-0.20260306221011-519528254156 h1:XOfIInPVufMjifwy3fli8qQVsGHWVCDVY/zp6elAOsY=
github.com/bufbuild/protocompile v0.14.2-0.20260306221011-519528254156/go.mod h1:cxhE8h+14t0Yxq2H9MV/UggzQ1L0gh0t2tJobITWsBE=
+github.com/bufbuild/protocompile v0.14.2-0.20260429155904-12ef1ef2ce91 h1:RPIMBLTMx/CRy0NVyb6yJDlGx2Vo84FsU+kAh46zqIA=
+github.com/bufbuild/protocompile v0.14.2-0.20260429155904-12ef1ef2ce91/go.mod h1:DhgqsRznX/F0sGkUYtTQJRP+q8xMReQRQ3qr+n1opWU=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1 h1:V1xulAoqLqVg44rY97xOR+mQpD2N+GzhMHVwJ030WEU=
github.com/bufbuild/protoplugin v0.0.0-20250218205857-750e09ce93e1/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
+github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b h1:b7wvo9ZhjLzCp7tGbOUMvgtYTnd33zGSAmMxcdxMnhQ=
+github.com/bufbuild/protoplugin v0.0.0-20260414125817-25d1d281b46b/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -140,6 +164,8 @@ github.com/docker/cli v29.2.1+incompatible h1:n3Jt0QVCN65eiVBoUTZQM9mcQICCJt3akW
github.com/docker/cli v29.2.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk=
github.com/docker/cli v29.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v29.4.3+incompatible h1:u+UliYm2J/rYrIh2FqHQg32neRG8GjbvNuwQRTzGspU=
+github.com/docker/cli v29.4.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
@@ -148,14 +174,20 @@ github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
+github.com/docker/docker-credential-helpers v0.9.7 h1:jaPIxEIDz5bQeghNAdzz0ETwMMnM4vzjZlxz3pWP4JA=
+github.com/docker/docker-credential-helpers v0.9.7/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
+github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
+github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
+github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
@@ -206,6 +238,8 @@ github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ=
github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=
+github.com/google/cel-go v0.28.0 h1:KjSWstCpz/MN5t4a8gnGJNIYUsJRpdi/r97xWDphIQc=
+github.com/google/cel-go v0.28.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -216,6 +250,8 @@ github.com/google/go-containerregistry v0.21.0 h1:ocqxUOczFwAZQBMNE7kuzfqvDe0VWo
github.com/google/go-containerregistry v0.21.0/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0=
github.com/google/go-containerregistry v0.21.3 h1:Xr+yt3VvwOOn/5nJzd7UoOhwPGiPkYW0zWDLLUXqAi4=
github.com/google/go-containerregistry v0.21.3/go.mod h1:D5ZrJF1e6dMzvInpBPuMCX0FxURz7GLq2rV3Us9aPkc=
+github.com/google/go-containerregistry v0.21.5 h1:KTJG9Pn/jC0VdZR6ctV3/jcN+q6/Iqlx0sTVz3ywZlM=
+github.com/google/go-containerregistry v0.21.5/go.mod h1:ySvMuiWg+dOsRW0Hw8GYwfMwBlNRTmpYBFJPlkco5zU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
@@ -238,6 +274,8 @@ github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
+github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
+github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
@@ -266,6 +304,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
+github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -274,6 +314,10 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
+github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
+github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY=
+github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
@@ -294,6 +338,8 @@ github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQ
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE=
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM=
+github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -375,6 +421,8 @@ github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
+github.com/vbatts/tar-split v0.12.3 h1:Cd46rkGXI3Td4yrVNwU8ripbxFaQbmesqhjBUUYAJSw=
+github.com/vbatts/tar-split v0.12.3/go.mod h1:sQOc6OlqGCr7HkGx/IDBeKiTIvqhmj8KffNhEXG4Nq0=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.akshayshah.org/attest v1.0.0 h1:RVGitcLbAO5i4PIJJDztZ/E9qQ8VSp1PS5PnR4Btg0c=
go.akshayshah.org/attest v1.0.0/go.mod h1:PnWzcW5j9dkyGwTlBmUsYpPnHG0AUPrs1RQ+HrldWO0=
@@ -396,12 +444,16 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
+go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
+go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
@@ -415,20 +467,26 @@ go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
+go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
+go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
+go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
+go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
+go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
+go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
@@ -440,6 +498,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
+go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
@@ -457,6 +517,8 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
+golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
+golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
@@ -467,6 +529,8 @@ golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDME
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
+golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
+golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20251219203646-944ab1f22d93 h1:PbC785RGO6yPO051ItgbG/adwoKRWC0VS7kXXeD/iqk=
@@ -477,6 +541,8 @@ golang.org/x/exp/typeparams v0.0.0-20260212183809-81e46e3db34a h1:n3SZDk8iNpMasC
golang.org/x/exp/typeparams v0.0.0-20260212183809-81e46e3db34a/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo=
golang.org/x/exp/typeparams v0.0.0-20260312153236-7ab1446f8b90 h1:cfW8UCYSVdPblxA7qQe3o5Iad55Vsx4BFmuGS9RNOmc=
golang.org/x/exp/typeparams v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo=
+golang.org/x/exp/typeparams v0.0.0-20260508232706-74f9aab9d74a h1:H06+n8uULVXJdhbdJ9+40jLzRcAPQP2h1UXcs01jzsk=
+golang.org/x/exp/typeparams v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
@@ -487,6 +553,8 @@ golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
+golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
+golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -500,6 +568,8 @@ golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
+golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
@@ -532,6 +602,8 @@ golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
+golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -545,6 +617,8 @@ golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
+golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
+golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -559,6 +633,8 @@ golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
+golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
+golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
@@ -575,6 +651,8 @@ golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
+golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
+golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
@@ -589,6 +667,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
+google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1:U8orV30l6KpDsi9dxU0CoJZGbjS8EEpw+6ba+XwGPQA=
+google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
@@ -601,8 +681,12 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
+google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
+google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
From 24e02e674f7181b22f2b4e29aa334dcd3b2f4af8 Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 10 May 2026 21:49:56 +0100
Subject: [PATCH 072/148] Refetch Init after EventConfigChanged for live UI
updates
Co-authored-by: Cursor
---
frontend/js/websocket.js | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/frontend/js/websocket.js b/frontend/js/websocket.js
index 0fffb88..bfb6345 100644
--- a/frontend/js/websocket.js
+++ b/frontend/js/websocket.js
@@ -50,6 +50,27 @@ async function reconnectWebsocket () {
}, RECONNECT_DELAY_MS)
}
+async function refreshInitAfterConfigChange () {
+ if (!window.client) {
+ return
+ }
+
+ try {
+ window.initResponse = await window.client.init({})
+
+ if (typeof window.updateHeaderFromInit === 'function') {
+ window.updateHeaderFromInit()
+ }
+ } catch (err) {
+ console.error('Failed to refresh config from server after EventConfigChanged:', err)
+ }
+}
+
+async function handleConfigChangedEvent (j) {
+ await refreshInitAfterConfigChange()
+ window.dispatchEvent(j)
+}
+
function handleEvent (msg) {
const typeName = msg.event.value.$typeName.replace('olivetin.api.v1.', '')
@@ -57,8 +78,12 @@ function handleEvent (msg) {
j.payload = msg.event.value
switch (typeName) {
- case 'EventOutputChunk':
case 'EventConfigChanged':
+ handleConfigChangedEvent(j).catch((err) => {
+ console.error('EventConfigChanged handler failed:', err)
+ })
+ break
+ case 'EventOutputChunk':
case 'EventEntityChanged':
window.dispatchEvent(j)
break
From c132eacc00e708fc130bc3e7311a150a80b1b76c Mon Sep 17 00:00:00 2001
From: jamesread
Date: Sun, 10 May 2026 21:58:48 +0100
Subject: [PATCH 073/148] docs: add Antora site sources under docs/ with CI
smoke build
Move the docs.olivetin.app AsciiDoc component into this repository, add
local Antora playbooks for contributors and CI, and document the split
between this repo and the docs build repository.
Co-authored-by: Cursor
---
.github/workflows/docs-antora.yml | 34 ++
.gitignore | 4 +-
CONTRIBUTING.adoc | 1 +
README.md | 2 +
docs/antora.yml | 15 +
docs/modules/ROOT/check_chevron_links.py | 40 ++
docs/modules/ROOT/check_no_h1.py | 24 ++
docs/modules/ROOT/check_unnavigable.py | 25 ++
.../action_customization/icons/config.yaml | 15 +
docs/modules/ROOT/examples/k8s_configmap.yml | 9 +
docs/modules/ROOT/examples/k8s_deployment.yml | 37 ++
docs/modules/ROOT/examples/k8s_ingress.yml | 21 +
.../etc/npm-docker-compose.yml | 19 +
.../etc/reverse_proxy_nginx_dns.conf | 25 ++
.../config/config.yaml | 48 +++
.../config/containers.json | 2 +
.../solutions/directory-actions/config.yaml | 32 ++
.../heating-control-panel/configs/config.yaml | 28 ++
.../configs/heating.yaml | 2 +
.../solutions/primitive-password/password.js | 35 ++
.../systemd-control-panel/config/config.yaml | 36 ++
.../config/systemd_units.json | 4 +
.../ROOT/examples/solutions/wol/config.yaml | 10 +
.../examples/solutions/wol/config_docker.yaml | 6 +
.../ROOT/images/action-button-iconify.png | Bin 0 -> 6574 bytes
.../ROOT/images/action-confirmation.png | Bin 0 -> 12592 bytes
.../ROOT/images/additionalNavigationLinks.png | Bin 0 -> 3255 bytes
docs/modules/ROOT/images/arg-datetime.png | Bin 0 -> 28036 bytes
.../ROOT/images/arg-suggestions-chrome.png | Bin 0 -> 26304 bytes
.../ROOT/images/arg-suggestions-firefox.png | Bin 0 -> 21820 bytes
.../ROOT/images/args-choices-entities.png | Bin 0 -> 15347 bytes
.../modules/ROOT/images/args-choices-exec.png | Bin 0 -> 26982 bytes
.../ROOT/images/args-multiline-text.png | Bin 0 -> 11654 bytes
docs/modules/ROOT/images/args1.png | Bin 0 -> 9255 bytes
docs/modules/ROOT/images/args2.png | Bin 0 -> 19330 bytes
docs/modules/ROOT/images/args3.png | Bin 0 -> 22165 bytes
docs/modules/ROOT/images/args4.png | Bin 0 -> 17603 bytes
docs/modules/ROOT/images/authentik_login.png | Bin 0 -> 12967 bytes
docs/modules/ROOT/images/authentik_login2.png | Bin 0 -> 350892 bytes
docs/modules/ROOT/images/authentik_login3.png | Bin 0 -> 4181 bytes
.../modules/ROOT/images/authentik_new_app.png | Bin 0 -> 66212 bytes
.../ROOT/images/authentik_provider_config.png | Bin 0 -> 76047 bytes
.../images/authentik_provider_secrets.png | Bin 0 -> 87597 bytes
.../ROOT/images/authentik_select_oauth2.png | Bin 0 -> 106028 bytes
docs/modules/ROOT/images/blocked.png | Bin 0 -> 26200 bytes
.../modules/ROOT/images/dashboard-display.png | Bin 0 -> 16959 bytes
.../dashboard-heating-control-panel.png | Bin 0 -> 21920 bytes
docs/modules/ROOT/images/dashboard.png | Bin 0 -> 81959 bytes
docs/modules/ROOT/images/defaultUiHideNav.png | Bin 0 -> 50150 bytes
docs/modules/ROOT/images/defaultUiWithNav.png | Bin 0 -> 51689 bytes
docs/modules/ROOT/images/diagnostics.png | Bin 0 -> 51079 bytes
.../images/directory-actions-screenshot.png | Bin 0 -> 15236 bytes
docs/modules/ROOT/images/exampleIcons.png | Bin 0 -> 17929 bytes
docs/modules/ROOT/images/executionButtons.png | Bin 0 -> 8459 bytes
docs/modules/ROOT/images/executionDialog.png | Bin 0 -> 30060 bytes
docs/modules/ROOT/images/fieldset.png | Bin 0 -> 21279 bytes
docs/modules/ROOT/images/flashyButton.png | Bin 0 -> 7089 bytes
docs/modules/ROOT/images/folders.png | Bin 0 -> 33739 bytes
docs/modules/ROOT/images/gitops.png | Bin 0 -> 34590 bytes
docs/modules/ROOT/images/hacs-custom-repo.png | Bin 0 -> 13003 bytes
docs/modules/ROOT/images/hacs-download.png | Bin 0 -> 43351 bytes
docs/modules/ROOT/images/hacs-dropdown.png | Bin 0 -> 18409 bytes
docs/modules/ROOT/images/hacs-search.png | Bin 0 -> 34853 bytes
.../ROOT/images/hass-add-integration.png | Bin 0 -> 6478 bytes
docs/modules/ROOT/images/hass-buttons.png | Bin 0 -> 4731 bytes
.../images/hass-configure-integration.png | Bin 0 -> 6448 bytes
.../ROOT/images/hass-devices-and-services.png | Bin 0 -> 19423 bytes
docs/modules/ROOT/images/hassButtonSetup.png | Bin 0 -> 72229 bytes
docs/modules/ROOT/images/hassConfigYaml.png | Bin 0 -> 49511 bytes
docs/modules/ROOT/images/hassFileEditor.png | Bin 0 -> 32421 bytes
.../ROOT/images/hassFileEditorConfig.png | Bin 0 -> 91571 bytes
docs/modules/ROOT/images/hello-world.png | Bin 0 -> 6653 bytes
docs/modules/ROOT/images/iconify.png | Bin 0 -> 76599 bytes
docs/modules/ROOT/images/icons/Discord.png | Bin 0 -> 1736 bytes
docs/modules/ROOT/images/icons/GitHub.png | Bin 0 -> 1186 bytes
.../ROOT/images/icons/OliveTinLogo.png | Bin 0 -> 14762 bytes
docs/modules/ROOT/images/maxRate.png | Bin 0 -> 74577 bytes
docs/modules/ROOT/images/mrGreenAction.png | Bin 0 -> 8355 bytes
docs/modules/ROOT/images/mre.png | Bin 0 -> 16347 bytes
docs/modules/ROOT/images/mrgreen.gif | Bin 0 -> 4266 bytes
docs/modules/ROOT/images/npm.png | Bin 0 -> 60865 bytes
docs/modules/ROOT/images/page-title.png | Bin 0 -> 14738 bytes
docs/modules/ROOT/images/pocketid.png | Bin 0 -> 42025 bytes
docs/modules/ROOT/images/popupOutputOnly.png | Bin 0 -> 41317 bytes
docs/modules/ROOT/images/portDiagram.png | Bin 0 -> 43974 bytes
docs/modules/ROOT/images/sidebar.png | Bin 0 -> 21993 bytes
docs/modules/ROOT/images/snapshot-archive.png | Bin 0 -> 91556 bytes
.../modules/ROOT/images/snapshot-download.png | Bin 0 -> 110041 bytes
docs/modules/ROOT/images/snapshots.png | Bin 0 -> 131490 bytes
.../ROOT/images/solution-k8s-hosted.png | Bin 0 -> 69620 bytes
.../images/solution-systemd-control-panel.png | Bin 0 -> 44979 bytes
.../container-control-panel/preview.png | Bin 0 -> 34365 bytes
.../ROOT/images/solutions/wol/preview.png | Bin 0 -> 19474 bytes
docs/modules/ROOT/images/ssh-diagram.png | Bin 0 -> 92918 bytes
.../ROOT/images/stream-deck/config.png | Bin 0 -> 15374 bytes
.../ROOT/images/stream-deck/inputs.png | Bin 0 -> 13174 bytes
.../ROOT/images/stream-deck/marketplace.png | Bin 0 -> 19153 bytes
.../modules/ROOT/images/stream-deck/panel.png | Bin 0 -> 10681 bytes
docs/modules/ROOT/images/timeoutLogs.png | Bin 0 -> 21183 bytes
docs/modules/ROOT/images/topbar.png | Bin 0 -> 23269 bytes
docs/modules/ROOT/nav.adoc | 174 ++++++++
.../action_customization/concurrency.adoc | 32 ++
.../enabledExpression.adoc | 189 +++++++++
.../pages/action_customization/icons.adoc | 144 +++++++
.../ROOT/pages/action_customization/ids.adoc | 16 +
.../pages/action_customization/intro.adoc | 33 ++
.../action_customization/popuponstart.adoc | 71 ++++
.../action_customization/ratelimiting.adoc | 30 ++
.../pages/action_customization/savelogs.adoc | 64 +++
.../pages/action_customization/timeouts.adoc | 30 ++
.../pages/action_customization/users.adoc | 26 ++
.../ROOT/pages/action_examples/ansible.adoc | 21 +
.../pages/action_examples/containers.adoc | 21 +
.../pages/action_examples/docker-proxy.adoc | 40 ++
.../ROOT/pages/action_examples/intro.adoc | 24 ++
.../ROOT/pages/action_examples/ping.adoc | 16 +
.../pages/action_examples/powershell.adoc | 17 +
.../ROOT/pages/action_examples/ssh-easy.adoc | 43 ++
.../pages/action_examples/ssh-manual.adoc | 159 ++++++++
.../action_examples/systemd_service.adoc | 24 ++
.../action_execution/aftercompletion.adoc | 39 ++
.../action_execution/create_your_first.adoc | 35 ++
.../pages/action_execution/oncalendar.adoc | 48 +++
.../ROOT/pages/action_execution/oncron.adoc | 62 +++
.../ROOT/pages/action_execution/ondemand.adoc | 5 +
.../pages/action_execution/onfilechanged.adoc | 27 ++
.../pages/action_execution/onfilecreated.adoc | 26 ++
.../pages/action_execution/onstartup.adoc | 32 ++
.../pages/action_execution/onwebhook.adoc | 320 +++++++++++++++
.../action_execution/onwebhook_github.adoc | 385 ++++++++++++++++++
.../pages/action_execution/shellvsexec.adoc | 44 ++
.../ROOT/pages/action_execution/triggers.adoc | 25 ++
.../advanced_configuration/config_envs.adoc | 54 +++
.../advanced_configuration/diagnostics.adoc | 38 ++
.../pages/advanced_configuration/intro.adoc | 32 ++
.../advanced_configuration/logs-actions.adoc | 79 ++++
.../advanced_configuration/logs-calendar.adoc | 55 +++
.../pages/advanced_configuration/logs.adoc | 51 +++
.../pages/advanced_configuration/ports.adoc | 6 +
.../advanced_configuration/prometheus.adoc | 40 ++
.../advanced_configuration/stylemods.adoc | 26 ++
.../advanced_configuration/timezones.adoc | 18 +
.../pages/advanced_configuration/webui.adoc | 184 +++++++++
docs/modules/ROOT/pages/api/intro.adoc | 27 ++
docs/modules/ROOT/pages/api/login.adoc | 28 ++
.../ROOT/pages/api/method_StartAction.adoc | 35 ++
.../pages/api/method_StartActionAndWait.adoc | 12 +
.../pages/api/method_StartActionByGet.adoc | 29 ++
.../api/method_StartActionByGetAndWait.adoc | 11 +
docs/modules/ROOT/pages/api/misc.adoc | 20 +
docs/modules/ROOT/pages/api/start_action.adoc | 105 +++++
docs/modules/ROOT/pages/args/env.adoc | 95 +++++
docs/modules/ROOT/pages/args/input.adoc | 50 +++
.../ROOT/pages/args/input_checkbox.adoc | 21 +
.../ROOT/pages/args/input_confirmation.adoc | 21 +
.../ROOT/pages/args/input_datetime.adoc | 30 ++
.../ROOT/pages/args/input_dropdown.adoc | 83 ++++
.../ROOT/pages/args/input_textarea.adoc | 21 +
docs/modules/ROOT/pages/args/intro.adoc | 23 ++
docs/modules/ROOT/pages/args/password.adoc | 19 +
docs/modules/ROOT/pages/args/regex.adoc | 25 ++
docs/modules/ROOT/pages/args/safety.adoc | 12 +
docs/modules/ROOT/pages/args/suggestions.adoc | 129 ++++++
docs/modules/ROOT/pages/args/types.adoc | 33 ++
docs/modules/ROOT/pages/config.adoc | 133 ++++++
.../ROOT/pages/dashboards/2-fieldsets.adoc | 42 ++
.../ROOT/pages/dashboards/3-folders.adoc | 52 +++
.../ROOT/pages/dashboards/4-displays.adoc | 63 +++
.../ROOT/pages/dashboards/5-output-views.adoc | 34 ++
.../ROOT/pages/dashboards/actions.adoc | 9 +
docs/modules/ROOT/pages/dashboards/css.adoc | 38 ++
.../pages/dashboards/entity-directories.adoc | 35 ++
.../ROOT/pages/dashboards/examples.adoc | 7 +
.../ROOT/pages/dashboards/inline-actions.adoc | 18 +
docs/modules/ROOT/pages/dashboards/intro.adoc | 96 +++++
.../modules/ROOT/pages/entities/examples.adoc | 9 +
docs/modules/ROOT/pages/entities/intro.adoc | 35 ++
docs/modules/ROOT/pages/entities/json.adoc | 12 +
docs/modules/ROOT/pages/entities/yaml.adoc | 22 +
docs/modules/ROOT/pages/index.adoc | 42 ++
docs/modules/ROOT/pages/install/bsd.adoc | 7 +
.../ROOT/pages/install/choose_package.adoc | 46 +++
.../modules/ROOT/pages/install/container.adoc | 28 ++
.../pages/install/container_vs_service.adoc | 21 +
.../ROOT/pages/install/docker_compose.adoc | 77 ++++
docs/modules/ROOT/pages/install/helm.adoc | 87 ++++
docs/modules/ROOT/pages/install/intro.adoc | 42 ++
docs/modules/ROOT/pages/install/k8s.adoc | 44 ++
.../ROOT/pages/install/linux_alpine.adoc | 18 +
.../ROOT/pages/install/linux_arch.adoc | 23 ++
.../modules/ROOT/pages/install/linux_deb.adoc | 20 +
.../ROOT/pages/install/linux_fedora.adoc | 27 ++
.../ROOT/pages/install/linux_manjaro.adoc | 18 +
.../modules/ROOT/pages/install/linux_rpm.adoc | 18 +
docs/modules/ROOT/pages/install/macos.adoc | 8 +
.../ROOT/pages/install/podmandocker.adoc | 32 ++
docs/modules/ROOT/pages/install/targz.adoc | 25 ++
docs/modules/ROOT/pages/install/windows.adoc | 12 +
.../ROOT/pages/install/windows_service.adoc | 65 +++
.../homeassistant-integration.adoc | 39 ++
.../pages/integrations/homeassistant.adoc | 62 +++
docs/modules/ROOT/pages/integrations/n8n.adoc | 15 +
.../ROOT/pages/integrations/stream-deck.adoc | 33 ++
.../reference/containerInstallPackages.adoc | 37 ++
.../ROOT/pages/reference/contribute.adoc | 14 +
.../reference/donations_and_sponsorship.adoc | 11 +
.../ROOT/pages/reference/exitCodes.adoc | 20 +
.../ROOT/pages/reference/includes.adoc | 26 ++
.../pages/reference/multiple_instances.adoc | 59 +++
.../ROOT/pages/reference/network-ports.adoc | 56 +++
.../pages/reference/reference_snapshots.adoc | 25 ++
.../reference_themes_for_developers.adoc | 53 +++
.../reference/reference_themes_for_users.adoc | 96 +++++
.../ROOT/pages/reference/release_policy.adoc | 29 ++
.../ROOT/pages/reference/updateChecks.adoc | 20 +
.../ROOT/pages/reference/updateTracking.adoc | 121 ++++++
.../ROOT/pages/reference/version_display.adoc | 70 ++++
.../ROOT/pages/reverse-proxies/apache.adoc | 42 ++
.../ROOT/pages/reverse-proxies/caddy.adoc | 35 ++
.../ROOT/pages/reverse-proxies/haproxy.adoc | 30 ++
.../ROOT/pages/reverse-proxies/intro.adoc | 50 +++
.../ROOT/pages/reverse-proxies/nginx.adoc | 42 ++
.../reverse-proxies/nginx_proxy_manager.adoc | 29 ++
.../ROOT/pages/reverse-proxies/traefik.adoc | 40 ++
docs/modules/ROOT/pages/security/acl.adoc | 152 +++++++
.../modules/ROOT/pages/security/concepts.adoc | 34 ++
.../security/content_security_policy.adoc | 54 +++
.../ROOT/pages/security/design_choices.adoc | 21 +
.../security/example_login_required.adoc | 61 +++
.../security/example_some_admin_actions.adoc | 52 +++
.../modules/ROOT/pages/security/examples.adoc | 10 +
docs/modules/ROOT/pages/security/jwt.adoc | 22 +
.../modules/ROOT/pages/security/jwt_hmac.adoc | 100 +++++
.../modules/ROOT/pages/security/jwt_keys.adoc | 51 +++
docs/modules/ROOT/pages/security/local.adoc | 94 +++++
docs/modules/ROOT/pages/security/oauth2.adoc | 106 +++++
.../ROOT/pages/security/oauth2_authelia.adoc | 79 ++++
.../ROOT/pages/security/oauth2_authentik.adoc | 203 +++++++++
.../ROOT/pages/security/oauth2_pocketid.adoc | 55 +++
.../ROOT/pages/security/trusted_header.adoc | 34 ++
.../cloudflare_access_tunnel/index.adoc | 66 +++
.../container-control-panel/index.adoc | 32 ++
.../solutions/directory-actions/index.adoc | 17 +
.../heating-control-panel/index.adoc | 28 ++
docs/modules/ROOT/pages/solutions/intro.adoc | 7 +
.../k8s-control-panel-hosted/index.adoc | 117 ++++++
.../pages/solutions/on-git-push/index.adoc | 115 ++++++
.../solutions/primitive-password/index.adoc | 25 ++
.../systemd-control-panel/index.adoc | 44 ++
.../ROOT/pages/solutions/wol/index.adoc | 54 +++
docs/modules/ROOT/pages/style.css | 212 ++++++++++
.../ROOT/pages/troubleshooting/advanced.adoc | 25 ++
.../troubleshooting/browser-console-logs.adoc | 55 +++
.../troubleshooting/err-fetch-buttons.adoc | 6 +
.../err-fetch-webui-settings.adoc | 9 +
.../err-js-modules-not-supported.adoc | 10 +
.../err-websocket-connection.adoc | 14 +
.../troubleshooting/err-webui-mismatch.adoc | 12 +
.../ROOT/pages/troubleshooting/exit127.adoc | 5 +
.../troubleshooting/log-debug-options.adoc | 12 +
.../ROOT/pages/troubleshooting/puid-pgid.adoc | 16 +
.../troubleshooting/server-diagnostics.adoc | 69 ++++
.../pages/troubleshooting/service-logs.adoc | 118 ++++++
.../troubleshooting/wheretofindhelp.adoc | 4 +
docs/modules/ROOT/pages/upgrade/2k3k.adoc | 106 +++++
.../ROOT/pages/upgrade/github_latest.adoc | 28 ++
.../ROOT/pages/upgrade/upgrade_notes.adoc | 67 +++
.../action_examples/actionHeader.adoc | 13 +
.../partials/action_examples/ssh_intro.adoc | 2 +
.../onfileindir_arguments.adoc | 17 +
.../partials/api/start_action_methods.adoc | 30 ++
.../ROOT/partials/args/reject-null.adoc | 9 +
docs/modules/ROOT/partials/config-start.adoc | 2 +
.../ROOT/partials/container_socket.adoc | 74 ++++
docs/modules/ROOT/partials/earlydoc.adoc | 2 +
.../ROOT/partials/install/container.adoc | 6 +
.../install/container_registries.adoc | 6 +
.../ROOT/partials/install/post_container.adoc | 63 +++
.../ROOT/partials/install/post_generic.adoc | 25 ++
.../ROOT/partials/install/post_systemd.adoc | 53 +++
.../ROOT/partials/install/to_config.adoc | 8 +
.../partials/reverse-proxies/diagram.adoc | 19 +
.../reverse-proxies/external-rest.adoc | 10 +
docs/modules/ROOT/partials/support.adoc | 12 +
local-antora-playbook-ci.yml | 21 +
local-antora-playbook.yml | 26 ++
286 files changed, 9225 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/docs-antora.yml
create mode 100644 docs/antora.yml
create mode 100755 docs/modules/ROOT/check_chevron_links.py
create mode 100755 docs/modules/ROOT/check_no_h1.py
create mode 100755 docs/modules/ROOT/check_unnavigable.py
create mode 100644 docs/modules/ROOT/examples/action_customization/icons/config.yaml
create mode 100644 docs/modules/ROOT/examples/k8s_configmap.yml
create mode 100644 docs/modules/ROOT/examples/k8s_deployment.yml
create mode 100644 docs/modules/ROOT/examples/k8s_ingress.yml
create mode 100644 docs/modules/ROOT/examples/reverse-proxies/etc/npm-docker-compose.yml
create mode 100644 docs/modules/ROOT/examples/reverse-proxies/etc/reverse_proxy_nginx_dns.conf
create mode 100644 docs/modules/ROOT/examples/solutions/container-control-panel/config/config.yaml
create mode 100644 docs/modules/ROOT/examples/solutions/container-control-panel/config/containers.json
create mode 100644 docs/modules/ROOT/examples/solutions/directory-actions/config.yaml
create mode 100644 docs/modules/ROOT/examples/solutions/heating-control-panel/configs/config.yaml
create mode 100644 docs/modules/ROOT/examples/solutions/heating-control-panel/configs/heating.yaml
create mode 100644 docs/modules/ROOT/examples/solutions/primitive-password/password.js
create mode 100644 docs/modules/ROOT/examples/solutions/systemd-control-panel/config/config.yaml
create mode 100644 docs/modules/ROOT/examples/solutions/systemd-control-panel/config/systemd_units.json
create mode 100644 docs/modules/ROOT/examples/solutions/wol/config.yaml
create mode 100644 docs/modules/ROOT/examples/solutions/wol/config_docker.yaml
create mode 100644 docs/modules/ROOT/images/action-button-iconify.png
create mode 100644 docs/modules/ROOT/images/action-confirmation.png
create mode 100644 docs/modules/ROOT/images/additionalNavigationLinks.png
create mode 100644 docs/modules/ROOT/images/arg-datetime.png
create mode 100644 docs/modules/ROOT/images/arg-suggestions-chrome.png
create mode 100644 docs/modules/ROOT/images/arg-suggestions-firefox.png
create mode 100644 docs/modules/ROOT/images/args-choices-entities.png
create mode 100644 docs/modules/ROOT/images/args-choices-exec.png
create mode 100644 docs/modules/ROOT/images/args-multiline-text.png
create mode 100644 docs/modules/ROOT/images/args1.png
create mode 100644 docs/modules/ROOT/images/args2.png
create mode 100644 docs/modules/ROOT/images/args3.png
create mode 100644 docs/modules/ROOT/images/args4.png
create mode 100644 docs/modules/ROOT/images/authentik_login.png
create mode 100644 docs/modules/ROOT/images/authentik_login2.png
create mode 100644 docs/modules/ROOT/images/authentik_login3.png
create mode 100644 docs/modules/ROOT/images/authentik_new_app.png
create mode 100644 docs/modules/ROOT/images/authentik_provider_config.png
create mode 100644 docs/modules/ROOT/images/authentik_provider_secrets.png
create mode 100644 docs/modules/ROOT/images/authentik_select_oauth2.png
create mode 100644 docs/modules/ROOT/images/blocked.png
create mode 100644 docs/modules/ROOT/images/dashboard-display.png
create mode 100644 docs/modules/ROOT/images/dashboard-heating-control-panel.png
create mode 100644 docs/modules/ROOT/images/dashboard.png
create mode 100644 docs/modules/ROOT/images/defaultUiHideNav.png
create mode 100644 docs/modules/ROOT/images/defaultUiWithNav.png
create mode 100644 docs/modules/ROOT/images/diagnostics.png
create mode 100644 docs/modules/ROOT/images/directory-actions-screenshot.png
create mode 100644 docs/modules/ROOT/images/exampleIcons.png
create mode 100644 docs/modules/ROOT/images/executionButtons.png
create mode 100644 docs/modules/ROOT/images/executionDialog.png
create mode 100644 docs/modules/ROOT/images/fieldset.png
create mode 100644 docs/modules/ROOT/images/flashyButton.png
create mode 100644 docs/modules/ROOT/images/folders.png
create mode 100644 docs/modules/ROOT/images/gitops.png
create mode 100644 docs/modules/ROOT/images/hacs-custom-repo.png
create mode 100644 docs/modules/ROOT/images/hacs-download.png
create mode 100644 docs/modules/ROOT/images/hacs-dropdown.png
create mode 100644 docs/modules/ROOT/images/hacs-search.png
create mode 100644 docs/modules/ROOT/images/hass-add-integration.png
create mode 100644 docs/modules/ROOT/images/hass-buttons.png
create mode 100644 docs/modules/ROOT/images/hass-configure-integration.png
create mode 100644 docs/modules/ROOT/images/hass-devices-and-services.png
create mode 100644 docs/modules/ROOT/images/hassButtonSetup.png
create mode 100644 docs/modules/ROOT/images/hassConfigYaml.png
create mode 100644 docs/modules/ROOT/images/hassFileEditor.png
create mode 100644 docs/modules/ROOT/images/hassFileEditorConfig.png
create mode 100644 docs/modules/ROOT/images/hello-world.png
create mode 100644 docs/modules/ROOT/images/iconify.png
create mode 100644 docs/modules/ROOT/images/icons/Discord.png
create mode 100644 docs/modules/ROOT/images/icons/GitHub.png
create mode 100644 docs/modules/ROOT/images/icons/OliveTinLogo.png
create mode 100644 docs/modules/ROOT/images/maxRate.png
create mode 100644 docs/modules/ROOT/images/mrGreenAction.png
create mode 100644 docs/modules/ROOT/images/mre.png
create mode 100644 docs/modules/ROOT/images/mrgreen.gif
create mode 100644 docs/modules/ROOT/images/npm.png
create mode 100644 docs/modules/ROOT/images/page-title.png
create mode 100644 docs/modules/ROOT/images/pocketid.png
create mode 100644 docs/modules/ROOT/images/popupOutputOnly.png
create mode 100644 docs/modules/ROOT/images/portDiagram.png
create mode 100644 docs/modules/ROOT/images/sidebar.png
create mode 100644 docs/modules/ROOT/images/snapshot-archive.png
create mode 100644 docs/modules/ROOT/images/snapshot-download.png
create mode 100644 docs/modules/ROOT/images/snapshots.png
create mode 100644 docs/modules/ROOT/images/solution-k8s-hosted.png
create mode 100644 docs/modules/ROOT/images/solution-systemd-control-panel.png
create mode 100644 docs/modules/ROOT/images/solutions/container-control-panel/preview.png
create mode 100644 docs/modules/ROOT/images/solutions/wol/preview.png
create mode 100644 docs/modules/ROOT/images/ssh-diagram.png
create mode 100644 docs/modules/ROOT/images/stream-deck/config.png
create mode 100644 docs/modules/ROOT/images/stream-deck/inputs.png
create mode 100644 docs/modules/ROOT/images/stream-deck/marketplace.png
create mode 100644 docs/modules/ROOT/images/stream-deck/panel.png
create mode 100644 docs/modules/ROOT/images/timeoutLogs.png
create mode 100644 docs/modules/ROOT/images/topbar.png
create mode 100644 docs/modules/ROOT/nav.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/concurrency.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/enabledExpression.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/icons.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/ids.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/intro.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/popuponstart.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/ratelimiting.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/savelogs.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/timeouts.adoc
create mode 100644 docs/modules/ROOT/pages/action_customization/users.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/ansible.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/containers.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/docker-proxy.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/intro.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/ping.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/powershell.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/ssh-easy.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/ssh-manual.adoc
create mode 100644 docs/modules/ROOT/pages/action_examples/systemd_service.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/aftercompletion.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/create_your_first.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/oncalendar.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/oncron.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/ondemand.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/onfilechanged.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/onfilecreated.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/onstartup.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/onwebhook.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/onwebhook_github.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/shellvsexec.adoc
create mode 100644 docs/modules/ROOT/pages/action_execution/triggers.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/config_envs.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/diagnostics.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/intro.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/logs-actions.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/logs-calendar.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/logs.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/ports.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/prometheus.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/stylemods.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/timezones.adoc
create mode 100644 docs/modules/ROOT/pages/advanced_configuration/webui.adoc
create mode 100644 docs/modules/ROOT/pages/api/intro.adoc
create mode 100644 docs/modules/ROOT/pages/api/login.adoc
create mode 100644 docs/modules/ROOT/pages/api/method_StartAction.adoc
create mode 100644 docs/modules/ROOT/pages/api/method_StartActionAndWait.adoc
create mode 100644 docs/modules/ROOT/pages/api/method_StartActionByGet.adoc
create mode 100644 docs/modules/ROOT/pages/api/method_StartActionByGetAndWait.adoc
create mode 100644 docs/modules/ROOT/pages/api/misc.adoc
create mode 100644 docs/modules/ROOT/pages/api/start_action.adoc
create mode 100644 docs/modules/ROOT/pages/args/env.adoc
create mode 100644 docs/modules/ROOT/pages/args/input.adoc
create mode 100644 docs/modules/ROOT/pages/args/input_checkbox.adoc
create mode 100644 docs/modules/ROOT/pages/args/input_confirmation.adoc
create mode 100644 docs/modules/ROOT/pages/args/input_datetime.adoc
create mode 100644 docs/modules/ROOT/pages/args/input_dropdown.adoc
create mode 100644 docs/modules/ROOT/pages/args/input_textarea.adoc
create mode 100644 docs/modules/ROOT/pages/args/intro.adoc
create mode 100644 docs/modules/ROOT/pages/args/password.adoc
create mode 100644 docs/modules/ROOT/pages/args/regex.adoc
create mode 100644 docs/modules/ROOT/pages/args/safety.adoc
create mode 100644 docs/modules/ROOT/pages/args/suggestions.adoc
create mode 100644 docs/modules/ROOT/pages/args/types.adoc
create mode 100644 docs/modules/ROOT/pages/config.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/2-fieldsets.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/3-folders.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/4-displays.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/5-output-views.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/actions.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/css.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/entity-directories.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/examples.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/inline-actions.adoc
create mode 100644 docs/modules/ROOT/pages/dashboards/intro.adoc
create mode 100644 docs/modules/ROOT/pages/entities/examples.adoc
create mode 100644 docs/modules/ROOT/pages/entities/intro.adoc
create mode 100644 docs/modules/ROOT/pages/entities/json.adoc
create mode 100644 docs/modules/ROOT/pages/entities/yaml.adoc
create mode 100644 docs/modules/ROOT/pages/index.adoc
create mode 100644 docs/modules/ROOT/pages/install/bsd.adoc
create mode 100644 docs/modules/ROOT/pages/install/choose_package.adoc
create mode 100644 docs/modules/ROOT/pages/install/container.adoc
create mode 100644 docs/modules/ROOT/pages/install/container_vs_service.adoc
create mode 100644 docs/modules/ROOT/pages/install/docker_compose.adoc
create mode 100644 docs/modules/ROOT/pages/install/helm.adoc
create mode 100644 docs/modules/ROOT/pages/install/intro.adoc
create mode 100644 docs/modules/ROOT/pages/install/k8s.adoc
create mode 100644 docs/modules/ROOT/pages/install/linux_alpine.adoc
create mode 100644 docs/modules/ROOT/pages/install/linux_arch.adoc
create mode 100644 docs/modules/ROOT/pages/install/linux_deb.adoc
create mode 100644 docs/modules/ROOT/pages/install/linux_fedora.adoc
create mode 100644 docs/modules/ROOT/pages/install/linux_manjaro.adoc
create mode 100644 docs/modules/ROOT/pages/install/linux_rpm.adoc
create mode 100644 docs/modules/ROOT/pages/install/macos.adoc
create mode 100644 docs/modules/ROOT/pages/install/podmandocker.adoc
create mode 100644 docs/modules/ROOT/pages/install/targz.adoc
create mode 100644 docs/modules/ROOT/pages/install/windows.adoc
create mode 100644 docs/modules/ROOT/pages/install/windows_service.adoc
create mode 100644 docs/modules/ROOT/pages/integrations/homeassistant-integration.adoc
create mode 100644 docs/modules/ROOT/pages/integrations/homeassistant.adoc
create mode 100644 docs/modules/ROOT/pages/integrations/n8n.adoc
create mode 100644 docs/modules/ROOT/pages/integrations/stream-deck.adoc
create mode 100644 docs/modules/ROOT/pages/reference/containerInstallPackages.adoc
create mode 100644 docs/modules/ROOT/pages/reference/contribute.adoc
create mode 100644 docs/modules/ROOT/pages/reference/donations_and_sponsorship.adoc
create mode 100644 docs/modules/ROOT/pages/reference/exitCodes.adoc
create mode 100644 docs/modules/ROOT/pages/reference/includes.adoc
create mode 100644 docs/modules/ROOT/pages/reference/multiple_instances.adoc
create mode 100644 docs/modules/ROOT/pages/reference/network-ports.adoc
create mode 100644 docs/modules/ROOT/pages/reference/reference_snapshots.adoc
create mode 100644 docs/modules/ROOT/pages/reference/reference_themes_for_developers.adoc
create mode 100644 docs/modules/ROOT/pages/reference/reference_themes_for_users.adoc
create mode 100644 docs/modules/ROOT/pages/reference/release_policy.adoc
create mode 100644 docs/modules/ROOT/pages/reference/updateChecks.adoc
create mode 100644 docs/modules/ROOT/pages/reference/updateTracking.adoc
create mode 100644 docs/modules/ROOT/pages/reference/version_display.adoc
create mode 100644 docs/modules/ROOT/pages/reverse-proxies/apache.adoc
create mode 100644 docs/modules/ROOT/pages/reverse-proxies/caddy.adoc
create mode 100644 docs/modules/ROOT/pages/reverse-proxies/haproxy.adoc
create mode 100644 docs/modules/ROOT/pages/reverse-proxies/intro.adoc
create mode 100644 docs/modules/ROOT/pages/reverse-proxies/nginx.adoc
create mode 100644 docs/modules/ROOT/pages/reverse-proxies/nginx_proxy_manager.adoc
create mode 100644 docs/modules/ROOT/pages/reverse-proxies/traefik.adoc
create mode 100644 docs/modules/ROOT/pages/security/acl.adoc
create mode 100644 docs/modules/ROOT/pages/security/concepts.adoc
create mode 100644 docs/modules/ROOT/pages/security/content_security_policy.adoc
create mode 100644 docs/modules/ROOT/pages/security/design_choices.adoc
create mode 100644 docs/modules/ROOT/pages/security/example_login_required.adoc
create mode 100644 docs/modules/ROOT/pages/security/example_some_admin_actions.adoc
create mode 100644 docs/modules/ROOT/pages/security/examples.adoc
create mode 100644 docs/modules/ROOT/pages/security/jwt.adoc
create mode 100644 docs/modules/ROOT/pages/security/jwt_hmac.adoc
create mode 100644 docs/modules/ROOT/pages/security/jwt_keys.adoc
create mode 100644 docs/modules/ROOT/pages/security/local.adoc
create mode 100644 docs/modules/ROOT/pages/security/oauth2.adoc
create mode 100644 docs/modules/ROOT/pages/security/oauth2_authelia.adoc
create mode 100644 docs/modules/ROOT/pages/security/oauth2_authentik.adoc
create mode 100644 docs/modules/ROOT/pages/security/oauth2_pocketid.adoc
create mode 100644 docs/modules/ROOT/pages/security/trusted_header.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/cloudflare_access_tunnel/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/container-control-panel/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/directory-actions/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/heating-control-panel/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/intro.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/k8s-control-panel-hosted/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/on-git-push/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/primitive-password/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/systemd-control-panel/index.adoc
create mode 100644 docs/modules/ROOT/pages/solutions/wol/index.adoc
create mode 100644 docs/modules/ROOT/pages/style.css
create mode 100644 docs/modules/ROOT/pages/troubleshooting/advanced.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/browser-console-logs.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/err-fetch-buttons.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/err-fetch-webui-settings.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/err-js-modules-not-supported.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/err-websocket-connection.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/err-webui-mismatch.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/exit127.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/log-debug-options.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/puid-pgid.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/server-diagnostics.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/service-logs.adoc
create mode 100644 docs/modules/ROOT/pages/troubleshooting/wheretofindhelp.adoc
create mode 100644 docs/modules/ROOT/pages/upgrade/2k3k.adoc
create mode 100644 docs/modules/ROOT/pages/upgrade/github_latest.adoc
create mode 100644 docs/modules/ROOT/pages/upgrade/upgrade_notes.adoc
create mode 100644 docs/modules/ROOT/partials/action_examples/actionHeader.adoc
create mode 100644 docs/modules/ROOT/partials/action_examples/ssh_intro.adoc
create mode 100644 docs/modules/ROOT/partials/action_execution/onfileindir_arguments.adoc
create mode 100644 docs/modules/ROOT/partials/api/start_action_methods.adoc
create mode 100644 docs/modules/ROOT/partials/args/reject-null.adoc
create mode 100644 docs/modules/ROOT/partials/config-start.adoc
create mode 100644 docs/modules/ROOT/partials/container_socket.adoc
create mode 100644 docs/modules/ROOT/partials/earlydoc.adoc
create mode 100644 docs/modules/ROOT/partials/install/container.adoc
create mode 100644 docs/modules/ROOT/partials/install/container_registries.adoc
create mode 100644 docs/modules/ROOT/partials/install/post_container.adoc
create mode 100644 docs/modules/ROOT/partials/install/post_generic.adoc
create mode 100644 docs/modules/ROOT/partials/install/post_systemd.adoc
create mode 100644 docs/modules/ROOT/partials/install/to_config.adoc
create mode 100644 docs/modules/ROOT/partials/reverse-proxies/diagram.adoc
create mode 100644 docs/modules/ROOT/partials/reverse-proxies/external-rest.adoc
create mode 100644 docs/modules/ROOT/partials/support.adoc
create mode 100644 local-antora-playbook-ci.yml
create mode 100644 local-antora-playbook.yml
diff --git a/.github/workflows/docs-antora.yml b/.github/workflows/docs-antora.yml
new file mode 100644
index 0000000..96036b2
--- /dev/null
+++ b/.github/workflows/docs-antora.yml
@@ -0,0 +1,34 @@
+name: Antora docs
+on:
+ push:
+ paths:
+ - 'docs/**'
+ - 'local-antora-playbook.yml'
+ - 'local-antora-playbook-ci.yml'
+ - '.github/workflows/docs-antora.yml'
+ pull_request:
+ paths:
+ - 'docs/**'
+ - 'local-antora-playbook.yml'
+ - 'local-antora-playbook-ci.yml'
+ - '.github/workflows/docs-antora.yml'
+
+jobs:
+ antora:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install Antora toolchain
+ run: npm i antora@3.1.14 asciidoctor-kroki@0.18.1 @asciidoctor/tabs@1.0.0-beta.6
+
+ - name: Generate docs site (smoke)
+ run: npx antora local-antora-playbook-ci.yml --log-level info
diff --git a/.gitignore b/.gitignore
index aa5cc25..6986eee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,4 +19,6 @@ OliveTin
integration-tests/configs/authRequireGuestsToLogin/sessions.yaml
webui
webui.dev
-sessions.yaml
\ No newline at end of file
+sessions.yaml
+docs/build/
+build/
\ No newline at end of file
diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc
index e0f0672..40ac7da 100644
--- a/CONTRIBUTING.adoc
+++ b/CONTRIBUTING.adoc
@@ -58,6 +58,7 @@ make
The project layout is reasonably straightforward;
* See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any).
+* End-user documentation (AsciiDoc for link:https://docs.olivetin.app[docs.olivetin.app]) lives in `docs/` as an Antora component; the published site is built from the separate link:https://github.com/OliveTin/docs.olivetin.app[docs.olivetin.app] repository.
* The API is defined in protobuf+Connect RPC - you will need to `make proto`.
* The Go daemon is built from the `cmd` and `internal` directories mostly.
* The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver.
diff --git a/README.md b/README.md
index 792d035..a1c8a9d 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,8 @@
All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app). This includes installation and usage guide, etc.
+The AsciiDoc sources for that site live in this repository under [`docs/`](docs/) (Antora component). The [docs.olivetin.app](https://github.com/OliveTin/docs.olivetin.app) repository contains the Antora playbook, theme supplemental files, and the workflow that publishes GitHub Pages.
+
## Use cases
**Safely** give access to commands, for less technical people;
diff --git a/docs/antora.yml b/docs/antora.yml
new file mode 100644
index 0000000..ec27f39
--- /dev/null
+++ b/docs/antora.yml
@@ -0,0 +1,15 @@
+---
+name: ROOT
+title: OliveTin
+version: ''
+display_version: 'Version 3k'
+start_page: index.adoc
+asciidoc:
+ attributes:
+ source-language: asciidoc@
+ table-caption: false
+ toclevels: 2
+nav:
+- modules/ROOT/nav.adoc
+
+
diff --git a/docs/modules/ROOT/check_chevron_links.py b/docs/modules/ROOT/check_chevron_links.py
new file mode 100755
index 0000000..b7683f9
--- /dev/null
+++ b/docs/modules/ROOT/check_chevron_links.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+
+import glob
+import re
+
+nav_file = open('nav.adoc', 'r')
+nav_string = nav_file.read()
+
+adoc_files = glob.glob('pages/**/*.adoc', recursive=True)
+
+filelist = dict()
+
+for file in adoc_files:
+ with open(file, 'r') as handle:
+ content = handle.read()
+
+ matches = re.findall(r'<<(.*?),?([\w\- ]+)>>', content)
+
+ for match in matches:
+ m = match
+
+ if match[0] == "":
+ m = match[1]
+ else:
+ m = match[0]
+
+ if content.count("#" + m) != 1:
+ if file not in filelist:
+ filelist[file] = list()
+
+ filelist[file].append(m)
+
+
+print("Files:", len(filelist))
+
+for file in filelist.keys():
+ print(file)
+
+ for match in filelist[file]:
+ print("\t", match)
diff --git a/docs/modules/ROOT/check_no_h1.py b/docs/modules/ROOT/check_no_h1.py
new file mode 100755
index 0000000..04319b1
--- /dev/null
+++ b/docs/modules/ROOT/check_no_h1.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+import glob
+import re
+
+adoc_files = glob.glob('pages/**/*.adoc', recursive=True)
+
+filelist = list()
+
+for file in adoc_files:
+ with open(file, 'r') as handle:
+ content = handle.read()
+
+ matches = re.findall('^= ', content, re.MULTILINE)
+
+ if len(matches) == 0:
+ filelist.append(file)
+
+
+print("Files:", len(filelist))
+
+for file in filelist:
+ print(file)
+
diff --git a/docs/modules/ROOT/check_unnavigable.py b/docs/modules/ROOT/check_unnavigable.py
new file mode 100755
index 0000000..826f550
--- /dev/null
+++ b/docs/modules/ROOT/check_unnavigable.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+
+# find .adoc files that are not navigable from the nav.adoc file
+
+import glob
+
+nav_file = open('nav.adoc', 'r')
+nav_string = nav_file.read()
+
+adoc_files = glob.glob('pages/**/*.adoc', recursive=True)
+
+unnavigable_files = []
+
+for file in adoc_files:
+ filename = file.replace("pages/", "")
+
+ if filename not in nav_string:
+ unnavigable_files.append(filename)
+
+
+unnavigable_files = sorted(unnavigable_files)
+
+print("Unnavigable files:", len(unnavigable_files))
+for file in unnavigable_files:
+ print(file)
diff --git a/docs/modules/ROOT/examples/action_customization/icons/config.yaml b/docs/modules/ROOT/examples/action_customization/icons/config.yaml
new file mode 100644
index 0000000..13028dc
--- /dev/null
+++ b/docs/modules/ROOT/examples/action_customization/icons/config.yaml
@@ -0,0 +1,15 @@
+actions:
+ - title: Unicode (emoji) alias icon
+ shell: echo "Hello!"
+ icon: smile
+
+ - title: Unicode (emoji) icon
+ shell: echo "Hello!"
+ icon: "😎"
+
+ - title: Iconify Icon
+ icon:
+
+ - title: HTML Image (jpg/png/gif/etc) icon
+ shell: echo "Hello!"
+ icon: '
'
diff --git a/docs/modules/ROOT/examples/k8s_configmap.yml b/docs/modules/ROOT/examples/k8s_configmap.yml
new file mode 100644
index 0000000..15131cf
--- /dev/null
+++ b/docs/modules/ROOT/examples/k8s_configmap.yml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: olivetin-config
+data:
+ config.yaml: |
+ actions:
+ - title: "Hello world!"
+ shell: echo 'Hello World!'
diff --git a/docs/modules/ROOT/examples/k8s_deployment.yml b/docs/modules/ROOT/examples/k8s_deployment.yml
new file mode 100644
index 0000000..0e6756d
--- /dev/null
+++ b/docs/modules/ROOT/examples/k8s_deployment.yml
@@ -0,0 +1,37 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: olivetin
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: olivetin
+ template:
+ metadata:
+ labels:
+ app: olivetin
+ spec:
+ containers:
+ - name: olivetin
+ image: docker.io/jamesread/olivetin:latest
+ ports:
+ - containerPort: 1337
+ volumeMounts:
+ - name: olivetin-config
+ mountPath: "/config"
+ readOnly: true
+
+ livenessProbe:
+ exec:
+ command:
+ - curl
+ - localhost:1337
+ initialDelaySeconds: 5
+ periodSeconds: 30
+
+ volumes:
+ - name: olivetin-config
+ configMap:
+ name: olivetin-config
+
diff --git a/docs/modules/ROOT/examples/k8s_ingress.yml b/docs/modules/ROOT/examples/k8s_ingress.yml
new file mode 100644
index 0000000..af03a40
--- /dev/null
+++ b/docs/modules/ROOT/examples/k8s_ingress.yml
@@ -0,0 +1,21 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: olivetin-ingress
+spec:
+ defaultBackend:
+ service:
+ name: olivetin
+ port:
+ number: 1337
+ rules:
+ - host: olivetin.apps.ocp.teratan.net
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: olivetin
+ port:
+ number: 1337
diff --git a/docs/modules/ROOT/examples/reverse-proxies/etc/npm-docker-compose.yml b/docs/modules/ROOT/examples/reverse-proxies/etc/npm-docker-compose.yml
new file mode 100644
index 0000000..c49c4e7
--- /dev/null
+++ b/docs/modules/ROOT/examples/reverse-proxies/etc/npm-docker-compose.yml
@@ -0,0 +1,19 @@
+services:
+ app:
+ image: 'jc21/nginx-proxy-manager:latest'
+ restart: unless-stopped
+ ports:
+ - '80:80'
+ - '81:81'
+ - '443:443'
+ volumes:
+ - ./data:/data
+ - ./letsencrypt:/etc/letsencrypt
+ olivetin:
+ container_name: olivetin
+ image: jamesread/olivetin
+ volumes:
+ - ./OliveTin:/config # replace host path or volume as needed
+ ports:
+ - "1337:1337"
+ restart: unless-stopped
diff --git a/docs/modules/ROOT/examples/reverse-proxies/etc/reverse_proxy_nginx_dns.conf b/docs/modules/ROOT/examples/reverse-proxies/etc/reverse_proxy_nginx_dns.conf
new file mode 100644
index 0000000..3023d7a
--- /dev/null
+++ b/docs/modules/ROOT/examples/reverse-proxies/etc/reverse_proxy_nginx_dns.conf
@@ -0,0 +1,25 @@
+server {
+ listen 443 ssl;
+
+ ssl_certificate "/etc/nginx/conf.d/server.crt";
+ ssl_certificate_key "/etc/nginx/conf.d/server.key";
+
+ access_log /var/log/nginx/ot.access.log main;
+ error_log /var/log/nginx/ot.error.log notice;
+
+ server_name olivetin.example.com;
+
+ location / {
+ proxy_pass http://localhost:1337/;
+ proxy_redirect http://localhost:1337/ http://localhost/OliveTin/;
+ }
+
+ location /websocket {
+ proxy_set_header Upgrade "websocket";
+ proxy_set_header Connection "upgrade";
+ proxy_pass http://localhost:1337/websocket;
+ proxy_read_timeout 600s;
+ proxy_send_timeout 600s;
+ }
+}
+
diff --git a/docs/modules/ROOT/examples/solutions/container-control-panel/config/config.yaml b/docs/modules/ROOT/examples/solutions/container-control-panel/config/config.yaml
new file mode 100644
index 0000000..5e8ff4d
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/container-control-panel/config/config.yaml
@@ -0,0 +1,48 @@
+# This config has two actions which are applied to all "container" entities
+# found in the entity file.
+#
+# Docs: http://localhost/docs.olivetin.app/docs/entities.html
+actions:
+ - title: Start {{ container.Names }}
+ icon: box
+ shell: docker start {{ container.Names }}
+ entity: container
+ triggers:
+ - Update container entity file
+
+ - title: Stop {{ container.Names }}
+ icon: box
+ shell: docker stop {{ container.Names }}
+ entity: container
+ triggers:
+ - Update container entity file
+
+ # This is a hidden action, that is run on startup, and every 5 minutes, and
+ # when the above start/stop commands are run (see the `triggers` property).
+
+ - title: Update container entity file
+ shell: 'docker ps -a --format json > /etc/OliveTin/entities/containers.json'
+ hidden: true
+ execOnStartup: true
+ execOnCron: '*/5 * * * *'
+
+# Docs: http://docs.olivetin.app/entities.html
+entities:
+ - file: /etc/OliveTin/entities/containers.json
+ name: container
+
+# The only way to properly use entities, are to use them with a `fieldset` on
+# a dashboard.
+dashboards:
+ # This is the second dashboard.
+ - title: My Containers
+ contents:
+ - title: 'Container {{ container.Names }} ({{ container.Image }})'
+ entity: container
+ type: fieldset
+ contents:
+ - type: display
+ title: |
+ {{ container.RunningFor }}
{{ container.State }}
+ - title: 'Start {{ container.Names }}'
+ - title: 'Stop {{ container.Names }}'
diff --git a/docs/modules/ROOT/examples/solutions/container-control-panel/config/containers.json b/docs/modules/ROOT/examples/solutions/container-control-panel/config/containers.json
new file mode 100644
index 0000000..fc6f592
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/container-control-panel/config/containers.json
@@ -0,0 +1,2 @@
+{"Command":"\"/bin/bash\"","CreatedAt":"2024-02-28 22:33:35 +0000 GMT","ID":"fcf468e18a0e","Image":"fedora","Labels":"maintainer=Clement Verna \u003ccverna@fedoraproject.org\u003e","LocalVolumes":"0","Mounts":"","Names":"minecraft","Networks":"bridge","Ports":"","RunningFor":"3 minutes ago","Size":"0B","State":"created","Status":"Created"}
+{"Command":"\"/bin/bash\"","CreatedAt":"2024-02-23 23:18:57 +0000 GMT","ID":"442dd6fe316a","Image":"fedora","Labels":"maintainer=Clement Verna \u003ccverna@fedoraproject.org\u003e","LocalVolumes":"0","Mounts":"","Names":"brave_shirley","Networks":"bridge","Ports":"","RunningFor":"4 days ago","Size":"0B","State":"created","Status":"Created"}
diff --git a/docs/modules/ROOT/examples/solutions/directory-actions/config.yaml b/docs/modules/ROOT/examples/solutions/directory-actions/config.yaml
new file mode 100644
index 0000000..436d265
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/directory-actions/config.yaml
@@ -0,0 +1,32 @@
+actions:
+ - title: check log directory
+ hidden: true
+ shell: |
+ function addDirectory {
+ COUNT=$(ls -l $1 | wc -l)
+ echo "- directory: $1" >> /etc/OliveTin/entities/directories.yaml
+ echo " count: $COUNT" >> /etc/OliveTin/entities/directories.yaml
+ }
+
+ truncate -s 0 /etc/OliveTin/entities/directories.yaml
+ addDirectory /var/log/
+ addDirectory /home/xconspirisist/logs
+ execOnStartup: true
+ execOnCron: "* * * * *"
+
+ - title: clean {{ log_directory.directory }} ({{log_directory.count }} files)
+ shell: |
+ echo "Removing all files in {{ log_directory.directory }}"
+ entity: log_directory
+
+entities:
+ - name: log_directory
+ file: /etc/OliveTin/entities/directories.yaml
+
+dashboards:
+ - title: Log Actions
+ contents:
+ - entity: log_directory
+ type: fieldset
+ contents:
+ - title: clean {{ log_directory.directory }} ({{log_directory.count }} files)
diff --git a/docs/modules/ROOT/examples/solutions/heating-control-panel/configs/config.yaml b/docs/modules/ROOT/examples/solutions/heating-control-panel/configs/config.yaml
new file mode 100644
index 0000000..a84130b
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/heating-control-panel/configs/config.yaml
@@ -0,0 +1,28 @@
+logLevel: "INFO"
+
+actions:
+ - title: Turn heating up
+ icon: '🔼'
+ shell: /opt/heating.sh up
+
+ - title: Turn heating down
+ icon: '🔽'
+ shell: /opt/heating.sh down
+
+entities:
+ - file: /etc/OliveTin/entities/heating.yaml
+ name: heating
+
+dashboards:
+ - title: Heating Control Panel
+ contents:
+ - title: "{{ heater.title }}"
+ entity: heating
+ type: fieldset
+ contents:
+ - type: display
+ title: |
+ 🌡
{{ heating.temperature }}
+
+ - title: Turn heating up
+ - title: Turn heating down
diff --git a/docs/modules/ROOT/examples/solutions/heating-control-panel/configs/heating.yaml b/docs/modules/ROOT/examples/solutions/heating-control-panel/configs/heating.yaml
new file mode 100644
index 0000000..5d8aebb
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/heating-control-panel/configs/heating.yaml
@@ -0,0 +1,2 @@
+- title: Main heater
+ temperature: 20 degrees
diff --git a/docs/modules/ROOT/examples/solutions/primitive-password/password.js b/docs/modules/ROOT/examples/solutions/primitive-password/password.js
new file mode 100644
index 0000000..64cef31
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/primitive-password/password.js
@@ -0,0 +1,35 @@
+const myPassword = 'sekrit'
+
+const domMain = document.getElementsByTagName('main')[0]
+domMain.style.display = 'none'
+
+const domPassword = document.createElement('input')
+const domLogin = document.createElement('button')
+
+function checkPassword () {
+ if (domPassword.value === myPassword) {
+ domMain.style.display = 'block'
+ domPassword.remove()
+ domLogin.remove()
+ } else {
+ window.alert('Incorrect password. Please try again.')
+ }
+}
+
+function setupPasswordForm () {
+ domPassword.setAttribute('type', 'password')
+ domPassword.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') {
+ checkPassword()
+ }
+ })
+
+ domLogin.innerText = 'Login'
+ domLogin.onclick = checkPassword
+
+ const domHeader = document.querySelector('header')
+ domHeader.appendChild(domPassword)
+ domHeader.appendChild(domLogin)
+}
+
+document.addEventListener('DOMContentLoaded', setupPasswordForm)
diff --git a/docs/modules/ROOT/examples/solutions/systemd-control-panel/config/config.yaml b/docs/modules/ROOT/examples/solutions/systemd-control-panel/config/config.yaml
new file mode 100644
index 0000000..6fe5749
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/systemd-control-panel/config/config.yaml
@@ -0,0 +1,36 @@
+actions:
+ - title: Stop {{ systemd_unit.unit }}
+ shell: systemctl stop {{ systemd_unit.unit }}
+ icon:
+ entity: systemd_unit
+ triggers:
+ - Update services file
+
+ - title: Start {{ systemd_unit.unit }}
+ shell: systemctl start {{ systemd_unit.unit }}
+ icon:
+ entity: systemd_unit
+ triggers:
+ - Update services file
+
+ - title: Update services file
+ shell: systemctl list-units -a -o json --no-pager | jq -c 'map(select (.unit | contains ("upsilon", "podman", "boot.mount"))) | .[]' > /etc/OliveTin/entities/systemd_units.json
+ hidden: true
+ execOnStartup: true
+
+entities:
+ - file: /etc/OliveTin/entities/systemd_units.json
+ name: systemd_unit
+
+dashboards:
+ - title: My Services
+ contents:
+ - title: '{{ systemd_unit.description }}'
+ type: fieldset
+ entity: systemd_unit
+ contents:
+ - title: 'Status: {{ systemd_unit.sub }}'
+ type: display
+
+ - title: Start {{ systemd_unit.unit }}
+ - title: Stop {{ systemd_unit.unit }}
diff --git a/docs/modules/ROOT/examples/solutions/systemd-control-panel/config/systemd_units.json b/docs/modules/ROOT/examples/solutions/systemd-control-panel/config/systemd_units.json
new file mode 100644
index 0000000..00a0022
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/systemd-control-panel/config/systemd_units.json
@@ -0,0 +1,4 @@
+{"unit":"boot.mount","load":"loaded","active":"active","sub":"mounted","description":"/boot"}
+{"unit":"podman.service","load":"loaded","active":"inactive","sub":"dead","description":"Podman API Service"}
+{"unit":"upsilon-drone.service","load":"loaded","active":"active","sub":"running","description":"upsilon-drone"}
+{"unit":"podman.socket","load":"loaded","active":"active","sub":"listening","description":"Podman API Socket"}
diff --git a/docs/modules/ROOT/examples/solutions/wol/config.yaml b/docs/modules/ROOT/examples/solutions/wol/config.yaml
new file mode 100644
index 0000000..ab38c02
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/wol/config.yaml
@@ -0,0 +1,10 @@
+actions:
+ - title: WakeOnLan Server1
+ shell: ether-wake A8:5E:45:E4:FF:2A
+ icon: ping
+
+ - title: Install ether-wake on startup
+ shell: microdnf install -y net-tools
+ hidden: true
+ execOnStartup: true
+ timeout: 120
diff --git a/docs/modules/ROOT/examples/solutions/wol/config_docker.yaml b/docs/modules/ROOT/examples/solutions/wol/config_docker.yaml
new file mode 100644
index 0000000..fc37b89
--- /dev/null
+++ b/docs/modules/ROOT/examples/solutions/wol/config_docker.yaml
@@ -0,0 +1,6 @@
+ - title: WakeOnLan Server1
+ # The r0gger/docker-wake-on-lan is a minimal container for WOL
+ # that can be run on the host network.
+ # It is not required to run the OliveTin container on the host network.
+ shell: |
+ docker run --rm --name wake-on-lan --net=host -e MAC='A8:5E:45:E4:FF:2A' r0gger/docker-wake-on-lan
diff --git a/docs/modules/ROOT/images/action-button-iconify.png b/docs/modules/ROOT/images/action-button-iconify.png
new file mode 100644
index 0000000000000000000000000000000000000000..94239d826883d5d481414b399903e96689b24b2a
GIT binary patch
literal 6574
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
i{#b^|_T1G%Pwq%(A
literal 0
HcmV?d00001
diff --git a/docs/modules/ROOT/images/action-confirmation.png b/docs/modules/ROOT/images/action-confirmation.png
new file mode 100644
index 0000000000000000000000000000000000000000..d681c03ff1584eb1a1230834c74750caf08636d0
GIT binary patch
literal 12592
zcmb7qby!vJmoF#+q6i2GNJ@*ebP9rsG*Z&i-JMDc0xA;H9S13q?viei?k?$)nsvVS
zH#5&XGjs2A|8RSr!#?kR_q*!z`K$7+0&;AQcs^!+1lF}n^_v6pfG;&`Xv0K`3YI8?yF2{LX2A>j_v&dW0IkKznKV9
z#d8QXoY*T5ZpHneeaIT_Yth&k(Tea3Eh)HD;7fIaI=?=ju}V#M$@cI^@a}8{<@O)p
zKhvnjllk0$>8&*D5a3uA<>xlA+!F?yAq71DZWh1EvCa#
z9G_AzGJ2ccmAV+Eq|#+OC=RQwe(Sh@MR4zfMT9kMxyFxoRyFO?#q8WXfxGb~aiz
z!SMEqq++o8QsY+a1#RuvF!l3>9Ufb|so^iev-TLo9*C~M%@^Kr;>DOX$pgGg?>=ej
z+4gMZmqZL>BHR{nFQuq?XQA$=4W92p(5%*mMY2R}Aio{V;-SMf7
zm6egTBg#{Iqc@I5hEy(Qj;2)4rDPS<-rXTWL7_sCk`PmN9p6fHaU~f3+a~&0bMVmv
zG)!9BcU088%4UOt*Qm7=C92w0lv&ljgy!UE)w)V%YAJa8Qf<9#>;FQnt?*JJgy6vw
zed^i_%kNvqZQq+&rKoPAitl+RnIugm@;fYd3$Az`3vVazr_(YpOl&y|_TFePFV!#A
zC?2!4?&?`XYwXxt>Uy86O70hg=QkZJm;HP=moX#+ff6!b=QkDJbH}+FJAOED*u2rQ
z<7Yxt&u)7CZVSGAu;L9~0?C1UDk@X=66J#AvWEuOZ0<4T4TrSqcN9-DTl>z2j9LCP
z>})YF(I3$4Uprt#gt*sIFVkB(SChw2g-#jt^}VFTf%#tOjl5y3w>`%Ak8WAV%FquE4)&AYUOhy2MBI^7DO7v5
zhs*Q4wq(Ncj@Xab;@BRzg;l$X_8H-%f{F@0TdhvSfT^Qq!m48xDaAxjlQHMl_V#wF
z$B!ewePf0POjTvim)?Cw_uT3XKAb9=5g#5!un~bafl)h%U$NBg%zssKl
zVinu{-&)m)&(iU5aBymBYIcr}e*5=*mD?Xp@LeJ(`|#nmM!7Z1Jwmip^^!Nu==m0i
zp|zqh%N)0xn#RUi>N2?g7-{9Rw`2I7WLn3_D{>#jMMcrw!$(UUUAP^^zkPjl>xfia
zF}s1kmZxN_yJrGT3^(L@h;;XFOCF2qxL4VeLOj_;{KC9qxc>9uL$4gj!?UW~_2u3;
zcw_le5x8h^cl8)lli=});ZktDagcN(EQzoE=jPs?Dx>lVhvQLLk-|^{`QID-|M}uC
zi;E`qKU|QjqW#y4zh|0HigfZJM#cqt7H#`W)P18OBVU$V1j0EwDaSV=>}M5GYYZB)
z5eD&8nADc7*9El%F^PB^qmmmvb8$rwc%ByGwvRC_(FE9XZ}?I*Ig1{2UnAmqvVOPy
zWo=1~<(REgp@Cwy*b70%5%LcYlt^J?TNHL?g0wwvH}@rz%UN#q{%o!s#vLbQ61tAa
z{)GjvRV^LMCN?h-Wa2a;EdJ=c@;0kgK=7Mc-RI_kR6-2|ebs4d__O_wj-UO~>JY(c
zn+{kkY-BB@BtY984GD3um^G|KQ(^Y>K2MN_B`Z_PziR(6H;
z^uEuKCKGW^8uHP4AUN?dXqiJfqQj^Es*@Z4>g))`jAYC|;N&hjiA)&6X!p2BvZi1&
z(7;Bldm?2&DvlZsB4D;Jvod77^uUtGQnc}CD!bC1rOQe)tD{@gfTHX8LD{8aTrGwY
z3ZMJ@HBE1U00iSNw+x}Faa9JcmNu`TA2YKj9twwEyOVGp`ERvGF{Mf-g}DUJcNU~n
zJ+DZ!n57GK9K2Cy#A9J8xh4`TOPmFjop-wbJLf@AzllSzkyZ
z0#%aY7udG!{G%P2+{K8OO4e7BtBJ06=_XUZp9f+LQWa1WcAerA&q}F}`8wg{sXS~x
zQM5YU_q=bymQixGlT1q{(`sC9P&|I(-L<_bdd~3l>{qsDPN7HnK}4lOap5mwq+zgs
zJM!(~jrNQq^2}b#BveLbe90TH3f_5=WO=3VZr;poZo{Bugduh{SnvLDZpiD&g6
zUhltjB-P|feVH8I<2*X+n#l+iEup4gbhB!&0(~)?t~Xq4Y0~*@9np%ZmPPd$PvUYX
z$}Q#g;_Ua;bF_F%P8Ky@$~+8d
z;Iv!r*7*~N*P%zB64r6K)5Y5Iu2W_@4KWg^RBp7s55t;EZl*{V=NFYVtdFgaw^$qF
zy=#;lB)Tk}j|Yb*^dc^7L%r01fz8~7nNV^rK;$qutg5`v2K~fs?^rAh6U$bJ*yl9l
zy_2Soi@YPxRg)mqGpZm^jtCUnF4$I#@;g~gwkjUqbah|UPut%}X>#CP`UHod
z-4=IK&3WmM2;yZ@TA7;(^}gCU;^Ok+m*n50lUuUuD!s?4Y+9X*qPmt0!p(TRg8OZR
zmoI96hI(zRys%(jWnYZ?#Bz)E3e!kK3i~eFTFA8+7lCDoM<^qM-_^e#*@hp6s*-m)
z>{wefYkH{Tb2HSF$r;TYj_SOts91VeJ!&yhAgM?|Aem}5ltov-Nwj>vKAe}mRpPug
zK9NPqZT=UG*BIgYtdCKlZ;j%*Avjre-3*O1
z$2{0wP$$aH92@&+w%!_C>!zOVVykz|%MeJsV`F2})ZA?L`x}W({RNMdlvHchAT2#T
zr}L)z&v5dtgSDYd!kg5VT7v4c+HwrK9S##?p=+m|G>4nq-&8feOZ|V>RUqm7VUdl;|~HyT_{44mJFBi7|FOkCp!MpD9V7AKyf=N;M0Hu(TQa
z*?eBPVVq1+<6Mc$VI)NNpup%WH^&lMDKy}iAKE>9N71f5qXTs%F6oHl;94h=ko-*9J$E?>HLikOi
zG~^zO#-yr{`_U#6!%h88=Ckv?iBDf-IR-8~DpqN~w5RgGB^CGX^PC3$y;@!U}v=@ouBBK^I)-CrNqTpUdR
zdfbPycHtsl}VA9LPexylEBRH2z0+0Ck*?u9yvpEo3=xEg7{9s64W6a}l
zJ!%16MoH-|L?kUO?IgEdS9^Pde(}esr~tX_r`bcPW#$t}_pHVTz8P8E=e3Z#2n@?n
zslKCPkT(-hV5*VoeAe}JY0_k(qPKZi{3hFWa-k5xjd9_7i3YWD*@IbT2g1V1zJoc6
zG~!QfZmUmMk8l|My}UT%-rd>T)1R(!gB-920hq#wMM6R%bUf)`Xk;|Sd6oMr+dNIR
z)LaE=Z}1*sPQze&TiE+`z40GrGI3mMKa<5Rnc=97Hh$-8WU?acWs`(n<8$lk>e9<4
zya1Ob7j#CW5*AJr@%pRBIraB+3YIZXTi)900V}Il*gjA}D9^!(2h_2i9c`uC%+xV!
zSA9{_ag)@%f*lo^^igabuMVWYF1IER!l9(6qYHo(!EV3!0;NE^`X?NBM&<>WgInWq+Y4m(HdhLM+{(6zPVYfLDht
zr`8^gTZDg9(6~S2bxe)|(X%|F>$3!#IzL&6K)z*}kBE!-@aXz4gm%zhY|?>>*A8)|
zmSNL+T>9l>iV8(zo9YMm4&1K`Olf1*6YinftEjlnjs2DRYtBH#CRp`mZs~$#XQblV
z(h$OqpyT1lXkD8fk#eEh+6WD++CcZrso``dT-%GL6_dg+mMjVW<)OzUd8d++-dmo@Kx+%lRIC(er47y
z`|wDuV5glXqQZ@`uC9(hP!-B~-Q{14tusg)`R|{1MYH58=iZHB)u!g>{~AWhI|!y0
z+J9Z;m>b{zftW*lL-o0YgdR`)EhpLaU%BaH1s=zL$oTCgEGDI+nNzK&s!)XXyC2IW
z3Pgc{KgPwqbapO_ͱ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