mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-22 16:36:30 +00:00
Implement visual display of queue statistics (#1657)
Light mode: ![attels](https://user-images.githubusercontent.com/165205/226354030-5769ddce-3727-441a-8ecb-c6cc3cf70529.png) Dark mode: ![attels](https://user-images.githubusercontent.com/165205/226353854-51b2da0a-b35a-4f61-bf28-e21179164a05.png)
This commit is contained in:
parent
2337f1854a
commit
7870c29f5f
7 changed files with 140 additions and 19 deletions
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
|
@ -12,6 +12,7 @@ declare module '@vue/runtime-core' {
|
|||
ActionsTab: typeof import('./src/components/repo/settings/ActionsTab.vue')['default']
|
||||
ActivePipelines: typeof import('./src/components/layout/header/ActivePipelines.vue')['default']
|
||||
AdminAgentsTab: typeof import('./src/components/admin/settings/AdminAgentsTab.vue')['default']
|
||||
AdminQueueStats: typeof import('./src/components/admin/settings/queue/AdminQueueStats.vue')['default']
|
||||
AdminQueueTab: typeof import('./src/components/admin/settings/AdminQueueTab.vue')['default']
|
||||
AdminSecretsTab: typeof import('./src/components/admin/settings/AdminSecretsTab.vue')['default']
|
||||
AdminUsersTab: typeof import('./src/components/admin/settings/AdminUsersTab.vue')['default']
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"@vueuse/core": "^9.13.0",
|
||||
"ansi_up": "^5.1.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"floating-vue": "2.0.0-beta.20",
|
||||
"floating-vue": "^2.0.0-beta.20",
|
||||
"fuse.js": "^6.6.2",
|
||||
"humanize-duration": "^3.28.0",
|
||||
"javascript-time-ago": "^2.5.9",
|
||||
|
@ -56,7 +56,7 @@
|
|||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"eslint-plugin-vue-scoped-css": "^2.4.0",
|
||||
"prettier": "^2.8.4",
|
||||
"prettier": "^2.8.5",
|
||||
"typescript": "5.0.2",
|
||||
"unplugin-icons": "^0.15.3",
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
|
|
|
@ -27,14 +27,14 @@ specifiers:
|
|||
eslint-plugin-simple-import-sort: ^10.0.0
|
||||
eslint-plugin-vue: ^9.9.0
|
||||
eslint-plugin-vue-scoped-css: ^2.4.0
|
||||
floating-vue: 2.0.0-beta.20
|
||||
floating-vue: ^2.0.0-beta.20
|
||||
fuse.js: ^6.6.2
|
||||
humanize-duration: ^3.28.0
|
||||
javascript-time-ago: ^2.5.9
|
||||
lodash: ^4.17.21
|
||||
node-emoji: ^1.11.0
|
||||
pinia: ^2.0.33
|
||||
prettier: ^2.8.4
|
||||
prettier: ^2.8.5
|
||||
prismjs: ^1.29.0
|
||||
typescript: 5.0.2
|
||||
unplugin-icons: ^0.15.3
|
||||
|
@ -85,12 +85,12 @@ devDependencies:
|
|||
eslint-config-airbnb-typescript: 17.0.0_g4ufqspmwdmninnalnmypeb4gi
|
||||
eslint-config-prettier: 8.7.0_eslint@8.36.0
|
||||
eslint-plugin-import: 2.27.5_a7er6olmtneep4uytpot6gt7wu
|
||||
eslint-plugin-prettier: 4.2.1_eqzx3hpkgx5nnvxls3azrcc7dm
|
||||
eslint-plugin-prettier: 4.2.1_e4jq4ivnnnwcsomlcyj2qoljy4
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.36.0
|
||||
eslint-plugin-simple-import-sort: 10.0.0_eslint@8.36.0
|
||||
eslint-plugin-vue: 9.9.0_eslint@8.36.0
|
||||
eslint-plugin-vue-scoped-css: 2.4.0_baumsyql7ym4rscil5ymcumriy
|
||||
prettier: 2.8.4
|
||||
prettier: 2.8.5
|
||||
typescript: 5.0.2
|
||||
unplugin-icons: 0.15.3_@vue+compiler-sfc@3.2.47
|
||||
unplugin-vue-components: 0.24.1_vue@3.2.47
|
||||
|
@ -1763,7 +1763,7 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-prettier/4.2.1_eqzx3hpkgx5nnvxls3azrcc7dm:
|
||||
/eslint-plugin-prettier/4.2.1_e4jq4ivnnnwcsomlcyj2qoljy4:
|
||||
resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
|
@ -1776,7 +1776,7 @@ packages:
|
|||
dependencies:
|
||||
eslint: 8.36.0
|
||||
eslint-config-prettier: 8.7.0_eslint@8.36.0
|
||||
prettier: 2.8.4
|
||||
prettier: 2.8.5
|
||||
prettier-linter-helpers: 1.0.0
|
||||
dev: true
|
||||
|
||||
|
@ -2842,8 +2842,8 @@ packages:
|
|||
fast-diff: 1.2.0
|
||||
dev: true
|
||||
|
||||
/prettier/2.8.4:
|
||||
resolution: {integrity: sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==}
|
||||
/prettier/2.8.5:
|
||||
resolution: {integrity: sha512-3gzuxrHbKUePRBB4ZeU08VNkUcqEHaUaouNt0m7LGP4Hti/NuB07C7PPTM/LkWqXoJYJn2McEo5+kxPNrtQkLQ==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
|
|
@ -376,7 +376,14 @@
|
|||
"tasks": "Tasks",
|
||||
"task_running": "Task is running",
|
||||
"task_pending": "Task is pending",
|
||||
"task_waiting_on_deps": "Task is waiting on dependencies"
|
||||
"task_waiting_on_deps": "Task is waiting on dependencies",
|
||||
"stats": {
|
||||
"completed_count": "Completed Tasks",
|
||||
"worker_count": "Free",
|
||||
"running_count": "Running",
|
||||
"pending_count": "Pending",
|
||||
"waiting_on_deps_count": "Waiting on dependencies"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"users": "Users",
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<pre>{{ queueInfo?.stats }}</pre>
|
||||
<AdminQueueStats :stats="queueInfo?.stats" />
|
||||
|
||||
<div v-if="tasks.length > 0" class="flex flex-col">
|
||||
<p class="mt-6 mb-2 text-xl">{{ $t('admin.settings.queue.tasks') }}</p>
|
||||
|
@ -85,6 +85,8 @@ import useApiClient from '~/compositions/useApiClient';
|
|||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { QueueInfo } from '~/lib/api/types/queue';
|
||||
|
||||
import AdminQueueStats from './queue/AdminQueueStats.vue';
|
||||
|
||||
const apiClient = useApiClient();
|
||||
const notifications = useNotifications();
|
||||
const { t } = useI18n();
|
||||
|
|
109
web/src/components/admin/settings/queue/AdminQueueStats.vue
Normal file
109
web/src/components/admin/settings/queue/AdminQueueStats.vue
Normal file
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div v-if="stats" class="flex justify-center">
|
||||
<div class="bg-gray-100 dark:bg-dark-gray-600 text-color dark:text-gray-400 rounded-md py-5 px-5 w-full">
|
||||
<div class="flex w-full">
|
||||
<h3 class="text-lg font-semibold leading-tight uppercase flex-1">
|
||||
{{ $t('admin.settings.queue.stats.completed_count') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="relative overflow-hidden transition-all duration-500">
|
||||
<div>
|
||||
<div class="pb-4 lg:pb-6">
|
||||
<h4 class="text-2xl lg:text-3xl font-semibold leading-tight inline-block">
|
||||
{{ stats.completed_count }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="pb-4 lg:pb-6">
|
||||
<div class="overflow-hidden rounded-full h-3 flex transition-all duration-500">
|
||||
<div
|
||||
v-for="item in data"
|
||||
:key="item.key"
|
||||
class="h-full"
|
||||
:class="`${item.color}`"
|
||||
:style="{ width: `${item.perc}%` }"
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex -mx-4 sm:flex-wrap">
|
||||
<div
|
||||
v-for="(item, index) in data"
|
||||
:key="item.key"
|
||||
class="px-4 md:w-1/4 sm:w-full"
|
||||
:class="{ 'md:border-l border-gray-300 dark:border-gray-600': index !== 0 }"
|
||||
>
|
||||
<div class="text-sm whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
<span class="inline-block w-2 h-2 rounded-full mr-1 align-middle" :class="`${item.color}`"> </span>
|
||||
<span class="align-middle">{{ item.label }}</span>
|
||||
</div>
|
||||
<div class="font-medium text-lg">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { QueueStats } from '~/lib/api/types/queue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
stats?: QueueStats;
|
||||
}>();
|
||||
|
||||
const total = computed(() => {
|
||||
if (!props.stats) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
props.stats.worker_count + props.stats.running_count + props.stats.pending_count + props.stats.waiting_on_deps_count
|
||||
);
|
||||
});
|
||||
|
||||
const data = computed(() => {
|
||||
if (!props.stats) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'worker_count',
|
||||
label: t('admin.settings.queue.stats.worker_count'),
|
||||
value: props.stats.worker_count,
|
||||
perc: total.value > 0 ? (props.stats.worker_count / total.value) * 100 : 0,
|
||||
color: 'bg-green-500',
|
||||
},
|
||||
{
|
||||
key: 'running_count',
|
||||
label: t('admin.settings.queue.stats.running_count'),
|
||||
value: props.stats.running_count,
|
||||
perc: total.value > 0 ? (props.stats.running_count / total.value) * 100 : 100,
|
||||
color: 'bg-blue-500',
|
||||
},
|
||||
{
|
||||
key: 'pending_count',
|
||||
label: t('admin.settings.queue.stats.pending_count'),
|
||||
value: props.stats.pending_count,
|
||||
perc: total.value > 0 ? (props.stats.pending_count / total.value) * 100 : 0,
|
||||
color: 'bg-red-500',
|
||||
},
|
||||
{
|
||||
key: 'waiting_on_deps_count',
|
||||
label: t('admin.settings.queue.stats.waiting_on_deps_count'),
|
||||
value: props.stats.waiting_on_deps_count,
|
||||
perc: total.value > 0 ? (props.stats.waiting_on_deps_count / total.value) * 100 : 0,
|
||||
color: 'bg-red-800',
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
|
@ -7,16 +7,18 @@ export type Task = {
|
|||
run_on: string[];
|
||||
};
|
||||
|
||||
export type QueueStats = {
|
||||
worker_count: number;
|
||||
pending_count: number;
|
||||
waiting_on_deps_count: number;
|
||||
running_count: number;
|
||||
completed_count: number;
|
||||
};
|
||||
|
||||
export type QueueInfo = {
|
||||
pending: Task[];
|
||||
waiting_on_deps: Task[];
|
||||
running: Task[];
|
||||
stats: {
|
||||
worker_count: number;
|
||||
pending_count: number;
|
||||
waiting_on_deps_count: number;
|
||||
running_count: number;
|
||||
completed_count: number;
|
||||
};
|
||||
stats: QueueStats;
|
||||
paused: boolean;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue