chore: coderabbit suggestions

This commit is contained in:
jamesread 2026-07-19 00:03:12 +01:00
parent c72d180fae
commit c5f0387045
17 changed files with 161 additions and 54 deletions

View File

@ -38,28 +38,28 @@ repos:
entry: make service-codestyle
language: system
pass_filenames: false
files: ^(service/|proto/|lang/)
files: ^(service/|proto/|lang/|Makefile)
- id: frontend-codestyle
name: frontend-codestyle
entry: make frontend-codestyle
language: system
pass_filenames: false
files: ^frontend/
files: ^(frontend/|Makefile)
- id: service-unittests
name: service-unittests
entry: make service-unittests
language: system
pass_filenames: false
files: ^(service/|proto/|lang/)
files: ^(service/|proto/|lang/|Makefile)
- id: service-build
name: service-build
entry: make service
language: system
pass_filenames: false
files: ^(service/|proto/|lang/)
files: ^(service/|proto/|lang/|Makefile)
- id: it
name: integration-tests

View File

@ -33,7 +33,7 @@
"stylelint-config-standard": "^40.0.0"
},
"engines": {
"node": ">=22.0.0"
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@babel/code-frame": {

View File

@ -41,6 +41,6 @@
"vue-router": "^5.1.0"
},
"engines": {
"node": ">=22.0.0"
"node": "^20.19.0 || >=22.12.0"
}
}

View File

