Compare commits
9 Commits
265bfac74a
...
ad5ce609fc
Author | SHA1 | Date | |
---|---|---|---|
ad5ce609fc | |||
c458b564ce | |||
bcb9569b26 | |||
90be95afda | |||
c864664536 | |||
9762505a24 | |||
9adeaa4483 | |||
43ce135fc6 | |||
4e722e5e60 |
3
frontend/.env.example
Normal file
3
frontend/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
VITE_BASE_URL="API_URL"
|
||||
VITE_UPDATE_INTERVAL=5
|
||||
VITE_LIMIT=50
|
8
frontend/components.d.ts
vendored
8
frontend/components.d.ts
vendored
@@ -9,10 +9,14 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BuildServerStats: typeof import('./src/components/BuildServerStats.vue')['default']
|
||||
BuildStats: typeof import('./src/components/BuildStats.vue')['default']
|
||||
BuildStats: typeof import('./src/components/MainNav/BuildStats.vue')['default']
|
||||
CurrentlyBuilding: typeof import('./src/components/CurrentlyBuilding.vue')['default']
|
||||
MainNav: typeof import('./src/components/MainNav.vue')['default']
|
||||
PackageFilters: typeof import('./src/components/Packages/PackageFilters.vue')['default']
|
||||
Packages: typeof import('./src/components/Packages.vue')['default']
|
||||
QueuedPackagesList: typeof import('./src/components/QueuedPackagesList.vue')['default']
|
||||
PackageTable: typeof import('./src/components/Packages/PackageTable.vue')['default']
|
||||
QueuedPackagesList: typeof import('./src/components/CurrentlyBuilding/QueuedPackagesList.vue')['default']
|
||||
StatItem: typeof import('./src/components/MainNav/BuildStats/StatItem.vue')['default']
|
||||
StatsListSection: typeof import('./src/components/MainNav/BuildStats/StatsListSection.vue')['default']
|
||||
}
|
||||
}
|
||||
|
13
frontend/env.d.ts
vendored
Normal file
13
frontend/env.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_BASE_URL: string
|
||||
readonly VITE_UPDATE_INTERVAL: number
|
||||
readonly VITE_LIMIT: number
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
||||
|
||||
declare module '@/*'
|
@@ -10,21 +10,22 @@
|
||||
"@fontsource/roboto": "^5.2.5",
|
||||
"@mdi/font": "7.4.47",
|
||||
"fork-awesome": "^1.2.0",
|
||||
"pinia": "^3.0.2",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"vue": "^3.5.13",
|
||||
"vuetify": "^3.7.18"
|
||||
"vuetify": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.26.10",
|
||||
"@types/node": "^22.13.11",
|
||||
"@babel/types": "^7.27.0",
|
||||
"@types/node": "^22.14.1",
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.86.0",
|
||||
"typescript": "^5.8.2",
|
||||
"sass": "^1.86.3",
|
||||
"typescript": "^5.8.3",
|
||||
"unplugin-fonts": "^1.3.1",
|
||||
"unplugin-vue-components": "^28.4.1",
|
||||
"vite": "^6.2.2",
|
||||
"vite-plugin-vuetify": "^2.1.0",
|
||||
"unplugin-vue-components": "^28.5.0",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-vuetify": "^2.1.1",
|
||||
"vue-tsc": "^2.2.8"
|
||||
},
|
||||
"packageManager": "yarn@4.7.0"
|
||||
|
@@ -4,9 +4,19 @@
|
||||
<main-nav />
|
||||
|
||||
<v-main>
|
||||
<build-server-stats />
|
||||
<currently-building />
|
||||
<packages />
|
||||
<v-sheet v-if="!lastUpdated || isLoading" class="mt-2" color="transparent">
|
||||
<h5 class="text-h5">Fetching Data</h5>
|
||||
</v-sheet>
|
||||
|
||||
<v-sheet v-else-if="error" class="mt-2" color="transparent">
|
||||
<h5 class="text-h5">Can't fetch data. Please try again later</h5>
|
||||
</v-sheet>
|
||||
|
||||
<template v-else>
|
||||
<build-server-stats />
|
||||
<currently-building />
|
||||
<packages />
|
||||
</template>
|
||||
</v-main>
|
||||
</v-container>
|
||||
</v-app>
|
||||
@@ -15,6 +25,25 @@
|
||||
<script lang="ts" setup>
|
||||
import MainNav from '@/components/MainNav.vue'
|
||||
import BuildServerStats from '@/components/BuildServerStats.vue'
|
||||
import Packages from '@/components/Packages.vue'
|
||||
import CurrentlyBuilding from '@/components/CurrentlyBuilding.vue'
|
||||
import Packages from '@/components/Packages.vue'
|
||||
import { useDataStore } from '@/stores/dataStore'
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const dataStore = useDataStore()
|
||||
|
||||
const isLoading = computed(() => dataStore.loading)
|
||||
const error = computed(() => dataStore.error)
|
||||
const lastUpdated = computed(() => {
|
||||
if (!dataStore.data) return null
|
||||
return dataStore.data.lastUpdated
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await dataStore.startAutoRefresh()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
dataStore.stopAutoRefresh()
|
||||
})
|
||||
</script>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<v-sheet class="mt-2" color="transparent">
|
||||
<h5 class="text-h5">Buildserver Stats</h5>
|
||||
<iframe
|
||||
:height="width <= 800 ? `${IFRAME_HEIGHT}px` : '420px'"
|
||||
:height="iframeHeight"
|
||||
allowtransparency="true"
|
||||
class="w-100 border-0"
|
||||
src="https://stats.itsh.dev/public-dashboards/0fb04abb0c5e4b7390cf26a98e6dead1"></iframe>
|
||||
@@ -11,40 +11,16 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { width } = useDisplay()
|
||||
|
||||
/**
|
||||
* Represents the height of a graph in pixels.
|
||||
* This constant is used to define the vertical dimension of the graph
|
||||
* when rendering it in a user interface or graphical representation.
|
||||
*
|
||||
* @constant {number} GRAPH_HEIGHT
|
||||
*/
|
||||
// Configuration constants
|
||||
const GRAPH_HEIGHT = 335
|
||||
/**
|
||||
* Represents the total number of graphs to be processed or displayed.
|
||||
*
|
||||
* This variable is used to define the fixed amount of graphs that the system
|
||||
* is intended to handle at a given time. It can be leveraged to set limits
|
||||
* on iterations, arrays, or display logic related to graph management.
|
||||
*
|
||||
* Default value: 4
|
||||
*
|
||||
* Type: number
|
||||
*/
|
||||
const NUMBER_OF_GRAPHS = 4
|
||||
/**
|
||||
* Represents the height of an iframe calculated dynamically based on the number of graphs
|
||||
* and the height of each graph.
|
||||
*
|
||||
* IFRAME_HEIGHT is determined by multiplying the number of graphs (`NUMBER_OF_GRAPHS`)
|
||||
* displayed in the iframe by the height of a single graph (`GRAPH_HEIGHT`).
|
||||
*
|
||||
* This variable is commonly used to ensure the iframe adjusts correctly to accommodate
|
||||
* all graphs without introducing unnecessary scrollbars.
|
||||
*
|
||||
* @constant {number} IFRAME_HEIGHT
|
||||
*/
|
||||
const IFRAME_HEIGHT = NUMBER_OF_GRAPHS * GRAPH_HEIGHT
|
||||
|
||||
// Computed property for responsive iframe height
|
||||
const iframeHeight = computed(() =>
|
||||
width.value <= 800 ? `${NUMBER_OF_GRAPHS * GRAPH_HEIGHT}px` : '420px'
|
||||
)
|
||||
</script>
|
||||
|
@@ -1,102 +0,0 @@
|
||||
<template>
|
||||
<v-sheet :style="sheetStyles" class="d-flex" color="transparent">
|
||||
<!-- Dynamically render the "Stats" section -->
|
||||
<v-list :style="listStyles" bg-color="transparent" class="d-flex">
|
||||
<v-list-subheader>Stats:</v-list-subheader>
|
||||
<v-list-item
|
||||
v-for="item in statsList"
|
||||
:key="item.label"
|
||||
:style="{ color: item.color }"
|
||||
:title="item.label">
|
||||
{{ item.value }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<!-- Dynamically render the "LTO" section -->
|
||||
<v-list :style="listStyles" bg-color="transparent" class="d-flex">
|
||||
<v-list-subheader>LTO:</v-list-subheader>
|
||||
<v-list-item
|
||||
v-for="item in ltoList"
|
||||
:key="item.label"
|
||||
:style="{ color: item.color }"
|
||||
:title="item.label">
|
||||
{{ item.value }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { type Stats } from '@/types/Stats'
|
||||
|
||||
// Define reusable styles and colors
|
||||
const sheetStyles = { gap: '50px' }
|
||||
const listStyles = { borderRadius: '5px' }
|
||||
const colors = {
|
||||
latest: '#069b35',
|
||||
queued: '#b97808',
|
||||
skipped: '#878787',
|
||||
failed: '#b30303',
|
||||
enabled: '#069b35',
|
||||
disabled: '#b30303',
|
||||
unknown: '#878787'
|
||||
}
|
||||
|
||||
// Reactive stats object
|
||||
const stats = ref<Stats>({
|
||||
latest: 0,
|
||||
queued: 0,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
lto: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
unknown: 0
|
||||
}
|
||||
})
|
||||
|
||||
// Map stats and LTO data for simpler rendering in the template
|
||||
const statsList = ref([
|
||||
{ label: 'latest', value: stats.value.latest, color: colors.latest },
|
||||
{ label: 'queued', value: stats.value.queued, color: colors.queued },
|
||||
{ label: 'skipped', value: stats.value.skipped, color: colors.skipped },
|
||||
{ label: 'failed', value: stats.value.failed, color: colors.failed }
|
||||
])
|
||||
|
||||
const ltoList = ref([
|
||||
{ label: 'enabled', value: stats.value.lto.enabled, color: colors.enabled },
|
||||
{ label: 'disabled', value: stats.value.lto.disabled, color: colors.disabled },
|
||||
{ label: 'unknown', value: stats.value.lto.unknown, color: colors.unknown }
|
||||
])
|
||||
|
||||
// Function to fetch stats data (refactored with async/await)
|
||||
const getStats = async (): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch('https://api.alhp.dev/stats')
|
||||
if (!response.ok) throw new Error(response.statusText)
|
||||
|
||||
const statistics = await response.json()
|
||||
stats.value = statistics
|
||||
|
||||
// Update lists with fresh data
|
||||
statsList.value = [
|
||||
{ label: 'latest', value: stats.value.latest, color: colors.latest },
|
||||
{ label: 'queued', value: stats.value.queued, color: colors.queued },
|
||||
{ label: 'skipped', value: stats.value.skipped, color: colors.skipped },
|
||||
{ label: 'failed', value: stats.value.failed, color: colors.failed }
|
||||
]
|
||||
ltoList.value = [
|
||||
{ label: 'enabled', value: stats.value.lto.enabled, color: colors.enabled },
|
||||
{ label: 'disabled', value: stats.value.lto.disabled, color: colors.disabled },
|
||||
{ label: 'unknown', value: stats.value.lto.unknown, color: colors.unknown }
|
||||
]
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch stats:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch stats on mount
|
||||
onMounted(() => {
|
||||
getStats()
|
||||
})
|
||||
</script>
|
@@ -74,138 +74,84 @@
|
||||
</v-list>
|
||||
<v-sheet class="ps-4" color="transparent" rounded width="100%">
|
||||
<h4 class="mb-2 font-weight-light text-grey">Queued</h4>
|
||||
<QueuedPackagesList :packages="packages.queued" />
|
||||
<queued-packages-list :packages="packages.queued" />
|
||||
</v-sheet>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import type { Packages } from '@/types/Packages'
|
||||
import { Package } from '@/types/Package'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import QueuedPackagesList from './QueuedPackagesList.vue'
|
||||
|
||||
const BASE_URL = 'https://api.alhp.dev/packages'
|
||||
const UPDATE_INTERVAL_MINUTES = 5
|
||||
import { useDataStore } from '@/stores/dataStore'
|
||||
import QueuedPackagesList from '@/components/CurrentlyBuilding/QueuedPackagesList.vue'
|
||||
|
||||
const { mobile } = useDisplay()
|
||||
const lastUpdatedTime = ref(0)
|
||||
const dataStore = useDataStore()
|
||||
const lastUpdatedSeconds = ref(0)
|
||||
const updateFailed = ref(false)
|
||||
const rtf = new Intl.RelativeTimeFormat('en', {
|
||||
localeMatcher: 'best fit',
|
||||
numeric: 'always',
|
||||
style: 'long'
|
||||
})
|
||||
|
||||
const packageCount = ref({
|
||||
total: 0,
|
||||
building: 0,
|
||||
built: 0,
|
||||
queued: 0
|
||||
})
|
||||
// Computed properties to access store data
|
||||
const updateFailed = computed(() => !!dataStore.error)
|
||||
|
||||
const packages = ref<{
|
||||
built: Array<Package>
|
||||
building: Array<Package>
|
||||
queued: Array<Package>
|
||||
}>({
|
||||
built: [],
|
||||
building: [],
|
||||
queued: []
|
||||
})
|
||||
|
||||
let timerInterval: NodeJS.Timeout // Declare timerInterval outside the function
|
||||
|
||||
const fetchPackages = async (offset: string, status?: string) => {
|
||||
let url = `${BASE_URL}?limit=0&offset=${offset}`
|
||||
if (status) {
|
||||
url += `&status=${status}`
|
||||
const packageCount = computed(() => {
|
||||
const stats = dataStore.getPackageStats() || { total: 0, building: 0, built: 0, queued: 0 }
|
||||
return {
|
||||
total: stats.total || 0,
|
||||
building: stats.building || 0,
|
||||
built: stats.built || 0,
|
||||
queued: stats.queued || 0
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET' })
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
const packages = computed(() => {
|
||||
return {
|
||||
building: dataStore.getPackagesByStatus('building').packages || [],
|
||||
queued: dataStore.getPackagesByStatus('queued').packages || [],
|
||||
built: dataStore.getPackagesByStatus('built').packages || []
|
||||
}
|
||||
})
|
||||
|
||||
// Set up and clean up the timer for "last updated" display
|
||||
let updateTimer: number | undefined
|
||||
|
||||
const resetLastUpdatedCounter = () => {
|
||||
lastUpdatedSeconds.value = 0
|
||||
}
|
||||
|
||||
const startLastUpdatedTimer = () => {
|
||||
updateTimer = window.setInterval(() => {
|
||||
lastUpdatedSeconds.value++
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dataStore.loading,
|
||||
(isLoading) => {
|
||||
if (!isLoading) {
|
||||
resetLastUpdatedCounter()
|
||||
|
||||
console.log(updateFailed.value)
|
||||
console.log(packageCount.value)
|
||||
console.log(packages.value)
|
||||
console.log(updateTimer)
|
||||
}
|
||||
const json: Packages = await response.json()
|
||||
|
||||
lastUpdatedTime.value = Date.now()
|
||||
lastUpdatedSeconds.value = 0 // Reset seconds counter
|
||||
updateFailed.value = false
|
||||
|
||||
return json
|
||||
} catch (error) {
|
||||
console.error('Error fetching packages:', error)
|
||||
updateFailed.value = true
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const getTotalPackages = async () => {
|
||||
const json = await fetchPackages('1')
|
||||
if (json) {
|
||||
packageCount.value.total = json.total
|
||||
}
|
||||
}
|
||||
|
||||
const getBuiltPackages = async () => {
|
||||
const json = await fetchPackages('0', 'built')
|
||||
if (json) {
|
||||
packageCount.value.built = json.total
|
||||
packages.value.built = json.packages
|
||||
}
|
||||
}
|
||||
|
||||
const getBuildingPackages = async () => {
|
||||
const json = await fetchPackages('0', 'building')
|
||||
if (json) {
|
||||
packageCount.value.building = json.total
|
||||
packages.value.building = json.packages
|
||||
}
|
||||
}
|
||||
|
||||
const getQueuedPackages = async () => {
|
||||
const json = await fetchPackages('0', 'queued')
|
||||
if (json) {
|
||||
packageCount.value.queued = json.total
|
||||
packages.value.queued = json.packages
|
||||
}
|
||||
}
|
||||
|
||||
// Function to start the timer
|
||||
const startTimer = () => {
|
||||
stopTimer() // Clear any existing timer
|
||||
getTotalPackages()
|
||||
getBuiltPackages()
|
||||
getBuildingPackages()
|
||||
getQueuedPackages()
|
||||
timerInterval = setInterval(
|
||||
() => {
|
||||
getTotalPackages()
|
||||
getBuiltPackages()
|
||||
getBuildingPackages()
|
||||
getQueuedPackages()
|
||||
},
|
||||
UPDATE_INTERVAL_MINUTES * 60 * 1000
|
||||
)
|
||||
}
|
||||
|
||||
// Function to stop the timer
|
||||
const stopTimer = () => {
|
||||
clearInterval(timerInterval)
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
startTimer() // Start the timer when the component is mounted
|
||||
startLastUpdatedTimer()
|
||||
})
|
||||
|
||||
// Update seconds counter every second
|
||||
setInterval(() => {
|
||||
lastUpdatedSeconds.value++
|
||||
}, 1000)
|
||||
onUnmounted(() => {
|
||||
if (updateTimer) {
|
||||
clearInterval(updateTimer)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<v-app-bar color="#191c2a" style="background: #0d1538">
|
||||
<v-container :class="width < 1440 ? 'ms-3' : 'mx-auto'" fluid style="max-width: 1440px">
|
||||
<v-app-bar :color="appBarColors.background">
|
||||
<v-container :class="containerClasses" :style="{ maxWidth: maxContainerWidth }" fluid>
|
||||
<v-row>
|
||||
<v-app-bar-title style="align-self: center">
|
||||
<span style="font-size: 20px">
|
||||
ALHP Status
|
||||
<a class="ms-2 gitea-link" href="https://somegit.dev/ALHP/ALHP.GO" target="_blank">
|
||||
<v-app-bar-title class="app-title">
|
||||
<span>
|
||||
{{ appTitle }}
|
||||
<a :href="repoUrl" class="ms-2 gitea-link" target="_blank">
|
||||
<i class="fa fa-gitea"></i>
|
||||
</a>
|
||||
</span>
|
||||
@@ -18,21 +18,40 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BuildStats from '@/components/BuildStats.vue'
|
||||
import BuildStats from '@/components/MainNav/BuildStats.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { mobile, width } = useDisplay()
|
||||
|
||||
const appTitle = 'ALHP Status'
|
||||
const repoUrl = 'https://somegit.dev/ALHP/ALHP.GO'
|
||||
const maxContainerWidth = '1440px'
|
||||
const appBarColors = {
|
||||
background: '#0d1538',
|
||||
accent: '#609926'
|
||||
}
|
||||
|
||||
const containerClasses = computed(() => ({
|
||||
'ms-3': width.value < 1440,
|
||||
'mx-auto': width.value >= 1440
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-title {
|
||||
align-self: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.gitea-link {
|
||||
color: white;
|
||||
font-size: 25px;
|
||||
text-decoration: none; /* Optional: To prevent underlining */
|
||||
transition: color 0.2s; /* Smooth color transition */
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.gitea-link:hover {
|
||||
color: #609926;
|
||||
color: v-bind('appBarColors.accent');
|
||||
}
|
||||
</style>
|
||||
|
104
frontend/src/components/MainNav/BuildStats.vue
Normal file
104
frontend/src/components/MainNav/BuildStats.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<v-sheet :style="sheetStyles" class="d-flex" color="transparent">
|
||||
<StatsListSection title="Stats">
|
||||
<StatItem
|
||||
v-for="(value, key) in generalStats"
|
||||
:key="key"
|
||||
:color="value.color"
|
||||
:count="value.count"
|
||||
:title="key" />
|
||||
</StatsListSection>
|
||||
|
||||
<StatsListSection title="LTO">
|
||||
<StatItem
|
||||
v-for="(value, key) in customStats.lto"
|
||||
:key="key"
|
||||
:color="value.color"
|
||||
:count="value.count"
|
||||
:title="key" />
|
||||
</StatsListSection>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useDataStore } from '@/stores/dataStore'
|
||||
import { type dataStore } from '@/types/dataStore'
|
||||
import StatsListSection from '@/components/MainNav/BuildStats/StatsListSection.vue'
|
||||
import StatItem from '@/components/MainNav/BuildStats/StatItem.vue'
|
||||
|
||||
interface CustomStatItem {
|
||||
count: number
|
||||
color: string
|
||||
}
|
||||
|
||||
interface CustomStats {
|
||||
latest: CustomStatItem
|
||||
queued: CustomStatItem
|
||||
building: CustomStatItem
|
||||
skipped: CustomStatItem
|
||||
failed: CustomStatItem
|
||||
lto: {
|
||||
enabled: CustomStatItem
|
||||
disabled: CustomStatItem
|
||||
unknown: CustomStatItem
|
||||
}
|
||||
}
|
||||
|
||||
const COLORS = {
|
||||
SUCCESS: '#069b35',
|
||||
WARNING: '#b97808',
|
||||
ERROR: '#b30303',
|
||||
NEUTRAL: '#878787'
|
||||
}
|
||||
|
||||
const sheetStyles = { gap: '50px' }
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const stats = ref<dataStore['stats']>()
|
||||
|
||||
const customStats = ref<CustomStats>({
|
||||
latest: { count: 0, color: COLORS.SUCCESS },
|
||||
queued: { count: 0, color: COLORS.WARNING },
|
||||
building: { count: 0, color: COLORS.WARNING },
|
||||
skipped: { count: 0, color: COLORS.NEUTRAL },
|
||||
failed: { count: 0, color: COLORS.ERROR },
|
||||
lto: {
|
||||
enabled: { count: 0, color: COLORS.SUCCESS },
|
||||
disabled: { count: 0, color: COLORS.ERROR },
|
||||
unknown: { count: 0, color: COLORS.NEUTRAL }
|
||||
}
|
||||
})
|
||||
|
||||
const generalStats = computed(() => {
|
||||
const { lto, ...rest } = customStats.value
|
||||
return rest
|
||||
})
|
||||
|
||||
const updateStats = (): void => {
|
||||
stats.value = dataStore.getPackageStats() || undefined
|
||||
|
||||
if (!stats.value) return
|
||||
|
||||
Object.keys(generalStats.value).forEach((key) => {
|
||||
const typedKey = key as keyof typeof generalStats.value
|
||||
customStats.value[typedKey].count = stats.value?.[typedKey] as number
|
||||
})
|
||||
|
||||
Object.keys(customStats.value.lto).forEach((ltoKey) => {
|
||||
const typedLtoKey = ltoKey as keyof typeof customStats.value.lto
|
||||
customStats.value.lto[typedLtoKey].count = stats.value?.lto[typedLtoKey] as number
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dataStore.loading,
|
||||
(isLoading) => {
|
||||
if (!isLoading) {
|
||||
updateStats()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(updateStats)
|
||||
</script>
|
13
frontend/src/components/MainNav/BuildStats/StatItem.vue
Normal file
13
frontend/src/components/MainNav/BuildStats/StatItem.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-list-item :style="{ color }" :title="title">
|
||||
{{ count }}
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
title: string
|
||||
count: number
|
||||
color: string
|
||||
}>()
|
||||
</script>
|
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<v-list :style="listStyles" bg-color="transparent" class="d-flex">
|
||||
<v-list-subheader>{{ title }}:</v-list-subheader>
|
||||
<slot></slot>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
title: string
|
||||
}>()
|
||||
|
||||
// Reusable styles
|
||||
const listStyles = { borderRadius: '5px' }
|
||||
</script>
|
@@ -1,398 +1,23 @@
|
||||
<template>
|
||||
<v-sheet :color="TRANSPARENT_COLOR" class="mt-6" width="100%">
|
||||
<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">
|
||||
<v-text-field
|
||||
v-model="inputPkgBase"
|
||||
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>
|
||||
|
||||
<PackageFilters :filter-options="filterOptions" :total-pages="filterResult.totalPages" />
|
||||
|
||||
<PackageTable :no-results="filterResult.noResults" :packages="filterResult.packages" />
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { type Package } from '@/types/Package'
|
||||
import { type Packages } from '@/types/Packages'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { onMounted } from 'vue'
|
||||
import { usePackageFilters } from '@/composables/Packages/usePackageFilters'
|
||||
import { TRANSPARENT_COLOR } from '@/config/constants'
|
||||
import PackageFilters from '@/components/Packages/PackageFilters.vue'
|
||||
import PackageTable from '@/components/Packages/PackageTable.vue'
|
||||
|
||||
// Constants
|
||||
const OFFSET = 50
|
||||
const ROW_HEIGHT = 60
|
||||
const TRANSPARENT_COLOR = 'transparent'
|
||||
const { filterOptions, filterResult, initFromUrl } = usePackageFilters()
|
||||
|
||||
// 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(() => {
|
||||
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()
|
||||
initFromUrl()
|
||||
})
|
||||
|
||||
// Watchers
|
||||
watch(
|
||||
[page, inputPkgBase, selectedRepo, selectedStatuses, enableExact],
|
||||
() => {
|
||||
if (init.value) return
|
||||
updateUrlParams()
|
||||
searchPackages()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</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
|
||||
}
|
||||
}
|
214
frontend/src/composables/Packages/usePackageFilters.ts
Normal file
214
frontend/src/composables/Packages/usePackageFilters.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
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 = () => {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
|
||||
const defaultOptions: FilterOptions = {
|
||||
pkgbaseSearch: '',
|
||||
page: 1,
|
||||
exactSearch: false,
|
||||
statuses: [],
|
||||
repo: undefined
|
||||
}
|
||||
|
||||
let page = 1
|
||||
if (urlParams.has('page')) {
|
||||
const pageParam = urlParams.get('page') || '1'
|
||||
const parsedPage = parseInt(pageParam, 10)
|
||||
page = !isNaN(parsedPage) && parsedPage > 0 ? parsedPage : 1
|
||||
}
|
||||
|
||||
const statuses = urlParams
|
||||
.getAll('status')
|
||||
.filter((status) => typeof status === 'string' && status.trim() !== '')
|
||||
.map((status) => ({
|
||||
title: status.toUpperCase(),
|
||||
value: status
|
||||
}))
|
||||
|
||||
filterOptions.value = {
|
||||
...defaultOptions,
|
||||
pkgbaseSearch: urlParams.get('pkgbase') || '',
|
||||
page,
|
||||
exactSearch: urlParams.has('exact'),
|
||||
statuses
|
||||
}
|
||||
|
||||
const repoValue = urlParams.get('repo')
|
||||
if (repoValue && typeof repoValue === 'string' && repoValue.trim() !== '') {
|
||||
filterOptions.value.repo = { title: repoValue, value: repoValue }
|
||||
}
|
||||
|
||||
applyFilters()
|
||||
|
||||
// Check if page exceeds total pages and adjust if necessary
|
||||
if (
|
||||
filterOptions.value.page > filterResult.value.totalPages &&
|
||||
filterResult.value.totalPages > 0
|
||||
) {
|
||||
filterOptions.value.page = filterResult.value.totalPages
|
||||
applyFilters()
|
||||
updateUrlParams()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing URL parameters:', error)
|
||||
// Reset to default state
|
||||
filterOptions.value = {
|
||||
pkgbaseSearch: '',
|
||||
page: 1,
|
||||
exactSearch: false,
|
||||
statuses: [],
|
||||
repo: undefined
|
||||
}
|
||||
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,
|
||||
() => {
|
||||
if (dataStore) {
|
||||
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' }
|
||||
]
|
@@ -1,22 +1,13 @@
|
||||
/**
|
||||
* main.ts
|
||||
*
|
||||
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import {registerPlugins} from '@/plugins'
|
||||
|
||||
// Components
|
||||
import { registerPlugins } from '@/plugins'
|
||||
import App from './App.vue'
|
||||
|
||||
// Composables
|
||||
import {createApp} from 'vue'
|
||||
|
||||
// Styles
|
||||
import { createApp } from 'vue'
|
||||
import '@/assets/styles/base.scss'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(pinia)
|
||||
|
||||
registerPlugins(app)
|
||||
|
||||
|
258
frontend/src/stores/dataStore.ts
Normal file
258
frontend/src/stores/dataStore.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { dataStore } from '@/types/dataStore'
|
||||
import { apiClient } from '@/utils/fetchUtils'
|
||||
import { Package } from '@/types/Package'
|
||||
import { Stats } from '@/types/Stats'
|
||||
|
||||
const UpdateInterval = Number(import.meta.env.VITE_UPDATE_INTERVAL) || 5
|
||||
|
||||
export const useDataStore = defineStore('data', () => {
|
||||
const data = ref<dataStore>()
|
||||
const loading = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
let refreshInterval: NodeJS.Timeout | null = null
|
||||
|
||||
const init = () => {
|
||||
data.value = {
|
||||
lastUpdated: Date.now(),
|
||||
packages: {
|
||||
offset: 0,
|
||||
limit: Number(import.meta.env.VITE_OFFSET) || 50,
|
||||
totalPackages: [],
|
||||
byStatus: {
|
||||
latest: [],
|
||||
built: [],
|
||||
failed: [],
|
||||
skipped: [],
|
||||
delayed: [],
|
||||
queued: [],
|
||||
building: [],
|
||||
signing: [],
|
||||
unknown: []
|
||||
},
|
||||
byRepo: {
|
||||
core: {
|
||||
v2: [],
|
||||
v3: [],
|
||||
v4: []
|
||||
},
|
||||
extra: {
|
||||
v2: [],
|
||||
v3: [],
|
||||
v4: []
|
||||
},
|
||||
multilib: {
|
||||
v2: [],
|
||||
v3: [],
|
||||
v4: []
|
||||
}
|
||||
}
|
||||
},
|
||||
stats: {
|
||||
total: 0,
|
||||
built: 0,
|
||||
building: 0,
|
||||
latest: 0,
|
||||
queued: 0,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
lto: {
|
||||
enabled: 0,
|
||||
disabled: 0,
|
||||
unknown: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all package data from the API
|
||||
const fetchAllPackageData = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const [totalStats, totalPackages] = await Promise.allSettled([
|
||||
apiClient.getStats(),
|
||||
apiClient.getPackages({ limit: 0, offset: 0 })
|
||||
])
|
||||
|
||||
// Combine into a single data structure
|
||||
if (
|
||||
totalPackages.status === 'fulfilled' &&
|
||||
totalStats.status === 'fulfilled' &&
|
||||
totalPackages.value &&
|
||||
totalStats.value
|
||||
) {
|
||||
// Initialize if needed
|
||||
if (!data.value) init()
|
||||
|
||||
// Update with the fetched data
|
||||
data.value!.lastUpdated = Date.now()
|
||||
data.value!.packages.totalPackages = [...totalPackages.value.packages]
|
||||
data.value!.packages.offset = totalPackages.value.offset
|
||||
data.value!.packages.limit = totalPackages.value.limit
|
||||
|
||||
// Update byStatus categorization
|
||||
data.value!.packages.byStatus = {
|
||||
latest: totalPackages.value.packages.filter((pkg) => pkg.status === 'latest') || [],
|
||||
built: totalPackages.value.packages.filter((pkg) => pkg.status === 'built') || [],
|
||||
failed: totalPackages.value.packages.filter((pkg) => pkg.status === 'failed') || [],
|
||||
skipped: totalPackages.value.packages.filter((pkg) => pkg.status === 'skipped') || [],
|
||||
delayed: totalPackages.value.packages.filter((pkg) => pkg.status === 'delayed') || [],
|
||||
queued: totalPackages.value.packages.filter((pkg) => pkg.status === 'queued') || [],
|
||||
building: totalPackages.value.packages.filter((pkg) => pkg.status === 'building') || [],
|
||||
signing: totalPackages.value.packages.filter((pkg) => pkg.status === 'signing') || [],
|
||||
unknown: totalPackages.value.packages.filter((pkg) => pkg.status === 'unknown') || []
|
||||
}
|
||||
|
||||
// Update byRepo categorization
|
||||
data.value!.packages.byRepo = {
|
||||
core: {
|
||||
v2: totalPackages.value.packages.filter((pkg) => pkg.repo === 'core-x86-64-v2') || [],
|
||||
v3: totalPackages.value.packages.filter((pkg) => pkg.repo === 'core-x86-64-v3') || [],
|
||||
v4: totalPackages.value.packages.filter((pkg) => pkg.repo === 'core-x86-64-v4') || []
|
||||
},
|
||||
extra: {
|
||||
v2: totalPackages.value.packages.filter((pkg) => pkg.repo === 'extra-x86-64-v2') || [],
|
||||
v3: totalPackages.value.packages.filter((pkg) => pkg.repo === 'extra-x86-64-v3') || [],
|
||||
v4: totalPackages.value.packages.filter((pkg) => pkg.repo === 'extra-x86-64-v4') || []
|
||||
},
|
||||
multilib: {
|
||||
v2:
|
||||
totalPackages.value.packages.filter((pkg) => pkg.repo === 'multilib-x86-64-v2') || [],
|
||||
v3:
|
||||
totalPackages.value.packages.filter((pkg) => pkg.repo === 'multilib-x86-64-v3') || [],
|
||||
v4:
|
||||
totalPackages.value.packages.filter((pkg) => pkg.repo === 'multilib-x86-64-v4') || []
|
||||
}
|
||||
}
|
||||
|
||||
// Update stats
|
||||
data.value!.stats = {
|
||||
...totalStats.value,
|
||||
total: totalPackages.value.total, // Total count from API
|
||||
built: data.value!.packages.byStatus.built.length,
|
||||
building: data.value!.packages.byStatus.building.length
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error fetching package data:', e)
|
||||
error.value = e as Error
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch a specific page of packages
|
||||
const fetchPackagePage = async (offset: number, limit: number) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const packageData = await apiClient.getPackages({ limit, offset })
|
||||
|
||||
if (packageData && data.value) {
|
||||
data.value.packages.totalPackages = [...packageData.packages]
|
||||
data.value.packages.offset = offset
|
||||
data.value.packages.limit = limit
|
||||
|
||||
// Update byStatus categorization
|
||||
data.value.packages.byStatus = {
|
||||
latest: packageData.packages.filter((pkg) => pkg.status === 'latest') || [],
|
||||
built: packageData.packages.filter((pkg) => pkg.status === 'built') || [],
|
||||
failed: packageData.packages.filter((pkg) => pkg.status === 'failed') || [],
|
||||
skipped: packageData.packages.filter((pkg) => pkg.status === 'skipped') || [],
|
||||
delayed: packageData.packages.filter((pkg) => pkg.status === 'delayed') || [],
|
||||
queued: packageData.packages.filter((pkg) => pkg.status === 'queued') || [],
|
||||
building: packageData.packages.filter((pkg) => pkg.status === 'building') || [],
|
||||
signing: packageData.packages.filter((pkg) => pkg.status === 'signing') || [],
|
||||
unknown: packageData.packages.filter((pkg) => pkg.status === 'unknown') || []
|
||||
}
|
||||
|
||||
// Update byRepo categorization
|
||||
data.value.packages.byRepo = {
|
||||
core: {
|
||||
v2: packageData.packages.filter((pkg) => pkg.repo === 'core-x86-64-v2') || [],
|
||||
v3: packageData.packages.filter((pkg) => pkg.repo === 'core-x86-64-v3') || [],
|
||||
v4: packageData.packages.filter((pkg) => pkg.repo === 'core-x86-64-v4') || []
|
||||
},
|
||||
extra: {
|
||||
v2: packageData.packages.filter((pkg) => pkg.repo === 'extra-x86-64-v2') || [],
|
||||
v3: packageData.packages.filter((pkg) => pkg.repo === 'extra-x86-64-v3') || [],
|
||||
v4: packageData.packages.filter((pkg) => pkg.repo === 'extra-x86-64-v4') || []
|
||||
},
|
||||
multilib: {
|
||||
v2: packageData.packages.filter((pkg) => pkg.repo === 'multilib-x86-64-v2') || [],
|
||||
v3: packageData.packages.filter((pkg) => pkg.repo === 'multilib-x86-64-v3') || [],
|
||||
v4: packageData.packages.filter((pkg) => pkg.repo === 'multilib-x86-64-v4') || []
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error fetching page data:', e)
|
||||
error.value = e as Error
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const startAutoRefresh = async () => {
|
||||
stopAutoRefresh()
|
||||
|
||||
init()
|
||||
await fetchAllPackageData()
|
||||
|
||||
refreshInterval = setInterval(fetchAllPackageData, UpdateInterval * 60 * 1000)
|
||||
}
|
||||
|
||||
const stopAutoRefresh = () => {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval)
|
||||
refreshInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
const getAllPackages = (): { packages: Package[]; total: number } => {
|
||||
if (!data.value) return { packages: [], total: 0 }
|
||||
|
||||
return {
|
||||
packages: data.value.packages.totalPackages || [],
|
||||
total: data.value.stats.total || 0
|
||||
}
|
||||
}
|
||||
|
||||
const getPackagesByStatus = (
|
||||
status: Package['status']
|
||||
): { packages: Array<Package>; total: number } => {
|
||||
if (!data.value) return { packages: [], total: 0 }
|
||||
|
||||
return {
|
||||
packages: data.value.packages.byStatus[status],
|
||||
total:
|
||||
status in data.value.stats
|
||||
? typeof data.value.stats[status as keyof typeof data.value.stats] === 'number'
|
||||
? (data.value.stats[status as keyof typeof data.value.stats] as number)
|
||||
: 0
|
||||
: 0
|
||||
}
|
||||
}
|
||||
|
||||
const getPackageStats = ():
|
||||
| (Stats & { total: number; built: number; building: number })
|
||||
| null => {
|
||||
return data.value?.stats ?? null
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
init,
|
||||
fetchAllPackageData,
|
||||
fetchPackagePage,
|
||||
startAutoRefresh,
|
||||
stopAutoRefresh,
|
||||
getAllPackages,
|
||||
getPackageStats,
|
||||
getPackagesByStatus
|
||||
}
|
||||
})
|
44
frontend/src/types/dataStore.ts
Normal file
44
frontend/src/types/dataStore.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Stats } from '@/types/Stats'
|
||||
import { Package } from '@/types/Package'
|
||||
|
||||
export interface dataStore {
|
||||
lastUpdated: number
|
||||
packages: {
|
||||
offset: number
|
||||
limit: number
|
||||
totalPackages: Array<Package>
|
||||
byStatus: {
|
||||
latest: Array<Package>
|
||||
built: Array<Package>
|
||||
failed: Array<Package>
|
||||
skipped: Array<Package>
|
||||
delayed: Array<Package>
|
||||
queued: Array<Package>
|
||||
building: Array<Package>
|
||||
signing: Array<Package>
|
||||
unknown: Array<Package>
|
||||
}
|
||||
byRepo: {
|
||||
core: {
|
||||
v2: Array<Package>
|
||||
v3: Array<Package>
|
||||
v4: Array<Package>
|
||||
}
|
||||
extra: {
|
||||
v2: Array<Package>
|
||||
v3: Array<Package>
|
||||
v4: Array<Package>
|
||||
}
|
||||
multilib: {
|
||||
v2: Array<Package>
|
||||
v3: Array<Package>
|
||||
v4: Array<Package>
|
||||
}
|
||||
}
|
||||
}
|
||||
stats: Stats & {
|
||||
total: number
|
||||
built: number
|
||||
building: number
|
||||
}
|
||||
}
|
50
frontend/src/utils/fetchUtils.ts
Normal file
50
frontend/src/utils/fetchUtils.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Packages } from '@/types/Packages'
|
||||
import { Stats } from '@/types/Stats'
|
||||
|
||||
export class ApiClient {
|
||||
private readonly baseUrl: string
|
||||
|
||||
constructor() {
|
||||
this.baseUrl = import.meta.env.VITE_BASE_URL
|
||||
}
|
||||
|
||||
async fetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||
const url = `${this.baseUrl}${endpoint}`
|
||||
|
||||
try {
|
||||
const response = await fetch(url, options)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
} catch (error) {
|
||||
console.error(`Error fetching from ${url}:`, error)
|
||||
throw error // Re-throw to allow handling by caller
|
||||
}
|
||||
}
|
||||
|
||||
async getPackages(
|
||||
params: {
|
||||
limit?: string | number
|
||||
offset?: string | number
|
||||
status?: string
|
||||
} = {}
|
||||
): Promise<Packages> {
|
||||
const { limit = 0, offset = 0, status } = params
|
||||
|
||||
let endpoint = `/packages?limit=${limit}&offset=${offset}`
|
||||
if (status) {
|
||||
endpoint += `&status=${status}`
|
||||
}
|
||||
|
||||
return this.fetch<Packages>(endpoint)
|
||||
}
|
||||
|
||||
async getStats(): Promise<Stats> {
|
||||
return this.fetch<Stats>('/stats')
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new ApiClient()
|
@@ -40,6 +40,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/types@npm:^7.27.0":
|
||||
version: 7.27.0
|
||||
resolution: "@babel/types@npm:7.27.0"
|
||||
dependencies:
|
||||
"@babel/helper-string-parser": "npm:^7.25.9"
|
||||
"@babel/helper-validator-identifier": "npm:^7.25.9"
|
||||
checksum: 10c0/6f1592eabe243c89a608717b07b72969be9d9d2fce1dee21426238757ea1fa60fdfc09b29de9e48d8104311afc6e6fb1702565a9cc1e09bc1e76f2b2ddb0f6e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@esbuild/aix-ppc64@npm:0.25.1":
|
||||
version: 0.25.1
|
||||
resolution: "@esbuild/aix-ppc64@npm:0.25.1"
|
||||
@@ -590,12 +600,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^22.13.11":
|
||||
version: 22.13.11
|
||||
resolution: "@types/node@npm:22.13.11"
|
||||
"@types/node@npm:^22.14.1":
|
||||
version: 22.14.1
|
||||
resolution: "@types/node@npm:22.14.1"
|
||||
dependencies:
|
||||
undici-types: "npm:~6.20.0"
|
||||
checksum: 10c0/f6ee33d36372242535c38640fe7550a6640d8a775ec19b55bfc11775b521cba072d892ca92a912332ce01b317293d645c1bf767f3f882ec719f2404a3d2a5b96
|
||||
undici-types: "npm:~6.21.0"
|
||||
checksum: 10c0/d49c4d00403b1c2348cf0701b505fd636d80aabe18102105998dc62fdd36dcaf911e73c7a868c48c21c1022b825c67b475b65b1222d84b704d8244d152bb7f86
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -696,6 +706,39 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/devtools-api@npm:^7.7.2":
|
||||
version: 7.7.2
|
||||
resolution: "@vue/devtools-api@npm:7.7.2"
|
||||
dependencies:
|
||||
"@vue/devtools-kit": "npm:^7.7.2"
|
||||
checksum: 10c0/418d3c868143a91518bc846965f7c8a955f072b8526d0f739f4d7dc00b13a0f56b214d876bfff338dc841762b526a1a4c11b5e8b0ab6dd7f3250a694ec8dfbe3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/devtools-kit@npm:^7.7.2":
|
||||
version: 7.7.2
|
||||
resolution: "@vue/devtools-kit@npm:7.7.2"
|
||||
dependencies:
|
||||
"@vue/devtools-shared": "npm:^7.7.2"
|
||||
birpc: "npm:^0.2.19"
|
||||
hookable: "npm:^5.5.3"
|
||||
mitt: "npm:^3.0.1"
|
||||
perfect-debounce: "npm:^1.0.0"
|
||||
speakingurl: "npm:^14.0.1"
|
||||
superjson: "npm:^2.2.1"
|
||||
checksum: 10c0/e052ba756558040855304b6ee13ba39131a44c89a9f78ab262c79f8a0e6b58fa379e1efa306a9a50675cac3e48baeb3f86b1560f64edf48cbc0695165d0b2be6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/devtools-shared@npm:^7.7.2":
|
||||
version: 7.7.2
|
||||
resolution: "@vue/devtools-shared@npm:7.7.2"
|
||||
dependencies:
|
||||
rfdc: "npm:^1.4.1"
|
||||
checksum: 10c0/6399135da41a91f48c3db7c59cedb01ad331af7784ef0877c15c669ad5a5d1cce68f73d50d81f85a31a90b0d6323ff807ebe5b1fb041d1e86932f2c983a0cdad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@vue/language-core@npm:2.2.8":
|
||||
version: 2.2.8
|
||||
resolution: "@vue/language-core@npm:2.2.8"
|
||||
@@ -818,23 +861,24 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "alhp-web@workspace:."
|
||||
dependencies:
|
||||
"@babel/types": "npm:^7.26.10"
|
||||
"@babel/types": "npm:^7.27.0"
|
||||
"@fontsource/roboto": "npm:^5.2.5"
|
||||
"@mdi/font": "npm:7.4.47"
|
||||
"@types/node": "npm:^22.13.11"
|
||||
"@types/node": "npm:^22.14.1"
|
||||
"@vitejs/plugin-vue": "npm:^5.2.3"
|
||||
fork-awesome: "npm:^1.2.0"
|
||||
pinia: "npm:^3.0.2"
|
||||
prettier: "npm:^3.5.3"
|
||||
roboto-fontface: "npm:^0.10.0"
|
||||
sass: "npm:^1.86.0"
|
||||
typescript: "npm:^5.8.2"
|
||||
sass: "npm:^1.86.3"
|
||||
typescript: "npm:^5.8.3"
|
||||
unplugin-fonts: "npm:^1.3.1"
|
||||
unplugin-vue-components: "npm:^28.4.1"
|
||||
vite: "npm:^6.2.2"
|
||||
vite-plugin-vuetify: "npm:^2.1.0"
|
||||
unplugin-vue-components: "npm:^28.5.0"
|
||||
vite: "npm:^6.2.6"
|
||||
vite-plugin-vuetify: "npm:^2.1.1"
|
||||
vue: "npm:^3.5.13"
|
||||
vue-tsc: "npm:^2.2.8"
|
||||
vuetify: "npm:^3.7.18"
|
||||
vuetify: "npm:^3.8.1"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -899,6 +943,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"birpc@npm:^0.2.19":
|
||||
version: 0.2.19
|
||||
resolution: "birpc@npm:0.2.19"
|
||||
checksum: 10c0/be3c6a4044e3041a5d8eb4c4d50b57b46158dc8149ada718ead20544e50b68b72b34c9d8bf0457d23d5f18e5a66d206b8bef5ff22c1018e1e39d373187eed455
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"brace-expansion@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "brace-expansion@npm:2.0.1"
|
||||
@@ -1018,6 +1069,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"copy-anything@npm:^3.0.2":
|
||||
version: 3.0.5
|
||||
resolution: "copy-anything@npm:3.0.5"
|
||||
dependencies:
|
||||
is-what: "npm:^4.1.8"
|
||||
checksum: 10c0/01eadd500c7e1db71d32d95a3bfaaedcb839ef891c741f6305ab0461398056133de08f2d1bf4c392b364e7bdb7ce498513896e137a7a183ac2516b065c28a4fe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-spawn@npm:^7.0.0":
|
||||
version: 7.0.3
|
||||
resolution: "cross-spawn@npm:7.0.3"
|
||||
@@ -1393,6 +1453,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hookable@npm:^5.5.3":
|
||||
version: 5.5.3
|
||||
resolution: "hookable@npm:5.5.3"
|
||||
checksum: 10c0/275f4cc84d27f8d48c5a5cd5685b6c0fea9291be9deea5bff0cfa72856ed566abde1dcd8cb1da0f9a70b4da3d7ec0d60dc3554c4edbba647058cc38816eced3d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-cache-semantics@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "http-cache-semantics@npm:4.1.1"
|
||||
@@ -1506,6 +1573,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-what@npm:^4.1.8":
|
||||
version: 4.1.16
|
||||
resolution: "is-what@npm:4.1.16"
|
||||
checksum: 10c0/611f1947776826dcf85b57cfb7bd3b3ea6f4b94a9c2f551d4a53f653cf0cb9d1e6518846648256d46ee6c91d114b6d09d2ac8a07306f7430c5900f87466aae5b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isexe@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "isexe@npm:2.0.0"
|
||||
@@ -1540,7 +1614,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"local-pkg@npm:^1.0.0":
|
||||
"local-pkg@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "local-pkg@npm:1.1.1"
|
||||
dependencies:
|
||||
@@ -1716,6 +1790,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mitt@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "mitt@npm:3.0.1"
|
||||
checksum: 10c0/3ab4fdecf3be8c5255536faa07064d05caa3dd332bd318ff02e04621f7b3069ca1de9106cfe8e7ced675abfc2bec2ce4c4ef321c4a1bb1fb29df8ae090741913
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mkdirp@npm:^1.0.3":
|
||||
version: 1.0.4
|
||||
resolution: "mkdirp@npm:1.0.4"
|
||||
@@ -1868,6 +1949,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"perfect-debounce@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "perfect-debounce@npm:1.0.0"
|
||||
checksum: 10c0/e2baac416cae046ef1b270812cf9ccfb0f91c04ea36ac7f5b00bc84cb7f41bdbba087c0ab21b4e02a7ef3a1f1f6db399f137cecec46868bd7d8d88c2a9ee431f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"picocolors@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "picocolors@npm:1.1.1"
|
||||
@@ -1889,6 +1977,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pinia@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "pinia@npm:3.0.2"
|
||||
dependencies:
|
||||
"@vue/devtools-api": "npm:^7.7.2"
|
||||
peerDependencies:
|
||||
typescript: ">=4.4.4"
|
||||
vue: ^2.7.0 || ^3.5.11
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 10c0/4c21412ddb32c48c1d9fb9fb47a2cd40bc4af9198e65392423ab97f6a9da31d0b880bc59b008967058643a988cb574025f885a1d0e4faf47bec25521933bb27f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pkg-types@npm:^1.3.0":
|
||||
version: 1.3.1
|
||||
resolution: "pkg-types@npm:1.3.1"
|
||||
@@ -1999,6 +2102,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rfdc@npm:^1.4.1":
|
||||
version: 1.4.1
|
||||
resolution: "rfdc@npm:1.4.1"
|
||||
checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"roboto-fontface@npm:^0.10.0":
|
||||
version: 0.10.0
|
||||
resolution: "roboto-fontface@npm:0.10.0"
|
||||
@@ -2094,9 +2204,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sass@npm:^1.86.0":
|
||||
version: 1.86.0
|
||||
resolution: "sass@npm:1.86.0"
|
||||
"sass@npm:^1.86.3":
|
||||
version: 1.86.3
|
||||
resolution: "sass@npm:1.86.3"
|
||||
dependencies:
|
||||
"@parcel/watcher": "npm:^2.4.1"
|
||||
chokidar: "npm:^4.0.0"
|
||||
@@ -2107,7 +2217,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
sass: sass.js
|
||||
checksum: 10c0/921caea1fd8a450d4a986e5570ce13c4ca7b2a57da390811add3d2087ad8f46f53b34652ddcb237d8bdaad49c560b8d6eee130c733c787d058bc5a71a914c139
|
||||
checksum: 10c0/ba819a0828f732adf7a94cd8ca017bce92bc299ffb878836ed1da80a30612bfbbf56a5e42d6dff3ad80d919c2025afb42948fc7b54a7bc61a9a2d58e1e0c558a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2192,6 +2302,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"speakingurl@npm:^14.0.1":
|
||||
version: 14.0.1
|
||||
resolution: "speakingurl@npm:14.0.1"
|
||||
checksum: 10c0/1de1d1b938a7c4d9e79593ff7a26d312ec04a7c3234ca40b7f9b8106daf74ea9d2110a077f5db97ecf3762b83069e3ccbf9694431b51d4fcfd863f0b3333c342
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sprintf-js@npm:^1.1.3":
|
||||
version: 1.1.3
|
||||
resolution: "sprintf-js@npm:1.1.3"
|
||||
@@ -2248,6 +2365,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"superjson@npm:^2.2.1":
|
||||
version: 2.2.2
|
||||
resolution: "superjson@npm:2.2.2"
|
||||
dependencies:
|
||||
copy-anything: "npm:^3.0.2"
|
||||
checksum: 10c0/aa49ebe6653e963020bc6a1ed416d267dfda84cfcc3cbd3beffd75b72e44eb9df7327215f3e3e77528f6e19ad8895b16a4964fdcd56d1799d14350db8c92afbc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:^6.1.11, tar@npm:^6.1.2":
|
||||
version: 6.2.1
|
||||
resolution: "tar@npm:6.2.1"
|
||||
@@ -2281,23 +2407,23 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^5.8.2":
|
||||
version: 5.8.2
|
||||
resolution: "typescript@npm:5.8.2"
|
||||
"typescript@npm:^5.8.3":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@npm:5.8.3"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6
|
||||
checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A^5.8.2#optional!builtin<compat/typescript>":
|
||||
version: 5.8.2
|
||||
resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin<compat/typescript>::version=5.8.2&hash=5786d5"
|
||||
"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin<compat/typescript>":
|
||||
version: 5.8.3
|
||||
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2
|
||||
checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2308,10 +2434,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici-types@npm:~6.20.0":
|
||||
version: 6.20.0
|
||||
resolution: "undici-types@npm:6.20.0"
|
||||
checksum: 10c0/68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf
|
||||
"undici-types@npm:~6.21.0":
|
||||
version: 6.21.0
|
||||
resolution: "undici-types@npm:6.21.0"
|
||||
checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2359,17 +2485,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unplugin-vue-components@npm:^28.4.1":
|
||||
version: 28.4.1
|
||||
resolution: "unplugin-vue-components@npm:28.4.1"
|
||||
"unplugin-vue-components@npm:^28.5.0":
|
||||
version: 28.5.0
|
||||
resolution: "unplugin-vue-components@npm:28.5.0"
|
||||
dependencies:
|
||||
chokidar: "npm:^3.6.0"
|
||||
debug: "npm:^4.4.0"
|
||||
local-pkg: "npm:^1.0.0"
|
||||
local-pkg: "npm:^1.1.1"
|
||||
magic-string: "npm:^0.30.17"
|
||||
mlly: "npm:^1.7.4"
|
||||
tinyglobby: "npm:^0.2.12"
|
||||
unplugin: "npm:^2.2.0"
|
||||
unplugin: "npm:^2.3.2"
|
||||
unplugin-utils: "npm:^0.2.4"
|
||||
peerDependencies:
|
||||
"@babel/parser": ^7.15.8
|
||||
@@ -2380,7 +2506,7 @@ __metadata:
|
||||
optional: true
|
||||
"@nuxt/kit":
|
||||
optional: true
|
||||
checksum: 10c0/f05448285e6d049b8aeadf5747cf7cda23105bfe8691326217abf3a5aa924768279f1c26f37ed0cd98c00f97ea7ce1cd5d5ed2916fb09f2b817f25563ece825a
|
||||
checksum: 10c0/4a2419cee6a8d19e0dd121fa56afef29c981434ee4f632e972e46aec52f161608c85b0806737308c597ca6d19b07d62f654f4070bd4302c1691a1c865acb9248
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2394,13 +2520,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unplugin@npm:^2.2.0":
|
||||
version: 2.2.2
|
||||
resolution: "unplugin@npm:2.2.2"
|
||||
"unplugin@npm:^2.3.2":
|
||||
version: 2.3.2
|
||||
resolution: "unplugin@npm:2.3.2"
|
||||
dependencies:
|
||||
acorn: "npm:^8.14.1"
|
||||
picomatch: "npm:^4.0.2"
|
||||
webpack-virtual-modules: "npm:^0.6.2"
|
||||
checksum: 10c0/76ba320f0c5d18c31c6efab0bcf1f487e900193da7d9a63d50ccb87ea3c50bc9952111caee4ec5017bdcb53445dce275b994c6aeca6b92567db283ec5d9fc01b
|
||||
checksum: 10c0/157a50072601b9bfbf3ab27a76a04685fb0af0c1a579d958787ffcb28a4d64e09acf42f0176e8767ccd940f27ee52d97a7f6aa6ce2e1e0dbe666ec26519750ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2411,9 +2538,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite-plugin-vuetify@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "vite-plugin-vuetify@npm:2.1.0"
|
||||
"vite-plugin-vuetify@npm:^2.1.1":
|
||||
version: 2.1.1
|
||||
resolution: "vite-plugin-vuetify@npm:2.1.1"
|
||||
dependencies:
|
||||
"@vuetify/loader-shared": "npm:^2.1.0"
|
||||
debug: "npm:^4.3.3"
|
||||
@@ -2422,13 +2549,13 @@ __metadata:
|
||||
vite: ">=5"
|
||||
vue: ^3.0.0
|
||||
vuetify: ^3.0.0
|
||||
checksum: 10c0/c9b6b3ee4b75ffc9b1f124f8f635d372f3258bd6f2abb48db42a80a4efe54127c9e325ad12eb92278ddbd629dfd1111810ec2bd6b1fb076f0724b789f33054e7
|
||||
checksum: 10c0/629893488ae23ffd9e9a32fccf2d6ff4d5a00826329ec90e9765a17d182a9200ffe11430bd418227119e8ef3ed21eaec1bab0635a77fdcb4b64aac10f38adcb0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:^6.2.2":
|
||||
version: 6.2.2
|
||||
resolution: "vite@npm:6.2.2"
|
||||
"vite@npm:^6.2.6":
|
||||
version: 6.2.6
|
||||
resolution: "vite@npm:6.2.6"
|
||||
dependencies:
|
||||
esbuild: "npm:^0.25.0"
|
||||
fsevents: "npm:~2.3.3"
|
||||
@@ -2474,7 +2601,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
vite: bin/vite.js
|
||||
checksum: 10c0/52f5b1c10cfe5e3b6382c6de1811ebbf76df9b5a8bab3d65169446c6b54a5f1528f775b1548009a6d8aad11def20fba046bb3e9abb10c0c2c9ccd78118623bb8
|
||||
checksum: 10c0/68a2ed3e61bdd654c59b817b4f3203065241c66d1739faa707499130f3007bc3a666c7a8320a4198e275e62b5e4d34d9b78a6533f69e321d366e76f5093b2071
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2517,14 +2644,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vuetify@npm:^3.7.18":
|
||||
version: 3.7.18
|
||||
resolution: "vuetify@npm:3.7.18"
|
||||
"vuetify@npm:^3.8.1":
|
||||
version: 3.8.1
|
||||
resolution: "vuetify@npm:3.8.1"
|
||||
peerDependencies:
|
||||
typescript: ">=4.7"
|
||||
vite-plugin-vuetify: ">=1.0.0"
|
||||
vue: ^3.3.0
|
||||
webpack-plugin-vuetify: ">=2.0.0"
|
||||
vite-plugin-vuetify: ">=2.1.0"
|
||||
vue: ^3.5.0
|
||||
webpack-plugin-vuetify: ">=3.1.0"
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
@@ -2532,7 +2659,7 @@ __metadata:
|
||||
optional: true
|
||||
webpack-plugin-vuetify:
|
||||
optional: true
|
||||
checksum: 10c0/98686640dc11bb59e12fdb214e0fc8b2a4ecd2f8286448fddb6abecd858a0fd5db5927d443e9b3be7ff92303ae1c8bc01bf4712278a9b6e4f4d72358a36e0a0e
|
||||
checksum: 10c0/b6fd1a96325b16a1f88acd1ef9d385118c09f25793bc7eddc3da2fe57e84a17d200d77dc14a2e0bc1b67fb6f301ce15b760185d2faafeaf61c584a92df6b0b45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Reference in New Issue
Block a user