Compare commits

...

2 Commits

Author SHA1 Message Date
8fe8f1e122 Refactor and modularize queued packages handling
Extract queued packages logic into a dedicated `QueuedPackagesList` component for better code modularity and maintainability. Streamline API fetch functions with reusable `fetchPackages` utility and improve code readability by adopting async/await syntax.
2025-04-07 21:06:10 +02:00
db9c7ee523 Refactor Packages component for better readability and maintainability
Simplified logic by extracting helper functions and constants, and improved state management with more descriptive variable names. Enhanced URL parameter handling and adjusted watchers to dynamically update based on changes, ensuring cleaner and more modular code.
2025-04-07 20:51:32 +02:00
4 changed files with 287 additions and 355 deletions

View File

@@ -13,5 +13,6 @@ declare module 'vue' {
CurrentlyBuilding: typeof import('./src/components/CurrentlyBuilding.vue')['default'] CurrentlyBuilding: typeof import('./src/components/CurrentlyBuilding.vue')['default']
MainNav: typeof import('./src/components/MainNav.vue')['default'] MainNav: typeof import('./src/components/MainNav.vue')['default']
Packages: typeof import('./src/components/Packages.vue')['default'] Packages: typeof import('./src/components/Packages.vue')['default']
QueuedPackagesList: typeof import('./src/components/QueuedPackagesList.vue')['default']
} }
} }

View File

@@ -7,7 +7,7 @@
variant="elevated"> variant="elevated">
<v-card-title> <v-card-title>
<v-row align="center" class="w-100" style="margin-top: 1px"> <v-row align="center" class="w-100" style="margin-top: 1px">
<v-col class="v-col-12 v-col-lg-1"> <v-col class="v-col-12 v-col-lg-1 mb-3">
<v-row :class="mobile ? 'mt-1' : ''" class="flex-nowrap ms-1 d-flex align-items-center"> <v-row :class="mobile ? 'mt-1' : ''" class="flex-nowrap ms-1 d-flex align-items-center">
<div <div
:class=" :class="
@@ -19,8 +19,7 @@
</span> </span>
</v-row> </v-row>
</v-col> </v-col>
<v-col v-if="packageCount.building > 0" class="v-col-12 v-col-lg-8 mb-3">
<v-col v-if="packageCount.building > 0" class="v-col-12 v-col-lg-8">
<v-progress-linear <v-progress-linear
:max="packageCount.built + packageCount.building + packageCount.queued" :max="packageCount.built + packageCount.building + packageCount.queued"
:model-value="packageCount.built" :model-value="packageCount.built"
@@ -29,10 +28,9 @@
rounded rounded
striped></v-progress-linear> striped></v-progress-linear>
</v-col> </v-col>
<v-col <v-col
:class="mobile ? 'mt-n3' : 'text-end ms-auto'" :class="mobile ? 'mt-n3' : 'text-end ms-auto'"
class="text-grey v-col-12 v-col-lg-2" class="text-grey v-col-12 v-col-lg-2 mb-3"
cols="auto" cols="auto"
style="font-size: 13px"> style="font-size: 13px">
Last updated Last updated
@@ -44,7 +42,6 @@
</v-col> </v-col>
</v-row> </v-row>
</v-card-title> </v-card-title>
<v-card-text <v-card-text
v-if="packageCount.building > 0 || packageCount.queued > 0" v-if="packageCount.building > 0 || packageCount.queued > 0"
class="d-flex flex-column"> class="d-flex flex-column">
@@ -55,88 +52,31 @@
<div class="pulsating-circle-amber me-4" /> <div class="pulsating-circle-amber me-4" />
</template> </template>
<v-list-item-title> <v-list-item-title>
{{ pkg.pkgbase }} <span class="text-grey">({{ pkg.repo }})</span> {{ pkg.pkgbase }}
<span class="text-grey">({{ pkg.repo }})</span>
</v-list-item-title> </v-list-item-title>
<v-list-item-subtitle>{{ pkg.arch_version }}</v-list-item-subtitle> <v-list-item-subtitle>{{ pkg.arch_version }}</v-list-item-subtitle>
</v-list-item> </v-list-item>
</v-list> </v-list>
<v-sheet class="ps-4 mt-4" color="transparent" rounded width="100%">
<v-list width="100%"> <h4 class="mb-2 font-weight-light text-grey">Queued</h4>
<v-list-subheader>Queued</v-list-subheader> <QueuedPackagesList :packages="packages.queued" />
</v-sheet>
<!-- SHOW MESSAGE IF NO QUEUED PACKAGES -->
<template v-if="packages.queued.length === 0">
<v-list-item>
<v-list-item-title class="text-grey"> No packages queued.</v-list-item-title>
</v-list-item>
</template>
<!-- ELSE SHOW SUMMARY VIEW AND EXPANSION PANEL -->
<template v-else>
<!-- SUMMARY VIEW: First few queued items -->
<v-list-item
v-for="(pkg, index) in packages.queued.slice(0, packageCount.building)"
:key="index"
prepend-icon="mdi-chevron-right">
<v-list-item-title>
{{ pkg.pkgbase }} <span class="text-grey">({{ pkg.repo }})</span>
</v-list-item-title>
<v-list-item-subtitle>{{ pkg.arch_version }}</v-list-item-subtitle>
</v-list-item>
<!-- SUMMARY VIEW: Remaining count -->
<v-list-item
v-if="packageCount.queued > packageCount.building"
prepend-icon="mdi-dots-horizontal">
<v-list-item-title>
<span v-if="packageCount.building > 0">+</span>
{{ packageCount.queued - packageCount.building }}
<span v-if="packageCount.building > 0">more</span><span v-else>in queue</span>
</v-list-item-title>
</v-list-item>
<!-- FULL LIST EXPANSION PANEL -->
<v-expansion-panels>
<v-expansion-panel>
<v-expansion-panel-title>
Show all queued packages
<span class="text-grey">({{ packages.queued.length }})</span>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-list outlined rounded>
<v-list-item
v-for="(pkg, index) in packages.queued"
:key="index"
class="d-flex justify-space-between">
<v-list-item-title>
{{ pkg.pkgbase }} <span class="text-grey">({{ pkg.repo }})</span>
</v-list-item-title>
<v-list-item-subtitle>
{{ pkg.arch_version }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</template>
</v-list>
</v-card-text> </v-card-text>
</v-card> </v-card>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// built + building + queued = total
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import type { Packages } from '@/types/Packages' import type { Packages } from '@/types/Packages'
import { Package } from '@/types/Package' import { Package } from '@/types/Package'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
import QueuedPackagesList from './QueuedPackagesList.vue'
// This variable sets the update-interval const BASE_URL = 'https://api.alhp.dev/packages'
const updateIntervalInMinutes = 5 const UPDATE_INTERVAL_MINUTES = 5
const { mobile } = useDisplay() const { mobile } = useDisplay()
const lastUpdatedTime = ref(0) const lastUpdatedTime = ref(0)
const lastUpdatedSeconds = ref(0) const lastUpdatedSeconds = ref(0)
const rtf = new Intl.RelativeTimeFormat('en', { const rtf = new Intl.RelativeTimeFormat('en', {
@@ -151,6 +91,7 @@ const packageCount = ref({
built: 0, built: 0,
queued: 0 queued: 0
}) })
const packages = ref<{ const packages = ref<{
built: Array<Package> built: Array<Package>
building: Array<Package> building: Array<Package>
@@ -161,71 +102,54 @@ const packages = ref<{
queued: [] queued: []
}) })
const getTotalPackages = () => { const fetchPackages = async (endpoint: string, status?: string) => {
fetch('https://api.alhp.dev/packages?limit=1&offset=0', { let url = `${BASE_URL}?limit=0&offset=0`
method: 'GET' if (status) {
}) url += `&status=${status}`
.then((response) => { }
if (response.ok) return response.json()
}) try {
.then((json: Packages) => { const response = await fetch(url, { method: 'GET' })
if (!json) return if (!response.ok) {
packageCount.value.total = json.total throw new Error(`HTTP error! status: ${response.status}`)
}) }
.catch((error) => { const json: Packages = await response.json()
console.error(error) return json
}) } catch (error) {
console.error('Error fetching packages:', error)
return null
}
} }
const getBuiltPackages = () => { const getTotalPackages = async () => {
fetch('https://api.alhp.dev/packages?limit=1&offset=0&status=built', { const json = await fetchPackages(`${BASE_URL}?limit=1&offset=0`)
method: 'GET' if (json) {
}) packageCount.value.total = json.total
.then((response) => { }
if (response.ok) return response.json() }
})
.then((json: Packages) => { const getBuiltPackages = async () => {
if (!json) return const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'built')
if (json) {
packageCount.value.built = json.total packageCount.value.built = json.total
packages.value.built = json.packages packages.value.built = json.packages
}) }
.catch((error) => {
console.error(error)
})
} }
const getBuildingPackages = () => { const getBuildingPackages = async () => {
fetch('https://api.alhp.dev/packages?limit=0&offset=0&status=building', { const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'building')
method: 'GET' if (json) {
})
.then((response) => {
if (response.ok) return response.json()
})
.then((json: Packages) => {
if (!json) return
packageCount.value.building = json.total packageCount.value.building = json.total
packages.value.building = json.packages packages.value.building = json.packages
}) }
.catch((error) => {
console.error(error)
})
} }
const getQueuedPackages = () => { const getQueuedPackages = async () => {
fetch('https://api.alhp.dev/packages?limit=0&offset=0&status=queued', { const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'queued')
method: 'GET' if (json) {
})
.then((response) => {
if (response.ok) return response.json()
})
.then((json: Packages) => {
if (!json) return
packageCount.value.queued = json.total packageCount.value.queued = json.total
packages.value.queued = json.packages packages.value.queued = json.packages
}) }
.catch((error) => {
console.error(error)
})
} }
onMounted(() => { onMounted(() => {
@@ -233,14 +157,15 @@ onMounted(() => {
getBuiltPackages() getBuiltPackages()
getBuildingPackages() getBuildingPackages()
getQueuedPackages() getQueuedPackages()
lastUpdatedTime.value = Date.now() lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000) lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
const lastUpdatedInterval = setInterval(() => { setInterval(() => {
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000) lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
}, 1000) }, 1000)
const interval = setInterval( setInterval(
() => { () => {
getTotalPackages() getTotalPackages()
getBuiltPackages() getBuiltPackages()
@@ -249,7 +174,7 @@ onMounted(() => {
lastUpdatedTime.value = Date.now() lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000) lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
}, },
updateIntervalInMinutes * 60 * 1000 UPDATE_INTERVAL_MINUTES * 60 * 1000
) )
}) })
</script> </script>
@@ -313,6 +238,7 @@ onMounted(() => {
0% { 0% {
transform: scale(0.33); transform: scale(0.33);
} }
80%, 80%,
100% { 100% {
opacity: 0; opacity: 0;
@@ -323,6 +249,7 @@ onMounted(() => {
0% { 0% {
transform: scale(0.33); transform: scale(0.33);
} }
80%, 80%,
100% { 100% {
opacity: 0; opacity: 0;
@@ -333,9 +260,11 @@ onMounted(() => {
0% { 0% {
transform: scale(0.8); transform: scale(0.8);
} }
50% { 50% {
transform: scale(1); transform: scale(1);
} }
100% { 100% {
transform: scale(0.8); transform: scale(0.8);
} }
@@ -345,9 +274,11 @@ onMounted(() => {
0% { 0% {
transform: scale(0.8); transform: scale(0.8);
} }
50% { 50% {
transform: scale(1); transform: scale(1);
} }
100% { 100% {
transform: scale(0.8); transform: scale(0.8);
} }

View File

@@ -1,8 +1,7 @@
<template> <template>
<v-sheet class="mt-6" color="transparent" 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-row :style="mobile ? '' : 'height: 60px'" width="100%">
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2"> <v-col class="v-col-12 v-col-sm-2 v-col-lg-2">
<v-text-field <v-text-field
v-model="inputPkgBase" v-model="inputPkgBase"
@@ -11,10 +10,9 @@
placeholder="Search Pkgbase" placeholder="Search Pkgbase"
variant="outlined"></v-text-field> variant="outlined"></v-text-field>
</v-col> </v-col>
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2 mt-n6 mt-sm-0"> <v-col class="v-col-12 v-col-sm-2 v-col-lg-2 mt-n6 mt-sm-0">
<v-select <v-select
v-model="selectRepo" v-model="selectedRepo"
:items="selectRepoItems" :items="selectRepoItems"
clearable clearable
color="primary" color="primary"
@@ -25,10 +23,9 @@
single-line single-line
variant="outlined"></v-select> variant="outlined"></v-select>
</v-col> </v-col>
<v-col class="v-col-12 v-col-sm-2 v-col-lg-3 mt-n6 mt-sm-0"> <v-col class="v-col-12 v-col-sm-2 v-col-lg-3 mt-n6 mt-sm-0">
<v-select <v-select
v-model="selectStatus" v-model="selectedStatuses"
:items="selectStatusItems" :items="selectStatusItems"
chips chips
closable-chips closable-chips
@@ -41,11 +38,9 @@
return-object return-object
variant="outlined"></v-select> variant="outlined"></v-select>
</v-col> </v-col>
<v-col class="v-col-12 v-col-sm-2 v-col-lg-2 mt-n6 mt-sm-0"> <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-switch v-model="enableExact" color="primary" label="Exact search"></v-switch>
</v-col> </v-col>
<v-col :class="mobile ? 'mt-n6' : ''" :cols="mobile ? 12 : 'auto'" class="ms-auto"> <v-col :class="mobile ? 'mt-n6' : ''" :cols="mobile ? 12 : 'auto'" class="ms-auto">
<v-pagination <v-pagination
v-model="page" v-model="page"
@@ -57,7 +52,6 @@
variant="text"></v-pagination> variant="text"></v-pagination>
</v-col> </v-col>
</v-row> </v-row>
<v-table class="mt-2 mt-sm-6" style="width: 100%; background: transparent; font-size: 1rem"> <v-table class="mt-2 mt-sm-6" style="width: 100%; background: transparent; font-size: 1rem">
<thead> <thead>
<tr> <tr>
@@ -84,7 +78,6 @@
<th class="text-end" scope="col" style="width: 30px">Info</th> <th class="text-end" scope="col" style="width: 30px">Info</th>
</tr> </tr>
</thead> </thead>
<tbody id="main-tbody"> <tbody id="main-tbody">
<tr v-if="noPackagesFound"> <tr v-if="noPackagesFound">
No Packages found No Packages found
@@ -93,16 +86,16 @@
<tr <tr
v-for="(pkg, index) in packages" v-for="(pkg, index) in packages"
:key="index" :key="index"
:style="`background-color: ${getStatus(pkg.status)};`"> :style="`background-color: ${getStatusColor(pkg.status)};`">
<td class="font-weight-bold text-no-wrap"> <td class="font-weight-bold text-no-wrap">
{{ pkg.repo.split('-')[0] }} {{ repoName(pkg.repo) }}
<v-chip <v-chip
:color="getVersionColor(pkg.repo.split('-')[pkg.repo.split('-').length - 1])" :color="getVersionColor(repoVersion(pkg.repo))"
class="ms-2" class="ms-2"
density="comfortable" density="comfortable"
label label
variant="flat"> variant="flat">
{{ pkg.repo.split('-')[pkg.repo.split('-').length - 1] }} {{ repoVersion(pkg.repo) }}
</v-chip> </v-chip>
</td> </td>
<td class="text-no-wrap">{{ pkg.pkgbase }}</td> <td class="text-no-wrap">{{ pkg.pkgbase }}</td>
@@ -165,23 +158,31 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, UnwrapRef, watch } from 'vue' import { onMounted, ref, watch } from 'vue'
import { type Package } from '@/types/Package' import { type Package } from '@/types/Package'
import { type Packages } from '@/types/Packages' import { type Packages } from '@/types/Packages'
import { useDisplay } from 'vuetify' import { useDisplay } from 'vuetify'
// Constants
const OFFSET = 50 const OFFSET = 50
const ROW_HEIGHT = 60
const TRANSPARENT_COLOR = 'transparent'
// Refs
const page = ref(0) const page = ref(0)
const pagesMax = ref(1) const pagesMax = ref(1)
const packages = ref<Array<Package>>([]) const packages = ref<Array<Package>>([])
const noPackagesFound = ref(false) const noPackagesFound = ref(false)
const init = 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() const { mobile } = useDisplay()
const inputPkgBase = ref('') // Select Items
const selectRepo = ref()
const selectRepoItems = [ const selectRepoItems = [
{ title: 'core-x86-64-v2', value: 'core-x86-64-v2' }, { title: 'core-x86-64-v2', value: 'core-x86-64-v2' },
{ title: 'extra-x86-64-v2', value: 'extra-x86-64-v2' }, { title: 'extra-x86-64-v2', value: 'extra-x86-64-v2' },
@@ -193,7 +194,7 @@ const selectRepoItems = [
{ title: 'extra-x86-64-v4', value: 'extra-x86-64-v4' }, { title: 'extra-x86-64-v4', value: 'extra-x86-64-v4' },
{ title: 'multilib-x86-64-v4', value: 'multilib-x86-64-v4' } { title: 'multilib-x86-64-v4', value: 'multilib-x86-64-v4' }
] ]
const selectStatus = ref<Array<{ title: string; value: string }>>([])
const selectStatusItems = [ const selectStatusItems = [
{ title: 'Latest', value: 'latest' }, { title: 'Latest', value: 'latest' },
{ title: 'Built', value: 'built' }, { title: 'Built', value: 'built' },
@@ -205,63 +206,142 @@ const selectStatusItems = [
{ title: 'Signing', value: 'signing' }, { title: 'Signing', value: 'signing' },
{ title: 'Unknown', value: 'unknown' } { title: 'Unknown', value: 'unknown' }
] ]
const enableExact = ref(false)
const searchPackages = () => { // Computed properties
const offset = OFFSET * (page.value > 0 ? page.value - 1 : page.value) const repoName = (repo: string) => repo.split('-')[0]
noPackagesFound.value = false const repoVersion = (repo: string) => repo.split('-')[repo.split('-').length - 1]
// init params // 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() const params = new URLSearchParams()
if (page.value > 0) {
// set params params.set('offset', (page.value * OFFSET).toString())
if (offset > 0) {
url.searchParams.set('offset', offset.toString())
} else {
url.searchParams.delete('offset')
} }
if (inputPkgBase.value) { if (inputPkgBase.value) {
params.set('pkgbase', inputPkgBase.value.toLowerCase()) params.set('pkgbase', inputPkgBase.value.toLowerCase())
url.searchParams.set('pkgbase', inputPkgBase.value.toLowerCase())
} else {
params.delete('pkgbase')
url.searchParams.delete('pkgbase')
} }
if (selectRepo.value) { if (selectedRepo.value) {
params.set('repo', selectRepo.value.value) params.set('repo', selectedRepo.value.value)
url.searchParams.set('repo', selectRepo.value.value)
} else {
params.delete('repo')
url.searchParams.delete('repo')
} }
if (selectStatus.value.length > 0) { if (selectedStatuses.value.length > 0) {
selectStatus.value.forEach((status) => { selectedStatuses.value.forEach((status) => {
if (!params.has('status', status.value)) {
params.append('status', status.value) params.append('status', status.value)
}
if (!url.searchParams.has('status', status.value)) {
url.searchParams.append('status', status.value)
}
}) })
} else {
params.delete('status')
url.searchParams.delete('status')
} }
if (enableExact.value) { if (enableExact.value) {
params.set('exact', '') params.set('exact', '')
url.searchParams.set('exact', '')
} else {
params.delete('exact')
url.searchParams.delete('exact')
} }
window.history.pushState(null, '', `${window.location.pathname}?${params}`)
}
// push params to url const searchPackages = () => {
window.history.pushState(null, '', url.toString()) const offset = OFFSET * page.value
noPackagesFound.value = false
// add limit to query params 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('limit', OFFSET.toString())
params.set('offset', offset.toString()) params.set('offset', offset.toString())
fetch('https://api.alhp.dev/packages?' + params, { fetch('https://api.alhp.dev/packages?' + params, {
method: 'GET' method: 'GET'
}) })
@@ -276,174 +356,43 @@ const searchPackages = () => {
}) })
.then((json: Packages) => { .then((json: Packages) => {
if (!json) return if (!json) return
if (pagesMax.value !== json.total) {
pagesMax.value = json.total / OFFSET + 1 pagesMax.value = json.total / OFFSET + 1
}
packages.value = json.packages packages.value = json.packages
}) })
.catch((error) => { .catch((error) => {
console.error(error) console.error(error)
}) })
} }
// Properly set the initial values from URL params
const getVersionColor = (version: string) => {
let color = 'grey'
switch (version) {
case 'v2':
color = '#3498db'
break
case 'v3':
color = '#f39c12'
break
case 'v4':
color = '#2ecc71'
break
}
return color
}
const getStatus = (status: UnwrapRef<Package['status']>) => {
let color = ''
switch (status) {
case 'skipped':
color = '#373737'
break
case 'queued':
color = '#5d2f03'
break
case 'latest':
color = ''
break
case 'failed':
color = '#4f140f'
break
case 'signing':
color = '#093372'
break
case 'building':
color = '#084f46'
break
case 'unknown':
color = '#191919'
break
}
return color
}
const getLto = (lto: UnwrapRef<Package['lto']>) => {
let icon = {
title: '',
class: ''
}
switch (lto) {
case 'enabled':
icon = {
title: 'built with LTO',
class: 'fa fa-check fa-lg text-success'
}
break
case 'unknown':
icon = {
title: 'not built with LTO yet',
class: 'fa fa-hourglass-o fa-lg text-grey'
}
break
case 'disabled':
icon = {
title: 'LTO explicitly disabled',
class: 'fa fa-times fa-lg text-red'
}
break
case 'auto_disabled':
icon = {
title: 'LTO automatically disabled',
class: 'fa fa-times-circle-o fa-lg text-amber'
}
break
}
return icon
}
const getDs = (ds: UnwrapRef<Package['debug_symbols']>) => {
let icon = {
title: '',
class: ''
}
switch (ds) {
case 'available':
icon = {
title: 'Debug symbols available',
class: 'fa fa-check fa-lg text-success'
}
break
case 'unknown':
icon = {
title: 'Not built yet',
class: 'fa fa-hourglass-o fa-lg text-grey'
}
break
case 'not_available':
icon = {
title: 'Not built with debug symbols',
class: 'fa fa-times fa-lg text-red'
}
break
}
return icon
}
const url = new URL(window.location.href)
window.addEventListener('load', () => {
init.value = true
url.searchParams.has('offset')
? (page.value = parseInt(url.searchParams.get('offset') || '0'))
: (page.value = 0)
inputPkgBase.value = url.searchParams.has('pkgbase')
? (url.searchParams.get('pkgbase') || '').toLowerCase()
: ''
url.searchParams.has('repo')
? (selectRepo.value = {
value: url.searchParams.get('repo'),
title: url.searchParams.get('repo')
})
: undefined
url.searchParams.has('status')
? url.searchParams.getAll('status').forEach((status) => {
selectStatus.value.push({ value: status, title: status.toLocaleUpperCase() })
})
: undefined
enableExact.value = url.searchParams.has('exact')
searchPackages()
init.value = false
})
watch(
[
() => enableExact.value,
() => inputPkgBase.value,
() => selectRepo.value,
() => selectStatus.value
],
() => {
page.value = 0
searchPackages()
}
)
watch(
() => page.value,
() => {
searchPackages()
}
)
onMounted(() => { onMounted(() => {
const urlParams = new URLSearchParams(window.location.search)
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() searchPackages()
}) })
// Watchers
watch(
[page, inputPkgBase, selectedRepo, selectedStatuses, enableExact],
() => {
if (init.value) return
updateUrlParams()
searchPackages()
},
{ deep: true }
)
</script> </script>

View File

@@ -0,0 +1,51 @@
<template>
<v-sheet color="transparent" rounded width="100%">
<!-- SHOW MESSAGE IF NO QUEUED PACKAGES -->
<template v-if="packages.length === 0">
<span class="text-grey"> No packages queued.</span>
</template>
<!-- ELSE SHOW EXPANSION PANEL -->
<template v-else>
<!-- FULL LIST EXPANSION PANEL -->
<v-expansion-panels>
<v-expansion-panel bg-color="#303030" color="primary" elevation="0">
<v-expansion-panel-title>
Show all queued packages
<span class="ms-1 text-disabled text-high-emphasis">({{ packages.length }})</span>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-list bg-color="transparent" outlined rounded>
<v-list-item v-for="(pkg, index) in packages" :key="index">
<template #prepend>
<v-icon
icon="mdi-chevron-right"
style="margin-left: -20px; margin-right: -20px" />
</template>
<v-list-item-title>
{{ pkg.pkgbase }}
<span class="text-grey">({{ pkg.repo }})</span>
</v-list-item-title>
<v-list-item-subtitle>
{{ pkg.arch_version }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</template>
</v-sheet>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import type { Package } from '@/types/Package'
defineProps({
packages: {
type: Array<Package>,
required: true,
default: () => []
}
})
</script>