Added banlist and fixed pointer stringify

canary
WolverinDEV 2018-10-20 19:58:06 +02:00
parent f743f9b3fc
commit 90a042eea3
20 changed files with 15510 additions and 52 deletions

View File

@ -1,4 +1,9 @@
# Changelog: # Changelog:
* **20.10.18**
- Project restructuring (forgot to update the changelog)
- Added a complete ban ui
- Fixed pointer stringify bug
* **30.09.18** * **30.09.18**
- Added the permission system (Assignments and management) - Added the permission system (Assignments and management)
* Fixed poke and client description with empty message * Fixed poke and client description with empty message

View File

@ -40,6 +40,7 @@
?> ?>
<link rel="stylesheet" href="css/main.css" type="text/css"> <link rel="stylesheet" href="css/main.css" type="text/css">
<link rel="stylesheet" href="css/helptag.css" type="text/css">
<link rel="stylesheet" href="css/scroll.css" type="text/css"> <link rel="stylesheet" href="css/scroll.css" type="text/css">
<link rel="stylesheet" href="css/ts/tab.css" type="text/css"> <link rel="stylesheet" href="css/ts/tab.css" type="text/css">
<link rel="stylesheet" href="css/ts/chat.css" type="text/css"> <link rel="stylesheet" href="css/ts/chat.css" type="text/css">
@ -47,6 +48,8 @@
<link rel="stylesheet" href="css/ts/icons.css" type="text/css"> <link rel="stylesheet" href="css/ts/icons.css" type="text/css">
<link rel="stylesheet" href="css/general.css" type="text/css"> <link rel="stylesheet" href="css/general.css" type="text/css">
<link rel="stylesheet" href="css/modals.css" type="text/css"> <link rel="stylesheet" href="css/modals.css" type="text/css">
<link rel="stylesheet" href="css/modal-banlist.css" type="text/css">
<link rel="stylesheet" href="css/modal-bancreate.css" type="text/css">
<link rel="stylesheet" href="css/loader.css" type="text/css"> <link rel="stylesheet" href="css/loader.css" type="text/css">
<link rel="stylesheet" href="css/music/info_plate.css" type="text/css"> <link rel="stylesheet" href="css/music/info_plate.css" type="text/css">
<link rel="stylesheet" href="css/frame/SelectInfo.css" type="text/css"> <link rel="stylesheet" href="css/frame/SelectInfo.css" type="text/css">
@ -93,7 +96,7 @@
</div> </div>
</head> </head>
<body> <body>
<?php if(!$CLIENT) { ?> <?php if(!$CLIENT && false) { ?>
<!-- No javascript error --> <!-- No javascript error -->
<div style="display: block; position: fixed; top: 0px; bottom: 0px; left: 0px; right: 0px; background-color: gray; z-index: 1000; text-align: center;" class="no-js"> <div style="display: block; position: fixed; top: 0px; bottom: 0px; left: 0px; right: 0px; background-color: gray; z-index: 1000; text-align: center;" class="no-js">
<div style="position: relative; display: inline-block; top: 30%"> <div style="position: relative; display: inline-block; top: 30%">
@ -128,8 +131,7 @@
</div> </div>
<!-- Critical load error --> <!-- Critical load error -->
<div style="display: none; position: fixed; top: 0; bottom: 0; left: 0; right: 0; background-color: gray; z-index: 1000; text-align: center;" <div style="display: none; position: fixed; top: 0; bottom: 0; left: 0; right: 0; background-color: gray; z-index: 1000; text-align: center;" id="critical-load">
id="critical-load">
<div style="position: relative; display: inline-block; top: 30%"> <div style="position: relative; display: inline-block; top: 30%">
<img src="img/script.svg" height="128px"> <img src="img/script.svg" height="128px">
<h1 style="color: red">Got some trouble while loading important files!</h1> <h1 style="color: red">Got some trouble while loading important files!</h1>

View File

@ -22,7 +22,9 @@
"sass": "^1.14.1", "sass": "^1.14.1",
"typescript": "^3.1.1" "typescript": "^3.1.1"
}, },
"dependencies": {}, "dependencies": {
"@types/moment": "^2.13.0"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/TeaSpeak/TeaWeb/TeaWeb.git" "url": "git+https://github.com/TeaSpeak/TeaWeb/TeaWeb.git"

View File

@ -1,6 +1,9 @@
#!/usr/bin/env bash #!/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 #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

125
shared/css/helptag.scss Normal file
View File

@ -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%; }
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -87,10 +87,6 @@
} }
} }
.container {
margin: 2px;
}
/* Channel edit/create modal */ /* Channel edit/create modal */
.settings_audio { .settings_audio {
display: grid; display: grid;
@ -337,6 +333,10 @@
} }
} }
.container-ban-type {
margin: 5px;
}
.arrow { .arrow {
display: inline-block; display: inline-block;
border: solid black; border: solid black;

View File

@ -20,7 +20,7 @@ namespace TSIdentityHelper {
let functionDestroyString: any; let functionDestroyString: any;
let functionDestroyIdentity: any; let functionDestroyIdentity: any;
export function setup() : boolean { export function setup() : boolean {
functionDestroyString = Module.cwrap("destroy_string", "pointer", []); functionDestroyString = Module.cwrap("destroy_string", "pointer", []);
functionLastError = Module.cwrap("last_error_message", null, ["string"]); functionLastError = Module.cwrap("last_error_message", null, ["string"]);
funcationParseIdentity = Module.cwrap("parse_identity", "pointer", ["string"]); funcationParseIdentity = Module.cwrap("parse_identity", "pointer", ["string"]);
@ -43,10 +43,10 @@ namespace TSIdentityHelper {
export function unwarpString(str) : string { export function unwarpString(str) : string {
if(str == "") return ""; if(str == "") return "";
try { try {
if(!$.isFunction(window.Pointer_stringify) || !$.isFunction(Pointer_stringify)) { if(!$.isFunction(window.Pointer_stringify)) {
displayCriticalError("Missing required wasm function!<br>Please reload the page!"); displayCriticalError("Missing required wasm function!<br>Please reload the page!");
} }
let message: string = Pointer_stringify(str); let message: string = window.Pointer_stringify(str);
functionDestroyString(str); functionDestroyString(str);
return message; return message;
} catch (error) { } catch (error) {

View File

@ -215,7 +215,7 @@ class ServerConnection {
"type": "command", "type": "command",
"command": command, "command": command,
"data": _data, "data": _data,
"flags": flags "flags": flags.filter(entry => entry.length != 0)
})); }));
}); });
return new Promise<CommandResult>((resolve, failed) => { return new Promise<CommandResult>((resolve, failed) => {
@ -225,7 +225,8 @@ class ServerConnection {
let res = ex; let res = ex;
if(!res.success) { if(!res.success) {
if(res.id == 2568) { //Permission error 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 { } else {
chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message); chat.serverChat().appendError(res.extra_message.length == 0 ? res.message : res.extra_message);
} }

View File

@ -136,6 +136,8 @@ function loadDebug() {
"js/ui/modal/ModalConnect.js", "js/ui/modal/ModalConnect.js",
"js/ui/modal/ModalChangeVolume.js", "js/ui/modal/ModalChangeVolume.js",
"js/ui/modal/ModalBanClient.js", "js/ui/modal/ModalBanClient.js",
"js/ui/modal/ModalBanCreate.js",
"js/ui/modal/ModalBanList.js",
"js/ui/modal/ModalYesNo.js", "js/ui/modal/ModalYesNo.js",
"js/ui/modal/ModalPermissionEdit.js", "js/ui/modal/ModalPermissionEdit.js",
"js/ui/modal/ModalServerGroupDialog.js", "js/ui/modal/ModalServerGroupDialog.js",
@ -224,6 +226,10 @@ function loadTemplates() {
tags = node.children; tags = node.children;
let root = document.getElementById("templates"); let root = document.getElementById("templates");
if(!root) {
displayCriticalError("Failed to find template tag!");
return;
}
while(tags.length > 0){ while(tags.length > 0){
let tag = tags.item(0); let tag = tags.item(0);
if(tag.id == "tmpl_main") { if(tag.id == "tmpl_main") {
@ -283,6 +289,7 @@ function loadSide() {
"vendor/jsrender/jsrender.min.js" "vendor/jsrender/jsrender.min.js"
])).then(() => load_wait_scripts([ ])).then(() => load_wait_scripts([
["vendor/bbcode/xbbcode.js"], ["vendor/bbcode/xbbcode.js"],
["vendor/moment/moment.js"],
["https://webrtc.github.io/adapter/adapter-latest.js"] ["https://webrtc.github.io/adapter/adapter-latest.js"]
])).then(() => { ])).then(() => {
//Load the teaweb scripts //Load the teaweb scripts

View File

@ -4,8 +4,10 @@
/// <reference path="utils/modal.ts" /> /// <reference path="utils/modal.ts" />
/// <reference path="ui/modal/ModalConnect.ts" /> /// <reference path="ui/modal/ModalConnect.ts" />
/// <reference path="ui/modal/ModalCreateChannel.ts" /> /// <reference path="ui/modal/ModalCreateChannel.ts" />
/// <reference path="ui/modal/ModalBanCreate.ts" />
/// <reference path="ui/modal/ModalBanClient.ts" /> /// <reference path="ui/modal/ModalBanClient.ts" />
/// <reference path="ui/modal/ModalYesNo.ts" /> /// <reference path="ui/modal/ModalYesNo.ts" />
/// <reference path="ui/modal/ModalBanList.ts" />
/// <reference path="codec/CodecWrapper.ts" /> /// <reference path="codec/CodecWrapper.ts" />
/// <reference path="settings.ts" /> /// <reference path="settings.ts" />
/// <reference path="log.ts" /> /// <reference path="log.ts" />
@ -49,6 +51,7 @@ function setup_close() {
}; };
} }
declare function moment(...arguments) : any;
function setup_jsrender() : boolean { function setup_jsrender() : boolean {
if(!js_render) { if(!js_render) {
displayCriticalError("Missing jsrender extension!"); displayCriticalError("Missing jsrender extension!");
@ -66,6 +69,10 @@ function setup_jsrender() : boolean {
return (Math.round(Math.random() * (min + max + 1) - min)).toString(); 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) => { $(".jsrender-template").each((idx, _entry) => {
if(!js_render.templates(_entry.id, _entry.innerHTML)) { //, _entry.innerHTML if(!js_render.templates(_entry.id, _entry.innerHTML)) { //, _entry.innerHTML
console.error("Failed to cache template " + _entry.id + " for js render!"); console.error("Failed to cache template " + _entry.id + " for js render!");
@ -138,8 +145,6 @@ function main() {
}) })
*/ */
//Modals.spawnPermissionEdit();
setup_close(); setup_close();
$(window).on('resize', () => { $(window).on('resize', () => {
globalClient.channelTree.handle_resized(); globalClient.channelTree.handle_resized();
@ -154,7 +159,7 @@ app.loadedListener.push(() => {
$(document).one('click', event => AudioController.initializeFromGesture()); $(document).one('click', event => AudioController.initializeFromGesture());
} }
} catch (ex) { } catch (ex) {
if(ex instanceof ReferenceError) if(ex instanceof ReferenceError || ex instanceof TypeError)
ex = ex.message + ":<br>" + ex.stack; ex = ex.message + ":<br>" + ex.stack;
displayCriticalError("Failed to invoke main function:<br>" + ex); displayCriticalError("Failed to invoke main function:<br>" + ex);
} }

View File

@ -307,12 +307,12 @@ class ClientEntry {
name: "Ban client", name: "Ban client",
invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1), invalidPermission: !this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_BAN_MAX_BANTIME).granted(1),
callback: () => { callback: () => {
Modals.spawnBanClient(this.properties.client_nickname, (duration, reason) => { Modals.spawnBanClient(this.properties.client_nickname, (data) => {
this.channelTree.client.serverConnection.sendCommand("banclient", { this.channelTree.client.serverConnection.sendCommand("banclient", {
uid: this.properties.client_unique_identifier, uid: this.properties.client_unique_identifier,
banreason: reason, banreason: data.reason,
time: duration time: data.length
}); }, [data.no_ip ? "no-ip" : "", data.no_hwid ? "no-hardware-id" : "", data.no_name ? "no-nickname" : ""]);
}); });
} }
}, },

View File

@ -1,5 +1,6 @@
/// <reference path="../../client.ts" /> /// <reference path="../../client.ts" />
/// <reference path="../modal/ModalSettings.ts" /> /// <reference path="../modal/ModalSettings.ts" />
/// <reference path="../modal/ModalBanList.ts" />
/* /*
client_output_hardware Value: '1' client_output_hardware Value: '1'
client_output_muted Value: '0' client_output_muted Value: '0'
@ -11,6 +12,8 @@
client_away Value: '0' client_away Value: '0'
client_away_message Value: '' client_away_message Value: ''
*/ */
import openBanList = Modals.openBanList;
class ControlBar { class ControlBar {
private _muteInput: boolean; private _muteInput: boolean;
private _muteOutput: 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_mute_output").on('click', this.onOutputMute.bind(this));
this.htmlTag.find(".btn_open_settings").on('click', this.onOpenSettings.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_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"); let tokens = this.htmlTag.find(".btn_token");
tokens.find(".button-dropdown").on('click', () => { tokens.find(".button-dropdown").on('click', () => {
@ -256,4 +260,10 @@ class ControlBar {
button.removeClass("activated"); button.removeClass("activated");
}, 0); }, 0);
} }
private onBanlist() {
if(!this.handle.serverConnection) return;
openBanList(this.handle);
}
} }

View File

@ -3,7 +3,13 @@
/// <reference path="../../client.ts" /> /// <reference path="../../client.ts" />
namespace Modals { 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({ const connectModal = createModal({
header: function() { header: function() {
return "Ban client"; return "Ban client";
@ -85,7 +91,14 @@ namespace Modals {
let duration = connectModal.htmlTag.find(".ban_duration_type option:selected"); let duration = connectModal.htmlTag.find(".ban_duration_type option:selected");
console.log(duration); console.log(duration);
console.log(length + "*" + duration.attr("duration-scale")); 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")
});
}) })
} }
} }

View File

@ -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<HTMLSelectElement>;
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();
}
}

View File

@ -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.<br>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.<br>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.<br>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<chrono::seconds>(elm->created.time_since_epoch()).count();
if (elm->until.time_since_epoch().count() != 0)
notify[index]["duration"] = chrono::duration_cast<chrono::seconds>(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();
};
}
}
/*
<tr class="ban-entry">
<td class="field-properties">
<a class="property property-name">name=WolverasdasdinDEV</a>
<a class="property property-ip">ip=WolverasdasdinDEV</a>
<a class="property property-unique-id">uid=WolverasdasdinDEV</a>
<a class="property property-hardware-id">hwid=WolverasdasdinDEV</a>
</td>
<td class="field-reason">Insult</td>
<td class="field-invoker">WolverinDEV</td>
<td class="field-timestamp">
<a class="timestamp-created">Created: 1.1.2013 12:22</a>
<a class="timestamp-expires">Expires: 1.21.2013 12:22</a>
</td>
</tr>
*/

View File

@ -71,12 +71,16 @@ class ModalProperties {
} }
} }
class Modal { class Modal extends EventTarget {
private _htmlTag: JQuery; private _htmlTag: JQuery;
properties: ModalProperties; properties: ModalProperties;
shown: boolean; shown: boolean;
close_listener: (() => any)[] = [];
constructor(props: ModalProperties) { constructor(props: ModalProperties) {
super();
this.properties = props; this.properties = props;
this.shown = false; this.shown = false;
} }
@ -124,12 +128,16 @@ class Modal {
} }
close() { close() {
if(!this.shown) return;
this.shown = false; this.shown = false;
const _this = this; const _this = this;
this.htmlTag.animate({opacity: 0}, () => { this.htmlTag.animate({opacity: 0}, () => {
_this.htmlTag.detach(); _this.htmlTag.detach();
}); });
this.properties.triggerClose(); 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); ModalFunctions.divify(ModalFunctions.jqueriefy(header)).appendTo(head);
props.header = 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("")); props.footer = ModalFunctions.divify(ModalFunctions.jqueriefy(""));
return createModal(props); return createModal(props);

View File

@ -56,6 +56,9 @@
</div> </div>
<div style="width: 100%"></div> <div style="width: 100%"></div>
<div class="button btn_banlist" title="Banlist">
<div class="icon_x32 client-ban_list"></div>
</div>
<div class="button btn_permissions" title="View/edit permissions"> <div class="button btn_permissions" title="View/edit permissions">
<div class="icon_x32 client-permission_overview"></div> <div class="icon_x32 client-permission_overview"></div>
</div> </div>
@ -153,7 +156,7 @@
</div> </div>
</script> </script>
<!-- Template for chennel create & edit--> <!-- Template for channel create & edit-->
<script class="jsrender-template" id="tmpl_channel_edit" type="text/html"> <script class="jsrender-template" id="tmpl_channel_edit" type="text/html">
<div class="align_column general_properties"> <div class="align_column general_properties">
<div class="properties"> <div class="properties">
@ -613,31 +616,6 @@
<div class="display_volume" style="width: 60px; align-self: center; text-align: center">&plusmn;0 %</div> <div class="display_volume" style="width: 60px; align-self: center; text-align: center">&plusmn;0 %</div>
</div> </div>
</script> </script>
<script class="jsrender-template" id="tmpl_client_ban" type="text/html">
<div class="align_column">
<div class="align_column" style="margin: 5px">
<a>Name:</a>
<input value="{{>client_name}}" readonly>
</div>
<div class="align_column" style="margin: 5px">
<a>Reason:</a>
<textarea style="height: 32px; resize: vertical; max-height: 150px; min-height: 32px" maxlength="512" class="ban_reason"></textarea>
</div>
<div class="align_row" style="margin: 5px; justify-content: space-between">
<a>Duration:</a>
<div class="align_row">
<input type="number" value="1" class="ban_duration" style="margin-right: 7px" min="1">
<select class="ban_duration_type">
<option value="sec">seconds</option>
<option value="min">minutes</option>
<option value="hours">hours</option>
<option value="days">days</option>
<option value="perm">permanent</option>
</select>
</div>
</div>
</div>
</script>
<!-- Permission overview --> <!-- Permission overview -->
<script class="jsrender-template" id="tmpl_server_permissions" type="text/html"> <script class="jsrender-template" id="tmpl_server_permissions" type="text/html">
@ -856,6 +834,220 @@
</div> </div>
</script> </script>
<!-- General management templates -->
<script class="jsrender-template" id="tmpl_ban_list" type="text/html">
<div class="banlist">
<div class="frame-container">
<div class="top-menu">
<div class="manage-buttons">
<button class="button-add">Add</button>
<button class="button-edit">Edit</button>
<button class="button-remove">Remove</button>
</div>
<div class="search">
<input type="text" class="entry-filter">
</div>
</div>
<div class="entry-container">
<div class="table-container">
<table>
<thead>
<tr>
<th class="field-properties">Name/IP/UID/HWID</th>
<th class="field-reason">Reason</th>
<th class="field-invoker">Creator</th>
<th class="field-timestamp">Created / Expires</th>
</tr>
</thead>
<tbody class="ban-entry-list">
<!--
<tr class="ban-entry">
<td class="field-properties">
<a class="property property-name">name=WolverasdasdinDEV</a>
<a class="property property-ip">ip=WolverasdasdinDEV</a>
<a class="property property-unique-id">uid=WolverasdasdinDEV</a>
<a class="property property-hardware-id">hwid=WolverasdasdinDEV</a>
</td>
<td class="field-reason">Insult</td>
<td class="field-invoker">WolverinDEV</td>
<td class="field-timestamp">1.1.2013 12:22 / 1.21.2013 12:22</td>
</tr>
<tr class="ban-entry">
<td class="field-properties">
<a class="property property-name">name=WolverasdasdinDEV</a>
<a class="property property-ip">ip=WolverasdasdinDEV</a>
<a class="property property-unique-id">uid=WolverasdasdinDEV</a>
<a class="property property-hardware-id">hwid=WolverasdasdinDEV</a>
</td>
<td class="field-reason">Insult</td>
<td class="field-invoker">WolverinDEV</td>
<td class="field-timestamp">
<a class="timestamp-created">Created: 1.1.2013 12:22</a>
<a class="timestamp-expires">Expires: 1.21.2013 12:22</a>
</td>
</tr>
-->
</tbody>
</table>
</div>
</div>
<div class="bottom-menu">
<div class="left">
<button class="button-refresh">Reload</button>
<div><input type="checkbox" class="filter-flag-force-own"> Show only own bans</div>
<div><input type="checkbox" class="filter-flag-highlight-own"> Highlight own bans</div>
</div>
<div class="right">
<a class="entry-count-info"></a>
<button class="button-close">Close</button>
</div>
</div>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_client_ban" type="text/html">
<div class="align_column">
<div class="align_column" style="margin: 5px">
<a>Name:</a>
<input value="{{>client_name}}" readonly>
</div>
<div class="align_column" style="margin: 5px">
<a>Reason:</a>
<textarea style="height: 32px; resize: vertical; max-height: 150px; min-height: 32px" maxlength="512" class="ban_reason"></textarea>
</div>
<div class="align_row" style="margin: 5px; justify-content: space-between">
<a>Duration:</a>
<div class="align_row">
<input type="number" value="1" class="ban_duration" style="margin-right: 7px" min="1">
<select class="ban_duration_type">
<option value="sec">seconds</option>
<option value="min">minutes</option>
<option value="hours">hours</option>
<option value="days">days</option>
<option value="perm">permanent</option>
</select>
</div>
</div>
<div class="group_box container-ban-type">
<div class="header">Ban client by</div>
<div class="content ban-types">
<div>
<input type="checkbox" class="ban-type-nickname">
<a>Nickname</a>
<div class="help-tip tip-right tip-small" checked>
<p>
Bans the client by his current nickname.<br>
The currently nickname cant be used until the ban expired
</p>
</div>
</div>
<div>
<input type="checkbox" class="ban-type-hardware-id" checked>
<a>Hardware ID</a>
<div class="help-tip tip-right tip-small">
<p>
Bans the client by his hardware id.<br>
The hardware id has different meanings, depends on the users agent<br>
TeaClient: The hardware id will be equal to the mac address<br>
TeaWeb: The TeaSpeak web client hasn't a hardware id, it will be random<br>
TeamSpeak 3 client: The hardware id will be a result of some hashes from hardware specific properties
</p>
</div>
</div>
<div>
<input type="checkbox" class="ban-type-ip" checked>
<a>IP Address</a>
</div>
</div>
</div>
</div>
</script>
<script class="jsrender-template" id="tmpl_ban_entry" type="text/html">
<tr class="ban-entry {{if server_id == 0}}ban-entry-global{{/if}} {{if flag_own}}ban-entry-own{{/if}}" ban-id={{>banid}} server-id={{>server_id}}>
<td class="field-properties">
{{if name }}<a class="property property-name">name={{>name}}</a>{{/if}}
{{if ip }} <a class="property property-ip">ip={{>ip}}</a>{{/if}}
{{if unique_id }} <a class="property property-unique-id">uid={{>unique_id}}</a>{{/if}}
{{if hardware_id }}<a class="property property-hardware-id">hwid={{>hardware_id}}</a>{{/if}}
</td>
<td class="field-reason">{{>reason}}</td>
<td class="field-invoker">{{>invoker_name}}</td>
<td class="field-timestamp">
<a class="timestamp-created">Created: {{fmt_date timestamp_created "DD.MM.YYYY HH:mm" /}}</a>
<a class="timestamp-expire">Expire: {{if (!timestamp_expire || timestamp_expire.getTime() == 0) }}never{{else}}{{fmt_date timestamp_expire "DD.MM.YYYY HH:mm" /}}{{/if}}</a>
</td>
</tr>
</script>
<!-- `hwid` VARCHAR(" CLIENT_UID_LENGTH "), `uid` VARCHAR(" CLIENT_UID_LENGTH "), `name` VARCHAR(" CLIENT_NAME_LENGTH "), `ip` VARCHAR(128) -->
<script class="jsrender-template" id="tmpl_ban_create" type="text/html">
<div class="bancreate">
<div class="frame-container">
<div class="container container-ip">
<a>IP:</a>
<input class="input-ip" type="text" maxlength="128">
</div>
<div class="container container-name">
<a>Name:</a>
<input class="input-name" type="text" maxlength="128">
<div class="container-name-type">
<a>Interpret IP/Name as:</a>
<select class="input-name-type">
<option value="wildcard-4">Wildcard IPv4</option>
<option value="wildcard-6">Wildcard IPv6</option>
<option value="fixed" selected>Fixed string</option>
<option value="regex">Regular Expression</option>
</select>
</div>
</div>
<div class="container container-uid">
<a>Unique ID:</a>
<div><input class="input-uid" type="text" maxlength="64"></div>
</div>
<div class="container container-hwid">
<a>Hardware ID:</a>
<input class="input-hwid" type="text" max="64">
</div>
<div class="container container-reason">
<a>Reason</a>
<textarea class="input-reason"></textarea>
</div>
<div class="container container-time">
<a>Duration</a>
<div class="container-time-input">
<input type="number" value="1" class="input-time" style="margin-right: 7px" min="1">
<select class="input-time-unit">
<option value="sec">seconds</option>
<option value="min">minutes</option>
<option value="hours">hours</option>
<option value="days">days</option>
<option value="perm">permanent</option>
</select>
</div>
</div>
<div class="footer">
<div>
<div class="container-global">
<input type="checkbox" class="input-global">
<a>Use this ban as a global ban</a>
<div class="help-tip tip-center tip-small">
<p>
Global bans are bans which apply instance wide.<br>
This means that (if this rule apply to a victim) cant join <b>any</b> virtual server!<br>
Global bans are by default shown to every server admin group,<br>
but could only be created with query rights
</p>
</div>
</div>
</div>
<div class="container-buttons modal-button-group" style="text-align: right; margin-top: 3px; margin-bottom: 6px;">
<button class="button-close">Cancel</button>
<button class="button-success">OK</button>
</div>
</div>
</div>
</div>
</script>
<!-- Music interface --> <!-- Music interface -->
<script class="jsrender-template" id="tmpl_music_frame" type="text/html"> <script class="jsrender-template" id="tmpl_music_frame" type="text/html">

14380
vendor/moment/moment.js vendored Normal file

File diff suppressed because it is too large Load Diff