import { PermissionManager, } from "tc-shared/permission/PermissionManager"; import {PermissionType} from "tc-shared/permission/PermissionType"; import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {createErrorModal, createInfoModal, createInputModal, createModal, Modal} from "tc-shared/ui/elements/Modal"; import {HTMLPermissionEditor} from "tc-shared/ui/modal/permission/HTMLPermissionEditor"; import {spawnIconSelect} from "tc-shared/ui/modal/ModalIconSelect"; import {CanvasPermissionEditor} from "tc-shared/ui/modal/permission/CanvasPermissionEditor"; import {Settings, settings} from "tc-shared/settings"; import {ChannelEntry} from "tc-shared/ui/channel"; import {LogCategory} from "tc-shared/log"; import {CommandResult, ErrorID} from "tc-shared/connection/ServerConnectionDeclaration"; import * as log from "tc-shared/log"; import {Group, GroupManager, GroupTarget, GroupType} from "tc-shared/permission/GroupManager"; import {spawnYesNo} from "tc-shared/ui/modal/ModalYesNo"; import * as contextmenu from "tc-shared/ui/elements/ContextMenu"; import {copy_to_clipboard} from "tc-shared/utils/helpers"; import {formatMessage} from "tc-shared/ui/frames/chat"; import { senseless_channel_group_permissions, senseless_channel_permissions, senseless_client_channel_permissions, senseless_client_permissions, senseless_server_group_permissions } from "tc-shared/ui/modal/permission/SenselessPermissions"; import {AbstractPermissionEditor, PermissionEditorMode} from "tc-shared/ui/modal/permission/AbstractPermissionEditor"; import {tra} from "tc-shared/i18n/localize"; declare global { interface JQuery { dropdown: any; } } export type OptionsServerGroup = {}; export type OptionsChannelGroup = {}; export type OptionsClientPermissions = { unique_id?: string }; export type OptionsChannelPermissions = { channel_id?: number }; export type OptionsClientChannelPermissions = OptionsClientPermissions & OptionsChannelPermissions; export interface OptionMap { "sg": OptionsServerGroup, "cg": OptionsChannelGroup, "clp": OptionsClientPermissions, "chp": OptionsChannelPermissions, "clchp": OptionsClientChannelPermissions } export function _space() { const now = Date.now(); while(now + 100 > Date.now()); } export function spawnPermissionEdit(connection: ConnectionHandler, selected_tab?: T, options?: OptionMap[T]) : Modal { options = options || {}; const modal = createModal({ header: function() { return tr("Server Permissions"); }, body: function () { let properties: any = {}; let tag = $("#tmpl_server_permissions").renderTag(properties); /* build the permission editor */ const permission_editor: AbstractPermissionEditor = (() => { const editor = new HTMLPermissionEditor(); editor.initialize(connection.permissions.groupedPermissions()); editor.icon_resolver = async id => { const icon = connection.fileManager.icons.load_icon(id); await icon.await_loading(); const tag = document.createElement("img"); await new Promise((resolve, reject) => { tag.onerror = reject; tag.onload = resolve; tag.src = icon.loaded_url || "nope"; }); return tag; }; editor.icon_selector = current_icon => new Promise(resolve => { spawnIconSelect(connection, id => resolve(new Int32Array([id])[0]), current_icon); }); if(editor instanceof CanvasPermissionEditor) setTimeout(() => editor.update_ui(), 500); return editor; })(); const container_tab_list = tag.find(".right > .header"); { const label_current = tag.find(".left .container-selected"); const create_tab = (tab_entry: JQuery, container_name: string, hidden_permissions: PermissionType[]) => { const target_container = tag.find(".body .container." + container_name); tab_entry.on('click', () => { /* Using a timeout here prevents unnecessary style calculations required by other click event handlers */ setTimeout(() => { container_tab_list.find(".selected").removeClass("selected"); tab_entry.addClass("selected"); label_current.text(tab_entry.find("a").text()); /* dont use show() here because it causes a style recalculation */ for(const element of tag.find(".body .container")) (element).style.display = "none"; permission_editor.set_hidden_permissions(settings.static_global(Settings.KEY_PERMISSIONS_SHOW_ALL) ? undefined : hidden_permissions); permission_editor.html_tag()[0].remove(); target_container.find(".permission-editor").trigger('show'); target_container.find(".permission-editor").append(permission_editor.html_tag()); for(const element of target_container) (element).style.display = null; }, 0); }); }; create_tab(container_tab_list.find(".sg"), "container-view-server-groups", senseless_server_group_permissions); create_tab(container_tab_list.find(".cg"), "container-view-channel-groups", senseless_channel_group_permissions); create_tab(container_tab_list.find(".chp"), "container-view-channel-permissions", senseless_channel_permissions); create_tab(container_tab_list.find(".clp"), "container-view-client-permissions", senseless_client_permissions); create_tab(container_tab_list.find(".clchp"), "container-view-client-channel-permissions", senseless_client_channel_permissions); } apply_server_groups(connection, permission_editor, tag.find(".left .container-view-server-groups"), tag.find(".right .container-view-server-groups")); apply_channel_groups(connection, permission_editor, tag.find(".left .container-view-channel-groups"), tag.find(".right .container-view-channel-groups")); apply_channel_permission(connection, permission_editor, tag.find(".left .container-view-channel-permissions"), tag.find(".right .container-view-channel-permissions")); apply_client_permission(connection, permission_editor, tag.find(".left .container-view-client-permissions"), tag.find(".right .container-view-client-permissions"), selected_tab == "clp" ? options : {}); apply_client_channel_permission(connection, permission_editor, tag.find(".left .container-view-client-channel-permissions"), tag.find(".right .container-view-client-channel-permissions"), selected_tab == "clchp" ? options : {}); setTimeout(() => container_tab_list.find("." + (selected_tab || "sg")).trigger('click'), 0); return tag.dividerfy(); }, footer: undefined, min_width: "30em", height: "80%", trigger_tab: false, full_size: true }); const tag = modal.htmlTag; tag.find(".modal-body").addClass("modal-permission-editor"); if(selected_tab) setTimeout(() => tag.find(".tab-header .entry[x-id=" + selected_tab + "]").first().trigger("click"), 1); tag.find(".btn_close").on('click', () => { modal.close(); }); return modal; } function build_channel_tree(connection: ConnectionHandler, channel_list: JQuery, selected_channel: number, select_callback: (channel: ChannelEntry, icon_update: (id: number) => any) => any) { const root = connection.channelTree.get_first_channel(); if(!root) return; const build_channel = (channel: ChannelEntry, level: number) => { let tag = $.spawn("div").addClass("channel").css("padding-left", "calc(0.25em + " + (level * 16) + "px)").attr("channel-id", channel.channelId); let icon_tag = connection.fileManager.icons.generateTag(channel.properties.channel_icon_id); icon_tag.appendTo(tag); const _update_icon = icon_id => icon_tag.replaceWith(icon_tag = connection.fileManager.icons.generateTag(icon_id)); { let name = $.spawn("a").text(channel.channelName() + " (" + channel.channelId + ")").addClass("name"); name.appendTo(tag); } tag.on('click', event => { channel_list.find(".selected").removeClass("selected"); tag.addClass("selected"); select_callback(channel, _update_icon); }); return tag; }; const build_channels = (root: ChannelEntry, level: number) => { build_channel(root, level).appendTo(channel_list); const child_head = root.children(false).find(e => e.channel_previous === undefined); if(child_head) build_channels(child_head, level + 1); if(root.channel_next) build_channels(root.channel_next, level) }; build_channels(root, 0); let selected_channel_tag = channel_list.find(".channel[channel-id=" + selected_channel + "]"); if(!selected_channel_tag || selected_channel_tag.length < 1) selected_channel_tag = channel_list.find('.channel').first(); setTimeout(() => selected_channel_tag.trigger('click'), 0); } function apply_client_channel_permission(connection: ConnectionHandler, editor: AbstractPermissionEditor, tab_left: JQuery, tab_right: JQuery, options: OptionsClientChannelPermissions) { let current_cldbid: number = 0; let current_channel: ChannelEntry; /* the editor */ { const pe_client = tab_right.find(".permission-editor"); tab_right.on('show', event => { editor.set_toggle_button(undefined, undefined); pe_client.append(editor.html_tag()); if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) { if(current_cldbid && current_channel) editor.set_mode(PermissionEditorMode.VISIBLE); else editor.set_mode(PermissionEditorMode.UNSET); } else { editor.set_mode(PermissionEditorMode.NO_PERMISSION); return; } editor.set_listener_update(() => { if(!current_cldbid || !current_channel) return; connection.permissions.requestClientChannelPermissions(current_cldbid, current_channel.channelId).then(result => { editor.set_permissions(result); editor.set_mode(PermissionEditorMode.VISIBLE); }).catch(error => { console.log(error); //TODO handling? }); }); /* TODO: Error handling? */ editor.set_listener(async (permission, value) => { if (!current_cldbid) throw "unset client"; if (!current_channel) throw "unset channel"; if (value.remove) { /* remove the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Removing client channel permission %s. permission.id: %o"), permission.name, permission.id, ); await connection.serverConnection.send_command("channelclientdelperm", { cldbid: current_cldbid, cid: current_channel.channelId, permid: permission.id, }); } else { log.info(LogCategory.PERMISSIONS, tr("Removing client channel grant permission %s. permission.id: %o"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("channelclientdelperm", { cldbid: current_cldbid, cid: current_channel.channelId, permid: permission.id_grant(), }); } } else { /* add the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Adding or updating client channel permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}"), permission.name, permission.id, value.value, value.flag_skip, value.flag_negate ); await connection.serverConnection.send_command("channelclientaddperm", { cldbid: current_cldbid, cid: current_channel.channelId, permid: permission.id, permvalue: value.value, permskip: value.flag_skip, permnegated: value.flag_negate }); } else { log.info(LogCategory.PERMISSIONS, tr("Adding or updating client channel grant permission %s. permission.{id: %o, value: %o}"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("channelclientaddperm", { cldbid: current_cldbid, cid: current_channel.channelId, permid: permission.id_grant(), permvalue: value.granted, permskip: false, permnegated: false }); } } }); /* FIXME: Use cached permissions */ editor.trigger_update(); }); } build_channel_tree(connection, tab_left.find(".list-channel .entries"), options.channel_id || 0, channel => { if(current_channel == channel) return; current_channel = channel; /* TODO: Test for visibility */ editor.trigger_update(); }); { const tag_select = tab_left.find(".client-select"); const tag_select_uid = tag_select.find("input"); const tag_select_error = tag_select.find(".invalid-feedback"); const tag_client_name = tab_left.find(".client-name"); const tag_client_uid = tab_left.find(".client-uid"); const tag_client_dbid = tab_left.find(".client-dbid"); const resolve_client = () => { let client_uid = tag_select_uid.val() as string; connection.serverConnection.command_helper.info_from_uid(client_uid).then(result => { if(!result || result.length == 0) return Promise.reject("invalid data"); tag_select.removeClass('is-invalid'); tag_client_name.val(result[0].client_nickname ); tag_client_uid.val(result[0].client_unique_id); tag_client_dbid.val(result[0].client_database_id); current_cldbid = result[0].client_database_id; editor.trigger_update(); }).catch(error => { console.log(error); if(error instanceof CommandResult) { if(error.id == ErrorID.EMPTY_RESULT) error = "unknown client"; else error = error.extra_message || error.message; } tag_client_name.val(""); tag_client_uid.val(""); tag_client_dbid.val(""); tag_select_error.text(error); tag_select.addClass('is-invalid'); editor.set_mode(PermissionEditorMode.UNSET); }); }; tag_select_uid.on('change', event => resolve_client()); if(options.unique_id) { tag_select_uid.val(options.unique_id); setTimeout(() => resolve_client()); } } } function apply_client_permission(connection: ConnectionHandler, editor: AbstractPermissionEditor, tab_left: JQuery, tab_right: JQuery, options: OptionsClientPermissions) { let current_cldbid: number = 0; /* the editor */ { const pe_client = tab_right.find("permission-editor.client"); tab_right.on('show', event => { editor.set_toggle_button(undefined, undefined); pe_client.append(editor.html_tag()); if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CLIENT_PERMISSION_LIST).granted(1)) { if(current_cldbid) editor.set_mode(PermissionEditorMode.VISIBLE); else editor.set_mode(PermissionEditorMode.UNSET); } else { editor.set_mode(PermissionEditorMode.NO_PERMISSION); return; } editor.set_listener_update(() => { if(!current_cldbid) return; connection.permissions.requestClientPermissions(current_cldbid).then(result => { editor.set_permissions(result); editor.set_mode(PermissionEditorMode.VISIBLE); }).catch(error => { console.log(error); //TODO handling? }); }); /* TODO: Error handling? */ editor.set_listener(async (permission, value) => { if (!current_cldbid) throw "unset client"; if (value.remove) { /* remove the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Removing client permission %s. permission.id: %o"), permission.name, permission.id, ); await connection.serverConnection.send_command("clientdelperm", { cldbid: current_cldbid, permid: permission.id, }); } else { log.info(LogCategory.PERMISSIONS, tr("Removing client grant permission %s. permission.id: %o"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("clientdelperm", { cldbid: current_cldbid, permid: permission.id_grant(), }); } } else { /* add the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Adding or updating client permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}"), permission.name, permission.id, value.value, value.flag_skip, value.flag_negate ); await connection.serverConnection.send_command("clientaddperm", { cldbid: current_cldbid, permid: permission.id, permvalue: value.value, permskip: value.flag_skip, permnegated: value.flag_negate }); } else { log.info(LogCategory.PERMISSIONS, tr("Adding or updating client grant permission %s. permission.{id: %o, value: %o}"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("clientaddperm", { cldbid: current_cldbid, permid: permission.id_grant(), permvalue: value.granted, permskip: false, permnegated: false }); } } }); /* FIXME: Use cached permissions */ editor.trigger_update(); }); } const tag_select = tab_left.find(".client-select"); const tag_select_uid = tag_select.find("input"); const tag_select_error = tag_select.find(".invalid-feedback"); const tag_client_name = tab_left.find(".client-name"); const tag_client_uid = tab_left.find(".client-uid"); const tag_client_dbid = tab_left.find(".client-dbid"); const resolve_client = () => { let client_uid = tag_select_uid.val() as string; connection.serverConnection.command_helper.info_from_uid(client_uid).then(result => { if(!result || result.length == 0) return Promise.reject("invalid data"); tag_select.removeClass("is-invalid"); tag_client_name.val(result[0].client_nickname ); tag_client_uid.val(result[0].client_unique_id); tag_client_dbid.val(result[0].client_database_id); current_cldbid = result[0].client_database_id; editor.trigger_update(); }).catch(error => { console.log(error); if(error instanceof CommandResult) { if(error.id == ErrorID.EMPTY_RESULT) error = "unknown client"; else error = error.extra_message || error.message; } tag_client_name.val(""); tag_client_uid.val(""); tag_client_dbid.val(""); tag_select_error.text(error); tag_select.addClass("is-invalid"); editor.set_mode(PermissionEditorMode.UNSET); }); }; tag_select_uid.on('change', event => resolve_client()); if(options.unique_id) { tag_select_uid.val(options.unique_id); setTimeout(() => resolve_client()); } } function apply_channel_permission(connection: ConnectionHandler, editor: AbstractPermissionEditor, tab_left: JQuery, tab_right: JQuery) { let current_channel: ChannelEntry | undefined; let update_channel_icon: (id: number) => any; /* the editor */ { const pe_channel = tab_right.find(".permission-editor"); tab_right.on('show', event => { editor.set_toggle_button(undefined, undefined); pe_channel.append(editor.html_tag()); if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNEL_PERMISSION_LIST).granted(1)) editor.set_mode(PermissionEditorMode.VISIBLE); else { editor.set_mode(PermissionEditorMode.NO_PERMISSION); return; } editor.set_listener_update(() => { if(!current_channel) return; connection.permissions.requestChannelPermissions(current_channel.channelId).then(result => editor.set_permissions(result)).catch(error => { editor.set_permissions([]); console.log(error); //TODO handling? }); }); editor.set_listener(async (permission, value) => { if (!current_channel) throw "unset channel"; if (value.remove) { /* remove the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Removing channel permission %s. permission.id: %o"), permission.name, permission.id, ); await connection.serverConnection.send_command("channeldelperm", { cid: current_channel.channelId, permid: permission.id, }).then(e => { if(permission.name === "i_icon_id" && update_channel_icon) update_channel_icon(undefined); return e; }); } else { /* TODO Remove this because its totally useless. Remove this from the UI as well */ log.info(LogCategory.PERMISSIONS, tr("Removing channel grant permission %s. permission.id: %o"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("channeldelperm", { cid: current_channel.channelId, permid: permission.id_grant(), }); } } else { /* add the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Adding or updating channel permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}"), permission.name, permission.id, value.value, value.flag_skip, value.flag_negate ); await connection.serverConnection.send_command("channeladdperm", { cid: current_channel.channelId, permid: permission.id, permvalue: value.value, permskip: value.flag_skip, permnegated: value.flag_negate }).then(e => { if(permission.name === "i_icon_id" && update_channel_icon) update_channel_icon(value.value); return e; }); } else { /* TODO Remove this because its totally useless. Remove this from the UI as well */ log.info(LogCategory.PERMISSIONS, tr("Adding or updating channel grant permission %s. permission.{id: %o, value: %o}"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("channeladdperm", { cid: current_channel.channelId, permid: permission.id_grant(), permvalue: value.granted, permskip: false, permnegated: false }); } } }); /* FIXME: Use cached permissions */ editor.trigger_update(); }); } let channel_list = tab_left.find(".list-channel .entries"); build_channel_tree(connection, channel_list, 0, (channel, update) => { current_channel = channel; update_channel_icon = update; editor.trigger_update(); }); } function apply_channel_groups(connection: ConnectionHandler, editor: AbstractPermissionEditor, tab_left: JQuery, tab_right: JQuery) { let current_group; let update_group_icon: (id: number) => any; let update_groups: (selected_group: number) => any; let update_buttons: () => any; /* the editor */ { const pe_server = tab_right.find(".permission-editor"); tab_right.on('show', event => { editor.set_toggle_button(undefined, undefined); pe_server.append(editor.html_tag()); if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_PERMISSION_LIST).granted(1)) editor.set_mode(PermissionEditorMode.VISIBLE); else { editor.set_mode(PermissionEditorMode.NO_PERMISSION); return; } editor.set_listener_update(() => { if(!current_group) return; connection.groups.request_permissions(current_group).then(result => editor.set_permissions(result)).catch(error => { console.log(error); //TODO handling? }); }); editor.set_listener(async (permission, value) => { if (!current_group) throw "unset channel group"; if (value.remove) { /* remove the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Removing channel group permission %s. permission.id: %o"), permission.name, permission.id, ); await connection.serverConnection.send_command("channelgroupdelperm", { cgid: current_group.id, permid: permission.id, }).then(e => { if(permission.name === "i_icon_id" && update_group_icon) update_group_icon(undefined); return e; }); } else { log.info(LogCategory.PERMISSIONS, tr("Removing channel group grant permission %s. permission.id: %o"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("channelgroupdelperm", { cgid: current_group.id, permid: permission.id_grant(), }); } } else { /* add the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Adding or updating channel group permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}"), permission.name, permission.id, value.value, value.flag_skip, value.flag_negate ); await connection.serverConnection.send_command("channelgroupaddperm", { cgid: current_group.id, permid: permission.id, permvalue: value.value, permskip: value.flag_skip, permnegated: value.flag_negate }).then(e => { if(permission.name === "i_icon_id" && update_group_icon) update_group_icon(value.value); return e; }); } else { log.info(LogCategory.PERMISSIONS, tr("Adding or updating channel group grant permission %s. permission.{id: %o, value: %o}"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("channelgroupaddperm", { cgid: current_group.id, permid: permission.id_grant(), permvalue: value.granted, permskip: false, permnegated: false }); } } }); /* FIXME: Use cached permissions */ editor.trigger_update(); }); } /* list all channel groups */ { let group_list = tab_left.find(".list-groups .entries"); update_groups = (selected_group: number) => { group_list.children().remove(); const allow_query_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_QUERYGROUP).granted(1); const allow_template_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_TEMPLATES).granted(1); for (let group of connection.groups.channelGroups.sort(GroupManager.sorter())) { if (group.type == GroupType.QUERY) { if (!allow_query_groups) continue; } else if (group.type == GroupType.TEMPLATE) { if (!allow_template_groups) continue; } let tag = $.spawn("div").addClass("group").attr("group-id", group.id); let icon_tag = connection.fileManager.icons.generateTag(group.properties.iconid); icon_tag.appendTo(tag); const _update_icon = icon_id => icon_tag.replaceWith(icon_tag = connection.fileManager.icons.generateTag(icon_id)); { let name = $.spawn("a").text(group.name + " (" + group.id + ")").addClass("name"); if (group.properties.savedb) name.addClass("savedb"); if (connection.channelTree.server.properties.virtualserver_default_channel_group == group.id) name.addClass("default"); name.appendTo(tag); } tag.appendTo(group_list); tag.on('click', event => { current_group = group; update_group_icon = _update_icon; group_list.find(".selected").removeClass("selected"); tag.addClass("selected"); update_buttons(); //TODO trigger only if the editor is in channel group mode! editor.trigger_update(); }); tag.on('contextmenu', event => { if(event.isDefaultPrevented()) return; contextmenu.spawn_context_menu(event.pageX, event.pageY, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Create a channel group"), icon_class: 'client-add', invalidPermission: !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_CREATE).granted(1), callback: () => tab_left.find(".button-add").trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Rename channel group"), icon_class: 'client-edit', invalidPermission: !connection.permissions.neededPermission(PermissionType.I_CHANNEL_GROUP_MODIFY_POWER).granted(current_group.requiredModifyPower), callback: () => tab_left.find(".button-rename").trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Duplicate channel group"), icon_class: 'client-copy', callback: () => tab_left.find(".button-duplicate").trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Delete channel group"), icon_class: 'client-delete', invalidPermission: !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_DELETE).granted(1), callback: () => tab_left.find(".button-delete").trigger('click') }); event.preventDefault(); }); if(group.id === selected_group) { setTimeout(() => tag.trigger('click'), 0); selected_group = undefined; } } /* because the server menu is the first which will be shown */ if(typeof(selected_group) !== "undefined") { setTimeout(() => group_list.find('.group').first().trigger('click'), 0); } }; tab_left.find(".list-groups").on('contextmenu', event => { if(event.isDefaultPrevented()) return; contextmenu.spawn_context_menu(event.pageX, event.pageY, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Create a channel group"), icon_class: 'client-add', callback: () => tab_left.find(".button-add").trigger('click') }); event.preventDefault(); }); } { const container_buttons = tab_left.find(".container-buttons"); const button_add = container_buttons.find(".button-add"); const button_rename = container_buttons.find(".button-rename"); const button_duplicate = container_buttons.find(".button-duplicate"); const button_delete = container_buttons.find(".button-delete"); button_add.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_CREATE).granted(1)); button_delete.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_CREATE).granted(1)); update_buttons = () => { const permission_modify = current_group && connection.permissions.neededPermission(PermissionType.I_CHANNEL_GROUP_MODIFY_POWER).granted(current_group.requiredModifyPower); button_rename.prop("disabled", !permission_modify); button_duplicate.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_CHANNELGROUP_CREATE).granted(1)); }; button_add.on('click', () => { spawnGroupAdd(false, connection.permissions, (name, type) => name.length > 0 && !connection.groups.channelGroups.find(e => e.target == GroupTarget.CHANNEL && e.name.toLowerCase() === name.toLowerCase() && e.type == type) , (name, type) => { console.log("Creating channel group: %o, %o", name, type); connection.serverConnection.send_command('channelgroupadd', { name: name, type: type }).then(() => { createInfoModal(tr("Group created"), tr("The channel group has been created.")).open(); update_groups(0); //TODO: May get the created group? }).catch(error => { console.warn(tr("Failed to create channel group: %o"), error); if(error instanceof CommandResult) { error = error.extra_message || error.message; } createErrorModal(tr("Failed to create group"), formatMessage(tr("Failed to create group:{:br:}"), error)).open(); }); }); }); button_rename.on('click', () => { if(!current_group) return; createInputModal(tr("Rename group"), tr("Enter the new group name"), name => name.length > 0 && !connection.groups.channelGroups.find(e => e.target == GroupTarget.CHANNEL && e.name.toLowerCase() === name.toLowerCase() && e.type == current_group.type), result => { if(typeof(result) !== "string" || !result) return; connection.serverConnection.send_command('channelgrouprename', { cgid: current_group.id, name: result }).then(() => { createInfoModal(tr("Group renamed"), tr("The channel group has been renamed.")).open(); update_groups(current_group.id); }).catch(error => { console.warn(tr("Failed to rename channel group: %o"), error); if(error instanceof CommandResult) { error = error.extra_message || error.message; } createErrorModal(tr("Failed to rename group"), formatMessage(tr("Failed to rename group:{:br:}"), error)).open(); }); }).open(); }); button_duplicate.on('click', () => { createErrorModal(tr("Not implemented yet"), tr("This function hasn't been implemented yet!")).open(); }); button_delete.on('click', () => { if(!current_group) return; spawnYesNo(tr("Are you sure?"), formatMessage(tr("Do you really want to delete the group {}?"), current_group.name), result => { if(result !== true) return; connection.serverConnection.send_command("channelgroupdel", { cgid: current_group.id, force: true }).then(() => { createInfoModal(tr("Group deleted"), tr("The channel group has been deleted.")).open(); update_groups(0); }).catch(error => { console.warn(tr("Failed to delete channel group: %o"), error); if(error instanceof CommandResult) { error = error.extra_message || error.message; } createErrorModal(tr("Failed to delete group"), formatMessage(tr("Failed to delete group:{:br:}"), error)).open(); }); }); }); } update_groups(0); } function apply_server_groups(connection: ConnectionHandler, editor: AbstractPermissionEditor, tab_left: JQuery, tab_right: JQuery) { let current_group: Group; let current_group_changed: (() => any)[] = []; let update_buttons: () => any; /* list all groups */ let update_icon: ((icon_id: number) => any)[] = []; let update_groups: (selected_group: number) => any; { let group_list = tab_left.find(".container-group-list .list-groups .entries"); let group_list_update_icon: (i: number) => any; update_icon.push(i => group_list_update_icon(i)); update_groups = (selected_group: number) => { group_list.children().remove(); const allow_query_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_QUERYGROUP).granted(1); const allow_template_groups = connection.permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_TEMPLATES).granted(1); for(const group of connection.groups.serverGroups.sort(GroupManager.sorter())) { if(group.type == GroupType.QUERY) { if(!allow_query_groups) continue; } else if(group.type == GroupType.TEMPLATE) { if(!allow_template_groups) continue; } let tag = $.spawn("div").addClass("group").attr("group-id", group.id); let icon_tag = connection.fileManager.icons.generateTag(group.properties.iconid); icon_tag.appendTo(tag); const _update_icon = icon_id => icon_tag.replaceWith(icon_tag = connection.fileManager.icons.generateTag(icon_id)); { let name = $.spawn("div").text(group.name + " (" + group.id + ")").addClass("name"); if(group.properties.savedb) name.addClass("savedb"); if(connection.channelTree.server.properties.virtualserver_default_server_group == group.id) name.addClass("default"); name.appendTo(tag); } tag.appendTo(group_list); tag.on('click', event => { if(current_group === group) return; current_group = group; group_list_update_icon = _update_icon; if(update_buttons) update_buttons(); for(const entry of current_group_changed) entry(); group_list.find(".selected").removeClass("selected"); tag.addClass("selected"); editor.trigger_update(); }); tag.on('contextmenu', event => { if(event.isDefaultPrevented()) return; contextmenu.spawn_context_menu(event.pageX, event.pageY, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Create a server group"), icon_class: 'client-add', invalidPermission: !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_CREATE).granted(1), callback: () => tab_left.find(".button-add").trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Rename server group"), icon_class: 'client-edit', invalidPermission: !connection.permissions.neededPermission(PermissionType.I_SERVER_GROUP_MODIFY_POWER).granted(current_group.requiredModifyPower), callback: () => tab_left.find(".button-rename").trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Duplicate server group"), icon_class: 'client-copy', callback: () => tab_left.find(".button-duplicate").trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Delete server group"), icon_class: 'client-delete', invalidPermission: !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_DELETE).granted(1), callback: () => tab_left.find(".button-delete").trigger('click') }); event.preventDefault(); }); if(group.id === selected_group) { setTimeout(() => tag.trigger('click'), 0); selected_group = undefined; } } /* because the server menu is the first which will be shown */ if(typeof(selected_group) !== "undefined") { setTimeout(() => group_list.find('.group').first().trigger('click'), 0); } }; tab_left.find(".list-groups").on('contextmenu', event => { if(event.isDefaultPrevented()) return; contextmenu.spawn_context_menu(event.pageX, event.pageY, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Create a server group"), icon_class: 'client-add', callback: () => tab_left.find(".button-add").trigger('click') }); event.preventDefault(); }); } { const container_buttons = tab_left.find(".container-group-list .container-buttons"); const button_add = container_buttons.find(".button-add"); const button_rename = container_buttons.find(".button-rename"); const button_duplicate = container_buttons.find(".button-duplicate"); const button_delete = container_buttons.find(".button-delete"); button_add.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_CREATE).granted(1)); button_delete.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_DELETE).granted(1)); update_buttons = () => { const permission_modify = current_group && connection.permissions.neededPermission(PermissionType.I_SERVER_GROUP_MODIFY_POWER).granted(current_group.requiredModifyPower); button_rename.prop("disabled", !permission_modify); button_duplicate.prop("disabled", !connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_CREATE).granted(1)); }; button_add.on('click', () => { spawnGroupAdd(true, connection.permissions, (name, type) => name.length > 0 && !connection.groups.serverGroups.find(e => e.target == GroupTarget.SERVER && e.name.toLowerCase() === name.toLowerCase() && e.type == type) , (name, type) => { console.log("Creating group: %o, %o", name, type); connection.serverConnection.send_command('servergroupadd', { name: name, type: type }).then(() => { createInfoModal(tr("Group created"), tr("The server group has been created.")).open(); update_groups(0); //TODO: May get the created group? }).catch(error => { console.warn(tr("Failed to create server group: %o"), error); if(error instanceof CommandResult) { error = error.extra_message || error.message; } createErrorModal(tr("Failed to create group"), formatMessage(tr("Failed to create group:{:br:}"), error)).open(); }); }); }); button_rename.on('click', () => { if(!current_group) return; createInputModal(tr("Rename group"), tr("Enter the new group name"), name => name.length > 0 && !connection.groups.serverGroups.find(e => e.target == GroupTarget.SERVER && e.name.toLowerCase() === name.toLowerCase() && e.type == current_group.type), result => { if(typeof(result) !== "string" || !result) return; connection.serverConnection.send_command('servergrouprename', { sgid: current_group.id, name: result }).then(() => { createInfoModal(tr("Group renamed"), tr("The server group has been renamed.")).open(); update_groups(current_group.id); }).catch(error => { console.warn(tr("Failed to rename server group: %o"), error); if(error instanceof CommandResult) { error = error.extra_message || error.message; } createErrorModal(tr("Failed to rename group"), formatMessage(tr("Failed to rename group:{:br:}"), error)).open(); }); }).open(); }); button_duplicate.on('click', () => { createErrorModal(tr("Not implemented yet"), tr("This function hasn't been implemented yet!")).open(); }); button_delete.on('click', () => { if(!current_group) return; spawnYesNo(tr("Are you sure?"), formatMessage(tr("Do you really want to delete the group {}?"), current_group.name), result => { if(result !== true) return; connection.serverConnection.send_command("servergroupdel", { sgid: current_group.id, force: true }).then(() => { createInfoModal(tr("Group deleted"), tr("The server group has been deleted.")).open(); update_groups(0); }).catch(error => { console.warn(tr("Failed to delete server group: %o"), error); if(error instanceof CommandResult) { error = error.extra_message || error.message; } createErrorModal(tr("Failed to delete group"), formatMessage(tr("Failed to delete group:{:br:}"), error)).open(); }); }); }); } update_groups(0); /* the editor */ { const pe_server = tab_right.find(".permission-editor"); tab_right.on('show', event => { pe_server.append(editor.html_tag()); if(connection.permissions.neededPermission(PermissionType.B_VIRTUALSERVER_SERVERGROUP_PERMISSION_LIST).granted(1)) editor.set_mode(PermissionEditorMode.VISIBLE); else { editor.set_mode(PermissionEditorMode.NO_PERMISSION); return; } editor.set_listener_update(() => { console.log("Updating permissions"); connection.groups.request_permissions(current_group).then(result => editor.set_permissions(result)).catch(error => { console.log(error); //TODO handling? }); }); editor.set_listener(async (permission, value) => { if (!current_group) throw "unset server group"; if (value.remove) { /* remove the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Removing server group permission %s. permission.id: %o"), permission.name, permission.id, ); await connection.serverConnection.send_command("servergroupdelperm", { sgid: current_group.id, permid: permission.id, }).then(e => { if(permission.name === "i_icon_id") for(const c of update_icon) c(0); return e; }); } else { log.info(LogCategory.PERMISSIONS, tr("Removing server group grant permission %s. permission.id: %o"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("servergroupdelperm", { sgid: current_group.id, permid: permission.id_grant(), }); } } else { /* add the permission */ if (typeof (value.value) !== "undefined") { log.info(LogCategory.PERMISSIONS, tr("Adding or updating server group permission %s. permission.{id: %o, value: %o, flag_skip: %o, flag_negate: %o}"), permission.name, permission.id, value.value, value.flag_skip, value.flag_negate ); await connection.serverConnection.send_command("servergroupaddperm", { sgid: current_group.id, permid: permission.id, permvalue: value.value, permskip: value.flag_skip, permnegated: value.flag_negate }).then(e => { if(permission.name === "i_icon_id") for(const c of update_icon) c(value.value); return e; }); } else { log.info(LogCategory.PERMISSIONS, tr("Adding or updating server group grant permission %s. permission.{id: %o, value: %o}"), permission.name, permission.id_grant(), value.granted, ); await connection.serverConnection.send_command("servergroupaddperm", { sgid: current_group.id, permid: permission.id_grant(), permvalue: value.granted, permskip: false, permnegated: false }); } } }); editor.trigger_update(); }); } /* client list */ { //container-client-list container-group-list let clients_visible = false; let selected_client: { tag: JQuery, dbid: number }; const container_client_list = tab_left.find(".container-client-list").addClass("hidden"); const container_group_list = tab_left.find(".container-group-list"); const container_selected_group = container_client_list.find(".container-current-group"); const container_clients = container_client_list.find(".list-clients .entries"); const input_filter = container_client_list.find(".filter-client-list"); const button_add = container_client_list.find(".button-add"); const button_delete = container_client_list.find(".button-delete"); const update_filter = () => { const filter_text = (input_filter.val() || "").toString().toLowerCase(); if(!filter_text) { container_clients.find(".entry").css('display', 'block'); } else { const entries = container_clients.find(".entry"); for(const _entry of entries) { const entry = $(_entry); if(entry.attr("search-string").toLowerCase().indexOf(filter_text) !== -1) entry.css('display', 'block'); else entry.css('display', 'none'); } } }; const update_client_list = () => { container_clients.empty(); button_delete.prop('disabled', true); connection.serverConnection.command_helper.request_clients_by_server_group(current_group.id).then(clients => { for(const client of clients) { const tag = $.spawn("div").addClass("client").text(client.client_nickname); tag.attr("search-string", client.client_nickname + "-" + client.client_unique_identifier + "-" + client.client_database_id); container_clients.append(tag); tag.on('click contextmenu', event => { container_clients.find(".selected").removeClass("selected"); tag.addClass("selected"); selected_client = { tag: tag, dbid: client.client_database_id }; button_delete.prop('disabled', false); }); tag.on('contextmenu', event => { if(event.isDefaultPrevented()) return; event.preventDefault(); contextmenu.spawn_context_menu(event.pageX, event.pageY, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Add client"), icon_class: 'client-add', callback: () => button_add.trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Remove client"), icon_class: 'client-delete', callback: () => button_delete.trigger('click') }, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Copy unique id"), icon_class: 'client-copy', callback: () => copy_to_clipboard(client.client_unique_identifier) }) }); } update_filter(); }).catch(error => { if(error instanceof CommandResult && error.id === ErrorID.PERMISSION_ERROR) return; console.warn(tr("Failed to receive server group clients for group %d: %o"), current_group.id, error); }); }; current_group_changed.push(update_client_list); button_delete.on('click', event => { const client = selected_client; if(!client) return; connection.serverConnection.send_command("servergroupdelclient", { sgid: current_group.id, cldbid: client.dbid }).then(() => { selected_client.tag.detach(); button_delete.prop('disabled', true); /* nothing is selected */ }).catch(error => { console.log(tr("Failed to delete client %o from server group %o: %o"), client.dbid, current_group.id, error); if(error instanceof CommandResult) error = error.extra_message || error.message; createErrorModal(tr("Failed to remove client"), tr("Failed to remove client from server group")).open(); }); }); button_add.on('click', event => { createInputModal(tr("Add client to server group"), tr("Enter the client unique id or database id"), text => { if(!text) return false; if(!!text.match(/^[0-9]+$/)) return true; try { return atob(text).length >= 20; } catch(error) { return false; } }, async text => { if(typeof(text) !== "string") return; let dbid; if(!!text.match(/^[0-9]+$/)) { dbid = parseInt(text); debugger; } else { try { const data = await connection.serverConnection.command_helper.info_from_uid(text.trim()); dbid = data[0].client_database_id; } catch(error) { console.log(tr("Failed to resolve client database id from unique id (%s): %o"), text, error); if(error instanceof CommandResult) error = error.extra_message || error.message; createErrorModal(tr("Failed to add client"), formatMessage(tr("Failed to add client to server group\nFailed to resolve database id: {}."), error)).open(); return; } } if(!dbid) { console.log(tr("Failed to resolve client database id from unique id (%s): Client not found")); createErrorModal(tr("Failed to add client"), tr("Failed to add client to server group\nClient database id not found")).open(); return; } connection.serverConnection.send_command("servergroupaddclient", { sgid: current_group.id, cldbid: dbid }).then(() => { update_client_list(); }).catch(error => { console.log(tr("Failed to add client %o to server group %o: %o"), dbid, current_group.id, error); if(error instanceof CommandResult) error = error.extra_message || error.message; createErrorModal(tr("Failed to add client"), tra("Failed to add client to server group{:br:}", error)).open(); }); }).open(); }); container_client_list.on('contextmenu', event => { if(event.isDefaultPrevented()) return; event.preventDefault(); contextmenu.spawn_context_menu(event.pageX, event.pageY, { type: contextmenu.MenuEntryType.ENTRY, name: tr("Add client"), icon_class: 'client-add', callback: () => button_add.trigger('click') }) }); /* icon handler and current group display */ { let update_icon_callback: (i: number) => any; update_icon.push(i => update_icon_callback(i)); input_filter.on('change keyup', event => update_filter()); current_group_changed.push(() => { container_selected_group.empty(); if(!current_group) return; let icon_container = $.spawn("div").addClass("icon-container").appendTo(container_selected_group); connection.fileManager.icons.generateTag(current_group.properties.iconid).appendTo(icon_container); update_icon_callback = icon => { icon_container.empty(); connection.fileManager.icons.generateTag(icon).appendTo(icon_container); }; $.spawn("div").addClass("name").text(current_group.name + " (" + current_group.id + ")").appendTo(container_selected_group); }); } tab_right.on('show', event => { editor.set_toggle_button(() => { clients_visible = !clients_visible; container_client_list.toggleClass("hidden", !clients_visible); container_group_list.toggleClass("hidden", clients_visible); return clients_visible ? tr("Hide clients in group") : tr("Show clients in group"); }, clients_visible ? tr("Hide clients in group") : tr("Show clients in group")); }); } } function spawnGroupAdd(server_group: boolean, permissions: PermissionManager, valid_name: (name: string, group_type: number) => boolean, callback: (group_name: string, group_type: number) => any) { let modal: Modal; modal = createModal({ header: tr("Create a new group"), body: () => { let tag = $("#tmpl_group_add").renderTag({ server_group: server_group }); tag.find(".group-type-template").prop("disabled", !permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_TEMPLATES).granted(1)); tag.find(".group-type-query").prop("disabled", !permissions.neededPermission(PermissionType.B_SERVERINSTANCE_MODIFY_QUERYGROUP).granted(1)); const container_name = tag.find(".group-name"); const button_create = tag.find(".button-create"); const group_type = () => (tag.find(".group-type")[0] as HTMLSelectElement).selectedIndex; container_name.on('keyup change', (event: Event) => { if(event.type === 'keyup') { const kevent = event as KeyboardEvent; if(!kevent.shiftKey && kevent.key == 'Enter') { button_create.trigger('click'); return; } } const valid = valid_name(container_name.val() as string, group_type()); button_create.prop("disabled", !valid); container_name.parent().toggleClass("is-invalid", !valid); }).trigger('change'); tag.find(".group-type").on('change', () => container_name.trigger('change')); button_create.on('click', event => { if(button_create.prop("disabled")) return; button_create.prop("disabled", true); /* disable double clicking */ modal.close(); callback(container_name.val() as string, group_type()); }); return tag; }, footer: null, width: 600 }); modal.htmlTag.find(".modal-body").addClass("modal-group-add"); modal.open_listener.push(() => { modal.htmlTag.find(".group-name").focus(); }); modal.open(); }