mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2026-09-03 05:10:26 +03:00
feat(activity logs and admin middleware): add admin middleware to check for admin privilege
This commit is contained in:
@@ -196,4 +196,99 @@
|
||||
"youMayAlsoLike": "You may also like",
|
||||
"unableGetServerConfiguration": "Unable to get server configuration",
|
||||
"unableGetPublicUsers": "Unable to get users"
|
||||
"activityLogs": "Activity Logs",
|
||||
"actors": "Actors",
|
||||
"albums": "Albums",
|
||||
"alphabetically": "Alphabetically",
|
||||
"artist": "Artist",
|
||||
"artists": "Artists",
|
||||
"badRequest": "Bad request. Try again",
|
||||
"biography": "Biography",
|
||||
"browserNotSupported": "Your browser is not supported for playing this file.",
|
||||
"byArtist": "By",
|
||||
"changeServer": "Change server",
|
||||
"changeUser": "Change user",
|
||||
"collections": "Collections",
|
||||
"connect": "Connect",
|
||||
"continueListening": "Continue listening",
|
||||
"continueWatching": "Continue watching",
|
||||
"darkModeToggle": "Toggle dark mode",
|
||||
"disc": "Disc {discNumber}",
|
||||
"dislikes": "Dislikes",
|
||||
"endDate": "End date",
|
||||
"endsAt": "Ends at {time}",
|
||||
"episodeNumber": "Episode {episodeNumber}",
|
||||
"failedToRefreshItems": "Failed to refresh items",
|
||||
"favorite": "Favorite",
|
||||
"features": "Features",
|
||||
"filter": "Filter",
|
||||
"filtersNotFound": "Unable to load filters",
|
||||
"genres": "Genres",
|
||||
"home": "Home",
|
||||
"incorrectUsernameOrPassword": "Incorrect username or password",
|
||||
"itemNotFound": "Item not found",
|
||||
"latestLibrary": "Latest {libraryName}",
|
||||
"libraries": "Libraries",
|
||||
"libraryEmpty": "This library is empty",
|
||||
"libraryNotFound": "Library not found",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"login": "Login",
|
||||
"loginAs": "Login as {name}",
|
||||
"logout": "Logout",
|
||||
"manualLogin": "Manual login",
|
||||
"more": "More",
|
||||
"moreLikeArtist": "More like {artist}",
|
||||
"moreLikeThis": "More like this",
|
||||
"movies": "Movies",
|
||||
"name": "Name",
|
||||
"networks": "Networks",
|
||||
"nextUp": "Next up",
|
||||
"noNetworkConnection": "No network connection",
|
||||
"numberTracks": "{number} tracks",
|
||||
"noResultsFound": "There is nothing here",
|
||||
"parentalRatings": "Parental Ratings",
|
||||
"password": "Password",
|
||||
"play": "Play",
|
||||
"played": "Played",
|
||||
"present": "Present",
|
||||
"rating": "Rating",
|
||||
"releaseDate": "Release date",
|
||||
"resumable": "Resumable",
|
||||
"selectServer": "Select server",
|
||||
"selectUser": "Select a user",
|
||||
"series": "Series",
|
||||
"serverAddress": "Server address",
|
||||
"serverAddressMustBeUrl": "Server address must be a valid address",
|
||||
"serverAddressRequired": "Server address is required",
|
||||
"serverNotFound": "Server not found",
|
||||
"serverVersionTooLow": "Server version needs to be 10.7.0 or higher",
|
||||
"settings": "Settings",
|
||||
"shows": "Shows",
|
||||
"shuffleAll": "Shuffle all",
|
||||
"signIn": "Sign in",
|
||||
"sortByType": "By {type}",
|
||||
"specialFeatures": "Special Features",
|
||||
"status": "Status",
|
||||
"studios": "Studios",
|
||||
"subtitles": "Subtitles",
|
||||
"themeSong": "Theme Song",
|
||||
"themeVideo": "Theme Video",
|
||||
"trailer": "Trailer",
|
||||
"unableGetRelated": "Unable to get related items",
|
||||
"unexpectedError": "Unexpected error",
|
||||
"unhandledException": "Unhandled exception",
|
||||
"unliked": "Unliked",
|
||||
"unplayed": "Unplayed",
|
||||
"upNext": "Up next",
|
||||
"username": "Username",
|
||||
"usernameRequired": "Username is required",
|
||||
"validation": {
|
||||
"mustBeUrl": "This field must be a valid URL",
|
||||
"required": "This field is required"
|
||||
},
|
||||
"videoTypes": "Video Types",
|
||||
"viewDetails": "View details",
|
||||
"years": "Years",
|
||||
"youMayAlsoLike": "You may also like"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Context } from '@nuxt/types';
|
||||
|
||||
/**
|
||||
* Middleware providing a redirect to the home page in case the
|
||||
* User is not Admin and tries to access the admin page.
|
||||
*
|
||||
* @param {Context} context Nuxt application context
|
||||
* @returns {void}
|
||||
*/
|
||||
export default function (context: Context): void {
|
||||
if (!context.$auth.user.Policy.IsAdministrator) {
|
||||
return context.redirect('/');
|
||||
}
|
||||
}
|
||||
+115
-41
@@ -2,16 +2,37 @@
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="12" :offset-md="1" md="5" class="pt-0 pb-4">
|
||||
<!-- Administrator settings -->
|
||||
<v-list
|
||||
v-if="$auth.user.Policy.IsAdministrator"
|
||||
two-line
|
||||
class="mb-4"
|
||||
disabled
|
||||
>
|
||||
<v-list two-line class="mb-2">
|
||||
<v-list-item-group>
|
||||
<v-list-item
|
||||
v-for="file in logFiles"
|
||||
:key="file.Name"
|
||||
:href="getLogFileLink(file.Name)"
|
||||
target="_blank"
|
||||
>
|
||||
<v-list-item-avatar>
|
||||
<v-icon>mdi-file</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="file.Name" />
|
||||
<v-list-item-subtitle
|
||||
v-text="getFormattedLogDate(file.DateModified)"
|
||||
/>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon>mdi-chevron-right</v-icon>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
</v-col>
|
||||
<v-col cols="12" md="5" class="pt-0 pb-4">
|
||||
<v-list two-line class="mb-2" disabled>
|
||||
<v-list-item-group>
|
||||
<v-list-item v-for="activity in activityList" :key="activity.Id">
|
||||
<v-list-item-avatar>
|
||||
<v-list-item-avatar
|
||||
:color="getColorFromSeverity(activity.Severity)"
|
||||
>
|
||||
<v-icon>mdi-shield-account-outline</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
@@ -19,27 +40,10 @@
|
||||
<v-list-item-subtitle v-text="activity.ShortOverview" />
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-list-item-subtitle v-text="activity.Date" />
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
<!-- Mobile links -->
|
||||
<v-list v-if="$vuetify.breakpoint.mobile">
|
||||
<v-list-item-group>
|
||||
<v-list-item
|
||||
v-for="linkItem in linkItems"
|
||||
:key="linkItem.name"
|
||||
:href="linkItem.link"
|
||||
>
|
||||
<v-list-item-avatar>
|
||||
<v-icon v-text="linkItem.icon" />
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="linkItem.name" />
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon>mdi-chevron-right</v-icon>
|
||||
<v-list-item-subtitle
|
||||
class="text-capitalize-first-letter"
|
||||
v-text="getFormattedActivityDate(activity.Date)"
|
||||
/>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list-item-group>
|
||||
@@ -50,32 +54,102 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { isEmpty } from 'lodash';
|
||||
import Vue from 'vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import { SystemInfo } from '~/api';
|
||||
import colors from 'vuetify/lib/util/colors';
|
||||
import { ActivityLogEntry, LogFile, LogLevel } from '~/api';
|
||||
import htmlHelper from '~/mixins/htmlHelper';
|
||||
|
||||
export default Vue.extend({
|
||||
mixins: [htmlHelper],
|
||||
middleware: 'adminMiddleware',
|
||||
data() {
|
||||
return {
|
||||
activityList: {} as SystemInfo
|
||||
activityList: [] as ActivityLogEntry[],
|
||||
logFiles: [] as LogFile[]
|
||||
};
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: this.$store.state.page.title
|
||||
};
|
||||
},
|
||||
async beforeMount() {
|
||||
this.setAppBarOpacity({ opaqueAppBar: true });
|
||||
this.setPageTitle({ title: this.$t('settings') });
|
||||
|
||||
if (this.$auth.user.Policy.IsAdministrator) {
|
||||
this.activityList = (await this.$api.system.getActivityLogs()).data.Items;
|
||||
}
|
||||
this.setPageTitle({ title: this.$t('activityLogs') });
|
||||
const minDate = new Date();
|
||||
minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
this.activityList =
|
||||
(await this.$api.activityLog.getLogEntries({ minDate })).data.Items || [];
|
||||
this.logFiles = (await this.$api.system.getServerLogs()).data;
|
||||
},
|
||||
methods: {
|
||||
...mapActions('page', ['setPageTitle', 'setAppBarOpacity']),
|
||||
isEmpty(object: any): boolean {
|
||||
return isEmpty(object);
|
||||
...mapActions('page', ['setPageTitle']),
|
||||
getColorFromSeverity(severity: LogLevel): string {
|
||||
switch (severity) {
|
||||
case LogLevel.Trace:
|
||||
return (
|
||||
this.$vuetify.theme.currentTheme.success?.toString() ||
|
||||
colors.green.base
|
||||
);
|
||||
case LogLevel.Debug:
|
||||
return (
|
||||
this.$vuetify.theme.currentTheme.accent?.toString() ||
|
||||
colors.blue.accent1
|
||||
);
|
||||
case LogLevel.Information:
|
||||
return (
|
||||
this.$vuetify.theme.currentTheme.info?.toString() ||
|
||||
colors.blue.base
|
||||
);
|
||||
case LogLevel.Warning:
|
||||
return (
|
||||
this.$vuetify.theme.currentTheme.warning?.toString() ||
|
||||
colors.amber.base
|
||||
);
|
||||
case LogLevel.Error:
|
||||
return (
|
||||
this.$vuetify.theme.currentTheme.error?.toString() ||
|
||||
colors.red.accent2
|
||||
);
|
||||
case LogLevel.Critical:
|
||||
return (
|
||||
this.$vuetify.theme.currentTheme.secondary?.toString() ||
|
||||
colors.grey.darken3
|
||||
);
|
||||
default:
|
||||
return (
|
||||
this.$vuetify.theme.currentTheme.primary?.toString() ||
|
||||
colors.blue.darken2
|
||||
);
|
||||
}
|
||||
},
|
||||
getFormattedActivityDate(date: Date): string {
|
||||
const today = this.$dateFns.endOfToday();
|
||||
const day = this.$dateFns.formatRelative(
|
||||
this.$dateFns.parseJSON(date),
|
||||
today
|
||||
);
|
||||
return day;
|
||||
},
|
||||
getFormattedLogDate(date: Date): string {
|
||||
const day = this.$dateFns.format(date, 'Ppp');
|
||||
return day;
|
||||
},
|
||||
getLogFileLink(name: string): string {
|
||||
return (
|
||||
this.$axios.defaults.baseURL +
|
||||
'/System/Logs/Log?name=' +
|
||||
name +
|
||||
'&api_key=' +
|
||||
this.$store.state.user.accessToken
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.text-capitalize-first-letter::first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user