finish up the admin side for now
This commit is contained in:
parent
b8913702ae
commit
23a206ce99
27 changed files with 455 additions and 30 deletions
|
@ -1,9 +1,17 @@
|
||||||
import AdminBadgesViewModel from "../models/admin/AdminBadgesViewModel";
|
import AdminBadgesViewModel from "../models/admin/AdminBadgesViewModel";
|
||||||
import AdminBadgeViewModel from "../models/admin/AdminBadgeViewModel";
|
import AdminBadgeViewModel from "../models/admin/AdminBadgeViewModel";
|
||||||
import AdminDeleteBadgeModel from "../models/admin/AdminDeleteBadgeModel";
|
import AdminDeleteBadgeModel from "../models/admin/AdminDeleteBadgeModel";
|
||||||
|
import AdminDeletePartyModel from "../models/admin/AdminDeletePartyModel";
|
||||||
|
import AdminExpireSessionModel from "../models/admin/AdminExpireSessionModel";
|
||||||
import AdminIndexViewModel from "../models/admin/AdminIndexViewModel";
|
import AdminIndexViewModel from "../models/admin/AdminIndexViewModel";
|
||||||
import AdminPartiesViewModel from "../models/admin/AdminPartiesViewModel";
|
import AdminPartiesViewModel from "../models/admin/AdminPartiesViewModel";
|
||||||
|
import AdminPartyViewModel from "../models/admin/AdminPartyViewModel";
|
||||||
import AdminUsersViewModel from "../models/admin/AdminUsersViewModel";
|
import AdminUsersViewModel from "../models/admin/AdminUsersViewModel";
|
||||||
|
import AdminUserViewModel from "../models/admin/AdminUserViewModel";
|
||||||
|
import AdminWebSessionsViewModel from "../models/admin/AdminWebSessionsViewModel";
|
||||||
|
import AdminWsSessionsViewModel from "../models/admin/AdminWsSessionsViewModel";
|
||||||
|
import Session from "../objects/Session";
|
||||||
|
import WsData from "../objects/WsData";
|
||||||
import BadgeService from "../services/BadgeService";
|
import BadgeService from "../services/BadgeService";
|
||||||
import PartyService from "../services/PartyService";
|
import PartyService from "../services/PartyService";
|
||||||
import UserService from "../services/UserService";
|
import UserService from "../services/UserService";
|
||||||
|
@ -13,7 +21,8 @@ export default class AdminController_Auth$Admin extends Controller {
|
||||||
public async Index_Get() {
|
public async Index_Get() {
|
||||||
const adminIndexViewModel: AdminIndexViewModel = {
|
const adminIndexViewModel: AdminIndexViewModel = {
|
||||||
userCount: await UserService.GetUserCount(),
|
userCount: await UserService.GetUserCount(),
|
||||||
partyCount: await PartyService.GetPartyCount()
|
partyCount: await PartyService.GetPartyCount(),
|
||||||
|
badgeCount: await BadgeService.GetBadgeCount()
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.view(adminIndexViewModel);
|
return this.view(adminIndexViewModel);
|
||||||
|
@ -27,6 +36,33 @@ export default class AdminController_Auth$Admin extends Controller {
|
||||||
return this.view(adminUsersViewModel);
|
return this.view(adminUsersViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async User_Get(adminUserViewModel: AdminUserViewModel) {
|
||||||
|
const user = adminUserViewModel.id ? await UserService.GetUser(parseInt(adminUserViewModel.id)) : null;
|
||||||
|
if (typeof(adminUserViewModel.id) !== "undefined" && user) {
|
||||||
|
adminUserViewModel.username = user.Username;
|
||||||
|
adminUserViewModel.userLevel = String(user.UserLevel);
|
||||||
|
} else {
|
||||||
|
adminUserViewModel.username = "";
|
||||||
|
adminUserViewModel.userLevel = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.view(adminUserViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async User_Post(adminUserViewModel: AdminUserViewModel) {
|
||||||
|
if (typeof(adminUserViewModel.id) === "undefined") {
|
||||||
|
return this.badRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await UserService.SaveUser(this.session.userId, parseInt(adminUserViewModel.id), adminUserViewModel.username ?? "", parseInt(adminUserViewModel.userLevel ?? ""));
|
||||||
|
if (!user) {
|
||||||
|
adminUserViewModel.message = "A user with that username already exists.";
|
||||||
|
return this.view(adminUserViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.redirectToAction("users");
|
||||||
|
}
|
||||||
|
|
||||||
public async Parties_Get() {
|
public async Parties_Get() {
|
||||||
const adminPartiesViewModel: AdminPartiesViewModel = {
|
const adminPartiesViewModel: AdminPartiesViewModel = {
|
||||||
parties: await PartyService.GetAll()
|
parties: await PartyService.GetAll()
|
||||||
|
@ -35,6 +71,40 @@ export default class AdminController_Auth$Admin extends Controller {
|
||||||
return this.view(adminPartiesViewModel);
|
return this.view(adminPartiesViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Party_Get(adminPartyViewModel: AdminPartyViewModel) {
|
||||||
|
const party = adminPartyViewModel.id ? await PartyService.GetParty(parseInt(adminPartyViewModel.id)) : null;
|
||||||
|
if (typeof(adminPartyViewModel.id) !== "undefined" && party) {
|
||||||
|
adminPartyViewModel.partyRef = party.PartyRef;
|
||||||
|
adminPartyViewModel.name = party.Name;
|
||||||
|
} else {
|
||||||
|
adminPartyViewModel.partyRef = "";
|
||||||
|
adminPartyViewModel.name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.view(adminPartyViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Party_Post(adminPartyViewModel: AdminPartyViewModel) {
|
||||||
|
if (typeof(adminPartyViewModel.id) === "undefined") {
|
||||||
|
return this.badRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
await PartyService.SaveParty(this.session.userId, parseInt(adminPartyViewModel.id), adminPartyViewModel.name ?? "", adminPartyViewModel.partyRef ?? "");
|
||||||
|
|
||||||
|
return this.redirectToAction("parties");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async DeleteParty_Get(adminDeletePartyModel: AdminDeletePartyModel) {
|
||||||
|
if (typeof(adminDeletePartyModel.id) === "undefined" || typeof(adminDeletePartyModel.id) !== "string") {
|
||||||
|
return this.badRequest();
|
||||||
|
}
|
||||||
|
const partyId = parseInt(adminDeletePartyModel.id);
|
||||||
|
|
||||||
|
await PartyService.DeletePartyAdmin(this.session.userId, partyId);
|
||||||
|
|
||||||
|
return this.redirectToAction("parties");
|
||||||
|
}
|
||||||
|
|
||||||
public async Badges_Get() {
|
public async Badges_Get() {
|
||||||
const adminBadgesViewModel: AdminBadgesViewModel = {
|
const adminBadgesViewModel: AdminBadgesViewModel = {
|
||||||
badges: await BadgeService.LoadAll()
|
badges: await BadgeService.LoadAll()
|
||||||
|
@ -80,4 +150,30 @@ export default class AdminController_Auth$Admin extends Controller {
|
||||||
|
|
||||||
return this.redirectToAction("badges");
|
return this.redirectToAction("badges");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async WebSessions_Get() {
|
||||||
|
const adminWebSessionsViewModel: AdminWebSessionsViewModel = {
|
||||||
|
sessions: Session.Sessions
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.view(adminWebSessionsViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ExpireSession_Get(adminExpireSessionModel: AdminExpireSessionModel) {
|
||||||
|
if (typeof(adminExpireSessionModel.key) === "undefined" || typeof(adminExpireSessionModel.key) !== "string") {
|
||||||
|
return this.badRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
Session.Sessions.remove(adminExpireSessionModel.key);
|
||||||
|
|
||||||
|
return this.redirectToAction("websessions");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async WsSessions_Get() {
|
||||||
|
const adminWsSessionsViewModel: AdminWsSessionsViewModel = {
|
||||||
|
users: WsData.users
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.view(adminWsSessionsViewModel);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -49,10 +49,12 @@ import ApiController from "./controller/ApiController";
|
||||||
import PartyService from "./services/PartyService";
|
import PartyService from "./services/PartyService";
|
||||||
import AdminController_Auth$Admin from "./controller/AdminController";
|
import AdminController_Auth$Admin from "./controller/AdminController";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
import WsData from "./objects/WsData";
|
||||||
|
|
||||||
Console.customHeader(`MultiProbe server started at ${new Date()}`);
|
Console.customHeader(`MultiProbe server started at ${new Date()}`);
|
||||||
|
|
||||||
const users = new FunkyArray<string, RemoteUser>();
|
const users = new FunkyArray<string, RemoteUser>();
|
||||||
|
WsData.users = users;
|
||||||
|
|
||||||
new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name);
|
new Database(Config.database.address, Config.database.port, Config.database.username, Config.database.password, Config.database.name);
|
||||||
|
|
||||||
|
|
3
server/models/admin/AdminDeletePartyModel.ts
Normal file
3
server/models/admin/AdminDeletePartyModel.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default interface AdminDeletePartyModel {
|
||||||
|
id: string
|
||||||
|
}
|
3
server/models/admin/AdminExpireSessionModel.ts
Normal file
3
server/models/admin/AdminExpireSessionModel.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default interface AdminExpireSessionModel {
|
||||||
|
key?:string
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
export default interface AdminIndexViewModel {
|
export default interface AdminIndexViewModel {
|
||||||
userCount: number,
|
userCount: number,
|
||||||
partyCount: number
|
partyCount: number,
|
||||||
|
badgeCount: number
|
||||||
}
|
}
|
5
server/models/admin/AdminPartyViewModel.ts
Normal file
5
server/models/admin/AdminPartyViewModel.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export default interface AdminPartyViewModel {
|
||||||
|
id?: string,
|
||||||
|
partyRef?: string,
|
||||||
|
name?: string
|
||||||
|
}
|
7
server/models/admin/AdminUserViewModel.ts
Normal file
7
server/models/admin/AdminUserViewModel.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default interface AdminUserViewModel {
|
||||||
|
message?:string,
|
||||||
|
|
||||||
|
id?: string,
|
||||||
|
username?: string,
|
||||||
|
userLevel?: string
|
||||||
|
}
|
6
server/models/admin/AdminWebSessionsViewModel.ts
Normal file
6
server/models/admin/AdminWebSessionsViewModel.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import FunkyArray from "funky-array";
|
||||||
|
import SessionUser from "../../objects/SessionUser";
|
||||||
|
|
||||||
|
export default interface AdminWebSessionsViewModel {
|
||||||
|
sessions: FunkyArray<string, SessionUser>
|
||||||
|
}
|
6
server/models/admin/AdminWsSessionsViewModel.ts
Normal file
6
server/models/admin/AdminWsSessionsViewModel.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import FunkyArray from "funky-array";
|
||||||
|
import RemoteUser from "../../objects/RemoteUser";
|
||||||
|
|
||||||
|
export default interface AdminWsSessionsViewModel {
|
||||||
|
users: FunkyArray<string, RemoteUser>
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ export default abstract class Session {
|
||||||
validPeriod.setTime(validPeriod.getTime() + Config.session.validity);
|
validPeriod.setTime(validPeriod.getTime() + Config.session.validity);
|
||||||
const key = randomBytes(Config.session.length).toString("hex");
|
const key = randomBytes(Config.session.length).toString("hex");
|
||||||
|
|
||||||
Session.Sessions.set(key, new SessionUser(user.Id, user.UserLevel, validPeriod));
|
Session.Sessions.set(key, new SessionUser(user.Id, user.Username, user.UserLevel, validPeriod));
|
||||||
|
|
||||||
res.setCookie("MP_SESSION", key, {
|
res.setCookie("MP_SESSION", key, {
|
||||||
path: "/",
|
path: "/",
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { UserLevel } from "../enums/UserLevel";
|
||||||
|
|
||||||
export default class SessionUser {
|
export default class SessionUser {
|
||||||
public readonly userId:number;
|
public readonly userId:number;
|
||||||
|
public readonly username:string;
|
||||||
public readonly userLevel:UserLevel;
|
public readonly userLevel:UserLevel;
|
||||||
public readonly validityPeriod:Date;
|
public readonly validityPeriod:Date;
|
||||||
|
|
||||||
constructor(userId:number, userLevel:UserLevel, validityPeriod:Date) {
|
constructor(userId:number, username:string, userLevel:UserLevel, validityPeriod:Date) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
this.username = username;
|
||||||
this.userLevel = userLevel;
|
this.userLevel = userLevel;
|
||||||
this.validityPeriod = validityPeriod;
|
this.validityPeriod = validityPeriod;
|
||||||
}
|
}
|
||||||
|
|
6
server/objects/WsData.ts
Normal file
6
server/objects/WsData.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import FunkyArray from "funky-array";
|
||||||
|
import RemoteUser from "./RemoteUser";
|
||||||
|
|
||||||
|
export default abstract class WsData {
|
||||||
|
public static users: FunkyArray<string, RemoteUser>;
|
||||||
|
}
|
|
@ -27,6 +27,12 @@ export default abstract class BadgeRepo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async selectBadgeCount() {
|
||||||
|
const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `Badge` WHERE IsDeleted = 0 LIMIT 1");
|
||||||
|
|
||||||
|
return countResult[0]["COUNT(Id)"];
|
||||||
|
}
|
||||||
|
|
||||||
public static async insertUpdate(badge:Badge) {
|
public static async insertUpdate(badge:Badge) {
|
||||||
if (badge.Id === Number.MIN_VALUE) {
|
if (badge.Id === Number.MIN_VALUE) {
|
||||||
badge.Id = (await Database.Instance.query("INSERT Badge (Name, Description, ImageUrl, ForUrl, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
badge.Id = (await Database.Instance.query("INSERT Badge (Name, Description, ImageUrl, ForUrl, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||||
|
|
|
@ -56,21 +56,23 @@ export default class PartyRepo {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async selectPartyCount() {
|
public static async selectPartyCount() {
|
||||||
const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `Party` LIMIT 1");
|
const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `Party` WHERE IsDeleted = 0 LIMIT 1");
|
||||||
|
|
||||||
return countResult[0]["COUNT(Id)"];
|
return countResult[0]["COUNT(Id)"];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async insertUpdate(party:Party) {
|
public static async insertUpdate(party:Party) {
|
||||||
if (party.Id === Number.MIN_VALUE) {
|
if (party.Id === Number.MIN_VALUE) {
|
||||||
await Database.Instance.query("INSERT Party (Name, PartyRef, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
party.Id = (await Database.Instance.query("INSERT Party (Name, PartyRef, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||||
party.Name, party.PartyRef, party.CreatedByUserId, party.CreatedDatetime.getTime(), party.LastModifiedByUserId ?? null, party.LastModifiedDatetime?.getTime() ?? null, party.DeletedByUserId ?? null, party.DeletedDatetime?.getTime() ?? null, Number(party.IsDeleted)
|
party.Name, party.PartyRef, party.CreatedByUserId, party.CreatedDatetime.getTime(), party.LastModifiedByUserId ?? null, party.LastModifiedDatetime?.getTime() ?? null, party.DeletedByUserId ?? null, party.DeletedDatetime?.getTime() ?? null, Number(party.IsDeleted)
|
||||||
]);
|
]))[0]["Id"];
|
||||||
} else {
|
} else {
|
||||||
await Database.Instance.query("UPDATE Party SET Name = ?, PartyRef = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?", [
|
await Database.Instance.query("UPDATE Party SET Name = ?, PartyRef = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?", [
|
||||||
party.Name, party.PartyRef, party.CreatedByUserId, party.CreatedDatetime.getTime(), party.LastModifiedByUserId ?? null, party.LastModifiedDatetime?.getTime() ?? null, party.DeletedByUserId ?? null, party.DeletedDatetime?.getTime() ?? null, Number(party.IsDeleted), party.Id
|
party.Name, party.PartyRef, party.CreatedByUserId, party.CreatedDatetime.getTime(), party.LastModifiedByUserId ?? null, party.LastModifiedDatetime?.getTime() ?? null, party.DeletedByUserId ?? null, party.DeletedDatetime?.getTime() ?? null, Number(party.IsDeleted), party.Id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return party;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,14 +60,16 @@ export default class UserPartyRepo {
|
||||||
|
|
||||||
public static async insertUpdate(userParty:UserParty) {
|
public static async insertUpdate(userParty:UserParty) {
|
||||||
if (userParty.Id === Number.MIN_VALUE) {
|
if (userParty.Id === Number.MIN_VALUE) {
|
||||||
await Database.Instance.query("INSERT UserParty (UserId, PartyId, IsActive, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
userParty.Id = (await Database.Instance.query("INSERT UserParty (UserId, PartyId, IsActive, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||||
userParty.UserId, userParty.PartyId, Number(userParty.IsActive), userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted)
|
userParty.UserId, userParty.PartyId, Number(userParty.IsActive), userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted)
|
||||||
]);
|
]))[0]["Id"];
|
||||||
} else {
|
} else {
|
||||||
await Database.Instance.query(`UPDATE UserParty SET UserId = ?, PartyId = ?, IsActive = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
await Database.Instance.query(`UPDATE UserParty SET UserId = ?, PartyId = ?, IsActive = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||||
userParty.UserId, userParty.PartyId, Number(userParty.IsActive), userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted), userParty.Id
|
userParty.UserId, userParty.PartyId, Number(userParty.IsActive), userParty.CreatedByUserId, userParty.CreatedDatetime.getTime(), userParty.LastModifiedByUserId ?? null, userParty.LastModifiedDatetime?.getTime() ?? null, userParty.DeletedByUserId ?? null, userParty.DeletedDatetime?.getTime() ?? null, Number(userParty.IsDeleted), userParty.Id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return userParty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,21 +50,23 @@ export default class UserRepo {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async selectUserCount() {
|
public static async selectUserCount() {
|
||||||
const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `User` LIMIT 1");
|
const countResult = await Database.Instance.query("SELECT COUNT(Id) FROM `User` WHERE IsDeleted = 0 LIMIT 1");
|
||||||
|
|
||||||
return countResult[0]["COUNT(Id)"];
|
return countResult[0]["COUNT(Id)"];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async insertUpdate(user:User) {
|
public static async insertUpdate(user:User) {
|
||||||
if (user.Id === Number.MIN_VALUE) {
|
if (user.Id === Number.MIN_VALUE) {
|
||||||
await Database.Instance.query("INSERT User (UserLevelId, Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
user.Id = (await Database.Instance.query("INSERT User (UserLevelId, Username, PasswordHash, PasswordSalt, CreatedByUserId, CreatedDatetime, LastModifiedByUserId, LastModifiedDatetime, DeletedByUserId, DeletedDatetime, IsDeleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING Id;", [
|
||||||
user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
|
user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted)
|
||||||
]);
|
]))[0]["Id"];
|
||||||
} else {
|
} else {
|
||||||
await Database.Instance.query(`UPDATE User SET UserLevelId = ?, Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
await Database.Instance.query(`UPDATE User SET UserLevelId = ?, Username = ?, PasswordHash = ?, PasswordSalt = ?, APIKey = ?, CreatedByUserId = ?, CreatedDatetime = ?, LastModifiedByUserId = ?, LastModifiedDatetime = ?, DeletedByUserId = ?, DeletedDatetime = ?, IsDeleted = ? WHERE Id = ?`, [
|
||||||
user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
user.UserLevel, user.Username, user.PasswordHash, user.PasswordSalt, user.APIKey, user.CreatedByUserId, user.CreatedDatetime.getTime(), user.LastModifiedByUserId ?? null, user.LastModifiedDatetime?.getTime() ?? null, user.DeletedByUserId ?? null, user.DeletedDatetime?.getTime() ?? null, Number(user.IsDeleted), user.Id
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,4 +57,13 @@ export default abstract class BadgeService {
|
||||||
|
|
||||||
return await BadgeRepo.insertUpdate(badge);
|
return await BadgeRepo.insertUpdate(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async GetBadgeCount() {
|
||||||
|
try {
|
||||||
|
return await BadgeRepo.selectBadgeCount();
|
||||||
|
} catch (e) {
|
||||||
|
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,26 +7,44 @@ import UserPartyRepo from "../repos/UserPartyRepo";
|
||||||
export default abstract class PartyService {
|
export default abstract class PartyService {
|
||||||
public static async CreateParty(currentUserId:number, name:string, partyRef:string) {
|
public static async CreateParty(currentUserId:number, name:string, partyRef:string) {
|
||||||
try {
|
try {
|
||||||
const party = new Party();
|
let party = new Party();
|
||||||
party.Name = name;
|
party.Name = name;
|
||||||
party.PartyRef = partyRef;
|
party.PartyRef = partyRef;
|
||||||
party.CreatedByUserId = currentUserId;
|
party.CreatedByUserId = currentUserId;
|
||||||
party.CreatedDatetime = new Date();
|
party.CreatedDatetime = new Date();
|
||||||
|
|
||||||
await PartyRepo.insertUpdate(party);
|
party = await PartyRepo.insertUpdate(party);
|
||||||
|
|
||||||
const newParty = await PartyRepo.selectByPartyRef(partyRef);
|
|
||||||
if (!newParty) {
|
|
||||||
throw "This shouldn't happen";
|
|
||||||
}
|
|
||||||
|
|
||||||
const userParty = new UserParty();
|
const userParty = new UserParty();
|
||||||
userParty.UserId = currentUserId;
|
userParty.UserId = currentUserId;
|
||||||
userParty.PartyId = newParty.Id;
|
userParty.PartyId = party.Id;
|
||||||
userParty.CreatedByUserId = currentUserId;
|
userParty.CreatedByUserId = currentUserId;
|
||||||
userParty.CreatedDatetime = new Date();
|
userParty.CreatedDatetime = new Date();
|
||||||
|
|
||||||
await UserPartyRepo.insertUpdate(userParty);
|
await UserPartyRepo.insertUpdate(userParty);
|
||||||
|
|
||||||
|
return party;
|
||||||
|
} catch (e) {
|
||||||
|
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async SaveParty(currentUserId:number, id:number | undefined, name:string, partyRef:string) {
|
||||||
|
try {
|
||||||
|
let party = id ? await PartyRepo.selectById(id) : null;
|
||||||
|
if (!party) {
|
||||||
|
party = new Party();
|
||||||
|
party.CreatedByUserId = currentUserId;
|
||||||
|
party.CreatedDatetime = new Date();
|
||||||
|
} else {
|
||||||
|
party.LastModifiedByUserId = currentUserId;
|
||||||
|
party.LastModifiedDatetime = new Date();
|
||||||
|
}
|
||||||
|
party.Name = name;
|
||||||
|
party.PartyRef = partyRef;
|
||||||
|
|
||||||
|
return await PartyRepo.insertUpdate(party);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -76,7 +94,25 @@ export default abstract class PartyService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(party);
|
party.DeletedByUserId = currentUserId;
|
||||||
|
party.DeletedDatetime = new Date();
|
||||||
|
party.IsDeleted = true;
|
||||||
|
|
||||||
|
await PartyRepo.insertUpdate(party);
|
||||||
|
|
||||||
|
return party;
|
||||||
|
} catch (e) {
|
||||||
|
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async DeletePartyAdmin(currentUserId:number, partyId:number) {
|
||||||
|
try {
|
||||||
|
const party = await PartyRepo.selectById(partyId);
|
||||||
|
if (!party) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
party.DeletedByUserId = currentUserId;
|
party.DeletedByUserId = currentUserId;
|
||||||
party.DeletedDatetime = new Date();
|
party.DeletedDatetime = new Date();
|
||||||
|
|
|
@ -4,6 +4,7 @@ import UserRepo from "../repos/UserRepo";
|
||||||
import PasswordUtility from "../utilities/PasswordUtility";
|
import PasswordUtility from "../utilities/PasswordUtility";
|
||||||
import UserParty from "../entities/UserParty";
|
import UserParty from "../entities/UserParty";
|
||||||
import UserPartyRepo from "../repos/UserPartyRepo";
|
import UserPartyRepo from "../repos/UserPartyRepo";
|
||||||
|
import { UserLevel } from "../enums/UserLevel";
|
||||||
|
|
||||||
export default class UserService {
|
export default class UserService {
|
||||||
public static async AuthenticateUser(username:string, password:string) {
|
public static async AuthenticateUser(username:string, password:string) {
|
||||||
|
@ -201,4 +202,31 @@ export default class UserService {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async SaveUser(currentUserId:number, id:number | undefined, username:string, userLevel:UserLevel) {
|
||||||
|
try {
|
||||||
|
const existingCheck = id ? null : await UserRepo.selectByUsername(username);
|
||||||
|
if (existingCheck) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = id ? await UserRepo.selectById(id) : null;
|
||||||
|
if (!user) {
|
||||||
|
user = new User();
|
||||||
|
user.CreatedByUserId = currentUserId;
|
||||||
|
user.CreatedDatetime = new Date();
|
||||||
|
} else {
|
||||||
|
user.LastModifiedByUserId = currentUserId;
|
||||||
|
user.LastModifiedDatetime = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Username = username;
|
||||||
|
user.UserLevel = userLevel;
|
||||||
|
|
||||||
|
return await UserRepo.insertUpdate(user);
|
||||||
|
} catch (e) {
|
||||||
|
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -37,7 +37,7 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title">Badges</h5>
|
<h4 class="card-title">Badges</h5>
|
||||||
<h3 class="card-text mb-0"><%= 0 %></h3>
|
<h3 class="card-text mb-0"><%= badgeCount %></h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +48,8 @@
|
||||||
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/users">Manage Users</a>
|
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/users">Manage Users</a>
|
||||||
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/parties">Manage Parties</a>
|
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/parties">Manage Parties</a>
|
||||||
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/badges">Manage Badges</a>
|
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/badges">Manage Badges</a>
|
||||||
|
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/websessions">Web Sessions</a>
|
||||||
|
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/wssessions">Websocket Sessions</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%- include("../base/footer") %>
|
<%- include("../base/footer") %>
|
|
@ -15,6 +15,9 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1>Party Management</h1>
|
<h1>Party Management</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a class="btn btn-primary btn-lg me-2" href="/admin/party">Add Party</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row my-5">
|
<div class="row my-5">
|
||||||
|
@ -33,8 +36,8 @@
|
||||||
<td><%= party.PartyRef %></td>
|
<td><%= party.PartyRef %></td>
|
||||||
<td><%= party.Name %></td>
|
<td><%= party.Name %></td>
|
||||||
<td class="text-end text-nowrap align-middle">
|
<td class="text-end text-nowrap align-middle">
|
||||||
<a class="btn btn-sm btn-primary" href="/admin/user?id=<%= party.Id %>">Edit</a>
|
<a class="btn btn-sm btn-primary" href="/admin/party?id=<%= party.Id %>">Edit</a>
|
||||||
<a class="btn btn-sm btn-danger" href="/admin/userdelete?id=<%= party.Id %>">Delete</a>
|
<a class="btn btn-sm btn-danger" href="/admin/deleteparty?id=<%= party.Id %>" onclick="return confirm(`Are you sure you want to delete '<%= party.Name %>'?`)">Delete</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
43
server/views/admin/party.ejs
Normal file
43
server/views/admin/party.ejs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<%- include("../base/header", { title: typeof(id) === "undefined" || id.trim().length === 0 ? "Add Party" : `Edit ${name}`, userId: session.userId, isAdmin: true }) %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin">Admin</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/admin/parties">Party Management</a></li>
|
||||||
|
<li class="breadcrumb-item active"><a><%= typeof(id) === "undefined" || id.trim().length === 0 ? "Add Party" : `Edit ${name}` %></a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1><%= typeof(id) === "undefined" || id.trim().length === 0 ? "Add Party" : `Edit ${name}` %></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="needs-validation" novalidate>
|
||||||
|
<input type="hidden" name="id" value="<%= typeof(id) === "undefined" ? "" : id %>" />
|
||||||
|
|
||||||
|
<div class="row my-5 row-cols-1 row-cols-sm-2">
|
||||||
|
<div class="col col-sm-3 mb-3">
|
||||||
|
<label for="partyRef" class="form-label">Party Ref</label>
|
||||||
|
<input class="form-control" id="partyRef" name="partyRef" value="<%= typeof(partyRef) === "undefined" ? "" : partyRef %>" required maxlength="5" />
|
||||||
|
</div>
|
||||||
|
<div class="col col-sm-9 col-sm-9">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input class="form-control" id="name" name="name" value="<%= typeof(name) === "undefined" ? "" : name %>" required maxlength="255" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col text-center">
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a type="submit" class="btn btn-danger" href="/admin/parties">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<%- include("../base/footer") %>
|
65
server/views/admin/user.ejs
Normal file
65
server/views/admin/user.ejs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<%- include("../base/header", { title: typeof(id) === "undefined" || id.trim().length === 0 ? "Add User" : `Edit ${username}`, userId: session.userId, isAdmin: true }) %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin">Admin</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/admin/users">User Management</a></li>
|
||||||
|
<li class="breadcrumb-item active"><a><%= typeof(id) === "undefined" ? "Add User" : `Edit ${username}` %></a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1><%= typeof(id) === "undefined" || id.trim().length === 0 ? "Add User" : `Edit ${username}` %></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if (typeof(message) === "string") { %>
|
||||||
|
<div class="alert alert-danger text-center mt-5" role="alert"><%= message %></div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<form method="post" class="needs-validation" novalidate>
|
||||||
|
<input type="hidden" name="id" value="<%= typeof(id) === "undefined" || id.trim().length === 0 ? "" : id %>" />
|
||||||
|
|
||||||
|
<div class="row mt-5 mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input class="form-control" id="username" name="username" value="<%= typeof(username) === "undefined" ? "" : username %>" required maxlength="255" autocomplete="one-time-code" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-5">
|
||||||
|
<div class="col">
|
||||||
|
<label for="name" class="form-label">Permissions</label>
|
||||||
|
<select class="form-select" name="userLevel" required>
|
||||||
|
<option value="" disabled <%= userLevel === "0" ? "selected" : "" %>>Please select...</option>
|
||||||
|
<option value="10" <%= userLevel === "10" ? "selected" : "" %>>User</option>
|
||||||
|
<option value="999" <%= userLevel === "999" ? "selected" : "" %>>Admin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col text-center">
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
<a type="submit" class="btn btn-danger" href="/admin/users">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const imageImg = document.querySelector("#imageImg");
|
||||||
|
const imageUrl = document.querySelector("#imageUrl");
|
||||||
|
imageUrl.addEventListener("change", () => {
|
||||||
|
if (imageUrl.value.trim() === ""){
|
||||||
|
imageImg.src = "/img/missing.png";
|
||||||
|
} else {
|
||||||
|
imageImg.src = imageUrl.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<%- include("../base/footer") %>
|
|
@ -15,6 +15,9 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1>User Management</h1>
|
<h1>User Management</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a class="btn btn-primary btn-lg me-2" href="/admin/user">Add User</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row my-5">
|
<div class="row my-5">
|
||||||
|
|
45
server/views/admin/websessions.ejs
Normal file
45
server/views/admin/websessions.ejs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<%- include("../base/header", { title: "Web Sessions", userId: session.userId, isAdmin: true }) %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin">Admin</a></li>
|
||||||
|
<li class="breadcrumb-item active"><a>Web Sessions</a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1>Web Sessions</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-5">
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<th class="align-middle text-center">User Id</th>
|
||||||
|
<th class="align-middle text-center">Username</th>
|
||||||
|
<th class="align-middle text-center">Expires At</th>
|
||||||
|
<th> </th>
|
||||||
|
</thead>
|
||||||
|
<tbody><%
|
||||||
|
const keys = sessions._getKeys();
|
||||||
|
for (const key of keys) {
|
||||||
|
const session = sessions.get(key); %>
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle text-center"><%= session.userId %></td>
|
||||||
|
<td class="align-middle text-center"><%= session.username %></td>
|
||||||
|
<td class="align-middle text-center"><%= session.validityPeriod.toString().split("GMT")[0] %></td>
|
||||||
|
<td class="text-end text-nowrap align-middle">
|
||||||
|
<a class="btn btn-sm btn-danger" href="/admin/expiresession?key=<%= key %>">Expire</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% } %></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include("../base/footer") %>
|
43
server/views/admin/wssessions.ejs
Normal file
43
server/views/admin/wssessions.ejs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<%- include("../base/header", { title: "Websocket Sessions", userId: session.userId, isAdmin: true }) %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/admin">Admin</a></li>
|
||||||
|
<li class="breadcrumb-item active"><a>Websocket Sessions</a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1>Websocket Sessions</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-5">
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<th class="align-middle text-center">#</th>
|
||||||
|
<th class="align-middle text-center">Username</th>
|
||||||
|
<th class="align-middle text-center">Current Location</th>
|
||||||
|
<th class="align-middle text-center">Is AFK?</th>
|
||||||
|
</thead>
|
||||||
|
<tbody><%
|
||||||
|
const keys = users._getKeys();
|
||||||
|
for (const key of keys) {
|
||||||
|
const session = users.get(key); %>
|
||||||
|
<tr>
|
||||||
|
<td class="align-middle text-center"><%= session.id %></td>
|
||||||
|
<td class="align-middle text-center"><%= session.username %></td>
|
||||||
|
<td class="align-middle text-center"><a href="https://<%= session.rawURL %>">https://<%= session.rawURL %></a></td>
|
||||||
|
<td class="align-middle text-center"><%= session.isAfk ? "Yes" : "No" %></td>
|
||||||
|
</tr>
|
||||||
|
<% } %></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%- include("../base/footer") %>
|
|
@ -20,17 +20,16 @@
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
<nav class="navbar navbar-expand bg-body-tertiary">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
MultiProbe<%= typeof(isAdmin) === "undefined" ? "" : " Admin" %>
|
MultiProbe<%= typeof(isAdmin) === "undefined" ? "" : " Admin" %>
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto">
|
||||||
|
<div class="nav-item">
|
||||||
|
<a class="nav-link" href="https://git.eusv.net/tgpholly/t00-multiuser" target="_blank">Source Code</a>
|
||||||
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<% if (typeof(userId) !== "undefined") { %>
|
<% if (typeof(userId) !== "undefined") { %>
|
||||||
|
|
Loading…
Reference in a new issue