ae5d052274
to keep things manageable i merged a lot of one off values into just a handful of common sizes, so some parts of the ui will look different than upstream even with the "Misskey" rounding mode
152 lines
4.0 KiB
Vue
152 lines
4.0 KiB
Vue
<!--
|
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
-->
|
|
|
|
<template>
|
|
<MkContainer :showHeader="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" data-cy-mkw-federation class="mkw-federation">
|
|
<template #icon><i class="ph-globe-hemisphere-west ph-bold ph-lg"></i></template>
|
|
<template #header>{{ i18n.ts._widgets.federation }}</template>
|
|
|
|
<div class="wbrkwalb">
|
|
<MkLoading v-if="fetching"/>
|
|
<TransitionGroup v-else tag="div" :name="defaultStore.state.animation ? 'chart' : ''" class="instances">
|
|
<div v-for="(instance, i) in instances" :key="instance.id" class="instance">
|
|
<img :src="getInstanceIcon(instance)" alt=""/>
|
|
<div class="body">
|
|
<MkA class="a" :to="`/instance-info/${instance.host}`" behavior="window" :title="instance.host">{{ instance.host }}</MkA>
|
|
<p>{{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</p>
|
|
</div>
|
|
<MkMiniChart class="chart" :src="charts[i].requests.received"/>
|
|
</div>
|
|
</TransitionGroup>
|
|
</div>
|
|
</MkContainer>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ref } from 'vue';
|
|
import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
|
|
import { GetFormResultType } from '@/scripts/form.js';
|
|
import MkContainer from '@/components/MkContainer.vue';
|
|
import MkMiniChart from '@/components/MkMiniChart.vue';
|
|
import * as os from '@/os.js';
|
|
import { useInterval } from '@/scripts/use-interval.js';
|
|
import { i18n } from '@/i18n.js';
|
|
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
|
|
import { defaultStore } from '@/store.js';
|
|
|
|
const name = 'federation';
|
|
|
|
const widgetPropsDef = {
|
|
showHeader: {
|
|
type: 'boolean' as const,
|
|
default: true,
|
|
},
|
|
};
|
|
|
|
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
|
|
|
|
const props = defineProps<WidgetComponentProps<WidgetProps>>();
|
|
const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
|
|
|
|
const { widgetProps, configure } = useWidgetPropsManager(name,
|
|
widgetPropsDef,
|
|
props,
|
|
emit,
|
|
);
|
|
|
|
const instances = ref([]);
|
|
const charts = ref([]);
|
|
const fetching = ref(true);
|
|
|
|
const fetch = async () => {
|
|
const fetchedInstances = await os.api('federation/instances', {
|
|
sort: '+latestRequestReceivedAt',
|
|
limit: 5,
|
|
});
|
|
const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.apiGet('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
|
|
instances.value = fetchedInstances;
|
|
charts.value = fetchedCharts;
|
|
fetching.value = false;
|
|
};
|
|
|
|
useInterval(fetch, 1000 * 60, {
|
|
immediate: true,
|
|
afterMounted: true,
|
|
});
|
|
|
|
function getInstanceIcon(instance): string {
|
|
return getProxiedImageUrlNullable(instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.faviconUrl, 'preview') ?? '/client-assets/dummy.png';
|
|
}
|
|
|
|
defineExpose<WidgetComponentExpose>({
|
|
name,
|
|
configure,
|
|
id: props.widget ? props.widget.id : null,
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.wbrkwalb {
|
|
$bodyTitleHieght: 18px;
|
|
$bodyInfoHieght: 16px;
|
|
|
|
height: (62px + 1px) + (62px + 1px) + (62px + 1px) + (62px + 1px) + 62px;
|
|
overflow: hidden;
|
|
|
|
> .instances {
|
|
.chart-move {
|
|
transition: transform 1s ease;
|
|
}
|
|
|
|
> .instance {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 14px 16px;
|
|
border-bottom: solid 0.5px var(--divider);
|
|
|
|
> img {
|
|
display: block;
|
|
width: ($bodyTitleHieght + $bodyInfoHieght);
|
|
height: ($bodyTitleHieght + $bodyInfoHieght);
|
|
object-fit: cover;
|
|
border-radius: var(--radius-xs);
|
|
margin-right: 8px;
|
|
}
|
|
|
|
> .body {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
font-size: 0.9em;
|
|
color: var(--fg);
|
|
padding-right: 8px;
|
|
|
|
> .a {
|
|
display: block;
|
|
width: 100%;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
line-height: $bodyTitleHieght;
|
|
}
|
|
|
|
> p {
|
|
margin: 0;
|
|
font-size: 75%;
|
|
opacity: 0.7;
|
|
line-height: $bodyInfoHieght;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
|
|
> .chart {
|
|
height: 30px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|