mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-05-30 19:54:44 -04: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
.github/workflows/convenitonal-commits.yml
vendored
23
.github/workflows/convenitonal-commits.yml
vendored
@ -1,23 +0,0 @@
|
|||||||
name: Check PR title
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- mealie-next
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- reopened
|
|
||||||
- edited
|
|
||||||
- synchronize
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: aslafy-z/conventional-pr-title-action@master
|
|
||||||
with:
|
|
||||||
success-state: Title follows the specification.
|
|
||||||
failure-state: Title does not follow the specification.
|
|
||||||
context-name: conventional-pr-title
|
|
||||||
preset: conventional-changelog-angular@latest
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -9,7 +9,9 @@
|
|||||||
[](https://github.com/hay-kot/mealie/actions/workflows/test-all.yml)
|
[](https://github.com/hay-kot/mealie/actions/workflows/test-all.yml)
|
||||||
[](https://github.com/hay-kot/mealie/actions/workflows/dockerbuild.dev.yml)
|
[](https://github.com/hay-kot/mealie/actions/workflows/dockerbuild.dev.yml)
|
||||||
[](https://github.com/hay-kot/mealie/actions/workflows/test-all.yml)
|
[](https://github.com/hay-kot/mealie/actions/workflows/test-all.yml)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- PROJECT LOGO -->
|
<!-- PROJECT LOGO -->
|
||||||
<br />
|
<br />
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
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 { RecipeAPI } from "./class-interfaces/recipes";
|
||||||
import { UserApi } from "./class-interfaces/users";
|
import { UserApi } from "./class-interfaces/users";
|
||||||
import { GroupAPI } from "./class-interfaces/groups";
|
import { GroupAPI } from "./class-interfaces/groups";
|
||||||
import { DebugAPI } from "./class-interfaces/debug";
|
|
||||||
import { EventsAPI } from "./class-interfaces/events";
|
import { EventsAPI } from "./class-interfaces/events";
|
||||||
import { BackupAPI } from "./class-interfaces/backups";
|
import { BackupAPI } from "./class-interfaces/backups";
|
||||||
import { UploadFile } from "./class-interfaces/upload";
|
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 { UnitAPI } from "./class-interfaces/recipe-units";
|
||||||
import { CookbookAPI } from "./class-interfaces/cookbooks";
|
import { CookbookAPI } from "./class-interfaces/cookbooks";
|
||||||
import { WebhooksAPI } from "./class-interfaces/group-webhooks";
|
import { WebhooksAPI } from "./class-interfaces/group-webhooks";
|
||||||
|
import { AdminAboutAPI } from "./class-interfaces/admin-about";
|
||||||
import { ApiRequestInstance } from "~/types/api";
|
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 {
|
class Api {
|
||||||
private static instance: Api;
|
private static instance: Api;
|
||||||
public recipes: RecipeAPI;
|
public recipes: RecipeAPI;
|
||||||
public users: UserApi;
|
public users: UserApi;
|
||||||
public groups: GroupAPI;
|
public groups: GroupAPI;
|
||||||
public debug: DebugAPI;
|
|
||||||
public events: EventsAPI;
|
public events: EventsAPI;
|
||||||
public backups: BackupAPI;
|
public backups: BackupAPI;
|
||||||
public categories: CategoriesAPI;
|
public categories: CategoriesAPI;
|
||||||
@ -54,7 +69,6 @@ class Api {
|
|||||||
this.groupWebhooks = new WebhooksAPI(requests);
|
this.groupWebhooks = new WebhooksAPI(requests);
|
||||||
|
|
||||||
// Admin
|
// Admin
|
||||||
this.debug = new DebugAPI(requests);
|
|
||||||
this.events = new EventsAPI(requests);
|
this.events = new EventsAPI(requests);
|
||||||
this.backups = new BackupAPI(requests);
|
this.backups = new BackupAPI(requests);
|
||||||
this.notifications = new NotificationsAPI(requests);
|
this.notifications = new NotificationsAPI(requests);
|
||||||
@ -68,4 +82,4 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Api };
|
export { Api, AdminAPI };
|
||||||
|
@ -30,42 +30,16 @@
|
|||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<!-- Navigation Menu -->
|
<!-- Navigation Menu -->
|
||||||
<v-menu
|
<template v-if="menu">
|
||||||
v-if="menu"
|
<v-btn v-if="$auth.loggedIn" text @click="$auth.logout()">
|
||||||
transition="slide-x-transition"
|
<v-icon left>{{ $globals.icons.logout }}</v-icon>
|
||||||
bottom
|
{{ $t("user.logout") }}
|
||||||
right
|
</v-btn>
|
||||||
offset-y
|
<v-btn v-else text nuxt to="/user/login">
|
||||||
offset-overflow
|
<v-icon left>{{ $globals.icons.user }}</v-icon>
|
||||||
open-on-hover
|
{{ $t("user.login") }}
|
||||||
close-delay="200"
|
</v-btn>
|
||||||
>
|
</template>
|
||||||
<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>
|
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -83,46 +57,6 @@ export default defineComponent({
|
|||||||
default: true,
|
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>
|
</script>
|
||||||
|
|
@ -102,20 +102,22 @@
|
|||||||
<template v-if="bottomLinks">
|
<template v-if="bottomLinks">
|
||||||
<v-list class="fixedBottom" nav dense>
|
<v-list class="fixedBottom" nav dense>
|
||||||
<v-list-item-group v-model="bottomSelected" color="primary">
|
<v-list-item-group v-model="bottomSelected" color="primary">
|
||||||
<v-list-item
|
<template v-for="nav in bottomLinks">
|
||||||
v-for="nav in bottomLinks"
|
<v-list-item
|
||||||
:key="nav.title"
|
v-if="!nav.restricted || $auth.loggedIn"
|
||||||
exact
|
:key="nav.title"
|
||||||
link
|
exact
|
||||||
:to="nav.to || null"
|
link
|
||||||
:href="nav.href || null"
|
:to="nav.to || null"
|
||||||
:target="nav.href ? '_blank' : 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-icon>
|
<v-icon>{{ nav.icon }}</v-icon>
|
||||||
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
</v-list-item-icon>
|
||||||
</v-list-item>
|
<v-list-item-title>{{ nav.title }}</v-list-item-title>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
</v-list-item-group>
|
</v-list-item-group>
|
||||||
</v-list>
|
</v-list>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { useContext } from "@nuxtjs/composition-api";
|
import { useContext } from "@nuxtjs/composition-api";
|
||||||
import { NuxtAxiosInstance } from "@nuxtjs/axios";
|
import { NuxtAxiosInstance } from "@nuxtjs/axios";
|
||||||
import { Api } from "~/api";
|
import { AdminAPI, Api } from "~/api";
|
||||||
import { ApiRequestInstance } from "~/types/api";
|
import { ApiRequestInstance } from "~/types/api";
|
||||||
|
|
||||||
interface RequestResponse<T> {
|
interface RequestResponse<T> {
|
||||||
@ -53,6 +53,11 @@ function getRequests(axoisInstance: NuxtAxiosInstance): ApiRequestInstance {
|
|||||||
return requests;
|
return requests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useAdminApi = function (): AdminAPI {
|
||||||
|
const { $axios } = useContext();
|
||||||
|
const requests = getRequests($axios);
|
||||||
|
return new AdminAPI(requests);
|
||||||
|
};
|
||||||
|
|
||||||
export const useApiSingleton = function (): Api {
|
export const useApiSingleton = function (): Api {
|
||||||
const { $axios } = useContext();
|
const { $axios } = useContext();
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
:top-link="topLinks"
|
:top-link="topLinks"
|
||||||
secondary-header="Cookbooks"
|
secondary-header="Cookbooks"
|
||||||
:secondary-links="cookbookLinks || []"
|
:secondary-links="cookbookLinks || []"
|
||||||
|
:bottom-links="bottomLink"
|
||||||
@input="sidebar = !sidebar"
|
@input="sidebar = !sidebar"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -57,6 +58,14 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
sidebar: null,
|
sidebar: null,
|
||||||
|
bottomLink: [
|
||||||
|
{
|
||||||
|
icon: this.$globals.icons.cog,
|
||||||
|
title: this.$t("general.settings"),
|
||||||
|
to: "/user/profile",
|
||||||
|
restricted: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
topLinks: [
|
topLinks: [
|
||||||
{
|
{
|
||||||
icon: this.$globals.icons.calendar,
|
icon: this.$globals.icons.calendar,
|
||||||
|
@ -1,16 +1,103 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<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>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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({
|
export default defineComponent({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
setup() {
|
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>
|
</script>
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, useAsync } from "@nuxtjs/composition-api";
|
import { defineComponent, useAsync } from "@nuxtjs/composition-api";
|
||||||
import AdminEventViewer from "@/components/Domain/Admin/AdminEventViewer.vue";
|
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";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@ -103,9 +103,11 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const api = useApiSingleton();
|
const api = useApiSingleton();
|
||||||
|
|
||||||
|
const adminApi = useAdminApi();
|
||||||
|
|
||||||
function getStatistics() {
|
function getStatistics() {
|
||||||
const statistics = useAsync(async () => {
|
const statistics = useAsync(async () => {
|
||||||
const { data } = await api.debug.getAppStatistics();
|
const { data } = await adminApi.about.statistics();
|
||||||
return data;
|
return data;
|
||||||
}, useAsyncKey());
|
}, useAsyncKey());
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from fastapi.middleware.gzip import GZipMiddleware
|
|||||||
|
|
||||||
from mealie.core.config import APP_VERSION, settings
|
from mealie.core.config import APP_VERSION, settings
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
from mealie.routes import backup_routes, debug_routes, migration_routes, router, utility_routes
|
from mealie.routes import backup_routes, migration_routes, router, utility_routes
|
||||||
from mealie.routes.about import about_router
|
from mealie.routes.about import about_router
|
||||||
from mealie.routes.mealplans import meal_plan_router
|
from mealie.routes.mealplans import meal_plan_router
|
||||||
from mealie.routes.media import media_router
|
from mealie.routes.media import media_router
|
||||||
@ -45,9 +45,6 @@ def api_routers():
|
|||||||
# Migration Routes
|
# Migration Routes
|
||||||
app.include_router(migration_routes.router)
|
app.include_router(migration_routes.router)
|
||||||
# Debug routes
|
# Debug routes
|
||||||
app.include_router(debug_routes.public_router)
|
|
||||||
app.include_router(debug_routes.admin_router)
|
|
||||||
# Utility routes
|
|
||||||
app.include_router(utility_routes.router)
|
app.include_router(utility_routes.router)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import auth, categories, groups, recipe, shopping_lists, tags, unit_and_foods, users
|
from . import admin, app, auth, categories, groups, recipe, shopping_lists, tags, unit_and_foods, users
|
||||||
|
|
||||||
router = APIRouter(prefix="/api")
|
router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
|
router.include_router(app.router)
|
||||||
router.include_router(auth.router)
|
router.include_router(auth.router)
|
||||||
router.include_router(users.router)
|
router.include_router(users.router)
|
||||||
router.include_router(groups.router)
|
router.include_router(groups.router)
|
||||||
@ -11,5 +12,5 @@ router.include_router(recipe.router)
|
|||||||
router.include_router(unit_and_foods.router)
|
router.include_router(unit_and_foods.router)
|
||||||
router.include_router(categories.router)
|
router.include_router(categories.router)
|
||||||
router.include_router(tags.router)
|
router.include_router(tags.router)
|
||||||
|
|
||||||
router.include_router(shopping_lists.router)
|
router.include_router(shopping_lists.router)
|
||||||
|
router.include_router(admin.router)
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import defaults, events, notifications
|
from . import events, notifications
|
||||||
|
|
||||||
about_router = APIRouter(prefix="/api/about")
|
about_router = APIRouter(prefix="/api/about")
|
||||||
|
|
||||||
about_router.include_router(events.router, tags=["Events: CRUD"])
|
about_router.include_router(events.router, tags=["Events: CRUD"])
|
||||||
about_router.include_router(notifications.router, tags=["Events: Notifications"])
|
about_router.include_router(notifications.router, tags=["Events: Notifications"])
|
||||||
about_router.include_router(defaults.router, tags=["Recipe: Defaults"])
|
|
||||||
|
8
mealie/routes/admin/__init__.py
Normal file
8
mealie/routes/admin/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from . import admin_about, admin_log
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/admin")
|
||||||
|
|
||||||
|
router.include_router(admin_about.router, tags=["Admin: About"])
|
||||||
|
router.include_router(admin_log.router, tags=["Admin: Log"])
|
38
mealie/routes/admin/admin_about.py
Normal file
38
mealie/routes/admin/admin_about.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
from mealie.core.config import APP_VERSION, get_settings
|
||||||
|
from mealie.db.database import get_database
|
||||||
|
from mealie.db.db_setup import generate_session
|
||||||
|
from mealie.schema.admin.about import AdminAboutInfo, AppStatistics
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/about")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("", response_model=AdminAboutInfo)
|
||||||
|
async def get_app_info():
|
||||||
|
""" Get general application information """
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
|
return AdminAboutInfo(
|
||||||
|
production=settings.PRODUCTION,
|
||||||
|
version=APP_VERSION,
|
||||||
|
demo_status=settings.IS_DEMO,
|
||||||
|
api_port=settings.API_PORT,
|
||||||
|
api_docs=settings.API_DOCS,
|
||||||
|
db_type=settings.DB_ENGINE,
|
||||||
|
db_url=settings.DB_URL_PUBLIC,
|
||||||
|
default_group=settings.DEFAULT_GROUP,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/statistics", response_model=AppStatistics)
|
||||||
|
async def get_app_statistics(session: Session = Depends(generate_session)):
|
||||||
|
db = get_database()
|
||||||
|
return AppStatistics(
|
||||||
|
total_recipes=db.recipes.count_all(session),
|
||||||
|
uncategorized_recipes=db.recipes.count_uncategorized(session),
|
||||||
|
untagged_recipes=db.recipes.count_untagged(session),
|
||||||
|
total_users=db.users.count_all(session),
|
||||||
|
total_groups=db.groups.count_all(session),
|
||||||
|
)
|
44
mealie/routes/admin/admin_log.py
Normal file
44
mealie/routes/admin/admin_log.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from mealie.core.root_logger import LOGGER_FILE
|
||||||
|
from mealie.core.security import create_file_token
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/logs")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{num}")
|
||||||
|
async def get_log(num: int):
|
||||||
|
""" Doc Str """
|
||||||
|
with open(LOGGER_FILE, "rb") as f:
|
||||||
|
log_text = tail(f, num)
|
||||||
|
return log_text
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
|
async def get_log_file():
|
||||||
|
""" Returns a token to download a file """
|
||||||
|
return {"fileToken": create_file_token(LOGGER_FILE)}
|
||||||
|
|
||||||
|
|
||||||
|
def tail(f, lines=20):
|
||||||
|
total_lines_wanted = lines
|
||||||
|
|
||||||
|
BLOCK_SIZE = 1024
|
||||||
|
f.seek(0, 2)
|
||||||
|
block_end_byte = f.tell()
|
||||||
|
lines_to_go = total_lines_wanted
|
||||||
|
block_number = -1
|
||||||
|
blocks = []
|
||||||
|
while lines_to_go > 0 and block_end_byte > 0:
|
||||||
|
if block_end_byte - BLOCK_SIZE > 0:
|
||||||
|
f.seek(block_number * BLOCK_SIZE, 2)
|
||||||
|
blocks.append(f.read(BLOCK_SIZE))
|
||||||
|
else:
|
||||||
|
f.seek(0, 0)
|
||||||
|
blocks.append(f.read(block_end_byte))
|
||||||
|
lines_found = blocks[-1].count(b"\n")
|
||||||
|
lines_to_go -= lines_found
|
||||||
|
block_end_byte -= BLOCK_SIZE
|
||||||
|
block_number -= 1
|
||||||
|
all_read_text = b"".join(reversed(blocks))
|
||||||
|
return b"/n".join(all_read_text.splitlines()[-total_lines_wanted:])
|
8
mealie/routes/app/__init__.py
Normal file
8
mealie/routes/app/__init__.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from . import app_about, app_defaults
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/app")
|
||||||
|
|
||||||
|
router.include_router(app_about.router, tags=["App: About"])
|
||||||
|
router.include_router(app_defaults.router, tags=["App: Defaults"])
|
18
mealie/routes/app/app_about.py
Normal file
18
mealie/routes/app/app_about.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from mealie.core.config import APP_VERSION, get_settings
|
||||||
|
from mealie.schema.admin.about import AppInfo
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/about")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("", response_model=AppInfo)
|
||||||
|
async def get_app_info():
|
||||||
|
""" Get general application information """
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
|
return AppInfo(
|
||||||
|
version=APP_VERSION,
|
||||||
|
demo_status=settings.IS_DEMO,
|
||||||
|
production=settings.PRODUCTION,
|
||||||
|
)
|
@ -1,11 +1,11 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from mealie.schema.recipe import RecipeSettings
|
from mealie.schema.recipe.recipe_settings import RecipeSettings
|
||||||
|
|
||||||
router = APIRouter(prefix="/recipes")
|
router = APIRouter(prefix="/defaults")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/defaults")
|
@router.get("/recipe", response_model=RecipeSettings)
|
||||||
async def get_recipe_settings_defaults():
|
async def get_recipe_settings_defaults():
|
||||||
""" Returns the Default Settings for Recieps as set by ENV variables """
|
""" Returns the Default Settings for Recieps as set by ENV variables """
|
||||||
|
|
@ -1,89 +0,0 @@
|
|||||||
from fastapi import Depends
|
|
||||||
from fastapi.routing import APIRouter
|
|
||||||
from sqlalchemy.orm.session import Session
|
|
||||||
|
|
||||||
from mealie.core.config import APP_VERSION, settings
|
|
||||||
from mealie.core.root_logger import LOGGER_FILE
|
|
||||||
from mealie.core.security import create_file_token
|
|
||||||
from mealie.db.database import db
|
|
||||||
from mealie.db.db_setup import generate_session
|
|
||||||
from mealie.routes.routers import AdminAPIRouter
|
|
||||||
from mealie.schema.admin import AppInfo, AppStatistics, DebugInfo
|
|
||||||
|
|
||||||
admin_router = AdminAPIRouter(prefix="/api/debug", tags=["Debug"])
|
|
||||||
public_router = APIRouter(prefix="/api/debug", tags=["Debug"])
|
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("")
|
|
||||||
async def get_debug_info():
|
|
||||||
""" Returns general information about the application for debugging """
|
|
||||||
|
|
||||||
return DebugInfo(
|
|
||||||
production=settings.PRODUCTION,
|
|
||||||
version=APP_VERSION,
|
|
||||||
demo_status=settings.IS_DEMO,
|
|
||||||
api_port=settings.API_PORT,
|
|
||||||
api_docs=settings.API_DOCS,
|
|
||||||
db_type=settings.DB_ENGINE,
|
|
||||||
db_url=settings.DB_URL_PUBLIC,
|
|
||||||
default_group=settings.DEFAULT_GROUP,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/statistics")
|
|
||||||
async def get_app_statistics(session: Session = Depends(generate_session)):
|
|
||||||
return AppStatistics(
|
|
||||||
total_recipes=db.recipes.count_all(session),
|
|
||||||
uncategorized_recipes=db.recipes.count_uncategorized(session),
|
|
||||||
untagged_recipes=db.recipes.count_untagged(session),
|
|
||||||
total_users=db.users.count_all(session),
|
|
||||||
total_groups=db.groups.count_all(session),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@public_router.get("/version")
|
|
||||||
async def get_mealie_version():
|
|
||||||
""" Returns the current version of mealie"""
|
|
||||||
return AppInfo(
|
|
||||||
version=APP_VERSION,
|
|
||||||
demo_status=settings.IS_DEMO,
|
|
||||||
production=settings.PRODUCTION,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/log/{num}")
|
|
||||||
async def get_log(num: int):
|
|
||||||
""" Doc Str """
|
|
||||||
with open(LOGGER_FILE, "rb") as f:
|
|
||||||
log_text = tail(f, num)
|
|
||||||
return log_text
|
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/log")
|
|
||||||
async def get_log_file():
|
|
||||||
""" Returns a token to download a file """
|
|
||||||
return {"fileToken": create_file_token(LOGGER_FILE)}
|
|
||||||
|
|
||||||
|
|
||||||
def tail(f, lines=20):
|
|
||||||
total_lines_wanted = lines
|
|
||||||
|
|
||||||
BLOCK_SIZE = 1024
|
|
||||||
f.seek(0, 2)
|
|
||||||
block_end_byte = f.tell()
|
|
||||||
lines_to_go = total_lines_wanted
|
|
||||||
block_number = -1
|
|
||||||
blocks = []
|
|
||||||
while lines_to_go > 0 and block_end_byte > 0:
|
|
||||||
if block_end_byte - BLOCK_SIZE > 0:
|
|
||||||
f.seek(block_number * BLOCK_SIZE, 2)
|
|
||||||
blocks.append(f.read(BLOCK_SIZE))
|
|
||||||
else:
|
|
||||||
f.seek(0, 0)
|
|
||||||
blocks.append(f.read(block_end_byte))
|
|
||||||
lines_found = blocks[-1].count(b"\n")
|
|
||||||
lines_to_go -= lines_found
|
|
||||||
block_end_byte -= BLOCK_SIZE
|
|
||||||
block_number -= 1
|
|
||||||
all_read_text = b"".join(reversed(blocks))
|
|
||||||
return b"/n".join(all_read_text.splitlines()[-total_lines_wanted:])
|
|
@ -17,7 +17,7 @@ class AppInfo(CamelModel):
|
|||||||
demo_status: bool
|
demo_status: bool
|
||||||
|
|
||||||
|
|
||||||
class DebugInfo(AppInfo):
|
class AdminAboutInfo(AppInfo):
|
||||||
api_port: int
|
api_port: int
|
||||||
api_docs: bool
|
api_docs: bool
|
||||||
db_type: str
|
db_type: str
|
||||||
|
Loading…
x
Reference in New Issue
Block a user