fix: remove charts from client (close #76)

This commit is contained in:
naskya 2023-09-17 08:56:51 +09:00
parent 3e663f975e
commit 670e750257
Signed by: naskya
GPG key ID: 164DFF24E2D40139
22 changed files with 8 additions and 1755 deletions

View file

@ -365,235 +365,6 @@ const exportData = () => {
// TODO
};
const fetchFederationChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/federation", {
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Received",
type: "area",
data: format(raw.inboxInstances),
color: colors.blue,
},
{
name: "Delivered",
type: "area",
data: format(raw.deliveredInstances),
color: colors.green,
},
{
name: "Stalled",
type: "area",
data: format(raw.stalled),
color: colors.red,
},
{
name: "Pub Active",
type: "line",
data: format(raw.pubActive),
color: colors.purple,
},
{
name: "Sub Active",
type: "line",
data: format(raw.subActive),
color: colors.orange,
},
{
name: "Pub & Sub",
type: "line",
data: format(raw.pubsub),
dashed: true,
color: colors.cyan,
},
{
name: "Pub",
type: "line",
data: format(raw.pub),
dashed: true,
color: colors.purple,
},
{
name: "Sub",
type: "line",
data: format(raw.sub),
dashed: true,
color: colors.orange,
},
],
};
};
const fetchApRequestChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/ap-request", {
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "In",
type: "area",
color: "#31748f",
data: format(raw.inboxReceived),
},
{
name: "Out (succ)",
type: "area",
color: "#c4a7e7",
data: format(raw.deliverSucceeded),
},
{
name: "Out (fail)",
type: "area",
color: "#f6c177",
data: format(raw.deliverFailed),
},
],
};
};
const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/notes", {
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "All",
type: "line",
data: format(
type === "combined"
? sum(
raw.local.inc,
negate(raw.local.dec),
raw.remote.inc,
negate(raw.remote.dec),
)
: sum(raw[type].inc, negate(raw[type].dec)),
),
color: "#888888",
},
{
name: "Renotes",
type: "area",
data: format(
type === "combined"
? sum(raw.local.diffs.renote, raw.remote.diffs.renote)
: raw[type].diffs.renote,
),
color: colors.green,
},
{
name: "Replies",
type: "area",
data: format(
type === "combined"
? sum(raw.local.diffs.reply, raw.remote.diffs.reply)
: raw[type].diffs.reply,
),
color: colors.yellow,
},
{
name: "Normal",
type: "area",
data: format(
type === "combined"
? sum(raw.local.diffs.normal, raw.remote.diffs.normal)
: raw[type].diffs.normal,
),
color: colors.blue,
},
{
name: "With file",
type: "area",
data: format(
type === "combined"
? sum(
raw.local.diffs.withFile,
raw.remote.diffs.withFile,
)
: raw[type].diffs.withFile,
),
color: colors.purple,
},
],
};
};
const fetchNotesTotalChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/notes", {
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Combined",
type: "line",
data: format(sum(raw.local.total, raw.remote.total)),
},
{
name: "Local",
type: "area",
data: format(raw.local.total),
},
{
name: "Remote",
type: "area",
data: format(raw.remote.total),
},
],
};
};
const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/users", {
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Combined",
type: "line",
data: format(
total
? sum(raw.local.total, raw.remote.total)
: sum(
raw.local.inc,
negate(raw.local.dec),
raw.remote.inc,
negate(raw.remote.dec),
),
),
},
{
name: "Local",
type: "area",
data: format(
total
? raw.local.total
: sum(raw.local.inc, negate(raw.local.dec)),
),
},
{
name: "Remote",
type: "area",
data: format(
total
? raw.remote.total
: sum(raw.remote.inc, negate(raw.remote.dec)),
),
},
],
};
};
const fetchActiveUsersChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/active-users", {
limit: props.limit,
@ -659,424 +430,13 @@ const fetchActiveUsersChart = async (): Promise<typeof chartData> => {
};
};
const fetchDriveChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/drive", {
limit: props.limit,
span: props.span,
});
return {
bytes: true,
series: [
{
name: "All",
type: "line",
dashed: true,
data: format(
sum(
raw.local.incSize,
negate(raw.local.decSize),
raw.remote.incSize,
negate(raw.remote.decSize),
),
),
},
{
name: "Local +",
type: "area",
data: format(raw.local.incSize),
},
{
name: "Local -",
type: "area",
data: format(negate(raw.local.decSize)),
},
{
name: "Remote +",
type: "area",
data: format(raw.remote.incSize),
},
{
name: "Remote -",
type: "area",
data: format(negate(raw.remote.decSize)),
},
],
};
};
const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/drive", {
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "All",
type: "line",
dashed: true,
data: format(
sum(
raw.local.incCount,
negate(raw.local.decCount),
raw.remote.incCount,
negate(raw.remote.decCount),
),
),
},
{
name: "Local +",
type: "area",
data: format(raw.local.incCount),
},
{
name: "Local -",
type: "area",
data: format(negate(raw.local.decCount)),
},
{
name: "Remote +",
type: "area",
data: format(raw.remote.incCount),
},
{
name: "Remote -",
type: "area",
data: format(negate(raw.remote.decCount)),
},
],
};
};
const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", {
host: props.args.host,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "In",
type: "area",
color: "#31748f",
data: format(raw.requests.received),
},
{
name: "Out (succ)",
type: "area",
color: "#c4a7e7",
data: format(raw.requests.succeeded),
},
{
name: "Out (fail)",
type: "area",
color: "#f6c177",
data: format(raw.requests.failed),
},
],
};
};
const fetchInstanceUsersChart = async (
total: boolean,
): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", {
host: props.args.host,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Users",
type: "area",
color: "#31748f",
data: format(
total
? raw.users.total
: sum(raw.users.inc, negate(raw.users.dec)),
),
},
],
};
};
const fetchInstanceNotesChart = async (
total: boolean,
): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", {
host: props.args.host,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Posts",
type: "area",
color: "#31748f",
data: format(
total
? raw.notes.total
: sum(raw.notes.inc, negate(raw.notes.dec)),
),
},
],
};
};
const fetchInstanceFfChart = async (
total: boolean,
): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", {
host: props.args.host,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Following",
type: "area",
color: "#31748f",
data: format(
total
? raw.following.total
: sum(raw.following.inc, negate(raw.following.dec)),
),
},
{
name: "Followers",
type: "area",
color: "#c4a7e7",
data: format(
total
? raw.followers.total
: sum(raw.followers.inc, negate(raw.followers.dec)),
),
},
],
};
};
const fetchInstanceDriveUsageChart = async (
total: boolean,
): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", {
host: props.args.host,
limit: props.limit,
span: props.span,
});
return {
bytes: true,
series: [
{
name: "Drive usage",
type: "area",
color: "#31748f",
data: format(
total
? raw.drive.totalUsage
: sum(raw.drive.incUsage, negate(raw.drive.decUsage)),
),
},
],
};
};
const fetchInstanceDriveFilesChart = async (
total: boolean,
): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/instance", {
host: props.args.host,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Drive files",
type: "area",
color: "#31748f",
data: format(
total
? raw.drive.totalFiles
: sum(raw.drive.incFiles, negate(raw.drive.decFiles)),
),
},
],
};
};
const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/user/notes", {
userId: props.args.user.id,
limit: props.limit,
span: props.span,
});
return {
series: [
...(props.args.withoutAll
? []
: [
{
name: "All",
type: "line",
data: format(sum(raw.inc, negate(raw.dec))),
color: "#888888",
},
]),
{
name: "With file",
type: "area",
data: format(raw.diffs.withFile),
color: colors.purple,
},
{
name: "Renotes",
type: "area",
data: format(raw.diffs.renote),
color: colors.green,
},
{
name: "Replies",
type: "area",
data: format(raw.diffs.reply),
color: colors.yellow,
},
{
name: "Normal",
type: "area",
data: format(raw.diffs.normal),
color: colors.blue,
},
],
};
};
const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/user/following", {
userId: props.args.user.id,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Local",
type: "area",
data: format(raw.local.followings.total),
},
{
name: "Remote",
type: "area",
data: format(raw.remote.followings.total),
},
],
};
};
const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/user/following", {
userId: props.args.user.id,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Local",
type: "area",
data: format(raw.local.followers.total),
},
{
name: "Remote",
type: "area",
data: format(raw.remote.followers.total),
},
],
};
};
const fetchPerUserDriveChart = async (): Promise<typeof chartData> => {
const raw = await os.apiGet("charts/user/drive", {
userId: props.args.user.id,
limit: props.limit,
span: props.span,
});
return {
series: [
{
name: "Inc",
type: "area",
data: format(raw.incSize),
},
{
name: "Dec",
type: "area",
data: format(raw.decSize),
},
],
};
};
const fetchAndRender = async () => {
const fetchData = () => {
switch (props.src) {
case "federation":
return fetchFederationChart();
case "ap-request":
return fetchApRequestChart();
case "users":
return fetchUsersChart(false);
case "users-total":
return fetchUsersChart(true);
case "active-users":
return fetchActiveUsersChart();
case "notes":
return fetchNotesChart("combined");
case "local-notes":
return fetchNotesChart("local");
case "remote-notes":
return fetchNotesChart("remote");
case "notes-total":
return fetchNotesTotalChart();
case "drive":
return fetchDriveChart();
case "drive-files":
return fetchDriveFilesChart();
case "instance-requests":
return fetchInstanceRequestsChart();
case "instance-users":
return fetchInstanceUsersChart(false);
case "instance-users-total":
return fetchInstanceUsersChart(true);
case "instance-notes":
return fetchInstanceNotesChart(false);
case "instance-notes-total":
return fetchInstanceNotesChart(true);
case "instance-ff":
return fetchInstanceFfChart(false);
case "instance-ff-total":
return fetchInstanceFfChart(true);
case "instance-drive-usage":
return fetchInstanceDriveUsageChart(false);
case "instance-drive-usage-total":
return fetchInstanceDriveUsageChart(true);
case "instance-drive-files":
return fetchInstanceDriveFilesChart(false);
case "instance-drive-files-total":
return fetchInstanceDriveFilesChart(true);
case "per-user-notes":
return fetchPerUserNotesChart();
case "per-user-following":
return fetchPerUserFollowingChart();
case "per-user-followers":
return fetchPerUserFollowersChart();
case "per-user-drive":
return fetchPerUserDriveChart();
default:
console.error(`${props.src} chart is disabled.`);
}
};
fetching.value = true;

View file

@ -19,7 +19,6 @@
{{ instance.softwareVersion }}</span
>
</div>
<MkMiniChart v-if="chartValues" class="chart" :src="chartValues" />
</div>
</template>
@ -27,7 +26,6 @@
import { ref } from "vue";
import type * as firefish from "firefish-js";
import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
@ -35,18 +33,6 @@ const props = defineProps<{
instance: firefish.entities.Instance;
}>();
const chartValues = ref<number[] | null>(null);
os.apiGet("charts/instance", {
host: props.instance.host,
limit: 16 + 1,
span: "day",
}).then((res) => {
//
res.requests.received.splice(0, 1);
chartValues.value = res.requests.received;
});
function getInstanceIcon(instance): string {
return (
getProxiedImageUrlNullable(instance.iconUrl, "preview") ??
@ -104,10 +90,6 @@ function getInstanceIcon(instance): string {
}
}
> :global(.chart) {
height: 30px;
}
&:global(.yellow) {
--c: rgb(255 196 0 / 15%);
background-image: linear-gradient(

View file

@ -1,52 +1,9 @@
<template>
<div :class="$style.root">
<MkFolder class="item">
<template #header>Chart</template>
<template #header>Active Users</template>
<div :class="$style.chart">
<div class="selects">
<MkSelect v-model="chartSrc" style="margin: 0; flex: 1">
<optgroup :label="i18n.ts.federation">
<option value="federation">
{{ i18n.ts._charts.federation }}
</option>
<option value="ap-request">
{{ i18n.ts._charts.apRequest }}
</option>
</optgroup>
<optgroup :label="i18n.ts.users">
<option value="users">
{{ i18n.ts._charts.usersIncDec }}
</option>
<option value="users-total">
{{ i18n.ts._charts.usersTotal }}
</option>
<option value="active-users">
{{ i18n.ts._charts.activeUsers }}
</option>
</optgroup>
<optgroup :label="i18n.ts.notes">
<option value="notes">
{{ i18n.ts._charts.notesIncDec }}
</option>
<option value="local-notes">
{{ i18n.ts._charts.localNotesIncDec }}
</option>
<option value="remote-notes">
{{ i18n.ts._charts.remoteNotesIncDec }}
</option>
<option value="notes-total">
{{ i18n.ts._charts.notesTotal }}
</option>
</optgroup>
<optgroup :label="i18n.ts.drive">
<option value="drive-files">
{{ i18n.ts._charts.filesIncDec }}
</option>
<option value="drive">
{{ i18n.ts._charts.storageUsageIncDec }}
</option>
</optgroup>
</MkSelect>
<MkSelect v-model="chartSpan" style="margin: 0 0 0 10px">
<option value="hour">{{ i18n.ts.perHour }}</option>
<option value="day">{{ i18n.ts.perDay }}</option>
@ -54,32 +11,15 @@
</div>
<div class="chart _panel">
<MkChart
:src="chartSrc"
src="active-users"
:span="chartSpan"
:limit="chartLimit"
:detailed="true"
></MkChart>
</div>
</div>
</MkFolder>
<MkFolder class="item">
<template #header>Active users heatmap</template>
<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0">
<option value="active-users">Active users</option>
<option value="notes">Posts</option>
<option value="ap-requests-inbox-received">
Fediverse Requests: inboxReceived
</option>
<option value="ap-requests-deliver-succeeded">
Fediverse Requests: deliverSucceeded
</option>
<option value="ap-requests-deliver-failed">
Fediverse Requests: deliverFailed
</option>
</MkSelect>
<div class="_panel" :class="$style.heatmap">
<MkHeatmap :src="heatmapSrc" />
<MkHeatmap src="active-users" />
</div>
</MkFolder>
@ -117,8 +57,6 @@ initChart();
const chartLimit = 500;
const chartSpan = ref<"hour" | "day">("hour");
const chartSrc = ref("active-users");
const heatmapSrc = ref("active-users");
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();

View file

@ -98,7 +98,6 @@
v-if="tab === 'renotes' && renotes"
:key="item.user.id"
:user="item.user"
:with-chart="false"
/>
<!-- </MkPagination> -->
<MkLoading v-else-if="tab === 'renotes' && note.renoteCount > 0" />

View file

@ -23,12 +23,7 @@
}}</span>
</button>
</div>
<MkUserCardMini
v-for="user in users"
:key="user.id"
:user="user"
:with-chart="false"
/>
<MkUserCardMini v-for="user in users" :key="user.id" :user="user" />
</div>
<div v-else>
<MkLoading />

View file

@ -19,7 +19,6 @@
><span class="acct _monospace">@{{ acct(user) }}</span></span
>
</div>
<MkMiniChart v-if="chartValues" class="chart" :src="chartValues" />
</MkA>
</template>
@ -27,35 +26,17 @@
import { ref } from "vue";
import type * as misskey from "firefish-js";
import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os";
import { acct, userPage } from "@/filters/user";
const props = withDefaults(
defineProps<{
user: misskey.entities.User;
withChart?: boolean;
showAboutPage?: boolean;
}>(),
{
withChart: true,
showAboutPage: false,
},
);
const chartValues = ref<number[] | null>(null);
if (props.withChart) {
os.apiGet("charts/user/notes", {
userId: props.user.id,
limit: 16 + 1,
span: "day",
}).then((res) => {
//
res.inc.splice(0, 1);
chartValues.value = res.inc;
});
}
</script>
<style lang="scss" module>

View file

@ -1,289 +0,0 @@
<template>
<div>
<MkLoading v-if="fetching" />
<div v-show="!fetching" :class="$style.root">
<div class="charts _panel">
<div class="chart">
<canvas ref="chartEl2"></canvas>
</div>
<div class="chart">
<canvas ref="chartEl"></canvas>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from "vue";
import { Chart } from "chart.js";
import gradient from "chartjs-plugin-gradient";
import * as os from "@/os";
import { useChartTooltip } from "@/scripts/use-chart-tooltip";
import { chartVLine } from "@/scripts/chart-vline";
import { defaultStore } from "@/store";
import { alpha } from "@/scripts/color";
import { initChart } from "@/scripts/init-chart";
initChart();
const chartLimit = 50;
const chartEl = shallowRef<HTMLCanvasElement>();
const chartEl2 = shallowRef<HTMLCanvasElement>();
const fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip();
const { handler: externalTooltipHandler2 } = useChartTooltip();
onMounted(async () => {
const now = new Date();
const getDate = (ago: number) => {
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
return new Date(y, m, d - ago);
};
const format = (arr) => {
return arr.map((v, i) => ({
x: getDate(i).getTime(),
y: v,
}));
};
const formatMinus = (arr) => {
return arr.map((v, i) => ({
x: getDate(i).getTime(),
y: -v,
}));
};
const raw = await os.api("charts/ap-request", {
limit: chartLimit,
span: "day",
});
const vLineColor = defaultStore.state.darkMode
? "rgba(255, 255, 255, 0.2)"
: "rgba(0, 0, 0, 0.2)";
const succColor = "#9ccfd8";
const failColor = "#f6c177";
new Chart(chartEl.value, {
type: "line",
data: {
datasets: [
{
stack: "a",
parsing: false,
label: "Out: Succ",
data: format(raw.deliverSucceeded).slice().reverse(),
tension: 0.3,
pointRadius: 0,
borderWidth: 2,
borderColor: succColor,
borderJoinStyle: "round",
borderRadius: 4,
backgroundColor: alpha(succColor, 0.35),
fill: true,
clip: 8,
},
{
stack: "a",
parsing: false,
label: "Out: Fail",
data: formatMinus(raw.deliverFailed).slice().reverse(),
tension: 0.3,
pointRadius: 0,
borderWidth: 2,
borderColor: failColor,
borderJoinStyle: "round",
borderRadius: 4,
backgroundColor: alpha(failColor, 0.35),
fill: true,
clip: 8,
},
],
},
options: {
aspectRatio: 2.5,
layout: {
padding: {
left: 0,
right: 8,
top: 0,
bottom: 0,
},
},
scales: {
x: {
type: "time",
stacked: true,
offset: false,
time: {
stepSize: 1,
unit: "day",
},
grid: {
display: true,
},
ticks: {
display: true,
maxRotation: 0,
autoSkipPadding: 16,
},
min: getDate(chartLimit).getTime(),
},
y: {
stacked: true,
position: "left",
suggestedMax: 10,
grid: {
display: true,
},
ticks: {
display: true,
// mirror: true,
callback: (value, index, values) =>
value < 0 ? -value : value,
},
},
},
interaction: {
intersect: false,
mode: "index",
},
elements: {
point: {
hoverRadius: 5,
hoverBorderWidth: 2,
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
mode: "index",
animation: {
duration: 0,
},
external: externalTooltipHandler,
},
gradient,
},
},
plugins: [chartVLine(vLineColor)],
});
new Chart(chartEl2.value, {
type: "bar",
data: {
datasets: [
{
parsing: false,
label: "In",
data: format(raw.inboxReceived).slice().reverse(),
tension: 0.3,
pointRadius: 0,
borderWidth: 0,
borderJoinStyle: "round",
borderRadius: 4,
backgroundColor: "#c4a7e7",
barPercentage: 0.8,
categoryPercentage: 0.9,
fill: true,
clip: 8,
},
],
},
options: {
aspectRatio: 5,
layout: {
padding: {
left: 0,
right: 8,
top: 0,
bottom: 0,
},
},
scales: {
x: {
type: "time",
offset: false,
time: {
stepSize: 1,
unit: "day",
displayFormats: {
day: "M/d",
month: "Y/M",
},
},
grid: {
display: false,
},
ticks: {
display: false,
maxRotation: 0,
autoSkipPadding: 16,
},
min: getDate(chartLimit).getTime(),
},
y: {
position: "left",
suggestedMax: 10,
grid: {
display: true,
},
},
},
interaction: {
intersect: false,
mode: "index",
},
elements: {
point: {
hoverRadius: 5,
hoverBorderWidth: 2,
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
mode: "index",
animation: {
duration: 0,
},
external: externalTooltipHandler2,
},
gradient,
},
},
plugins: [chartVLine(vLineColor)],
});
fetching.value = false;
});
</script>
<style lang="scss" module>
.root {
&:global {
> .charts {
> .chart {
padding: 16px;
&:first-child {
border-bottom: solid 0.5px var(--divider);
}
}
}
}
}
</style>

View file

@ -17,40 +17,6 @@
<div class="subTitle">Top 10</div>
</div>
</div>
<div v-if="!fetching" class="items">
<div class="item _panel sub">
<div class="icon">
<i class="ph-download ph-bold ph-xl"></i>
</div>
<div class="body">
<div class="value">
{{ number(federationSubActive) }}
<MkNumberDiff
v-tooltip="i18n.ts.dayOverDayChanges"
class="diff"
:value="federationSubActiveDiff"
></MkNumberDiff>
</div>
<div class="label">Sub</div>
</div>
</div>
<div class="item _panel pub">
<div class="icon">
<i class="ph-upload ph-bold ph-xl"></i>
</div>
<div class="body">
<div class="value">
{{ number(federationPubActive) }}
<MkNumberDiff
v-tooltip="i18n.ts.dayOverDayChanges"
class="diff"
:value="federationPubActiveDiff"
></MkNumberDiff>
</div>
<div class="label">Pub</div>
</div>
</div>
</div>
</div>
</div>
</template>

View file

@ -1,29 +1,11 @@
<template>
<div class="_panel" :class="$style.root">
<MkSelect v-model="src" style="margin: 0 0 12px 0" small>
<option value="notes">Posts</option>
<option value="active-users">Active users</option>
<option value="ap-requests-inbox-received">
Fediverse Requests: inboxReceived
</option>
<option value="ap-requests-deliver-succeeded">
Fediverse Requests: deliverSucceeded
</option>
<option value="ap-requests-deliver-failed">
Fediverse Requests: deliverFailed
</option>
</MkSelect>
<MkHeatmap :src="src" />
<MkHeatmap src="active-users" />
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import MkHeatmap from "@/components/MkHeatmap.vue";
import MkSelect from "@/components/form/select.vue";
const src = ref("notes");
</script>
<style lang="scss" module>

View file

@ -16,11 +16,6 @@
:value="stats.originalUsersCount"
style="margin-right: 0.5em"
/>
<MkNumberDiff
v-tooltip="i18n.ts.dayOverDayChanges"
class="diff"
:value="usersComparedToThePrevDay"
></MkNumberDiff>
</div>
<div class="label">{{ i18n.ts.users }}</div>
</div>
@ -35,11 +30,6 @@
:value="stats.originalNotesCount"
style="margin-right: 0.5em"
/>
<MkNumberDiff
v-tooltip="i18n.ts.dayOverDayChanges"
class="diff"
:value="notesComparedToThePrevDay"
></MkNumberDiff>
</div>
<div class="label">{{ i18n.ts.notes }}</div>
</div>
@ -96,7 +86,6 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import * as os from "@/os";
import MkNumberDiff from "@/components/MkNumberDiff.vue";
import MkNumber from "@/components/MkNumber.vue";
import { i18n } from "@/i18n";

View file

@ -12,7 +12,6 @@
><span class="acct _monospace">@{{ acct(user) }}</span></span
>
</div>
<MkMiniChart v-if="chart" class="chart" :src="chart.inc" />
</MkA>
</template>
@ -20,23 +19,12 @@
import { ref } from "vue";
import type * as misskey from "firefish-js";
import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os";
import { acct } from "@/filters/user";
const props = defineProps<{
user: misskey.entities.User;
}>();
const chart = ref(null);
os.apiGet("charts/user/notes", {
userId: props.user.id,
limit: 16,
span: "day",
}).then((res) => {
chart.value = res;
});
</script>
<style lang="scss" module>
@ -81,9 +69,5 @@ os.apiGet("charts/user/notes", {
text-overflow: ellipsis;
}
}
> :global(.chart) {
height: 30px;
}
}
</style>

View file

@ -9,10 +9,6 @@
<MkFolder class="item">
<template #header>Active users</template>
<XActiveUsers />
</MkFolder>
<MkFolder class="item">
<template #header>Heatmap</template>
<XHeatmap />
</MkFolder>
@ -31,11 +27,6 @@
<XInstances />
</MkFolder>
<MkFolder class="item">
<template #header>Fediverse Requests</template>
<XApRequests />
</MkFolder>
<MkFolder class="item">
<template #header>New users</template>
<XUsers />
@ -71,7 +62,6 @@ import {
import XFederation from "./overview.federation.vue";
import XInstances from "./overview.instances.vue";
import XQueue from "./overview.queue.vue";
import XApRequests from "./overview.ap-requests.vue";
import XUsers from "./overview.users.vue";
import XActiveUsers from "./overview.active-users.vue";
import XStats from "./overview.stats.vue";

View file

@ -223,79 +223,6 @@
</FormSection>
</div>
</swiper-slide>
<swiper-slide>
<div class="_formRoot">
<div class="cmhjzshl">
<div class="selects">
<MkSelect
v-model="chartSrc"
style="margin: 0 10px 0 0; flex: 1"
>
<option value="instance-requests">
{{ i18n.ts._instanceCharts.requests }}
</option>
<option value="instance-users">
{{ i18n.ts._instanceCharts.users }}
</option>
<option value="instance-users-total">
{{ i18n.ts._instanceCharts.usersTotal }}
</option>
<option value="instance-notes">
{{ i18n.ts._instanceCharts.notes }}
</option>
<option value="instance-notes-total">
{{ i18n.ts._instanceCharts.notesTotal }}
</option>
<option value="instance-ff">
{{ i18n.ts._instanceCharts.ff }}
</option>
<option value="instance-ff-total">
{{ i18n.ts._instanceCharts.ffTotal }}
</option>
<option value="instance-drive-usage">
{{ i18n.ts._instanceCharts.cacheSize }}
</option>
<option value="instance-drive-usage-total">
{{
i18n.ts._instanceCharts
.cacheSizeTotal
}}
</option>
<option value="instance-drive-files">
{{ i18n.ts._instanceCharts.files }}
</option>
<option value="instance-drive-files-total">
{{ i18n.ts._instanceCharts.filesTotal }}
</option>
</MkSelect>
</div>
<div class="charts">
<div class="label">
{{ i18n.t("recentNHours", { n: 90 }) }}
</div>
<MkChart
class="chart"
:src="chartSrc"
span="hour"
:limit="90"
:args="{ host: host }"
:detailed="true"
></MkChart>
<div class="label">
{{ i18n.t("recentNDays", { n: 90 }) }}
</div>
<MkChart
class="chart"
:src="chartSrc"
span="day"
:limit="90"
:args="{ host: host }"
:detailed="true"
></MkChart>
</div>
</div>
</div>
</swiper-slide>
<swiper-slide>
<div class="_formRoot">
<MkPagination
@ -341,7 +268,6 @@ import { computed, ref, watch } from "vue";
import { Virtual } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/vue";
import type * as firefish from "firefish-js";
import MkChart from "@/components/MkChart.vue";
import MkObjectView from "@/components/MkObjectView.vue";
import FormLink from "@/components/form/link.vue";
import MkLink from "@/components/MkLink.vue";
@ -377,11 +303,10 @@ const props = defineProps<{
}>();
const tabs = ["overview"];
if (iAmAdmin) tabs.push("chart", "users", "raw");
if (iAmAdmin) tabs.push("users", "raw");
const tab = ref(tabs[0]);
watch(tab, () => syncSlide(tabs.indexOf(tab.value)));
const chartSrc = ref("instance-requests");
const meta = ref<AugmentedInstanceMetadata | null>(null);
const instance = ref<AugmentedInstance | null>(null);
const suspended = ref(false);
@ -488,11 +413,6 @@ const theTabs = [
if (iAmAdmin) {
theTabs.push(
{
key: "chart",
title: i18n.ts.charts,
icon: "ph-chart-bar ph-bold ph-lg",
},
{
key: "users",
title: i18n.ts.users,
@ -551,12 +471,5 @@ function syncSlide(index) {
display: flex;
margin: 0 0 16px 0;
}
> .charts {
> .label {
margin-bottom: 12px;
font-weight: bold;
}
}
}
</style>

View file

@ -17,20 +17,6 @@
</FormSplit>
</FormSection>
<FormSection>
<template #label>{{ i18n.ts.statistics }}</template>
<MkChart
src="per-user-drive"
:args="{ user: $i }"
span="day"
:limit="7 * 5"
:bar="true"
:stacked="true"
:detailed="false"
:aspect-ratio="6"
/>
</FormSection>
<FormSection>
<FormButton @click="chooseUploadFolder()">
{{ i18n.ts.uploadFolder }}
@ -82,7 +68,6 @@ import FormSplit from "@/components/form/split.vue";
import * as os from "@/os";
import bytes from "@/filters/bytes";
import { defaultStore } from "@/store";
import MkChart from "@/components/MkChart.vue";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import { $i } from "@/account";

View file

@ -335,44 +335,6 @@
</FormInput>
</FormSection>
</div>
<div v-else-if="tab === 'chart'" class="_formRoot">
<div class="cmhjzshm">
<div class="selects">
<MkSelect
v-model="chartSrc"
style="margin: 0 10px 0 0; flex: 1"
>
<option value="per-user-notes">
{{ i18n.ts.notes }}
</option>
</MkSelect>
</div>
<div class="charts">
<div class="label">
{{ i18n.t("recentNHours", { n: 90 }) }}
</div>
<MkChart
class="chart"
:src="chartSrc"
span="hour"
:limit="90"
:args="{ user, withoutAll: true }"
:detailed="true"
></MkChart>
<div class="label">
{{ i18n.t("recentNDays", { n: 90 }) }}
</div>
<MkChart
class="chart"
:src="chartSrc"
span="day"
:limit="90"
:args="{ user, withoutAll: true }"
:detailed="true"
></MkChart>
</div>
</div>
</div>
<div v-else-if="tab === 'raw'" class="_formRoot">
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
</MkObjectView>
@ -387,7 +349,6 @@
<script lang="ts" setup>
import { computed, ref, watch } from "vue";
import type * as misskey from "firefish-js";
import MkChart from "@/components/MkChart.vue";
import MkObjectView from "@/components/MkObjectView.vue";
import FormTextarea from "@/components/form/textarea.vue";
import FormSwitch from "@/components/form/switch.vue";
@ -415,7 +376,6 @@ const props = defineProps<{
}>();
const tab = ref("overview");
const chartSrc = ref("per-user-notes");
const user = ref<null | misskey.entities.UserDetailed>();
const init = ref<ReturnType<typeof createFetcher>>();
const info = ref();
@ -685,11 +645,6 @@ const headerTabs = computed(() =>
icon: "ph-shield ph-bold ph-lg",
}
: null,
{
key: "chart",
title: i18n.ts.charts,
icon: "ph-chart-bar ph-bold ph-lg",
},
{
key: "raw",
title: "Raw",
@ -783,13 +738,6 @@ definePageMetadata(
display: flex;
margin: 0 0 16px 0;
}
> .charts {
> .label {
margin-bottom: 12px;
font-weight: bold;
}
}
}
</style>

View file

@ -362,11 +362,6 @@
>
<template v-if="narrow">
<XPhotos :key="user.id" :user="user" />
<!-- <XActivity
:key="user.id"
:user="user"
style="margin-top: var(--margin)"
/> -->
</template>
</div>
<div>
@ -375,11 +370,6 @@
</div>
<div v-if="!narrow" class="sub">
<XPhotos :key="user.id" :user="user" />
<XActivity
:key="user.id"
:user="user"
style="margin-top: var(--margin)"
/>
</div>
</div>
</MkSpacer>
@ -413,7 +403,6 @@ import { $i } from "@/account";
import { host } from "@/config";
const XPhotos = defineAsyncComponent(() => import("./index.photos.vue"));
const XActivity = defineAsyncComponent(() => import("./index.activity.vue"));
const hideFollowButton = defaultStore.state.hideFollowButtons;
const emphasizeFollowed = defaultStore.state.emphasizeFollowed;

View file

@ -1,76 +0,0 @@
<template>
<MkContainer>
<template #header
><i
class="ph-chart-bar ph-bold ph-lg"
style="margin-right: 0.5em"
></i
>{{ i18n.ts.activity }}</template
>
<template #func>
<button class="_button" @click="showMenu">
<i class="ph-dots-three-outline ph-bold ph-lg"></i>
</button>
</template>
<div style="padding: 8px">
<MkChart
:src="chartSrc"
:args="{ user, withoutAll: true }"
span="day"
:limit="limit"
:bar="true"
:stacked="true"
:detailed="false"
:aspect-ratio="5"
/>
</div>
</MkContainer>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import type * as misskey from "firefish-js";
import MkContainer from "@/components/MkContainer.vue";
import MkChart from "@/components/MkChart.vue";
import * as os from "@/os";
import { i18n } from "@/i18n";
const props = withDefaults(
defineProps<{
user: misskey.entities.User;
limit?: number;
}>(),
{
limit: 50,
},
);
const chartSrc = ref("per-user-notes");
function showMenu(ev: MouseEvent) {
os.popupMenu(
[
{
text: i18n.ts.notes,
active: true,
action: () => {
chartSrc.value = "per-user-notes";
},
} /*, {
text: i18n.ts.following,
action: () => {
chartSrc = 'per-user-following';
}
}, {
text: i18n.ts.followers,
action: () => {
chartSrc = 'per-user-followers';
}
} */,
],
ev.currentTarget ?? ev.target,
);
}
</script>

View file

@ -1,102 +0,0 @@
<template>
<svg viewBox="0 0 21 7">
<rect
v-for="record in activity"
class="day"
width="1"
height="1"
:x="record.x"
:y="record.date.weekday"
rx="1"
ry="1"
fill="transparent"
>
<title>
{{ record.date.year }}/{{ record.date.month + 1 }}/{{
record.date.day
}}
</title>
</rect>
<rect
v-for="record in activity"
class="day"
:width="record.v"
:height="record.v"
:x="record.x + (1 - record.v) / 2"
:y="record.date.weekday + (1 - record.v) / 2"
rx="1"
ry="1"
:fill="record.color"
style="pointer-events: none"
/>
<rect
class="today"
width="1"
height="1"
:x="activity[0].x"
:y="activity[0].date.weekday"
rx="1"
ry="1"
fill="none"
stroke-width="0.1"
stroke="#eb6f92"
/>
</svg>
</template>
<script lang="ts" setup>
const props = defineProps<{
activity: any[];
}>();
for (const d of props.activity) {
d.total = d.notes + d.replies + d.renotes;
}
const peak = Math.max(...props.activity.map((d) => d.total));
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
const day = now.getDate();
let x = 20;
props.activity.slice().forEach((d, i) => {
d.x = x;
const date = new Date(year, month, day - i);
d.date = {
year: date.getFullYear(),
month: date.getMonth(),
day: date.getDate(),
weekday: date.getDay(),
};
d.v = peak === 0 ? 0 : d.total / (peak / 2);
if (d.v > 1) d.v = 1;
const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170;
const cs = d.v * 100;
const cl = 15 + (1 - d.v) * 80;
d.color = `hsl(${ch}, ${cs}%, ${cl}%)`;
if (d.date.weekday === 0) x--;
});
</script>
<style lang="scss" scoped>
svg {
display: block;
padding: 16px;
width: 100%;
box-sizing: border-box;
> rect {
transform-origin: center;
&.day {
&:hover {
fill: rgba(#000, 0.05);
}
}
}
}
</style>

View file

@ -1,136 +0,0 @@
<template>
<svg
:viewBox="`0 0 ${viewBoxX} ${viewBoxY}`"
@mousedown.prevent="onMousedown"
>
<polyline
:points="pointsNote"
fill="none"
stroke-width="1"
stroke="#c4a7e7"
/>
<polyline
:points="pointsReply"
fill="none"
stroke-width="1"
stroke="#eb6f92"
/>
<polyline
:points="pointsRenote"
fill="none"
stroke-width="1"
stroke="#ebbcba"
/>
<polyline
:points="pointsTotal"
fill="none"
stroke-width="1"
stroke="#6e6a86"
stroke-dasharray="2 2"
/>
</svg>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const props = defineProps<{
activity: any[];
}>();
const viewBoxX: number = ref(147);
const viewBoxY: number = ref(60);
const zoom: number = ref(1);
const pos: number = ref(0);
const pointsNote: any = ref(null);
const pointsReply: any = ref(null);
const pointsRenote: any = ref(null);
const pointsTotal: any = ref(null);
function dragListen(fn) {
window.addEventListener("mousemove", fn);
window.addEventListener("mouseleave", dragClear.bind(null, fn));
window.addEventListener("mouseup", dragClear.bind(null, fn));
}
function dragClear(fn) {
window.removeEventListener("mousemove", fn);
window.removeEventListener("mouseleave", dragClear);
window.removeEventListener("mouseup", dragClear);
}
function onMousedown(ev) {
const clickX = ev.clientX;
const clickY = ev.clientY;
const baseZoom = zoom.value;
const basePos = pos.value;
//
dragListen((me) => {
const moveLeft = me.clientX - clickX;
const moveTop = me.clientY - clickY;
zoom.value = Math.max(1, baseZoom + -moveTop / 20);
pos.value = Math.min(0, basePos + moveLeft);
if (
pos.value <
-((props.activity.length - 1) * zoom.value - viewBoxX.value)
)
pos.value = -(
(props.activity.length - 1) * zoom.value -
viewBoxX.value
);
render();
});
}
function render() {
const peak = Math.max(...props.activity.map((d) => d.total));
if (peak !== 0) {
const activity = props.activity.slice().reverse();
pointsNote.value = activity
.map(
(d, i) =>
`${i * zoom.value + pos.value},${
(1 - d.notes / peak) * viewBoxY.value
}`,
)
.join(" ");
pointsReply.value = activity
.map(
(d, i) =>
`${i * zoom.value + pos.value},${
(1 - d.replies / peak) * viewBoxY.value
}`,
)
.join(" ");
pointsRenote.value = activity
.map(
(d, i) =>
`${i * zoom.value + pos.value},${
(1 - d.renotes / peak) * viewBoxY.value
}`,
)
.join(" ");
pointsTotal.value = activity
.map(
(d, i) =>
`${i * zoom.value + pos.value},${
(1 - d.total / peak) * viewBoxY.value
}`,
)
.join(" ");
}
}
</script>
<style lang="scss" scoped>
svg {
display: block;
padding: 16px;
width: 100%;
box-sizing: border-box;
cursor: all-scroll;
}
</style>

View file

@ -1,124 +0,0 @@
<template>
<MkContainer
:show-header="widgetProps.showHeader"
:naked="widgetProps.transparent"
class="mkw-activity"
>
<template #header
><i class="ph-chart-bar ph-bold ph-lg"></i
>{{ i18n.ts._widgets.activity }}</template
>
<template #func
><button
v-if="!widgetProps.newStyle"
class="_button"
@click="toggleView()"
>
<i class="ph-sort-ascending ph-bold ph-lg"></i></button
></template>
<div v-if="widgetProps.newStyle">
<MkHeatmap src="my-notes" />
</div>
<div v-else>
<MkLoading v-if="fetching" />
<template v-else>
<XCalendar
v-show="widgetProps.view === 0"
:activity="[].concat(activity)"
/>
<XChart
v-show="widgetProps.view === 1"
:activity="[].concat(activity)"
/>
</template>
</div>
</MkContainer>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
import type { Widget, WidgetComponentExpose } from "./widget";
import {
WidgetComponentEmits,
WidgetComponentProps,
useWidgetPropsManager,
} from "./widget";
import XCalendar from "./activity.calendar.vue";
import XChart from "./activity.chart.vue";
import MkHeatmap from "@/components/MkHeatmap.vue";
import type { GetFormResultType } from "@/scripts/form";
import * as os from "@/os";
import MkContainer from "@/components/MkContainer.vue";
import { $i } from "@/account";
import { i18n } from "@/i18n";
const name = "activity";
const widgetPropsDef = {
newStyle: {
type: "boolean" as const,
default: true,
},
showHeader: {
type: "boolean" as const,
default: true,
},
transparent: {
type: "boolean" as const,
default: false,
},
view: {
type: "number" as const,
default: 0,
hidden: true,
},
};
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
// vueimporttype
// const props = defineProps<WidgetComponentProps<WidgetProps>>();
// const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
const props = defineProps<{ widget?: Widget<WidgetProps> }>();
const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
const { widgetProps, configure, save } = useWidgetPropsManager(
name,
widgetPropsDef,
props,
emit,
);
const activity = ref(null);
const fetching = ref(true);
const toggleView = () => {
if (widgetProps.view === 1) {
widgetProps.view = 0;
} else {
widgetProps.view++;
}
save();
};
os.apiGet("charts/user/notes", {
userId: $i.id,
span: "day",
limit: 7 * 21,
}).then((res) => {
activity.value = res.diffs.normal.map((_, i) => ({
total: res.diffs.normal[i] + res.diffs.reply[i] + res.diffs.renote[i],
notes: res.diffs.normal[i],
replies: res.diffs.reply[i],
renotes: res.diffs.renote[i],
}));
fetching.value = false;
});
defineExpose<WidgetComponentExpose>({
name,
configure,
id: props.widget ? props.widget.id : null,
});
</script>

View file

@ -37,10 +37,6 @@
{{ instance.softwareVersion }}
</p>
</div>
<MkMiniChart
class="chart"
:src="charts[i].requests.received"
/>
</div>
</transition-group>
</div>
@ -57,7 +53,6 @@ import {
} from "./widget";
import type { GetFormResultType } from "@/scripts/form";
import MkContainer from "@/components/MkContainer.vue";
import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os";
import { useInterval } from "@/scripts/use-interval";
import { i18n } from "@/i18n";
@ -92,7 +87,6 @@ const { widgetProps, configure } = useWidgetPropsManager(
);
const instances = ref([]);
const charts = ref([]);
const fetching = ref(true);
const fetch = async () => {
@ -100,17 +94,7 @@ const fetch = async () => {
sort: "+lastCommunicatedAt",
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;
};

View file

@ -34,10 +34,6 @@ export default function (app: App) {
"MkwClock",
defineAsyncComponent(() => import("./clock.vue")),
);
app.component(
"MkwActivity",
defineAsyncComponent(() => import("./activity.vue")),
);
app.component(
"MkwPhotos",
defineAsyncComponent(() => import("./photos.vue")),
@ -110,7 +106,6 @@ export const widgets = [
"rssTicker",
"trends",
"clock",
"activity",
"photos",
"digitalClock",
"unixClock",