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 AdminBadgeViewModel from "../models/admin/AdminBadgeViewModel";
|
||||
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 AdminPartiesViewModel from "../models/admin/AdminPartiesViewModel";
|
||||
import AdminPartyViewModel from "../models/admin/AdminPartyViewModel";
|
||||
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 PartyService from "../services/PartyService";
|
||||
import UserService from "../services/UserService";
|
||||
|
@ -13,7 +21,8 @@ export default class AdminController_Auth$Admin extends Controller {
|
|||
public async Index_Get() {
|
||||
const adminIndexViewModel: AdminIndexViewModel = {
|
||||
userCount: await UserService.GetUserCount(),
|
||||
partyCount: await PartyService.GetPartyCount()
|
||||
partyCount: await PartyService.GetPartyCount(),
|
||||
badgeCount: await BadgeService.GetBadgeCount()
|
||||
};
|
||||
|
||||
return this.view(adminIndexViewModel);
|
||||
|
@ -27,6 +36,33 @@ export default class AdminController_Auth$Admin extends Controller {
|
|||
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() {
|
||||
const adminPartiesViewModel: AdminPartiesViewModel = {
|
||||
parties: await PartyService.GetAll()
|
||||
|
@ -35,6 +71,40 @@ export default class AdminController_Auth$Admin extends Controller {
|
|||
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() {
|
||||
const adminBadgesViewModel: AdminBadgesViewModel = {
|
||||
badges: await BadgeService.LoadAll()
|
||||
|
@ -80,4 +150,30 @@ export default class AdminController_Auth$Admin extends Controller {
|
|||
|
||||
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 AdminController_Auth$Admin from "./controller/AdminController";
|
||||
import { join } from "path";
|
||||
import WsData from "./objects/WsData";
|
||||
|
||||
Console.customHeader(`MultiProbe server started at ${new Date()}`);
|
||||
|
||||
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);
|
||||
|
||||
|
|
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 {
|
||||
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);
|
||||
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, {
|
||||
path: "/",
|
||||
|
|
|
@ -2,11 +2,13 @@ import { UserLevel } from "../enums/UserLevel";
|
|||
|
||||
export default class SessionUser {
|
||||
public readonly userId:number;
|
||||
public readonly username:string;
|
||||
public readonly userLevel:UserLevel;
|
||||
public readonly validityPeriod:Date;
|
||||
|
||||
constructor(userId:number, userLevel:UserLevel, validityPeriod:Date) {
|
||||
constructor(userId:number, username:string, userLevel:UserLevel, validityPeriod:Date) {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.userLevel = userLevel;
|
||||
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) {
|
||||
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;", [
|
||||
|
|
|
@ -56,21 +56,23 @@ export default class PartyRepo {
|
|||
}
|
||||
|
||||
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)"];
|
||||
}
|
||||
|
||||
public static async insertUpdate(party:Party) {
|
||||
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)
|
||||
]);
|
||||
]))[0]["Id"];
|
||||
} else {
|
||||
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
|
||||
]);
|
||||
}
|
||||
|
||||
return party;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,14 +60,16 @@ export default class UserPartyRepo {
|
|||
|
||||
public static async insertUpdate(userParty:UserParty) {
|
||||
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)
|
||||
]);
|
||||
]))[0]["Id"];
|
||||
} else {
|
||||
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
|
||||
]);
|
||||
}
|
||||
|
||||
return userParty;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,21 +50,23 @@ export default class UserRepo {
|
|||
}
|
||||
|
||||
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)"];
|
||||
}
|
||||
|
||||
public static async insertUpdate(user:User) {
|
||||
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)
|
||||
]);
|
||||
]))[0]["Id"];
|
||||
} else {
|
||||
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
|
||||
]);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,4 +57,13 @@ export default abstract class BadgeService {
|
|||
|
||||
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 {
|
||||
public static async CreateParty(currentUserId:number, name:string, partyRef:string) {
|
||||
try {
|
||||
const party = new Party();
|
||||
let party = new Party();
|
||||
party.Name = name;
|
||||
party.PartyRef = partyRef;
|
||||
party.CreatedByUserId = currentUserId;
|
||||
party.CreatedDatetime = new Date();
|
||||
|
||||
await PartyRepo.insertUpdate(party);
|
||||
|
||||
const newParty = await PartyRepo.selectByPartyRef(partyRef);
|
||||
if (!newParty) {
|
||||
throw "This shouldn't happen";
|
||||
}
|
||||
party = await PartyRepo.insertUpdate(party);
|
||||
|
||||
const userParty = new UserParty();
|
||||
userParty.UserId = currentUserId;
|
||||
userParty.PartyId = newParty.Id;
|
||||
userParty.PartyId = party.Id;
|
||||
userParty.CreatedByUserId = currentUserId;
|
||||
userParty.CreatedDatetime = new Date();
|
||||
|
||||
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) {
|
||||
Console.printError(`MultiProbe server service error:\n${e}`);
|
||||
throw e;
|
||||
|
@ -76,7 +94,25 @@ export default abstract class PartyService {
|
|||
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.DeletedDatetime = new Date();
|
||||
|
|
|
@ -4,6 +4,7 @@ import UserRepo from "../repos/UserRepo";
|
|||
import PasswordUtility from "../utilities/PasswordUtility";
|
||||
import UserParty from "../entities/UserParty";
|
||||
import UserPartyRepo from "../repos/UserPartyRepo";
|
||||
import { UserLevel } from "../enums/UserLevel";
|
||||
|
||||
export default class UserService {
|
||||
public static async AuthenticateUser(username:string, password:string) {
|
||||
|
@ -201,4 +202,31 @@ export default class UserService {
|
|||
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-body">
|
||||
<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>
|
||||
|
@ -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/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/websessions">Web Sessions</a>
|
||||
<a class="btn btn-primary btn-lg me-2 mb-3" href="/admin/wssessions">Websocket Sessions</a>
|
||||
</div>
|
||||
</div>
|
||||
<%- include("../base/footer") %>
|
|
@ -15,6 +15,9 @@
|
|||
<div class="col">
|
||||
<h1>Party Management</h1>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a class="btn btn-primary btn-lg me-2" href="/admin/party">Add Party</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row my-5">
|
||||
|
@ -33,8 +36,8 @@
|
|||
<td><%= party.PartyRef %></td>
|
||||
<td><%= party.Name %></td>
|
||||
<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-danger" href="/admin/userdelete?id=<%= party.Id %>">Delete</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/deleteparty?id=<%= party.Id %>" onclick="return confirm(`Are you sure you want to delete '<%= party.Name %>'?`)">Delete</a>
|
||||
</td>
|
||||
</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">
|
||||
<h1>User Management</h1>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a class="btn btn-primary btn-lg me-2" href="/admin/user">Add User</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<nav class="navbar navbar-expand bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">
|
||||
MultiProbe<%= typeof(isAdmin) === "undefined" ? "" : " Admin" %>
|
||||
</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">
|
||||
<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 class="navbar-nav">
|
||||
<% if (typeof(userId) !== "undefined") { %>
|
||||
|
|
Loading…
Reference in a new issue