@ -247,10 +247,16 @@ function updateFromJson (json) {
if (json.datetimeRateLimitExpires) {
const date = new Date(json.datetimeRateLimitExpires.replace(' ', 'T'))
rateLimitExpires.value = date.getTime() / 1000
if (bindingId.value) {
rateLimits[bindingId.value] = rateLimitExpires.value
}
updateRateLimitStatus()
} else if (json.datetimeRateLimitExpires === '') {
// Explicitly clear if empty string
rateLimitExpires.value = 0
if (bindingId.value) {
rateLimits[bindingId.value] = 0
}
updateRateLimitStatus()
}
}
@ -359,6 +365,15 @@ async function pollExecutionUntilDone (trackingId) {
}
}
let stopButtonResultWatch = null
function stopWatchingButtonResult () {
if (stopButtonResultWatch) {
stopButtonResultWatch()
stopButtonResultWatch = null
}
}
async function startAction (actionArgs) {
buttonClasses.value = [] // Removes old animation classes
@ -376,7 +391,8 @@ async function startAction (actionArgs) {
console.log('Watching buttonResults for', startActionArgs.uniqueTrackingId)
watch(
stopWatchingButtonResult()
stopButtonResultWatch = watch(
() => buttonResults[startActionArgs.uniqueTrackingId],
(newResult, oldResult) => {
onLogEntryChanged(newResult)
@ -397,12 +413,18 @@ async function startAction (actionArgs) {
await pollExecutionUntilDone(trackingId)
}
} catch (err) {
stopWatchingButtonResult()
console.error('Failed to start action:', err)
}
}
function onLogEntryChanged (logEntry) {
if (!logEntry) {
return
}
if (logEntry.executionFinished) {
stopWatchingButtonResult()
onExecutionFinished(logEntry)
} else if (logEntry.queued && !logEntry.executionStarted) {
onExecutionQueued(logEntry)

View File

@ -1,6 +1,6 @@
<template>
<section
v-if="!dashboard && !initError"
v-if="!dashboard && !initError && !loadError"
style="text-align: center; padding: 2em;"
>
<HugeiconsIcon
@ -27,6 +27,19 @@
Please check your configuration and try again.
</p>
</section>
<section
v-else-if="loadError"
style="text-align: center; padding: 2em;"
class="bad"
>
<h2 style="color: var(--error);">
Failed to Load Dashboard
</h2>
<p>{{ loadError }}</p>
<p style="color: var(--fg2);">
Please check your configuration and try again.
</p>
</section>
<template v-else-if="dashboard">
<section v-if="dashboard.contents.length == 0">
<div
@ -135,6 +148,7 @@ const router = useRouter()
const dashboard = ref(null)
const loadingTime = ref(0)
const initError = ref(null)
const loadError = ref(null)
let loadingTimer = null
let checkInitInterval = null
let dashboardRequestId = 0
@ -194,8 +208,9 @@ async function getDashboard () {
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
document.title = ret.dashboard.title + ' - ' + pageTitle
// Clear any previous init error since we successfully loaded
// Clear any previous errors since we successfully loaded
initError.value = null
loadError.value = null
// Stop the loading timer once dashboard is loaded
if (loadingTimer) {
@ -210,9 +225,9 @@ async function getDashboard () {
return
}
// On error, provide a safe fallback state
console.error('Failed to load dashboard', e)
dashboard.value = { title: title || 'Default', contents: [] }
dashboard.value = null
loadError.value = e.message || 'Failed to load dashboard'
const pageTitle = window.initResponse?.pageTitle || 'OliveTin'
document.title = 'Error - ' + pageTitle
@ -221,14 +236,13 @@ async function getDashboard () {
clearInterval(loadingTimer)
loadingTimer = null
}
// Set attribute even on error so tests can proceed
document.body.setAttribute('loaded-dashboard', title || 'error')
}
}
function waitForInitAndLoadDashboard () {
document.body.removeAttribute('loaded-dashboard')
loadError.value = null
dashboard.value = null
if (loadingTimer) {
clearInterval(loadingTimer)

View File

@ -24,6 +24,7 @@ export default {
required: true
}
},
emits: ['show'],
data () {
return {
trackingId: '',
@ -51,11 +52,11 @@ export default {
this.isWaiting = true
},
show () {
async show () {
this.$emit('show')
if (window.executionDialog) {
window.executionDialog.reset()
await window.executionDialog.reset()
window.executionDialog.show()
window.executionDialog.fetchExecutionResult(this.trackingId)
}
@ -89,7 +90,10 @@ export default {
// For execution button, we don't need to update classes as much
// since it's a simpler component
if (resultCssClass) {
this.$el.classList.add(resultCssClass)
const button = this.$el.querySelector('button')
if (button) {
button.classList.add(resultCssClass)
}
}
}
}

View File

@ -319,11 +319,13 @@ function getArgumentValue (arg) {
function handleJustificationInput (event) {
justificationValue.value = event.target.value
justificationEditedManually.value = true
event.target.setCustomValidity('')
}
function handleInput (arg, event) {
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value
argValues.value[arg.name] = value
event.target.setCustomValidity('')
updateUrlWithArg(arg.name, value)
updateJustificationFromTemplate()
}

View File

@ -117,9 +117,18 @@ async function fetchDiagnostics () {
}
async function generateServerDiagnostics () {
const response = await window.client.serverDiagnostics()
console.log('response', response)
serverDiagnostics.value = `\`\`\`\n${response.alert}\n\`\`\`\n`
loading.value = true
try {
const response = await window.client.serverDiagnostics()
console.log('response', response)
serverDiagnostics.value = `\`\`\`\n${response.alert}\n\`\`\`\n`
} catch (err) {
console.error('Failed to generate server diagnostics:', err)
serverDiagnostics.value = ''
} finally {
loading.value = false
}
}
async function copyServerDiagnostics () {

View File

@ -24,10 +24,15 @@
@click="loginWithOAuth(provider)"
>
<span
v-if="provider.icon"
v-if="providerIcon(provider)"
class="provider-icon"
v-html="provider.icon"
/>
>
<iconify-icon
v-if="providerIcon(provider).kind === 'iconify'"
:icon="providerIcon(provider).id"
/>
<span v-else>{{ providerIcon(provider).text }}</span>
</span>
<span class="provider-name">Login with {{ provider.title }}</span>
</button>
</div>
@ -96,6 +101,38 @@ const hasOAuth = ref(false)
const hasLocalLogin = ref(false)
const oauthProviders = ref([])
const trustedProviderIconifyIds = {
github: 'simple-icons:github',
google: 'simple-icons:google'
}
function providerIcon (provider) {
const raw = (provider?.icon || '').trim()
if (!raw) {
return null
}
const iconifyTagMatch = raw.match(/<iconify-icon\b[^>]*\bicon=["']([^"']+)["'][^>]*>/i)
if (iconifyTagMatch) {
return { kind: 'iconify', id: iconifyTagMatch[1] }
}
if (/^[a-z0-9-]+:[a-z0-9-]+$/i.test(raw)) {
return { kind: 'iconify', id: raw }
}
const trustedId = trustedProviderIconifyIds[provider.key]
if (trustedId && (raw.includes('<') || raw === provider.key)) {
return { kind: 'iconify', id: trustedId }
}
if (!raw.includes('<')) {
return { kind: 'text', text: raw }
}
return trustedId ? { kind: 'iconify', id: trustedId } : null
}
function loadLoginOptions () {
// Use the init response data that was loaded in App.vue
if (window.initResponse) {

View File

@ -15,11 +15,9 @@ linters:
- staticcheck
- unconvert
- unused
linters-settings:
gocyclo:
min-complexity: 5
issues:
exclude-dirs:
- gen
settings:
gocyclo:
min-complexity: 5
exclusions:
paths:
- gen

View File

@ -53,11 +53,11 @@ find-flakey-tests-inf:
go run ./scripts/find-flakey-tests-inf
go-tools:
go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest"
go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2"
.PHONY: unittests unittests-fast find-flakey-tests find-flakey-tests-inf
go-tools-all:
go install "github.com/bufbuild/buf/cmd/buf"
go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest"
go install "github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2"
go install "google.golang.org/protobuf/cmd/protoc-gen-go"

View File

@ -349,13 +349,13 @@ func getUserInfo(cfg *config.Config, client *http.Client, provider *config.OAuth
return ret
}
defer func() { _ = res.Body.Close() }()
if res.StatusCode != http.StatusOK {
log.Errorf("Failed to get user data: %v", res.StatusCode)
return ret
}
defer func() { _ = res.Body.Close() }()
contents, err := io.ReadAll(res.Body)
if err != nil {

View File

@ -8,6 +8,7 @@ import (
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserLoadingFromConfig(t *testing.T) {
@ -30,13 +31,13 @@ actions:
// Create temporary file
tmpFile, err := os.CreateTemp("", "test_config_*.yaml")
assert.NoError(t, err, "Should create temporary file")
require.NoError(t, err, "Should create temporary file")
defer func() { _ = os.Remove(tmpFile.Name()) }()
// Write test config to file
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
assert.NoError(t, tmpFile.Close())
require.NoError(t, err, "Should write test config to file")
require.NoError(t, tmpFile.Close())
// Load config using koanf
k := koanf.New(".")
@ -81,12 +82,12 @@ actions:
`
tmpFile, err := os.CreateTemp("", "test_config_empty_*.yaml")
assert.NoError(t, err, "Should create temporary file")
require.NoError(t, err, "Should create temporary file")
defer func() { _ = os.Remove(tmpFile.Name()) }()
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
assert.NoError(t, tmpFile.Close())
require.NoError(t, err, "Should write test config to file")
require.NoError(t, tmpFile.Close())
k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
@ -116,12 +117,12 @@ actions:
`
tmpFile, err := os.CreateTemp("", "test_config_disabled_*.yaml")
assert.NoError(t, err, "Should create temporary file")
require.NoError(t, err, "Should create temporary file")
defer func() { _ = os.Remove(tmpFile.Name()) }()
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
assert.NoError(t, tmpFile.Close())
require.NoError(t, err, "Should write test config to file")
require.NoError(t, tmpFile.Close())
k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
@ -147,12 +148,12 @@ actions:
`
tmpFile, err := os.CreateTemp("", "test_config_no_auth_*.yaml")
assert.NoError(t, err, "Should create temporary file")
require.NoError(t, err, "Should create temporary file")
defer func() { _ = os.Remove(tmpFile.Name()) }()
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
assert.NoError(t, tmpFile.Close())
require.NoError(t, err, "Should write test config to file")
require.NoError(t, tmpFile.Close())
k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())

View File

@ -74,7 +74,11 @@ func watchPath(ctx *watchContext) {
return
}
defer func() { _ = watcher.Close() }()
defer func() {
if err := watcher.Close(); err != nil {
log.Errorf("Failed to close file watcher: %v", err)
}
}()
done := make(chan bool)

View File

@ -23,7 +23,12 @@ func WriteFile(filename string, out []byte) {
return
}
_ = handle.Close()
if err := handle.Close(); err != nil {
log.WithFields(log.Fields{
"error": err,
"filename": filename,
}).Errorf("Failed to close %v", filename)
}
}
err := os.WriteFile(filename, out, 0600)

View File

@ -8,6 +8,8 @@ import (
"path/filepath"
"runtime"
"strings"
log "github.com/sirupsen/logrus"
)
type RuntimeInfo struct {
@ -85,6 +87,14 @@ func getOsReleasePrettyName() string {
return ""
}
defer func() {
if closeErr := handle.Close(); closeErr != nil {
log.WithFields(log.Fields{
"error": closeErr,
}).Warn("Failed to close /etc/os-release")
}
}()
scanner := bufio.NewScanner(handle)
scanner.Split(bufio.ScanLines)
@ -96,7 +106,5 @@ func getOsReleasePrettyName() string {
}
}
_ = handle.Close()
return "notfound"
}

View File

@ -202,10 +202,13 @@ func appendFile(path, content string) error {
if err != nil {
return err
}
defer func() { _ = file.Close() }()
_, err = file.WriteString(content)
return err
_, writeErr := file.WriteString(content)
closeErr := file.Close()
if writeErr != nil {
return writeErr
}
return closeErr
}
func newTestRunState() *testRunState {