Refactor package filtering and display components
Replaced inline package filtering and table logic with dedicated reusable components: `PackageFilters` and `PackageTable`. Introduced a composable (`usePackageFilters`) for managing filtering logic and state, and optimized display logic with a new `usePackageDisplay` composable. This improves maintainability, readability, and separation of concerns.
This commit is contained in:
@@ -1,398 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-sheet :color="TRANSPARENT_COLOR" class="mt-6" width="100%">
|
<v-sheet :color="TRANSPARENT_COLOR" class="mt-6" width="100%">
|
||||||
<h5 class="text-h5 mb-4">Packages</h5>
|
<h5 class="text-h5 mb-4">Packages</h5>
|
||||||
<v-row :style="mobile ? '' : `height: ${ROW_HEIGHT}px`" width="100%">
|
|
||||||
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2">
|
<PackageFilters :filter-options="filterOptions" :total-pages="filterResult.totalPages" />
|
||||||
<v-text-field
|
|
||||||
v-model="inputPkgBase"
|
<PackageTable :no-results="filterResult.noResults" :packages="filterResult.packages" />
|
||||||
clearable
|
|
||||||
color="primary"
|
|
||||||
placeholder="Search Pkgbase"
|
|
||||||
variant="outlined"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2 mt-n6 mt-sm-0">
|
|
||||||
<v-select
|
|
||||||
v-model="selectedRepo"
|
|
||||||
:items="selectRepoItems"
|
|
||||||
clearable
|
|
||||||
color="primary"
|
|
||||||
item-title="title"
|
|
||||||
item-value="value"
|
|
||||||
placeholder="Any Repo"
|
|
||||||
return-object
|
|
||||||
single-line
|
|
||||||
variant="outlined"></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col class="v-col-12 v-col-sm-2 v-col-lg-3 mt-n6 mt-sm-0">
|
|
||||||
<v-select
|
|
||||||
v-model="selectedStatuses"
|
|
||||||
:items="selectStatusItems"
|
|
||||||
chips
|
|
||||||
closable-chips
|
|
||||||
color="primary"
|
|
||||||
density="default"
|
|
||||||
item-title="title"
|
|
||||||
item-value="value"
|
|
||||||
multiple
|
|
||||||
placeholder="Any Status"
|
|
||||||
return-object
|
|
||||||
variant="outlined"></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2 mt-n6 mt-sm-0">
|
|
||||||
<v-switch v-model="enableExact" color="primary" label="Exact search"></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col :class="mobile ? 'mt-n6' : ''" :cols="mobile ? 12 : 'auto'" class="ms-auto">
|
|
||||||
<v-pagination
|
|
||||||
v-model="page"
|
|
||||||
:length="pagesMax"
|
|
||||||
:total-visible="mobile ? undefined : 3"
|
|
||||||
active-color="primary"
|
|
||||||
density="comfortable"
|
|
||||||
start="1"
|
|
||||||
variant="text"></v-pagination>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-table class="mt-2 mt-sm-6" style="width: 100%; background: transparent; font-size: 1rem">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Repository</th>
|
|
||||||
<th scope="col">Pkgbase</th>
|
|
||||||
<th scope="col">Status</th>
|
|
||||||
<th scope="col">Reason</th>
|
|
||||||
<th scope="col">
|
|
||||||
LTO
|
|
||||||
<v-tooltip activator="parent" location="bottom">
|
|
||||||
Link time optimization;
|
|
||||||
<br />
|
|
||||||
Does not guarantee that package is actually built with LTO
|
|
||||||
</v-tooltip>
|
|
||||||
</th>
|
|
||||||
<th scope="col">
|
|
||||||
DS
|
|
||||||
<v-tooltip activator="parent" location="bottom">
|
|
||||||
Debug-symbols available via debuginfod
|
|
||||||
</v-tooltip>
|
|
||||||
</th>
|
|
||||||
<th scope="col">Archlinux Version</th>
|
|
||||||
<th scope="col">ALHP Version</th>
|
|
||||||
<th class="text-end" scope="col" style="width: 30px">Info</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="main-tbody">
|
|
||||||
<tr v-if="noPackagesFound">
|
|
||||||
No Packages found
|
|
||||||
</tr>
|
|
||||||
<template v-else>
|
|
||||||
<tr
|
|
||||||
v-for="(pkg, index) in packages"
|
|
||||||
:key="index"
|
|
||||||
:style="`background-color: ${getStatusColor(pkg.status)};`">
|
|
||||||
<td class="font-weight-bold text-no-wrap">
|
|
||||||
{{ repoName(pkg.repo) }}
|
|
||||||
<v-chip
|
|
||||||
:color="getVersionColor(repoVersion(pkg.repo))"
|
|
||||||
class="ms-2"
|
|
||||||
density="comfortable"
|
|
||||||
label
|
|
||||||
variant="flat">
|
|
||||||
{{ repoVersion(pkg.repo) }}
|
|
||||||
</v-chip>
|
|
||||||
</td>
|
|
||||||
<td class="text-no-wrap">{{ pkg.pkgbase }}</td>
|
|
||||||
<td>{{ pkg.status.toLocaleUpperCase() }}</td>
|
|
||||||
<td>{{ pkg.skip_reason || '' }}</td>
|
|
||||||
<td><i :class="getLto(pkg.lto).class" :title="getLto(pkg.lto).title"></i></td>
|
|
||||||
<td>
|
|
||||||
<i
|
|
||||||
:class="getDs(pkg.debug_symbols).class"
|
|
||||||
:title="getDs(pkg.debug_symbols).title"></i>
|
|
||||||
</td>
|
|
||||||
<td>{{ pkg.arch_version }}</td>
|
|
||||||
<td>{{ pkg.repo_version }}</td>
|
|
||||||
<td class="d-flex align-center" style="gap: 3px">
|
|
||||||
<a
|
|
||||||
v-if="pkg.status === 'failed'"
|
|
||||||
:href="`https://alhp.dev/logs/${pkg.repo.slice(pkg.repo.indexOf('-') + 1)}/${pkg.pkgbase}.log`"
|
|
||||||
class="text-decoration-none"
|
|
||||||
style="
|
|
||||||
color: darkgrey;
|
|
||||||
transform: translateY(-3px) translateX(-22px);
|
|
||||||
max-width: 15px;
|
|
||||||
"
|
|
||||||
target="_blank">
|
|
||||||
<i class="fa fa-file-text fa-lg"></i>
|
|
||||||
</a>
|
|
||||||
<span v-else style="width: 15px"></span>
|
|
||||||
<a
|
|
||||||
:href="`https://archlinux.org/packages/?q=${pkg.pkgbase}`"
|
|
||||||
class="text-decoration-none font-weight-bold"
|
|
||||||
style="
|
|
||||||
color: darkgrey;
|
|
||||||
font-size: 18px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
width: 15px;
|
|
||||||
transform: translateX(-15px);
|
|
||||||
"
|
|
||||||
target="_blank"
|
|
||||||
title="ArchWeb">
|
|
||||||
AW
|
|
||||||
</a>
|
|
||||||
<span
|
|
||||||
v-if="pkg.build_date && pkg.peak_mem"
|
|
||||||
class="fa fa-info-circle fa-lg"
|
|
||||||
style="color: darkgrey; transform: translateY(-1px); max-width: 15px !important">
|
|
||||||
<v-tooltip activator="parent" location="start">
|
|
||||||
{{ `Built on ${pkg.build_date}` }}
|
|
||||||
<br />
|
|
||||||
{{ `Peak-Memory: ${pkg.peak_mem}` }}
|
|
||||||
</v-tooltip>
|
|
||||||
</span>
|
|
||||||
<span v-else style="max-width: 15px !important"></span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</tbody>
|
|
||||||
</v-table>
|
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { type Package } from '@/types/Package'
|
import { usePackageFilters } from '@/composables/Packages/usePackageFilters'
|
||||||
import { type Packages } from '@/types/Packages'
|
import { TRANSPARENT_COLOR } from '@/config/constants'
|
||||||
import { useDisplay } from 'vuetify'
|
import PackageFilters from '@/components/Packages/PackageFilters.vue'
|
||||||
|
import PackageTable from '@/components/Packages/PackageTable.vue'
|
||||||
|
|
||||||
// Constants
|
const { filterOptions, filterResult, initFromUrl } = usePackageFilters()
|
||||||
const OFFSET = 50
|
|
||||||
const ROW_HEIGHT = 60
|
|
||||||
const TRANSPARENT_COLOR = 'transparent'
|
|
||||||
|
|
||||||
// Refs
|
|
||||||
const page = ref(0)
|
|
||||||
const pagesMax = ref(1)
|
|
||||||
const packages = ref<Array<Package>>([])
|
|
||||||
const noPackagesFound = ref(false)
|
|
||||||
const init = ref(false)
|
|
||||||
const inputPkgBase = ref('')
|
|
||||||
const selectedRepo = ref<{ title: string; value: string } | undefined>(undefined)
|
|
||||||
const selectedStatuses = ref<Array<{ title: string; value: string }>>([])
|
|
||||||
const enableExact = ref(false)
|
|
||||||
|
|
||||||
// Display
|
|
||||||
const { mobile } = useDisplay()
|
|
||||||
|
|
||||||
// Select Items
|
|
||||||
const selectRepoItems = [
|
|
||||||
{ title: 'core-x86-64-v2', value: 'core-x86-64-v2' },
|
|
||||||
{ title: 'extra-x86-64-v2', value: 'extra-x86-64-v2' },
|
|
||||||
{ title: 'multilib-x86-64-v2', value: 'multilib-x86-64-v2' },
|
|
||||||
{ title: 'core-x86-64-v3', value: 'core-x86-64-v3' },
|
|
||||||
{ title: 'extra-x86-64-v3', value: 'extra-x86-64-v3' },
|
|
||||||
{ title: 'multilib-x86-64-v3', value: 'multilib-x86-64-v3' },
|
|
||||||
{ title: 'core-x86-64-v4', value: 'core-x86-64-v4' },
|
|
||||||
{ title: 'extra-x86-64-v4', value: 'extra-x86-64-v4' },
|
|
||||||
{ title: 'multilib-x86-64-v4', value: 'multilib-x86-64-v4' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const selectStatusItems = [
|
|
||||||
{ title: 'Latest', value: 'latest' },
|
|
||||||
{ title: 'Built', value: 'built' },
|
|
||||||
{ title: 'Failed', value: 'failed' },
|
|
||||||
{ title: 'Skipped', value: 'skipped' },
|
|
||||||
{ title: 'Delayed', value: 'delayed' },
|
|
||||||
{ title: 'Queued', value: 'queued' },
|
|
||||||
{ title: 'Building', value: 'building' },
|
|
||||||
{ title: 'Signing', value: 'signing' },
|
|
||||||
{ title: 'Unknown', value: 'unknown' }
|
|
||||||
]
|
|
||||||
|
|
||||||
// Computed properties
|
|
||||||
const repoName = (repo: string) => repo.split('-')[0]
|
|
||||||
const repoVersion = (repo: string) => repo.split('-')[repo.split('-').length - 1]
|
|
||||||
|
|
||||||
// Helper function for getting the color associated with a version
|
|
||||||
const getVersionColor = (version: string) => {
|
|
||||||
switch (version) {
|
|
||||||
case 'v2':
|
|
||||||
return '#3498db'
|
|
||||||
case 'v3':
|
|
||||||
return '#f39c12'
|
|
||||||
case 'v4':
|
|
||||||
return '#2ecc71'
|
|
||||||
default:
|
|
||||||
return 'grey'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function for getting the status color
|
|
||||||
const getStatusColor = (status: Package['status']) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'skipped':
|
|
||||||
return '#373737'
|
|
||||||
case 'queued':
|
|
||||||
return '#5d2f03'
|
|
||||||
case 'latest':
|
|
||||||
return ''
|
|
||||||
case 'failed':
|
|
||||||
return '#4f140f'
|
|
||||||
case 'signing':
|
|
||||||
return '#093372'
|
|
||||||
case 'building':
|
|
||||||
return '#084f46'
|
|
||||||
case 'unknown':
|
|
||||||
return '#191919'
|
|
||||||
default:
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getLto = (lto: Package['lto']) => {
|
|
||||||
switch (lto) {
|
|
||||||
case 'enabled':
|
|
||||||
return {
|
|
||||||
title: 'built with LTO',
|
|
||||||
class: 'fa fa-check fa-lg text-success'
|
|
||||||
}
|
|
||||||
case 'unknown':
|
|
||||||
return {
|
|
||||||
title: 'not built with LTO yet',
|
|
||||||
class: 'fa fa-hourglass-o fa-lg text-grey'
|
|
||||||
}
|
|
||||||
case 'disabled':
|
|
||||||
return {
|
|
||||||
title: 'LTO explicitly disabled',
|
|
||||||
class: 'fa fa-times fa-lg text-red'
|
|
||||||
}
|
|
||||||
case 'auto_disabled':
|
|
||||||
return {
|
|
||||||
title: 'LTO automatically disabled',
|
|
||||||
class: 'fa fa-times-circle-o fa-lg text-amber'
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return { title: '', class: '' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDs = (ds: Package['debug_symbols']) => {
|
|
||||||
switch (ds) {
|
|
||||||
case 'available':
|
|
||||||
return {
|
|
||||||
title: 'Debug symbols available',
|
|
||||||
class: 'fa fa-check fa-lg text-success'
|
|
||||||
}
|
|
||||||
case 'unknown':
|
|
||||||
return {
|
|
||||||
title: 'Not built yet',
|
|
||||||
class: 'fa fa-hourglass-o fa-lg text-grey'
|
|
||||||
}
|
|
||||||
case 'not_available':
|
|
||||||
return {
|
|
||||||
title: 'Not built with debug symbols',
|
|
||||||
class: 'fa fa-times fa-lg text-red'
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return { title: '', class: '' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function for updating URL params dynamically
|
|
||||||
const updateUrlParams = () => {
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
if (page.value > 0) {
|
|
||||||
params.set('offset', (page.value * OFFSET).toString())
|
|
||||||
}
|
|
||||||
if (inputPkgBase.value) {
|
|
||||||
params.set('pkgbase', inputPkgBase.value.toLowerCase())
|
|
||||||
}
|
|
||||||
if (selectedRepo.value) {
|
|
||||||
params.set('repo', selectedRepo.value.value)
|
|
||||||
}
|
|
||||||
if (selectedStatuses.value.length > 0) {
|
|
||||||
selectedStatuses.value.forEach((status) => {
|
|
||||||
params.append('status', status.value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (enableExact.value) {
|
|
||||||
params.set('exact', '')
|
|
||||||
}
|
|
||||||
window.history.pushState(null, '', `${window.location.pathname}?${params}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchPackages = () => {
|
|
||||||
const offset = OFFSET * page.value
|
|
||||||
noPackagesFound.value = false
|
|
||||||
const params = new URLSearchParams()
|
|
||||||
if (page.value > 0) {
|
|
||||||
params.set('offset', offset.toString())
|
|
||||||
}
|
|
||||||
if (inputPkgBase.value) {
|
|
||||||
params.set('pkgbase', inputPkgBase.value.toLowerCase())
|
|
||||||
}
|
|
||||||
if (selectedRepo.value) {
|
|
||||||
params.set('repo', selectedRepo.value.value)
|
|
||||||
}
|
|
||||||
if (selectedStatuses.value.length > 0) {
|
|
||||||
selectedStatuses.value.forEach((status) => {
|
|
||||||
params.append('status', status.value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (enableExact.value) {
|
|
||||||
params.set('exact', '')
|
|
||||||
}
|
|
||||||
params.set('limit', OFFSET.toString())
|
|
||||||
params.set('offset', offset.toString())
|
|
||||||
fetch('https://api.alhp.dev/packages?' + params, {
|
|
||||||
method: 'GET'
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.ok) return response.json()
|
|
||||||
if (response.status === 404) {
|
|
||||||
noPackagesFound.value = true
|
|
||||||
pagesMax.value = 1
|
|
||||||
} else {
|
|
||||||
throw new Error(response.statusText)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((json: Packages) => {
|
|
||||||
if (!json) return
|
|
||||||
pagesMax.value = json.total / OFFSET + 1
|
|
||||||
packages.value = json.packages
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Properly set the initial values from URL params
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
initFromUrl()
|
||||||
page.value = urlParams.has('offset')
|
|
||||||
? Math.floor(parseInt(urlParams.get('offset') || '0') / OFFSET)
|
|
||||||
: 0
|
|
||||||
inputPkgBase.value = urlParams.get('pkgbase') || ''
|
|
||||||
const repoValue = urlParams.get('repo')
|
|
||||||
if (repoValue) {
|
|
||||||
selectedRepo.value = { title: repoValue, value: repoValue }
|
|
||||||
} else {
|
|
||||||
selectedRepo.value = undefined
|
|
||||||
}
|
|
||||||
const statuses = urlParams.getAll('status')
|
|
||||||
selectedStatuses.value = statuses.map((status) => ({
|
|
||||||
title: status.toUpperCase(),
|
|
||||||
value: status
|
|
||||||
}))
|
|
||||||
enableExact.value = urlParams.has('exact')
|
|
||||||
searchPackages()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watchers
|
|
||||||
watch(
|
|
||||||
[page, inputPkgBase, selectedRepo, selectedStatuses, enableExact],
|
|
||||||
() => {
|
|
||||||
if (init.value) return
|
|
||||||
updateUrlParams()
|
|
||||||
searchPackages()
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
66
frontend/src/components/Packages/PackageFilters.vue
Normal file
66
frontend/src/components/Packages/PackageFilters.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<v-row :style="mobile ? '' : `height: ${ROW_HEIGHT}px`" width="100%">
|
||||||
|
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2">
|
||||||
|
<v-text-field
|
||||||
|
v-model="filterOptions.pkgbaseSearch"
|
||||||
|
clearable
|
||||||
|
color="primary"
|
||||||
|
placeholder="Search Pkgbase"
|
||||||
|
variant="outlined"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2 mt-n6 mt-sm-0">
|
||||||
|
<v-select
|
||||||
|
v-model="filterOptions.repo"
|
||||||
|
:items="REPO_ITEMS"
|
||||||
|
clearable
|
||||||
|
color="primary"
|
||||||
|
item-title="title"
|
||||||
|
item-value="value"
|
||||||
|
placeholder="Any Repo"
|
||||||
|
return-object
|
||||||
|
single-line
|
||||||
|
variant="outlined"></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col class="v-col-12 v-col-sm-2 v-col-lg-3 mt-n6 mt-sm-0">
|
||||||
|
<v-select
|
||||||
|
v-model="filterOptions.statuses"
|
||||||
|
:items="STATUS_ITEMS"
|
||||||
|
chips
|
||||||
|
closable-chips
|
||||||
|
color="primary"
|
||||||
|
density="default"
|
||||||
|
item-title="title"
|
||||||
|
item-value="value"
|
||||||
|
multiple
|
||||||
|
placeholder="Any Status"
|
||||||
|
return-object
|
||||||
|
variant="outlined"></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2 mt-n6 mt-sm-0">
|
||||||
|
<v-switch v-model="filterOptions.exactSearch" color="primary" label="Exact search"></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col :class="mobile ? 'mt-n6' : ''" :cols="mobile ? 12 : 'auto'" class="ms-auto">
|
||||||
|
<v-pagination
|
||||||
|
v-model="filterOptions.page"
|
||||||
|
:length="totalPages"
|
||||||
|
:total-visible="mobile ? undefined : 3"
|
||||||
|
active-color="primary"
|
||||||
|
density="comfortable"
|
||||||
|
start="1"
|
||||||
|
variant="text"></v-pagination>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useDisplay } from 'vuetify'
|
||||||
|
import { REPO_ITEMS, ROW_HEIGHT, STATUS_ITEMS } from '@/config/constants'
|
||||||
|
import { type FilterOptions } from '@/composables/Packages/usePackageFilters'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
filterOptions: FilterOptions
|
||||||
|
totalPages: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { mobile } = useDisplay()
|
||||||
|
</script>
|
115
frontend/src/components/Packages/PackageTable.vue
Normal file
115
frontend/src/components/Packages/PackageTable.vue
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<v-table class="mt-2 mt-sm-6" style="width: 100%; background: transparent; font-size: 1rem">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Repository</th>
|
||||||
|
<th scope="col">Pkgbase</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">Reason</th>
|
||||||
|
<th scope="col">
|
||||||
|
LTO
|
||||||
|
<v-tooltip activator="parent" location="bottom">
|
||||||
|
Link time optimization;
|
||||||
|
<br />
|
||||||
|
Does not guarantee that package is actually built with LTO
|
||||||
|
</v-tooltip>
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
DS
|
||||||
|
<v-tooltip activator="parent" location="bottom">
|
||||||
|
Debug-symbols available via debuginfod
|
||||||
|
</v-tooltip>
|
||||||
|
</th>
|
||||||
|
<th scope="col">Archlinux Version</th>
|
||||||
|
<th scope="col">ALHP Version</th>
|
||||||
|
<th class="text-end" scope="col" style="width: 30px">Info</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="main-tbody">
|
||||||
|
<tr v-if="noResults">
|
||||||
|
No Packages found
|
||||||
|
</tr>
|
||||||
|
<template v-else>
|
||||||
|
<tr
|
||||||
|
v-for="(pkg, index) in packages"
|
||||||
|
:key="index"
|
||||||
|
:style="`background-color: ${getStatusColor(pkg.status)};`">
|
||||||
|
<td class="font-weight-bold text-no-wrap">
|
||||||
|
{{ repoName(pkg.repo) }}
|
||||||
|
<v-chip
|
||||||
|
:color="getVersionColor(repoVersion(pkg.repo))"
|
||||||
|
class="ms-2"
|
||||||
|
density="comfortable"
|
||||||
|
label
|
||||||
|
variant="flat">
|
||||||
|
{{ repoVersion(pkg.repo) }}
|
||||||
|
</v-chip>
|
||||||
|
</td>
|
||||||
|
<td class="text-no-wrap">{{ pkg.pkgbase }}</td>
|
||||||
|
<td>{{ pkg.status.toLocaleUpperCase() }}</td>
|
||||||
|
<td>{{ pkg.skip_reason || '' }}</td>
|
||||||
|
<td><i :class="getLto(pkg.lto).class" :title="getLto(pkg.lto).title"></i></td>
|
||||||
|
<td>
|
||||||
|
<i :class="getDs(pkg.debug_symbols).class" :title="getDs(pkg.debug_symbols).title"></i>
|
||||||
|
</td>
|
||||||
|
<td>{{ pkg.arch_version }}</td>
|
||||||
|
<td>{{ pkg.repo_version }}</td>
|
||||||
|
<td class="d-flex align-center" style="gap: 3px">
|
||||||
|
<a
|
||||||
|
v-if="pkg.status === 'failed'"
|
||||||
|
:href="`https://alhp.dev/logs/${pkg.repo.slice(pkg.repo.indexOf('-') + 1)}/${pkg.pkgbase}.log`"
|
||||||
|
class="text-decoration-none"
|
||||||
|
style="
|
||||||
|
color: darkgrey;
|
||||||
|
transform: translateY(-3px) translateX(-22px);
|
||||||
|
max-width: 15px;
|
||||||
|
"
|
||||||
|
target="_blank">
|
||||||
|
<i class="fa fa-file-text fa-lg"></i>
|
||||||
|
</a>
|
||||||
|
<span v-else style="width: 15px"></span>
|
||||||
|
<a
|
||||||
|
:href="`https://archlinux.org/packages/?q=${pkg.pkgbase}`"
|
||||||
|
class="text-decoration-none font-weight-bold"
|
||||||
|
style="
|
||||||
|
color: darkgrey;
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 15px;
|
||||||
|
transform: translateX(-15px);
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
title="ArchWeb">
|
||||||
|
AW
|
||||||
|
</a>
|
||||||
|
<span
|
||||||
|
v-if="pkg.build_date && pkg.peak_mem"
|
||||||
|
class="fa fa-info-circle fa-lg"
|
||||||
|
style="color: darkgrey; transform: translateY(-1px); max-width: 15px !important">
|
||||||
|
<v-tooltip activator="parent" location="start">
|
||||||
|
{{ `Built on ${pkg.build_date}` }}
|
||||||
|
<br />
|
||||||
|
{{ `Peak-Memory: ${pkg.peak_mem}` }}
|
||||||
|
</v-tooltip>
|
||||||
|
</span>
|
||||||
|
<span v-else style="max-width: 15px !important"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { type Package } from '@/types/Package'
|
||||||
|
import { usePackageDisplay } from '@/composables/Packages/usePackageDisplay'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
packages: Array<Package>
|
||||||
|
noResults: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { repoName, repoVersion, getVersionColor, getStatusColor, getLto, getDs } =
|
||||||
|
usePackageDisplay()
|
||||||
|
</script>
|
98
frontend/src/composables/Packages/usePackageDisplay.ts
Normal file
98
frontend/src/composables/Packages/usePackageDisplay.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { type Package } from '@/types/Package'
|
||||||
|
|
||||||
|
export function usePackageDisplay() {
|
||||||
|
const repoName = (repo: string) => repo.split('-')[0]
|
||||||
|
const repoVersion = (repo: string) => repo.split('-')[repo.split('-').length - 1]
|
||||||
|
|
||||||
|
const getVersionColor = (version: string) => {
|
||||||
|
switch (version) {
|
||||||
|
case 'v2':
|
||||||
|
return '#3498db'
|
||||||
|
case 'v3':
|
||||||
|
return '#f39c12'
|
||||||
|
case 'v4':
|
||||||
|
return '#2ecc71'
|
||||||
|
default:
|
||||||
|
return 'grey'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (status: Package['status']) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'skipped':
|
||||||
|
return '#373737'
|
||||||
|
case 'queued':
|
||||||
|
return '#5d2f03'
|
||||||
|
case 'latest':
|
||||||
|
return ''
|
||||||
|
case 'failed':
|
||||||
|
return '#4f140f'
|
||||||
|
case 'signing':
|
||||||
|
return '#093372'
|
||||||
|
case 'building':
|
||||||
|
return '#084f46'
|
||||||
|
case 'unknown':
|
||||||
|
return '#191919'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLto = (lto: Package['lto']) => {
|
||||||
|
switch (lto) {
|
||||||
|
case 'enabled':
|
||||||
|
return {
|
||||||
|
title: 'built with LTO',
|
||||||
|
class: 'fa fa-check fa-lg text-success'
|
||||||
|
}
|
||||||
|
case 'unknown':
|
||||||
|
return {
|
||||||
|
title: 'not built with LTO yet',
|
||||||
|
class: 'fa fa-hourglass-o fa-lg text-grey'
|
||||||
|
}
|
||||||
|
case 'disabled':
|
||||||
|
return {
|
||||||
|
title: 'LTO explicitly disabled',
|
||||||
|
class: 'fa fa-times fa-lg text-red'
|
||||||
|
}
|
||||||
|
case 'auto_disabled':
|
||||||
|
return {
|
||||||
|
title: 'LTO automatically disabled',
|
||||||
|
class: 'fa fa-times-circle-o fa-lg text-amber'
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return { title: '', class: '' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDs = (ds: Package['debug_symbols']) => {
|
||||||
|
switch (ds) {
|
||||||
|
case 'available':
|
||||||
|
return {
|
||||||
|
title: 'Debug symbols available',
|
||||||
|
class: 'fa fa-check fa-lg text-success'
|
||||||
|
}
|
||||||
|
case 'unknown':
|
||||||
|
return {
|
||||||
|
title: 'Not built yet',
|
||||||
|
class: 'fa fa-hourglass-o fa-lg text-grey'
|
||||||
|
}
|
||||||
|
case 'not_available':
|
||||||
|
return {
|
||||||
|
title: 'Not built with debug symbols',
|
||||||
|
class: 'fa fa-times fa-lg text-red'
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return { title: '', class: '' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
repoName,
|
||||||
|
repoVersion,
|
||||||
|
getVersionColor,
|
||||||
|
getStatusColor,
|
||||||
|
getLto,
|
||||||
|
getDs
|
||||||
|
}
|
||||||
|
}
|
171
frontend/src/composables/Packages/usePackageFilters.ts
Normal file
171
frontend/src/composables/Packages/usePackageFilters.ts
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { type Package } from '@/types/Package'
|
||||||
|
import { useDataStore } from '@/stores/dataStore'
|
||||||
|
|
||||||
|
const OFFSET = 50
|
||||||
|
|
||||||
|
export interface FilterOptions {
|
||||||
|
pkgbaseSearch: string
|
||||||
|
repo?: { title: string; value: string }
|
||||||
|
statuses: Array<{ title: string; value: string }>
|
||||||
|
exactSearch: boolean
|
||||||
|
page: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterResult {
|
||||||
|
packages: Array<Package>
|
||||||
|
totalPages: number
|
||||||
|
noResults: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePackageFilters() {
|
||||||
|
const dataStore = useDataStore()
|
||||||
|
|
||||||
|
const filterOptions = ref<FilterOptions>({
|
||||||
|
pkgbaseSearch: '',
|
||||||
|
repo: undefined,
|
||||||
|
statuses: [],
|
||||||
|
exactSearch: false,
|
||||||
|
page: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const filterResult = ref<FilterResult>({
|
||||||
|
packages: [],
|
||||||
|
totalPages: 1,
|
||||||
|
noResults: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const applyFilters = () => {
|
||||||
|
const { pkgbaseSearch, repo, statuses, exactSearch, page } = filterOptions.value
|
||||||
|
const offset = OFFSET * (page - 1)
|
||||||
|
|
||||||
|
let filteredPackages: Package[] = []
|
||||||
|
|
||||||
|
if (statuses.length === 1) {
|
||||||
|
const statusValue = statuses[0].value as Package['status']
|
||||||
|
const result = dataStore.getPackagesByStatus(statusValue)
|
||||||
|
filteredPackages = result.packages
|
||||||
|
} else {
|
||||||
|
const allPackagesResult = dataStore.getAllPackages()
|
||||||
|
filteredPackages = allPackagesResult.packages
|
||||||
|
|
||||||
|
if (statuses.length > 0) {
|
||||||
|
const statusValues = statuses.map((status) => status.value)
|
||||||
|
filteredPackages = filteredPackages.filter((pkg) => statusValues.includes(pkg.status))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repo) {
|
||||||
|
filteredPackages = filteredPackages.filter((pkg) => pkg.repo === repo.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pkgbaseSearch) {
|
||||||
|
const searchTerm = pkgbaseSearch.toLowerCase()
|
||||||
|
filteredPackages = filteredPackages.filter((pkg) =>
|
||||||
|
exactSearch
|
||||||
|
? pkg.pkgbase.toLowerCase() === searchTerm
|
||||||
|
: pkg.pkgbase.toLowerCase().includes(searchTerm)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredPackages.length > 0) {
|
||||||
|
const startIndex = offset
|
||||||
|
const endIndex = startIndex + OFFSET
|
||||||
|
|
||||||
|
filterResult.value = {
|
||||||
|
packages: filteredPackages.slice(startIndex, endIndex),
|
||||||
|
totalPages: Math.ceil(filteredPackages.length / OFFSET),
|
||||||
|
noResults: false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filterResult.value = {
|
||||||
|
packages: [],
|
||||||
|
totalPages: 1,
|
||||||
|
noResults: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initFromUrl = () => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
|
||||||
|
filterOptions.value = {
|
||||||
|
pkgbaseSearch: urlParams.get('pkgbase') || '',
|
||||||
|
page: urlParams.has('page') ? parseInt(urlParams.get('page') || '1') : 1,
|
||||||
|
exactSearch: urlParams.has('exact'),
|
||||||
|
statuses: urlParams.getAll('status').map((status) => ({
|
||||||
|
title: status.toUpperCase(),
|
||||||
|
value: status
|
||||||
|
})),
|
||||||
|
repo: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const repoValue = urlParams.get('repo')
|
||||||
|
if (repoValue) {
|
||||||
|
filterOptions.value.repo = { title: repoValue, value: repoValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
applyFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUrlParams = () => {
|
||||||
|
const { pkgbaseSearch, repo, statuses, exactSearch, page } = filterOptions.value
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
|
||||||
|
// Only add page parameter if it's not the first page
|
||||||
|
if (page > 1) {
|
||||||
|
params.set('page', page.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pkgbaseSearch) {
|
||||||
|
params.set('pkgbase', pkgbaseSearch.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repo) {
|
||||||
|
params.set('repo', repo.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statuses.length > 0) {
|
||||||
|
statuses.forEach((status) => {
|
||||||
|
params.append('status', status.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exactSearch) {
|
||||||
|
params.set('exact', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsString = params.toString()
|
||||||
|
if (paramsString) {
|
||||||
|
window.history.pushState(null, '', `${window.location.pathname}?${paramsString}`)
|
||||||
|
} else {
|
||||||
|
window.history.pushState(null, '', window.location.pathname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
filterOptions,
|
||||||
|
() => {
|
||||||
|
updateUrlParams()
|
||||||
|
applyFilters()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => dataStore.data.value,
|
||||||
|
() => {
|
||||||
|
if (dataStore.data.value) {
|
||||||
|
applyFilters()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
filterOptions,
|
||||||
|
filterResult,
|
||||||
|
initFromUrl,
|
||||||
|
applyFilters
|
||||||
|
}
|
||||||
|
}
|
28
frontend/src/config/constants.ts
Normal file
28
frontend/src/config/constants.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// UI Constants
|
||||||
|
export const TRANSPARENT_COLOR = 'transparent'
|
||||||
|
export const ROW_HEIGHT = 60
|
||||||
|
|
||||||
|
// Select Items Constants
|
||||||
|
export const REPO_ITEMS = [
|
||||||
|
{ title: 'core-x86-64-v2', value: 'core-x86-64-v2' },
|
||||||
|
{ title: 'core-x86-64-v3', value: 'core-x86-64-v3' },
|
||||||
|
{ title: 'core-x86-64-v4', value: 'core-x86-64-v4' },
|
||||||
|
{ title: 'extra-x86-64-v2', value: 'extra-x86-64-v2' },
|
||||||
|
{ title: 'extra-x86-64-v3', value: 'extra-x86-64-v3' },
|
||||||
|
{ title: 'extra-x86-64-v4', value: 'extra-x86-64-v4' },
|
||||||
|
{ title: 'multilib-x86-64-v2', value: 'multilib-x86-64-v2' },
|
||||||
|
{ title: 'multilib-x86-64-v3', value: 'multilib-x86-64-v3' },
|
||||||
|
{ title: 'multilib-x86-64-v4', value: 'multilib-x86-64-v4' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const STATUS_ITEMS = [
|
||||||
|
{ title: 'Latest', value: 'latest' },
|
||||||
|
{ title: 'Built', value: 'built' },
|
||||||
|
{ title: 'Failed', value: 'failed' },
|
||||||
|
{ title: 'Skipped', value: 'skipped' },
|
||||||
|
{ title: 'Delayed', value: 'delayed' },
|
||||||
|
{ title: 'Queued', value: 'queued' },
|
||||||
|
{ title: 'Building', value: 'building' },
|
||||||
|
{ title: 'Signing', value: 'signing' },
|
||||||
|
{ title: 'Unknown', value: 'unknown' }
|
||||||
|
]
|
Reference in New Issue
Block a user