Added rudimentary entity health overview.
This commit is contained in:
@@ -16,6 +16,13 @@
|
||||
class="link"
|
||||
>Actor Revisions</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/admin/entities"
|
||||
class="link"
|
||||
>Entity Health</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Admin>
|
||||
</template>
|
||||
|
||||
157
pages/admin/entities/+Page.vue
Normal file
157
pages/admin/entities/+Page.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<Admin class="page">
|
||||
<div class="header">
|
||||
<div class="params">
|
||||
<label>
|
||||
Alert: <input
|
||||
v-model="alertThreshold"
|
||||
type="number"
|
||||
placeholder="Alert threshold"
|
||||
class="input"
|
||||
> months
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Dead: <input
|
||||
v-model="deadThreshold"
|
||||
type="number"
|
||||
placeholder="Alert threshold"
|
||||
class="input"
|
||||
> months
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<span class="attention">{{ alertEntities.length }} entities might require your attention</span>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-header">Entity</th>
|
||||
|
||||
<th
|
||||
class="table-header noselect"
|
||||
@click="sort('releases')"
|
||||
>Releases</th>
|
||||
|
||||
<th
|
||||
class="table-header noselect"
|
||||
@click="sort('latest')"
|
||||
>Latest release</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="entity in alertEntities"
|
||||
:key="`entity-${entity.id}`"
|
||||
>
|
||||
<td class="table-cell table-name ellipsis">{{ entity.name }}</td>
|
||||
<td class="table-cell table-total">{{ entity.totalReleases }}</td>
|
||||
<td
|
||||
class="table-cell table-date"
|
||||
:class="{ alert: entity.latestReleaseDate && entity.latestReleaseDate < alertDate }"
|
||||
>{{ entity.latestReleaseDate && format(entity.latestReleaseDate, 'yyyy-MM-dd hh:mm') }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Admin>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
inject,
|
||||
} from 'vue';
|
||||
|
||||
import { format, subMonths } from 'date-fns';
|
||||
|
||||
import navigate from '#/src/navigate.js';
|
||||
|
||||
import Admin from '#/components/admin/admin.vue';
|
||||
|
||||
const {
|
||||
pageProps,
|
||||
urlParsed,
|
||||
meta,
|
||||
} = inject('pageContext');
|
||||
|
||||
const { entities } = pageProps;
|
||||
|
||||
const alertThreshold = ref(Number(urlParsed.search.alert) || 3);
|
||||
const deadThreshold = ref(Number(urlParsed.search.dead) || 36);
|
||||
const order = urlParsed.search.order || 'desc';
|
||||
|
||||
const alertDate = computed(() => subMonths(meta.now, alertThreshold.value));
|
||||
const deadDate = computed(() => subMonths(meta.now, deadThreshold.value));
|
||||
|
||||
const alertEntities = computed(() => entities.filter((entity) => entity.latestReleaseDate > deadDate.value && entity.latestReleaseDate < alertDate.value));
|
||||
|
||||
function sort(sorting) {
|
||||
navigate('/admin/entities', {
|
||||
sort: sorting,
|
||||
order: order === 'desc' ? 'asc' : 'desc',
|
||||
alert: alertThreshold.value,
|
||||
dead: deadThreshold.value,
|
||||
}, {
|
||||
redirect: true,
|
||||
});
|
||||
}
|
||||
|
||||
watch([alertThreshold, deadThreshold], () => {
|
||||
navigate('/admin/entities', {
|
||||
...urlParsed.search,
|
||||
alert: alertThreshold.value,
|
||||
dead: deadThreshold.value,
|
||||
}, {
|
||||
redirect: false,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.params {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
|
||||
.input {
|
||||
width: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.attention {
|
||||
margin-left: 2rem;
|
||||
color: var(--warn);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.table-name {
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
.table-total {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.alert {
|
||||
color: var(--warn);
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
27
pages/admin/entities/+onBeforeRender.js
Normal file
27
pages/admin/entities/+onBeforeRender.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { render } from 'vike/abort'; /* eslint-disable-line import/extensions */
|
||||
import { fetchEntityHealths } from '#/src/entities.js';
|
||||
|
||||
export async function onBeforeRender(pageContext) {
|
||||
if (!pageContext.user || pageContext.user.role === 'user') {
|
||||
throw render(404);
|
||||
}
|
||||
|
||||
const {
|
||||
entities,
|
||||
} = await fetchEntityHealths({
|
||||
sort: pageContext.urlParsed.search.sort || 'releases',
|
||||
order: pageContext.urlParsed.search.order || 'desc',
|
||||
}, pageContext.user);
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
title: pageContext.routeParams.section,
|
||||
pageProps: {
|
||||
entities,
|
||||
},
|
||||
routeParams: {
|
||||
section: 'entities',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -61,7 +61,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, onMounted } from 'vue';
|
||||
import { ref, inject } from 'vue';
|
||||
|
||||
import navigate from '#/src/navigate.js';
|
||||
|
||||
@@ -119,15 +119,11 @@ const sections = [
|
||||
},
|
||||
].filter(Boolean);
|
||||
|
||||
// const tags = Object.values(Object.fromEntries(networks.flatMap((entity) => entity.tags).map((tag) => [tag.id, tag])));
|
||||
|
||||
async function search() {
|
||||
navigate('/channels', { q: query.value || undefined }, { redirect: true });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('load', (event) => {
|
||||
console.log(event);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user