Show user and actors media usage in admin

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2020-11-23 16:58:50 +01:00
parent b11d35cbec
commit 2ef973000e
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
11 changed files with 52 additions and 11 deletions

View File

@ -10,6 +10,7 @@ export const FETCH_PERSON = gql`
summary
preferredUsername
suspended
mediaSize
avatar {
id
name
@ -51,6 +52,7 @@ export const GET_PERSON = gql`
summary
preferredUsername
suspended
mediaSize
avatar {
id
name

View File

@ -84,6 +84,7 @@ export const GROUP_FIELDS_FRAGMENTS = gql`
id
url
}
mediaSize
organizedEvents(
afterDatetime: $afterDateTime
beforeDatetime: $beforeDateTime

View File

@ -200,6 +200,7 @@ export const GET_USER = gql`
currentSignInAt
locale
disabled
mediaSize
defaultActor {
id
}

View File

@ -799,5 +799,6 @@
"Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want.": "Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want.",
"Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere.": "Mobilizon is a federated software, meaning you can interact - depending on your admin federation settings - with content from other instances, such as joining groups or events that were created elsewhere.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.",
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:"
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:",
"Uploaded media size": "Uploaded media size"
}

View File

@ -887,5 +887,6 @@
"Mobilizon uses a system of profiles to compartiment your activities. You will be able to create as many profiles as you want.": "Mobilizon utilise un système de profils pour compartimenter vos activités. Vous pourrez créer autant de profils que vous voulez.",
"Mobilizon is a federated software, meaning you can interact - depending on your admin's federation settings - with content from other instances, such as joining groups or events that were created elsewhere.": "Mobilizon est un logiciel fédéré, ce qui signifie que vous pouvez interagir - en fonction des paramètres de fédération de votre administrateur·ice - avec du contenu d'autres instances, comme par exemple rejoindre des groupes ou des événements ayant été créés ailleurs.",
"This instance, <b>{instanceName} ({domain})</b>, hosts your profile, so remember its name.": "Cette instance, <b>{instanceName} ({domain})</b>, héberge votre profil, donc notez bien son nom.",
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Si l'on vous demande votre identité fédérée, elle est composée de votre nom d'utilisateur·ice et de votre instance. Par exemple, l'identité fédérée de votre premier profil est :"
"If you are being asked for your federated indentity, it's composed of your username and your instance. For instance, the federated identity for your first profile is:": "Si l'on vous demande votre identité fédérée, elle est composée de votre nom d'utilisateur·ice et de votre instance. Par exemple, l'identité fédérée de votre premier profil est :",
"Uploaded media size": "Taille des médias téléversés"
}

View File

@ -13,6 +13,7 @@ export interface IActor {
url: string;
name: string;
domain: string | null;
mediaSize: number;
summary: string;
preferredUsername: string;
suspended: boolean;
@ -30,6 +31,8 @@ export class Actor implements IActor {
domain: string | null = null;
mediaSize = 0;
name = "";
preferredUsername = "";

View File

@ -39,6 +39,7 @@ export interface IUser extends ICurrentUser {
actors: IPerson[];
disabled: boolean;
participations: Paginate<IParticipant>;
mediaSize: number;
drafts: IEvent[];
settings: IUserSettings;
locale: string;

View File

@ -18,4 +18,17 @@ function localeShortWeekDayNames(): string[] {
return weekDayNames;
}
export { localeMonthNames, localeShortWeekDayNames };
// https://stackoverflow.com/a/18650828/10204399
function formatBytes(bytes: number, decimals = 2): string {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
}
export { localeMonthNames, localeShortWeekDayNames, formatBytes };

View File

@ -198,6 +198,7 @@
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { GET_GROUP, REFRESH_PROFILE } from "@/graphql/group";
import { formatBytes } from "@/utils/datetime";
import { SUSPEND_PROFILE, UNSUSPEND_PROFILE } from "../../graphql/actor";
import { IGroup, MemberRole } from "../../types/actor";
import { usernameWithDomain, IActor } from "../../types/actor/actor.model";
@ -258,6 +259,10 @@ export default class AdminGroupProfile extends Vue {
key: this.$t("Domain") as string,
value: (this.group.domain ? this.group.domain : this.$t("Local")) as string,
},
{
key: this.$i18n.t("Uploaded media size") as string,
value: formatBytes(this.group.mediaSize),
},
];
return res;
}

View File

@ -126,11 +126,11 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { formatBytes } from "@/utils/datetime";
import { GET_PERSON, SUSPEND_PROFILE, UNSUSPEND_PROFILE } from "../../graphql/actor";
import { IPerson } from "../../types/actor";
import { usernameWithDomain } from "../../types/actor/actor.model";
import RouteName from "../../router/name";
import { IEvent } from "../../types/event.model";
import ActorCard from "../../components/Account/ActorCard.vue";
const EVENTS_PER_PAGE = 10;
@ -171,9 +171,9 @@ export default class AdminProfile extends Vue {
participationsPage = 1;
get metadata(): Array<object> {
get metadata(): Array<Record<string, unknown>> {
if (!this.person) return [];
const res: object[] = [
const res: Record<string, unknown>[] = [
{
key: this.$t("Status") as string,
value: this.person.suspended ? this.$t("Suspended") : this.$t("Active"),
@ -182,6 +182,10 @@ export default class AdminProfile extends Vue {
key: this.$t("Domain") as string,
value: this.person.domain ? this.person.domain : this.$t("Local"),
},
{
key: this.$i18n.t("Uploaded media size"),
value: formatBytes(this.person.mediaSize),
},
];
if (!this.person.domain && this.person.user) {
res.push({
@ -193,7 +197,7 @@ export default class AdminProfile extends Vue {
return res;
}
async suspendProfile() {
async suspendProfile(): Promise<void> {
this.$apollo.mutate<{ suspendProfile: { id: string } }>({
mutation: SUSPEND_PROFILE,
variables: {
@ -229,7 +233,7 @@ export default class AdminProfile extends Vue {
});
}
async unsuspendProfile() {
async unsuspendProfile(): Promise<void> {
const profileID = this.id;
this.$apollo.mutate<{ unsuspendProfile: { id: string } }>({
mutation: UNSUSPEND_PROFILE,
@ -249,7 +253,7 @@ export default class AdminProfile extends Vue {
});
}
async onOrganizedEventsPageChange(page: number) {
async onOrganizedEventsPageChange(page: number): Promise<void> {
this.organizedEventsPage = page;
await this.$apollo.queries.person.fetchMore({
variables: {
@ -274,7 +278,7 @@ export default class AdminProfile extends Vue {
});
}
async onParticipationsPageChange(page: number) {
async onParticipationsPageChange(page: number): Promise<void> {
this.participationsPage = page;
await this.$apollo.queries.person.fetchMore({
variables: {

View File

@ -26,7 +26,7 @@
</nav>
<table v-if="metadata.length > 0" class="table is-fullwidth">
<tbody>
<tr v-for="{ key, value, link, elements } in metadata" :key="key">
<tr v-for="{ key, value, link, elements, type } in metadata" :key="key">
<td>{{ key }}</td>
<td v-if="elements && elements.length > 0">
<ul v-for="{ value, link: elementLink, active } in elements" :key="value">
@ -46,6 +46,9 @@
{{ value }}
</router-link>
</td>
<td v-else-if="type == 'code'">
<code>{{ value }}</code>
</td>
<td v-else>{{ value }}</td>
</tr>
</tbody>
@ -60,6 +63,7 @@
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { Route } from "vue-router";
import { formatBytes } from "@/utils/datetime";
import { GET_USER, SUSPEND_USER } from "../../graphql/user";
import { usernameWithDomain } from "../../types/actor/actor.model";
import RouteName from "../../router/name";
@ -139,11 +143,16 @@ export default class AdminUserProfile extends Vue {
{
key: this.$i18n.t("Last IP adress"),
value: this.user.currentSignInIp || this.$t("Unknown"),
type: "code",
},
{
key: this.$i18n.t("Participations"),
value: this.user.participations.total,
},
{
key: this.$i18n.t("Uploaded media size"),
value: formatBytes(this.user.mediaSize),
},
];
}