mirror of
https://github.com/lukaszraczylo/ghtracker.git
synced 2026-10-13 00:48:42 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1852ea0885 |
+3
-3
@@ -64,7 +64,7 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="mx-auto max-w-7xl px-5 py-6 sm:px-8">
|
||||
<main class="w-full px-4 py-4 sm:px-6 sm:py-6 lg:px-10">
|
||||
<AppHeader
|
||||
:last-refresh="state?.lastRefresh ?? ''"
|
||||
:now-ms="nowMs"
|
||||
@@ -92,7 +92,7 @@ onBeforeUnmount(() => {
|
||||
Fetching data from GitHub
|
||||
</p>
|
||||
<template v-else>
|
||||
<div class="mt-12">
|
||||
<div class="mt-8 sm:mt-12">
|
||||
<FleetOverview
|
||||
:crit="state.counts.crit"
|
||||
:warn="state.counts.warn"
|
||||
@@ -106,7 +106,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mt-14 grid grid-cols-[minmax(0,1fr)] gap-x-10 gap-y-12 lg:grid-cols-[minmax(0,1fr)_23rem]"
|
||||
class="mt-10 grid grid-cols-[minmax(0,1fr)] gap-x-10 gap-y-10 sm:mt-14 lg:grid-cols-[minmax(0,1fr)_24rem] 2xl:grid-cols-[minmax(0,1fr)_28rem]"
|
||||
>
|
||||
<AttentionList
|
||||
ref="list"
|
||||
|
||||
@@ -28,7 +28,7 @@ const nextIn = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="flex items-center justify-between gap-4">
|
||||
<header class="flex flex-wrap items-center justify-between gap-x-4 gap-y-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
class="border-border bg-surface text-link flex size-9 items-center justify-center rounded-md border"
|
||||
@@ -37,8 +37,12 @@ const nextIn = computed(() => {
|
||||
</span>
|
||||
<span class="text-lg font-semibold tracking-tight">ghtracker</span>
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-5 text-sm">
|
||||
<span v-if="updated" class="hidden sm:inline" data-test="updated">
|
||||
<div class="text-muted-foreground flex flex-wrap items-center gap-x-5 gap-y-1 text-sm">
|
||||
<span
|
||||
v-if="updated"
|
||||
class="order-last w-full text-xs sm:order-none sm:w-auto sm:text-sm"
|
||||
data-test="updated"
|
||||
>
|
||||
Scanned {{ updated }} ago
|
||||
<span class="text-muted-foreground/70"> · next in {{ nextIn }}</span>
|
||||
</span>
|
||||
@@ -52,7 +56,8 @@ const nextIn = computed(() => {
|
||||
@click="$emit('rescan')"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faArrowsRotate" :spin="scanning" aria-hidden="true" />
|
||||
{{ scanning ? 'Rescanning…' : 'Rescan repositories' }}
|
||||
<span class="hidden sm:inline">{{ scanning ? 'Rescanning…' : 'Rescan repositories' }}</span>
|
||||
<span class="sm:hidden">{{ scanning ? 'Scanning…' : 'Rescan' }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -43,7 +43,7 @@ defineExpose({ focusSearch: () => bar.value?.focus() })
|
||||
<FontAwesomeIcon :icon="faCircleCheck" class="text-ok text-xl" aria-hidden="true" />
|
||||
Nothing matches the current filters.
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-4">
|
||||
<div v-else class="grid grid-cols-[minmax(0,1fr)] items-start gap-4 2xl:grid-cols-2">
|
||||
<RepoGroup
|
||||
v-for="block in blocks"
|
||||
:key="block.repo.fullName"
|
||||
|
||||
@@ -12,7 +12,10 @@ const ratio = computed(() => secondsSince(props.row.since, props.nowMs) / props.
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="grid grid-cols-[2rem_minmax(0,1fr)_auto] items-start gap-x-3 py-3" data-test="row">
|
||||
<li
|
||||
class="grid grid-cols-[2rem_minmax(0,1fr)] items-start gap-x-3 gap-y-1 py-3 sm:grid-cols-[2rem_minmax(0,1fr)_auto]"
|
||||
data-test="row"
|
||||
>
|
||||
<span
|
||||
class="flex size-8 items-center justify-center rounded-md"
|
||||
:class="row.severity === 'crit' ? 'bg-crit/12 text-crit' : 'bg-warn/12 text-warn'"
|
||||
@@ -31,7 +34,9 @@ const ratio = computed(() => secondsSince(props.row.since, props.nowMs) / props.
|
||||
{{ shorten(row.detail, 120) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-end gap-1.5 pt-0.5">
|
||||
<div
|
||||
class="col-start-2 flex items-center gap-3 sm:col-start-auto sm:flex-col sm:items-end sm:gap-1.5 sm:pt-0.5"
|
||||
>
|
||||
<span class="text-muted-foreground font-mono text-xs whitespace-nowrap">
|
||||
{{ isZeroTime(row.since) ? '' : ageSince(row.since, nowMs) }}
|
||||
</span>
|
||||
|
||||
@@ -49,7 +49,7 @@ function onOwners(value: unknown): void {
|
||||
<template>
|
||||
<div class="flex flex-col gap-3" data-test="filter-bar">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="relative min-w-56 flex-1">
|
||||
<div class="relative min-w-0 basis-full sm:min-w-56 sm:flex-1 sm:basis-0">
|
||||
<FontAwesomeIcon
|
||||
:icon="faMagnifyingGlass"
|
||||
class="text-muted-foreground pointer-events-none absolute top-1/2 left-3 -translate-y-1/2"
|
||||
@@ -75,6 +75,7 @@ function onOwners(value: unknown): void {
|
||||
</div>
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
class="max-w-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:model-value="filters.severity"
|
||||
@@ -96,6 +97,7 @@ function onOwners(value: unknown): void {
|
||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-2">
|
||||
<ToggleGroup
|
||||
type="multiple"
|
||||
class="max-w-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:model-value="filters.kinds"
|
||||
@@ -117,6 +119,7 @@ function onOwners(value: unknown): void {
|
||||
<ToggleGroup
|
||||
v-if="owners.length > 1"
|
||||
type="multiple"
|
||||
class="max-w-full overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:model-value="filters.owners"
|
||||
|
||||
@@ -57,7 +57,7 @@ const rows = computed<{ label: string; value: number; icon: IconDefinition; tone
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="grid grid-cols-[minmax(0,1fr)] items-end gap-x-14 gap-y-8 lg:grid-cols-[minmax(0,1fr)_20rem]"
|
||||
class="grid grid-cols-[minmax(0,1fr)] items-end gap-x-14 gap-y-8 lg:grid-cols-[minmax(0,1fr)_22rem]"
|
||||
aria-label="Fleet status"
|
||||
>
|
||||
<div>
|
||||
@@ -68,22 +68,22 @@ const rows = computed<{ label: string; value: number; icon: IconDefinition; tone
|
||||
<template v-else>All {{ repos.length }} projects are healthy</template>
|
||||
</p>
|
||||
<h1
|
||||
class="flex flex-wrap items-baseline gap-x-10 gap-y-1 font-semibold tracking-tight"
|
||||
class="flex flex-wrap items-baseline gap-x-6 gap-y-1 sm:gap-x-10 font-semibold tracking-tight"
|
||||
data-test="headline"
|
||||
>
|
||||
<template v-if="crit > 0 || warn > 0">
|
||||
<span v-if="crit > 0" class="text-crit">
|
||||
<span class="text-7xl leading-none tabular-nums">{{ crit }}</span>
|
||||
<span class="text-5xl leading-none tabular-nums sm:text-7xl">{{ crit }}</span>
|
||||
<span class="ml-2 text-xl font-medium">critical</span>
|
||||
</span>
|
||||
<span v-if="warn > 0" class="text-warn">
|
||||
<span class="text-7xl leading-none tabular-nums">{{ warn }}</span>
|
||||
<span class="text-5xl leading-none tabular-nums sm:text-7xl">{{ warn }}</span>
|
||||
<span class="ml-2 text-xl font-medium">{{ warn === 1 ? 'warning' : 'warnings' }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<span v-else class="text-ok text-7xl leading-none">All clear</span>
|
||||
<span v-else class="text-ok text-5xl leading-none sm:text-7xl">All clear</span>
|
||||
</h1>
|
||||
<div class="mt-8">
|
||||
<div class="mt-6 sm:mt-8">
|
||||
<FleetStrip
|
||||
:repos="repos"
|
||||
:counts="counts"
|
||||
@@ -101,7 +101,7 @@ const rows = computed<{ label: string; value: number; icon: IconDefinition; tone
|
||||
<span class="flex items-center gap-1.5"
|
||||
><span class="bg-ok/40 size-2 rounded-[2px]"></span>healthy</span
|
||||
>
|
||||
<span class="ml-auto">Select a segment to focus a project</span>
|
||||
<span class="sm:ml-auto">Tap a segment to focus a project</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,14 +24,19 @@ function summary(c: RepoCounts | undefined): string {
|
||||
|
||||
<template>
|
||||
<TooltipProvider :delay-duration="80">
|
||||
<div class="flex gap-1" role="group" aria-label="Project health" data-test="strip">
|
||||
<div
|
||||
class="grid grid-cols-[repeat(auto-fill,minmax(1.5rem,1fr))] gap-1"
|
||||
role="group"
|
||||
aria-label="Project health"
|
||||
data-test="strip"
|
||||
>
|
||||
<Tooltip v-for="repo in repos" :key="repo.fullName">
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="`${repo.fullName}: ${summary(counts.get(repo.fullName))}`"
|
||||
:aria-pressed="focused === repo.fullName"
|
||||
class="h-14 min-w-0 flex-1 rounded-[3px] transition-colors focus-visible:outline-offset-1"
|
||||
class="h-9 min-w-0 rounded-[3px] sm:h-11 transition-colors focus-visible:outline-offset-1"
|
||||
:class="[
|
||||
SEGMENT[repo.health],
|
||||
focused === repo.fullName
|
||||
|
||||
@@ -26,7 +26,10 @@ const LABEL = { crit: 'Critical', warn: 'Warning', ok: 'Healthy' } as const
|
||||
>
|
||||
No projects match the search.
|
||||
</p>
|
||||
<ul v-else class="border-border bg-surface divide-border divide-y rounded-lg border">
|
||||
<ul
|
||||
v-else
|
||||
class="border-border bg-surface divide-border max-h-[70vh] divide-y overflow-y-auto rounded-lg border lg:max-h-[calc(100vh-7rem)]"
|
||||
>
|
||||
<li
|
||||
v-for="repo in repos"
|
||||
:key="repo.fullName"
|
||||
|
||||
@@ -39,12 +39,12 @@ describe('AppHeader', () => {
|
||||
|
||||
it('emits rescan and shows progress while a scan runs', async () => {
|
||||
const w = mount(AppHeader, { props: { ...base, manualRefresh: true } })
|
||||
expect(w.get('[data-test=rescan]').text()).toBe('Rescan repositories')
|
||||
expect(w.get('[data-test=rescan]').text()).toContain('Rescan repositories')
|
||||
await w.get('[data-test=rescan]').trigger('click')
|
||||
expect(w.emitted('rescan')).toHaveLength(1)
|
||||
const busy = mount(AppHeader, { props: { ...base, manualRefresh: true, scanning: true } })
|
||||
expect(busy.get('[data-test=rescan]').attributes('disabled')).toBeDefined()
|
||||
expect(busy.get('[data-test=rescan]').text()).toBe('Rescanning…')
|
||||
expect(busy.get('[data-test=rescan]').text()).toContain('Rescanning…')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user