/
githubmirror
/
photoprism
Обзор
Документация
Войти
/
githubmirror
/
photoprism
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/src/page/library/errors.vue
384 строки
11 KB
Michael Mayer
Frontend: Enforce braced if-bodies via ESLint curly rule #4966
16 май 2026, 10:40
16 май 2026, 10:40
bdd33df
Код
Авторство
О чём код?
<template> <div ref="page" tabindex="-1" class="p-page p-page-errors"> <v-form ref="form" validate-on="invalid-input" class="p-errors-search p-page__navigation" @submit.prevent="updateQuery()"> <v-toolbar flat :density="$vuetify.display.smAndDown ? 'compact' : 'default'" class="page-toolbar" color="secondary"> <v-text-field :model-value="filter.q" hide-details clearable overflow single-line rounded variant="solo-filled" :density="density" validate-on="invalid-input" autocomplete="off" autocorrect="off" autocapitalize="none" :placeholder="$gettext('Search')" prepend-inner-icon="mdi-magnify" color="surface-variant" class="input-search input-search--focus background-inherit elevation-0" @update:model-value=" (v) => { updateFilter({ q: v }); } " @keyup.enter="() => updateQuery()" @click:clear=" () => { updateQuery({ q: '' }); } " ></v-text-field> <v-btn v-if="!isPublic" :title="$gettext('Delete All')" icon="mdi-delete-sweep" class="action-delete action-delete-all ms-1" @click.stop="onDelete"> </v-btn> <p-action-menu v-if="$vuetify.display.mdAndUp" :items="menuActions" button-class="ms-1"></p-action-menu> </v-toolbar> </v-form> <div v-if="loading" class="p-page__loading"> <p-loading></p-loading> </div> <div v-else-if="errors.length > 0" fluid class="pa-0"> <p-scroll :load-more="loadMore" :load-disabled="scrollDisabled" :load-distance="scrollDistance" :loading="loading"></p-scroll> <v-list lines="one" bg-color="table" density="compact" class="py-0"> <v-list-item v-for="err in errors" :key="err.ID" :prepend-icon="err.Level === 'error' ? 'mdi-alert-circle-outline' : 'mdi-alert'" density="default" :title="err.Message" :subtitle="localTime(err.Time)" class="py-2" @click="showDetails(err)" > <template #prepend> <v-icon v-if="err.Level === 'error'" icon="mdi-alert-circle-outline" color="error"></v-icon> <v-icon v-else-if="err.Level === 'warning'" icon="mdi-alert" color="warning"></v-icon> <v-icon v-else icon="mdi-information-outline" color="info"></v-icon> </template> <template #title="{ title }"> <div class="text-body-2 text-truncate">{{ title }}</div> </template> </v-list-item> </v-list> </div> <div v-else class="pa-3"> <v-alert color="primary" icon="mdi-check-circle-outline" class="no-results" variant="outlined"> <div v-if="filter.q"> {{ $gettext(`No warnings or error containing this keyword. Note that search is case-sensitive.`) }} </div> <div v-else> {{ $gettext(`Log messages appear here whenever PhotoPrism comes across broken files, or there are other potential issues.`) }} </div> </v-alert> </div> <p-confirm-dialog :visible="dialog.delete" :text="$gettext(`Delete all?`)" icon="mdi-delete-sweep-outline" @close="dialog.delete = false" @confirm="onConfirmDelete" ></p-confirm-dialog> <v-dialog :model-value="details.visible" max-width="550" class="p-dialog" @keydown.esc.exact="details.visible = false"> <v-card> <v-card-title class="d-flex justify-start align-center ga-3"> <v-icon v-if="details.err.Level === 'error'" icon="mdi-alert-circle-outline" color="error"></v-icon> <v-icon v-else-if="details.err.Level === 'warning'" icon="mdi-alert" color="warning"></v-icon> <v-icon v-else icon="mdi-information-outline" color="info"></v-icon> <h6 class="text-h6 text-capitalize">{{ formatLevel(details.err.Level) }}</h6> </v-card-title> <v-card-text> <div :class="'p-log-' + details.err.Level" class="p-log-message text-body-2 text-selectable" dir="ltr"> <div :title="utcTime(details.err.Time)" class="p-log-message__time cursor-help mb-3"> {{ localTime(details.err.Time) }} </div> <div class="text-break p-log-message__text">{{ details.err.Message }}</div> </div> </v-card-text> <v-card-actions> <v-btn color="button" variant="flat" class="action-close" @click="details.visible = false"> {{ $gettext(`Close`) }} </v-btn> </v-card-actions> </v-card> </v-dialog> </div> </template> <script> import { DateTime } from "luxon"; import $api from "common/api"; import links from "common/links"; import * as formats from "options/formats"; import PLoading from "component/loading.vue"; import PActionMenu from "component/action/menu.vue"; import PConfirmDialog from "component/confirm/dialog.vue"; export default { name: "PPageErrors", components: { PLoading, PActionMenu, PConfirmDialog, }, expose: ["onShortCut"], data() { const query = this.$route.query; const q = query["q"] ? query["q"] : ""; return { dirty: false, loading: false, scrollDisabled: false, scrollDistance: window.innerHeight * 2, filter: { q }, isPublic: this.$config.get("public"), timeZone: this.$config.getTimeZone(), batchSize: 100, offset: 0, page: 0, errors: [], dialog: { delete: false, }, details: { visible: false, err: { Level: "", Message: "", Time: "" }, }, }; }, computed: { density() { return this.$vuetify.display.smAndDown ? "compact" : "comfortable"; }, }, watch: { $route() { if (!this.$view.isActive(this)) { return; } this.$view.focus(this.$refs?.page); const query = this.$route.query; this.filter.q = query["q"] ? query["q"] : ""; this.onReload(); }, }, created() { if (this.$config.deny("logs", "access_all")) { this.$router.push({ name: this.$session.getDefaultRoute() }); return; } this.loadMore(); }, mounted() { this.$view.enter(this); }, unmounted() { this.$view.leave(this); }, methods: { menuActions() { return [ { name: "refresh", icon: "mdi-refresh", text: this.$gettext("Refresh"), shortcut: "Ctrl-R", visible: true, click: () => { this.onReload(); }, }, { name: "troubleshooting", icon: "mdi-book-open-page-variant-outline", text: this.$gettext("Troubleshooting"), visible: true, href: links.troubleshooting, target: "_blank", }, ]; }, onShortCut(ev) { switch (ev.code) { case "KeyR": this.onReload(); return true; case "KeyF": this.$view.focus(this.$refs?.form, ".input-search input", true); return true; } }, updateFilter(props) { if (!props || typeof props !== "object" || props.target) { return; } for (const [key, value] of Object.entries(props)) { if (!this.filter.hasOwnProperty(key)) { continue; } switch (typeof value) { case "string": this.filter[key] = value.trim(); break; default: this.filter[key] = value; } } }, updateQuery(props) { this.updateFilter(props); if (this.loading) { return false; } const query = {}; Object.assign(query, this.filter); for (let key in query) { if (query[key] === undefined || !query[key]) { delete query[key]; } } if (JSON.stringify(this.$route.query) === JSON.stringify(query)) { return false; } this.$router.replace({ query }); return true; }, showDetails(err) { this.details.err = err; this.details.visible = true; }, onDelete() { if (this.loading) { return; } this.dialog.delete = true; }, onConfirmDelete() { this.dialog.delete = false; if (this.loading) { return; } this.loading = true; this.scrollDisabled = true; // Delete error logs. $api .delete("errors") .then((resp) => { if (resp && resp.data.code && resp.data.code === 200) { this.errors = []; this.dirty = false; this.page = 0; this.offset = 0; } }) .finally(() => { this.scrollDisabled = false; this.loading = false; }); }, onReload() { if (this.loading) { return; } this.page = 0; this.offset = 0; this.scrollDisabled = false; this.loadMore(); }, loadMore() { if (this.scrollDisabled) { return; } if (this.offset === 0) { this.loading = true; } this.scrollDisabled = true; const count = this.dirty ? (this.page + 2) * this.batchSize : this.batchSize; const offset = this.dirty ? 0 : this.offset; const q = this.filter.q; const params = { count, offset, q }; // Fetch error logs. $api .get("errors", { params }) .then((resp) => { if (!resp.data) { resp.data = []; } if (offset === 0) { this.errors = resp.data; } else { this.errors = this.errors.concat(resp.data); } this.scrollDisabled = resp.data.length < count; if (!this.scrollDisabled) { this.offset = offset + count; this.page++; } }) .finally(() => { this.loading = false; this.dirty = false; }); }, level(s) { return s.substring(0, 4).toUpperCase(); }, formatLevel(level) { switch (level) { case "error": return this.$gettext("Error"); case "warning": return this.$gettext("Warning"); } return level; }, localTime(s) { if (!s) { return this.$gettext("Unknown"); } return DateTime.fromISO(s, { zone: this.timeZone }).toLocaleString(formats.TIMESTAMP_LONG_TZ); }, utcTime(s) { if (!s) { return this.$gettext("Unknown"); } return DateTime.fromISO(s, { zone: "UTC" }).toLocaleString(formats.TIMESTAMP_LONG_TZ); }, }, }; </script>