From 439e952a252e48eaf4029ef2d5e51553cf0f73a9 Mon Sep 17 00:00:00 2001 From: James Read Date: Fri, 24 Oct 2025 20:52:01 +0100 Subject: [PATCH 01/15] fix: sosreport contains pwd and abs paths (#660) --- service/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/main.go b/service/main.go index 1a0b068..23c9f41 100644 --- a/service/main.go +++ b/service/main.go @@ -130,7 +130,7 @@ func initConfig(configDir string) { var firstConfigPath string for _, directory := range directories { - configPath := filepath.Join(directory, "config.yaml") + configPath, _ := filepath.Abs(filepath.Join(directory, "config.yaml")) log.Debugf("Checking config path: %s", configPath) if _, err := os.Stat(configPath); err != nil { From fa943573742f3c401623747832251a5923fdde91 Mon Sep 17 00:00:00 2001 From: jamesread Date: Fri, 24 Oct 2025 21:31:54 +0100 Subject: [PATCH 02/15] chore: Stop AI agents adding superflous comments --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index a64f036..11c6c7e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,7 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`. - Footer visibility is controlled by `showFooter` from Init API; tests may assert the footer is absent when config disables it. ### Coding Standards (Go) +- Avoid adding superflous comments that explain what the code is doing. Comments are only to describe business logic decisions. - Prefer clear, descriptive names; avoid 1–2 letter identifiers. - Use early returns and handle edge cases first. - Do not swallow errors; propagate or log meaningfully. From 76a33e2e542283e8a5e2cbae9417b1fd0b5d09cb Mon Sep 17 00:00:00 2001 From: James Read Date: Fri, 24 Oct 2025 21:57:07 +0100 Subject: [PATCH 03/15] chore: Remove some old dead code (#662) --- frontend/js/ArgumentForm.js | 273 ------------------------- frontend/js/ExecutionFeedbackButton.js | 29 --- 2 files changed, 302 deletions(-) delete mode 100644 frontend/js/ArgumentForm.js delete mode 100644 frontend/js/ExecutionFeedbackButton.js diff --git a/frontend/js/ArgumentForm.js b/frontend/js/ArgumentForm.js deleted file mode 100644 index b28f030..0000000 --- a/frontend/js/ArgumentForm.js +++ /dev/null @@ -1,273 +0,0 @@ -class ArgumentForm extends window.HTMLElement { - getQueryParams () { - return new URLSearchParams(window.location.search.substring(1)) - } - - setup (json, callback) { - this.setAttribute('class', 'action-arguments') - - this.constructTemplate() - this.domTitle.innerText = json.title - this.domIcon.innerHTML = json.icon - this.createDomFormArguments(json.arguments) - - this.domBtnStart.onclick = () => { - for (const arg of this.argInputs) { - if (!arg.validity.valid) { - return - } - } - - const argvs = this.getArgumentValues() - - callback(argvs) - - this.remove() - } - - this.domBtnCancel.onclick = () => { - this.clearBookmark() - this.remove() - } - } - - getArgumentValues () { - const ret = [] - - for (const arg of this.argInputs) { - if (arg.type === 'checkbox') { - if (arg.checked) { - arg.value = '1' - } else { - arg.value = '0' - } - } - - if (arg.name === '') { - continue - } - - ret.push({ - name: arg.name, - value: arg.value - }) - } - - return ret - } - - constructTemplate () { - const tpl = document.getElementById('tplArgumentForm') - const content = tpl.content.cloneNode(true) - - this.appendChild(content) - - this.domTitle = this.querySelector('h2') - this.domIcon = this.querySelector('span.icon') - this.domWrapper = this.querySelector('.wrapper') - - this.domArgs = this.querySelector('.arguments') - - this.domBtnStart = this.querySelector('[name=start]') - this.domBtnCancel = this.querySelector('[name=cancel]') - } - - createDomFormArguments (args) { - this.argInputs = [] - - for (const arg of args) { - this.domArgs.appendChild(this.createDomLabel(arg)) - this.domArgs.appendChild(this.createDomSuggestions(arg)) - this.domArgs.appendChild(this.createDomInput(arg)) - this.domArgs.appendChild(this.createDomDescription(arg)) - } - } - - createDomLabel (arg) { - const domLbl = document.createElement('label') - - const lastChar = arg.title.charAt(arg.title.length - 1) - - if (lastChar === '?' || lastChar === '.' || lastChar === ':') { - domLbl.innerHTML = arg.title - } else { - domLbl.innerHTML = arg.title + ':' - } - - domLbl.setAttribute('for', arg.name) - - return domLbl - } - - createDomSuggestions (arg) { - if (typeof arg.suggestions !== 'object' || arg.suggestions.length === 0) { - return document.createElement('span') - } - - const ret = document.createElement('datalist') - ret.setAttribute('id', arg.name + '-choices') - - for (const suggestion of Object.keys(arg.suggestions)) { - const opt = document.createElement('option') - - opt.setAttribute('value', suggestion) - - if (typeof arg.suggestions[suggestion] !== 'undefined' && arg.suggestions[suggestion].length > 0) { - opt.innerText = arg.suggestions[suggestion] - } - - ret.appendChild(opt) - } - - return ret - } - - createDomInput (arg) { - let domEl = null - - if (arg.choices.length > 0 && (arg.type === 'select' || arg.type === '')) { - domEl = document.createElement('select') - - // select/choice elements don't get an onchange/validation because theoretically - // the user should only select from a dropdown of valid options. The choices are - // riggeriously checked on StartAction anyway. ValidateArgumentType is only - // meant for showing simple warnings in the UI before running. - - for (const choice of arg.choices) { - domEl.appendChild(this.createSelectOption(choice)) - } - } else { - switch (arg.type) { - case 'html': - domEl = document.createElement('div') - domEl.innerHTML = arg.defaultValue - - return domEl - case 'confirmation': - this.domBtnStart.disabled = true - - domEl = document.createElement('input') - domEl.setAttribute('type', 'checkbox') - domEl.onchange = () => { - this.domBtnStart.disabled = false - domEl.disabled = true - } - break - case 'raw_string_multiline': - domEl = document.createElement('textarea') - domEl.setAttribute('rows', '5') - domEl.style.resize = 'vertical' - break - case 'datetime': - domEl = document.createElement('input') - domEl.setAttribute('type', 'datetime-local') - domEl.setAttribute('step', '1') - break - case 'checkbox': - domEl = document.createElement('input') - domEl.setAttribute('type', 'checkbox') - domEl.setAttribute('name', arg.name) - domEl.setAttribute('value', '1') - - break - case 'password': - case 'email': - domEl = document.createElement('input') - domEl.setAttribute('type', arg.type) - break - default: - domEl = document.createElement('input') - - if (arg.type.startsWith('regex:')) { - domEl.setAttribute('pattern', arg.type.replace('regex:', '')) - } - - domEl.onchange = () => { - this.formatValidation(domEl, arg) - } - } - } - - domEl.name = arg.name - - // Use query parameter value if available - const params = this.getQueryParams() - const paramValue = params.get(arg.name) - - if (paramValue !== null) { - domEl.value = paramValue - } else { - domEl.value = arg.defaultValue - } - - // update the URL when a parameter is changed - domEl.addEventListener('change', this.updateUrlWithArg) - - if (typeof arg.suggestions === 'object' && Object.keys(arg.suggestions).length > 0) { - domEl.setAttribute('list', arg.name + '-choices') - } - - this.argInputs.push(domEl) - - return domEl - } - - async formatValidation (domEl, arg) { - const validateArgumentTypeArgs = { - value: domEl.value, - type: arg.type - } - - const validation = await window.validateArgumentType(validateArgumentTypeArgs) - - if (validation.valid) { - domEl.setCustomValidity('') - } else { - domEl.setCustomValidity(validation.description) - } - } - - updateUrlWithArg (ev) { - if (!ev.target.name) { - return - } - - const url = new URL(window.location.href) - - if (ev.target.type === 'password') { - return - } - - // copy the parameter value - url.searchParams.set(ev.target.name, ev.target.value) - - // Update the URL without reloading the page - window.history.replaceState({}, '', url.toString()) - } - - createDomDescription (arg) { - const domArgumentDescription = document.createElement('span') - domArgumentDescription.classList.add('argument-description') - domArgumentDescription.innerHTML = arg.description - - return domArgumentDescription - } - - createSelectOption (choice) { - const domEl = document.createElement('option') - - domEl.setAttribute('value', choice.value) - domEl.innerText = choice.title - - return domEl - } - - clearBookmark () { - // remove the action from the URL - window.history.replaceState({ - path: window.location.pathname - }, '', window.location.pathname) - } -} - -window.customElements.define('argument-form', ArgumentForm) diff --git a/frontend/js/ExecutionFeedbackButton.js b/frontend/js/ExecutionFeedbackButton.js deleted file mode 100644 index 50e30fb..0000000 --- a/frontend/js/ExecutionFeedbackButton.js +++ /dev/null @@ -1,29 +0,0 @@ -export class ExecutionFeedbackButton extends window.HTMLElement { - onExecutionFinished (LogEntry) { - if (LogEntry.timedOut) { - this.renderExecutionResult('action-timeout', 'Timed out') - } else if (LogEntry.blocked) { - this.renderExecutionResult('action-blocked', 'Blocked!') - } else if (LogEntry.exitCode !== 0) { - this.renderExecutionResult('action-nonzero-exit', 'Exit code ' + LogEntry.exitCode) - } else { - this.ellapsed = Math.ceil(new Date(LogEntry.datetimeFinished) - new Date(LogEntry.datetimeStarted)) / 1000 - this.renderExecutionResult('action-success', 'Success!') - } - } - - renderExecutionResult (resultCssClass, temporaryStatusMessage) { - this.updateDom(resultCssClass, '[' + temporaryStatusMessage + ']') - this.onExecStatusChanged() - } - - updateDom (resultCssClass, title) { - if (resultCssClass == null) { - this.btn.className = '' - } else { - this.btn.classList.add(resultCssClass) - } - - this.domTitle.innerText = title - } -} From 8b2b85c3d0eef46cd998c0ce9e05f8f7415deebe Mon Sep 17 00:00:00 2001 From: James Read Date: Fri, 24 Oct 2025 21:57:23 +0100 Subject: [PATCH 04/15] fix: Argument form start button, and input validation was also broken! (#663) --- frontend/resources/vue/views/ArgumentForm.vue | 89 ++++++++++++++----- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/frontend/resources/vue/views/ArgumentForm.vue b/frontend/resources/vue/views/ArgumentForm.vue index 0ee97e5..70f5aca 100644 --- a/frontend/resources/vue/views/ArgumentForm.vue +++ b/frontend/resources/vue/views/ArgumentForm.vue @@ -4,7 +4,7 @@

Start action: {{ title }}

-
+