mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-11-04 03:28:28 -05: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 }}
 | 
					 | 
				
			||||||
@ -10,6 +10,8 @@
 | 
				
			|||||||
[](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"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <template #activator="{ on, attrs }">
 | 
					 | 
				
			||||||
        <v-btn v-bind="attrs" icon v-on="on">
 | 
					 | 
				
			||||||
          <v-icon>{{ $globals.icons.user }}</v-icon>
 | 
					 | 
				
			||||||
      </v-btn>
 | 
					      </v-btn>
 | 
				
			||||||
    </template>
 | 
					    </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,8 +102,9 @@
 | 
				
			|||||||
    <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">
 | 
				
			||||||
 | 
					          <template v-for="nav in bottomLinks">
 | 
				
			||||||
            <v-list-item
 | 
					            <v-list-item
 | 
				
			||||||
            v-for="nav in bottomLinks"
 | 
					              v-if="!nav.restricted || $auth.loggedIn"
 | 
				
			||||||
              :key="nav.title"
 | 
					              :key="nav.title"
 | 
				
			||||||
              exact
 | 
					              exact
 | 
				
			||||||
              link
 | 
					              link
 | 
				
			||||||
@ -116,6 +117,7 @@
 | 
				
			|||||||
              </v-list-item-icon>
 | 
					              </v-list-item-icon>
 | 
				
			||||||
              <v-list-item-title>{{ nav.title }}</v-list-item-title>
 | 
					              <v-list-item-title>{{ nav.title }}</v-list-item-title>
 | 
				
			||||||
            </v-list-item>
 | 
					            </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