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.
This commit is contained in:
8
frontend/components.d.ts
vendored
8
frontend/components.d.ts
vendored
@@ -9,10 +9,14 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
BuildServerStats: typeof import('./src/components/BuildServerStats.vue')['default']
|
||||
BuildStats: typeof import('./src/components/BuildStats.vue')['default']
|
||||
BuildStats: typeof import('./src/components/MainNav/BuildStats.vue')['default']
|
||||
CurrentlyBuilding: typeof import('./src/components/CurrentlyBuilding.vue')['default']
|
||||
MainNav: typeof import('./src/components/MainNav.vue')['default']
|
||||
PackageFilters: typeof import('./src/components/Packages/PackageFilters.vue')['default']
|
||||
Packages: typeof import('./src/components/Packages.vue')['default']
|
||||
QueuedPackagesList: typeof import('./src/components/QueuedPackagesList.vue')['default']
|
||||
PackageTable: typeof import('./src/components/Packages/PackageTable.vue')['default']
|
||||
QueuedPackagesList: typeof import('./src/components/CurrentlyBuilding/QueuedPackagesList.vue')['default']
|
||||
StatItem: typeof import('./src/components/MainNav/BuildStats/StatItem.vue')['default']
|
||||
StatsListSection: typeof import('./src/components/MainNav/BuildStats/StatsListSection.vue')['default']
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<v-app-bar color="#191c2a" style="background: #0d1538">
|
||||
<v-container :class="width < 1440 ? 'ms-3' : 'mx-auto'" fluid style="max-width: 1440px">
|
||||
<v-app-bar :color="appBarColors.background">
|
||||
<v-container :class="containerClasses" :style="{ maxWidth: maxContainerWidth }" fluid>
|
||||
<v-row>
|
||||
<v-app-bar-title style="align-self: center">
|
||||
<span style="font-size: 20px">
|
||||
ALHP Status
|
||||
<a class="ms-2 gitea-link" href="https://somegit.dev/ALHP/ALHP.GO" target="_blank">
|
||||
<v-app-bar-title class="app-title">
|
||||
<span>
|
||||
{{ appTitle }}
|
||||
<a :href="repoUrl" class="ms-2 gitea-link" target="_blank">
|
||||
<i class="fa fa-gitea"></i>
|
||||
</a>
|
||||
</span>
|
||||
@@ -18,21 +18,40 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import BuildStats from '@/components/BuildStats.vue'
|
||||
import BuildStats from '@/components/MainNav/BuildStats.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { mobile, width } = useDisplay()
|
||||
|
||||
const appTitle = 'ALHP Status'
|
||||
const repoUrl = 'https://somegit.dev/ALHP/ALHP.GO'
|
||||
const maxContainerWidth = '1440px'
|
||||
const appBarColors = {
|
||||
background: '#0d1538',
|
||||
accent: '#609926'
|
||||
}
|
||||
|
||||
const containerClasses = computed(() => ({
|
||||
'ms-3': width.value < 1440,
|
||||
'mx-auto': width.value >= 1440
|
||||
}))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-title {
|
||||
align-self: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.gitea-link {
|
||||
color: white;
|
||||
font-size: 25px;
|
||||
text-decoration: none; /* Optional: To prevent underlining */
|
||||
transition: color 0.2s; /* Smooth color transition */
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.gitea-link:hover {
|
||||
color: #609926;
|
||||
color: v-bind('appBarColors.accent');
|
||||
}
|
||||
</style>
|
||||
|
104
frontend/src/components/MainNav/BuildStats.vue
Normal file
104
frontend/src/components/MainNav/BuildStats.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<v-sheet :style="sheetStyles" class="d-flex" color="transparent">
|
||||
<StatsListSection title="Stats">
|
||||
<StatItem
|
||||
v-for="(value, key) in generalStats"
|
||||
:key="key"
|
||||
:color="value.color"
|
||||
:count="value.count"
|
||||
:title="key" />
|
||||
</StatsListSection>
|
||||
|
||||
<StatsListSection title="LTO">
|
||||
<StatItem
|
||||
v-for="(value, key) in customStats.lto"
|
||||
:key="key"
|
||||
:color="value.color"
|
||||
:count="value.count"
|
||||
:title="key" />
|
||||
</StatsListSection>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useDataStore } from '@/stores/dataStore'
|
||||
import { type dataStore } from '@/types/dataStore'
|
||||
import StatsListSection from '@/components/MainNav/BuildStats/StatsListSection.vue'
|
||||
import StatItem from '@/components/MainNav/BuildStats/StatItem.vue'
|
||||
|
||||
interface CustomStatItem {
|
||||
count: number
|
||||
color: string
|
||||
}
|
||||
|
||||
interface CustomStats {
|
||||
latest: CustomStatItem
|
||||
queued: CustomStatItem
|
||||
building: CustomStatItem
|
||||
skipped: CustomStatItem
|
||||
failed: CustomStatItem
|
||||
lto: {
|
||||
enabled: CustomStatItem
|
||||
disabled: CustomStatItem
|
||||
unknown: CustomStatItem
|
||||
}
|
||||
}
|
||||
|
||||
const COLORS = {
|
||||
SUCCESS: '#069b35',
|
||||
WARNING: '#b97808',
|
||||
ERROR: '#b30303',
|
||||
NEUTRAL: '#878787'
|
||||
}
|
||||
|
||||
const sheetStyles = { gap: '50px' }
|
||||
|
||||
const dataStore = useDataStore()
|
||||
const stats = ref<dataStore['stats']>()
|
||||
|
||||
const customStats = ref<CustomStats>({
|
||||
latest: { count: 0, color: COLORS.SUCCESS },
|
||||
queued: { count: 0, color: COLORS.WARNING },
|
||||
building: { count: 0, color: COLORS.WARNING },
|
||||
skipped: { count: 0, color: COLORS.NEUTRAL },
|
||||
failed: { count: 0, color: COLORS.ERROR },
|
||||
lto: {
|
||||
enabled: { count: 0, color: COLORS.SUCCESS },
|
||||
disabled: { count: 0, color: COLORS.ERROR },
|
||||
unknown: { count: 0, color: COLORS.NEUTRAL }
|
||||
}
|
||||
})
|
||||
|
||||
const generalStats = computed(() => {
|
||||
const { lto, ...rest } = customStats.value
|
||||
return rest
|
||||
})
|
||||
|
||||
const updateStats = (): void => {
|
||||
stats.value = dataStore.getPackageStats()
|
||||
|
||||
if (!stats.value) return
|
||||
|
||||
Object.keys(generalStats.value).forEach((key) => {
|
||||
const typedKey = key as keyof typeof generalStats.value
|
||||
customStats.value[typedKey].count = stats.value?.[typedKey] as number
|
||||
})
|
||||
|
||||
Object.keys(customStats.value.lto).forEach((ltoKey) => {
|
||||
const typedLtoKey = ltoKey as keyof typeof customStats.value.lto
|
||||
customStats.value.lto[typedLtoKey].count = stats.value?.lto[typedLtoKey] as number
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => dataStore.loading,
|
||||
(isLoading) => {
|
||||
if (!isLoading) {
|
||||
updateStats()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(updateStats)
|
||||
</script>
|
13
frontend/src/components/MainNav/BuildStats/StatItem.vue
Normal file
13
frontend/src/components/MainNav/BuildStats/StatItem.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<v-list-item :style="{ color }" :title="title">
|
||||
{{ count }}
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
title: string
|
||||
count: number
|
||||
color: string
|
||||
}>()
|
||||
</script>
|
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<v-list :style="listStyles" bg-color="transparent" class="d-flex">
|
||||
<v-list-subheader>{{ title }}:</v-list-subheader>
|
||||
<slot></slot>
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
title: string
|
||||
}>()
|
||||
|
||||
// Reusable styles
|
||||
const listStyles = { borderRadius: '5px' }
|
||||
</script>
|
Reference in New Issue
Block a user