Got some trouble while loading important files!
diff --git a/package.json b/package.json
index cd8d64c9..2eb108e6 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,9 @@
"sass": "^1.14.1",
"typescript": "^3.1.1"
},
- "dependencies": {},
+ "dependencies": {
+ "@types/moment": "^2.13.0"
+ },
"repository": {
"type": "git",
"url": "git+https://github.com/TeaSpeak/TeaWeb/TeaWeb.git"
diff --git a/public_files.sh b/public_files.sh
index 383d513e..ff4ea1b4 100755
--- a/public_files.sh
+++ b/public_files.sh
@@ -1,6 +1,9 @@
#!/usr/bin/env bash
-tsc -p tsconfig/tsconfig_release.json
+npm run compile-sass
+npm run build-worker
+npm run build-web-app-release
+npm run build-web-preload
#uglifyjs -c --source-map --verbose -o generated/js/client.min.js generated/js/client.js
-mv generated/js/client.js generated/js/client.min.js
+cp generated/js/client.js generated/js/client.min.js
diff --git a/shared/css/helptag.scss b/shared/css/helptag.scss
new file mode 100644
index 00000000..6ec8d72e
--- /dev/null
+++ b/shared/css/helptag.scss
@@ -0,0 +1,125 @@
+.help-tip {
+ z-index: 100;
+
+ display: inline-block;
+ position: relative;
+ text-align: center;
+ background-color: #BCDBEA;
+ border-radius: 50%;
+
+ width: 24px;
+ height: 24px;
+
+ font-size: 14px;
+ line-height: 26px;
+
+ cursor: default;
+
+ &:before {
+ content:'?';
+ font-weight: bold;
+ color:#fff;
+ }
+
+ &:hover {
+ p {
+ display:block;
+ transform-origin: 100% 0%;
+
+ -webkit-animation: fadeIn 0.3s ease-in-out;
+ animation: fadeIn 0.3s ease-in-out;
+ }
+ }
+
+ p {
+ display: none;
+ text-align: left;
+ background-color: #1E2021;
+ padding: 20px;
+
+ width: 400px; /* fallback */
+ width: max-content;
+
+ position: absolute;
+ border-radius: 3px;
+ box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2);
+ color: #FFF;
+ font-size: 13px;
+ line-height: 1.4;
+
+
+ &:before {
+ position: absolute;
+ content: '';
+ width:0;
+ height: 0;
+ border:6px solid transparent;
+ border-bottom-color:#1E2021;
+ top:-12px;
+ }
+
+ &:after {
+ width:100%;
+ height:40px;
+ content:'';
+ position: absolute;
+ top:-40px;
+ left:0;
+ }
+ }
+
+ &.tip-left {
+ p {
+ right: -4px;
+
+ &:before {
+ right: 10px;
+ }
+ }
+ }
+ &.tip-right {
+ p {
+ left: -4px;
+
+ &:before {
+ left: 10px;
+ }
+ }
+ }
+
+ &.tip-center {
+ p {
+ left: 50%;
+ transform: translate(-50%, 0);
+
+ &:before {
+ right: calc(50% - 5px);
+ }
+ }
+ }
+
+ &.tip-small {
+ width: 16px;
+ height: 16px;
+
+ font-size: 12px;
+ line-height: 18px;
+ }
+}
+
+@-webkit-keyframes fadeIn {
+ 0% {
+ opacity:0;
+ transform: scale(0.6);
+ }
+
+ 100% {
+ opacity:100%;
+ transform: scale(1);
+ }
+}
+
+@keyframes fadeIn {
+ 0% { opacity:0; }
+ 100% { opacity:100%; }
+}
\ No newline at end of file
diff --git a/shared/css/modal-bancreate.scss b/shared/css/modal-bancreate.scss
new file mode 100644
index 00000000..c6f67565
--- /dev/null
+++ b/shared/css/modal-bancreate.scss
@@ -0,0 +1,73 @@
+.bancreate {
+ display: flex;
+ flex-direction: column;
+
+ .frame-container {
+ display: flex;
+ flex-direction: column;
+
+ .container {
+ display: block;
+ flex-shrink: 0;
+ flex-grow: 0;
+
+ a {
+ display: block;
+ }
+
+ input, textarea {
+ width: 100%;
+ }
+
+ textarea {
+ resize: vertical;
+ max-height: 100%;
+ overflow-y: auto;
+ }
+
+ &:not(:first-of-type) {
+ margin-top: 5px;
+ }
+
+ &.container-reason {
+ max-height: 500px;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .container-name-type {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-top: 2px;
+
+ * {
+ display: inline-block;
+ }
+ }
+
+ .container-time-input {
+ display: flex;
+ flex-direction: row;
+ justify-content: stretch;
+ }
+ }
+
+ .footer {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+
+ .container-global {
+ display: inline-block;
+
+ .input-global {
+ vertical-align: bottom;
+ }
+ }
+ .container-buttons {
+ display: inline-block;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/css/modal-banlist.scss b/shared/css/modal-banlist.scss
new file mode 100644
index 00000000..70e70c4e
--- /dev/null
+++ b/shared/css/modal-banlist.scss
@@ -0,0 +1,164 @@
+.banlist {
+ display: flex;
+ flex-direction: column;
+ justify-content: stretch;
+ flex-grow: 1;
+
+ height: 100%;
+ width: 100%;
+
+ .frame-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: stretch;
+ flex-grow: 1;
+
+ .top-menu {
+ display: flex;
+ flex-direction: row;
+ justify-content: stretch;
+ flex-grow: 0;
+ flex-shrink: 0;
+ margin-bottom: 5px;
+
+ .manage-buttons {
+ flex-grow: 0;
+ flex-shrink: 0;
+
+ margin-right: 5px;
+ }
+
+ .search {
+ flex-grow: 1;
+
+ input {
+ width: 100%;
+ }
+ }
+ }
+
+ .entry-container {
+ display: inline-flex;
+ flex-grow: 1;
+ margin-bottom: 5px;
+ position: relative;
+
+ width: 100%;
+ height: 100%;
+
+ .table-container {
+ width: 100%;
+ }
+
+ .ban-entry.selected {
+ background: blue;
+ }
+
+ .ban-entry-global {
+ color: red;
+ }
+
+ .ban-entry {
+ .field-properties {
+ a {
+ display: block;
+ }
+ }
+ .field-reason {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ }
+
+ &.ban-entry-own-bold {
+ font-weight: bold;
+ }
+ }
+
+ table {
+ border: grey solid 1px;
+
+ display: block;
+ width: 100%;
+ height: 100%;
+ text-align: left;
+ min-width: 610px;
+
+ tr {
+ td, th {
+ text-align: center;
+ vertical-align: top;
+
+ &:nth-child(1) {
+ width: 25%;
+ text-align: left;
+ }
+ &:nth-child(2) {
+ width: 25%;
+ float: left;
+ }
+ &:nth-child(3) {
+ width: 25%;
+ float: left;
+ }
+ &:nth-child(4) {
+ width: 25%;
+ float: left;
+ }
+ }
+ }
+
+ th {
+ border: grey solid;
+ border-width: 0 0 1px 1px;
+
+ &:first-of-type {
+ border-width: 0 0 1px 0;
+ }
+ }
+
+ tbody {
+ height: calc(100% - 22px);
+ overflow-y: auto;
+ overflow-x: hidden;
+ }
+ th, td, thead,tbody {
+ display: block;
+ }
+ tr {
+ display: inline-block;
+ width: 100%;
+
+ &:nth-of-type(even) {
+ background-color: lightgray;
+ }
+ }
+ td,th {
+ float: left;
+ }
+ }
+ }
+
+ .bottom-menu {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ flex-grow: 0;
+ flex-shrink: 0;
+ margin-bottom: 5px;
+
+ .left {
+ display: flex;
+ flex-direction: row;
+
+ div {
+ margin-left: 2px;
+ margin-right: 2px;
+ }
+ }
+
+ input {
+ vertical-align: bottom;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/shared/css/modals.scss b/shared/css/modals.scss
index 662e4cb1..6ca2a0b4 100644
--- a/shared/css/modals.scss
+++ b/shared/css/modals.scss
@@ -87,10 +87,6 @@
}
}
-.container {
- margin: 2px;
-}
-
/* Channel edit/create modal */
.settings_audio {
display: grid;
@@ -337,6 +333,10 @@
}
}
+.container-ban-type {
+ margin: 5px;
+}
+
.arrow {
display: inline-block;
border: solid black;
diff --git a/shared/js/Identity.ts b/shared/js/Identity.ts
index db46c3fe..0219b9dc 100644
--- a/shared/js/Identity.ts
+++ b/shared/js/Identity.ts
@@ -20,7 +20,7 @@ namespace TSIdentityHelper {
let functionDestroyString: any;
let functionDestroyIdentity: any;
- export function setup() : boolean {
+ export function setup() : boolean {
functionDestroyString = Module.cwrap("destroy_string", "pointer", []);
functionLastError = Module.cwrap("last_error_message", null, ["string"]);
funcationParseIdentity = Module.cwrap("parse_identity", "pointer", ["string"]);
@@ -43,10 +43,10 @@ namespace TSIdentityHelper {
export function unwarpString(str) : string {
if(str == "") return "";
try {
- if(!$.isFunction(window.Pointer_stringify) || !$.isFunction(Pointer_stringify)) {
+ if(!$.isFunction(window.Pointer_stringify)) {
displayCriticalError("Missing required wasm function!
Please reload the page!");
}
- let message: string = Pointer_stringify(str);
+ let message: string = window.Pointer_stringify(str);
functionDestroyString(str);
return message;
} catch (error) {
diff --git a/shared/js/connection.ts b/shared/js/connection.ts
index eb8f1da7..6799aeb1 100644
--- a/shared/js/connection.ts
+++ b/shared/js/connection.ts
@@ -215,7 +215,7 @@ class ServerConnection {
"type": "command",
"command": command,
"data": _data,
- "flags": flags
+ "flags": flags.filter(entry => entry.length != 0)
}));
});
return new Promise
((resolve, failed) => {
@@ -225,7 +225,8 @@ class ServerConnection {
let res = ex;
if(!res.success) {
if(res.id == 2568) { //Permission error
- chat.serverChat().appendError("insufficient client permissions. Failed on permission {}", this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
+ res.message = "Insufficient client permissions. Failed on permission " + this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name;
+ chat.serverChat().appendError("Insufficient client permissions. Failed on permission {}", this._client.permissions.resolveInfo(res.json["failed_permid"] as number).name);
} else {
chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
}
diff --git a/shared/js/load.ts b/shared/js/load.ts
index 3ca032d8..4bc4b3c3 100644
--- a/shared/js/load.ts
+++ b/shared/js/load.ts
@@ -136,6 +136,8 @@ function loadDebug() {
"js/ui/modal/ModalConnect.js",
"js/ui/modal/ModalChangeVolume.js",
"js/ui/modal/ModalBanClient.js",
+ "js/ui/modal/ModalBanCreate.js",
+ "js/ui/modal/ModalBanList.js",
"js/ui/modal/ModalYesNo.js",
"js/ui/modal/ModalPermissionEdit.js",
"js/ui/modal/ModalServerGroupDialog.js",
@@ -224,6 +226,10 @@ function loadTemplates() {
tags = node.children;
let root = document.getElementById("templates");
+ if(!root) {
+ displayCriticalError("Failed to find template tag!");
+ return;
+ }
while(tags.length > 0){
let tag = tags.item(0);
if(tag.id == "tmpl_main") {
@@ -283,6 +289,7 @@ function loadSide() {
"vendor/jsrender/jsrender.min.js"
])).then(() => load_wait_scripts([
["vendor/bbcode/xbbcode.js"],
+ ["vendor/moment/moment.js"],
["https://webrtc.github.io/adapter/adapter-latest.js"]
])).then(() => {
//Load the teaweb scripts
diff --git a/shared/js/main.ts b/shared/js/main.ts
index 8c622a7d..731e3dc8 100644
--- a/shared/js/main.ts
+++ b/shared/js/main.ts
@@ -4,8 +4,10 @@
///
///
///
+///
///
///
+///
///
///
///
@@ -49,6 +51,7 @@ function setup_close() {
};
}
+declare function moment(...arguments) : any;
function setup_jsrender() : boolean {
if(!js_render) {
displayCriticalError("Missing jsrender extension!");
@@ -66,6 +69,10 @@ function setup_jsrender() : boolean {
return (Math.round(Math.random() * (min + max + 1) - min)).toString();
});
+ js_render.views.tags("fmt_date", (...arguments) => {
+ return moment(arguments[0]).format(arguments[1]);
+ });
+
$(".jsrender-template").each((idx, _entry) => {
if(!js_render.templates(_entry.id, _entry.innerHTML)) { //, _entry.innerHTML
console.error("Failed to cache template " + _entry.id + " for js render!");
@@ -138,8 +145,6 @@ function main() {
})
*/
- //Modals.spawnPermissionEdit();
-
setup_close();
$(window).on('resize', () => {
globalClient.channelTree.handle_resized();
@@ -154,7 +159,7 @@ app.loadedListener.push(() => {
$(document).one('click', event => AudioController.initializeFromGesture());
}
} catch (ex) {
- if(ex instanceof ReferenceError)
+ if(ex instanceof ReferenceError || ex instanceof TypeError)
ex = ex.message + ":
" + ex.stack;
displayCriticalError("Failed to invoke main function:
" + ex);
}
diff --git a/shared/js/ui/client.ts b/shared/js/ui/client.ts
index c0b0cfbf..2a63c448 100644
--- a/shared/js/ui/client.ts
+++ b/shared/js/ui/client.ts
@@ -307,12 +307,12 @@ class ClientEntry {
name: "Ban client",
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => {
- Modals.spawnBanClient(this.properties.client_nickname, (duration, reason) => {
+ Modals.spawnBanClient(this.properties.client_nickname, (data) => {
this.channelTree.client.serverConnection.sendCommand("banclient", {
uid: this.properties.client_unique_identifier,
- banreason: reason,
- time: duration
- });
+ banreason: data.reason,
+ time: data.length
+ }, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]);
});
}
},
diff --git a/shared/js/ui/frames/ControlBar.ts b/shared/js/ui/frames/ControlBar.ts
index f515b509..d1f5e61e 100644
--- a/shared/js/ui/frames/ControlBar.ts
+++ b/shared/js/ui/frames/ControlBar.ts
@@ -1,5 +1,6 @@
///
///
+///
/*
client_output_hardware Value: '1'
client_output_muted Value: '0'
@@ -11,6 +12,8 @@
client_away Value: '0'
client_away_message Value: ''
*/
+import openBanList = Modals.openBanList;
+
class ControlBar {
private _muteInput: boolean;
private _muteOutput: boolean;
@@ -36,6 +39,7 @@ class ControlBar {
this.htmlTag.find(".btn_mute_output").on('click', this.onOutputMute.bind(this));
this.htmlTag.find(".btn_open_settings").on('click', this.onOpenSettings.bind(this));
this.htmlTag.find(".btn_permissions").on('click', this.onPermission.bind(this));
+ this.htmlTag.find(".btn_banlist").on('click', this.onBanlist.bind(this));
{
let tokens = this.htmlTag.find(".btn_token");
tokens.find(".button-dropdown").on('click', () => {
@@ -256,4 +260,10 @@ class ControlBar {
button.removeClass("activated");
}, 0);
}
+
+ private onBanlist() {
+ if(!this.handle.serverConnection) return;
+
+ openBanList(this.handle);
+ }
}
\ No newline at end of file
diff --git a/shared/js/ui/modal/ModalBanClient.ts b/shared/js/ui/modal/ModalBanClient.ts
index 407e792f..269b618c 100644
--- a/shared/js/ui/modal/ModalBanClient.ts
+++ b/shared/js/ui/modal/ModalBanClient.ts
@@ -3,7 +3,13 @@
///
namespace Modals {
- export function spawnBanClient(name: string, callback: (length: number, reason: string) => void) {
+ export function spawnBanClient(name: string, callback: (data: {
+ length: number,
+ reason: string,
+ no_name: boolean,
+ no_ip: boolean,
+ no_hwid: boolean
+ }) => void) {
const connectModal = createModal({
header: function() {
return "Ban client";
@@ -85,7 +91,14 @@ namespace Modals {
let duration = connectModal.htmlTag.find(".ban_duration_type option:selected");
console.log(duration);
console.log(length + "*" + duration.attr("duration-scale"));
- callback(length * parseInt(duration.attr("duration-scale")), connectModal.htmlTag.find(".ban_reason").val() as string);
+
+ callback({
+ length: length * parseInt(duration.attr("duration-scale")),
+ reason: connectModal.htmlTag.find(".ban_reason").val() as string,
+ no_hwid: !connectModal.htmlTag.find(".ban-type-hardware-id").prop("checked"),
+ no_ip: !connectModal.htmlTag.find(".ban-type-ip").prop("checked"),
+ no_name: !connectModal.htmlTag.find(".ban-type-nickname").prop("checked")
+ });
})
}
}
\ No newline at end of file
diff --git a/shared/js/ui/modal/ModalBanCreate.ts b/shared/js/ui/modal/ModalBanCreate.ts
new file mode 100644
index 00000000..8614f4b0
--- /dev/null
+++ b/shared/js/ui/modal/ModalBanCreate.ts
@@ -0,0 +1,148 @@
+namespace Modals {
+ export function spawnBanCreate(base?: BanEntry, callback?: (entry?: BanEntry) => any) {
+ let result: BanEntry = {} as any;
+ result.banid = base ? base.banid : 0;
+
+ let modal: Modal;
+ modal = createModal({
+ header: base && base.banid > 0 ? "Edit ban" : "Add ban",
+ body: () => {
+ let template = $("#tmpl_ban_create").renderTag();
+ template = $.spawn("div").append(template);
+
+ const input_name = template.find(".input-name");
+ const input_name_type = template.find(".input-name-type");
+ const input_ip = template.find(".input-ip");
+ const input_uid = template.find(".input-uid");
+ const input_reason = template.find(".input-reason");
+ const input_time = template.find(".input-time");
+ const input_time_type = template.find(".input-time-unit");
+ const input_hwid = template.find(".input-hwid");
+ const input_global = template.find(".input-global");
+
+ {
+ let maxTime = 0; //globalClient.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).value;
+ let unlimited = maxTime == 0 || maxTime == -1;
+ if(unlimited) maxTime = 0;
+
+ input_time_type.find("option[value=\"sec\"]").prop("disabled", !unlimited && 1 > maxTime)
+ .attr("duration-scale", 1)
+ .attr("duration-max", maxTime);
+ input_time_type.find("option[value=\"min\"]").prop("disabled", !unlimited && 60 > maxTime)
+ .attr("duration-scale", 60)
+ .attr("duration-max", maxTime / 60);
+ input_time_type.find("option[value=\"hours\"]").prop("disabled", !unlimited && 60 * 60 > maxTime)
+ .attr("duration-scale", 60 * 60)
+ .attr("duration-max", maxTime / (60 * 60));
+ input_time_type.find("option[value=\"days\"]").prop("disabled", !unlimited && 60 * 60 * 24 > maxTime)
+ .attr("duration-scale", 60 * 60 * 24)
+ .attr("duration-max", maxTime / (60 * 60 * 24));
+ input_time_type.find("option[value=\"perm\"]").prop("disabled", !unlimited)
+ .attr("duration-scale", 0);
+
+ input_time_type.change(event => {
+ let element = $((event.target as HTMLSelectElement).selectedOptions.item(0));
+ if(element.val() !== "perm") {
+ input_time.prop("disabled", false);
+
+ let current = input_time.val() as number;
+ let max = parseInt(element.attr("duration-max"));
+ if (max > 0 && current > max)
+ input_time.val(max);
+ else if(current <= 0)
+ input_time.val(1);
+ input_time.attr("max", max);
+ } else {
+ input_time.prop("disabled", true);
+ }
+ });
+ }
+
+ template.find('input, textarea').on('keyup change', event => {
+ let valid = false;
+
+ if(input_name.val() || input_ip.val() || input_uid.val())
+ valid = true;
+
+ modal.htmlTag.find(".button-success").prop("disabled", !valid);
+ });
+
+ if(base) {
+ input_ip.val(base.ip);
+ input_uid.val(base.unique_id);
+ input_name.val(base.name);
+ input_hwid.val(base.hardware_id);
+
+ (input_name_type[0] as HTMLSelectElement).selectedIndex = base.name_type || 0;
+ input_reason.val(base.reason);
+
+ if(base.timestamp_expire.getTime() == 0) {
+ input_time_type.find("option[value=\"perm\"]").prop("selected", true);
+ } else {
+ const time = (base.timestamp_expire.getTime() - base.timestamp_created.getTime()) / 1000;
+ if(time % (60 * 60 * 24) === 0) {
+ input_time_type.find("option[value=\"days\"]").prop("selected", true);
+ input_time.val(time / (60 * 60 * 24));
+ } else if(time % (60 * 60) === 0) {
+ input_time_type.find("option[value=\"hours\"]").prop("selected", true);
+ input_time.val(time / (60 * 60));
+ } else if(time % (60) === 0) {
+ input_time_type.find("option[value=\"min\"]").prop("selected", true);
+ input_time.val(time / (60));
+ } else {
+ input_time_type.find("option[value=\"sec\"]").prop("selected", true);
+ input_time.val(time);
+ }
+ }
+
+ template.find(".container-global").detach(); //We cant edit this
+ input_global.prop("checked", base.server_id == 0);
+ }
+
+ if(globalClient && globalClient.permissions)
+ input_global.prop("disabled", !globalClient.permissions.neededPermission(base ? PermissionType.B_CLIENT_BAN_EDIT_GLOBAL : PermissionType.B_CLIENT_BAN_CREATE_GLOBAL));
+
+ return template;
+ },
+ footer: undefined
+ });
+
+ modal.htmlTag.find(".button-close").on('click', () => modal.close());
+ modal.htmlTag.find(".button-success").on('click', () => {
+ {
+ let length = modal.htmlTag.find(".input-time").val() as number;
+ let duration = modal.htmlTag.find(".input-time-unit option:selected");
+
+ console.log(duration);
+ console.log(length + "*" + duration.attr("duration-scale"));
+ const time = length * parseInt(duration.attr("duration-scale"));
+
+ if(!result.timestamp_created)
+ result.timestamp_created = new Date();
+ if(time > 0)
+ result.timestamp_expire = new Date(result.timestamp_created.getTime() + time * 1000);
+ else
+ result.timestamp_expire = new Date(0);
+ }
+ {
+ result.name = modal.htmlTag.find(".input-name").val() as string;
+ {
+ const name_type = modal.htmlTag.find(".input-name-type") as JQuery;
+ result.name_type = name_type[0].selectedIndex;
+ }
+ result.ip = modal.htmlTag.find(".input-ip").val() as string;
+ result.unique_id = modal.htmlTag.find(".input-uid").val() as string;
+ result.reason = modal.htmlTag.find(".input-reason").val() as string;
+ result.hardware_id = modal.htmlTag.find(".input-hwid").val() as string;
+ result.server_id = modal.htmlTag.find(".input-global").prop("checked") ? 0 : -1;
+ }
+
+ modal.close();
+ if(callback) callback(result);
+ });
+
+ modal.htmlTag.find("input").trigger("change");
+
+ modal.open();
+ }
+}
\ No newline at end of file
diff --git a/shared/js/ui/modal/ModalBanList.ts b/shared/js/ui/modal/ModalBanList.ts
new file mode 100644
index 00000000..75bdf9fb
--- /dev/null
+++ b/shared/js/ui/modal/ModalBanList.ts
@@ -0,0 +1,320 @@
+namespace Modals {
+ export interface BanEntry {
+ server_id: number;
+ banid: number;
+
+ name?: string;
+ name_type?: number;
+
+ unique_id?: string;
+ ip?: string;
+ hardware_id?: string;
+
+ reason: string;
+ invoker_name: string;
+ invoker_unique_id?: string;
+ invoker_database_id?: number;
+
+ timestamp_created: Date;
+ timestamp_expire: Date;
+
+ enforcements: number;
+
+ flag_own?: boolean;
+ }
+ export interface BanListManager {
+ addbans : (ban: BanEntry[]) => void;
+ clear : (ban?: any) => void;
+ }
+
+ export function openBanList(client: TSClient) {
+ let update;
+ const modal = spawnBanListModal(() => update(), () => {
+ spawnBanCreate(undefined, result => {
+ if(result.server_id < 0) result.server_id = undefined;
+ console.log("Adding ban %o", result);
+
+ client.serverConnection.sendCommand("banadd", {
+ ip: result.ip,
+ name: result.name,
+ uid: result.unique_id,
+ hwid: result.hardware_id,
+ banreason: result.reason,
+ time: result.timestamp_expire.getTime() > 0 ? (result.timestamp_expire.getTime() - result.timestamp_created.getTime()) / 1000 : 0,
+ sid: result.server_id
+ }).then(() => {
+ update();
+ }).catch(error => {
+ createErrorModal("Failed to add ban", "Failed to add ban.
Reason: " + (error instanceof CommandResult ? error.extra_message || error.message : error)).open();
+ });
+ });
+ }, ban => {
+ console.log("Editing ban %o", ban);
+ spawnBanCreate(ban, result => {
+ console.log("Apply edit changes %o", result);
+ if(result.server_id < 0) result.server_id = undefined;
+
+ client.serverConnection.sendCommand("banedit", {
+ banid: result.banid,
+ ip: result.ip,
+ name: result.name,
+ uid: result.unique_id,
+ hwid: result.hardware_id,
+ banreason: result.reason,
+ time: result.timestamp_expire.getTime() > 0 ? (result.timestamp_expire.getTime() - result.timestamp_created.getTime()) / 1000 : 0,
+ sid: result.server_id
+ }).then(() => {
+ update();
+ }).catch(error => {
+ createErrorModal("Failed to edit ban", "Failed to edit ban.
Reason: " + (error instanceof CommandResult ? error.extra_message || error.message : error)).open();
+ });
+ });
+ }, ban => {
+ console.log("Deleting ban %o", ban);
+ client.serverConnection.sendCommand("bandel", {
+ banid: ban.banid,
+ sid: ban.server_id
+ }).then(() => {
+ update();
+ }).catch(error => {
+ createErrorModal("Failed to delete ban", "Failed to delete ban.
Reason: " + (error instanceof CommandResult ? error.extra_message || error.message : error)).open();
+ });
+ });
+
+ update = () => {
+ client.serverConnection.commandHandler["notifybanlist"] = json => {
+ console.log("Got banlist: ", json);
+
+ let bans: BanEntry[] = [];
+ for(const entry of json) {
+ /*
+ notify[index]["sid"] = elm->serverId;
+ notify[index]["banid"] = elm->banId;
+ if(allow_ip)
+ notify[index]["ip"] = elm->ip;
+ else
+ notify[index]["ip"] = "hidden";
+ notify[index]["name"] = elm->name;
+ notify[index]["uid"] = elm->uid;
+ notify[index]["lastnickname"] = elm->name; //Maybe update?
+
+ notify[index]["created"] = chrono::duration_cast(elm->created.time_since_epoch()).count();
+ if (elm->until.time_since_epoch().count() != 0)
+ notify[index]["duration"] = chrono::duration_cast(elm->until - elm->created).count();
+ else
+ notify[index]["duration"] = 0;
+
+ notify[index]["reason"] = elm->reason;
+ notify[index]["enforcements"] = elm->triggered;
+
+ notify[index]["invokername"] = elm->invokerName;
+ notify[index]["invokercldbid"] = elm->invokerDbId;
+ notify[index]["invokeruid"] = elm->invokerUid;
+ */
+ bans.push({
+ server_id: parseInt(entry["sid"]),
+ banid: parseInt(entry["banid"]),
+ ip: entry["ip"],
+ name: entry["name"],
+ unique_id: entry["uid"],
+ hardware_id: entry["hwid"],
+
+ timestamp_created: new Date(parseInt(entry["created"]) * 1000),
+ timestamp_expire: new Date(parseInt(entry["duration"]) > 0 ? parseInt(entry["created"]) * 1000 + parseInt(entry["duration"]) * 1000 : 0),
+
+ invoker_name: entry["invokername"],
+ invoker_database_id: parseInt(entry["invokercldbid"]),
+ invoker_unique_id: entry["invokeruid"],
+ reason: entry["reason"],
+
+ enforcements: parseInt(entry["enforcements"]),
+ flag_own: entry["invokeruid"] == client.getClient().properties.client_unique_identifier
+ });
+ }
+
+ modal.addbans(bans);
+ };
+
+ //TODO test permission
+ modal.clear();
+ client.serverConnection.sendCommand("banlist", { sid: 0 }); //Global ban list
+ client.serverConnection.sendCommand("banlist").catch(error => {
+ if(error instanceof CommandResult) {
+ } else {
+ console.error(error);
+ }
+ });
+ };
+
+ update();
+ }
+
+ export function spawnBanListModal(callback_update: () => any, callback_add: () => any, callback_edit: (entry: BanEntry) => any, callback_delete: (entry: BanEntry) => any) : BanListManager {
+ let result: BanListManager = {} as any;
+
+ let entries: BanEntry[] = [];
+ const _callback_edit = ban_id => {
+ for(const entry of entries)
+ if(entry.banid == ban_id) {
+ callback_edit(entry);
+ return;
+ }
+ console.warn("Missing ban entry with id " + ban_id);
+ };
+
+ const _callback_delete = ban_id => {
+ for (const entry of entries)
+ if (entry.banid == ban_id) {
+ callback_delete(entry);
+ return;
+ }
+ console.warn("Missing ban entry with id " + ban_id);
+ };
+
+ let update_function: () => any;
+ let modal: Modal;
+ modal = createModal({
+ header: "Banlist",
+ body: () => {
+ let template = $("#tmpl_ban_list").renderTag();
+ template = $.spawn("div").append(template);
+
+ apply_filter(template.find(".entry-filter"), template.find(".filter-flag-force-own"), template.find(".filter-flag-highlight-own"), template.find(".ban-entry-list"));
+ update_function = apply_buttons(template.find(".manage-buttons"), template.find(".ban-entry-list"), callback_add, _callback_edit, _callback_delete);
+ template.find(".button-close").on('click', _ => modal.close());
+ template.find(".button-refresh").on('click', () => callback_update());
+ return template;
+ },
+ footer: undefined,
+ width: "80%",
+ height: "80%"
+ });
+ modal.open();
+ modal.close_listener.push(() => entries = []);
+
+ result.addbans = (bans: BanEntry[]) => {
+ for(const entry of bans) {
+ entries.push(entry);
+ $("#tmpl_ban_entry").renderTag(entry).appendTo(modal.htmlTag.find(".ban-entry-list"));
+ }
+ modal.htmlTag.find(".entry-filter").trigger("change");
+ update_function();
+ };
+ result.clear = () => {
+ entries = [];
+ modal.htmlTag.find(".ban-entry-list").children().detach();
+ update_function();
+ };
+
+ return result;
+ }
+
+ function apply_filter(input: JQuery, show_own_bans: JQuery, highlight_own_bans: JQuery, elements: JQuery) {
+ input.on('keyup change', event => {
+ const filter = (input.val() as string).trim();
+ const show_own_only = show_own_bans.prop("checked");
+ const highlight_own = highlight_own_bans.prop("checked");
+
+ console.log("Search for filter %s", filter);
+
+ let shown = 0, hidden = 0;
+ elements.find(".ban-entry").each((_idx, _entry) => {
+ const entry = $(_entry);
+ if(entry.hasClass("ban-entry-own")) {
+ if(highlight_own)
+ entry.addClass("ban-entry-own-bold");
+ else
+ entry.removeClass("ban-entry-own-bold");
+
+ } else if(show_own_only) {
+ if(entry.hide().hasClass("selected"))
+ entry.trigger("click");
+ hidden++;
+ return;
+ }
+ if(filter.length == 0 || entry.text().indexOf(filter) > 0) {
+ entry.show();
+ shown++;
+ } else {
+ if(entry.hide().hasClass("selected"))
+ entry.trigger("click");
+ hidden++;
+ }
+ });
+
+ $(".entry-count-info").text((shown + hidden) + " entries. " + shown + " entries shown");
+ });
+ show_own_bans.on('click', () => input.trigger('change'));
+ highlight_own_bans.on('click', () => input.trigger('change'));
+ }
+
+ function apply_buttons(tag: JQuery, elements: JQuery, cb_add: () => any, cb_edit: (id: number) => any, cb_delete: (id: number) => any) : () => any {
+ const update = () => {
+ console.log(elements.find("tr.selected").length);
+ $(".button-edit, .button-remove").prop("disabled", elements.find("tr.selected").length == 0);
+ };
+
+ tag.find(".button-add").on('click', event => cb_add());
+ tag.find(".button-edit").on('click', event => {
+ const selected = elements.find("tr.selected");
+ if(!selected) return;
+ cb_edit(parseInt(selected.attr("ban-id")));
+ });
+ tag.find(".button-remove").on('click', event => {
+ const selected = elements.find("tr.selected");
+ if(!selected) return;
+ cb_delete(parseInt(selected.attr("ban-id")));
+ });
+
+ const element_selected = element => {
+ elements.find("tr").removeClass("selected");
+ if(element.is(":visible"))
+ element.addClass("selected");
+
+ update();
+ };
+
+ const click_handler = event => element_selected($(event.currentTarget));
+ const context_handler = event => {
+ const element = $(event.currentTarget);
+ element_selected(element);
+
+ event.preventDefault();
+
+ spawn_context_menu(event.pageX, event.pageY, {
+ name: "Edit",
+ type: MenuEntryType.ENTRY,
+ callback: () => cb_edit(parseInt(element.attr("ban-id")))
+ }, {
+ name: "Delete",
+ type: MenuEntryType.ENTRY,
+ callback: () => cb_delete(parseInt(element.attr("ban-id")))
+ });
+ };
+
+ return () => {
+ elements.find("tr").each((_idx, _entry) => {
+ _entry.addEventListener("click", click_handler);
+ _entry.addEventListener("contextmenu", context_handler)
+ });
+ update();
+ };
+ }
+}
+
+/*
+
+
+ name=WolverasdasdinDEV
+ ip=WolverasdasdinDEV
+ uid=WolverasdasdinDEV
+ hwid=WolverasdasdinDEV
+ |
+ Insult |
+ WolverinDEV |
+
+ Created: 1.1.2013 12:22
+ Expires: 1.21.2013 12:22
+ |
+
+ */
\ No newline at end of file
diff --git a/shared/js/utils/modal.ts b/shared/js/utils/modal.ts
index 2ca4d218..a9181dc4 100644
--- a/shared/js/utils/modal.ts
+++ b/shared/js/utils/modal.ts
@@ -71,12 +71,16 @@ class ModalProperties {
}
}
-class Modal {
+class Modal extends EventTarget {
private _htmlTag: JQuery;
properties: ModalProperties;
shown: boolean;
+ close_listener: (() => any)[] = [];
+
constructor(props: ModalProperties) {
+ super();
+
this.properties = props;
this.shown = false;
}
@@ -124,12 +128,16 @@ class Modal {
}
close() {
+ if(!this.shown) return;
+
this.shown = false;
const _this = this;
this.htmlTag.animate({opacity: 0}, () => {
_this.htmlTag.detach();
});
this.properties.triggerClose();
+ for(const listener of this.close_listener)
+ listener();
}
}
@@ -223,7 +231,7 @@ function createErrorModal(header: BodyCreator, message: BodyCreator, props: Moda
ModalFunctions.divify(ModalFunctions.jqueriefy(header)).appendTo(head);
props.header = head;
- props.body = ModalFunctions.divify(ModalFunctions.jqueriefy(message));
+ props.body = $.spawn("div").append(ModalFunctions.divify(ModalFunctions.jqueriefy(message)));
props.footer = ModalFunctions.divify(ModalFunctions.jqueriefy(""));
return createModal(props);
diff --git a/templates.html b/templates.html
index 7a6c2988..3f6bd3f8 100644
--- a/templates.html
+++ b/templates.html
@@ -56,6 +56,9 @@