mirror of
https://github.com/psforever/PSFPortal.git
synced 2026-07-16 00:44:40 +00:00
For PR 1247 roles
This commit is contained in:
parent
daac0eede9
commit
a64195bbed
12 changed files with 302 additions and 11 deletions
60
api/admin.js
60
api/admin.js
|
|
@ -127,4 +127,64 @@ api.get('/characters', NEED_ADMIN, async (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
api.get('/roles', NEED_ADMIN, async (req, res, next) => {
|
||||||
|
const pagination = get_pagination(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const roles = await db.get_roles(pagination, db.CHARACTER.LAST_LOGIN, db.SQL_ORDER.DESCENDING);
|
||||||
|
res.status(200).json({ characters: roles, page: pagination})
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
res.status(500).json({ message: 'error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.post('/roles/:avatar/add_gm', NEED_ADMIN, async (req, res, next) => {
|
||||||
|
const avatar = parseInt(req.params.avatar);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.update_roles(avatar, {[db.AVATARMODEPERMISSION.GM] : true})
|
||||||
|
res.status(200).json({});
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
res.status(500).json({ message: 'error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.post('/roles/:avatar/remove_gm', NEED_ADMIN, async (req, res, next) => {
|
||||||
|
const avatar = parseInt(req.params.avatar);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.update_roles(avatar, {[db.AVATARMODEPERMISSION.GM] : false})
|
||||||
|
res.status(200).json({});
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
res.status(500).json({ message: 'error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.post('/roles/:avatar/add_spectate', NEED_ADMIN, async (req, res, next) => {
|
||||||
|
const avatar = parseInt(req.params.avatar);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.update_roles(avatar, {[db.AVATARMODEPERMISSION.SPECTATE] : true})
|
||||||
|
res.status(200).json({});
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
res.status(500).json({ message: 'error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.post('/roles/:avatar/remove_spectate', NEED_ADMIN, async (req, res, next) => {
|
||||||
|
const avatar = parseInt(req.params.avatar);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.update_roles(avatar, {[db.AVATARMODEPERMISSION.SPECTATE] : false})
|
||||||
|
res.status(200).json({});
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
res.status(500).json({ message: 'error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|
|
||||||
58
api/db.js
58
api/db.js
|
|
@ -72,6 +72,13 @@ export const AVATAR = Object.freeze({
|
||||||
HEAD: Symbol("head_id"),
|
HEAD: Symbol("head_id"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const AVATARMODEPERMISSION = Object.freeze({
|
||||||
|
THIS: Symbol("avatarmodepermission"),
|
||||||
|
ID: Symbol("avatar_id"),
|
||||||
|
GM: Symbol("can_gm"),
|
||||||
|
SPECTATE: Symbol("can_spectate")
|
||||||
|
});
|
||||||
|
|
||||||
export const WEAPONSTAT = Object.freeze({
|
export const WEAPONSTAT = Object.freeze({
|
||||||
THIS: Symbol("weapon"),
|
THIS: Symbol("weapon"),
|
||||||
ID: Symbol("avatar_id"),
|
ID: Symbol("avatar_id"),
|
||||||
|
|
@ -314,7 +321,7 @@ export async function get_characters(pagination, sort, order) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const char_count = await get_row_count(CHARACTER.THIS);
|
const char_count = await get_row_count(CHARACTER.THIS);
|
||||||
const chars = await pool.query(`SELECT id, account_id, name, faction_id, created, last_login FROM avatar ORDER BY ${to_sql(sort)} ${to_sql(order)} OFFSET $1 LIMIT $2`, values);
|
const chars = await pool.query(`SELECT id, account_id, name, faction_id, created, last_login, avatar_id, can_gm, can_spectate FROM avatar LEFT JOIN avatarmodepermission ON avatar_id = id ORDER BY ${to_sql(sort)} ${to_sql(order)} OFFSET $1 LIMIT $2`, values);
|
||||||
|
|
||||||
pagination.item_count = char_count;
|
pagination.item_count = char_count;
|
||||||
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
|
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
|
||||||
|
|
@ -327,6 +334,24 @@ export async function get_characters(pagination, sort, order) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function get_roles(pagination) {
|
||||||
|
const start_id = (pagination.page - 1) * pagination.items_per_page;
|
||||||
|
const values = [start_id, pagination.items_per_page];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const roles = await pool.query(`SELECT avatar_id, can_spectate, can_gm, id, last_login, account_id, name FROM avatarmodepermission INNER JOIN avatar ON avatar_id = id WHERE can_gm = TRUE OR can_spectate = TRUE ORDER BY last_login DESC OFFSET $1 LIMIT $2`, values);
|
||||||
|
const char_count = roles.rowCount;
|
||||||
|
pagination.item_count = char_count;
|
||||||
|
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
|
||||||
|
|
||||||
|
return roles.rows;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code)
|
||||||
|
e.code = pg_error_inv[e.code]
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Database action added for the sake of reporting avatar information out to a publicly exposed API route for leader-boards and other components.
|
// Database action added for the sake of reporting avatar information out to a publicly exposed API route for leader-boards and other components.
|
||||||
export async function get_character_batch_for_stats(batch, sort, order) {
|
export async function get_character_batch_for_stats(batch, sort, order) {
|
||||||
const values = [batch];
|
const values = [batch];
|
||||||
|
|
@ -415,7 +440,7 @@ export async function get_top_kills_byDate() {
|
||||||
|
|
||||||
export async function get_characters_by_account(account_id) {
|
export async function get_characters_by_account(account_id) {
|
||||||
try {
|
try {
|
||||||
const characters = await pool.query('SELECT * FROM avatar WHERE account_id=$1 AND deleted=false', [account_id])
|
const characters = await pool.query('SELECT a.*, b.* FROM avatar a LEFT JOIN avatarmodepermission b ON a.id = b.avatar_id WHERE a.account_id = $1 AND a.deleted = false', [account_id])
|
||||||
return characters.rows;
|
return characters.rows;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code)
|
if (e.code)
|
||||||
|
|
@ -476,6 +501,31 @@ export async function update_account(account_id, fields) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function update_roles(avatar_id, fields) {
|
||||||
|
if (fields === {}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const set = build_SET(fields);
|
||||||
|
set.values.push(avatar_id)
|
||||||
|
const checkExists = await pool.query(`SELECT avatar_id FROM avatarmodepermission WHERE avatar_id = $1`, [avatar_id]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let query;
|
||||||
|
if (checkExists.rowCount > 0) {
|
||||||
|
await pool.query(`UPDATE avatarmodepermission ${to_sql(set.sql)} WHERE avatar_id=$${set.next_idx}`, set.values);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await pool.query(`INSERT INTO avatarmodepermission (avatar_id) VALUES ($1)`, [avatar_id]);
|
||||||
|
await pool.query(`UPDATE avatarmodepermission ${to_sql(set.sql)} WHERE avatar_id=$${set.next_idx}`, set.values);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code)
|
||||||
|
e.code = pg_error_inv[e.code]
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function get_empire_stats() {
|
export async function get_empire_stats() {
|
||||||
try {
|
try {
|
||||||
const query = await pool.query('SELECT faction_id, COUNT(*) FROM avatar GROUP BY faction_id');
|
const query = await pool.query('SELECT faction_id, COUNT(*) FROM avatar GROUP BY faction_id');
|
||||||
|
|
@ -571,8 +621,8 @@ export async function search(term, pagination) {
|
||||||
const accounts = await pool.query('SELECT id, username, gm, inactive FROM account ' +
|
const accounts = await pool.query('SELECT id, username, gm, inactive FROM account ' +
|
||||||
'WHERE upper(username) LIKE $1 ' +
|
'WHERE upper(username) LIKE $1 ' +
|
||||||
` ORDER BY username OFFSET $2 LIMIT $3`, values);
|
` ORDER BY username OFFSET $2 LIMIT $3`, values);
|
||||||
const characters = await pool.query('SELECT id, name, account_id, faction_id FROM avatar ' +
|
const characters = await pool.query('SELECT a.id, a.name, a.account_id, a.faction_id, b.avatar_id, b.can_spectate, b.can_gm FROM avatar a' +
|
||||||
'WHERE upper(name) LIKE $1 ' +
|
' LEFT JOIN avatarmodepermission b ON a.id = b.avatar_id WHERE upper(a.name) LIKE $1 ' +
|
||||||
` ORDER BY name OFFSET $2 LIMIT $3`, values);
|
` ORDER BY name OFFSET $2 LIMIT $3`, values);
|
||||||
|
|
||||||
pagination.item_count = 100;
|
pagination.item_count = 100;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if account.admin}
|
{#if account.admin}
|
||||||
<span class="badge badge-success">GM</span>
|
<span class="badge badge-success">Admin</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
data-account-id={account.id}
|
data-account-id={account.id}
|
||||||
data-account-name={account.name}
|
data-account-name={account.name}
|
||||||
data-toggle="modal"
|
data-toggle="modal"
|
||||||
data-target="#actionModal">Remove GM</button>
|
data-target="#actionModal">Remove Admin</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-success btn-sm"
|
class="btn btn-success btn-sm"
|
||||||
|
|
@ -33,6 +33,6 @@
|
||||||
data-account-id={account.id}
|
data-account-id={account.id}
|
||||||
data-account-name={account.name}
|
data-account-name={account.name}
|
||||||
data-toggle="modal"
|
data-toggle="modal"
|
||||||
data-target="#actionModal">Make GM</button>
|
data-target="#actionModal">Make Admin</button>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,12 @@
|
||||||
<a class="character-link" href="/user/{character.account_id}">
|
<a class="character-link" href="/user/{character.account_id}">
|
||||||
<FactionIcon {character} />
|
<FactionIcon {character} />
|
||||||
<span>{character.name}</span>
|
<span>{character.name}</span>
|
||||||
|
{#if character.can_gm}
|
||||||
|
<span class="badge badge-success">GM</span>
|
||||||
|
{/if}
|
||||||
|
{#if character.can_spectate}
|
||||||
|
<span class="badge badge-success">Spec</span>
|
||||||
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="character-link">
|
<span class="character-link">
|
||||||
|
|
|
||||||
38
app/components/PermissionButtons.svelte
Normal file
38
app/components/PermissionButtons.svelte
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
export let avatar;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if avatar.can_spectate}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
data-action="remove_spectate"
|
||||||
|
data-avatar-id={avatar.id}
|
||||||
|
data-account-name={avatar.name}
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#roleModal">- Spec</button>
|
||||||
|
{:else}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-success btn-sm"
|
||||||
|
data-action="add_spectate"
|
||||||
|
data-avatar-id={avatar.id}
|
||||||
|
data-account-name={avatar.name}
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#roleModal">+ Spec</button>
|
||||||
|
{/if}
|
||||||
|
{#if avatar.can_gm}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
data-action="remove_gm"
|
||||||
|
data-avatar-id={avatar.id}
|
||||||
|
data-account-name={avatar.name}
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#roleModal">- GM</button>
|
||||||
|
{:else}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-success btn-sm"
|
||||||
|
data-action="add_gm"
|
||||||
|
data-avatar-id={avatar.id}
|
||||||
|
data-account-name={avatar.name}
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#roleModal">+ GM</button>
|
||||||
|
{/if}
|
||||||
74
app/components/RoleModal.svelte
Normal file
74
app/components/RoleModal.svelte
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import axios from 'axios'
|
||||||
|
import Alert from './Alert'
|
||||||
|
import jq from 'jquery'
|
||||||
|
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
let modalAlert;
|
||||||
|
|
||||||
|
onMount(() => setup_actions());
|
||||||
|
|
||||||
|
function setup_actions() {
|
||||||
|
const modal = jq('#roleModal');
|
||||||
|
|
||||||
|
modal.on('hide.bs.modal', () => modalAlert.message(""));
|
||||||
|
modal.on('show.bs.modal', (event) => {
|
||||||
|
const button = jq(event.relatedTarget) // Button that triggered the modal
|
||||||
|
const username = button.data('account-name')
|
||||||
|
const avatar_id = button.data('avatar-id')
|
||||||
|
const action_type = button.data('action')
|
||||||
|
const action_name = button.text();
|
||||||
|
|
||||||
|
modal.find('.modal-title').text("Confirm " + action_name)
|
||||||
|
modal.find('.modal-body p').text("Are you sure you want to perform this action on \'" + username + "\'?")
|
||||||
|
|
||||||
|
const submit = modal.find('.modal-footer .btn-primary')
|
||||||
|
|
||||||
|
submit.text(action_name)
|
||||||
|
// remove ALL previous click handlers
|
||||||
|
submit.off()
|
||||||
|
|
||||||
|
submit.click(async (event) => {
|
||||||
|
submit.addClass("disabled")
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post("/api/roles/"+ avatar_id + "/" + action_type)
|
||||||
|
dispatch('action', {
|
||||||
|
target: avatar_id,
|
||||||
|
action: action_type,
|
||||||
|
});
|
||||||
|
modal.modal('hide')
|
||||||
|
} catch (e) {
|
||||||
|
modalAlert.message(e.message)
|
||||||
|
} finally {
|
||||||
|
submit.removeClass("disabled")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="modal fade" id="roleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="exampleModalLabel">Perform Action</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<Alert bind:this={modalAlert} />
|
||||||
|
<p>Are you sure?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">No</button>
|
||||||
|
<button type="button" class="btn btn-primary">Yes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
import UserList from '../views/UserList'
|
import UserList from '../views/UserList'
|
||||||
import CharacterList from '../views/CharacterList'
|
import CharacterList from '../views/CharacterList'
|
||||||
|
import RoleList from '../views/RoleList'
|
||||||
import CharacterLink from '../components/CharacterLink'
|
import CharacterLink from '../components/CharacterLink'
|
||||||
import AccountLink from '../components/AccountLink'
|
import AccountLink from '../components/AccountLink'
|
||||||
import { monitor_tabs } from '../util/navigation'
|
import { monitor_tabs } from '../util/navigation'
|
||||||
|
|
@ -41,6 +42,9 @@
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="characters-tab" data-toggle="tab" href="#characters" role="tab" aria-controls="profile" aria-selected="false">Characters</a>
|
<a class="nav-link" id="characters-tab" data-toggle="tab" href="#characters" role="tab" aria-controls="profile" aria-selected="false">Characters</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="roles-tab" data-toggle="tab" href="#roles" role="tab" aria-controls="profile" aria-selected="false">Roles</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content" id="tabs-tabContent">
|
<div class="tab-content" id="tabs-tabContent">
|
||||||
|
|
@ -74,4 +78,7 @@
|
||||||
<div class="tab-pane" id="characters" role="tabpanel" aria-labelledby="characters-tab">
|
<div class="tab-pane" id="characters" role="tabpanel" aria-labelledby="characters-tab">
|
||||||
<CharacterList setURLParam={true} {appAlert} />
|
<CharacterList setURLParam={true} {appAlert} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane" id="roles" role="tabpanel" aria-labelledby="roles-tab">
|
||||||
|
<RoleList setURLParam={true} {appAlert} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
export let setURLParam = true;
|
export let setURLParam = true;
|
||||||
|
|
||||||
export let appAlert
|
export let appAlert;
|
||||||
|
let characterList;
|
||||||
|
|
||||||
async function fetch(page) {
|
async function fetch(page) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -20,7 +21,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PaginatedList {setURLParam} URLSearchName='page_char' bind:fetch={fetch} let:data={characters} let:pagination={pagination}>
|
<PaginatedList {setURLParam} URLSearchName='page_char' bind:this={characterList} bind:fetch={fetch} let:data={characters} let:pagination={pagination}>
|
||||||
<div slot="header">
|
<div slot="header">
|
||||||
<p>{pagination.item_count.toLocaleString()} characters in the database</p>
|
<p>{pagination.item_count.toLocaleString()} characters in the database</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@
|
||||||
import LoginList from '../components/LoginList'
|
import LoginList from '../components/LoginList'
|
||||||
import AccountLink from '../components/AccountLink'
|
import AccountLink from '../components/AccountLink'
|
||||||
import ActionButtons from '../components/ActionButtons'
|
import ActionButtons from '../components/ActionButtons'
|
||||||
|
import PermissionButtons from '../components/PermissionButtons'
|
||||||
import ActionModal from '../components/ActionModal.svelte'
|
import ActionModal from '../components/ActionModal.svelte'
|
||||||
|
import RoleModal from '../components/RoleModal.svelte'
|
||||||
|
|
||||||
export let pageCtx;
|
export let pageCtx;
|
||||||
export let appAlert;
|
export let appAlert;
|
||||||
|
|
@ -86,7 +88,7 @@
|
||||||
{#if characters.length >= 1}
|
{#if characters.length >= 1}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{#each characters as char, i}
|
{#each characters as char, i}
|
||||||
<div class="col-md-4 col-12"><CharacterLink character={char} /></div>
|
<div class="col-md-4 col-12">{char.name} <PermissionButtons avatar={char} /></div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -101,3 +103,4 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<ActionModal on:action={() => refresh()} />
|
<ActionModal on:action={() => refresh()} />
|
||||||
|
<RoleModal on:action={() => refresh()} />
|
||||||
|
|
|
||||||
52
app/views/RoleList.svelte
Normal file
52
app/views/RoleList.svelte
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
import axios from 'axios'
|
||||||
|
import CharacterLink from '../components/CharacterLink'
|
||||||
|
import PermissionButtons from '../components/PermissionButtons'
|
||||||
|
import PaginatedList from '../components/PaginatedList'
|
||||||
|
import RoleModal from '../components/RoleModal.svelte'
|
||||||
|
|
||||||
|
import moment from 'moment'
|
||||||
|
export let setURLParam = true;
|
||||||
|
|
||||||
|
export let appAlert;
|
||||||
|
let roleList;
|
||||||
|
|
||||||
|
async function fetch(page) {
|
||||||
|
try {
|
||||||
|
const resp = await axios.get("/api/roles?page="+page)
|
||||||
|
appAlert.message("")
|
||||||
|
return [resp.data.characters, resp.data.page];
|
||||||
|
} catch (e) {
|
||||||
|
appAlert.message(e.message)
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<PaginatedList {setURLParam} URLSearchName='page_role' bind:this={roleList} bind:fetch={fetch} let:data={roles} let:pagination={pagination}>
|
||||||
|
<div slot="header">
|
||||||
|
<p>{pagination.item_count.toLocaleString()} characters in the database have a role assigned</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table slot="body" class="table table-sm table-dark table-responsive-md table-striped table-hover">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Last Played</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each roles as char, i}
|
||||||
|
<tr>
|
||||||
|
<td>#{char.id}</td>
|
||||||
|
<td><CharacterLink character={char} /></td>
|
||||||
|
<td>{moment(char.last_login).fromNow()}</td>
|
||||||
|
<td><PermissionButtons avatar={char} /></td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</PaginatedList>
|
||||||
|
|
||||||
|
<RoleModal on:action={() => roleList.refresh()} />
|
||||||
|
|
@ -110,7 +110,7 @@
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" name="gm" id="userFilterGM">
|
<input type="checkbox" class="form-check-input" name="gm" id="userFilterGM">
|
||||||
<label class="form-check-label" for="userFilterGM">
|
<label class="form-check-label" for="userFilterGM">
|
||||||
GM
|
Admin
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue