mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-04 13:05:21 +02:00
refactor(♻️): update 'about' page to new composition API (#667)
* test-commit * Remove PR Name Checker * refactor(backend): ♻️ split unrelated routes into clearer router paths Add an /app and /admin router base paths to split previously grouped public/admin data into different paths. Part of a longer migration to move 'admin' operations under the admin path. * refactor(backend): ♻️ rename imports * refactor(frontend): ♻️ refactor frontend API and Pages to refelect new API design Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
c7f8c96287
commit
abc0d0d59f
23 changed files with 317 additions and 275 deletions
37
frontend/api/class-interfaces/admin-about.ts
Normal file
37
frontend/api/class-interfaces/admin-about.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { BaseAPI } from "./_base";
|
||||
|
||||
const prefix = "/api";
|
||||
|
||||
const routes = {
|
||||
about: `${prefix}/admin/about`,
|
||||
aboutStatistics: `${prefix}/admin/about/statistics`,
|
||||
};
|
||||
|
||||
export interface AdminAboutInfo {
|
||||
production: boolean;
|
||||
version: string;
|
||||
demoStatus: boolean;
|
||||
apiPort: number;
|
||||
apiDocs: boolean;
|
||||
dbType: string;
|
||||
dbUrl: string;
|
||||
defaultGroup: string;
|
||||
}
|
||||
|
||||
export interface AdminStatistics {
|
||||
totalRecipes: number;
|
||||
totalUsers: number;
|
||||
totalGroups: number;
|
||||
uncategorizedRecipes: number;
|
||||
untaggedRecipes: number;
|
||||
}
|
||||
|
||||
export class AdminAboutAPI extends BaseAPI {
|
||||
async about() {
|
||||
return await this.requests.get<AdminAboutInfo>(routes.about);
|
||||
}
|
||||
|
||||
async statistics() {
|
||||
return await this.requests.get(routes.aboutStatistics);
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
import { BaseAPI } from "./_base";
|
||||
|
||||
export interface AppStatistics {
|
||||
totalRecipes: number;
|
||||
totalUsers: number;
|
||||
totalGroups: number;
|
||||
uncategorizedRecipes: number;
|
||||
untaggedRecipes: number;
|
||||
}
|
||||
|
||||
const prefix = "/api";
|
||||
|
||||
const routes = {
|
||||
debugVersion: `${prefix}/debug/version`,
|
||||
debug: `${prefix}/debug`,
|
||||
debugStatistics: `${prefix}/debug/statistics`,
|
||||
debugLastRecipeJson: `${prefix}/debug/last-recipe-json`,
|
||||
debugLog: `${prefix}/debug/log`,
|
||||
|
||||
debugLogNum: (num: number) => `${prefix}/debug/log/${num}`,
|
||||
};
|
||||
|
||||
export class DebugAPI extends BaseAPI {
|
||||
/** Returns the current version of mealie
|
||||
*/
|
||||
async getMealieVersion() {
|
||||
return await this.requests.get(routes.debugVersion);
|
||||
}
|
||||
|
||||
/** Returns general information about the application for debugging
|
||||
*/
|
||||
async getDebugInfo() {
|
||||
return await this.requests.get(routes.debug);
|
||||
}
|
||||
|
||||
async getAppStatistics() {
|
||||
return await this.requests.get<AppStatistics>(routes.debugStatistics);
|
||||
}
|
||||
|
||||
/** Doc Str
|
||||
*/
|
||||
async getLog(num: number) {
|
||||
return await this.requests.get(routes.debugLogNum(num));
|
||||
}
|
||||
|
||||
/** Returns a token to download a file
|
||||
*/
|
||||
async getLogFile() {
|
||||
return await this.requests.get(routes.debugLog);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { RecipeAPI } from "./class-interfaces/recipes";
|
||||
import { UserApi } from "./class-interfaces/users";
|
||||
import { GroupAPI } from "./class-interfaces/groups";
|
||||
import { DebugAPI } from "./class-interfaces/debug";
|
||||
import { EventsAPI } from "./class-interfaces/events";
|
||||
import { BackupAPI } from "./class-interfaces/backups";
|
||||
import { UploadFile } from "./class-interfaces/upload";
|
||||
|
@ -13,14 +12,30 @@ import { FoodAPI } from "./class-interfaces/recipe-foods";
|
|||
import { UnitAPI } from "./class-interfaces/recipe-units";
|
||||
import { CookbookAPI } from "./class-interfaces/cookbooks";
|
||||
import { WebhooksAPI } from "./class-interfaces/group-webhooks";
|
||||
import { AdminAboutAPI } from "./class-interfaces/admin-about";
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
|
||||
class AdminAPI {
|
||||
private static instance: AdminAPI;
|
||||
public about: AdminAboutAPI;
|
||||
|
||||
constructor(requests: ApiRequestInstance) {
|
||||
if (AdminAPI.instance instanceof AdminAPI) {
|
||||
return AdminAPI.instance;
|
||||
}
|
||||
|
||||
this.about = new AdminAboutAPI(requests);
|
||||
|
||||
Object.freeze(this);
|
||||
AdminAPI.instance = this;
|
||||
}
|
||||
}
|
||||
|
||||
class Api {
|
||||
private static instance: Api;
|
||||
public recipes: RecipeAPI;
|
||||
public users: UserApi;
|
||||
public groups: GroupAPI;
|
||||
public debug: DebugAPI;
|
||||
public events: EventsAPI;
|
||||
public backups: BackupAPI;
|
||||
public categories: CategoriesAPI;
|
||||
|
@ -54,7 +69,6 @@ class Api {
|
|||
this.groupWebhooks = new WebhooksAPI(requests);
|
||||
|
||||
// Admin
|
||||
this.debug = new DebugAPI(requests);
|
||||
this.events = new EventsAPI(requests);
|
||||
this.backups = new BackupAPI(requests);
|
||||
this.notifications = new NotificationsAPI(requests);
|
||||
|
@ -68,4 +82,4 @@ class Api {
|
|||
}
|
||||
}
|
||||
|
||||
export { Api };
|
||||
export { Api, AdminAPI };
|
||||
|
|
|
@ -30,42 +30,16 @@
|
|||
</div> -->
|
||||
|
||||
<!-- Navigation Menu -->
|
||||
<v-menu
|
||||
v-if="menu"
|
||||
transition="slide-x-transition"
|
||||
bottom
|
||||
right
|
||||
offset-y
|
||||
offset-overflow
|
||||
open-on-hover
|
||||
close-delay="200"
|
||||
>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn v-bind="attrs" icon v-on="on">
|
||||
<v-icon>{{ $globals.icons.user }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item-group v-model="itemSelected" color="primary">
|
||||
<v-list-item
|
||||
v-for="(item, i) in filteredItems"
|
||||
:key="i"
|
||||
link
|
||||
:to="item.nav ? item.nav : null"
|
||||
@click="item.logout ? $auth.logout() : null"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ item.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ item.title }}
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<template v-if="menu">
|
||||
<v-btn v-if="$auth.loggedIn" text @click="$auth.logout()">
|
||||
<v-icon left>{{ $globals.icons.logout }}</v-icon>
|
||||
{{ $t("user.logout") }}
|
||||
</v-btn>
|
||||
<v-btn v-else text nuxt to="/user/login">
|
||||
<v-icon left>{{ $globals.icons.user }}</v-icon>
|
||||
{{ $t("user.login") }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
</template>
|
||||
|
||||
|
@ -83,46 +57,6 @@ export default defineComponent({
|
|||
default: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
itemSelected: null,
|
||||
items: [
|
||||
{
|
||||
icon: this.$globals.icons.user,
|
||||
title: this.$t("user.login"),
|
||||
restricted: false,
|
||||
nav: "/user/login",
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.logout,
|
||||
title: this.$t("user.logout"),
|
||||
restricted: true,
|
||||
logout: true,
|
||||
},
|
||||
{
|
||||
icon: this.$globals.icons.cog,
|
||||
title: this.$t("general.settings"),
|
||||
nav: "/user/profile",
|
||||
restricted: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredItems(): Array<any> {
|
||||
if (this.loggedIn) {
|
||||
return this.items.filter((x) => x.restricted === true);
|
||||
} else {
|
||||
return this.items.filter((x) => x.restricted === false);
|
||||
}
|
||||
},
|
||||
loggedIn(): Boolean {
|
||||
return this.$auth.loggedIn;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -102,20 +102,22 @@
|
|||
<template v-if="bottomLinks">
|
||||
<v-list class="fixedBottom" nav dense>
|
||||
<v-list-item-group v-model="bottomSelected" color="primary">
|
||||
<v-list-item
|
||||
v-for="nav in bottomLinks"
|
||||
:key="nav.title"
|
||||
exact
|
||||
link
|
||||
:to="nav.to || null"
|
||||
:href="nav.href || null"
|
||||
:target="nav.href ? '_blank' : null"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ nav.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<template v-for="nav in bottomLinks">
|
||||
<v-list-item
|
||||
v-if="!nav.restricted || $auth.loggedIn"
|
||||
:key="nav.title"
|
||||
exact
|
||||
link
|
||||
:to="nav.to || null"
|
||||
:href="nav.href || null"
|
||||
:target="nav.href ? '_blank' : null"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon>{{ nav.icon }}</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AxiosResponse } from "axios";
|
||||
import { useContext } from "@nuxtjs/composition-api";
|
||||
import { NuxtAxiosInstance } from "@nuxtjs/axios";
|
||||
import { Api } from "~/api";
|
||||
import { AdminAPI, Api } from "~/api";
|
||||
import { ApiRequestInstance } from "~/types/api";
|
||||
|
||||
interface RequestResponse<T> {
|
||||
|
@ -53,6 +53,11 @@ function getRequests(axoisInstance: NuxtAxiosInstance): ApiRequestInstance {
|
|||
return requests;
|
||||
}
|
||||
|
||||
export const useAdminApi = function (): AdminAPI {
|
||||
const { $axios } = useContext();
|
||||
const requests = getRequests($axios);
|
||||
return new AdminAPI(requests);
|
||||
};
|
||||
|
||||
export const useApiSingleton = function (): Api {
|
||||
const { $axios } = useContext();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
:top-link="topLinks"
|
||||
secondary-header="Cookbooks"
|
||||
:secondary-links="cookbookLinks || []"
|
||||
:bottom-links="bottomLink"
|
||||
@input="sidebar = !sidebar"
|
||||
/>
|
||||
|
||||
|
@ -57,6 +58,14 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
sidebar: null,
|
||||
bottomLink: [
|
||||
{
|
||||
icon: this.$globals.icons.cog,
|
||||
title: this.$t("general.settings"),
|
||||
to: "/user/profile",
|
||||
restricted: true,
|
||||
},
|
||||
],
|
||||
topLinks: [
|
||||
{
|
||||
icon: this.$globals.icons.calendar,
|
||||
|
|
|
@ -1,16 +1,103 @@
|
|||
<template>
|
||||
<v-container fluid>
|
||||
<BaseCardSectionTitle title="About Mealie"> </BaseCardSectionTitle>
|
||||
<v-card class="mt-3">
|
||||
<v-card-title class="headline">
|
||||
{{ $t("about.about-mealie") }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>
|
||||
<v-list-item-group color="primary">
|
||||
<v-list-item v-for="property in appInfo" :key="property.name">
|
||||
<v-list-item-icon>
|
||||
<v-icon> {{ property.icon || $globals.icons.user }} </v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="pl-4 flex row justify-space-between">
|
||||
<div>{{ property.name }}</div>
|
||||
<div>{{ property.value }}</div>
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { defineComponent, useAsync, useContext } from "@nuxtjs/composition-api";
|
||||
import { useAdminApi } from "~/composables/use-api";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
|
||||
export default defineComponent({
|
||||
layout: "admin",
|
||||
setup() {
|
||||
return {};
|
||||
const adminApi = useAdminApi();
|
||||
// @ts-ignore
|
||||
const { $globals, i18n } = useContext();
|
||||
|
||||
function getAppInfo() {
|
||||
const statistics = useAsync(async () => {
|
||||
const { data } = await adminApi.about.about();
|
||||
|
||||
if (data) {
|
||||
const prettyInfo = [
|
||||
{
|
||||
name: i18n.t("about.version"),
|
||||
icon: $globals.icons.information,
|
||||
value: data.version,
|
||||
},
|
||||
{
|
||||
name: i18n.t("about.application-mode"),
|
||||
icon: $globals.icons.devTo,
|
||||
value: data.production ? i18n.t("about.production") : i18n.t("about.development"),
|
||||
},
|
||||
{
|
||||
name: i18n.t("about.demo-status"),
|
||||
icon: $globals.icons.testTube,
|
||||
value: data.demoStatus ? i18n.t("about.demo") : i18n.t("about.not-demo"),
|
||||
},
|
||||
{
|
||||
name: i18n.t("about.api-port"),
|
||||
icon: $globals.icons.api,
|
||||
value: data.apiPort,
|
||||
},
|
||||
{
|
||||
name: i18n.t("about.api-docs"),
|
||||
icon: $globals.icons.file,
|
||||
value: data.apiDocs ? i18n.t("general.enabled") : i18n.t("general.disabled"),
|
||||
},
|
||||
{
|
||||
name: i18n.t("about.database-type"),
|
||||
icon: $globals.icons.database,
|
||||
value: data.dbType,
|
||||
},
|
||||
{
|
||||
name: i18n.t("about.database-url"),
|
||||
icon: $globals.icons.database,
|
||||
value: data.dbUrl,
|
||||
},
|
||||
{
|
||||
name: i18n.t("about.default-group"),
|
||||
icon: $globals.icons.group,
|
||||
value: data.defaultGroup,
|
||||
},
|
||||
];
|
||||
|
||||
return prettyInfo;
|
||||
}
|
||||
|
||||
return data;
|
||||
}, useAsyncKey());
|
||||
|
||||
return statistics;
|
||||
}
|
||||
|
||||
const appInfo = getAppInfo();
|
||||
|
||||
return {
|
||||
appInfo,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, useAsync } from "@nuxtjs/composition-api";
|
||||
import AdminEventViewer from "@/components/Domain/Admin/AdminEventViewer.vue";
|
||||
import { useApiSingleton } from "~/composables/use-api";
|
||||
import { useAdminApi, useApiSingleton } from "~/composables/use-api";
|
||||
import { useAsyncKey } from "~/composables/use-utils";
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -103,9 +103,11 @@ export default defineComponent({
|
|||
setup() {
|
||||
const api = useApiSingleton();
|
||||
|
||||
const adminApi = useAdminApi();
|
||||
|
||||
function getStatistics() {
|
||||
const statistics = useAsync(async () => {
|
||||
const { data } = await api.debug.getAppStatistics();
|
||||
const { data } = await adminApi.about.statistics();
|
||||
return data;
|
||||
}, useAsyncKey());
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue