mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-23 15:12:52 +08:00
allow role management (closes #572)
This commit is contained in:
parent
1edb8d9af9
commit
c447bcb981
@ -835,7 +835,8 @@
|
||||
"accepted": "Accepted",
|
||||
"sidebar": "Other Administration",
|
||||
"hangarAuth": "HangarAuth Profile",
|
||||
"forum": "Forum Profile"
|
||||
"forum": "Forum Profile",
|
||||
"roles": "Roles"
|
||||
},
|
||||
"userActionLog": {
|
||||
"title": "User Action Log",
|
||||
|
@ -4,21 +4,29 @@ import { useI18n } from "vue-i18n";
|
||||
import Link from "~/components/design/Link.vue";
|
||||
import Card from "~/components/design/Card.vue";
|
||||
import { useApi, useInternalApi } from "~/composables/useApi";
|
||||
import { PaginatedResult, Project } from "hangar-api";
|
||||
import { useRoute } from "vue-router";
|
||||
import { PaginatedResult, Project, User } from "hangar-api";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { handleRequestError } from "~/composables/useErrorHandling";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
import { OrganizationRoleTable } from "hangar-internal";
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import SortableTable, { Header } from "~/components/SortableTable.vue";
|
||||
import InputCheckbox from "~/components/ui/InputCheckbox.vue";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useSeo } from "~/composables/useSeo";
|
||||
import { authUrl, forumUserUrl } from "~/composables/useUrlHelper";
|
||||
import { useUser } from "~/composables/useApiHelper";
|
||||
import Tag from "~/components/Tag.vue";
|
||||
import InputSelect from "~/components/ui/InputSelect.vue";
|
||||
import { useBackendDataStore } from "~/store/backendData";
|
||||
import Button from "~/components/design/Button.vue";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
const i18n = useI18n();
|
||||
const route = useRoute();
|
||||
const ctx = useContext();
|
||||
const router = useRouter();
|
||||
const backendData = useBackendDataStore();
|
||||
|
||||
const projects = await useApi<PaginatedResult<Project>>("projects", false, "get", {
|
||||
owner: route.params.user,
|
||||
@ -26,6 +34,7 @@ const projects = await useApi<PaginatedResult<Project>>("projects", false, "get"
|
||||
const orgs = await useInternalApi<{ [key: string]: OrganizationRoleTable }>(`organizations/${route.params.user}/userOrganizations`, false).catch((e) =>
|
||||
handleRequestError(e, ctx, i18n)
|
||||
);
|
||||
const user = await useUser(route.params.user as string).catch((e) => handleRequestError(e, ctx, i18n));
|
||||
|
||||
const projectsConfig = [
|
||||
{ title: i18n.t("userAdmin.project"), name: "name" },
|
||||
@ -52,6 +61,18 @@ const orgList = computed(() => {
|
||||
const _forumUserUrl = computed(() => forumUserUrl(route.params.user as string));
|
||||
const _authUrl = computed(() => authUrl(route.params.user as string));
|
||||
|
||||
const selectedRole = ref();
|
||||
async function processRole(add: boolean) {
|
||||
try {
|
||||
await useInternalApi("/admin/user/" + route.params.user + "/" + selectedRole.value, true, add ? "POST" : "DELETE");
|
||||
if (user) {
|
||||
user.value = await useApi<User>(("users/" + route.params.user) as string);
|
||||
}
|
||||
} catch (e) {
|
||||
handleRequestError(e as AxiosError, ctx, i18n);
|
||||
}
|
||||
}
|
||||
|
||||
useHead(useSeo(i18n.t("userAdmin.title") + " " + route.params.user, null, route, null));
|
||||
</script>
|
||||
|
||||
@ -108,7 +129,24 @@ useHead(useSeo(i18n.t("userAdmin.title") + " " + route.params.user, null, route,
|
||||
</template>
|
||||
</SortableTable>
|
||||
</Card>
|
||||
<Card md="col-start-2 row-start-1 row-end-2">
|
||||
<Card md="col-start-2 row-start-1">
|
||||
<template #header>{{ i18n.t("userAdmin.roles") }}</template>
|
||||
|
||||
<Tag v-for="role in user.roles" :key="role.value" :color="{ background: role.color }" :name="role.title" />
|
||||
|
||||
<div class="flex mt-2">
|
||||
<div class="flex-grow">
|
||||
<InputSelect v-model="selectedRole" :values="backendData.globalRoles" item-text="title" item-value="value"></InputSelect>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="medium" @click="processRole(true)">{{ i18n.t("general.add") }}</Button>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<Button size="medium" @click="processRole(false)">{{ i18n.t("general.delete") }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card md="col-start-2 row-start-2">
|
||||
<template #header>{{ i18n.t("userAdmin.sidebar") }}</template>
|
||||
|
||||
<ul>
|
||||
|
@ -44,6 +44,7 @@ export const useBackendDataStore = defineStore("backendData", () => {
|
||||
const licenses = ref<string[]>([]);
|
||||
const orgRoles = ref<Role[]>([]);
|
||||
const projectRoles = ref<Role[]>([]);
|
||||
const globalRoles = ref<Role[]>([]);
|
||||
const channelColors = ref<Color[]>([]);
|
||||
const flagReasons = ref<FlagReason[]>([]);
|
||||
|
||||
@ -86,6 +87,7 @@ export const useBackendDataStore = defineStore("backendData", () => {
|
||||
fetchIfNeeded(async () => useInternalApi("data/orgRoles", false), orgRoles),
|
||||
fetchIfNeeded(async () => useInternalApi("data/channelColors", false), channelColors),
|
||||
fetchIfNeeded(async () => useInternalApi("data/projectRoles", false), projectRoles),
|
||||
fetchIfNeeded(async () => useInternalApi("data/globalRoles", false), globalRoles),
|
||||
fetchIfNeeded(async () => useInternalApi("data/flagReasons", false), flagReasons),
|
||||
]);
|
||||
} catch (e) {
|
||||
@ -113,6 +115,7 @@ export const useBackendDataStore = defineStore("backendData", () => {
|
||||
visibilities,
|
||||
orgRoles,
|
||||
projectRoles,
|
||||
globalRoles,
|
||||
channelColors,
|
||||
flagReasons,
|
||||
initBackendData,
|
||||
|
@ -86,6 +86,7 @@ public class WebConfig extends WebMvcConfigurationSupport {
|
||||
} else {
|
||||
corsRegistration.allowedOrigins(hangarConfig.getBaseUrl());
|
||||
}
|
||||
corsRegistration.allowedMethods("GET", "HEAD", "POST", "DELETE");
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -17,6 +17,7 @@ import io.papermc.hangar.model.common.Prompt;
|
||||
import io.papermc.hangar.model.common.projects.Category;
|
||||
import io.papermc.hangar.model.common.projects.FlagReason;
|
||||
import io.papermc.hangar.model.common.projects.Visibility;
|
||||
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.security.annotations.Anyone;
|
||||
@ -33,6 +34,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@ -132,6 +134,11 @@ public class BackendDataController {
|
||||
return ResponseEntity.ok(ProjectRole.getAssignableRoles());
|
||||
}
|
||||
|
||||
@GetMapping("/globalRoles")
|
||||
public ResponseEntity<List<GlobalRole>> getGlobalRoles() {
|
||||
return ResponseEntity.ok(Arrays.stream(GlobalRole.values()).toList());
|
||||
}
|
||||
|
||||
@GetMapping("/orgRoles")
|
||||
public ResponseEntity<List<OrganizationRole>> getAssignableOrganizationRoles() {
|
||||
return ResponseEntity.ok(OrganizationRole.getAssignableRoles());
|
||||
|
@ -14,8 +14,10 @@ import io.papermc.hangar.controller.extras.pagination.filters.log.LogVersionFilt
|
||||
import io.papermc.hangar.model.api.PaginatedResult;
|
||||
import io.papermc.hangar.model.api.requests.RequestPagination;
|
||||
import io.papermc.hangar.model.common.NamedPermission;
|
||||
import io.papermc.hangar.model.common.roles.GlobalRole;
|
||||
import io.papermc.hangar.model.db.JobTable;
|
||||
import io.papermc.hangar.model.db.UserTable;
|
||||
import io.papermc.hangar.model.db.roles.GlobalRoleTable;
|
||||
import io.papermc.hangar.model.internal.admin.health.MissingFileCheck;
|
||||
import io.papermc.hangar.model.internal.admin.health.UnhealthyProject;
|
||||
import io.papermc.hangar.model.internal.api.requests.StringContent;
|
||||
@ -28,6 +30,7 @@ import io.papermc.hangar.service.internal.JobService;
|
||||
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.perms.roles.GlobalRoleService;
|
||||
import io.papermc.hangar.service.internal.users.UserService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -36,6 +39,7 @@ import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@ -60,15 +64,17 @@ public class AdminController extends HangarComponent {
|
||||
private final JobService jobService;
|
||||
private final UserService userService;
|
||||
private final ObjectMapper mapper;
|
||||
private final GlobalRoleService globalRoleService;
|
||||
|
||||
@Autowired
|
||||
public AdminController(PlatformService platformService, StatService statService, HealthService healthService, JobService jobService, UserService userService, ObjectMapper mapper) {
|
||||
public AdminController(PlatformService platformService, StatService statService, HealthService healthService, JobService jobService, UserService userService, ObjectMapper mapper, GlobalRoleService globalRoleService) {
|
||||
this.platformService = platformService;
|
||||
this.statService = statService;
|
||||
this.healthService = healthService;
|
||||
this.jobService = jobService;
|
||||
this.userService = userService;
|
||||
this.mapper = mapper;
|
||||
this.globalRoleService = globalRoleService;
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ -121,4 +127,18 @@ public class AdminController extends HangarComponent {
|
||||
public PaginatedResult<HangarLoggedAction> getActionLog(@NotNull @ConfigurePagination(maxLimit = 50) RequestPagination pagination) {
|
||||
return actionLogger.getLogs(pagination);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@PermissionRequired(NamedPermission.EDIT_ALL_USER_SETTINGS)
|
||||
@PostMapping(value = "/user/{user}/{role}")
|
||||
public void addRole(@PathVariable UserTable user, @PathVariable String role) {
|
||||
globalRoleService.addRole(new GlobalRoleTable(user.getUserId(), GlobalRole.byApiValue(role)));
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@PermissionRequired(NamedPermission.EDIT_ALL_USER_SETTINGS)
|
||||
@DeleteMapping(value = "/user/{user}/{role}")
|
||||
public void removeRole(@PathVariable UserTable user, @PathVariable String role) {
|
||||
globalRoleService.deleteRole(new GlobalRoleTable(user.getUserId(), GlobalRole.byApiValue(role)));
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
package io.papermc.hangar.model.common.roles;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import io.papermc.hangar.db.customtypes.RoleCategory;
|
||||
import io.papermc.hangar.model.common.Color;
|
||||
import io.papermc.hangar.model.common.Permission;
|
||||
import io.papermc.hangar.model.db.roles.GlobalRoleTable;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -30,10 +32,10 @@ public enum GlobalRole implements Role<GlobalRoleTable> {
|
||||
ADVISOR("Advisor", 13, Permission.None, "Advisor", Color.AQUA),
|
||||
|
||||
STONE_DONOR("Stone_Donor", 14, Permission.None, "Stone Donor", Color.GRAY, 5),
|
||||
QUARTZ_DONOR("Quartz_Donor",15, Permission.None, "Quartz Donor", Color.QUARTZ, 4),
|
||||
IRON_DONOR("Iron_Donor",16, Permission.None, "Iron Donor", Color.SILVER, 3),
|
||||
GOLD_DONOR("Gold_Donor",17, Permission.None, "Gold Donor", Color.GOLD, 2),
|
||||
DIAMOND_DONOR("Diamond_Donor",18, Permission.None, "Diamond Donor", Color.LIGHTBLUE, 1),
|
||||
QUARTZ_DONOR("Quartz_Donor", 15, Permission.None, "Quartz Donor", Color.QUARTZ, 4),
|
||||
IRON_DONOR("Iron_Donor", 16, Permission.None, "Iron Donor", Color.SILVER, 3),
|
||||
GOLD_DONOR("Gold_Donor", 17, Permission.None, "Gold Donor", Color.GOLD, 2),
|
||||
DIAMOND_DONOR("Diamond_Donor", 18, Permission.None, "Diamond Donor", Color.LIGHTBLUE, 1),
|
||||
|
||||
ORGANIZATION("Organization", 23, OrganizationRole.ORGANIZATION_OWNER.getPermissions(), "Organization", Color.PURPLE);
|
||||
|
||||
@ -108,4 +110,13 @@ public enum GlobalRole implements Role<GlobalRoleTable> {
|
||||
public GlobalRoleTable create(@Nullable Long ignored, long userId, boolean ignoredToo) {
|
||||
return new GlobalRoleTable(userId, this);
|
||||
}
|
||||
|
||||
public static GlobalRole byApiValue(String apiValue) {
|
||||
for (GlobalRole value : values()) {
|
||||
if (value.value.endsWith(apiValue)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No GlobalRole '" + apiValue + "'");
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public enum OrganizationRole implements Role<OrganizationRoleTable> {
|
||||
private final boolean isAssignable;
|
||||
|
||||
private static final OrganizationRole[] VALUES = values();
|
||||
private static final List<OrganizationRole> ASSIGNABLE_ROLES = Arrays.stream(VALUES).filter(OrganizationRole::isAssignable).collect(Collectors.toList());
|
||||
private static final List<OrganizationRole> ASSIGNABLE_ROLES = Arrays.stream(VALUES).filter(OrganizationRole::isAssignable).toList();
|
||||
|
||||
public static OrganizationRole[] getValues() { return VALUES; }
|
||||
public static List<OrganizationRole> getAssignableRoles() { return ASSIGNABLE_ROLES; }
|
||||
|
@ -30,7 +30,7 @@ public enum ProjectRole implements Role<ProjectRoleTable> {
|
||||
private final boolean isAssignable;
|
||||
|
||||
private static final ProjectRole[] VALUES = values();
|
||||
private static final List<ProjectRole> ASSIGNABLE_ROLES = Arrays.stream(VALUES).filter(ProjectRole::isAssignable).collect(Collectors.toList());
|
||||
private static final List<ProjectRole> ASSIGNABLE_ROLES = Arrays.stream(VALUES).filter(ProjectRole::isAssignable).toList();
|
||||
|
||||
public static ProjectRole[] getValues() {
|
||||
return VALUES;
|
||||
|
Loading…
Reference in New Issue
Block a user