Implemented a music bot control panel
parent
7fbc84128e
commit
78410c9fdf
|
@ -1,4 +1,7 @@
|
||||||
# Changelog:
|
# Changelog:
|
||||||
|
* **22.02.20**
|
||||||
|
- Added a music bot control panel
|
||||||
|
|
||||||
* **16.02.20**
|
* **16.02.20**
|
||||||
- Updated the `setup_windows.md` tutorial
|
- Updated the `setup_windows.md` tutorial
|
||||||
- Correct redirecting to `index.php` when using the serve mode
|
- Correct redirecting to `index.php` when using the serve mode
|
||||||
|
|
4
file.ts
4
file.ts
|
@ -31,7 +31,7 @@ type ProjectResource = {
|
||||||
const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
||||||
{ /* shared html and php files */
|
{ /* shared html and php files */
|
||||||
"type": "html",
|
"type": "html",
|
||||||
"search-pattern": /^([a-zA-Z]+)\.(html|php|json)$/,
|
"search-pattern": /^.*([a-zA-Z]+)\.(html|php|json)$/,
|
||||||
"build-target": "dev|rel",
|
"build-target": "dev|rel",
|
||||||
|
|
||||||
"path": "./",
|
"path": "./",
|
||||||
|
@ -1151,7 +1151,7 @@ async function main(args: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* proxy log for better format */
|
/* proxy log for better format */
|
||||||
const wrap_log = (original, prefix: string) => (message, ...args) => original(prefix + message.replace(/\n/g, "\n" + prefix), ...args.map(e => typeof(e) === "string" ? e.replace(/\n/g, "\n" + prefix) : e));
|
const wrap_log = (original, prefix: string) => (message, ...args) => original(prefix + (message ? message + "" : "").replace(/\n/g, "\n" + prefix), ...args.map(e => typeof(e) === "string" ? e.replace(/\n/g, "\n" + prefix) : e));
|
||||||
console.log = wrap_log(console.log, "[INFO ] ");
|
console.log = wrap_log(console.log, "[INFO ] ");
|
||||||
console.debug = wrap_log(console.debug, "[DEBUG] ");
|
console.debug = wrap_log(console.debug, "[DEBUG] ");
|
||||||
console.warn = wrap_log(console.warn, "[WARNING] ");
|
console.warn = wrap_log(console.warn, "[WARNING] ");
|
||||||
|
|
|
@ -149,3 +149,32 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin tooltip($base_with, $icon_size) {
|
||||||
|
.container-tooltip {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
width: $base_with;
|
||||||
|
margin-left: .5em;
|
||||||
|
margin-right: .5em;
|
||||||
|
font-size: .9em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img, .icon_em {
|
||||||
|
height: $icon_size;
|
||||||
|
width: $icon_size;
|
||||||
|
|
||||||
|
align-self: center;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,697 @@
|
||||||
|
@import "mixin";
|
||||||
|
@import "properties";
|
||||||
|
|
||||||
|
.modal-body.modal-music-manage {
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
display: flex !important;;
|
||||||
|
flex-direction: column !important;;
|
||||||
|
justify-content: stretch !important;;
|
||||||
|
|
||||||
|
width: 80em;
|
||||||
|
min-height: 20em; /* Set it here, so we dont have a inner modal scroll */
|
||||||
|
|
||||||
|
@include user-select(none);
|
||||||
|
|
||||||
|
> .header {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
height: 4em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.category {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-align: center;
|
||||||
|
color: #e1e1e1;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid #4e4e4e;
|
||||||
|
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
margin-right: -10em;
|
||||||
|
margin-left: -10em;
|
||||||
|
margin-bottom: -.2em;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: calc(100% + 20em);
|
||||||
|
|
||||||
|
box-shadow: inset 0px -1.2em 3em -20px #424242;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid #0073d4;
|
||||||
|
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
margin-right: -10em;
|
||||||
|
margin-left: -10em;
|
||||||
|
margin-bottom: -.2em;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: calc(100% + 20em);
|
||||||
|
|
||||||
|
box-shadow: inset 0px -1.2em 3em -20px #0073d4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
min-height: 20em;
|
||||||
|
|
||||||
|
background-color: #303036;
|
||||||
|
|
||||||
|
@include tooltip(1.6em, 1em);
|
||||||
|
.container {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
min-height: 20em;
|
||||||
|
height: 40.5em;
|
||||||
|
|
||||||
|
.input-boxed, .btn {
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
$border_color: #1e2025;
|
||||||
|
&.category-permissions {
|
||||||
|
.column {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&.column-permission {
|
||||||
|
flex-shrink: 1000;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 6em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include text-dotdotdot();
|
||||||
|
}
|
||||||
|
|
||||||
|
.master {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slave {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.column-required {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include text-dotdotdot();
|
||||||
|
}
|
||||||
|
|
||||||
|
min-width: 6em;
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.column-client-specific {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
min-width: 20em;
|
||||||
|
width: 30em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-head {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.column {
|
||||||
|
height: 5.5em;
|
||||||
|
padding: .5em;
|
||||||
|
|
||||||
|
justify-content: flex-end!important;
|
||||||
|
|
||||||
|
&.column-permission, &.column-required {
|
||||||
|
color: #e1e1e1;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
border-right: 1px solid $border_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-client {
|
||||||
|
padding-top: .5em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
width: 5em;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-select, .client-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 1em;
|
||||||
|
|
||||||
|
@include text-dotdotdot();
|
||||||
|
|
||||||
|
&.button-search {
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button-list-clients, &.button-client-deselect {
|
||||||
|
width: 8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
height: 2em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
margin-top: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
@include text-dotdotdot();
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
min-width: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-info {
|
||||||
|
color: #e1e1e1;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
a {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htmltag-client {
|
||||||
|
color: #e1e1e1;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
@include text-dotdotdot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-body {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
min-height: 6em;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@include chat-scrollbar-vertical();
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
height: 2.6em; /* input box + 2 * .5em */
|
||||||
|
padding: .5em;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: unset;
|
||||||
|
|
||||||
|
&:nth-of-type(2n) {
|
||||||
|
background-color: #25252a;
|
||||||
|
}
|
||||||
|
|
||||||
|
border-top: 1px solid $border_color;
|
||||||
|
border-right: 1px solid $border_color;
|
||||||
|
|
||||||
|
.container-input {
|
||||||
|
color: #e1e1e1;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
min-width: 2em;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
height: 1.6em;
|
||||||
|
|
||||||
|
/* fix the column padding */
|
||||||
|
padding-left: 1em;
|
||||||
|
margin-left: -.5em; /* have a bit of space on both sides */
|
||||||
|
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-tooltip {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-bottom-color: #3f7dbf;
|
||||||
|
|
||||||
|
input {
|
||||||
|
color: #e1e1e1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include transition(border-bottom-color $button_hover_animation_time ease-in-out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-client-specific {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
border-right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-client-list {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
border-top: 1px solid $border_color;
|
||||||
|
background-color: #303036;
|
||||||
|
padding: .5em;
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
|
@include transition(all .25s ease-in-out);
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-client-list {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
border: 1px #161616 solid;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
background-color: #28292b;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
padding-top: 0.25em;
|
||||||
|
padding-bottom: 0.25em;
|
||||||
|
|
||||||
|
margin-top: .5em;
|
||||||
|
margin-bottom: .5em;
|
||||||
|
|
||||||
|
@include chat-scrollbar-vertical();
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: #28292b;
|
||||||
|
color: #676468;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.3em;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.client {
|
||||||
|
padding-left: .5em;
|
||||||
|
padding-right: .5em;
|
||||||
|
|
||||||
|
.htmltag-client {
|
||||||
|
@include text-dotdotdot();
|
||||||
|
color: #999;
|
||||||
|
font-weight: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #2c2d2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-buttons {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-buttons {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
margin-top: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.category-settings {
|
||||||
|
.container-settings {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-height: 10em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
width: 50%;
|
||||||
|
min-width: 15em;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
padding: .5em;
|
||||||
|
|
||||||
|
&.settings-bot {
|
||||||
|
border-right: 1px solid $border_color;
|
||||||
|
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.settings-playlist {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
color: #e1e1e1;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
> label, > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
margin-top: .5em;
|
||||||
|
|
||||||
|
* {
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include text-dotdotdot();
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-name-country {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
.option-bot-name {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
min-width: 6em;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-country {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
width: 5em;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.country {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
margin: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
margin-right: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-replay-mode, .container-max-playlist-size {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
a {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
select, .input-boxed {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: stretch;
|
||||||
|
|
||||||
|
width: 10em;
|
||||||
|
|
||||||
|
margin-left: .5em;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-buttons {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-music-permission-overview {
|
||||||
|
padding-left: .25em;
|
||||||
|
padding-right: .25em;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
max-height: 8em;
|
||||||
|
|
||||||
|
.container-title {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-groups {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@include chat-scrollbar-horizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-status {
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,574 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Template Music manage</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script class="jsrender-template" id="tmpl_music_manage" type="text/html">
|
||||||
|
<div>
|
||||||
|
<div class="header">
|
||||||
|
<div class="category category-settings">
|
||||||
|
<a>{{tr "Settings" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="category category-permissions">
|
||||||
|
<a>{{tr "Permissions" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="container category-settings">
|
||||||
|
<div class="container-settings">
|
||||||
|
<div class="settings settings-bot">
|
||||||
|
<a>{{tr "Music Bot" /}}</a>
|
||||||
|
<div class="body">
|
||||||
|
<div class="container-name-country">
|
||||||
|
<input class="input-boxed option-bot-name" placeholder="Bot name" max="30">
|
||||||
|
<div class="input-boxed container-country">
|
||||||
|
<input class="option-bot-country" maxlength="2">
|
||||||
|
<div class="country flag-de"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" class="option-channel-commander">
|
||||||
|
<div class="mark"></div>
|
||||||
|
</div>
|
||||||
|
<a>{{tr "Music bot is channel commander" /}}</a>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" class="option-priority-speaker">
|
||||||
|
<div class="mark"></div>
|
||||||
|
</div>
|
||||||
|
<a>{{tr "Music bot is priority speaker" /}}</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings settings-playlist">
|
||||||
|
<a>{{tr "Replay" /}}</a>
|
||||||
|
<div class="body">
|
||||||
|
<div class="container-replay-mode">
|
||||||
|
<a>{{tr "Replay mode" /}}</a>
|
||||||
|
<select class="input-boxed option-replay-mode">
|
||||||
|
<option value="-1" style="display: none;">{{tr "Unknown" /}}</option>
|
||||||
|
<option value="0">{{tr "Normal" /}}</option>
|
||||||
|
<option value="1">{{tr "Looped" /}}</option>
|
||||||
|
<option value="2">{{tr "Single-Looped" /}}</option>
|
||||||
|
<option value="3">{{tr "Shuffle" /}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="container-max-playlist-size">
|
||||||
|
<a>{{tr "Max playlist size" /}}</a>
|
||||||
|
<div class="input-boxed">
|
||||||
|
<input type="number" min="0" value="60" maxlength="6">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>You could set this value up to X.</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label>
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" class="option-delete-played-songs">
|
||||||
|
<div class="mark"></div>
|
||||||
|
</div>
|
||||||
|
<a>{{tr "Delete played songs from playlist" /}}</a>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" class="option-notify-songs-change">
|
||||||
|
<div class="mark"></div>
|
||||||
|
</div>
|
||||||
|
<a>{{tr "Send a channel message when the song changes" /}}</a>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-buttons">
|
||||||
|
<button class="btn btn-blue button-reload">{{tr "Reload" /}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container category-permissions">
|
||||||
|
<div class="table-head">
|
||||||
|
<div class="column column-permission">
|
||||||
|
<a>{{tr "Permission" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="column column-required">
|
||||||
|
<a>{{tr "Required value" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="column column-client-specific">
|
||||||
|
<div class="client-select">
|
||||||
|
<div class="row">
|
||||||
|
<a>{{tr "Search for an client:" /}}</a>
|
||||||
|
<button class="btn btn-blue button-list-clients">error: client list</button>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" class="input-boxed input-search" placeholder="error: placeholder" maxlength="40">
|
||||||
|
<button class="btn btn-green button-search">{{tr "Search" /}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="client-info hidden">
|
||||||
|
<div class="row">
|
||||||
|
<a></a>
|
||||||
|
<button class="btn btn-blue button-client-deselect">{{tr "Unselect" /}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="row container-selected-client">
|
||||||
|
<a>{{tr "Showing permissions for:" /}}</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-body">
|
||||||
|
<div class="column column-permission">
|
||||||
|
<div class="entry master">
|
||||||
|
<a>{{tr "Bot permissions" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power to rename the music bot" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power to modify the music bot permissions" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power to delete the music bot" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry master">
|
||||||
|
<a>{{tr "Playlist permissions" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power to see the playlist songs" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power for editing playlist settings" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power for viewing playlist permissions" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power for editing playlist permissions" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power for adding a song to the playlist" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power to reorder a song within the playlist" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="entry slave">
|
||||||
|
<a>{{tr "Power to delete a song from the playlist" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column column-required">
|
||||||
|
<div class="entry"></div> <!-- bot permissions -->
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_client_music_needed_rename_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could rename the bot:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_client_music_needed_modify_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could modify the bot:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}<br>
|
||||||
|
b_virtualserver_permission_find
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_client_music_needed_delete_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could delete the bot:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry"></div> <!-- play permissions -->
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_playlist_needed_view_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could view the playlist:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_playlist_needed_modify_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could edit the playlist settings:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="b_virtualserver_playlist_permission_list">
|
||||||
|
<div style="flex-grow: 1; flex-shrink: 1; min-width: 0"></div>
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could view the playlist permissions:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_playlist_needed_permission_modify_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could edit the playlist permissions:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_playlist_song_needed_add_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could add a song:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_playlist_song_needed_move_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could reorder the songs:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input general-permission" x-permission="i_playlist_song_needed_remove_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<img src="img/icon_tooltip_music_permission.svg">
|
||||||
|
<div class="tooltip">
|
||||||
|
<a class="tooltip-music-permission-overview">
|
||||||
|
<div class="container-title">{{tr "These groups could delete a song:" /}}</div>
|
||||||
|
<div class="container-status status-no-groups">
|
||||||
|
{{tr "No group could do that." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-loading">
|
||||||
|
{{tr "loading..." /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error-permission">
|
||||||
|
{{tr "failed on permission" /}}
|
||||||
|
</div>
|
||||||
|
<div class="container-status status-error">
|
||||||
|
error: error
|
||||||
|
</div>
|
||||||
|
<div class="container-groups"> </div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column column-client-specific">
|
||||||
|
<div class="entry"></div> <!-- bot permissions -->
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_client_music_rename_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-apply"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_client_music_modify_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_client_music_delete_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry"></div> <!-- play permissions -->
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_playlist_view_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_playlist_modify_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="b_virtualserver_playlist_permission_list">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could no see the permissions" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_playlist_permission_modify_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_playlist_song_add_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_playlist_song_move_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="entry">
|
||||||
|
<div class="container-input client-permission" x-permission="i_playlist_song_remove_power">
|
||||||
|
<input type="number" min="0" value="60">
|
||||||
|
<div class="container-tooltip">
|
||||||
|
<div class="icon_em client-delete"></div>
|
||||||
|
<div class="tooltip">
|
||||||
|
<a>{{tr "Client could perform this action" /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overlay-client-list">
|
||||||
|
<div class="title">{{tr "Clients which have special permissions:" /}}</div>
|
||||||
|
<div class="container-client-list">
|
||||||
|
<div class="overlay overlay-filter-no-result">
|
||||||
|
<a>{{tr "No clients match the filter" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="overlay overlay-empty-list">
|
||||||
|
<a>{{tr "No clients available" /}}</a>
|
||||||
|
</div>
|
||||||
|
<div class="overlay overlay-query-error">
|
||||||
|
<a>error: error text</a>
|
||||||
|
</div>
|
||||||
|
<div class="overlay overlay-query-error-permissions">
|
||||||
|
<a>error: permission error text</a>
|
||||||
|
</div>
|
||||||
|
<div class="overlay overlay-refreshing">
|
||||||
|
<a>{{tr "Loading..." /}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-buttons">
|
||||||
|
<button class="btn btn-blue button-clientlist-refresh">{{tr "Refresh client list" /}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container-buttons">
|
||||||
|
<button class="btn btn-blue button-permission-refresh">{{tr "Refresh" /}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 400 400">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #999;
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path id="icon" class="cls-1" d="M200.012,0c110.457,0,200,89.545,200,200s-89.54,200-200,200S0.015,310.456.015,200,89.557,0,200.012,0Zm0.01,38.636A160.629,160.629,0,1,1,39.395,199.268,160.632,160.632,0,0,1,200.022,38.639ZM159.407,153.478s42.867-5.468,19.8,68.014c-24.208,77.113-66.566,140.45,63.467,81.447,0,0-64.556,21.6-42.619-37.407,12.79-34.406,28.292-81.421,27.092-93.4C225.387,154.546,207.6,141.025,159.407,153.478Zm65.564-79a24.685,24.685,0,1,1-24.687,24.684A24.685,24.685,0,0,1,224.971,74.479Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 757 B |
|
@ -704,9 +704,12 @@ class ConnectionHandler {
|
||||||
if(vconnection && vconnection.voice_recorder() && vconnection.voice_recorder().record_supported) {
|
if(vconnection && vconnection.voice_recorder() && vconnection.voice_recorder().record_supported) {
|
||||||
const active = !this.client_status.input_muted && !this.client_status.output_muted;
|
const active = !this.client_status.input_muted && !this.client_status.output_muted;
|
||||||
/* No need to start the microphone when we're not even connected */
|
/* No need to start the microphone when we're not even connected */
|
||||||
|
|
||||||
|
const input = vconnection.voice_recorder().input;
|
||||||
|
if(input) {
|
||||||
if(active && this.serverConnection.connected()) {
|
if(active && this.serverConnection.connected()) {
|
||||||
if(vconnection.voice_recorder().input.current_state() === audio.recorder.InputState.PAUSED) {
|
if(input.current_state() === audio.recorder.InputState.PAUSED) {
|
||||||
vconnection.voice_recorder().input.start().then(result => {
|
input.start().then(result => {
|
||||||
if(result != audio.recorder.InputStartResult.EOK)
|
if(result != audio.recorder.InputStartResult.EOK)
|
||||||
throw result;
|
throw result;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
|
@ -718,7 +721,8 @@ class ConnectionHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vconnection.voice_recorder().input.stop();
|
input.stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ namespace connection {
|
||||||
export class CommandHelper extends AbstractCommandHandler {
|
export class CommandHelper extends AbstractCommandHandler {
|
||||||
private _who_am_i: any;
|
private _who_am_i: any;
|
||||||
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
|
private _awaiters_unique_ids: {[unique_id: string]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||||
|
private _awaiters_unique_dbid: {[database_id: number]:((resolved: ClientNameInfo) => any)[]} = {};
|
||||||
|
|
||||||
constructor(connection) {
|
constructor(connection) {
|
||||||
super(connection);
|
super(connection);
|
||||||
|
@ -25,6 +26,8 @@ namespace connection {
|
||||||
handle_command(command: connection.ServerCommand): boolean {
|
handle_command(command: connection.ServerCommand): boolean {
|
||||||
if(command.command == "notifyclientnamefromuid")
|
if(command.command == "notifyclientnamefromuid")
|
||||||
this.handle_notifyclientnamefromuid(command.arguments);
|
this.handle_notifyclientnamefromuid(command.arguments);
|
||||||
|
if(command.command == "notifyclientgetnamefromdbid")
|
||||||
|
this.handle_notifyclientgetnamefromdbid(command.arguments);
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
@ -57,6 +60,8 @@ namespace connection {
|
||||||
const response: ClientNameInfo[] = [];
|
const response: ClientNameInfo[] = [];
|
||||||
const request = [];
|
const request = [];
|
||||||
const unique_ids = new Set(_unique_ids);
|
const unique_ids = new Set(_unique_ids);
|
||||||
|
if(!unique_ids.size) return [];
|
||||||
|
|
||||||
const unique_id_resolvers: {[unique_id: string]: (resolved: ClientNameInfo) => any} = {};
|
const unique_id_resolvers: {[unique_id: string]: (resolved: ClientNameInfo) => any} = {};
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,6 +88,54 @@ namespace connection {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handle_notifyclientgetnamefromdbid(json: any[]) {
|
||||||
|
for(const entry of json) {
|
||||||
|
const info: ClientNameInfo = {
|
||||||
|
client_unique_id: entry["cluid"],
|
||||||
|
client_nickname: entry["clname"],
|
||||||
|
client_database_id: parseInt(entry["cldbid"])
|
||||||
|
};
|
||||||
|
|
||||||
|
const functions = this._awaiters_unique_dbid[info.client_database_id] || [];
|
||||||
|
delete this._awaiters_unique_dbid[info.client_database_id];
|
||||||
|
|
||||||
|
for(const fn of functions)
|
||||||
|
fn(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async info_from_cldbid(..._cldbid: number[]) : Promise<ClientNameInfo[]> {
|
||||||
|
const response: ClientNameInfo[] = [];
|
||||||
|
const request = [];
|
||||||
|
const unique_cldbid = new Set(_cldbid);
|
||||||
|
if(!unique_cldbid.size) return [];
|
||||||
|
|
||||||
|
const unique_cldbid_resolvers: {[dbid: number]: (resolved: ClientNameInfo) => any} = {};
|
||||||
|
|
||||||
|
|
||||||
|
for(const cldbid of unique_cldbid) {
|
||||||
|
request.push({'cldbid': cldbid});
|
||||||
|
(this._awaiters_unique_dbid[cldbid] || (this._awaiters_unique_dbid[cldbid] = []))
|
||||||
|
.push(unique_cldbid_resolvers[cldbid] = info => response.push(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.connection.send_command("clientgetnamefromdbid", request);
|
||||||
|
} catch(error) {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||||
|
/* nothing */
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
/* cleanup */
|
||||||
|
for(const cldbid of Object.keys(unique_cldbid_resolvers))
|
||||||
|
(this._awaiters_unique_dbid[cldbid] || []).remove(unique_cldbid_resolvers[cldbid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private handle_notifyclientnamefromuid(json: any[]) {
|
private handle_notifyclientnamefromuid(json: any[]) {
|
||||||
for(const entry of json) {
|
for(const entry of json) {
|
||||||
const info: ClientNameInfo = {
|
const info: ClientNameInfo = {
|
||||||
|
@ -245,6 +298,40 @@ namespace connection {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request_playlist_client_list(playlist_id: number) : Promise<number[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const single_handler: SingleCommandHandler = {
|
||||||
|
command: "notifyplaylistclientlist",
|
||||||
|
function: command => {
|
||||||
|
const json = command.arguments;
|
||||||
|
|
||||||
|
if(json[0]["playlist_id"] != playlist_id) {
|
||||||
|
log.error(LogCategory.NETWORKING, tr("Received invalid notification for playlist clients"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: number[] = [];
|
||||||
|
|
||||||
|
for(const entry of json)
|
||||||
|
result.push(parseInt(entry["cldbid"]));
|
||||||
|
|
||||||
|
resolve(result.filter(e => !isNaN(e)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
|
|
||||||
|
this.connection.send_command("playlistclientlist", {playlist_id: playlist_id}).catch(error => {
|
||||||
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||||
|
resolve([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async request_clients_by_server_group(group_id: number) : Promise<ServerGroupClient[]> {
|
async request_clients_by_server_group(group_id: number) : Promise<ServerGroupClient[]> {
|
||||||
//servergroupclientlist sgid=2
|
//servergroupclientlist sgid=2
|
||||||
//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
|
//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
|
||||||
|
@ -309,6 +396,8 @@ namespace connection {
|
||||||
playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
|
playlist_flag_finished: json["playlist_flag_finished"] == true || json["playlist_flag_finished"] == "1",
|
||||||
playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
|
playlist_replay_mode: parseInt(json["playlist_replay_mode"]),
|
||||||
playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
|
playlist_current_song_id: parseInt(json["playlist_current_song_id"]),
|
||||||
|
|
||||||
|
playlist_max_songs: parseInt(json["playlist_max_songs"])
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
|
log.error(LogCategory.NETWORKING, tr("Failed to parse playlist info: %o"), error);
|
||||||
|
|
|
@ -96,6 +96,8 @@ interface PlaylistInfo {
|
||||||
playlist_flag_finished: boolean,
|
playlist_flag_finished: boolean,
|
||||||
playlist_replay_mode: number,
|
playlist_replay_mode: number,
|
||||||
playlist_current_song_id: number,
|
playlist_current_song_id: number,
|
||||||
|
|
||||||
|
playlist_max_songs: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlaylistSong {
|
interface PlaylistSong {
|
||||||
|
|
|
@ -160,6 +160,160 @@ namespace events {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace modal {
|
||||||
|
export type BotStatusType = "name" | "description" | "volume" | "country_code" | "channel_commander" | "priority_speaker";
|
||||||
|
export type PlaylistStatusType = "replay_mode" | "finished" | "delete_played" | "max_size" | "notify_song_change";
|
||||||
|
export interface music_manage {
|
||||||
|
show_container: { container: "settings" | "permissions"; };
|
||||||
|
|
||||||
|
/* setting relevant */
|
||||||
|
query_bot_status: {},
|
||||||
|
bot_status: {
|
||||||
|
status: "success" | "error";
|
||||||
|
error_msg?: string;
|
||||||
|
data?: {
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
volume: number,
|
||||||
|
|
||||||
|
country_code: string,
|
||||||
|
default_country_code: string,
|
||||||
|
|
||||||
|
channel_commander: boolean,
|
||||||
|
priority_speaker: boolean,
|
||||||
|
|
||||||
|
client_version: string,
|
||||||
|
client_platform: string,
|
||||||
|
|
||||||
|
uptime_mode: number,
|
||||||
|
bot_type: number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set_bot_status: {
|
||||||
|
key: BotStatusType,
|
||||||
|
value: any
|
||||||
|
},
|
||||||
|
set_bot_status_result: {
|
||||||
|
key: BotStatusType,
|
||||||
|
status: "success" | "error" | "timeout",
|
||||||
|
error_msg?: string,
|
||||||
|
value?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
query_playlist_status: {},
|
||||||
|
playlist_status: {
|
||||||
|
status: "success" | "error",
|
||||||
|
error_msg?: string,
|
||||||
|
data?: {
|
||||||
|
replay_mode: number,
|
||||||
|
finished: boolean,
|
||||||
|
delete_played: boolean,
|
||||||
|
max_size: number,
|
||||||
|
notify_song_change: boolean
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set_playlist_status: {
|
||||||
|
key: PlaylistStatusType,
|
||||||
|
value: any
|
||||||
|
},
|
||||||
|
set_playlist_status_result: {
|
||||||
|
key: PlaylistStatusType,
|
||||||
|
status: "success" | "error" | "timeout",
|
||||||
|
error_msg?: string,
|
||||||
|
value?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/* permission relevant */
|
||||||
|
show_client_list: {},
|
||||||
|
hide_client_list: {},
|
||||||
|
|
||||||
|
filter_client_list: { filter: string | undefined },
|
||||||
|
|
||||||
|
"refresh_permissions": {},
|
||||||
|
|
||||||
|
query_special_clients: {},
|
||||||
|
special_client_list: {
|
||||||
|
status: "success" | "error" | "error-permission",
|
||||||
|
error_msg?: string,
|
||||||
|
clients?: {
|
||||||
|
name: string,
|
||||||
|
unique_id: string,
|
||||||
|
database_id: number
|
||||||
|
}[]
|
||||||
|
},
|
||||||
|
|
||||||
|
search_client: { text: string },
|
||||||
|
search_client_result: {
|
||||||
|
status: "error" | "timeout" | "empty" | "success",
|
||||||
|
error_msg?: string,
|
||||||
|
client?: {
|
||||||
|
name: string,
|
||||||
|
unique_id: string,
|
||||||
|
database_id: number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* sets a client to set the permission for */
|
||||||
|
special_client_set: {
|
||||||
|
client?: {
|
||||||
|
name: string,
|
||||||
|
unique_id: string,
|
||||||
|
database_id: number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"query_general_permissions": {},
|
||||||
|
"general_permissions": {
|
||||||
|
status: "error" | "timeout" | "success",
|
||||||
|
error_msg?: string,
|
||||||
|
permissions?: {[key: string]:number}
|
||||||
|
},
|
||||||
|
"set_general_permission_result": {
|
||||||
|
status: "error" | "success",
|
||||||
|
key: string,
|
||||||
|
value?: number,
|
||||||
|
error_msg?: string
|
||||||
|
},
|
||||||
|
"set_general_permission": { /* try to change a permission for the server */
|
||||||
|
key: string,
|
||||||
|
value: number
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
"query_client_permissions": { client_database_id: number },
|
||||||
|
"client_permissions": {
|
||||||
|
status: "error" | "timeout" | "success",
|
||||||
|
client_database_id: number,
|
||||||
|
error_msg?: string,
|
||||||
|
permissions?: {[key: string]:number}
|
||||||
|
},
|
||||||
|
"set_client_permission_result": {
|
||||||
|
status: "error" | "success",
|
||||||
|
client_database_id: number,
|
||||||
|
key: string,
|
||||||
|
value?: number,
|
||||||
|
error_msg?: string
|
||||||
|
},
|
||||||
|
"set_client_permission": { /* try to change a permission for the server */
|
||||||
|
client_database_id: number,
|
||||||
|
key: string,
|
||||||
|
value: number
|
||||||
|
},
|
||||||
|
|
||||||
|
"query_group_permissions": { permission_name: string },
|
||||||
|
"group_permissions": {
|
||||||
|
permission_name: string;
|
||||||
|
status: "error" | "timeout" | "success"
|
||||||
|
groups?: {
|
||||||
|
name: string,
|
||||||
|
value: number,
|
||||||
|
id: number
|
||||||
|
}[],
|
||||||
|
error_msg?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const eclient = new events.Registry<events.channel_tree.client>();
|
const eclient = new events.Registry<events.channel_tree.client>();
|
||||||
|
|
|
@ -1492,7 +1492,7 @@ namespace i18n {
|
||||||
alpha_3: "ZWE",
|
alpha_3: "ZWE",
|
||||||
un_code: 716
|
un_code: 716
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export function country_name(alpha_code: string, fallback?: string) {
|
export function country_name(alpha_code: string, fallback?: string) {
|
||||||
return (alpha_2_map[alpha_code.toUpperCase()] || {name: fallback || tr("unknown country")}).name;
|
return (alpha_2_map[alpha_code.toUpperCase()] || {name: fallback || tr("unknown country")}).name;
|
||||||
|
|
|
@ -416,20 +416,68 @@ class NeededPermissionValue extends PermissionValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelPermissionRequest {
|
namespace permissions {
|
||||||
requested: number;
|
export type PermissionRequestKeys = {
|
||||||
channel_id: number;
|
|
||||||
callback_success: ((_: PermissionValue[]) => any)[] = [];
|
|
||||||
callback_error: ((_: any) => any)[] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
class TeaPermissionRequest {
|
|
||||||
client_id?: number;
|
client_id?: number;
|
||||||
channel_id?: number;
|
channel_id?: number;
|
||||||
playlist_id?: number;
|
playlist_id?: number;
|
||||||
promise: LaterPromise<PermissionValue[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PermissionRequest = PermissionRequestKeys & {
|
||||||
|
timeout_id: any;
|
||||||
|
promise: LaterPromise<PermissionValue[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export namespace find {
|
||||||
|
export type Entry = {
|
||||||
|
type: "server" | "channel" | "client" | "client_channel" | "channel_group" | "server_group";
|
||||||
|
value: number;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Client = Entry & {
|
||||||
|
type: "client",
|
||||||
|
|
||||||
|
client_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Channel = Entry & {
|
||||||
|
type: "channel",
|
||||||
|
|
||||||
|
channel_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Server = Entry & {
|
||||||
|
type: "server"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClientChannel = Entry & {
|
||||||
|
type: "client_channel",
|
||||||
|
|
||||||
|
client_id: number;
|
||||||
|
channel_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChannelGroup = Entry & {
|
||||||
|
type: "channel_group",
|
||||||
|
|
||||||
|
group_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServerGroup = Entry & {
|
||||||
|
type: "server_group",
|
||||||
|
|
||||||
|
group_id: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestLists =
|
||||||
|
"requests_channel_permissions" |
|
||||||
|
"requests_client_permissions" |
|
||||||
|
"requests_client_channel_permissions" |
|
||||||
|
"requests_playlist_permissions" |
|
||||||
|
"requests_playlist_client_permissions";
|
||||||
class PermissionManager extends connection.AbstractCommandHandler {
|
class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
readonly handle: ConnectionHandler;
|
readonly handle: ConnectionHandler;
|
||||||
|
|
||||||
|
@ -439,44 +487,50 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
|
|
||||||
needed_permission_change_listener: {[permission: string]:(() => any)[]} = {};
|
needed_permission_change_listener: {[permission: string]:(() => any)[]} = {};
|
||||||
|
|
||||||
requests_channel_permissions: ChannelPermissionRequest[] = [];
|
requests_channel_permissions: permissions.PermissionRequest[] = [];
|
||||||
requests_client_permissions: TeaPermissionRequest[] = [];
|
requests_client_permissions: permissions.PermissionRequest[] = [];
|
||||||
requests_client_channel_permissions: TeaPermissionRequest[] = [];
|
requests_client_channel_permissions: permissions.PermissionRequest[] = [];
|
||||||
requests_playlist_permissions: TeaPermissionRequest[] = [];
|
requests_playlist_permissions: permissions.PermissionRequest[] = [];
|
||||||
|
requests_playlist_client_permissions: permissions.PermissionRequest[] = [];
|
||||||
|
|
||||||
|
requests_permfind: {
|
||||||
|
timeout_id: number,
|
||||||
|
permission: string,
|
||||||
|
callback: (status: "success" | "error", data: any) => void
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
initializedListener: ((initialized: boolean) => void)[] = [];
|
initializedListener: ((initialized: boolean) => void)[] = [];
|
||||||
private _cacheNeededPermissions: any;
|
private _cacheNeededPermissions: any;
|
||||||
|
|
||||||
/* Static info mapping until TeaSpeak implements a detailed info */
|
/* Static info mapping until TeaSpeak implements a detailed info */
|
||||||
//TODO tr
|
|
||||||
static readonly group_mapping: {name: string, deep: number}[] = [
|
static readonly group_mapping: {name: string, deep: number}[] = [
|
||||||
{name: "Global", deep: 0},
|
{name: tr("Global"), deep: 0},
|
||||||
{name: "Information", deep: 1},
|
{name: tr("Information"), deep: 1},
|
||||||
{name: "Virtual server management", deep: 1},
|
{name: tr("Virtual server management"), deep: 1},
|
||||||
{name: "Administration", deep: 1},
|
{name: tr("Administration"), deep: 1},
|
||||||
{name: "Settings", deep: 1},
|
{name: tr("Settings"), deep: 1},
|
||||||
{name: "Virtual Server", deep: 0},
|
{name: tr("Virtual Server"), deep: 0},
|
||||||
{name: "Information", deep: 1},
|
{name: tr("Information"), deep: 1},
|
||||||
{name: "Administration", deep: 1},
|
{name: tr("Administration"), deep: 1},
|
||||||
{name: "Settings", deep: 1},
|
{name: tr("Settings"), deep: 1},
|
||||||
{name: "Channel", deep: 0},
|
{name: tr("Channel"), deep: 0},
|
||||||
{name: "Information", deep: 1},
|
{name: tr("Information"), deep: 1},
|
||||||
{name: "Create", deep: 1},
|
{name: tr("Create"), deep: 1},
|
||||||
{name: "Modify", deep: 1},
|
{name: tr("Modify"), deep: 1},
|
||||||
{name: "Delete", deep: 1},
|
{name: tr("Delete"), deep: 1},
|
||||||
{name: "Access", deep: 1},
|
{name: tr("Access"), deep: 1},
|
||||||
{name: "Group", deep: 0},
|
{name: tr("Group"), deep: 0},
|
||||||
{name: "Information", deep: 1},
|
{name: tr("Information"), deep: 1},
|
||||||
{name: "Create", deep: 1},
|
{name: tr("Create"), deep: 1},
|
||||||
{name: "Modify", deep: 1},
|
{name: tr("Modify"), deep: 1},
|
||||||
{name: "Delete", deep: 1},
|
{name: tr("Delete"), deep: 1},
|
||||||
{name: "Client", deep: 0},
|
{name: tr("Client"), deep: 0},
|
||||||
{name: "Information", deep: 1},
|
{name: tr("Information"), deep: 1},
|
||||||
{name: "Admin", deep: 1},
|
{name: tr("Admin"), deep: 1},
|
||||||
{name: "Basics", deep: 1},
|
{name: tr("Basics"), deep: 1},
|
||||||
{name: "Modify", deep: 1},
|
{name: tr("Modify"), deep: 1},
|
||||||
//TODO Music bot
|
//TODO Music bot
|
||||||
{name: "File Transfer", deep: 0},
|
{name: tr("File Transfer"), deep: 0},
|
||||||
];
|
];
|
||||||
private _group_mapping;
|
private _group_mapping;
|
||||||
|
|
||||||
|
@ -539,10 +593,10 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
|
|
||||||
this.neededPermissions = undefined;
|
this.neededPermissions = undefined;
|
||||||
|
|
||||||
this.requests_channel_permissions = undefined;
|
/* delete all requests */
|
||||||
this.requests_client_permissions = undefined;
|
for(const key of Object.keys(this))
|
||||||
this.requests_client_channel_permissions = undefined;
|
if(key.startsWith("requests"))
|
||||||
this.requests_playlist_permissions = undefined;
|
delete this[key];
|
||||||
|
|
||||||
this.initializedListener = undefined;
|
this.initializedListener = undefined;
|
||||||
this._cacheNeededPermissions = undefined;
|
this._cacheNeededPermissions = undefined;
|
||||||
|
@ -568,6 +622,9 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
case "notifyplaylistpermlist":
|
case "notifyplaylistpermlist":
|
||||||
this.onPlaylistPermList(command.arguments);
|
this.onPlaylistPermList(command.arguments);
|
||||||
return true;
|
return true;
|
||||||
|
case "notifyplaylistclientpermlist":
|
||||||
|
this.onPlaylistClientPermList(command.arguments);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -708,172 +765,252 @@ class PermissionManager extends connection.AbstractCommandHandler {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
|
/* channel permission request */
|
||||||
return new Promise<PermissionValue[]>((resolve, reject) => {
|
|
||||||
let request: ChannelPermissionRequest;
|
|
||||||
for(let element of this.requests_channel_permissions)
|
|
||||||
if(element.requested + 1000 < Date.now() && element.channel_id == channelId) {
|
|
||||||
request = element;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(!request) {
|
|
||||||
request = new ChannelPermissionRequest();
|
|
||||||
request.requested = Date.now();
|
|
||||||
request.channel_id = channelId;
|
|
||||||
this.handle.serverConnection.send_command("channelpermlist", {"cid": channelId}).catch(error => {
|
|
||||||
this.requests_channel_permissions.remove(request);
|
|
||||||
|
|
||||||
if(error instanceof CommandResult) {
|
|
||||||
if(error.id == ErrorID.EMPTY_RESULT) {
|
|
||||||
request.callback_success.forEach(e => e([]));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request.callback_error.forEach(e => e(error));
|
|
||||||
}).then(() => {
|
|
||||||
//Error handler if we've not received an notify
|
|
||||||
setTimeout(() => {
|
|
||||||
if(this.requests_channel_permissions.remove(request)) {
|
|
||||||
request.callback_error.forEach(e => e(tr("missing notify")));
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
this.requests_channel_permissions.push(request);
|
|
||||||
}
|
|
||||||
request.callback_error.push(reject);
|
|
||||||
request.callback_success.push(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private onChannelPermList(json) {
|
private onChannelPermList(json) {
|
||||||
let channelId: number = parseInt(json[0]["cid"]);
|
let channelId: number = parseInt(json[0]["cid"]);
|
||||||
|
|
||||||
let permissions = PermissionManager.parse_permission_bulk(json, this.handle.permissions);
|
this.fullfill_permission_request("requests_channel_permissions", {
|
||||||
log.debug(LogCategory.PERMISSIONS, tr("Got channel permissions for channel %o"), channelId);
|
channel_id: channelId
|
||||||
for(let element of this.requests_channel_permissions) {
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
if(element.channel_id == channelId) {
|
|
||||||
for(let l of element.callback_success)
|
|
||||||
l(permissions);
|
|
||||||
this.requests_channel_permissions.remove(element);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private execute_channel_permission_request(request: permissions.PermissionRequestKeys) {
|
||||||
|
this.handle.serverConnection.send_command("channelpermlist", {"cid": request.channel_id}).catch(error => {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
|
this.fullfill_permission_request("requests_channel_permissions", request, "success", []);
|
||||||
|
else
|
||||||
|
this.fullfill_permission_request("requests_channel_permissions", request, "error", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
log.debug(LogCategory.PERMISSIONS, tr("Missing channel permission handle for requested channel id %o"), channelId);
|
|
||||||
|
requestChannelPermissions(channelId: number) : Promise<PermissionValue[]> {
|
||||||
|
const keys: permissions.PermissionRequestKeys = {
|
||||||
|
channel_id: channelId
|
||||||
|
};
|
||||||
|
return this.execute_permission_request("requests_channel_permissions", keys, this.execute_channel_permission_request.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* client permission request */
|
||||||
|
private onClientPermList(json: any[]) {
|
||||||
|
let client = parseInt(json[0]["cldbid"]);
|
||||||
|
this.fullfill_permission_request("requests_client_permissions", {
|
||||||
|
client_id: client
|
||||||
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
|
}
|
||||||
|
|
||||||
|
private execute_client_permission_request(request: permissions.PermissionRequestKeys) {
|
||||||
|
this.handle.serverConnection.send_command("clientpermlist", {cldbid: request.client_id}).catch(error => {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
|
this.fullfill_permission_request("requests_client_permissions", request, "success", []);
|
||||||
|
else
|
||||||
|
this.fullfill_permission_request("requests_client_permissions", request, "error", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
|
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
|
||||||
for(let request of this.requests_client_permissions)
|
const keys: permissions.PermissionRequestKeys = {
|
||||||
if(request.client_id == client_id && request.promise.time() + 1000 > Date.now())
|
client_id: client_id
|
||||||
return request.promise;
|
};
|
||||||
|
return this.execute_permission_request("requests_client_permissions", keys, this.execute_client_permission_request.bind(this));
|
||||||
let request: TeaPermissionRequest = {} as any;
|
|
||||||
request.client_id = client_id;
|
|
||||||
request.promise = new LaterPromise<PermissionValue[]>();
|
|
||||||
|
|
||||||
this.handle.serverConnection.send_command("clientpermlist", {cldbid: client_id}).catch(error => {
|
|
||||||
this.requests_client_permissions.remove(request);
|
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
|
||||||
request.promise.resolved([]);
|
|
||||||
else
|
|
||||||
request.promise.rejected(error);
|
|
||||||
}).then(() => {
|
|
||||||
//Error handler if we've not received an notify
|
|
||||||
setTimeout(() => {
|
|
||||||
if(this.requests_client_permissions.remove(request)) {
|
|
||||||
request.promise.rejected(tr("missing notify"));
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.requests_client_permissions.push(request);
|
|
||||||
return request.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onClientPermList(json: any[]) {
|
|
||||||
let client = parseInt(json[0]["cldbid"]);
|
|
||||||
let permissions = PermissionManager.parse_permission_bulk(json, this);
|
|
||||||
for(let req of this.requests_client_permissions.slice(0)) {
|
|
||||||
if(req.client_id == client) {
|
|
||||||
this.requests_client_permissions.remove(req);
|
|
||||||
req.promise.resolved(permissions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
|
|
||||||
for(let request of this.requests_client_channel_permissions)
|
|
||||||
if(request.client_id == client_id && request.channel_id == channel_id && request.promise.time() + 1000 > Date.now())
|
|
||||||
return request.promise;
|
|
||||||
|
|
||||||
let request: TeaPermissionRequest = {} as any;
|
|
||||||
request.client_id = client_id;
|
|
||||||
request.channel_id = channel_id;
|
|
||||||
request.promise = new LaterPromise<PermissionValue[]>();
|
|
||||||
|
|
||||||
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: client_id, cid: channel_id}).catch(error => {
|
|
||||||
this.requests_client_channel_permissions.remove(request);
|
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
|
||||||
request.promise.resolved([]);
|
|
||||||
else
|
|
||||||
request.promise.rejected(error);
|
|
||||||
}).then(() => {
|
|
||||||
//Error handler if we've not received an notify
|
|
||||||
setTimeout(() => {
|
|
||||||
if(this.requests_client_channel_permissions.remove(request)) {
|
|
||||||
request.promise.rejected(tr("missing notify"));
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.requests_client_channel_permissions.push(request);
|
|
||||||
return request.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* client channel permission request */
|
||||||
private onChannelClientPermList(json: any[]) {
|
private onChannelClientPermList(json: any[]) {
|
||||||
let client_id = parseInt(json[0]["cldbid"]);
|
let client_id = parseInt(json[0]["cldbid"]);
|
||||||
let channel_id = parseInt(json[0]["cid"]);
|
let channel_id = parseInt(json[0]["cid"]);
|
||||||
|
|
||||||
let permissions = PermissionManager.parse_permission_bulk(json, this);
|
this.fullfill_permission_request("requests_client_channel_permissions", {
|
||||||
for(let req of this.requests_client_channel_permissions.slice(0)) {
|
client_id: client_id,
|
||||||
if(req.client_id == client_id && req.channel_id == channel_id) {
|
channel_id: channel_id
|
||||||
this.requests_client_channel_permissions.remove(req);
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
req.promise.resolved(permissions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private execute_client_channel_permission_request(request: permissions.PermissionRequestKeys) {
|
||||||
|
this.handle.serverConnection.send_command("channelclientpermlist", {cldbid: request.client_id, cid: request.channel_id})
|
||||||
|
.catch(error => {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
|
this.fullfill_permission_request("requests_client_channel_permissions", request, "success", []);
|
||||||
|
else
|
||||||
|
this.fullfill_permission_request("requests_client_channel_permissions", request, "error", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestClientChannelPermissions(client_id: number, channel_id: number) : Promise<PermissionValue[]> {
|
||||||
|
const keys: permissions.PermissionRequestKeys = {
|
||||||
|
client_id: client_id
|
||||||
|
};
|
||||||
|
return this.execute_permission_request("requests_client_channel_permissions", keys, this.execute_client_channel_permission_request.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* playlist permissions */
|
||||||
private onPlaylistPermList(json: any[]) {
|
private onPlaylistPermList(json: any[]) {
|
||||||
let playlist_id = parseInt(json[0]["playlist_id"]);
|
let playlist_id = parseInt(json[0]["playlist_id"]);
|
||||||
let permissions = PermissionManager.parse_permission_bulk(json, this);
|
|
||||||
for(let req of this.requests_playlist_permissions.slice(0)) {
|
this.fullfill_permission_request("requests_playlist_permissions", {
|
||||||
if(req.playlist_id == playlist_id) {
|
playlist_id: playlist_id
|
||||||
this.requests_playlist_permissions.remove(req);
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
req.promise.resolved(permissions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private execute_playlist_permission_request(request: permissions.PermissionRequestKeys) {
|
||||||
|
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: request.playlist_id})
|
||||||
|
.catch(error => {
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
|
this.fullfill_permission_request("requests_playlist_permissions", request, "success", []);
|
||||||
|
else
|
||||||
|
this.fullfill_permission_request("requests_playlist_permissions", request, "error", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
|
requestPlaylistPermissions(playlist_id: number) : Promise<PermissionValue[]> {
|
||||||
for(let request of this.requests_playlist_permissions)
|
const keys: permissions.PermissionRequestKeys = {
|
||||||
if(request.playlist_id == playlist_id && request.promise.time() + 1000 > Date.now())
|
playlist_id: playlist_id
|
||||||
return request.promise;
|
};
|
||||||
|
return this.execute_permission_request("requests_playlist_permissions", keys, this.execute_playlist_permission_request.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
let request: TeaPermissionRequest = {} as any;
|
/* playlist client permissions */
|
||||||
request.playlist_id = playlist_id;
|
private onPlaylistClientPermList(json: any[]) {
|
||||||
request.promise = new LaterPromise<PermissionValue[]>();
|
let playlist_id = parseInt(json[0]["playlist_id"]);
|
||||||
|
let client_id = parseInt(json[0]["cldbid"]);
|
||||||
|
|
||||||
this.handle.serverConnection.send_command("playlistpermlist", {playlist_id: playlist_id}).catch(error => {
|
this.fullfill_permission_request("requests_playlist_client_permissions", {
|
||||||
this.requests_playlist_permissions.remove(request);
|
playlist_id: playlist_id,
|
||||||
|
client_id: client_id
|
||||||
|
}, "success", PermissionManager.parse_permission_bulk(json, this.handle.permissions));
|
||||||
|
}
|
||||||
|
|
||||||
|
private execute_playlist_client_permission_request(request: permissions.PermissionRequestKeys) {
|
||||||
|
this.handle.serverConnection.send_command("playlistclientpermlist", {playlist_id: request.playlist_id, cldbid: request.client_id})
|
||||||
|
.catch(error => {
|
||||||
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT)
|
||||||
request.promise.resolved([]);
|
this.fullfill_permission_request("requests_playlist_client_permissions", request, "success", []);
|
||||||
else
|
else
|
||||||
request.promise.rejected(error);
|
this.fullfill_permission_request("requests_playlist_client_permissions", request, "error", error);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.requests_playlist_permissions.push(request);
|
requestPlaylistClientPermissions(playlist_id: number, client_database_id: number) : Promise<PermissionValue[]> {
|
||||||
|
const keys: permissions.PermissionRequestKeys = {
|
||||||
|
playlist_id: playlist_id,
|
||||||
|
client_id: client_database_id
|
||||||
|
};
|
||||||
|
return this.execute_permission_request("requests_playlist_client_permissions", keys, this.execute_playlist_client_permission_request.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly criteria_equal = (a, b) => {
|
||||||
|
for(const criteria of ["client_id", "channel_id", "playlist_id"]) {
|
||||||
|
if((typeof a[criteria] === "undefined") !== (typeof b[criteria] === "undefined")) return false;
|
||||||
|
if(a[criteria] != b[criteria]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
private execute_permission_request(list: RequestLists,
|
||||||
|
criteria: permissions.PermissionRequestKeys,
|
||||||
|
execute: (criteria: permissions.PermissionRequestKeys) => any) : Promise<PermissionValue[]> {
|
||||||
|
for(const request of this[list])
|
||||||
|
if(this.criteria_equal(request, criteria) && request.promise.time() + 1000 < Date.now())
|
||||||
return request.promise;
|
return request.promise;
|
||||||
|
|
||||||
|
const result = Object.assign({
|
||||||
|
timeout_id: setTimeout(() => this.fullfill_permission_request(list, criteria, "error", tr("timeout")), 5000),
|
||||||
|
promise: new LaterPromise<PermissionValue[]>()
|
||||||
|
}, criteria);
|
||||||
|
this[list].push(result);
|
||||||
|
execute(criteria);
|
||||||
|
return result.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
private fullfill_permission_request(list: RequestLists, criteria: permissions.PermissionRequestKeys, status: "success" | "error", result: any) {
|
||||||
|
for(const request of this[list]) {
|
||||||
|
if(this.criteria_equal(request, criteria)) {
|
||||||
|
this[list].remove(request);
|
||||||
|
clearTimeout(request.timeout_id);
|
||||||
|
status === "error" ? request.promise.rejected(result) : request.promise.resolved(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
find_permission(...permissions: string[]) : Promise<permissions.find.Entry[]> {
|
||||||
|
const permission_ids = [];
|
||||||
|
for(const permission of permissions) {
|
||||||
|
const info = this.resolveInfo(permission);
|
||||||
|
if(!info) continue;
|
||||||
|
|
||||||
|
permission_ids.push(info.id);
|
||||||
|
}
|
||||||
|
if(!permission_ids.length) return Promise.resolve([]);
|
||||||
|
|
||||||
|
return new Promise<permissions.find.Entry[]>((resolve, reject) => {
|
||||||
|
const single_handler = {
|
||||||
|
command: "notifypermfind",
|
||||||
|
function: command => {
|
||||||
|
const result: permissions.find.Entry[] = [];
|
||||||
|
for(const entry of command.arguments) {
|
||||||
|
const perm_id = parseInt(entry["p"]);
|
||||||
|
if(permission_ids.indexOf(perm_id) === -1) return; /* not our permfind result */
|
||||||
|
|
||||||
|
const value = parseInt(entry["v"]);
|
||||||
|
const type = parseInt(entry["t"]);
|
||||||
|
|
||||||
|
let data;
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
data = {
|
||||||
|
type: "server_group",
|
||||||
|
group_id: parseInt(entry["id1"]),
|
||||||
|
} as permissions.find.ServerGroup;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
data = {
|
||||||
|
type: "client",
|
||||||
|
client_id: parseInt(entry["id2"]),
|
||||||
|
} as permissions.find.Client;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
data = {
|
||||||
|
type: "channel",
|
||||||
|
channel_id: parseInt(entry["id2"]),
|
||||||
|
} as permissions.find.Channel;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
data = {
|
||||||
|
type: "channel_group",
|
||||||
|
group_id: parseInt(entry["id1"]),
|
||||||
|
} as permissions.find.ChannelGroup;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
data = {
|
||||||
|
type: "client_channel",
|
||||||
|
client_id: parseInt(entry["id1"]),
|
||||||
|
channel_id: parseInt(entry["id1"]),
|
||||||
|
} as permissions.find.ClientChannel;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.id = perm_id;
|
||||||
|
data.value = value;
|
||||||
|
result.push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.handler_boss.register_single_handler(single_handler);
|
||||||
|
|
||||||
|
this.connection.send_command("permfind", permission_ids.map(e => { return {permid: e }})).catch(error => {
|
||||||
|
this.handler_boss.remove_single_handler(single_handler);
|
||||||
|
|
||||||
|
if(error instanceof CommandResult && error.id == ErrorID.EMPTY_RESULT) {
|
||||||
|
resolve([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
neededPermission(key: number | string | PermissionType | PermissionInfo) : NeededPermissionValue {
|
neededPermission(key: number | string | PermissionType | PermissionInfo) : NeededPermissionValue {
|
||||||
|
|
|
@ -54,6 +54,7 @@ class ClientProperties {
|
||||||
client_total_bytes_downloaded: number = 0;
|
client_total_bytes_downloaded: number = 0;
|
||||||
|
|
||||||
client_talk_power: number = 0;
|
client_talk_power: number = 0;
|
||||||
|
client_is_priority_speaker: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientConnectionInfo {
|
class ClientConnectionInfo {
|
||||||
|
@ -1237,6 +1238,10 @@ class MusicClientProperties extends ClientProperties {
|
||||||
|
|
||||||
client_playlist_id: number = 0;
|
client_playlist_id: number = 0;
|
||||||
client_disabled: boolean = false;
|
client_disabled: boolean = false;
|
||||||
|
|
||||||
|
client_flag_notify_song_change: boolean = false;
|
||||||
|
client_bot_type: number = 0;
|
||||||
|
client_uptime_mode: number = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -10,7 +10,12 @@ namespace tooltip {
|
||||||
hide();
|
hide();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
export function initialize(entry: JQuery) : Handle {
|
export function initialize(entry: JQuery, callbacks?: {
|
||||||
|
on_show?(tag: JQuery),
|
||||||
|
on_hide?(tag: JQuery)
|
||||||
|
}) : Handle {
|
||||||
|
if(!callbacks) callbacks = {};
|
||||||
|
|
||||||
let _show;
|
let _show;
|
||||||
let _hide;
|
let _hide;
|
||||||
let _shown;
|
let _shown;
|
||||||
|
@ -34,12 +39,14 @@ namespace tooltip {
|
||||||
_global_tooltip[0].classList.add("shown");
|
_global_tooltip[0].classList.add("shown");
|
||||||
|
|
||||||
_global_tooltip[0].innerHTML = node_content[0].innerHTML;
|
_global_tooltip[0].innerHTML = node_content[0].innerHTML;
|
||||||
|
callbacks.on_show && callbacks.on_show(_global_tooltip);
|
||||||
_flag_shown = _flag_shown || !!event; /* if event is undefined then it has been triggered by hand */
|
_flag_shown = _flag_shown || !!event; /* if event is undefined then it has been triggered by hand */
|
||||||
};
|
};
|
||||||
|
|
||||||
const mouseexit = () => {
|
const mouseexit = () => {
|
||||||
if(_global_tooltip) {
|
if(_global_tooltip) {
|
||||||
if(!_force_show) {
|
if(!_force_show) {
|
||||||
|
callbacks.on_hide && callbacks.on_hide(_global_tooltip);
|
||||||
_global_tooltip[0].classList.remove("shown");
|
_global_tooltip[0].classList.remove("shown");
|
||||||
}
|
}
|
||||||
_flag_shown = false;
|
_flag_shown = false;
|
||||||
|
|
|
@ -431,6 +431,7 @@ namespace top_menu {
|
||||||
{
|
{
|
||||||
const menu = driver.append_item(tr("Tools"));
|
const menu = driver.append_item(tr("Tools"));
|
||||||
|
|
||||||
|
/*
|
||||||
item = menu.append_item(tr("Manage Playlists"));
|
item = menu.append_item(tr("Manage Playlists"));
|
||||||
item.icon('client-music');
|
item.icon('client-music');
|
||||||
item.click(() => {
|
item.click(() => {
|
||||||
|
@ -442,6 +443,7 @@ namespace top_menu {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_state_updater["tools.pl"] = { item: item, conditions: [condition_connected]};
|
_state_updater["tools.pl"] = { item: item, conditions: [condition_connected]};
|
||||||
|
*/
|
||||||
|
|
||||||
item = menu.append_item(tr("Ban List"));
|
item = menu.append_item(tr("Ban List"));
|
||||||
item.icon('client-ban_list');
|
item.icon('client-ban_list');
|
||||||
|
|
|
@ -65,7 +65,12 @@ namespace chat {
|
||||||
this.handle.show_private_conversations();
|
this.handle.show_private_conversations();
|
||||||
})[0];
|
})[0];
|
||||||
|
|
||||||
this._button_bot_manage = this._html_tag.find(".bot-manage");
|
this._button_bot_manage = this._html_tag.find(".bot-manage").on('click', event => {
|
||||||
|
const bot = this.handle.music_info().current_bot();
|
||||||
|
if(!bot) return;
|
||||||
|
|
||||||
|
Modals.openMusicManage(this.handle.handle, bot);
|
||||||
|
});
|
||||||
this._button_song_add = this._html_tag.find(".bot-add-song").on('click', event => {
|
this._button_song_add = this._html_tag.find(".bot-add-song").on('click', event => {
|
||||||
this.handle.music_info().events.fire("action_song_add");
|
this.handle.music_info().events.fire("action_song_add");
|
||||||
});
|
});
|
||||||
|
|
|
@ -656,6 +656,10 @@ namespace chat {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current_bot() : MusicClientEntry | undefined {
|
||||||
|
return this._current_bot;
|
||||||
|
}
|
||||||
|
|
||||||
private sort_songs(data: PlaylistSong[]) {
|
private sort_songs(data: PlaylistSong[]) {
|
||||||
const result = [];
|
const result = [];
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ namespace htmltags {
|
||||||
client_id: number,
|
client_id: number,
|
||||||
client_unique_id: string,
|
client_unique_id: string,
|
||||||
client_name: string,
|
client_name: string,
|
||||||
add_braces?: boolean
|
add_braces?: boolean,
|
||||||
|
client_database_id?: number; /* not yet used */
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChannelProperties {
|
export interface ChannelProperties {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,21 +0,0 @@
|
||||||
/// <reference path="../../ui/elements/modal.ts" />
|
|
||||||
/// <reference path="../../ConnectionHandler.ts" />
|
|
||||||
/// <reference path="../../proto.ts" />
|
|
||||||
|
|
||||||
namespace Modals {
|
|
||||||
export function openPlaylistManage(client: ConnectionHandler, playlist: Playlist) {
|
|
||||||
let modal = createModal({
|
|
||||||
header: tr(tr("Playlist Manage")),
|
|
||||||
body: () => $("#tmpl_playlist_manage").renderTag().children(),
|
|
||||||
footer: null,
|
|
||||||
|
|
||||||
width: "",
|
|
||||||
closeable: false
|
|
||||||
});
|
|
||||||
|
|
||||||
//TODO!
|
|
||||||
|
|
||||||
modal.open();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -196,6 +196,7 @@ const loader_javascript = {
|
||||||
"js/ui/modal/ModalInvite.js",
|
"js/ui/modal/ModalInvite.js",
|
||||||
"js/ui/modal/ModalIdentity.js",
|
"js/ui/modal/ModalIdentity.js",
|
||||||
"js/ui/modal/ModalBanList.js",
|
"js/ui/modal/ModalBanList.js",
|
||||||
|
"js/ui/modal/ModalMusicManage.js",
|
||||||
"js/ui/modal/ModalYesNo.js",
|
"js/ui/modal/ModalYesNo.js",
|
||||||
"js/ui/modal/ModalPoke.js",
|
"js/ui/modal/ModalPoke.js",
|
||||||
"js/ui/modal/ModalKeySelect.js",
|
"js/ui/modal/ModalKeySelect.js",
|
||||||
|
@ -362,6 +363,7 @@ const loader_style = {
|
||||||
"css/static/modal-channelinfo.css",
|
"css/static/modal-channelinfo.css",
|
||||||
"css/static/modal-clientinfo.css",
|
"css/static/modal-clientinfo.css",
|
||||||
"css/static/modal-serverinfo.css",
|
"css/static/modal-serverinfo.css",
|
||||||
|
"css/static/modal-musicmanage.css",
|
||||||
"css/static/modal-serverinfobandwidth.css",
|
"css/static/modal-serverinfobandwidth.css",
|
||||||
"css/static/modal-identity.css",
|
"css/static/modal-identity.css",
|
||||||
"css/static/modal-settings.css",
|
"css/static/modal-settings.css",
|
||||||
|
@ -391,34 +393,6 @@ const loader_style = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function load_templates() {
|
|
||||||
try {
|
|
||||||
const response = await $.ajax("templates.html" + loader.get_cache_version());
|
|
||||||
|
|
||||||
let node = document.createElement("html");
|
|
||||||
node.innerHTML = response;
|
|
||||||
let tags: HTMLCollection;
|
|
||||||
if(node.getElementsByTagName("body").length > 0)
|
|
||||||
tags = node.getElementsByTagName("body")[0].children;
|
|
||||||
else
|
|
||||||
tags = node.children;
|
|
||||||
|
|
||||||
let root = document.getElementById("templates");
|
|
||||||
if(!root) {
|
|
||||||
loader.critical_error("Failed to find template tag!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while(tags.length > 0){
|
|
||||||
let tag = tags.item(0);
|
|
||||||
root.appendChild(tag);
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch(error) {
|
|
||||||
loader.critical_error("Failed to find template tag!");
|
|
||||||
throw "template error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* register tasks */
|
/* register tasks */
|
||||||
loader.register_task(loader.Stage.INITIALIZING, {
|
loader.register_task(loader.Stage.INITIALIZING, {
|
||||||
name: "safari fix",
|
name: "safari fix",
|
||||||
|
@ -502,7 +476,12 @@ loader.register_task(loader.Stage.STYLE, {
|
||||||
|
|
||||||
loader.register_task(loader.Stage.TEMPLATES, {
|
loader.register_task(loader.Stage.TEMPLATES, {
|
||||||
name: "templates",
|
name: "templates",
|
||||||
function: load_templates,
|
function: async () => {
|
||||||
|
await loader.load_templates([
|
||||||
|
"templates.html",
|
||||||
|
"templates/music/manage.html"
|
||||||
|
]);
|
||||||
|
},
|
||||||
priority: 10
|
priority: 10
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -473,6 +473,70 @@ namespace loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function load_template(path: SourcePath) : Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await $.ajax(path + (cache_tag || ""));
|
||||||
|
|
||||||
|
let node = document.createElement("html");
|
||||||
|
node.innerHTML = response;
|
||||||
|
let tags: HTMLCollection;
|
||||||
|
if(node.getElementsByTagName("body").length > 0)
|
||||||
|
tags = node.getElementsByTagName("body")[0].children;
|
||||||
|
else
|
||||||
|
tags = node.children;
|
||||||
|
|
||||||
|
let root = document.getElementById("templates");
|
||||||
|
if(!root) {
|
||||||
|
loader.critical_error("Failed to find template tag!");
|
||||||
|
throw "Failed to find template tag";
|
||||||
|
}
|
||||||
|
while(tags.length > 0){
|
||||||
|
let tag = tags.item(0);
|
||||||
|
root.appendChild(tag);
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch(error) {
|
||||||
|
let msg;
|
||||||
|
if('responseText' in error)
|
||||||
|
msg = error.responseText;
|
||||||
|
else if(error instanceof Error)
|
||||||
|
msg = error.message;
|
||||||
|
|
||||||
|
loader.critical_error("failed to load template " + script_name(path), msg);
|
||||||
|
throw "template error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function load_templates(paths: SourcePath[]) : Promise<void> {
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
const errors: {
|
||||||
|
template: SourcePath,
|
||||||
|
error: any
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
for(const template of paths)
|
||||||
|
promises.push(load_template(template).catch(error => {
|
||||||
|
errors.push({
|
||||||
|
template: template,
|
||||||
|
error: error
|
||||||
|
});
|
||||||
|
return Promise.resolve();
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Promise.all([...promises]);
|
||||||
|
|
||||||
|
if(errors.length > 0) {
|
||||||
|
if (loader.config.error) {
|
||||||
|
console.error("Failed to load the following templates:");
|
||||||
|
for (const sheet of errors)
|
||||||
|
console.log(" - %o: %o", sheet.template, sheet.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.critical_error("Failed to load template " + script_name(errors[0].template) + " <br>" + "View the browser console for more information!");
|
||||||
|
throw "failed to load template " + script_name(errors[0].template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ErrorHandler = (message: string, detail: string) => void;
|
export type ErrorHandler = (message: string, detail: string) => void;
|
||||||
|
|
||||||
|
@ -578,7 +642,7 @@ namespace loader {
|
||||||
const display_detect = /./;
|
const display_detect = /./;
|
||||||
display_detect.toString = function() { print_security(); return ""; };
|
display_detect.toString = function() { print_security(); return ""; };
|
||||||
|
|
||||||
clog("%cLovely to see you using and debugging the TeaSpeak Web client.", css);
|
clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css);
|
||||||
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
clog("%cIf you have some good ideas or already done some incredible changes,", css);
|
||||||
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
|
clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2);
|
||||||
clog("%c ", display_detect);
|
clog("%c ", display_detect);
|
||||||
|
|
Loading…
Reference in New Issue