feat: refresh a single repository from its own button (#2)

* feat(refresh): refresh a single repository with POST /refresh?repo=

The endpoint refreshes one configured repository and returns the full state plus the repo name.
Each repository has a 60-second cooldown that is separate from the full-refresh limit.
A running full refresh or a refresh of the same repository returns 409.
An unknown repository returns 404. Cross-site and disabled-config behaviour stay the same.
The collector reuses its fetch path and replaces only that repository.

* feat(ui): add a refresh button beside each repository

The button appears in the project list and in each attention group header.
It stays hidden when the server disables manual refresh.
The button shows a spinner while its repository refreshes and reports refusals in the notice line.
The store swaps in the returned state, so no extra fetch follows.
This commit is contained in:
2026-10-02 23:16:22 +01:00
committed by GitHub
parent 4c2c9d3341
commit 8cbc495dc7
16 changed files with 760 additions and 19 deletions
+19 -2
View File
@@ -2,11 +2,20 @@
import { faCodePullRequest, faCircleDot, faGears } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import RepoRefreshButton from '@/components/RepoRefreshButton.vue'
import { ageSince, isZeroTime } from '@/lib/format'
import type { Repo } from '@/lib/types'
const props = defineProps<{ repos: Repo[]; total: number; focused: string | null; nowMs: number }>()
defineEmits<{ select: [fullName: string] }>()
const props = defineProps<{
repos: Repo[]
total: number
focused: string | null
nowMs: number
manualRefresh?: boolean
refreshing?: string[]
scanning?: boolean
}>()
defineEmits<{ select: [fullName: string]; refresh: [fullName: string] }>()
/** Rows rendered at first and added each time the list is scrolled near its end. */
const PAGE_SIZE = 20
@@ -111,6 +120,14 @@ const LABEL = { crit: 'Critical', warn: 'Warning', ok: 'Healthy' } as const
<a v-else :href="repo.releasesUrl" class="text-muted-foreground hover:text-link text-xs"
>no release</a
>
<RepoRefreshButton
v-if="manualRefresh"
:repo="repo.fullName"
:busy="refreshing?.includes(repo.fullName) ?? false"
:blocked="scanning ?? false"
class="-my-1 -mr-1"
@refresh="$emit('refresh', repo.fullName)"
/>
</div>
<div
class="text-muted-foreground mt-1 flex items-center gap-4 pl-[18px] text-xs tabular-nums"