From 0c77516b7b4c858a8595884968079d71de48763f Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sun, 23 Dec 2018 17:41:14 +0100 Subject: [PATCH] Implemented query account management --- shared/css/modal-query.scss | 114 ++++++++++++++++ shared/html/templates.html | 53 +++++++- shared/js/connection.ts | 71 ++++++++++ shared/js/load.ts | 1 + shared/js/permission/PermissionManager.ts | 3 +- shared/js/proto.ts | 10 +- shared/js/ui/frames/ControlBar.ts | 18 ++- shared/js/ui/modal/ModalQuery.ts | 19 ++- shared/js/ui/modal/ModalQueryManage.ts | 156 ++++++++++++++++++++++ 9 files changed, 431 insertions(+), 14 deletions(-) create mode 100644 shared/js/ui/modal/ModalQueryManage.ts diff --git a/shared/css/modal-query.scss b/shared/css/modal-query.scss index 746b4512..16c70228 100644 --- a/shared/css/modal-query.scss +++ b/shared/css/modal-query.scss @@ -56,4 +56,118 @@ margin-top: 5px; text-align: right; } +} + +.query-management { + height: 100%; + display: flex; + flex-direction: column; + + .container { + display: flex; + flex-direction: column; + justify-content: stretch; + + .header, .footer { + flex-grow: 0; + flex-shrink: 0; + } + + .header { + display: flex; + flex-direction: row; + justify-content: stretch; + + .buttons { + flex-grow: 0; + } + + .search { + margin-left: 5px; + flex-grow: 1; + + input { + width: 100%; + } + } + } + + .query-list { + margin-top: 5px; + + display: flex; + flex-grow: 1; + flex-direction: column; + justify-content: stretch; + + .column { + &.column-username { + width: calc(50% - 75px) + } + + &.column-unique-id { + width: calc(50% - 75px) + } + + &.column-bound-server { + width: 150px; + flex-grow: 0; + } + } + + .query-list-header { + flex-grow: 0; + flex-shrink: 0; + display: flex; + flex-direction: row; + height: 20px; + + .column { + border: 1px solid lightgray; + text-align: center; + } + } + + .query-list-entries-container { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: start; + overflow-y: auto; + min-height: 250px; + + .entry { + display: flex; + flex-direction: row; + + .column { + margin-left: 2px; + } + + cursor: pointer; + + &.selected { + background-color: blue; + } + } + + &.scrollbar { + .column-username { + width: calc(50% - 75px + 30px) + } + + .column-unique-id { + width: calc(50% - 75px + 30px) + } + } + } + } + + .footer { + margin-top: 5px; + display: flex; + flex-direction: row; + justify-content: space-between; + } + } } \ No newline at end of file diff --git a/shared/html/templates.html b/shared/html/templates.html index 6e75362c..364ab5d7 100644 --- a/shared/html/templates.html +++ b/shared/html/templates.html @@ -89,8 +89,9 @@
@@ -1105,7 +1106,7 @@ - +9 + + + + + \ No newline at end of file diff --git a/shared/js/connection.ts b/shared/js/connection.ts index 2adfe796..06fc815f 100644 --- a/shared/js/connection.ts +++ b/shared/js/connection.ts @@ -402,10 +402,24 @@ interface ClientNameFromUid { response: ClientNameInfo[] } +interface QueryListEntry { + username: string; + unique_id: string; + bounded_server: number; +} + +interface QueryList { + flag_own: boolean; + flag_all: boolean; + + queries: QueryListEntry[]; +} + class CommandHelper { readonly connection: ServerConnection; private _callbacks_namefromuid: ClientNameFromUid[] = []; + private _who_am_i: any; constructor(connection) { this.connection = connection; @@ -432,6 +446,63 @@ class CommandHelper { return req.promise; } + request_query_list(server_id: number = undefined) : Promise { + return new Promise((resolve, reject) => { + this.connection.commandHandler["notifyquerylist"] = json => { + const result = {} as QueryList; + + result.flag_all = json[0]["flag_all"]; + result.flag_own = json[0]["flag_own"]; + result.queries = []; + + for(const entry of json) { + const rentry = {} as QueryListEntry; + rentry.bounded_server = entry["client_bounded_server"]; + rentry.username = entry["client_login_name"]; + rentry.unique_id = entry["client_unique_identifier"]; + + result.queries.push(rentry); + } + + resolve(result); + this.connection.commandHandler["notifyquerylist"] = undefined; + }; + + let data = {}; + if(server_id !== undefined) + data["server_id"] = server_id; + + this.connection.sendCommand("querylist", data).catch(error => { + if(error instanceof CommandResult) { + if(error.id == 0x0501) { + resolve(undefined); + return; + } + } + reject(error); + }) + }); + } + + /** + * @deprecated + * Its just a workaround for the query management. + * There is no garante that the whoami trick will work forever + */ + current_virtual_server_id() : Promise { + if(this._who_am_i) + return Promise.resolve(parseInt(this._who_am_i["virtualserver_id"])); + + return new Promise((resolve, reject) => { + this.connection.commandHandler[""] = json => { + this._who_am_i = json[0]; + resolve(parseInt(this._who_am_i["virtualserver_id"])); + this.connection.commandHandler[""] = undefined; + }; + this.connection.sendCommand("whoami"); + }); + } + private handle_notifyclientnamefromuid(json: any[]) { for(let entry of json) { let info: ClientNameInfo = {} as any; diff --git a/shared/js/load.ts b/shared/js/load.ts index 49af45a2..a29a383d 100644 --- a/shared/js/load.ts +++ b/shared/js/load.ts @@ -181,6 +181,7 @@ function loadDebug() { //Load UI "js/ui/modal/ModalQuery.js", + "js/ui/modal/ModalQueryManage.js", "js/ui/modal/ModalConnect.js", "js/ui/modal/ModalSettings.js", "js/ui/modal/ModalCreateChannel.js", diff --git a/shared/js/permission/PermissionManager.ts b/shared/js/permission/PermissionManager.ts index 4372d735..e27be066 100644 --- a/shared/js/permission/PermissionManager.ts +++ b/shared/js/permission/PermissionManager.ts @@ -644,11 +644,12 @@ class PermissionManager { for(let perm of this.neededPermissions) if(perm.type.id == key || perm.type.name == key || perm.type == key) return perm; + log.debug(LogCategory.PERMISSIONS, tr("Could not resolve grant permission %o. Creating a new one."), key); let info = key instanceof PermissionInfo ? key : this.resolveInfo(key); if(!info) { log.warn(LogCategory.PERMISSIONS, tr("Requested needed permission with invalid key! (%o)"), key); - return undefined; + return new NeededPermissionValue(undefined, -2); } let result = new NeededPermissionValue(info, -2); this.neededPermissions.push(result); diff --git a/shared/js/proto.ts b/shared/js/proto.ts index fa1c855d..ea2c4910 100644 --- a/shared/js/proto.ts +++ b/shared/js/proto.ts @@ -13,6 +13,7 @@ interface JSON { interface JQuery { render(values?: any) : string; renderTag(values?: any) : JQuery; + hasScrollBar() : boolean; } interface JQueryStatic { @@ -106,8 +107,8 @@ if(typeof ($) !== "undefined") { return $(document.createElement(tagName) as any); } } - if(!$.prototype.renderTag) { - $.prototype.renderTag = function (values?: any) : JQuery { + if(!$.fn.renderTag) { + $.fn.renderTag = function (values?: any) : JQuery { let result; if(this.render) { result = $(this.render(values)); @@ -126,6 +127,11 @@ if(typeof ($) !== "undefined") { return result; } } + if(!$.fn.hasScrollBar) + $.fn.hasScrollBar = function() { + return this.get(0).scrollHeight > this.height(); + } + } if (!String.prototype.format) { diff --git a/shared/js/ui/frames/ControlBar.ts b/shared/js/ui/frames/ControlBar.ts index c0a9d9f6..8264d393 100644 --- a/shared/js/ui/frames/ControlBar.ts +++ b/shared/js/ui/frames/ControlBar.ts @@ -87,7 +87,8 @@ class ControlBar { }); query.find(".btn_query_toggle").on('click', this.on_query_visibility_toggle.bind(this)); - query.find(".btn_query_create").on('click', this.on_query_create.bind(this)) + query.find(".btn_query_create").on('click', this.on_query_create.bind(this)); + query.find(".btn_query_manage").on('click', this.on_query_manage.bind(this)); } //Need an initialise @@ -295,7 +296,12 @@ class ControlBar { private onBanlist() { if(!this.handle.serverConnection) return; - openBanList(this.handle); + if(this.handle.permissions.neededPermission(PermissionType.B_CLIENT_BAN_LIST).granted(1)) { + openBanList(this.handle); + } else { + createErrorModal(tr("You dont have the permission"), tr("You dont have the permission to view the ban list")).open(); + sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS); + } } update_bookmarks() { @@ -355,4 +361,12 @@ class ControlBar { sound.play(Sound.ERROR_INSUFFICIENT_PERMISSIONS); } } + + private on_query_manage() { + if(globalClient && globalClient.connected) { + Modals.spawnQueryManage(globalClient); + } else { + createErrorModal(tr("You have to be connected"), tr("You have to be connected!")).open(); + } + } } \ No newline at end of file diff --git a/shared/js/ui/modal/ModalQuery.ts b/shared/js/ui/modal/ModalQuery.ts index 16671c0d..ef4974ed 100644 --- a/shared/js/ui/modal/ModalQuery.ts +++ b/shared/js/ui/modal/ModalQuery.ts @@ -3,7 +3,7 @@ /// namespace Modals { - export function spawnQueryCreate() { + export function spawnQueryCreate(callback_created?: (user, pass) => any) { let modal; modal = createModal({ header: tr("Create a server query login"), @@ -20,17 +20,24 @@ namespace Modals { } //client_login_password - globalClient.serverConnection.commandHandler["notifyclientserverqueryloginpassword"] = json => { + globalClient.serverConnection.commandHandler["notifyquerycreated"] = json => { json = json[0]; spawnQueryCreated({ username: name, password: json.client_login_password - }); + }, true); + + if(callback_created) + callback_created(name, json.client_login_password); }; - globalClient.serverConnection.sendCommand("clientsetserverquerylogin", { + globalClient.serverConnection.sendCommand("querycreate", { client_login_name: name + }).catch(error => { + if(error instanceof CommandResult) + error = error.extra_message || error.message; + createErrorModal(tr("Unable to create account"), tr("Failed to create account
Message: ") + error).open(); }); modal.close(); @@ -47,10 +54,10 @@ namespace Modals { export function spawnQueryCreated(credentials: { username: string, password: string - }) { + }, yust_created: boolean) { let modal; modal = createModal({ - header: tr("Server query credentials"), + header: yust_created ? tr("Server query credentials") : tr("New server query credentials"), body: () => { let template = $("#tmpl_query_created").renderTag(credentials); template = $.spawn("div").append(template); diff --git a/shared/js/ui/modal/ModalQueryManage.ts b/shared/js/ui/modal/ModalQueryManage.ts new file mode 100644 index 00000000..7b990d7c --- /dev/null +++ b/shared/js/ui/modal/ModalQueryManage.ts @@ -0,0 +1,156 @@ +/// +/// +/// + +namespace Modals { + export function spawnQueryManage(client: TSClient) { + let modal: Modal; + let selected_query: QueryListEntry; + + const update_selected = () => { + const buttons = modal.htmlTag.find(".header .buttons"); + + //TODO gray out if no permissions (Server needs to send that... :D) + buttons.find(".button-query-delete").prop("disabled", selected_query === undefined); + buttons.find(".button-query-rename").prop("disabled", selected_query === undefined); + buttons.find(".button-query-change-password").prop("disabled", selected_query === undefined); + }; + + const update_list = () => { + const info_tag = modal.htmlTag.find(".footer .info a"); + info_tag.text("loading..."); + client.serverConnection.helper.current_virtual_server_id().then(server_id => { + client.serverConnection.helper.request_query_list(server_id).then(result => { + selected_query = undefined; + + const entries_tag = modal.htmlTag.find(".query-list-entries"); + const entry_template = $("#tmpl_query_manager-list_entry"); + entries_tag.empty(); + + for(const query of result.queries || []) { + entries_tag.append(entry_template.renderTag(query).on('click', event => { + entries_tag.find(".entry.selected").removeClass("selected"); + $(event.target).parent(".entry").addClass("selected"); + selected_query = query; + update_selected(); + })); + } + + const entry_container = modal.htmlTag.find(".query-list-entries-container"); + if(entry_container.hasScrollBar()) + entry_container.addClass("scrollbar"); + + if(!result || result.flag_all) { + info_tag.text("Showing all server queries"); + } else { + info_tag.text("Showing your server queries") + } + update_selected(); + }); + }); + //TODO error handling + }; + + modal = createModal({ + header: tr("Manage query accounts"), + body: () => { + let template = $("#tmpl_query_manager").renderTag(); + template = $.spawn("div").append(template); + + /* first open the modal */ + setTimeout(() => { + const entry_container = template.find(".query-list-entries-container"); + if(entry_container.hasScrollBar()) + entry_container.addClass("scrollbar"); + }, 100); + + template.find(".footer .buttons .button-refresh").on('click', update_list); + template.find(".button-query-create").on('click', () => { + Modals.spawnQueryCreate((user, pass) => update_list()); + }); + template.find(".button-query-rename").on('click', () => { + if(!selected_query) return; + + createInputModal(tr("Change account name"), tr("Enter the new name for the login:
"), text => text.length >= 3, result => { + if(result) { + client.serverConnection.sendCommand("queryrename", { + client_login_name: selected_query.username, + client_new_login_name: result + }).catch(error => { + if(error instanceof CommandResult) + error = error.extra_message || error.message; + createErrorModal(tr("Unable to rename account"), tr("Failed to rename account
Message: ") + error).open(); + }).then(() => { + createInfoModal(tr("Account successfully renamed"), tr("The query account has been renamed!")).open(); + update_list(); + }); + } + }).open(); + }); + template.find(".button-query-change-password").on('click', () => { + if(!selected_query) return; + + createInputModal(tr("Change account's password"), tr("Enter a new password (leave blank for auto generation):
"), text => true, result => { + if(result !== false) { + client.serverConnection.sendCommand("querychangepassword", { + client_login_name: selected_query.username, + client_login_password: result + }).catch(error => { + if(error instanceof CommandResult) + error = error.extra_message || error.message; + createErrorModal(tr("Unable to change password"), tr("Failed to change password
Message: ") + error).open(); + }); + + client.serverConnection.commandHandler["notifyquerypasswordchanges"] = json => { + Modals.spawnQueryCreated({ + username: json[0]["client_login_name"], + password: json[0]["client_login_password"] + }, false); + + client.serverConnection.commandHandler["notifyquerypasswordchanges"] = undefined; + }; + } + }).open(); + }); + template.find(".button-query-delete").on('click', () => { + if(!selected_query) return; + + Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this account?"), result => { + if(result) { + client.serverConnection.sendCommand("querydelete", { + client_login_name: selected_query.username + }).catch(error => { + if(error instanceof CommandResult) + error = error.extra_message || error.message; + createErrorModal(tr("Unable to delete account"), tr("Failed to delete account
Message: ") + error).open(); + }).then(() => { + createInfoModal(tr("Account successfully deleted"), tr("The query account has been successfully deleted!")).open(); + update_list(); + }); + } + }); + }); + template.find(".input-search").on('change keyup', () => { + const text = (template.find(".input-search").val() as string || "").toLowerCase(); + if(text.length == 0) { + template.find(".query-list-entries .entry").show(); + } else { + template.find(".query-list-entries .entry").each((_, e) => { + const element = $(e); + if(element.text().toLowerCase().indexOf(text) == -1) + element.hide(); + else + element.show(); + }) + } + }); + return template; + }, + footer: undefined, + width: 750 + }); + + update_list(); + modal.open(); + } +} \ No newline at end of file