Compare commits

...

22 Commits

Author SHA1 Message Date
e384635da5 Refactor last updated handling and add tooltips for timestamps
Moved `lastUpdated` assignment to appropriate API call logic and replaced `lastUpdatedSeconds` with a computed property driven by a reactive `now` value. Added tooltips to display localized date formats for "Last updated" and "Last Mirror sync" timestamps, enhancing UX.
2025-05-05 13:53:33 +02:00
145de73133 Add separate loading and error states for currently building
Introduced `loadingCurrentlyBuilding` and `errorCurrentlyBuilding` states to manage the fetching of currently building packages independently. Updated the logic in `fetchCurrentlyBuilding` and related UI bindings to reflect this change, ensuring clearer state handling and improved error tracking.
2025-05-04 23:57:45 +02:00
35806589f0 Rename .env.example and update .gitignore for consistency
Renamed the frontend `.env.example` to `example.env` for naming clarity. Updated `.gitignore` to include `.env` and `.idea/codeStyles` to ensure unnecessary files are excluded from version control.
2025-05-04 23:39:15 +02:00
7445919003 Refactor package types and improve filter handling
Switch to API schema-defined types for package properties, replacing custom typings for consistency. Streamline filter initialization, validation, and URL parameter handling, while adding safeguards for null values. Simplify components by removing unused exports and types.
2025-05-04 23:32:51 +02:00
3886c7bcbd Refactor StatItem to use required color prop without defaults
Replaced optional `color` prop with a required one and removed default value handling. Simplified template binding by directly applying inline styles for color. This improves clarity and ensures consistent color usage.
2025-05-04 22:19:06 +02:00
075c246710 Migrate data management to OpenAPI-based approach
Replaced manual data handling and filtering logic with an auto-generated OpenAPI client. Introduced new modular Pinia stores for stats and packages, improving maintainability and decoupling data management. Removed outdated custom implementations to streamline the codebase.
2025-05-04 22:18:02 +02:00
555feddabf Add type-safe API client with utility functions
Introduce a type-safe API client using OpenAPI types for ALHP. Implement utility functions `getPackages` and `getStats` for common API usage, and add comprehensive documentation with examples for ease of integration.
2025-05-04 22:18:02 +02:00
83debac064 Update example env file with full API_BASE_URL
This change replaces the placeholder `API_URL` with a full URL in the `VITE_BASE_URL` field of the example `.env` file. It ensures a clearer default configuration for developers setting up the environment. Other values remain unchanged.
2025-05-04 22:18:02 +02:00
24c3463c86 Add OpenAPI integration and generate API types
This commit adds `openapi-typescript` and `openapi-fetch` dependencies to enable OpenAPI integration. A new `generate-api-types` script is introduced to generate TypeScript types from the OpenAPI spec, and it runs prior to the `build` process via the `prebuild` script. Finally, updates to `yarn.lock` reflect the addition of these dependencies.
2025-05-04 22:18:02 +02:00
6f861798ba update deps 2025-04-20 17:32:14 +02:00
7ab90d4af6 add building to general stats 2025-04-20 17:30:30 +02:00
d5f24feb9e add last_mirror_ts to general stats 2025-04-20 17:21:43 +02:00
ad5ce609fc Improve URL parameter handling and add error handling in filters
Refactored the `initFromUrl` function to handle edge cases, including invalid or malformed URL parameters, and added a fallback to default filter options in case of errors. Improved validation for `page`, `status`, and `repo` parameters, ensuring robust behavior and logging errors for debugging.
2025-04-14 21:58:54 +02:00
c458b564ce Update dependencies and fix watch logic in usePackageFilters
Adjusted the watcher in `usePackageFilters` to track the correct reactive object and call `applyFilters` when necessary. Updated various dependencies in `yarn.lock` to their latest versions for compatibility and improved functionality.
2025-04-14 21:52:13 +02:00
bcb9569b26 Refactor package filtering and display components
Replaced inline package filtering and table logic with dedicated reusable components: `PackageFilters` and `PackageTable`. Introduced a composable (`usePackageFilters`) for managing filtering logic and state, and optimized display logic with a new `usePackageDisplay` composable. This improves maintainability, readability, and separation of concerns.
2025-04-14 21:43:18 +02:00
90be95afda Refactor CurrentlyBuilding to use centralized data store
Replaced local state and fetching logic with a Vuex-like data store for better state management. Updated import paths, removed redundant code, and ensured reactive updates using computed properties and watchers. This enhances maintainability and reduces code duplication.
2025-04-14 21:42:52 +02:00
c864664536 Refactor iframe height computation for responsiveness
Replaces the hard-coded iframe height logic with a computed property to dynamically adjust height based on screen width. This simplifies the code and improves maintainability while ensuring proper responsive design behavior.
2025-04-14 21:41:55 +02:00
9762505a24 Refactor BuildStats and MainNav components
Modularize BuildStats by splitting it into smaller components (StatsListSection, StatItem) for better code reusability and readability. Update MainNav to reflect this restructuring and improve the handling of dynamic styles, computed properties, and data binding.
2025-04-14 21:41:28 +02:00
9adeaa4483 Update UI to handle loading and error states for data fetch
Introduced conditional UI components to display loading and error messages during data fetching. Integrated the data store to manage state and added lifecycle hooks for starting and stopping auto-refresh. These changes improve user experience and ensure better feedback during data operations.
2025-04-14 21:39:20 +02:00
43ce135fc6 Add Pinia for state management and API data handling.
Introduced Pinia as the state management library and integrated it with the app. Implemented an `ApiClient` utility and a `dataStore` to fetch, categorize, and manage packages and stats data. Updated application entry to include Pinia and adjusted dependencies in `package.json`.
2025-04-14 21:38:48 +02:00
4e722e5e60 Add type definitions and example .env file for environment vars
Introduce `env.d.ts` to define types for VITE environment variables, ensuring better type safety and development experience. Provide a sample `.env.example` file to guide configuration setup.
2025-04-14 21:37:32 +02:00
265bfac74a Handle package data fetch errors and improve UI feedback.
Added error handling for package data fetches with visual cues to indicate failures (e.g., red pulsating circle). Updated UI text to prompt retries on failures and refactored timers for better synchronization. Improved code readability by removing redundant URL formatting.
2025-04-10 22:48:13 +02:00
37 changed files with 2007 additions and 799 deletions

2
.gitignore vendored
View File

@@ -11,6 +11,8 @@
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/codeStyles
.idea/codeStyles/*
# AWS User-specific
.idea/**/aws.xml

19
api.go
View File

@@ -5,6 +5,7 @@ import (
"github.com/go-chi/render"
log "github.com/sirupsen/logrus"
"net/http"
"os"
"somegit.dev/ALHP/ALHP.GO/ent"
"somegit.dev/ALHP/ALHP.GO/ent/dbpackage"
"somegit.dev/ALHP/ALHP.GO/ent/predicate"
@@ -18,6 +19,8 @@ type StatsResponse struct {
Skipped int `json:"skipped"`
Latest int `json:"latest"`
Queued int `json:"queued"`
Building int `json:"building"`
LastMirrorTimestamp *int64 `json:"last_mirror_timestamp,omitempty"`
LTO *struct {
Enabled int `json:"enabled"`
Disabled int `json:"disabled"`
@@ -71,6 +74,8 @@ func GetStats(w http.ResponseWriter, r *http.Request) {
resp.Latest = c.Count
case dbpackage.StatusQueued:
resp.Queued = c.Count
case dbpackage.StatusBuilding:
resp.Building = c.Count
}
}
@@ -93,6 +98,20 @@ func GetStats(w http.ResponseWriter, r *http.Request) {
}
}
if os.Getenv("ALHP_TIMESTAMP_PATH") != "" {
tsFile, err := os.ReadFile(os.Getenv("ALHP_TIMESTAMP_PATH"))
if err != nil {
log.Warningf("error reading timestamp file: %v", err)
} else {
ts, err := strconv.ParseInt(string(tsFile), 10, 64)
if err != nil {
log.Warningf("error parsing timestamp file: %v", err)
} else {
resp.LastMirrorTimestamp = &ts
}
}
}
render.Status(r, http.StatusOK)
render.JSON(w, r, resp)
}

1
frontend/.gitignore vendored
View File

@@ -3,6 +3,7 @@ node_modules
/dist
# local env files
.env
.env.local
.env.*.local

View File

@@ -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
View 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 '@/*'

3
frontend/example.env Normal file
View File

