mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-23 15:12:52 +08:00
api key creation vue component
This commit is contained in:
parent
6db5a2752b
commit
b5ec1dbc69
112
src/main/frontend/src/ApiKeyManagement.vue
Normal file
112
src/main/frontend/src/ApiKeyManagement.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div v-show="error" id="keyAlert" class="alert alert-danger" role="alert" v-text="error"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2 v-text="$t('user.apiKeys.createNew')"></h2>
|
||||
<div class="row">
|
||||
<div v-for="(chunk, index) in possiblePermissions" :key="`perm-chunk-${index}`" class="col-md-6">
|
||||
<div v-for="perm in chunk" :key="perm.value" class="checkbox">
|
||||
<label> <input v-model="form.perms" type="checkbox" :value="perm.value" /> {{ perm.name }} </label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<label for="new-key-name-input" class="input-group-text" v-text="$t('user.apiKeys.keyName')"></label>
|
||||
</div>
|
||||
<input v-model.trim="form.name" type="text" class="form-control" id="new-key-name-input" />
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
class="btn input-group-btn btn-primary"
|
||||
:disabled="!form.perms.length || !form.name"
|
||||
v-text="$t('user.apiKeys.createKeyBtn')"
|
||||
@click="createKey"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h2 v-text="$t('user.apiKeys.existingKeys')"></h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-text="$t('user.apiKeys.keyName')"></th>
|
||||
<th v-text="$t('user.apiKeys.keyToken')"></th>
|
||||
<th v-text="$t('user.apiKeys.keyIdentifier')"></th>
|
||||
<th v-text="$t('user.apiKeys.keyPermissions')"></th>
|
||||
<th v-text="$t('user.apiKeys.keyDeleteColumn')"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="key in existingKeys" :key="key.id">
|
||||
<th v-text="key.name"></th>
|
||||
<th v-text="key.token"></th>
|
||||
<th v-text="key.tokenIdentifier"></th>
|
||||
<th v-text="key.namedRawPermissions.join(', ')"></th>
|
||||
<th><button class="btn btn-danger" v-text="$t('user.apiKeys.keyDeleteButton')" @click="deleteKey(key)"></button></th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { chunk, remove } from 'lodash-es';
|
||||
import { API } from '@/api';
|
||||
|
||||
export default {
|
||||
name: 'ApiKeyManagement',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
perms: [],
|
||||
name: '',
|
||||
},
|
||||
error: null,
|
||||
possiblePermissions: chunk(window.API_KEY_PERMISSIONS, window.API_KEY_PERMISSIONS.length / 2),
|
||||
existingKeys: window.EXISTING_KEYS,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
createKey() {
|
||||
this.error = null;
|
||||
if (this.form.perms.length === 0) {
|
||||
this.error = this.$t('user.apiKeys.error.noPermsSet');
|
||||
return;
|
||||
}
|
||||
if (!this.form.name || !this.form.name.trim()) {
|
||||
this.error = this.$t('user.apiKeys.error.noNameSet');
|
||||
return;
|
||||
}
|
||||
if (this.form.name.length > 255) {
|
||||
this.error = this.$t('user.apiKeys.error.tooLongName');
|
||||
return;
|
||||
}
|
||||
if (this.existingKeys.find((key) => key.name === this.form.name)) {
|
||||
this.error = this.$t('user.apiKeys.error.nameAlreadyUsed');
|
||||
return;
|
||||
}
|
||||
API.request('keys', 'POST', {
|
||||
permissions: this.form.perms,
|
||||
name: this.form.name,
|
||||
}).then((newKey) => {
|
||||
this.error = null;
|
||||
this.existingKeys.push({
|
||||
name: this.form.name,
|
||||
token: newKey.key,
|
||||
namedRawPermissions: newKey.perms,
|
||||
});
|
||||
this.form.name = '';
|
||||
this.form.perms = [];
|
||||
});
|
||||
},
|
||||
deleteKey(key) {
|
||||
API.request(`keys?name=${key.name}`, 'DELETE').then(() => {
|
||||
remove(this.existingKeys, (k) => k.name === key.name);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
@ -81,7 +81,7 @@
|
||||
@prev="$emit('prev-page')"
|
||||
></Pagination>
|
||||
</div>
|
||||
<div v-else class="list-group-item empty-project-list">
|
||||
<div v-else class="list-group-item empty-project-list d-flex align-items-center">
|
||||
<i class="far fa-2x fa-sad-tear"></i>
|
||||
<span>Oops! No projects found...</span>
|
||||
</div>
|
||||
|
6
src/main/frontend/src/entrypoints/api-key-management.js
Normal file
6
src/main/frontend/src/entrypoints/api-key-management.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { createApp } from 'vue';
|
||||
import ApiKeyManagement from '@/ApiKeyManagement';
|
||||
import { setupI18n } from '@/plugins/i18n';
|
||||
|
||||
const i18n = setupI18n();
|
||||
createApp(ApiKeyManagement).use(i18n).mount('#api-key-management');
|
@ -1,95 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import { apiV2Request } from '@/js/apiRequests';
|
||||
|
||||
//=====> EXTERNAL CONSTANTS
|
||||
|
||||
var NO_PERMS_SET = window.NO_PERMS_SET;
|
||||
var NO_NAME_SET = window.NO_NAME_SET;
|
||||
var TOO_LONG_NAME = window.TOO_LONG_NAME;
|
||||
var NAMED_USED = window.NAMED_USED;
|
||||
var DELETE_KEY = window.DELETE_KEY;
|
||||
|
||||
//=====> HELPER FUNCTIONS
|
||||
|
||||
function deleteKey(name, row) {
|
||||
return function () {
|
||||
apiV2Request('keys?name=' + name, 'DELETE').then(function () {
|
||||
row.remove();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function showError(error) {
|
||||
var alert = $('#keyAlert');
|
||||
alert.text(error);
|
||||
alert.show();
|
||||
}
|
||||
|
||||
//=====> DOCUMENT READY
|
||||
|
||||
$(function () {
|
||||
$('.api-key-row').each(function () {
|
||||
var row = $(this);
|
||||
var name = row.find('.api-key-name').text();
|
||||
row.find('.api-key-row-delete-button').click(deleteKey(name, row));
|
||||
});
|
||||
|
||||
$('#button-create-new-key').click(function () {
|
||||
var checked = [];
|
||||
$('#api-create-key-form')
|
||||
.find('input[type=checkbox]')
|
||||
.filter("input[id^='perm.']")
|
||||
.filter(':checked')
|
||||
.each(function () {
|
||||
checked.push($(this).attr('id').substr('perm.'.length));
|
||||
});
|
||||
var name = $('#keyName').val();
|
||||
|
||||
if (checked.length === 0) {
|
||||
showError(NO_PERMS_SET);
|
||||
} else {
|
||||
var hasName = name.length !== 0;
|
||||
if (!hasName) {
|
||||
showError(NO_NAME_SET);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.length > 255) {
|
||||
showError(TOO_LONG_NAME);
|
||||
return;
|
||||
}
|
||||
var nameTaken = $('.api-key-name:contains(' + name + ')').length;
|
||||
if (nameTaken !== 0) {
|
||||
showError(NAMED_USED);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
permissions: checked,
|
||||
name: name,
|
||||
};
|
||||
|
||||
apiV2Request('keys', 'POST', data).then(function (newKey) {
|
||||
$('#keyAlert').hide();
|
||||
var namedPerms = '';
|
||||
|
||||
for (let perm of checked) {
|
||||
namedPerms += perm + ', ';
|
||||
}
|
||||
|
||||
namedPerms.substr(0, namedPerms.length - 2);
|
||||
|
||||
var row = $('<tr>');
|
||||
var token = newKey.key;
|
||||
|
||||
row.append($('<th>').addClass('api-key-name').text(name));
|
||||
row.append($('<th>').text(token));
|
||||
row.append($('<th>'));
|
||||
row.append($('<th>').text(namedPerms));
|
||||
row.append($('<th>').append($('<button>').addClass('btn btn-danger api-key-row-delete-button').text(DELETE_KEY).click(deleteKey(name, row))));
|
||||
|
||||
$('#api-key-rows:last-child').append(row);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
@ -39,21 +39,33 @@
|
||||
span, select { margin-left: auto; }
|
||||
}
|
||||
|
||||
.username {
|
||||
svg {
|
||||
font-size: 0.6em;
|
||||
font-weight: normal;
|
||||
padding: 3px;
|
||||
.user-title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
.user-actions {
|
||||
float: left;
|
||||
margin-left: 0.5em;
|
||||
|
||||
& > a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 1.5em;
|
||||
font-weight: normal;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.action-lock-account:hover, .action-api svg:hover, .user-settings svg:hover {
|
||||
background-color: gray;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
svg { cursor: pointer; }
|
||||
|
||||
.action-api, .user-settings { color: #333; }
|
||||
}
|
||||
|
||||
.action-lock-account:hover, .action-api svg:hover, .user-settings svg:hover {
|
||||
background-color: gray;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
svg { cursor: pointer; }
|
||||
|
||||
.action-api, .user-settings { color: #333; }
|
||||
}
|
||||
|
||||
.organization-avatar {
|
||||
@ -90,7 +102,6 @@
|
||||
.user-avatar { margin-right: 20px; }
|
||||
}
|
||||
|
||||
.user-badge > h1 { float: left; }
|
||||
.float-right { @include size(20%, 100%); }
|
||||
.user-roles li { display: inline; }
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
package io.papermc.hangar.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.db.customtypes.LoggedActionType;
|
||||
import io.papermc.hangar.db.dao.HangarDao;
|
||||
import io.papermc.hangar.db.dao.UserDao;
|
||||
import io.papermc.hangar.db.model.NotificationsTable;
|
||||
import io.papermc.hangar.db.model.OrganizationsTable;
|
||||
import io.papermc.hangar.db.model.UserSessionsTable;
|
||||
import io.papermc.hangar.db.model.UsersTable;
|
||||
import io.papermc.hangar.exceptions.HangarException;
|
||||
@ -14,6 +14,8 @@ import io.papermc.hangar.model.NamedPermission;
|
||||
import io.papermc.hangar.model.NotificationFilter;
|
||||
import io.papermc.hangar.model.Prompt;
|
||||
import io.papermc.hangar.model.viewhelpers.InviteSubject;
|
||||
import io.papermc.hangar.model.viewhelpers.OrganizationData;
|
||||
import io.papermc.hangar.model.viewhelpers.ScopedOrganizationData;
|
||||
import io.papermc.hangar.model.viewhelpers.UserData;
|
||||
import io.papermc.hangar.model.viewhelpers.UserRole;
|
||||
import io.papermc.hangar.security.annotations.GlobalPermission;
|
||||
@ -54,13 +56,16 @@ import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
public class UsersController extends HangarController {
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
private final HangarConfig hangarConfig;
|
||||
private final AuthenticationService authenticationService;
|
||||
private final UserService userService;
|
||||
@ -81,7 +86,8 @@ public class UsersController extends HangarController {
|
||||
|
||||
|
||||
@Autowired
|
||||
public UsersController(HangarConfig hangarConfig, AuthenticationService authenticationService, UserService userService, OrgService orgService, RoleService roleService, ApiKeyService apiKeyService, PermissionService permissionService, SessionService sessionService, NotificationService notificationService, SsoService ssoService, UserActionLogService userActionLogService, HangarDao<UserDao> userDao, SitemapService sitemapService, HttpServletRequest request, HttpServletResponse response, Supplier<UsersTable> usersTable) {
|
||||
public UsersController(ObjectMapper mapper, HangarConfig hangarConfig, AuthenticationService authenticationService, UserService userService, OrgService orgService, RoleService roleService, ApiKeyService apiKeyService, PermissionService permissionService, SessionService sessionService, NotificationService notificationService, SsoService ssoService, UserActionLogService userActionLogService, HangarDao<UserDao> userDao, SitemapService sitemapService, HttpServletRequest request, HttpServletResponse response, Supplier<UsersTable> usersTable) {
|
||||
this.mapper = mapper;
|
||||
this.hangarConfig = hangarConfig;
|
||||
this.authenticationService = authenticationService;
|
||||
this.userService = userService;
|
||||
@ -238,12 +244,12 @@ public class UsersController extends HangarController {
|
||||
@GetMapping("/{user}")
|
||||
public ModelAndView showProjects(@PathVariable String user) {
|
||||
ModelAndView mav = new ModelAndView("users/projects");
|
||||
OrganizationsTable organizationsTable = orgService.getOrganization(user);
|
||||
UserData userData = userService.getUserData(user);
|
||||
Optional<OrganizationData> orgData = Optional.ofNullable(orgService.getOrganizationData(userData.getUser()));
|
||||
Optional<ScopedOrganizationData> scopedOrgData = orgData.map(organizationData -> orgService.getScopedOrganizationData(organizationData.getOrg()));
|
||||
mav.addObject("u", userService.getUserData(user));
|
||||
if (organizationsTable != null) {
|
||||
mav.addObject("o", orgService.getOrganizationData(organizationsTable, null));
|
||||
mav.addObject("so", orgService.getScopedOrganizationData(organizationsTable));
|
||||
}
|
||||
mav.addObject("o", orgData);
|
||||
mav.addObject("so", scopedOrgData);
|
||||
return fillModel(mav);
|
||||
}
|
||||
|
||||
@ -253,10 +259,15 @@ public class UsersController extends HangarController {
|
||||
public ModelAndView editApiKeys(@PathVariable String user) {
|
||||
ModelAndView mav = new ModelAndView("users/apiKeys");
|
||||
UserData userData = userService.getUserData(user);
|
||||
Optional<OrganizationData> orgData = Optional.ofNullable(orgService.getOrganizationData(userData.getUser()));
|
||||
Optional<ScopedOrganizationData> scopedOrgData = orgData.map(organizationData -> orgService.getScopedOrganizationData(organizationData.getOrg()));
|
||||
long userId = userData.getUser().getId();
|
||||
mav.addObject("u", userData);
|
||||
mav.addObject("o", orgData);
|
||||
mav.addObject("so", scopedOrgData);
|
||||
mav.addObject("keys", apiKeyService.getKeys(userId));
|
||||
mav.addObject("perms", permissionService.getPossibleOrganizationPermissions(userId).add(permissionService.getPossibleProjectPermissions(userId)).add(userData.getUserPerm()).toNamed());
|
||||
List<NamedPermission> perms = permissionService.getPossibleOrganizationPermissions(userId).add(permissionService.getPossibleProjectPermissions(userId)).add(userData.getUserPerm()).toNamed();
|
||||
mav.addObject("perms", perms.stream().map(perm -> mapper.createObjectNode().put("value", perm.toString()).put("name", perm.getFrontendName())).collect(Collectors.toList()));
|
||||
return fillModel(mav);
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,9 @@ public interface ApiKeyDao {
|
||||
@SqlUpdate("DELETE FROM api_keys k WHERE k.name = :keyName AND k.owner_id = :ownerId")
|
||||
int delete(String keyName, long ownerId);
|
||||
|
||||
// Frontend key request, only show non-private info
|
||||
@RegisterBeanMapper(ApiKey.class)
|
||||
@SqlQuery("SELECT *, raw_key_permissions::BIGINT perm_value FROM api_keys WHERE owner_id = :ownerId")
|
||||
@SqlQuery("SELECT id, name, token_identifier, raw_key_permissions::BIGINT perm_value FROM api_keys WHERE owner_id = :ownerId")
|
||||
List<ApiKey> getByOwner(long ownerId);
|
||||
|
||||
@SqlQuery("SELECT *, raw_key_permissions::BIGINT perm_value FROM api_keys WHERE name = :keyName AND owner_id = :ownerId")
|
||||
|
@ -1,18 +1,56 @@
|
||||
package io.papermc.hangar.model.viewhelpers;
|
||||
|
||||
import io.papermc.hangar.db.model.ApiKeysTable;
|
||||
import io.papermc.hangar.model.NamedPermission;
|
||||
import io.papermc.hangar.model.Permission;
|
||||
import org.jdbi.v3.core.mapper.Nested;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class ApiKey extends ApiKeysTable {
|
||||
public class ApiKey {
|
||||
|
||||
private long id;
|
||||
private String name;
|
||||
private String tokenIdentifier;
|
||||
private Permission rawKeyPermissions;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getTokenIdentifier() {
|
||||
return tokenIdentifier;
|
||||
}
|
||||
|
||||
public void setTokenIdentifier(String tokenIdentifier) {
|
||||
this.tokenIdentifier = tokenIdentifier;
|
||||
}
|
||||
|
||||
public Permission getRawKeyPermissions() {
|
||||
return rawKeyPermissions;
|
||||
}
|
||||
|
||||
@Nested("perm")
|
||||
public void setRawKeyPermissions(Permission rawKeyPermissions) {
|
||||
this.rawKeyPermissions = rawKeyPermissions;
|
||||
}
|
||||
|
||||
public boolean getIsSubKey(Permission perm) {
|
||||
return this.getRawKeyPermissions().has(perm);
|
||||
return rawKeyPermissions.has(perm);
|
||||
}
|
||||
|
||||
public Collection<NamedPermission> getNamedRawPermissions() {
|
||||
return this.getRawKeyPermissions().toNamed();
|
||||
return rawKeyPermissions.toNamed();
|
||||
}
|
||||
}
|
||||
|
@ -2,73 +2,15 @@
|
||||
<#import "*/utils/hangar.ftlh" as hangar />
|
||||
<#import "*/users/view.ftlh" as userView />
|
||||
|
||||
<#--@(u: UserData, o: Option[(OrganizationData, ScopedOrganizationData)], keys: Seq[ApiKey], perms: Seq[NamedPermission])(implicit messages: Messages, flash: Flash, request: OreRequest[_], config: OreConfig, assetsFinder: AssetsFinder)-->
|
||||
<#assign scriptsVar>
|
||||
<script <#--@CSPNonce.attr-->>
|
||||
window.NO_PERMS_SET = '<@spring.message "user.apiKeys.error.noPermsSet" />';
|
||||
window.NO_NAME_SET = '<@spring.message "user.apiKeys.error.noNameSet" />';
|
||||
window.TOO_LONG_NAME = '<@spring.message "user.apiKeys.error.tooLongName" />';
|
||||
window.NAMED_USED = '<@spring.message "user.apiKeys.error.nameAlreadyUsed" />';
|
||||
window.DELETE_KEY = '<@spring.message "user.apiKeys.keyDeleteButton" />';
|
||||
<script nonce="${nonce}">
|
||||
window.API_KEY_PERMISSIONS = ${mapper.valueToTree(perms)};
|
||||
window.EXISTING_KEYS = ${mapper.valueToTree(keys)};
|
||||
</script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/apiKeysManagement.js" />"></script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/api-key-management.js" />"></script>
|
||||
</#assign>
|
||||
|
||||
<#assign NamedPermission=@helper["io.papermc.hangar.model.NamedPermission"] />
|
||||
<@userView.view u=u o=o additionalScripts=scriptsVar>
|
||||
<div id="keyAlert" class="alert alert-danger" role="alert" style="display: none;"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2><@spring.message "user.apiKeys.createNew" /></h2>
|
||||
<div id="api-create-key-form">
|
||||
<div class="row">
|
||||
<#list perms?chunk( (perms?size/2)?int ) as chunk>
|
||||
<div class="col-md-6">
|
||||
<#list chunk as perm>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="perm.${perm.name()}"> ${perm.name()}
|
||||
</label>
|
||||
</div>
|
||||
</#list>
|
||||
</div>
|
||||
</#list>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="keyName"><@spring.message "user.apiKeys.keyName" />:</label>
|
||||
<input type="text" class="form-control" id="keyName">
|
||||
</div>
|
||||
<button id="button-create-new-key" class="btn btn-default"><@spring.message "user.apiKeys.createKeyBtn" /></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h2><@spring.message "user.apiKeys.existingKeys" /></h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><@spring.message "user.apiKeys.keyName" /></th>
|
||||
<th><@spring.message "user.apiKeys.keyToken" /></th>
|
||||
<th><@spring.message "user.apiKeys.keyIdentifier" /></th>
|
||||
<th><@spring.message "user.apiKeys.keyPermissions" /></th>
|
||||
<th><@spring.message "user.apiKeys.keyDeleteColumn" /></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="api-key-rows">
|
||||
<#list keys as key>
|
||||
<tr class="api-key-row">
|
||||
<th class="api-key-name">${key.name}</th>
|
||||
<th></th>
|
||||
<th>${key.tokenIdentifier}</th>
|
||||
<th>${key.namedRawPermissions?map(np -> np.name())?join(", ")}</th>
|
||||
<th><button class="btn btn-danger api-key-row-delete-button"><@spring.message "user.apiKeys.keyDeleteButton" /></button></th>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<@userView.view u=u o=o so=so additionalScripts=scriptsVar>
|
||||
<div id="api-key-management"></div>
|
||||
</@userView.view>
|
||||
|
@ -3,45 +3,36 @@
|
||||
<#import "*/users/view.ftlh" as users />
|
||||
<#import "*/users/memberList.ftlh" as memberList />
|
||||
|
||||
<#--@import controllers.Routes.IO_PAPERMC_HANGAR_MODEL__PERMISSION.getRouteUrl(.Requests.OreRequest
|
||||
@import models.viewhelper.{OrganizationData, ScopedOrganizationData, UserData}
|
||||
@import ore.OreConfig
|
||||
@import ore.permission.Permission
|
||||
@import ore.permission.role.Role
|
||||
@import util.syntax._
|
||||
@import views.html.utils.userAvatar
|
||||
@(u: UserData, o: Option[(OrganizationData, ScopedOrganizationData)])(
|
||||
implicit messages: Messages, flash: Flash, request: OreRequest[_], config: OreConfig, assetsFinder: AssetsFinder)-->
|
||||
<#assign Permission=@helper["io.papermc.hangar.model.Permission"] />
|
||||
<#assign Role=@helper["io.papermc.hangar.model.Role"] />
|
||||
|
||||
<#function canEditOrgMembers>
|
||||
<#return u.isOrga() && o?? && so.permissions.has(Permission.ManageOrganizationMembers)>
|
||||
<#return u.isOrga() && o.present && so.get().permissions.has(Permission.ManageOrganizationMembers)>
|
||||
</#function>
|
||||
|
||||
<#assign scriptsVar>
|
||||
<script type="text/javascript" src="<@hangar.url "js/user-profile.js" />"></script>
|
||||
<#if u.isOrga() && o?? && canEditOrgMembers()>
|
||||
<#if u.isOrga() && o.present && canEditOrgMembers()>
|
||||
<script type="text/javascript" src="<@hangar.url "js/orgInvites.js" />"></script>
|
||||
</#if>
|
||||
<script type="text/javascript" src="<@hangar.url "js/userSearch.js" />"></script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/memberList.js" />"></script>
|
||||
</#assign>
|
||||
|
||||
<@users.view u=u o=o additionalScripts=scriptsVar>
|
||||
<@users.view u=u o=o so=so additionalScripts=scriptsVar>
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div id="user-profile"></div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<#if u.isOrga() && o?? && canEditOrgMembers()>
|
||||
<#if u.isOrga() && o.present && canEditOrgMembers()>
|
||||
<div class="card-user-info card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title"><@spring.message "project.manager" /></h3>
|
||||
</div>
|
||||
<table class="table card-body">
|
||||
<tbody>
|
||||
<#list o.projectRoles as role, project>
|
||||
<#list o.get().projectRoles as role, project>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${Routes.PROJECTS_SHOW.getRouteUrl(project.ownerName, project.slug)}">${project.ownerName}/${project.slug}</a>
|
||||
@ -132,9 +123,9 @@
|
||||
</div>
|
||||
<#else>
|
||||
<#assign orgData=o> <#-- todo fix scopeddata -->
|
||||
<@memberList.memberList project=orgData editable=true perms=so.permissions
|
||||
saveCall=Routes.ORG_UPDATE_MEMBERS.getRouteUrl(orgData.org.name)
|
||||
removeCall=Routes.ORG_REMOVE_MEMBER.getRouteUrl(orgData.org.name) />
|
||||
<@memberList.memberList project=orgData.get() editable=true perms=so.get().permissions
|
||||
saveCall=Routes.ORG_UPDATE_MEMBERS.getRouteUrl(orgData.get().org.name)
|
||||
removeCall=Routes.ORG_REMOVE_MEMBER.getRouteUrl(orgData.get().org.name) />
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,28 +8,34 @@
|
||||
<#import "*/utils/csrf.ftlh" as csrf>
|
||||
<#import "*/utils/prompt.ftlh" as promptView>
|
||||
|
||||
<#-- @ftlvariable name="u" type="io.papermc.hangar.model.viewhelpers.UserData" -->
|
||||
<#-- @ftlvariable name="Permission" type="io.papermc.hangar.model.Permission" -->
|
||||
<#assign Permission=@helper["io.papermc.hangar.model.Permission"]>
|
||||
<#function canEditOrgSettings u o={}>
|
||||
<#return u.isOrga() && o?? && so.permissions.has(Permission.EditOrganizationSettings)>
|
||||
<#function canEditOrgSettings u o so>
|
||||
<#-- @ftlvariable name="u" type="io.papermc.hangar.model.viewhelpers.UserData" -->
|
||||
<#-- @ftlvariable name="o" type="java.util.Optional<io.papermc.hangar.model.viewhelpers.OrganizationData>" -->
|
||||
<#-- @ftlvariable name="so" type="java.util.Optional<io.papermc.hangar.model.viewhelpers.ScopedOrganizationData>" -->
|
||||
<#-- @ftlvariable name="Permission" type="io.papermc.hangar.model.Permission" -->
|
||||
<#assign Permission=@helper["io.papermc.hangar.model.Permission"]>
|
||||
<#return u.isOrga() && o.present && so.get().permissions.has(Permission.EditOrganizationSettings)>
|
||||
</#function>
|
||||
|
||||
<#macro view u o={} additionalScripts="">
|
||||
<#-- @ftlvariable name="u" type="io.papermc.hangar.model.viewhelpers.UserData" -->
|
||||
<#macro view u o so additionalScripts="">
|
||||
<#-- @ftlvariable name="u" type="io.papermc.hangar.model.viewhelpers.UserData" -->
|
||||
<#-- @ftlvariable name="o" type="java.util.Optional<io.papermc.hangar.model.viewhelpers.OrganizationData>" -->
|
||||
<#-- @ftlvariable name="so" type="java.util.Optional<io.papermc.hangar.model.viewhelpers.ScopedOrganizationData>" -->
|
||||
<#-- @ftlvariable name="Permission" type="io.papermc.hangar.model.Permission" -->
|
||||
<#assign Permission=@helper["io.papermc.hangar.model.Permission"]>
|
||||
<#assign scriptsVar>
|
||||
<script <#-- @CSPNonce.attr -->>
|
||||
window.USERNAME ='${u.user.name}';
|
||||
window.NO_ACTION_MESSAGE = {};
|
||||
window.CATEGORY_TITLE = {};
|
||||
window.CATEGORY_ICON = {};
|
||||
<#assign Category=@helper["io.papermc.hangar.model.Category"]>
|
||||
<#list Category.values() as category>
|
||||
window.CATEGORY_TITLE['${category.apiName}'] = '${category.title}';
|
||||
window.CATEGORY_ICON['${category.apiName}'] = '${category.icon}';
|
||||
</#list>
|
||||
window.NO_ACTION_MESSAGE.starred = '<@spring.messageArgs code="user.noStars" args=[u.user.name] />';
|
||||
window.NO_ACTION_MESSAGE.watching = '<@spring.messageArgs code="user.noWatching" args=[u.user.name] />';
|
||||
window.USERNAME = '${u.user.name}';
|
||||
window.NO_ACTION_MESSAGE = {};
|
||||
window.CATEGORY_TITLE = {};
|
||||
window.CATEGORY_ICON = {};
|
||||
<#assign Category=@helper["io.papermc.hangar.model.Category"]>
|
||||
<#list Category.values() as category>
|
||||
window.CATEGORY_TITLE['${category.apiName}'] = '${category.title}';
|
||||
window.CATEGORY_ICON['${category.apiName}'] = '${category.icon}';
|
||||
</#list>
|
||||
window.NO_ACTION_MESSAGE.starred = '<@spring.messageArgs code="user.noStars" args=[u.user.name] />';
|
||||
window.NO_ACTION_MESSAGE.watching = '<@spring.messageArgs code="user.noWatching" args=[u.user.name] />';
|
||||
</script>
|
||||
<script type="text/javascript" src="<@hangar.url "js/userPage.js" />"></script>
|
||||
${additionalScripts}
|
||||
@ -44,81 +50,80 @@
|
||||
<strong>Success!</strong> <span class="success"></span>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<!-- Header -->
|
||||
<div class="row user-header">
|
||||
<div class="header-body">
|
||||
<!-- Title -->
|
||||
<!-- Title -->
|
||||
<span class="user-badge">
|
||||
<#assign avatarClass>
|
||||
user-avatar-md <#if canEditOrgSettings(u, o)>organization-avatar</#if>
|
||||
user-avatar-md <#if canEditOrgSettings(u, o, so)>organization-avatar</#if>
|
||||
</#assign>
|
||||
<@userAvatar.userAvatar userName=u.user.name avatarUrl=utils.avatarUrl(u.user.name) clazz=avatarClass />
|
||||
|
||||
<#if canEditOrgSettings(u, o)>
|
||||
<#if canEditOrgSettings(u, o, so)>
|
||||
<div class="edit-avatar" style="display: none;">
|
||||
<a href="${Routes.ORG_UPDATE_AVATAR.getRouteUrl(u.user.name)}"><i class="fas fa-edit"></i> <@spring.message "user.editAvatar" /></a>
|
||||
<a href="${Routes.ORG_UPDATE_AVATAR.getRouteUrl(u.user.name)}"><i
|
||||
class="fas fa-edit"></i> <@spring.message "user.editAvatar" /></a>
|
||||
</div>
|
||||
|
||||
<#assign Prompt=@helper["io.papermc.hangar.model.Prompt"] />
|
||||
<#-- @ftlvariable name="Prompt" type="io.papermc.hangar.model.Prompt" -->
|
||||
<#-- @ftlvariable name="Prompt" type="io.papermc.hangar.model.Prompt" -->
|
||||
<#if !u.headerData.currentUser.readPrompts?seq_contains(Prompt.CHANGE_AVATAR.ordinal())>
|
||||
<@promptView.prompt prompt=Prompt.CHANGE_AVATAR id="popover-avatar" />
|
||||
</#if>
|
||||
</#if>
|
||||
<div>
|
||||
<div class="user-title">
|
||||
<h1 class="username float-left">
|
||||
${u.user.name}
|
||||
</h1>
|
||||
<div class="user-actions">
|
||||
<#if u.isCurrent() && !u.isOrga()>
|
||||
<a class="user-settings" href="${config.getAuthUrl()}/accounts/settings">
|
||||
<i class="fas fa-cog" data-toggle="tooltip" data-placement="top" title="Settings"></i>
|
||||
</a>
|
||||
<span data-toggle="modal" data-target="#modal-lock">
|
||||
<i class="fas <#if u.user.isLocked()>fa-lock<#else>fa-unlock-alt</#if> action-lock-account"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title="<#if !u.user.isLocked()><@spring.message "user.lock" /><#else><@spring.message "user.unlock" /></#if>"></i>
|
||||
</span>
|
||||
|
||||
<span class="user-title">
|
||||
<h1 class="username">
|
||||
${u.user.name}
|
||||
|
||||
<#if u.isCurrent() && !u.isOrga()>
|
||||
<a class="user-settings" href="${config.getAuthUrl()}/accounts/settings">
|
||||
<i class="fas fa-cog" data-toggle="tooltip"
|
||||
data-placement="top" title="Settings"></i>
|
||||
</a>
|
||||
|
||||
<span data-toggle="modal" data-target="#modal-lock">
|
||||
<i class="fas <#if u.user.isLocked()>fa-lock<#else>fa-unlock-alt</#if> action-lock-account" data-toggle="tooltip"
|
||||
data-placement="top" title="<#if !u.user.isLocked()><@spring.message "user.lock" /><#else><@spring.message "user.unlock" /></#if>"></i>
|
||||
</span>
|
||||
|
||||
<a class="action-api" href="${Routes.USERS_EDIT_API_KEYS.getRouteUrl(u.user.name)}">
|
||||
<i class="fas fa-key" data-toggle="tooltip" data-placement="top" title="API Keys"></i>
|
||||
</a>
|
||||
</#if>
|
||||
|
||||
<#if u.hasUser()>
|
||||
<#if u.userPerm.has(Permission.ModNotesAndFlags) || u.userPerm.has(Permission.Reviewer)>
|
||||
<a class="user-settings" href="${Routes.SHOW_ACTIVITIES.getRouteUrl(u.user.name)}">
|
||||
<i class="fas fa-calendar" data-toggle="tooltip"
|
||||
data-placement="top" title="Activity"></i>
|
||||
<a class="action-api" href="${Routes.USERS_EDIT_API_KEYS.getRouteUrl(u.user.name)}">
|
||||
<i class="fas fa-key" data-toggle="tooltip" data-placement="top" title="API Keys"></i>
|
||||
</a>
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
<#if u.headerData.globalPerm(Permission.EditAllUserSettings)>
|
||||
<a class="user-settings" href="${Routes.USER_ADMIN.getRouteUrl(u.user.name)}">
|
||||
<i class="fas fa-wrench" data-toggle="tooltip"
|
||||
data-placement="top" title="User Admin"></i>
|
||||
</a>
|
||||
</#if>
|
||||
</h1>
|
||||
|
||||
<#if u.hasUser()>
|
||||
<#if u.userPerm.has(Permission.ModNotesAndFlags) || u.userPerm.has(Permission.Reviewer)>
|
||||
<a class="user-settings" href="${Routes.SHOW_ACTIVITIES.getRouteUrl(u.user.name)}">
|
||||
<i class="fas fa-calendar" data-toggle="tooltip" data-placement="top" title="Activity"></i>
|
||||
</a>
|
||||
</#if>
|
||||
</#if>
|
||||
<#if u.headerData.globalPerm(Permission.EditAllUserSettings)>
|
||||
<a class="user-settings" href="${Routes.USER_ADMIN.getRouteUrl(u.user.name)}">
|
||||
<i class="fas fa-wrench" data-toggle="tooltip" data-placement="top" title="User Admin"></i>
|
||||
</a>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="user-tag">
|
||||
<i class="minor">
|
||||
<#if u.user.tagline??>
|
||||
${u.user.tagline}
|
||||
<#elseif u.isCurrent() || canEditOrgSettings(u, o)>
|
||||
<#elseif u.isCurrent() || canEditOrgSettings(u, o, so)>
|
||||
Add a tagline
|
||||
</#if>
|
||||
</i>
|
||||
|
||||
<#if u.isCurrent() || canEditOrgSettings(u, o)>
|
||||
<#if u.isCurrent() || canEditOrgSettings(u, o, so)>
|
||||
<a href="#" data-toggle="modal" data-target="#modal-tagline">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
</#if>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<!-- Roles -->
|
||||
@ -131,7 +136,7 @@
|
||||
<div class="user-info">
|
||||
<i class="minor">${u.projectCount} <#if u.projectCount == 1>project<#else>projects</#if></i><br/>
|
||||
<i class="minor">
|
||||
<@spring.messageArgs code="user.memberSince" args=[utils.prettifyDate(u.user.joinDate!u.user.createdAt)] />
|
||||
<@spring.messageArgs code="user.memberSince" args=[utils.prettifyDate(u.user.joinDate!u.user.createdAt)] />
|
||||
</i><br/>
|
||||
<a href="https://papermc.io/forums/users/${u.user.name}">
|
||||
<@spring.message "user.viewOnForums" /> <i class="fas fa-external-link-alt"></i>
|
||||
@ -144,11 +149,11 @@
|
||||
|
||||
<#assign lockModalTitle>
|
||||
<#compress>
|
||||
<#if u.user.isLocked()>
|
||||
user.unlock
|
||||
<#else>
|
||||
user.lock
|
||||
</#if>
|
||||
<#if u.user.isLocked()>
|
||||
user.unlock
|
||||
<#else>
|
||||
user.lock
|
||||
</#if>
|
||||
</#compress>
|
||||
</#assign>
|
||||
<@modal.modal lockModalTitle "modal-lock" "label-lock">
|
||||
@ -163,7 +168,8 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><@spring.message "general.close" /></button>
|
||||
<button type="button" class="btn btn-default"
|
||||
data-dismiss="modal"><@spring.message "general.close" /></button>
|
||||
<@form.form method="POST" action=Routes.USERS_VERIFY.getRouteUrl(Routes.USERS_SET_LOCKED.getRouteUrl(u.user.name, (!u.user.isLocked())?string, "", "")) class="form-inline">
|
||||
<@csrf.formField />
|
||||
<button type="submit" class="btn btn-primary"><@spring.message "general.continue" /></button>
|
||||
@ -181,7 +187,7 @@
|
||||
<p><@spring.message "user.tagline.info" /></p>
|
||||
</div>
|
||||
<input class="form-control" type="text" value="${u.user.tagline!""}" id="tagline"
|
||||
name="tagline" maxlength="${config.getUser().maxTaglineLen}" />
|
||||
name="tagline" maxlength="${config.getUser().maxTaglineLen}"/>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
@ -189,7 +195,7 @@
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<@spring.message "general.close" />
|
||||
</button>
|
||||
<input type="submit" value="<@spring.message "general.save" />" class="btn btn-primary" />
|
||||
<input type="submit" value="<@spring.message "general.save" />" class="btn btn-primary"/>
|
||||
</div>
|
||||
</@form.form>
|
||||
</@modal.modal>
|
||||
|
Loading…
Reference in New Issue
Block a user