mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-03-13 15:39:18 +08:00
Backend log filters, pagination
This commit is contained in:
parent
c606afc144
commit
a1c10bf84a
@ -2,7 +2,16 @@
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('userActionLog.title') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-data-table :items="loggedActions.result" :headers="headers">
|
||||
<v-data-table
|
||||
:items="loggedActions.result"
|
||||
:headers="headers"
|
||||
:server-items-length="loggedActions.pagination.count"
|
||||
:items-per-page="50"
|
||||
:options.sync="options"
|
||||
:footer-props="{ itemsPerPageOptions: [10, 25, 50] }"
|
||||
:loading="loading"
|
||||
disable-sort
|
||||
>
|
||||
<template #item.user="{ item }">
|
||||
<NuxtLink :to="'/' + item.userName">{{ item.userName }}</NuxtLink>
|
||||
</template>
|
||||
@ -73,22 +82,26 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'nuxt-property-decorator';
|
||||
import { Component } from 'nuxt-property-decorator';
|
||||
import { LoggedAction } from 'hangar-internal';
|
||||
import { Context } from '@nuxt/types';
|
||||
import { PaginatedResult } from 'hangar-api';
|
||||
import { DataOptions } from 'vuetify';
|
||||
import { GlobalPermission } from '~/utils/perms';
|
||||
import { NamedPermission } from '~/types/enums';
|
||||
import MarkdownModal from '~/components/modals/MarkdownModal.vue';
|
||||
import DiffModal from '~/components/modals/DiffModal.vue';
|
||||
import { HangarComponent } from '~/components/mixins';
|
||||
|
||||
// TODO figure out a nice way to do filters for AdminLogPage
|
||||
@Component({
|
||||
components: { DiffModal, MarkdownModal },
|
||||
})
|
||||
@GlobalPermission(NamedPermission.VIEW_LOGS)
|
||||
export default class AdminLogPage extends Vue {
|
||||
export default class AdminLogPage extends HangarComponent {
|
||||
loggedActions!: PaginatedResult<LoggedAction>;
|
||||
loading = false;
|
||||
options = { page: 1, itemsPerPage: 50 } as DataOptions;
|
||||
headers = [
|
||||
{ text: this.$t('userActionLog.user'), value: 'user' },
|
||||
{ text: this.$t('userActionLog.address'), value: 'address' },
|
||||
@ -105,6 +118,31 @@ export default class AdminLogPage extends Vue {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO I'd like to move these things to a mixin since they are common across multiple components (see authors.vue, staff.vue, etc.)
|
||||
mounted() {
|
||||
this.$watch('options', this.onOptionsChanged, { deep: true });
|
||||
}
|
||||
|
||||
onOptionsChanged() {
|
||||
this.loading = true;
|
||||
this.$api
|
||||
.requestInternal<PaginatedResult<LoggedAction>>('admin/log', true, 'get', this.requestOptions)
|
||||
.then((log) => {
|
||||
this.loggedActions = log;
|
||||
})
|
||||
.catch(this.$util.handleRequestError)
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
get requestOptions() {
|
||||
return {
|
||||
limit: this.options.itemsPerPage,
|
||||
offset: (this.options.page - 1) * this.options.itemsPerPage,
|
||||
};
|
||||
}
|
||||
|
||||
async asyncData({ $api, $util }: Context) {
|
||||
const loggedActions = await $api.requestInternal<PaginatedResult<LoggedAction>>(`admin/log/`, false).catch<any>($util.handlePageRequestError);
|
||||
return { loggedActions };
|
||||
|
@ -3,6 +3,7 @@ package io.papermc.hangar.controller.extras;
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
import io.papermc.hangar.util.StaticContextAccessor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
@ -19,8 +20,12 @@ public class ApiUtils {
|
||||
* @return actual limit
|
||||
*/
|
||||
public static long limitOrDefault(@Nullable Long limit) {
|
||||
return limitOrDefault(limit, hangarConfig.projects.getInitLoad());
|
||||
}
|
||||
|
||||
public static long limitOrDefault(@Nullable Long limit, long maxLimit) {
|
||||
if (limit != null && limit < 1) throw new HangarApiException(HttpStatus.BAD_REQUEST, "Limit should be greater than 0");
|
||||
return Math.min(limit == null ? hangarConfig.projects.getInitLoad() : limit, hangarConfig.projects.getInitLoad());
|
||||
return Math.min(limit == null ? maxLimit : limit, maxLimit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,7 @@ public interface Filter<F extends FilterInstance> {
|
||||
String getDescription();
|
||||
|
||||
default boolean supports(NativeWebRequest webRequest) {
|
||||
return webRequest.getParameterMap().containsKey(getQueryParamNames());
|
||||
return webRequest.getParameterMap().containsKey(getSingleQueryParam());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -0,0 +1,21 @@
|
||||
package io.papermc.hangar.controller.extras.pagination.annotations;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Configure default page length/number
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ConfigurePagination {
|
||||
|
||||
/**
|
||||
* -1 means fallback to default configured value
|
||||
*/
|
||||
long maxLimit();
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package io.papermc.hangar.controller.extras.pagination.filters.log;
|
||||
|
||||
import io.papermc.hangar.controller.extras.pagination.Filter;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogActionFilter.LogActionFilterInstance;
|
||||
import io.papermc.hangar.model.internal.logs.LogAction;
|
||||
import org.jdbi.v3.core.statement.SqlStatement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class LogActionFilter implements Filter<LogActionFilterInstance> {
|
||||
|
||||
@Override
|
||||
public Set<String> getQueryParamNames() {
|
||||
return Set.of("logAction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Filters by log action";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(NativeWebRequest webRequest) {
|
||||
return Filter.super.supports(webRequest) && LogAction.LOG_REGISTRY.containsKey(webRequest.getParameter(getSingleQueryParam()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LogActionFilterInstance create(NativeWebRequest webRequest) {
|
||||
return new LogActionFilterInstance(LogAction.LOG_REGISTRY.get(webRequest.getParameter(getSingleQueryParam())));
|
||||
}
|
||||
|
||||
static class LogActionFilterInstance implements FilterInstance {
|
||||
|
||||
private final LogAction<?> logAction;
|
||||
|
||||
LogActionFilterInstance(LogAction<?> logAction) {
|
||||
this.logAction = logAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSql(StringBuilder sb, SqlStatement<?> q) {
|
||||
sb.append(" AND la.action = :actionFilter::LOGGED_ACTION_TYPE");
|
||||
q.bind("actionFilter", logAction.getPgLoggedAction().getValue());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package io.papermc.hangar.controller.extras.pagination.filters.log;
|
||||
|
||||
import io.papermc.hangar.controller.extras.pagination.Filter;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogSubjectFilter.LogSubjectFilterInstance;
|
||||
import org.jdbi.v3.core.statement.SqlStatement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
public class LogSubjectFilter implements Filter<LogSubjectFilterInstance> {
|
||||
|
||||
@Override
|
||||
public Set<String> getQueryParamNames() {
|
||||
return Set.of("subjectName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Filters by subject name, usually a user action where the subject name is the user the action is about, not the user that performed the action";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public LogSubjectFilterInstance create(NativeWebRequest webRequest) {
|
||||
return new LogSubjectFilterInstance(webRequest.getParameter(getSingleQueryParam()));
|
||||
}
|
||||
|
||||
static class LogSubjectFilterInstance implements FilterInstance {
|
||||
|
||||
private final String subjectName;
|
||||
|
||||
LogSubjectFilterInstance(String subjectName) {
|
||||
this.subjectName = subjectName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSql(StringBuilder sb, SqlStatement<?> q) {
|
||||
sb.append(" AND la.s_name = :subjectName");
|
||||
q.bind("subjectName", this.subjectName);
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import io.papermc.hangar.controller.extras.pagination.FilterRegistry;
|
||||
import io.papermc.hangar.controller.extras.pagination.SorterRegistry;
|
||||
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableFilters;
|
||||
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableSorters;
|
||||
import io.papermc.hangar.controller.extras.pagination.annotations.ConfigurePagination;
|
||||
import io.papermc.hangar.exceptions.HangarApiException;
|
||||
import io.papermc.hangar.model.api.requests.RequestPagination;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -45,8 +46,15 @@ public class RequestPaginationResolver implements HandlerMethodArgumentResolver
|
||||
|
||||
@Override
|
||||
public RequestPagination resolveArgument(@NotNull MethodParameter parameter, ModelAndViewContainer mavContainer, @NotNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
long limit = ApiUtils.limitOrDefault(Optional.ofNullable(webRequest.getParameter("limit")).map(Long::parseLong).orElse(null));
|
||||
long offset = ApiUtils.offsetOrZero(Optional.ofNullable(webRequest.getParameter("offset")).map(Long::parseLong).orElse(null));
|
||||
long limit;
|
||||
Optional<Long> limitParam = Optional.ofNullable(webRequest.getParameter("limit")).map(Long::parseLong);
|
||||
ConfigurePagination settings = parameter.getParameterAnnotation(ConfigurePagination.class);
|
||||
if (settings != null) {
|
||||
limit = ApiUtils.limitOrDefault(limitParam.orElse(null), settings.maxLimit());
|
||||
} else {
|
||||
limit = ApiUtils.limitOrDefault(limitParam.orElse(null));
|
||||
}
|
||||
RequestPagination pagination = new RequestPagination(limit, offset);
|
||||
|
||||
// find filters
|
||||
|
@ -3,9 +3,18 @@ package io.papermc.hangar.controller.internal.admin;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import io.papermc.hangar.HangarComponent;
|
||||
import io.papermc.hangar.controller.extras.pagination.annotations.ApplicableFilters;
|
||||
import io.papermc.hangar.controller.extras.pagination.annotations.ConfigurePagination;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogActionFilter;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogPageFilter;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogProjectFilter;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogSubjectFilter;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogUserFilter;
|
||||
import io.papermc.hangar.controller.extras.pagination.filters.log.LogVersionFilter;
|
||||
import io.papermc.hangar.controller.extras.resolvers.NoCache;
|
||||
import io.papermc.hangar.model.api.PaginatedResult;
|
||||
import io.papermc.hangar.model.api.Pagination;
|
||||
import io.papermc.hangar.model.api.requests.RequestPagination;
|
||||
import io.papermc.hangar.model.common.NamedPermission;
|
||||
import io.papermc.hangar.model.db.JobTable;
|
||||
import io.papermc.hangar.model.db.UserTable;
|
||||
@ -21,6 +30,7 @@ import io.papermc.hangar.service.internal.PlatformService;
|
||||
import io.papermc.hangar.service.internal.admin.HealthService;
|
||||
import io.papermc.hangar.service.internal.admin.StatService;
|
||||
import io.papermc.hangar.service.internal.users.UserService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
@ -103,12 +113,12 @@ public class AdminController extends HangarComponent {
|
||||
userService.setLocked(user, locked, comment.getContent());
|
||||
}
|
||||
|
||||
// TODO filters/pagination
|
||||
@ResponseBody
|
||||
@GetMapping(value = "/log", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PermissionRequired(NamedPermission.REVIEWER)
|
||||
public PaginatedResult<HangarLoggedAction> getActionLog() {
|
||||
List<HangarLoggedAction> log = actionLogger.getLog(0, null, null, null, null, null, null);
|
||||
return new PaginatedResult<>(new Pagination(25, 0, (long) log.size()), log);
|
||||
@ApplicableFilters({LogActionFilter.class, LogPageFilter.class, LogProjectFilter.class, LogSubjectFilter.class, LogUserFilter.class, LogVersionFilter.class})
|
||||
// TODO add sorters
|
||||
public PaginatedResult<HangarLoggedAction> getActionLog(@NotNull @ConfigurePagination(maxLimit = 50) RequestPagination pagination) {
|
||||
return actionLogger.getLogs(pagination);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.papermc.hangar.db.dao.internal;
|
||||
|
||||
import io.papermc.hangar.db.extras.BindPagination;
|
||||
import io.papermc.hangar.db.mappers.LogActionColumnMapper;
|
||||
import io.papermc.hangar.model.api.requests.RequestPagination;
|
||||
import io.papermc.hangar.model.db.log.LoggedActionsOrganizationTable;
|
||||
import io.papermc.hangar.model.db.log.LoggedActionsPageTable;
|
||||
import io.papermc.hangar.model.db.log.LoggedActionsProjectTable;
|
||||
@ -46,14 +48,14 @@ public interface LoggedActionsDAO {
|
||||
@RegisterColumnMapper(LogActionColumnMapper.class)
|
||||
@RegisterConstructorMapper(HangarLoggedAction.class)
|
||||
@SqlQuery("SELECT * FROM v_logged_actions la " +
|
||||
" WHERE true " +
|
||||
"<if(userFilter)>AND la.user_name = :userFilter<endif> " +
|
||||
"<if(projectFilter)>AND (la.p_owner_name || '/' || la.p_slug) = :projectFilter<endif> " +
|
||||
"<if(versionFilter)>AND la.pv_version_string = :versionFilter<endif> " +
|
||||
"<if(pageFilter)>AND la.pp_id = :pageFilter<endif> " +
|
||||
"<if(actionFilter)>AND la.action = :actionFilter::LOGGED_ACTION_TYPE<endif> " +
|
||||
"<if(subjectFilter)>AND la.s_name = :subjectFilter<endif> " +
|
||||
"ORDER BY la.created_at DESC OFFSET :offset LIMIT :pageSize")
|
||||
" WHERE true <filters>" +
|
||||
" ORDER BY la.created_at DESC <offsetLimit>")
|
||||
// TODO add <sorters>
|
||||
@DefineNamedBindings
|
||||
List<HangarLoggedAction> getLog(String userFilter, String projectFilter, String versionFilter, String pageFilter, String actionFilter, String subjectFilter, long offset, long pageSize);
|
||||
List<HangarLoggedAction> getLog(@BindPagination RequestPagination pagination);
|
||||
|
||||
@UseStringTemplateEngine
|
||||
@SqlQuery("SELECT count(*) FROM v_logged_actions la " +
|
||||
" WHERE true <filters>")
|
||||
long getLogCount(@BindPagination(isCount = true) RequestPagination pagination);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import org.jdbi.v3.sqlobject.customizer.SqlStatementCustomizingAnnotation;
|
||||
import org.jdbi.v3.sqlobject.customizer.SqlStatementParameterCustomizer;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@ -15,11 +16,20 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Configure filters, sorters, offset, and limit from a web request
|
||||
* into a db query
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
@SqlStatementCustomizingAnnotation(BindPagination.BindPaginationFactory.class)
|
||||
public @interface BindPagination {
|
||||
|
||||
/**
|
||||
* set to true to disable the injection of sorters, offset, and limit.<br>
|
||||
* used for getting the total count of all entries
|
||||
*/
|
||||
boolean isCount() default false;
|
||||
|
||||
class BindPaginationFactory implements SqlStatementCustomizerFactory {
|
||||
|
@ -6,13 +6,9 @@ public class Pagination extends RequestPagination {
|
||||
|
||||
private final long count;
|
||||
|
||||
public Pagination(long limit, long offset, Long count) {
|
||||
super(limit, offset);
|
||||
this.count = count != null ? count : 0;
|
||||
}
|
||||
|
||||
public Pagination(Long count, RequestPagination pagination) {
|
||||
this(pagination.getLimit(), pagination.getOffset(), count);
|
||||
super(pagination.getLimit(), pagination.getOffset());
|
||||
this.count = count != null ? count : 0;
|
||||
}
|
||||
|
||||
public long getCount() {
|
||||
|
@ -27,9 +27,12 @@ public class RequestPagination {
|
||||
@ApiModelProperty(hidden = true)
|
||||
private final Map<String, Consumer<StringBuilder>> sorters;
|
||||
|
||||
/**
|
||||
* limit/offset params should be validated before construction
|
||||
*/
|
||||
public RequestPagination(Long limit, Long offset) {
|
||||
this.limit = ApiUtils.limitOrDefault(limit);
|
||||
this.offset = ApiUtils.offsetOrZero(offset);
|
||||
this.limit = limit;
|
||||
this.offset = offset;
|
||||
this.filters = new ArrayList<>();
|
||||
this.sorters = new LinkedHashMap<>();
|
||||
}
|
||||
|
@ -3,6 +3,9 @@ package io.papermc.hangar.service.internal;
|
||||
import io.papermc.hangar.HangarComponent;
|
||||
import io.papermc.hangar.db.dao.HangarDao;
|
||||
import io.papermc.hangar.db.dao.internal.LoggedActionsDAO;
|
||||
import io.papermc.hangar.model.api.PaginatedResult;
|
||||
import io.papermc.hangar.model.api.Pagination;
|
||||
import io.papermc.hangar.model.api.requests.RequestPagination;
|
||||
import io.papermc.hangar.model.db.log.LoggedActionTable;
|
||||
import io.papermc.hangar.model.internal.logs.HangarLoggedAction;
|
||||
import io.papermc.hangar.model.internal.logs.LoggedAction;
|
||||
@ -55,14 +58,7 @@ public class UserActionLogService extends HangarComponent {
|
||||
inserter.accept(action.getContext().createTable(getHangarPrincipal().getUserId(), RequestUtil.getRemoteInetAddress(request), action));
|
||||
}
|
||||
|
||||
public List<HangarLoggedAction> getLog(Integer oPage, String userFilter, String projectFilter, String versionFilter, String pageFilter, String actionFilter, String subjectFilter) {
|
||||
long pageSize = 50L;
|
||||
long offset;
|
||||
if (oPage == null) {
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = oPage * pageSize;
|
||||
}
|
||||
return loggedActionsDAO.getLog(userFilter, projectFilter, versionFilter, pageFilter, actionFilter, subjectFilter, offset, pageSize);
|
||||
public PaginatedResult<HangarLoggedAction> getLogs(RequestPagination pagination) {
|
||||
return new PaginatedResult<>(new Pagination(loggedActionsDAO.getLogCount(pagination), pagination), loggedActionsDAO.getLog(pagination));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user