@@ -0,0 +1,3 @@
VITE_BASE_URL="https://API.URL"
VITE_UPDATE_INTERVAL=5
VITE_LIMIT=50

View File

@@ -2,6 +2,8 @@
"name": "alhp-web",
"version": "0.0.0",
"scripts": {
"generate-api-types": "yarn openapi-typescript ../openapi_alhp.yaml --output src/generated/alhp.ts",
"prebuild": "npm run generate-api-types",
"dev": "node --no-warnings ./node_modules/.bin/vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
@@ -10,21 +12,24 @@
"@fontsource/roboto": "^5.2.5",
"@mdi/font": "7.4.47",
"fork-awesome": "^1.2.0",
"openapi-fetch": "^0.13.5",
"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",
"openapi-typescript": "^7.6.1",
"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"

View File

@@ -15,6 +15,43 @@
<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 { useStatsStore } from '@/stores/statsStore'
import { onBeforeMount, onUnmounted } from 'vue'
import { usePackagesStore } from '@/stores'
const statsStore = useStatsStore()
const packagesStore = usePackagesStore()
let refreshInterval: number | null = null
const startAutoRefresh = (intervalMinutes = Number(import.meta.env.VITE_UPDATE_INTERVAL) || 5) => {
stopAutoRefresh()
refreshInterval = window.setInterval(
() => {
statsStore.fetchStats()
packagesStore.fetchPackages()
packagesStore.fetchCurrentlyBuilding()
},
intervalMinutes * 60 * 1000
)
}
const stopAutoRefresh = () => {
if (refreshInterval !== null) {
clearInterval(refreshInterval)
refreshInterval = null
}
}
onBeforeMount(() => {
statsStore.fetchStats()
packagesStore.fetchPackages(true)
packagesStore.fetchCurrentlyBuilding()
startAutoRefresh()
})
onUnmounted(() => {
stopAutoRefresh()
})
</script>

106
frontend/src/api/README.md Normal file
View File

@@ -0,0 +1,106 @@
# ALHP API Client
This directory contains a type-safe API client for the ALHP API, generated from the OpenAPI specification.
## Usage
### Importing
```typescript
// Import the API client and its functions
import { apiClient, getPackages, getStats } from '@/api';
// Import types
import type { components } from '@/api';
```
### Fetching Packages
```typescript
import { getPackages } from '@/api';
import type { components } from '@/api';
// Example: Get packages with filtering
async function fetchPackages() {
try {
const result = await getPackages({
status: ['latest', 'building'],
pkgbase: 'linux',
exact: false,
repo: 'core-x86-64-v3',
offset: 0,
limit: 50
});
// Access the packages
const packages: components['schemas']['Package'][] = result.packages || [];
const total: number = result.total || 0;
return { packages, total };
} catch (error) {
console.error('Failed to fetch packages:', error);
return { packages: [], total: 0 };
}
}
```
### Fetching Stats
```typescript
import { getStats } from '@/api';
import type { components } from '@/api';
// Example: Get build statistics
async function fetchStats() {
try {
const stats = await getStats();
// Access the stats
const failedCount: number = stats.failed || 0;
const ltoEnabled: number = stats.lto?.enabled || 0;
return stats;
} catch (error) {
console.error('Failed to fetch stats:', error);
return null;
}
}
```
### Using the Raw API Client
If you need more control or want to use endpoints not covered by the utility functions, you can use the raw API client:
```typescript
import { apiClient } from '@/api';
// Example: Custom API call
async function customApiCall() {
const { data, error } = await apiClient.GET('/packages', {
params: {
query: {
// Your custom parameters
status: ['latest'],
limit: 10,
offset: 0
}
}
});
if (error) {
console.error('API error:', error);
return null;
}
return data;
}
```
## Types
The API client uses types generated from the OpenAPI specification. The main types are:
- `components['schemas']['Package']`: Type for package data
- `components['schemas']['Stats']`: Type for statistics data
These types provide full TypeScript intellisense and type checking.

View File

@@ -0,0 +1,59 @@
import createFetch from 'openapi-fetch'
import type { paths } from '@/generated/alhp'
// Create a type-safe API client using the OpenAPI types
const apiClient = createFetch<paths>({
baseUrl: import.meta.env.VITE_BASE_URL
})
/**
* Get packages with optional filtering
* @param params Query parameters for filtering packages
* @returns Promise with the packages response
*/
export const getPackages = async (params: {
status?: Array<
| 'latest'
| 'failed'
| 'built'
| 'skipped'
| 'delayed'
| 'building'
| 'signing'
| 'unknown'
| 'queued'
>
pkgbase?: string
exact?: boolean
repo?: string
offset: number
limit: number
}) => {
const { data, error } = await apiClient.GET('/packages', {
params: {
query: params
}
})
if (error) {
throw new Error(`Failed to fetch packages: ${(error as any).statusCode}`)
}
return data
}
/**
* Get build statistics
* @returns Promise with the stats response
*/
export const getStats = async () => {
const { data, error } = await apiClient.GET('/stats')
if (error) {
throw new Error(`Failed to fetch stats: ${(error as any).statusCode || 'unknown error'}`)
}
return data
}
export default apiClient

View File

@@ -0,0 +1,6 @@
// Export the API client and its functions
export { default as apiClient } from './client';
export { getPackages, getStats } from './client';
// Export types from the generated OpenAPI file
export type { components } from '../generated/alhp';

View File

@@ -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,14 @@
<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
*/
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
const iframeHeight = computed(() =>
width.value <= 800 ? `${NUMBER_OF_GRAPHS * GRAPH_HEIGHT}px` : '420px'
)
</script>

View File

@@ -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>

View File

@@ -11,18 +11,32 @@
<v-row :class="mobile ? 'mt-1' : ''" class="flex-nowrap ms-1 d-flex align-items-center">
<div
:class="
packageCount.building > 0 ? 'pulsating-circle-amber' : 'pulsating-circle-green'
updateFailed
? 'pulsating-circle-error'
: packageArrays.building.length > 0
? 'pulsating-circle-amber'
: 'pulsating-circle-green'
"
class="circle-offset flex-circle" />
<span class="ms-2">
{{ packageCount.building > 0 ? 'Building' : 'Idle' }}
{{
updateFailed
? 'Could not fetch data.'
: packageArrays.building.length > 0
? 'Building'
: 'Idle'
}}
</span>
</v-row>
</v-col>
<v-col v-if="packageCount.building > 0" class="v-col-12 v-col-lg-8 mb-3">
<v-col v-if="packageArrays.building.length > 0" class="v-col-12 v-col-lg-8 mb-3">
<v-progress-linear
:max="packageCount.built + packageCount.building + packageCount.queued"
:model-value="packageCount.built"
:max="
packageArrays.built.length +
packageArrays.building.length +
packageArrays.queued.length
"
:model-value="packageArrays.built.length"
color="light-blue"
height="10"
rounded
@@ -33,21 +47,41 @@
class="text-grey v-col-12 v-col-lg-2 mb-3"
cols="auto"
style="font-size: 13px">
<div v-if="!updateFailed" class="d-flex flex-column">
<span>
Last updated
{{ formatTimeAgo(lastUpdatedSeconds) }}
<v-tooltip activator="parent" location="start">
{{
lastUpdatedSeconds > 59
? rtf.format(-Math.floor(lastUpdatedSeconds / 60), 'minutes')
: rtf.format(-lastUpdatedSeconds, 'seconds')
unixTimestampToLocalizedDate(
Math.floor((packagesStore.state.lastUpdated || Date.now()) / 1000)
)
}}
</v-tooltip>
</span>
<span>
Last Mirror sync
{{ formatTimeAgo(lastMirrorSync) }}
<v-tooltip activator="parent" location="start">
{{
unixTimestampToLocalizedDate(
statsStore.state.stats?.last_mirror_timestamp || Math.floor(Date.now() / 1000)
)
}}
</v-tooltip>
</span>
</div>
<template v-else>Please try again later.</template>
</v-col>
</v-row>
</v-card-title>
<v-card-text
v-if="packageCount.building > 0 || packageCount.queued > 0"
v-if="packageArrays.building.length > 0 || packageArrays.queued.length > 0"
class="d-flex flex-column">
<v-list width="100%">
<v-list v-if="packageArrays.building.length > 0" class="mb-4" width="100%">
<v-list-subheader>Building</v-list-subheader>
<v-list-item v-for="(pkg, index) in packages.building" :key="index">
<v-list-item v-for="(pkg, index) in packageArrays.building" :key="index">
<template v-slot:prepend>
<div class="pulsating-circle-amber me-4" />
</template>
@@ -58,124 +92,87 @@
<v-list-item-subtitle>{{ pkg.arch_version }}</v-list-item-subtitle>
</v-list-item>
</v-list>
<v-sheet class="ps-4 mt-4" color="transparent" rounded width="100%">
<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="packageArrays.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, reactive, ref } from 'vue'
import { useDisplay } from 'vuetify'
import QueuedPackagesList from './QueuedPackagesList.vue'
import QueuedPackagesList from '@/components/CurrentlyBuilding/QueuedPackagesList.vue'
import { usePackagesStore, useStatsStore } from '@/stores'
const BASE_URL = 'https://api.alhp.dev/packages'
const UPDATE_INTERVAL_MINUTES = 5
const statsStore = useStatsStore()
const packagesStore = usePackagesStore()
const { mobile } = useDisplay()
const lastUpdatedTime = ref(0)
const lastUpdatedSeconds = ref(0)
const rtf = new Intl.RelativeTimeFormat('en', {
localeMatcher: 'best fit',
numeric: 'always',
style: 'long'
})
const packageCount = ref({
total: 0,
building: 0,
built: 0,
queued: 0
const now = ref(Math.floor(Date.now() / 1000))
const updateFailed = computed(
() => !!packagesStore.state.errorCurrentlyBuilding || !!statsStore.state.error
)
const lastUpdatedSeconds = computed(
() => now.value - Math.floor((packagesStore.state.lastUpdated || Date.now()) / 1000)
)
const lastMirrorSync = computed(
() => now.value - (statsStore.state.stats?.last_mirror_timestamp || Math.floor(Date.now() / 1000))
)
const packageArrays = reactive({
building: computed(
() =>
packagesStore.state.currentlyBuildingPackages.filter((pkg) => pkg.status === 'building') || []
),
queued: computed(
() =>
packagesStore.state.currentlyBuildingPackages.filter((pkg) => pkg.status === 'queued') || []
),
built: computed(
() =>
packagesStore.state.currentlyBuildingPackages.filter((pkg) => pkg.status === 'built') || []
)
})
const packages = ref<{
built: Array<Package>
building: Array<Package>
queued: Array<Package>
}>({
built: [],
building: [],
queued: []
})
let updateTimer: number | undefined
const startLastUpdatedTimer = () => {
updateTimer = window.setInterval(() => {
now.value = Math.floor(Date.now() / 1000)
}, 1000)
}
const fetchPackages = async (endpoint: string, status?: string) => {
let url = `${BASE_URL}?limit=0&offset=0`
if (status) {
url += `&status=${status}`
}
try {
const response = await fetch(url, { method: 'GET' })
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const json: Packages = await response.json()
return json
} catch (error) {
console.error('Error fetching packages:', error)
return null
const formatTimeAgo = (seconds: number): string => {
if (seconds >= 3600) {
return rtf.format(-Math.floor(seconds / 3600), 'hours')
} else if (seconds >= 60) {
return rtf.format(-Math.floor(seconds / 60), 'minutes')
} else {
return rtf.format(-seconds, 'seconds')
}
}
const getTotalPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=1&offset=0`)
if (json) {
packageCount.value.total = json.total
}
}
const getBuiltPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'built')
if (json) {
packageCount.value.built = json.total
packages.value.built = json.packages
}
}
const getBuildingPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'building')
if (json) {
packageCount.value.building = json.total
packages.value.building = json.packages
}
}
const getQueuedPackages = async () => {
const json = await fetchPackages(`${BASE_URL}?limit=0&offset=0`, 'queued')
if (json) {
packageCount.value.queued = json.total
packages.value.queued = json.packages
}
function unixTimestampToLocalizedDate(timestamp: number): string {
const date = new Date(timestamp * 1000) // convert to milliseconds
return date.toLocaleString(navigator.language)
}
onMounted(() => {
getTotalPackages()
getBuiltPackages()
getBuildingPackages()
getQueuedPackages()
startLastUpdatedTimer()
})
lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
setInterval(() => {
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
}, 1000)
setInterval(
() => {
getTotalPackages()
getBuiltPackages()
getBuildingPackages()
getQueuedPackages()
lastUpdatedTime.value = Date.now()
lastUpdatedSeconds.value = Math.floor((Date.now() - lastUpdatedTime.value) / 1000)
},
UPDATE_INTERVAL_MINUTES * 60 * 1000
)
onUnmounted(() => {
if (updateTimer) {
clearInterval(updateTimer)
}
})
</script>
@@ -222,16 +219,37 @@ onMounted(() => {
animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
}
.pulsating-circle-error {
background-color: rgba(225, 64, 6, 0.94);
border-radius: 50%;
width: 12px;
height: 12px;
}
.pulsating-circle-error:before {
content: '';
display: block;
width: 200%;
height: 200%;
box-sizing: border-box;
margin-left: -50%;
margin-top: -50%;
border-radius: 50%;
background-color: rgba(225, 64, 6, 0.94);
-webkit-animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
}
.circle-offset {
transform: translateY(10px); /* Preserves vertical shift as needed */
}
.flex-circle {
flex-shrink: 0; /* Prevents shrinking */
flex-grow: 0; /* Prevents growing */
width: 12px; /* Ensures consistent dimensions */
flex-shrink: 0;
flex-grow: 0;
width: 12px;
height: 12px;
border-radius: 50%; /* Maintains circular appearance */
border-radius: 50%;
}
@-webkit-keyframes pulse-ring {

View File

@@ -39,11 +39,11 @@
<script lang="ts" setup>
import { defineProps } from 'vue'
import type { Package } from '@/types/Package'
import { components } from '@/api'
defineProps({
packages: {
type: Array<Package>,
type: Array<components['schemas']['Package']>,
required: true,
default: () => []
}

View File

@@ -1,38 +1,102 @@
<template>
<v-app-bar color="#191c2a" style="background: #0d1538">
<v-container :class="width < 1440 ? 'ms-3' : 'mx-auto'" fluid style="max-width: 1440px">
<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">
<i class="fa fa-gitea"></i>
</a>
<v-app-bar :color="appBarColors.background" aria-label="Main Navigation" role="navigation">
<v-container :class="containerClasses" :style="{ maxWidth: maxContainerWidth }" fluid>
<v-row align="center">
<v-app-bar-title class="app-title">
<span aria-label="Home" class="home-link" role="button" tabindex="0">
{{ appTitle }}
</span>
<a
:href="repoUrl"
aria-label="ALHP GitHub Repository"
class="ms-2 gitea-link"
rel="noopener noreferrer"
target="_blank">
<i aria-hidden="true" class="fa fa-gitea"></i>
</a>
</v-app-bar-title>
<build-stats v-if="!mobile" />
<v-spacer v-if="isDesktop"></v-spacer>
<build-stats v-if="isDesktop || isTablet" :show-lto="isDesktop" />
<!-- Mobile menu button could be added here -->
</v-row>
</v-container>
</v-app-bar>
</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 isTablet = computed(() => mobile && width.value >= 650 && width.value < 960)
const isDesktop = computed(() => !mobile.value && !isTablet.value)
interface AppBarColors {
background: string
accent: string
}
const appTitle = 'ALHP Status'
const repoUrl = 'https://somegit.dev/ALHP/ALHP.GO'
const maxContainerWidth = '1440px'
const appBarColors: 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;
display: flex;
align-items: center;
}
.home-link {
color: white;
text-decoration: none;
font-weight: 500;
transition: opacity 0.2s;
}
.home-link:hover,
.home-link:focus {
opacity: 0.9;
outline: none;
}
.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;
display: inline-flex;
align-items: center;
}
.gitea-link:hover {
color: #609926;
.gitea-link:hover,
.gitea-link:focus {
color: v-bind('appBarColors.accent');
outline: none;
}
@media (max-width: 600px) {
.app-title {
font-size: 18px;
}
.gitea-link {
font-size: 22px;
}
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<v-sheet
v-if="!statsStore.state.loading && !statsStore.state.error"
:style="sheetStyles"
class="d-flex"
color="transparent">
<StatsListSection title="Stats">
<StatItem
v-for="(stat, key) in generalStats"
:key="key"
:color="stat.color"
:count="stat.count"
:title="key" />
</StatsListSection>
<StatsListSection v-if="showLto" title="LTO">
<StatItem
v-for="(stat, key) in ltoStats"
:key="key"
:color="stat.color"
:count="stat.count"
:title="key" />
</StatsListSection>
</v-sheet>
<v-sheet
v-else-if="statsStore.state.loading"
:style="sheetStyles"
class="d-flex align-center"
color="transparent">
<v-progress-circular class="mr-2" color="white" indeterminate size="20"></v-progress-circular>
<span class="text-caption">Loading stats...</span>
</v-sheet>
<v-sheet
v-else-if="statsStore.state.error"
:style="sheetStyles"
class="d-flex align-center"
color="transparent">
<span class="text-caption text-error">Error loading stats</span>
</v-sheet>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { useStatsStore } from '@/stores/statsStore'
import StatsListSection from '@/components/MainNav/BuildStats/StatsListSection.vue'
import StatItem from '@/components/MainNav/BuildStats/StatItem.vue'
interface Props {
showLto?: boolean
}
withDefaults(defineProps<Props>(), {
showLto: true
})
const COLORS = {
SUCCESS: '#069b35',
WARNING: '#b97808',
ERROR: '#b30303',
NEUTRAL: '#878787'
}
const sheetStyles = { gap: '50px' }
const statsStore = useStatsStore()
const generalStats = computed(() => ({
latest: {
count: statsStore.state.stats?.latest || 0,
color: COLORS.SUCCESS
},
queued: {
count: statsStore.state.stats?.queued || 0,
color: COLORS.WARNING
},
building: {
count: statsStore.state.stats?.building || 0,
color: COLORS.WARNING
},
skipped: {
count: statsStore.state.stats?.skipped || 0,
color: COLORS.NEUTRAL
},
failed: {
count: statsStore.state.stats?.failed || 0,
color: COLORS.ERROR
}
}))
const ltoStats = computed(() => ({
enabled: {
count: statsStore.state.stats?.lto?.enabled || 0,
color: COLORS.SUCCESS
},
disabled: {
count: statsStore.state.stats?.lto?.disabled || 0,
color: COLORS.ERROR
},
unknown: {
count: statsStore.state.stats?.lto?.unknown || 0,
color: COLORS.NEUTRAL
}
}))
</script>

View 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>

View File

@@ -0,0 +1,18 @@
<template>
<v-list bg-color="transparent" class="stats-list d-flex">
<v-list-subheader>{{ title }}:</v-list-subheader>
<slot></slot>
</v-list>
</template>
<script lang="ts" setup>
defineProps<{
title: string
}>()
</script>
<style lang="scss" scoped>
.stats-list {
border-radius: 5px;
}
</style>

View File

@@ -1,398 +1,15 @@
<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 />
<PackageTable />
</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'
// Constants
const OFFSET = 50
const ROW_HEIGHT = 60
const TRANSPARENT_COLOR = 'transparent'
// Refs
const page = ref(0)
const pagesMax = ref(1)
const packages = ref<Array<Package>>([])
const noPackagesFound = ref(false)
const init = ref(false)
const inputPkgBase = ref('')
const selectedRepo = ref<{ title: string; value: string } | undefined>(undefined)
const selectedStatuses = ref<Array<{ title: string; value: string }>>([])
const enableExact = ref(false)
// Display
const { mobile } = useDisplay()
// Select Items
const selectRepoItems = [
{ title: 'core-x86-64-v2', value: 'core-x86-64-v2' },
{ title: 'extra-x86-64-v2', value: 'extra-x86-64-v2' },
{ title: 'multilib-x86-64-v2', value: 'multilib-x86-64-v2' },
{ title: 'core-x86-64-v3', value: 'core-x86-64-v3' },
{ title: 'extra-x86-64-v3', value: 'extra-x86-64-v3' },
{ title: 'multilib-x86-64-v3', value: 'multilib-x86-64-v3' },
{ title: 'core-x86-64-v4', value: 'core-x86-64-v4' },
{ title: 'extra-x86-64-v4', value: 'extra-x86-64-v4' },
{ title: 'multilib-x86-64-v4', value: 'multilib-x86-64-v4' }
]
const selectStatusItems = [
{ title: 'Latest', value: 'latest' },
{ title: 'Built', value: 'built' },
{ title: 'Failed', value: 'failed' },
{ title: 'Skipped', value: 'skipped' },
{ title: 'Delayed', value: 'delayed' },
{ title: 'Queued', value: 'queued' },
{ title: 'Building', value: 'building' },
{ title: 'Signing', value: 'signing' },
{ title: 'Unknown', value: 'unknown' }
]
// Computed properties
const repoName = (repo: string) => repo.split('-')[0]
const repoVersion = (repo: string) => repo.split('-')[repo.split('-').length - 1]
// Helper function for getting the color associated with a version
const getVersionColor = (version: string) => {
switch (version) {
case 'v2':
return '#3498db'
case 'v3':
return '#f39c12'
case 'v4':
return '#2ecc71'
default:
return 'grey'
}
}
// Helper function for getting the status color
const getStatusColor = (status: Package['status']) => {
switch (status) {
case 'skipped':
return '#373737'
case 'queued':
return '#5d2f03'
case 'latest':
return ''
case 'failed':
return '#4f140f'
case 'signing':
return '#093372'
case 'building':
return '#084f46'
case 'unknown':
return '#191919'
default:
return ''
}
}
const getLto = (lto: Package['lto']) => {
switch (lto) {
case 'enabled':
return {
title: 'built with LTO',
class: 'fa fa-check fa-lg text-success'
}
case 'unknown':
return {
title: 'not built with LTO yet',
class: 'fa fa-hourglass-o fa-lg text-grey'
}
case 'disabled':
return {
title: 'LTO explicitly disabled',
class: 'fa fa-times fa-lg text-red'
}
case 'auto_disabled':
return {
title: 'LTO automatically disabled',
class: 'fa fa-times-circle-o fa-lg text-amber'
}
default:
return { title: '', class: '' }
}
}
const getDs = (ds: Package['debug_symbols']) => {
switch (ds) {
case 'available':
return {
title: 'Debug symbols available',
class: 'fa fa-check fa-lg text-success'
}
case 'unknown':
return {
title: 'Not built yet',
class: 'fa fa-hourglass-o fa-lg text-grey'
}
case 'not_available':
return {
title: 'Not built with debug symbols',
class: 'fa fa-times fa-lg text-red'
}
default:
return { title: '', class: '' }
}
}
// Helper function for updating URL params dynamically
const updateUrlParams = () => {
const params = new URLSearchParams()
if (page.value > 0) {
params.set('offset', (page.value * OFFSET).toString())
}
if (inputPkgBase.value) {
params.set('pkgbase', inputPkgBase.value.toLowerCase())
}
if (selectedRepo.value) {
params.set('repo', selectedRepo.value.value)
}
if (selectedStatuses.value.length > 0) {
selectedStatuses.value.forEach((status) => {
params.append('status', status.value)
})
}
if (enableExact.value) {
params.set('exact', '')
}
window.history.pushState(null, '', `${window.location.pathname}?${params}`)
}
const searchPackages = () => {
const offset = OFFSET * page.value
noPackagesFound.value = false
const params = new URLSearchParams()
if (page.value > 0) {
params.set('offset', offset.toString())
}
if (inputPkgBase.value) {
params.set('pkgbase', inputPkgBase.value.toLowerCase())
}
if (selectedRepo.value) {
params.set('repo', selectedRepo.value.value)
}
if (selectedStatuses.value.length > 0) {
selectedStatuses.value.forEach((status) => {
params.append('status', status.value)
})
}
if (enableExact.value) {
params.set('exact', '')
}
params.set('limit', OFFSET.toString())
params.set('offset', offset.toString())
fetch('https://api.alhp.dev/packages?' + params, {
method: 'GET'
})
.then((response) => {
if (response.ok) return response.json()
if (response.status === 404) {
noPackagesFound.value = true
pagesMax.value = 1
} else {
throw new Error(response.statusText)
}
})
.then((json: Packages) => {
if (!json) return
pagesMax.value = json.total / OFFSET + 1
packages.value = json.packages
})
.catch((error) => {
console.error(error)
})
}
// Properly set the initial values from URL params
onMounted(() => {
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()
})
// Watchers
watch(
[page, inputPkgBase, selectedRepo, selectedStatuses, enableExact],
() => {
if (init.value) return
updateUrlParams()
searchPackages()
},
{ deep: true }
)
import { TRANSPARENT_COLOR } from '@/config/constants'
import PackageFilters from '@/components/Packages/PackageFilters.vue'
import PackageTable from '@/components/Packages/PackageTable.vue'
</script>

View File

@@ -0,0 +1,140 @@
<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="pkgbase"
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="repo"
:items="REPO_ITEMS"
clearable
color="primary"
placeholder="Any Repo"
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="status"
: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="exact" 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="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 { usePackagesStore } from '@/stores'
import { computed, onMounted, ref, watch } from 'vue'
import { components } from '@/api'
const { mobile } = useDisplay()
const packagesStore = usePackagesStore()
const page = ref<number>(1)
const pkgbase = ref<string>()
const repo = ref<string>()
const status = ref<{ title: string; value: string }[]>([])
const exact = ref<boolean>()
const totalPages = computed(() => Math.ceil(packagesStore.state.total / packagesStore.state.limit))
const updateFilter = (pageVal?: number) => {
if (packagesStore.state.loading) return
if (pageVal) {
page.value = pageVal
} else {
page.value = 1
}
packagesStore.setFilters(
{
exact: exact.value,
status: status.value.map(
(state) => state.value as components['schemas']['Package']['status']
),
pkgbase: pkgbase.value !== null ? pkgbase.value : undefined,
repo: repo.value
},
page.value
)
}
const initFilters = () => {
page.value = packagesStore.state.offset / packagesStore.state.limit + 1
pkgbase.value = packagesStore.state.filters.pkgbase
repo.value = packagesStore.state.filters.repo
exact.value = packagesStore.state.filters.exact
if (packagesStore.state.filters.status) {
for (const state of packagesStore.state.filters.status) {
if (state) {
status.value.push({ title: state.toUpperCase(), value: state.toLowerCase() })
}
}
}
}
watch(
() => page.value,
(pageVal) => {
packagesStore.goToPage(pageVal)
}
)
// Watcher for pkgbase with debounce
let pkgbaseTimeout: ReturnType<typeof setTimeout> | null = null
watch(
() => pkgbase.value,
() => {
if (pkgbaseTimeout) clearTimeout(pkgbaseTimeout)
pkgbaseTimeout = setTimeout(() => {
updateFilter()
}, 300)
}
)
// Watcher for other filters (repo, status, exact) without debounce
watch(
[() => repo.value, () => status.value?.map((state) => state.value), () => exact.value],
() => {
// Cancel pending pkgbase debounce if any
if (pkgbaseTimeout) {
clearTimeout(pkgbaseTimeout)
pkgbaseTimeout = null
}
updateFilter()
}
)
onMounted(() => {
initFilters()
})
</script>

View File

@@ -0,0 +1,112 @@
<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="packagesStore.state.packages.length === 0">
No Packages found
</tr>
<template v-else>
<tr
v-for="(pkg, index) in packagesStore.state.packages"
:key="index"
:style="`background-color: ${getStatusColor(pkg.status)};`">
<td class="font-weight-bold text-no-wrap">
<v-chip
:color="getVersionColor(repoVersion(pkg.repo || ''))"
class="me-2"
density="comfortable"
label
variant="flat">
{{ repoVersion(pkg.repo || '') }}
</v-chip>
{{ repoName(pkg.repo || '') }}
</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 { usePackageDisplay } from '@/composables/Packages/usePackageDisplay'
import { usePackagesStore } from '@/stores'
const { repoName, repoVersion, getVersionColor, getStatusColor, getLto, getDs } =
usePackageDisplay()
const packagesStore = usePackagesStore()
</script>

View File

@@ -0,0 +1,98 @@
import { components } from '@/api'
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: components['schemas']['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: components['schemas']['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: components['schemas']['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
}
}

View File

@@ -0,0 +1,28 @@
// UI Constants
export const TRANSPARENT_COLOR = 'transparent'
export const ROW_HEIGHT = 60
// Select Items Constants
export const REPO_ITEMS = [
'core-x86-64-v2',
'core-x86-64-v3',
'core-x86-64-v4',
'extra-x86-64-v2',
'extra-x86-64-v3',
'extra-x86-64-v4',
'multilib-x86-64-v2',
'multilib-x86-64-v3',
'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' }
]

View File

@@ -0,0 +1,273 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/packages": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Retrieve package information
* @description Fetch packages from the ALHP system. You can filter results using query parameters.
*/
get: operations["getPackages"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/stats": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Retrieve general build statistics */
get: operations["getStats"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
Package: {
/**
* @description The base name of the package
* @example linux-zen
*/
pkgbase?: string;
/**
* @description Repository from which the package originates
* @example extra-x86-64-v4
*/
repo?: string;
/**
* @description List of package names split from the base package
* @example [
* "linux-zen",
* "linux-zen-headers",
* "linux-zen-docs"
* ]
*/
split_packages?: string[];
/**
* @description Current build or repository status of the package
* @example latest
* @enum {string}
*/
status?: "latest" | "failed" | "built" | "skipped" | "delayed" | "building" | "signing" | "unknown" | "queued";
/**
* @description Reason for skipping the package build (if any)
* @example blacklisted
*/
skip_reason?: string;
/**
* @description Link Time Optimization (LTO) status for the package
* @example enabled
* @enum {string}
*/
lto?: "enabled" | "unknown" | "disabled" | "auto_disabled";
/**
* @description Availability of debug symbols in the package
* @example available
* @enum {string}
*/
debug_symbols?: "available" | "unknown" | "not_available";
/**
* @description Version available in the official Arch Linux repositories
* @example 1.3.4-2
*/
arch_version?: string;
/**
* @description Version available in ALHP repositories (may be empty if not built)
* @example 1.3.4-2.1
*/
repo_version?: string;
/**
* @description Date and time when the package was built (RFC1123 format)
* @example Fri, 15 Dec 2023 03:43:11 UTC
*/
build_date?: string;
/**
* @description Peak memory usage during the build process (human-readable format)
* @example 5 GB
*/
peak_mem?: string;
};
/** @description Aggregated statistics across all packages */
Stats: {
/**
* Format: int64
* @description Number of packages that failed to build
* @example 17
*/
failed?: number;
/**
* Format: int64
* @description Number of packages that were skipped
* @example 29
*/
skipped?: number;
/**
* Format: int64
* @description Number of packages that are up-to-date
* @example 743
*/
latest?: number;
/**
* Format: int64
* @description Number of packages currently in the build queue
* @example 5
*/
queued?: number;
/**
* Format: int64
* @description Number of packages currently building
* @example 11
*/
building?: number;
/**
* Format: int64
* @description Latest mirror timestamp to detect outdated mirrors (Unix timestamp)
* @example 1702612991
*/
last_mirror_timestamp?: number;
/** @description LTO status summary across all packages */
lto?: {
/**
* Format: int64
* @description Number of packages with LTO enabled
* @example 532
*/
enabled?: number;
/**
* Format: int64
* @description Number of packages with LTO explicitly disabled
* @example 203
*/
disabled?: number;
/**
* Format: int64
* @description Number of packages with unknown LTO status
* @example 11
*/
unknown?: number;
};
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
getPackages: {
parameters: {
query: {
/** @description Filter by package status. Accepts multiple values via repeated parameters. */
status?: ("latest" | "failed" | "built" | "skipped" | "delayed" | "building" | "signing" | "unknown" | "queued")[];
/** @description Filter by the base package name (`pkgbase`). This is often the main identifier of a package group. */
pkgbase?: string;
/** @description If present, matches the `pkgbase` exactly. If not provided, allows partial matches. */
exact?: boolean;
/** @description Filter by repository name. */
repo?: string;
/** @description Number of results to skip (for pagination). */
offset: number;
/** @description Maximum number of results to return. */
limit: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Package data retrieved successfully */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
packages?: components["schemas"]["Package"][];
/**
* Format: int64
* @description Total number of matching packages
* @example 1423
*/
total?: number;
/**
* Format: int64
* @example 0
*/
offset?: number;
/**
* Format: int64
* @example 25
*/
limit?: number;
};
};
};
/** @description No packages found matching the specified filters */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Internal server error occurred */
500: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
getStats: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description General statistics retrieved successfully */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Stats"];
};
};
/** @description Internal server error occurred */
500: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
}

View File

@@ -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)

View File

@@ -0,0 +1,3 @@
// Export all stores
export { useStatsStore } from './statsStore'
export { usePackagesStore } from './packagesStore'

View File

@@ -0,0 +1,229 @@
import { defineStore } from 'pinia'
import { reactive } from 'vue'
import { components, getPackages } from '@/api'
interface PackageFilters {
status?: components['schemas']['Package']['status'][]
pkgbase?: components['schemas']['Package']['pkgbase']
exact?: boolean
repo?: components['schemas']['Package']['repo']
}
export const usePackagesStore = defineStore('packages', () => {
const state = reactive({
packages: [] as components['schemas']['Package'][],
currentlyBuildingPackages: [] as components['schemas']['Package'][],
total: 0,
offset: 0,
limit: Number(import.meta.env.VITE_LIMIT) || 50,
loading: false,
loadingCurrentlyBuilding: false,
error: null as string | null,
errorCurrentlyBuilding: null as string | null,
lastUpdated: Date.now(),
filters: {
status: undefined,
pkgbase: undefined,
exact: undefined,
repo: undefined
} as PackageFilters
})
// Actions
const fetchPackages = (init = false) => {
state.loading = true
state.error = null
state.packages = []
state.total = 0
if (init) {
initFromUrl()
}
const filter: PackageFilters = {}
if (state.filters.status && state.filters.status.length > 0) {
filter.status = state.filters.status
}
if (state.filters.pkgbase) {
filter.pkgbase = state.filters.pkgbase
}
if (state.filters.exact === true) {
filter.exact = state.filters.exact
}
if (state.filters.repo) {
filter.repo = state.filters.repo
}
// @ts-ignore
getPackages({
limit: state.limit,
offset: state.offset,
...filter
})
.then((response) => {
if (!response) throw new Error('No response from API')
state.packages = response.packages || []
state.total = response.total || 0
state.offset = response.offset || 0
state.limit = response.limit || state.limit
})
.catch((err) => {
if (err.statusCode === 404) {
state.packages = []
state.total = 0
state.offset = 0
state.limit = Number(import.meta.env.VITE_LIMIT) || 50
} else {
state.error = err instanceof Error ? err.message : 'Failed to fetch packages'
console.error('Error fetching packages:', err)
}
})
.finally(() => {
state.loading = false
})
}
const fetchCurrentlyBuilding = () => {
state.loadingCurrentlyBuilding = true
state.errorCurrentlyBuilding = null
state.currentlyBuildingPackages = []
getPackages({
limit: 0,
offset: 0,
status: ['queued', 'building', 'built']
})
.then((response) => {
state.currentlyBuildingPackages = response?.packages || []
})
.catch((err) => {
if (err.statusCode === 404) {
state.currentlyBuildingPackages = []
} else {
state.errorCurrentlyBuilding =
err instanceof Error ? err.message : 'Failed to fetch currently building packages'
console.error('Error fetching queued packages:', err)
}
})
.finally(() => {
state.loadingCurrentlyBuilding = false
state.lastUpdated = Date.now()
})
}
const goToPage = (page: number) => {
state.offset = (page - 1) * state.limit
updateUrlParams()
fetchPackages()
}
const setFilters = (newFilters: PackageFilters, page?: number) => {
state.filters = JSON.parse(JSON.stringify(newFilters))
if (state.filters.exact === false) {
state.filters.exact = undefined
}
if (page) {
state.offset = (page - 1) * state.limit
}
updateUrlParams()
fetchPackages()
}
const resetFilters = () => {
state.filters = {
status: undefined,
pkgbase: undefined,
exact: undefined,
repo: undefined
}
state.offset = 0
state.limit = Number(import.meta.env.VITE_LIMIT) || 50
updateUrlParams()
fetchPackages()
}
const updateUrlParams = () => {
const params = new URLSearchParams()
let page = state.offset / state.limit + 1
// Only add a page parameter if it's not the first page
if (page > 1) {
params.set('page', page.toString())
}
if (state.filters.status && state.filters.status.length > 0) {
state.filters.status.forEach((status: components['schemas']['Package']['status']) => {
if (status) {
params.append('status', status)
}
})
}
if (state.filters.pkgbase) {
params.set('pkgbase', state.filters.pkgbase.toLowerCase())
}
if (state.filters.repo) {
params.set('repo', state.filters.repo)
}
if (state.filters.exact === true) {
params.set('exact', '')
} else {
params.delete('exact')
}
const paramsString = params.toString()
if (paramsString) {
window.history.pushState(null, '', `${window.location.pathname}?${paramsString}`)
} else {
window.history.pushState(null, '', window.location.pathname)
}
}
const initFromUrl = () => {
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has('page')) {
const pageParam = urlParams.get('page') || '1'
const parsedPage = parseInt(pageParam, 10)
const page = !isNaN(parsedPage) && parsedPage > 0 ? parsedPage : 1
state.offset = (page - 1) * state.limit
}
if (urlParams.has('status')) {
const status = urlParams.getAll('status')
state.filters.status = status as components['schemas']['Package']['status'][]
}
if (urlParams.has('pkgbase')) {
const pkgbase = urlParams.get('pkgbase')
if (pkgbase === null) return
state.filters.pkgbase = pkgbase as components['schemas']['Package']['pkgbase']
}
if (urlParams.has('repo')) {
const repo = urlParams.get('repo')
if (repo === null) return
state.filters.repo = repo as components['schemas']['Package']['repo']
}
if (urlParams.has('exact')) {
state.filters.exact = true
}
}
return {
state,
// Actions
fetchPackages,
fetchCurrentlyBuilding,
goToPage,
setFilters,
resetFilters
}
})

View File

@@ -0,0 +1,37 @@
import { defineStore } from 'pinia'
import { reactive } from 'vue'
import { components, getStats } from '@/api'
export const useStatsStore = defineStore('stats', () => {
const state = reactive({
stats: {} as components['schemas']['Stats'] | undefined | null,
loading: false,
error: null as string | null,
lastUpdated: Date.now()
})
// Actions
const fetchStats = () => {
state.loading = true
state.error = null
getStats()
.then((response) => {
state.stats = response
})
.catch((err) => {
state.error = err instanceof Error ? err.message : 'Failed to fetch packages'
console.error('Error fetching packages:', err)
})
.finally(() => {
state.loading = false
})
}
return {
state,
// Actions
fetchStats
}
})

View File

@@ -1,22 +0,0 @@
export interface Package {
pkgbase: string
repo: string
split_packages: Array<string>
status:
| 'latest'
| 'failed'
| 'built'
| 'skipped'
| 'delayed'
| 'building'
| 'signing'
| 'unknown'
| 'queued'
skip_reason: string
lto: 'enabled' | 'unknown' | 'disabled' | 'auto_disabled'
debug_symbols: 'available' | 'unknown' | 'not_available'
arch_version: string
repo_version: string
build_date: string
peak_mem: any
}

View File

@@ -1,8 +0,0 @@
import { type Package } from '@/types/Package'
export interface Packages {
packages: Array<Package>
total: number
offset: number
limit: number
}

View File

@@ -1,9 +0,0 @@
import { type Stats_Lto } from '@/types/Stats_Lto'
export interface Stats {
latest: number
queued: number
skipped: number
failed: number
lto: Stats_Lto
}

View File

@@ -1,5 +0,0 @@
export interface Stats_Lto {
enabled: number
disabled: number
unknown: number
}

View File

@@ -5,6 +5,17 @@ __metadata:
version: 8
cacheKey: 10c0
"@babel/code-frame@npm:^7.26.2":
version: 7.26.2
resolution: "@babel/code-frame@npm:7.26.2"
dependencies:
"@babel/helper-validator-identifier": "npm:^7.25.9"
js-tokens: "npm:^4.0.0"
picocolors: "npm:^1.0.0"
checksum: 10c0/7d79621a6849183c415486af99b1a20b84737e8c11cd55b6544f688c51ce1fd710e6d869c3dd21232023da272a79b91efb3e83b5bc2dc65c1187c5fcd1b72ea8
languageName: node
linkType: hard
"@babel/helper-string-parser@npm:^7.25.9":
version: 7.25.9
resolution: "@babel/helper-string-parser@npm:7.25.9"
@@ -40,6 +51,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"
@@ -450,6 +471,42 @@ __metadata:
languageName: node
linkType: hard
"@redocly/ajv@npm:^8.11.2":
version: 8.11.2
resolution: "@redocly/ajv@npm:8.11.2"
dependencies:
fast-deep-equal: "npm:^3.1.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
uri-js-replace: "npm:^1.0.1"
checksum: 10c0/249ca2e237f7b1248ee1018ba1ad3a739cb9f16e5f7fe821875948806980d65246c79ef7d5e7bd8db773c120e2cd5ce15aa47883893608e1965ca4d45c5572f4
languageName: node
linkType: hard
"@redocly/config@npm:^0.22.0":
version: 0.22.2
resolution: "@redocly/config@npm:0.22.2"
checksum: 10c0/625e947e7939e2d59bd83f516af5a581411167e3fc83adf7322bddf9bc69038fc601ed4ee8abae44d298ed367a16a1a09e7cdbe8b5dde172b4ce53c88d8717f4
languageName: node
linkType: hard
"@redocly/openapi-core@npm:^1.28.0":
version: 1.34.2
resolution: "@redocly/openapi-core@npm:1.34.2"
dependencies:
"@redocly/ajv": "npm:^8.11.2"
"@redocly/config": "npm:^0.22.0"
colorette: "npm:^1.2.0"
https-proxy-agent: "npm:^7.0.5"
js-levenshtein: "npm:^1.1.6"
js-yaml: "npm:^4.1.0"
minimatch: "npm:^5.0.1"
pluralize: "npm:^8.0.0"
yaml-ast-parser: "npm:0.0.43"
checksum: 10c0/252710cdc7fd182f37c2fedaa27cbd8ea5f882e3340515bf572372adc35e5cdc0bfc0aae2ce51defa91d1fd9a2f9d82454104b0c9ab22895f941196f50830b33
languageName: node
linkType: hard
"@rollup/rollup-android-arm-eabi@npm:4.36.0":
version: 4.36.0
resolution: "@rollup/rollup-android-arm-eabi@npm:4.36.0"
@@ -590,12 +647,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 +753,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"
@@ -804,6 +894,13 @@ __metadata:
languageName: node
linkType: hard
"agent-base@npm:^7.1.2":
version: 7.1.3
resolution: "agent-base@npm:7.1.3"
checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11
languageName: node
linkType: hard
"aggregate-error@npm:^3.0.0":
version: 3.1.0
resolution: "aggregate-error@npm:3.1.0"
@@ -818,23 +915,26 @@ __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"
openapi-fetch: "npm:^0.13.5"
openapi-typescript: "npm:^7.6.1"
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
@@ -845,6 +945,13 @@ __metadata:
languageName: node
linkType: hard
"ansi-colors@npm:^4.1.3":
version: 4.1.3
resolution: "ansi-colors@npm:4.1.3"
checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9
languageName: node
linkType: hard
"ansi-regex@npm:^5.0.1":
version: 5.0.1
resolution: "ansi-regex@npm:5.0.1"
@@ -885,6 +992,13 @@ __metadata:
languageName: node
linkType: hard
"argparse@npm:^2.0.1":
version: 2.0.1
resolution: "argparse@npm:2.0.1"
checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e
languageName: node
linkType: hard
"balanced-match@npm:^1.0.0":
version: 1.0.2
resolution: "balanced-match@npm:1.0.2"
@@ -899,6 +1013,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"
@@ -946,6 +1067,13 @@ __metadata:
languageName: node
linkType: hard
"change-case@npm:^5.4.4":
version: 5.4.4
resolution: "change-case@npm:5.4.4"
checksum: 10c0/2a9c2b9c9ad6ab2491105aaf506db1a9acaf543a18967798dcce20926c6a173aa63266cb6189f3086e3c14bf7ae1f8ea4f96ecc466fcd582310efa00372f3734
languageName: node
linkType: hard
"chokidar@npm:^3.6.0":
version: 3.6.0
resolution: "chokidar@npm:3.6.0"
@@ -1004,6 +1132,13 @@ __metadata:
languageName: node
linkType: hard
"colorette@npm:^1.2.0":
version: 1.4.0
resolution: "colorette@npm:1.4.0"
checksum: 10c0/4955c8f7daafca8ae7081d672e4bd89d553bd5782b5846d5a7e05effe93c2f15f7e9c0cb46f341b59f579a39fcf436241ff79594899d75d5f3460c03d607fe9e
languageName: node
linkType: hard
"confbox@npm:^0.1.8":
version: 0.1.8
resolution: "confbox@npm:0.1.8"
@@ -1018,6 +1153,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"
@@ -1246,6 +1390,13 @@ __metadata:
languageName: node
linkType: hard
"fast-deep-equal@npm:^3.1.1":
version: 3.1.3
resolution: "fast-deep-equal@npm:3.1.3"
checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0
languageName: node
linkType: hard
"fast-glob@npm:^3.3.2":
version: 3.3.2
resolution: "fast-glob@npm:3.3.2"
@@ -1393,6 +1544,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"
@@ -1420,6 +1578,16 @@ __metadata:
languageName: node
linkType: hard
"https-proxy-agent@npm:^7.0.5":
version: 7.0.6
resolution: "https-proxy-agent@npm:7.0.6"
dependencies:
agent-base: "npm:^7.1.2"
debug: "npm:4"
checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac
languageName: node
linkType: hard
"iconv-lite@npm:^0.6.2":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
@@ -1450,6 +1618,13 @@ __metadata:
languageName: node
linkType: hard
"index-to-position@npm:^1.1.0":
version: 1.1.0
resolution: "index-to-position@npm:1.1.0"
checksum: 10c0/77ef140f0218df0486a08cff204de4d382e8c43892039aaa441ac5b87f0c8d8a72af633c8a1c49f1b1ec4177bd809e4e045958a9aebe65545f203342b95886b3
languageName: node
linkType: hard
"ip-address@npm:^9.0.5":
version: 9.0.5
resolution: "ip-address@npm:9.0.5"
@@ -1506,6 +1681,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"
@@ -1533,6 +1715,31 @@ __metadata:
languageName: node
linkType: hard
"js-levenshtein@npm:^1.1.6":
version: 1.1.6
resolution: "js-levenshtein@npm:1.1.6"
checksum: 10c0/14045735325ea1fd87f434a74b11d8a14380f090f154747e613529c7cff68b5ee607f5230fa40665d5fb6125a3791f4c223f73b9feca754f989b059f5c05864f
languageName: node
linkType: hard
"js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed
languageName: node
linkType: hard
"js-yaml@npm:^4.1.0":
version: 4.1.0
resolution: "js-yaml@npm:4.1.0"
dependencies:
argparse: "npm:^2.0.1"
bin:
js-yaml: bin/js-yaml.js
checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f
languageName: node
linkType: hard
"jsbn@npm:1.1.0":
version: 1.1.0
resolution: "jsbn@npm:1.1.0"
@@ -1540,7 +1747,14 @@ __metadata:
languageName: node
linkType: hard
"local-pkg@npm:^1.0.0":
"json-schema-traverse@npm:^1.0.0":
version: 1.0.0
resolution: "json-schema-traverse@npm:1.0.0"
checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6
languageName: node
linkType: hard
"local-pkg@npm:^1.1.1":
version: 1.1.1
resolution: "local-pkg@npm:1.1.1"
dependencies:
@@ -1614,6 +1828,15 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^5.0.1":
version: 5.1.6
resolution: "minimatch@npm:5.1.6"
dependencies:
brace-expansion: "npm:^2.0.1"
checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3
languageName: node
linkType: hard
"minimatch@npm:^9.0.3":
version: 9.0.3
resolution: "minimatch@npm:9.0.3"
@@ -1716,6 +1939,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"
@@ -1821,6 +2051,40 @@ __metadata:
languageName: node
linkType: hard
"openapi-fetch@npm:^0.13.5":
version: 0.13.5
resolution: "openapi-fetch@npm:0.13.5"
dependencies:
openapi-typescript-helpers: "npm:^0.0.15"
checksum: 10c0/57736d9d4310d7bc7fa5e4e37e80d28893a7fefee88ee6e0327600de893e0638479445bf0c9f5bd7b1a2429f409425d3945d6e942b23b37b8081630ac52244fb
languageName: node
linkType: hard
"openapi-typescript-helpers@npm:^0.0.15":
version: 0.0.15
resolution: "openapi-typescript-helpers@npm:0.0.15"
checksum: 10c0/5eb68d487b787e3e31266470b1a310726549dd45a1079655ab18066ab291b0b3c343fdf629991013706a2329b86964f8798d56ef0272b94b931fe6c19abd7a88
languageName: node
linkType: hard
"openapi-typescript@npm:^7.6.1":
version: 7.6.1
resolution: "openapi-typescript@npm:7.6.1"
dependencies:
"@redocly/openapi-core": "npm:^1.28.0"
ansi-colors: "npm:^4.1.3"
change-case: "npm:^5.4.4"
parse-json: "npm:^8.1.0"
supports-color: "npm:^9.4.0"
yargs-parser: "npm:^21.1.1"
peerDependencies:
typescript: ^5.x
bin:
openapi-typescript: bin/cli.js
checksum: 10c0/3591be796ac5eb1fe051b765c29e7cc5fcf9bb59e83eb43e13b4c98f25957d78523a5c8922eb52f1785b3fab6f5e269bc02892d72350ac7274d41192a36673c9
languageName: node
linkType: hard
"p-map@npm:^4.0.0":
version: 4.0.0
resolution: "p-map@npm:4.0.0"
@@ -1837,6 +2101,17 @@ __metadata:
languageName: node
linkType: hard
"parse-json@npm:^8.1.0":
version: 8.3.0
resolution: "parse-json@npm:8.3.0"
dependencies:
"@babel/code-frame": "npm:^7.26.2"
index-to-position: "npm:^1.1.0"
type-fest: "npm:^4.39.1"
checksum: 10c0/0eb5a50f88b8428c8f7a9cf021636c16664f0c62190323652d39e7bdf62953e7c50f9957e55e17dc2d74fc05c89c11f5553f381dbc686735b537ea9b101c7153
languageName: node
linkType: hard
"path-browserify@npm:^1.0.1":
version: 1.0.1
resolution: "path-browserify@npm:1.0.1"
@@ -1868,7 +2143,14 @@ __metadata:
languageName: node
linkType: hard
"picocolors@npm:^1.1.1":
"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.0.0, picocolors@npm:^1.1.1":
version: 1.1.1
resolution: "picocolors@npm:1.1.1"
checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58
@@ -1889,6 +2171,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"
@@ -1911,6 +2208,13 @@ __metadata:
languageName: node
linkType: hard
"pluralize@npm:^8.0.0":
version: 8.0.0
resolution: "pluralize@npm:8.0.0"
checksum: 10c0/2044cfc34b2e8c88b73379ea4a36fc577db04f651c2909041b054c981cd863dd5373ebd030123ab058d194ae615d3a97cfdac653991e499d10caf592e8b3dc33
languageName: node
linkType: hard
"postcss@npm:^8.4.48, postcss@npm:^8.5.3":
version: 8.5.3
resolution: "postcss@npm:8.5.3"
@@ -1985,6 +2289,13 @@ __metadata:
languageName: node
linkType: hard
"require-from-string@npm:^2.0.2":
version: 2.0.2
resolution: "require-from-string@npm:2.0.2"
checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2
languageName: node
linkType: hard
"retry@npm:^0.12.0":
version: 0.12.0
resolution: "retry@npm:0.12.0"
@@ -1999,6 +2310,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 +2412,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 +2425,7 @@ __metadata:
optional: true
bin:
sass: sass.js
checksum: 10c0/921caea1fd8a450d4a986e5570ce13c4ca7b2a57da390811add3d2087ad8f46f53b34652ddcb237d8bdaad49c560b8d6eee130c733c787d058bc5a71a914c139
checksum: 10c0/ba819a0828f732adf7a94cd8ca017bce92bc299ffb878836ed1da80a30612bfbbf56a5e42d6dff3ad80d919c2025afb42948fc7b54a7bc61a9a2d58e1e0c558a
languageName: node
linkType: hard
@@ -2192,6 +2510,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 +2573,22 @@ __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
"supports-color@npm:^9.4.0":
version: 9.4.0
resolution: "supports-color@npm:9.4.0"
checksum: 10c0/6c24e6b2b64c6a60e5248490cfa50de5924da32cf09ae357ad8ebbf305cc5d2717ba705a9d4cb397d80bbf39417e8fdc8d7a0ce18bd0041bf7b5b456229164e4
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 +2622,30 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:^5.8.2":
version: 5.8.2
resolution: "typescript@npm:5.8.2"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6
"type-fest@npm:^4.39.1":
version: 4.39.1
resolution: "type-fest@npm:4.39.1"
checksum: 10c0/f5bf302eb2e2f70658be1757aa578f4a09da3f65699b0b12b7ae5502ccea76e5124521a6e6b69540f442c3dc924c394202a2ab58718d0582725c7ac23c072594
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@npm:^5.8.3":
version: 5.8.3
resolution: "typescript@npm:5.8.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/5448a08e595cc558ab321e49d4cac64fb43d1fa106584f6ff9a8d8e592111b373a995a1d5c7f3046211c8a37201eb6d0f1566f15cdb7a62a5e3be01d087848e2
checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48
languageName: node
linkType: hard
"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/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb
languageName: node
linkType: hard
@@ -2308,10 +2656,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 +2707,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 +2728,7 @@ __metadata:
optional: true
"@nuxt/kit":
optional: true
checksum: 10c0/f05448285e6d049b8aeadf5747cf7cda23105bfe8691326217abf3a5aa924768279f1c26f37ed0cd98c00f97ea7ce1cd5d5ed2916fb09f2b817f25563ece825a
checksum: 10c0/4a2419cee6a8d19e0dd121fa56afef29c981434ee4f632e972e46aec52f161608c85b0806737308c597ca6d19b07d62f654f4070bd4302c1691a1c865acb9248
languageName: node
linkType: hard
@@ -2394,13 +2742,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 +2760,16 @@ __metadata:
languageName: node
linkType: hard
"vite-plugin-vuetify@npm:^2.1.0":
version: 2.1.0
resolution: "vite-plugin-vuetify@npm:2.1.0"
"uri-js-replace@npm:^1.0.1":
version: 1.0.1
resolution: "uri-js-replace@npm:1.0.1"
checksum: 10c0/0be6c972c84c316e29667628ce7b4ce4de7fc77cec9a514f70c4a3336eea8d1d783c71c9988ac5da333f0f6a85a04a7ae05a3c4aa43af6cd07b7a4d85c8d9f11
languageName: node
linkType: hard
"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 +2778,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 +2830,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
checksum: 10c0/52f5b1c10cfe5e3b6382c6de1811ebbf76df9b5a8bab3d65169446c6b54a5f1528f775b1548009a6d8aad11def20fba046bb3e9abb10c0c2c9ccd78118623bb8
checksum: 10c0/68a2ed3e61bdd654c59b817b4f3203065241c66d1739faa707499130f3007bc3a666c7a8320a4198e275e62b5e4d34d9b78a6533f69e321d366e76f5093b2071
languageName: node
linkType: hard
@@ -2517,14 +2873,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 +2888,7 @@ __metadata:
optional: true
webpack-plugin-vuetify:
optional: true
checksum: 10c0/98686640dc11bb59e12fdb214e0fc8b2a4ecd2f8286448fddb6abecd858a0fd5db5927d443e9b3be7ff92303ae1c8bc01bf4712278a9b6e4f4d72358a36e0a0e
checksum: 10c0/b6fd1a96325b16a1f88acd1ef9d385118c09f25793bc7eddc3da2fe57e84a17d200d77dc14a2e0bc1b67fb6f301ce15b760185d2faafeaf61c584a92df6b0b45
languageName: node
linkType: hard
@@ -2593,3 +2949,17 @@ __metadata:
checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a
languageName: node
linkType: hard
"yaml-ast-parser@npm:0.0.43":
version: 0.0.43
resolution: "yaml-ast-parser@npm:0.0.43"
checksum: 10c0/4d2f1e761067b2c6abdd882279a406f879258787af470a6d4a659cb79cb2ab056b870b25f1f80f46ed556e8b499d611d247806376f53edf3412f72c0a8ea2e98
languageName: node
linkType: hard
"yargs-parser@npm:^21.1.1":
version: 21.1.1
resolution: "yargs-parser@npm:21.1.1"
checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2
languageName: node
linkType: hard

10
go.mod
View File

@@ -39,10 +39,10 @@ require (
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.31.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.32.0 // indirect
)

20
go.sum
View File

@@ -191,8 +191,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -212,8 +212,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -232,8 +232,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -249,8 +249,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@@ -262,8 +262,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -224,6 +224,16 @@ components:
format: int64
description: Number of packages currently in the build queue
example: 5
building:
type: integer
format: int64
description: Number of packages currently building
example: 11
last_mirror_timestamp:
type: integer
format: int64
description: Latest mirror timestamp to detect outdated mirrors (Unix timestamp)
example: 1702612991
lto:
type: object
description: LTO status summary across all packages