mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-01-24 14:24:47 +08:00
feat(front+backend): add filters to action log page
This commit is contained in:
parent
95113cda3e
commit
800ed9b39a
@ -2,6 +2,7 @@ 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.LogProjectFilter.LogProjectFilterInstance;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jdbi.v3.core.statement.SqlStatement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -19,7 +20,7 @@ public class LogProjectFilter implements Filter<LogProjectFilterInstance> {
|
||||
|
||||
@Override
|
||||
public boolean supports(NativeWebRequest webRequest) {
|
||||
return getQueryParamNames().stream().allMatch(webRequest.getParameterMap()::containsKey);
|
||||
return getQueryParamNames().stream().anyMatch(webRequest.getParameterMap()::containsKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -45,10 +46,14 @@ public class LogProjectFilter implements Filter<LogProjectFilterInstance> {
|
||||
|
||||
@Override
|
||||
public void createSql(StringBuilder sb, SqlStatement<?> q) {
|
||||
sb.append(" AND la.p_owner_name = :authorName");
|
||||
q.bind("authorName", this.authorName);
|
||||
sb.append(" AND la.p_slug = :projectSlug");
|
||||
q.bind("projectSlug", this.projectSlug);
|
||||
if (StringUtils.isNotBlank(this.authorName)) {
|
||||
sb.append(" AND la.p_owner_name = :authorName");
|
||||
q.bind("authorName", this.authorName);
|
||||
}
|
||||
if (StringUtils.isNotBlank(this.projectSlug)) {
|
||||
sb.append(" AND la.p_slug = :projectSlug");
|
||||
q.bind("projectSlug", this.projectSlug);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import io.papermc.hangar.model.common.roles.GlobalRole;
|
||||
import io.papermc.hangar.model.common.roles.OrganizationRole;
|
||||
import io.papermc.hangar.model.common.roles.ProjectRole;
|
||||
import io.papermc.hangar.model.internal.api.responses.Validations;
|
||||
import io.papermc.hangar.model.internal.logs.LogAction;
|
||||
import io.papermc.hangar.security.annotations.Anyone;
|
||||
import io.papermc.hangar.security.annotations.ratelimit.RateLimit;
|
||||
import io.papermc.hangar.service.internal.PlatformService;
|
||||
@ -27,6 +28,7 @@ import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.info.GitProperties;
|
||||
@ -43,7 +45,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@Controller
|
||||
@Anyone
|
||||
@RateLimit(path = "backenddata")
|
||||
@RequestMapping(path = "/api/internal/data", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
|
||||
@RequestMapping(path = "/api/internal/data", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public class BackendDataController {
|
||||
|
||||
private final ObjectMapper objectMapper; // ignores JsonValue annotations
|
||||
@ -215,5 +217,12 @@ public class BackendDataController {
|
||||
public Validations getValidations() {
|
||||
return Validations.create(this.config);
|
||||
}
|
||||
|
||||
@GetMapping("/loggedActions")
|
||||
@Cacheable("loggedActions")
|
||||
@ResponseBody
|
||||
public Set<String> getLoggedActions() {
|
||||
return LogAction.LOG_REGISTRY.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,8 @@ import prettier from "./src/lib/plugins/prettier";
|
||||
|
||||
const backendHost = process.env.BACKEND_HOST || "http://localhost:8080";
|
||||
const authHost = process.env.AUTH_HOST || "http://localhost:3001";
|
||||
const backendDataHost = process.env.BACKEND_DATA_HOST || "https://hangar.papermc.dev";
|
||||
const local = false; // set to true if backendData should be fetched from localhost
|
||||
const backendDataHost = process.env.BACKEND_DATA_HOST || (local ? "http://localhost:8080" : "https://hangar.papermc.dev");
|
||||
|
||||
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
||||
export default defineNuxtConfig({
|
||||
|
@ -16,6 +16,7 @@ export default defineNuxtModule({
|
||||
serverUrl: "https://hangar.papermc.dev",
|
||||
path: "./src/generated/backendData.json",
|
||||
ttl: 30 * 60 * 1000, // 30 min
|
||||
version: 1,
|
||||
},
|
||||
setup(moduleOptions, nuxt) {
|
||||
nuxt.hook("prepare:types", async () => {
|
||||
@ -33,7 +34,9 @@ export default defineNuxtModule({
|
||||
state?.meta?.lastGenerated &&
|
||||
new Date(state.meta.lastGenerated).getTime() + moduleOptions.ttl > Date.now() &&
|
||||
// ...but only if the API URL is the same
|
||||
state.meta.apiUrl === moduleOptions.serverUrl
|
||||
state.meta.apiUrl === moduleOptions.serverUrl &&
|
||||
// ...and the version is the same
|
||||
state.meta.version === moduleOptions.version
|
||||
) {
|
||||
backendDataLog("Not generating backend data since its still valid, lastGenerated was", new Date(state.meta.lastGenerated));
|
||||
return;
|
||||
@ -43,6 +46,7 @@ export default defineNuxtModule({
|
||||
state.meta = {
|
||||
lastGenerated: new Date().toISOString(),
|
||||
apiUrl: moduleOptions.serverUrl,
|
||||
version: moduleOptions.version,
|
||||
};
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
@ -92,6 +96,7 @@ async function loadData(state: BackendData, axiosInstance: AxiosInstance) {
|
||||
globalRoles,
|
||||
channelColors,
|
||||
flagReasons,
|
||||
loggedActions,
|
||||
] = (
|
||||
await Promise.all([
|
||||
axiosInstance.get<typeof state.projectCategories>("/categories"),
|
||||
@ -107,6 +112,7 @@ async function loadData(state: BackendData, axiosInstance: AxiosInstance) {
|
||||
axiosInstance.get<typeof state.globalRoles>("/globalRoles"),
|
||||
axiosInstance.get<typeof state.channelColors>("/channelColors"),
|
||||
axiosInstance.get<typeof state.flagReasons>("/flagReasons"),
|
||||
axiosInstance.get<typeof state.loggedActions>("/loggedActions"),
|
||||
])
|
||||
).map((it) => it.data);
|
||||
|
||||
@ -127,4 +133,5 @@ async function loadData(state: BackendData, axiosInstance: AxiosInstance) {
|
||||
state.globalRoles = globalRoles as typeof state.globalRoles;
|
||||
state.channelColors = channelColors as typeof state.channelColors;
|
||||
state.flagReasons = flagReasons as typeof state.flagReasons;
|
||||
state.loggedActions = loggedActions as typeof state.loggedActions;
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { PaginatedResult } from "hangar-api";
|
||||
import { computed, ref } from "vue";
|
||||
import { LoggedAction } from "hangar-internal";
|
||||
import { LoggedAction, LoggedActionType } from "hangar-internal";
|
||||
import { debounce } from "lodash-es";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useActionLogs } from "~/composables/useApiHelper";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
@ -15,7 +16,10 @@ import MarkdownModal from "~/components/modals/MarkdownModal.vue";
|
||||
import DiffModal from "~/components/modals/DiffModal.vue";
|
||||
import Button from "~/lib/components/design/Button.vue";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { definePageMeta, useInternalApi } from "#imports";
|
||||
import { definePageMeta, useInternalApi, watch } from "#imports";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import InputSelect from "~/lib/components/ui/InputSelect.vue";
|
||||
import { useBackendData } from "~/store/backendData";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["VIEW_LOGS"],
|
||||
@ -38,15 +42,24 @@ const headers = [
|
||||
|
||||
const page = ref(0);
|
||||
const sort = ref<string[]>([]);
|
||||
const filter = ref<{
|
||||
user?: string;
|
||||
logAction?: LoggedActionType;
|
||||
authorName?: string;
|
||||
projectSlug?: string;
|
||||
}>({});
|
||||
const requestParams = computed(() => {
|
||||
const limit = 25;
|
||||
return {
|
||||
...filter.value,
|
||||
limit,
|
||||
offset: page.value * limit,
|
||||
sort: sort.value,
|
||||
};
|
||||
});
|
||||
|
||||
watch(filter, debounce(update, 500), { deep: true });
|
||||
|
||||
async function updateSort(col: string, sorter: Record<string, number>) {
|
||||
sort.value = [...Object.keys(sorter)]
|
||||
.map((k) => {
|
||||
@ -76,6 +89,21 @@ useHead(useSeo(i18n.t("userActionLog.title"), null, route, null));
|
||||
<div>
|
||||
<PageTitle>{{ i18n.t("userActionLog.title") }}</PageTitle>
|
||||
<Card>
|
||||
<div class="flex mb-4">
|
||||
<div class="basis-3/12">
|
||||
<InputText v-model="filter.user" label="User" />
|
||||
</div>
|
||||
<div class="basis-3/12">
|
||||
<InputSelect v-model="filter.logAction" :values="useBackendData.loggedActions" label="Action" />
|
||||
</div>
|
||||
<div class="basis-3/12">
|
||||
<InputText v-model="filter.authorName" label="Author Name" />
|
||||
</div>
|
||||
<div class="basis-3/12">
|
||||
<InputText v-model="filter.projectSlug" label="Project Slug" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SortableTable
|
||||
:loading="true"
|
||||
:headers="headers"
|
||||
|
2
frontend/src/types/api.d.ts
vendored
2
frontend/src/types/api.d.ts
vendored
@ -64,6 +64,7 @@ declare module "hangar-api" {
|
||||
meta: {
|
||||
lastGenerated: string;
|
||||
apiUrl: string;
|
||||
version: number;
|
||||
};
|
||||
projectCategories: Map<ProjectCategory, IProjectCategory>;
|
||||
permissions: Map<NamedPermission, IPermission>;
|
||||
@ -78,5 +79,6 @@ declare module "hangar-api" {
|
||||
globalRoles: Role[];
|
||||
channelColors: Color[];
|
||||
flagReasons: FlagReason[];
|
||||
loggedActions: string[];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user