mirror of
https://github.com/HangarMC/Hangar.git
synced 2024-12-21 06:51:19 +08:00
Improvements and fixes to logs page
This commit is contained in:
parent
2edd173e25
commit
ad4fc8acf6
@ -76,7 +76,7 @@ public interface IProjectsController {
|
||||
})
|
||||
@GetMapping("/projects")
|
||||
ResponseEntity<PaginatedResult<Project>> getProjects(
|
||||
@Parameter(description = "Whether projects should be sorted by the relevance to the given query") @RequestParam(defaultValue = "true") boolean relevance,
|
||||
@Parameter(description = "Whether projects should be sorted by the relevance to the given query") @RequestParam(defaultValue = "true", required = false) boolean relevance,
|
||||
@Parameter(description = "Pagination information") @NotNull RequestPagination pagination
|
||||
);
|
||||
|
||||
|
@ -222,8 +222,8 @@ public class BackendDataController {
|
||||
@GetMapping("/loggedActions")
|
||||
@Cacheable(CacheConfig.LOGGED_ACTIONS)
|
||||
@ResponseBody
|
||||
public Set<String> getLoggedActions() {
|
||||
return LogAction.LOG_REGISTRY.keySet();
|
||||
public List<String> getLoggedActions() {
|
||||
return LogAction.LOG_REGISTRY.keySet().stream().sorted().toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,8 @@ public enum NamedPermission {
|
||||
RESTORE_PROJECT("restore_project", Permission.DeleteVersion, "RestoreProject"),
|
||||
HARD_DELETE_PROJECT("hard_delete_project", Permission.HardDeleteProject, "HardDeleteProject"),
|
||||
HARD_DELETE_VERSION("hard_delete_version", Permission.HardDeleteVersion, "HardDeleteVersion"),
|
||||
EDIT_ALL_USER_SETTINGS("edit_all_user_settings", Permission.EditAllUserSettings, "EditAllUserSettings");
|
||||
EDIT_ALL_USER_SETTINGS("edit_all_user_settings", Permission.EditAllUserSettings, "EditAllUserSettings"),
|
||||
SEE_IP_ADDRESSES("see_ip_addresses", Permission.SeeIPAdresses, "SeeIpAddresses");
|
||||
|
||||
private final String value;
|
||||
private final Permission permission;
|
||||
|
@ -57,7 +57,7 @@ public class APIKeyService extends HangarComponent {
|
||||
final String token = UUID.randomUUID().toString();
|
||||
final String hashedToken = CryptoUtils.hmacSha256(this.config.security.tokenSecret(), token.getBytes(StandardCharsets.UTF_8));
|
||||
this.apiKeyDAO.insert(new ApiKeyTable(apiKeyForm.getName(), userIdentified.getUserId(), tokenIdentifier, hashedToken, keyPermission));
|
||||
this.actionLogger.user(LogAction.USER_APIKEY_CREATED.create(UserContext.of(userIdentified.getUserId()), "Key Name: " + apiKeyForm.getName() + "<br>" + apiKeyForm.getPermissions().stream().map(NamedPermission::getFrontendName).collect(Collectors.joining(",<br>")), ""));
|
||||
this.actionLogger.user(LogAction.USER_APIKEY_CREATED.create(UserContext.of(userIdentified.getUserId()), "Key '" + apiKeyForm.getName() + "': " + apiKeyForm.getPermissions().stream().map(NamedPermission::getFrontendName).collect(Collectors.joining(", ")), ""));
|
||||
return tokenIdentifier + "." + token;
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ public class APIKeyService extends HangarComponent {
|
||||
if (this.apiKeyDAO.delete(keyName, userIdentified.getUserId()) == 0) {
|
||||
throw new HangarApiException(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
this.actionLogger.user(LogAction.USER_APIKEY_DELETED.create(UserContext.of(userIdentified.getUserId()), "", "Key Name: " + keyName));
|
||||
this.actionLogger.user(LogAction.USER_APIKEY_DELETED.create(UserContext.of(userIdentified.getUserId()), "", "Key '" + keyName + "'"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ const slug = computed(() => props.project.namespace.owner + "/" + props.project.
|
||||
<DropdownItem :to="`/${slug}/notes`">
|
||||
{{ i18n.t("project.actions.staffNotes", [project.info.noteCount]) }}
|
||||
</DropdownItem>
|
||||
<DropdownItem :to="`/admin/log/?projectFilter=/${slug}`">
|
||||
<DropdownItem :to="`/admin/log?projectFilter=/${slug}`">
|
||||
{{ i18n.t("project.actions.userActionLogs") }}
|
||||
</DropdownItem>
|
||||
<DropdownItem v-if="project.topicId" :href="forumUrl(project.topicId)">
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit e6e51d36f08276d341a6a8eebc44426848405128
|
||||
Subproject commit ffcce32128af5973c3e4d2e210355dacf9d99b1e
|
@ -2,9 +2,9 @@
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { PaginatedResult } from "hangar-api";
|
||||
import { PaginatedResult, Project, User } from "hangar-api";
|
||||
import { computed, ref } from "vue";
|
||||
import { LoggedAction, LoggedActionType } from "hangar-internal";
|
||||
import { LoggedAction } from "hangar-internal";
|
||||
import { debounce } from "lodash-es";
|
||||
import PageTitle from "~/lib/components/design/PageTitle.vue";
|
||||
import { useActionLogs } from "~/composables/useApiHelper";
|
||||
@ -15,11 +15,13 @@ 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, watch } from "#imports";
|
||||
import { definePageMeta, hasPerms, useApi, useInternalApi, useRouter, watch } from "#imports";
|
||||
import InputText from "~/lib/components/ui/InputText.vue";
|
||||
import InputSelect from "~/lib/components/ui/InputSelect.vue";
|
||||
import { useBackendData } from "~/store/backendData";
|
||||
import { Header } from "~/types/components/SortableTable";
|
||||
import { NamedPermission } from "~/types/enums";
|
||||
import InputAutocomplete from "~/lib/components/ui/InputAutocomplete.vue";
|
||||
|
||||
definePageMeta({
|
||||
globalPermsRequired: ["VIEW_LOGS"],
|
||||
@ -40,14 +42,19 @@ const headers: Header[] = [
|
||||
{ title: i18n.t("userActionLog.newState"), name: "newState", sortable: false },
|
||||
];
|
||||
|
||||
if (!hasPerms(NamedPermission.SEE_IP_ADDRESSES)) {
|
||||
headers.splice(1, 1);
|
||||
}
|
||||
|
||||
const page = ref(0);
|
||||
const sort = ref<string[]>([]);
|
||||
const filter = ref<{
|
||||
user?: string;
|
||||
logAction?: LoggedActionType;
|
||||
logAction?: string;
|
||||
authorName?: string;
|
||||
projectSlug?: string;
|
||||
}>({});
|
||||
|
||||
const requestParams = computed(() => {
|
||||
const limit = 25;
|
||||
return {
|
||||
@ -78,8 +85,45 @@ async function updatePage(newPage: number) {
|
||||
await update();
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
async function update() {
|
||||
loggedActions.value = await useInternalApi<PaginatedResult<LoggedAction>>("admin/log/", "GET", requestParams.value);
|
||||
loggedActions.value = await useInternalApi<PaginatedResult<LoggedAction>>("admin/log", "GET", requestParams.value);
|
||||
const { limit, offset, ...paramsWithoutLimit } = requestParams.value;
|
||||
await router.replace({ query: { ...paramsWithoutLimit } });
|
||||
}
|
||||
|
||||
const userSearchResult = ref<string[]>([]);
|
||||
const authorSearchResult = ref<string[]>([]);
|
||||
const projectSearchResult = ref<string[]>([]);
|
||||
|
||||
async function searchUser(val: string) {
|
||||
userSearchResult.value = [];
|
||||
const users = await useApi<PaginatedResult<User>>("users", "get", {
|
||||
query: val,
|
||||
limit: 25,
|
||||
offset: 0,
|
||||
});
|
||||
userSearchResult.value = users.result.filter((u) => !u.isOrganization).map((u) => u.name);
|
||||
}
|
||||
|
||||
async function searchAuthor(val: string) {
|
||||
authorSearchResult.value = [];
|
||||
const authors = await useApi<PaginatedResult<User>>("users", "get", {
|
||||
query: val,
|
||||
limit: 25,
|
||||
offset: 0,
|
||||
});
|
||||
authorSearchResult.value = authors.result.map((u) => u.name);
|
||||
}
|
||||
|
||||
async function searchProject(val: string) {
|
||||
projectSearchResult.value = [];
|
||||
const projects = await useApi<PaginatedResult<Project>>("projects", "get", {
|
||||
q: val,
|
||||
limit: 25,
|
||||
offset: 0,
|
||||
});
|
||||
projectSearchResult.value = projects.result.map((u) => u.namespace.slug);
|
||||
}
|
||||
|
||||
useHead(useSeo(i18n.t("userActionLog.title"), null, route, null));
|
||||
@ -91,16 +135,23 @@ useHead(useSeo(i18n.t("userActionLog.title"), null, route, null));
|
||||
<Card>
|
||||
<div class="flex mb-4 gap-2">
|
||||
<div class="basis-3/12">
|
||||
<InputText v-model="filter.user" label="User" />
|
||||
<InputAutocomplete
|
||||
id="userfilter"
|
||||
v-model="filter.user"
|
||||
:values="userSearchResult"
|
||||
:label="i18n.t('organization.settings.transferModal.transferTo')"
|
||||
@search="searchUser"
|
||||
/>
|
||||
</div>
|
||||
<div class="basis-3/12">
|
||||
<InputSelect v-model="filter.logAction" :values="useBackendData.loggedActions" label="Action" />
|
||||
<span v-if="filter.logAction" class="flex justify-center" cursor="pointer" @click="filter.logAction = undefined"> Clear selected action </span>
|
||||
</div>
|
||||
<div class="basis-3/12">
|
||||
<InputText v-model="filter.authorName" label="Author Name" />
|
||||
<InputAutocomplete id="authorfilter" v-model="filter.authorName" :values="authorSearchResult" label="Author Name" @search="searchAuthor" />
|
||||
</div>
|
||||
<div class="basis-3/12">
|
||||
<InputText v-model="filter.projectSlug" label="Project Slug" />
|
||||
<InputAutocomplete id="projectfilter" v-model="filter.projectSlug" :values="projectSearchResult" label="Project Slug" @search="searchProject" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -76,6 +76,7 @@ export enum NamedPermission {
|
||||
HARD_DELETE_PROJECT = "hard_delete_project",
|
||||
HARD_DELETE_VERSION = "hard_delete_version",
|
||||
EDIT_ALL_USER_SETTINGS = "edit_all_user_settings",
|
||||
SEE_IP_ADDRESSES = "see_ip_addresses",
|
||||
}
|
||||
|
||||
export enum Platform {
|
||||
|
Loading…
Reference in New Issue
Block a user