188 lines
3.5 KiB
Vue
188 lines
3.5 KiB
Vue
<template>
|
|
<Admin class="page">
|
|
<div class="header">
|
|
<div class="params">
|
|
<label>
|
|
Alert: <input
|
|
v-model="alertThreshold"
|
|
type="number"
|
|
placeholder="Alert threshold"
|
|
class="input"
|
|
> weeks
|
|
</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">Network</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
|
|
:title="entity.id"
|
|
class="table-cell table-name ellipsis"
|
|
>
|
|
<a
|
|
:href="`/${entity.type}/${entity.slug}`"
|
|
target="_blank"
|
|
class="link"
|
|
>{{ entity.name }}</a>
|
|
</td>
|
|
|
|
<td
|
|
v-if="entity.parent"
|
|
:title="entity.paren?.id"
|
|
class="table-cell table-name ellipsis"
|
|
>
|
|
<a
|
|
:href="`/network/${entity.parent.slug}`"
|
|
target="_blank"
|
|
class="link"
|
|
>{{ entity.parent.name }}</a>
|
|
</td>
|
|
|
|
<td v-else />
|
|
|
|
<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, subWeeks } 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) || 12);
|
|
const deadThreshold = ref(Number(urlParsed.search.dead) || 36);
|
|
const order = urlParsed.search.order || 'desc';
|
|
|
|
const alertDate = computed(() => subWeeks(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: 12rem;
|
|
}
|
|
|
|
.table-total {
|
|
width: 6rem;
|
|
}
|
|
|
|
.alert {
|
|
color: var(--warn);
|
|
font-weight: bold;
|
|
}
|
|
|
|
.link {
|
|
color: var(--text);
|
|
}
|
|
</style>
|