Added permission management system
parent
d9c858315f
commit
aad4023ed5
|
@ -1,4 +1,7 @@
|
|||
# Changelog:
|
||||
* **30.09.18**
|
||||
- Added the permission system (Assignments and management)
|
||||
|
||||
* **26.09.18**:
|
||||
- Added Safari support
|
||||
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
.context-menu {
|
||||
overflow: visible;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
border: 1px solid #CCC;
|
||||
white-space: nowrap;
|
||||
font-family: sans-serif;
|
||||
background: #FFF;
|
||||
color: #333;
|
||||
padding: 3px;
|
||||
|
||||
* {
|
||||
font-family: Arial, serif;
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.entry {
|
||||
/*padding: 8px 12px;*/
|
||||
padding-right: 12px;
|
||||
cursor: pointer;
|
||||
list-style-type: none;
|
||||
transition: all .3s ease;
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
|
||||
&.disabled {
|
||||
background-color: lightgray;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
background-color: #DEF;
|
||||
}
|
||||
}
|
||||
|
||||
.icon_empty, .icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
padding: 0;
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.sub-container {
|
||||
padding-right: 3px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.sub-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-menu {
|
||||
display: none;
|
||||
left: 100%;
|
||||
top: -4px;
|
||||
position: absolute;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 14px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
/* Hide the browser's default checkbox */
|
||||
input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 11px;
|
||||
width: 11px;
|
||||
background-color: #eee;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
|
||||
left: 4px;
|
||||
top: 1px;
|
||||
width: 3px;
|
||||
height: 7px;
|
||||
border: solid white;
|
||||
border-width: 0 2px 2px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover input ~ .checkmark {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
input:checked ~ .checkmark {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,53 +4,6 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.contextMenu {
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
border: 1px solid #CCC;
|
||||
white-space: nowrap;
|
||||
font-family: sans-serif;
|
||||
background: #FFF;
|
||||
color: #333;
|
||||
padding: 3px;
|
||||
}
|
||||
.contextMenu *{
|
||||
font-family: Arial;
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Each of the items in the list */
|
||||
.contextMenu li {
|
||||
/*padding: 8px 12px;*/
|
||||
padding-right: 12px;
|
||||
cursor: pointer;
|
||||
list-style-type: none;
|
||||
transition: all .3s ease;
|
||||
user-select: none;
|
||||
align-items: center;
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.contextMenu .icon_empty,
|
||||
.contextMenu .icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.contextMenu li:hover:not(.disabled) {
|
||||
background-color: #DEF;
|
||||
}
|
||||
|
||||
.contextMenu li.disabled {
|
||||
background-color: lightgray;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.align_row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
277
css/modals.css
277
css/modals.css
|
@ -85,4 +85,281 @@
|
|||
.settings_advanced .group_box fieldset, .settings_advanced .group_box fieldset > div {
|
||||
width: 100%; }
|
||||
|
||||
.permission-explorer {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: min-content auto;
|
||||
grid-gap: 5px; }
|
||||
.permission-explorer .bar-filter {
|
||||
display: grid;
|
||||
grid-gap: 5px;
|
||||
grid-template-columns: max-content auto max-content; }
|
||||
.permission-explorer .bar-filter input[type="text"] {
|
||||
width: 100%; }
|
||||
.permission-explorer.disabled {
|
||||
pointer-events: none; }
|
||||
.permission-explorer.disabled .overlay-disabled {
|
||||
display: block; }
|
||||
.permission-explorer.disabled input {
|
||||
background-color: #00000033; }
|
||||
.permission-explorer .overlay-disabled {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #00000033;
|
||||
z-index: 1000;
|
||||
height: 100%;
|
||||
width: 100%; }
|
||||
.permission-explorer .list {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
border: lightgray solid 2px;
|
||||
user-select: none;
|
||||
padding-bottom: 2px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden; }
|
||||
.permission-explorer .list .header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: lightgray;
|
||||
padding-left: 0 !important; }
|
||||
.permission-explorer .list .header > div {
|
||||
border: grey solid;
|
||||
border-width: 0 2px 0 0;
|
||||
padding-left: 2px; }
|
||||
.permission-explorer .list .header > div:last-of-type {
|
||||
border: none; }
|
||||
.permission-explorer .list > .entry {
|
||||
padding-left: 4px; }
|
||||
.permission-explorer .list .entry {
|
||||
display: grid;
|
||||
grid-template-columns: auto 100px 100px 100px 100px; }
|
||||
.permission-explorer .list .entry > div {
|
||||
padding-left: 2px; }
|
||||
.permission-explorer .list .entry.selected {
|
||||
background-color: #11111122; }
|
||||
.permission-explorer .list .entry.unset > .permission-value, .permission-explorer .list .entry.unset > .permission-skip, .permission-explorer .list .entry.unset > .permission-negate {
|
||||
visibility: hidden; }
|
||||
.permission-explorer .list .entry.unset > .permission-name {
|
||||
color: lightgray; }
|
||||
.permission-explorer .list .group {
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto auto; }
|
||||
.permission-explorer .list .group .group-entries {
|
||||
margin-left: 50px; }
|
||||
.permission-explorer .list .group .title.selected {
|
||||
background-color: #11111122; }
|
||||
.permission-explorer .list .arrow {
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
padding: 0;
|
||||
margin-right: 5px;
|
||||
margin-left: 3px; }
|
||||
.permission-explorer .list input {
|
||||
border: none;
|
||||
background: transparent;
|
||||
vertical-align: text-bottom;
|
||||
max-width: 90%; }
|
||||
.permission-explorer .list .checkbox {
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
/* Hide the browser's default checkbox */ }
|
||||
.permission-explorer .list .checkbox input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer; }
|
||||
.permission-explorer .list .checkbox .checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: #eee; }
|
||||
.permission-explorer .list .checkbox .checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
left: 6px;
|
||||
top: 2px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.permission-explorer .list .checkbox:hover input ~ .checkmark {
|
||||
background-color: #ccc; }
|
||||
.permission-explorer .list .checkbox input:checked ~ .checkmark {
|
||||
background-color: #2196F3; }
|
||||
.permission-explorer .list .checkbox input:checked ~ .checkmark:after {
|
||||
display: block; }
|
||||
|
||||
.arrow {
|
||||
display: inline-block;
|
||||
border: solid black;
|
||||
border-width: 0 3px 3px 0;
|
||||
padding: 3px; }
|
||||
.arrow.right {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg); }
|
||||
.arrow.left {
|
||||
transform: rotate(135deg);
|
||||
-webkit-transform: rotate(135deg); }
|
||||
.arrow.up {
|
||||
transform: rotate(-135deg);
|
||||
-webkit-transform: rotate(-135deg); }
|
||||
.arrow.down {
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg); }
|
||||
|
||||
.layout-group-server, .layout-group-channel, .layout-channel, .layout-client, .layout-client-channel {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row; }
|
||||
.layout-group-server > div, .layout-group-channel > div, .layout-channel > div, .layout-client > div, .layout-client-channel > div {
|
||||
margin: 5px; }
|
||||
.layout-group-server .list-group-server, .layout-group-server .list-group-channel, .layout-group-server .list-group-server-clients, .layout-group-server .list-channel, .layout-group-channel .list-group-server, .layout-group-channel .list-group-channel, .layout-group-channel .list-group-server-clients, .layout-group-channel .list-channel, .layout-channel .list-group-server, .layout-channel .list-group-channel, .layout-channel .list-group-server-clients, .layout-channel .list-channel, .layout-client .list-group-server, .layout-client .list-group-channel, .layout-client .list-group-server-clients, .layout-client .list-channel, .layout-client-channel .list-group-server, .layout-client-channel .list-group-channel, .layout-client-channel .list-group-server-clients, .layout-client-channel .list-channel {
|
||||
position: relative; }
|
||||
.layout-group-server .list-group-server .entries, .layout-group-server .list-group-channel .entries, .layout-group-server .list-group-server-clients .entries, .layout-group-server .list-channel .entries, .layout-group-channel .list-group-server .entries, .layout-group-channel .list-group-channel .entries, .layout-group-channel .list-group-server-clients .entries, .layout-group-channel .list-channel .entries, .layout-channel .list-group-server .entries, .layout-channel .list-group-channel .entries, .layout-channel .list-group-server-clients .entries, .layout-channel .list-channel .entries, .layout-client .list-group-server .entries, .layout-client .list-group-channel .entries, .layout-client .list-group-server-clients .entries, .layout-client .list-channel .entries, .layout-client-channel .list-group-server .entries, .layout-client-channel .list-group-channel .entries, .layout-client-channel .list-group-server-clients .entries, .layout-client-channel .list-channel .entries {
|
||||
display: table;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
min-width: 100%; }
|
||||
.layout-group-server .list-group-server, .layout-group-server .list-group-channel, .layout-group-channel .list-group-server, .layout-group-channel .list-group-channel, .layout-channel .list-group-server, .layout-channel .list-group-channel, .layout-client .list-group-server, .layout-client .list-group-channel, .layout-client-channel .list-group-server, .layout-client-channel .list-group-channel {
|
||||
width: 300px;
|
||||
flex-grow: 0;
|
||||
border: grey solid 1px;
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
position: relative; }
|
||||
.layout-group-server .list-group-server .group, .layout-group-server .list-group-channel .group, .layout-group-channel .list-group-server .group, .layout-group-channel .list-group-channel .group, .layout-channel .list-group-server .group, .layout-channel .list-group-channel .group, .layout-client .list-group-server .group, .layout-client .list-group-channel .group, .layout-client-channel .list-group-server .group, .layout-client-channel .list-group-channel .group {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer; }
|
||||
.layout-group-server .list-group-server .group .icon, .layout-group-server .list-group-server .group .icon_empty, .layout-group-server .list-group-channel .group .icon, .layout-group-server .list-group-channel .group .icon_empty, .layout-group-channel .list-group-server .group .icon, .layout-group-channel .list-group-server .group .icon_empty, .layout-group-channel .list-group-channel .group .icon, .layout-group-channel .list-group-channel .group .icon_empty, .layout-channel .list-group-server .group .icon, .layout-channel .list-group-server .group .icon_empty, .layout-channel .list-group-channel .group .icon, .layout-channel .list-group-channel .group .icon_empty, .layout-client .list-group-server .group .icon, .layout-client .list-group-server .group .icon_empty, .layout-client .list-group-channel .group .icon, .layout-client .list-group-channel .group .icon_empty, .layout-client-channel .list-group-server .group .icon, .layout-client-channel .list-group-server .group .icon_empty, .layout-client-channel .list-group-channel .group .icon, .layout-client-channel .list-group-channel .group .icon_empty {
|
||||
margin-right: 3px; }
|
||||
.layout-group-server .list-group-server .group .name.savedb, .layout-group-server .list-group-channel .group .name.savedb, .layout-group-channel .list-group-server .group .name.savedb, .layout-group-channel .list-group-channel .group .name.savedb, .layout-channel .list-group-server .group .name.savedb, .layout-channel .list-group-channel .group .name.savedb, .layout-client .list-group-server .group .name.savedb, .layout-client .list-group-channel .group .name.savedb, .layout-client-channel .list-group-server .group .name.savedb, .layout-client-channel .list-group-channel .group .name.savedb {
|
||||
color: blue; }
|
||||
.layout-group-server .list-group-server .group .name.default, .layout-group-server .list-group-channel .group .name.default, .layout-group-channel .list-group-server .group .name.default, .layout-group-channel .list-group-channel .group .name.default, .layout-channel .list-group-server .group .name.default, .layout-channel .list-group-channel .group .name.default, .layout-client .list-group-server .group .name.default, .layout-client .list-group-channel .group .name.default, .layout-client-channel .list-group-server .group .name.default, .layout-client-channel .list-group-channel .group .name.default {
|
||||
color: black;
|
||||
font-weight: bold; }
|
||||
.layout-group-server .list-group-server .group.selected, .layout-group-server .list-group-channel .group.selected, .layout-group-channel .list-group-server .group.selected, .layout-group-channel .list-group-channel .group.selected, .layout-channel .list-group-server .group.selected, .layout-channel .list-group-channel .group.selected, .layout-client .list-group-server .group.selected, .layout-client .list-group-channel .group.selected, .layout-client-channel .list-group-server .group.selected, .layout-client-channel .list-group-channel .group.selected {
|
||||
background-color: blue; }
|
||||
.layout-group-server .list-group-server .group.selected .name.savedb, .layout-group-server .list-group-channel .group.selected .name.savedb, .layout-group-channel .list-group-server .group.selected .name.savedb, .layout-group-channel .list-group-channel .group.selected .name.savedb, .layout-channel .list-group-server .group.selected .name.savedb, .layout-channel .list-group-channel .group.selected .name.savedb, .layout-client .list-group-server .group.selected .name.savedb, .layout-client .list-group-channel .group.selected .name.savedb, .layout-client-channel .list-group-server .group.selected .name.savedb, .layout-client-channel .list-group-channel .group.selected .name.savedb {
|
||||
color: white; }
|
||||
|
||||
.layout-group-server .permission-explorer {
|
||||
flex-grow: 70; }
|
||||
.layout-group-server .list-group-server-clients {
|
||||
flex-grow: 0;
|
||||
width: 200px;
|
||||
border: grey solid 1px; }
|
||||
|
||||
.layout-channel .list-channel, .layout-client-channel .list-channel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: scroll;
|
||||
overflow-y: auto;
|
||||
width: 300px;
|
||||
flex-grow: 1; }
|
||||
.layout-channel .list-channel .channel, .layout-client-channel .list-channel .channel {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
white-space: nowrap; }
|
||||
.layout-channel .list-channel .channel .icon, .layout-channel .list-channel .channel .icon_empty, .layout-client-channel .list-channel .channel .icon, .layout-client-channel .list-channel .channel .icon_empty {
|
||||
margin-right: 3px; }
|
||||
.layout-channel .list-channel .channel.selected, .layout-client-channel .list-channel .channel.selected {
|
||||
background-color: blue; }
|
||||
|
||||
.layout-client .client-info, .layout-client-channel .client-info {
|
||||
display: flex;
|
||||
flex-direction: column; }
|
||||
.layout-client .client-info > div:not(.list-channel), .layout-client-channel .client-info > div:not(.list-channel) {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: max-content; }
|
||||
.layout-client .client-info .client-info input, .layout-client-channel .client-info .client-info input {
|
||||
pointer-events: none; }
|
||||
|
||||
.group-assignment-list .group-list {
|
||||
border: lightgray solid 1px;
|
||||
padding: 3px; }
|
||||
.group-assignment-list .group-list .group-entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: max-content; }
|
||||
.group-assignment-list .group-list .checkbox {
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 18px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
/* Hide the browser's default checkbox */ }
|
||||
.group-assignment-list .group-list .checkbox input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
display: none; }
|
||||
.group-assignment-list .group-list .checkbox .checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: #eee;
|
||||
margin-right: 4px; }
|
||||
.group-assignment-list .group-list .checkbox .checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
left: 5px;
|
||||
top: 1px;
|
||||
width: 6px;
|
||||
height: 12px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg); }
|
||||
.group-assignment-list .group-list .checkbox:hover:not(.disabled) input ~ .checkmark {
|
||||
background-color: #ccc; }
|
||||
.group-assignment-list .group-list .checkbox input:checked ~ .checkmark {
|
||||
background-color: #2196F3; }
|
||||
.group-assignment-list .group-list .checkbox input:checked ~ .checkmark:after {
|
||||
display: block; }
|
||||
.group-assignment-list .group-list .checkbox.disabled {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed; }
|
||||
.group-assignment-list .group-list .checkbox.disabled .checkmark {
|
||||
background-color: #00000055; }
|
||||
.group-assignment-list .group-list .checkbox.disabled .checkmark:after {
|
||||
border-color: #00000055; }
|
||||
|
||||
/*# sourceMappingURL=modals.css.map */
|
||||
|
|
File diff suppressed because one or more lines are too long
417
css/modals.scss
417
css/modals.scss
|
@ -138,4 +138,421 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.permission-explorer {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: min-content auto;
|
||||
grid-gap: 5px;
|
||||
|
||||
.bar-filter {
|
||||
display: grid;
|
||||
grid-gap: 5px;
|
||||
grid-template-columns: max-content auto max-content;
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
|
||||
.overlay-disabled {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: #00000033;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-disabled {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #00000033;
|
||||
z-index: 1000;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
border: lightgray solid 2px;
|
||||
user-select: none;
|
||||
padding-bottom: 2px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: lightgray;
|
||||
padding-left: 0!important;
|
||||
|
||||
& > div {
|
||||
border: grey solid;
|
||||
border-width: 0 2px 0 0;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
& > div:last-of-type {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
& > .entry {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.entry {
|
||||
display: grid;
|
||||
grid-template-columns: auto 100px 100px 100px 100px;
|
||||
|
||||
& > div {
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: #11111122;
|
||||
}
|
||||
|
||||
&.unset {
|
||||
& > .permission-value, & > .permission-skip, & > .permission-negate {
|
||||
visibility: hidden;
|
||||
}
|
||||
& > .permission-name {
|
||||
color: lightgray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group {
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto auto;
|
||||
|
||||
.group-entries {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
&.selected {
|
||||
background-color: #11111122;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
padding: 0;
|
||||
margin-right: 5px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
background: transparent;
|
||||
vertical-align: text-bottom;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 35px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
/* Hide the browser's default checkbox */
|
||||
input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: #eee;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
|
||||
left: 6px;
|
||||
top: 2px;
|
||||
width: 5px;
|
||||
height: 10px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover input ~ .checkmark {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
input:checked ~ .checkmark {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
display: inline-block;
|
||||
border: solid black;
|
||||
border-width: 0 3px 3px 0;
|
||||
padding: 3px;
|
||||
|
||||
&.right {
|
||||
transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
&.left {
|
||||
transform: rotate(135deg);
|
||||
-webkit-transform: rotate(135deg);
|
||||
}
|
||||
|
||||
&.up {
|
||||
transform: rotate(-135deg);
|
||||
-webkit-transform: rotate(-135deg);
|
||||
}
|
||||
|
||||
&.down {
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.layout-group-server, .layout-group-channel, .layout-channel, .layout-client, .layout-client-channel {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
& > div {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.list-group-server, .list-group-channel, .list-group-server-clients, .list-channel {
|
||||
position: relative;
|
||||
.entries {
|
||||
display: table;
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
left: 0; right: 0;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group-server, .list-group-channel {
|
||||
width: 300px;
|
||||
flex-grow: 0;
|
||||
border: grey solid 1px;
|
||||
user-select: none;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
|
||||
.group {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
|
||||
.icon, .icon_empty {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.name.savedb {
|
||||
color: blue;
|
||||
}
|
||||
.name.default {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: blue;
|
||||
|
||||
.name.savedb {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-group-server {
|
||||
.list-group-server { }
|
||||
|
||||
.permission-explorer {
|
||||
flex-grow: 70;
|
||||
}
|
||||
|
||||
.list-group-server-clients {
|
||||
flex-grow: 0;
|
||||
width: 200px;
|
||||
border: grey solid 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-channel, .layout-client-channel {
|
||||
.list-channel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
overflow-x: scroll;
|
||||
overflow-y: auto;
|
||||
width: 300px;
|
||||
flex-grow: 1;
|
||||
|
||||
.channel {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
white-space: nowrap;
|
||||
|
||||
.icon, .icon_empty {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-client, .layout-client-channel {
|
||||
.client-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > div:not(.list-channel) {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: max-content;
|
||||
}
|
||||
|
||||
.client-info {
|
||||
input {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-assignment-list {
|
||||
.group-list {
|
||||
border: lightgray solid 1px;
|
||||
padding: 3px;
|
||||
|
||||
.group-entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 18px;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 22px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
/* Hide the browser's default checkbox */
|
||||
input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: #eee;
|
||||
margin-right: 4px;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
|
||||
left: 5px;
|
||||
top: 1px;
|
||||
width: 6px;
|
||||
height: 12px;
|
||||
border: solid white;
|
||||
border-width: 0 3px 3px 0;
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.disabled) input ~ .checkmark {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
input:checked ~ .checkmark {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
|
||||
.checkmark {
|
||||
background-color: #00000055;
|
||||
&:after {
|
||||
border-color: #00000055;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ x-tab { display:none }
|
|||
border-radius: 0px 2px 2px 2px;
|
||||
border-style: solid;
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 2 * 18px);
|
||||
height: 100%;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,6 @@ x-tab { display:none }
|
|||
display: none;
|
||||
}
|
||||
|
||||
.tab .selected {
|
||||
background: #11111111!important;
|
||||
.tab .tab-header .entry.selected {
|
||||
background: #11111111;
|
||||
}
|
|
@ -42,6 +42,7 @@
|
|||
<link rel="stylesheet" href="css/music/info_plate.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/frame/SelectInfo.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/control_bar.css" type="text/css">
|
||||
<link rel="stylesheet" href="css/context_menu.css" type="text/css">
|
||||
<link rel="stylesheet" href="vendor/bbcode/xbbcode.css" type="text/css">
|
||||
|
||||
<!-- https://localhost:9987/?forward_url=http%3A%2F%2Flocalhost%3A63344%2FWeb-Client%2Findex.php%3F_ijt%3D82b1uhmnh0a5l1n35nnjps5eid%26loader_ignore_age%3D1%26connect_default_host%3Dlocalhost%26default_connect_type%3Dforum%26default_connect_url%3Dtrue%26default_connect_type%3Dteamspeak%26default_connect_url%3Dlocalhost%253A9987 -->
|
||||
|
@ -214,7 +215,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="contextMenu" class="contextMenu"></div>
|
||||
<div id="contextMenu" class="context-menu"></div>
|
||||
|
||||
<!--
|
||||
<div style="background-color:white;">
|
||||
|
|
|
@ -321,7 +321,7 @@ class IconManager {
|
|||
}
|
||||
|
||||
//$("<img width=\"16\" height=\"16\" alt=\"tick\" src=\"data:image/png;base64," + value.base64 + "\">")
|
||||
generateTag(id: number) {
|
||||
generateTag(id: number) : JQuery<HTMLDivElement> {
|
||||
if(id == 0)
|
||||
return $("<div class='icon_empty'></div>");
|
||||
else if(id < 1000)
|
||||
|
|
|
@ -43,6 +43,9 @@ namespace TSIdentityHelper {
|
|||
export function unwarpString(str) : string {
|
||||
if(str == "") return "";
|
||||
try {
|
||||
if(!$.isFunction(window.Pointer_stringify) || !$.isFunction(Pointer_stringify)) {
|
||||
displayCriticalError("Missing required wasm function!<br>Please reload the page!", false);
|
||||
}
|
||||
let message: string = Pointer_stringify(str);
|
||||
functionDestroyString(str);
|
||||
return message;
|
||||
|
|
|
@ -225,7 +225,7 @@ class ChatEntry {
|
|||
//TODO Implement this?
|
||||
}
|
||||
});
|
||||
spawnMenu(e.pageX, e.pageY, ...actions);
|
||||
spawn_context_menu(e.pageX, e.pageY, ...actions);
|
||||
});
|
||||
|
||||
closeTag.click(function () {
|
||||
|
|
|
@ -37,6 +37,7 @@ class ServerConnection {
|
|||
_handshakeHandler: HandshakeHandler;
|
||||
commandHandler: ConnectionCommandHandler;
|
||||
|
||||
readonly helper: CommandHelper;
|
||||
private _connectTimeoutHandler: NodeJS.Timer = undefined;
|
||||
private _connected: boolean = false;
|
||||
private _retCodeIdx: number;
|
||||
|
@ -47,6 +48,7 @@ class ServerConnection {
|
|||
|
||||
this._socket = null;
|
||||
this.commandHandler = new ConnectionCommandHandler(this);
|
||||
this.helper = new CommandHelper(this);
|
||||
this._retCodeIdx = 0;
|
||||
this._retListener = [];
|
||||
}
|
||||
|
@ -351,6 +353,73 @@ class HandshakeHandler {
|
|||
}
|
||||
}
|
||||
|
||||
interface ClientNameInfo {
|
||||
//cluid=tYzKUryn\/\/Y8VBMf8PHUT6B1eiE= name=Exp clname=Exp cldbid=9
|
||||
client_unique_id: string;
|
||||
client_nickname: string;
|
||||
client_database_id: number;
|
||||
}
|
||||
|
||||
interface ClientNameFromUid {
|
||||
promise: LaterPromise<ClientNameInfo[]>,
|
||||
keys: string[],
|
||||
response: ClientNameInfo[]
|
||||
}
|
||||
|
||||
class CommandHelper {
|
||||
readonly connection: ServerConnection;
|
||||
|
||||
private _callbacks_namefromuid: ClientNameFromUid[] = [];
|
||||
|
||||
constructor(connection) {
|
||||
this.connection = connection;
|
||||
this.connection.commandHandler["notifyclientnamefromuid"] = this.handle_notifyclientnamefromuid.bind(this);
|
||||
}
|
||||
|
||||
info_from_uid(...uid: string[]) : Promise<ClientNameInfo[]> {
|
||||
let uids = [...uid];
|
||||
for(let p of this._callbacks_namefromuid)
|
||||
if(p.keys == uids) return p.promise;
|
||||
|
||||
let req: ClientNameFromUid = {} as any;
|
||||
req.keys = uids;
|
||||
req.response = new Array(uids.length);
|
||||
req.promise = new LaterPromise<ClientNameInfo[]>();
|
||||
|
||||
for(let uid of uids) {
|
||||
this.connection.sendCommand("clientgetnamefromuid", {
|
||||
cluid: uid
|
||||
}).catch(req.promise.function_rejected());
|
||||
}
|
||||
|
||||
this._callbacks_namefromuid.push(req);
|
||||
return req.promise;
|
||||
}
|
||||
|
||||
private handle_notifyclientnamefromuid(json: any[]) {
|
||||
for(let entry of json) {
|
||||
let info: ClientNameInfo = {} as any;
|
||||
info.client_unique_id = entry["cluid"];
|
||||
info.client_nickname = entry["clname"];
|
||||
info.client_database_id = parseInt(entry["cldbid"]);
|
||||
|
||||
for(let elm of this._callbacks_namefromuid.slice(0)) {
|
||||
let unset = 0;
|
||||
for(let index = 0; index < elm.keys.length; index++) {
|
||||
if(elm.keys[index] == info.client_unique_id) {
|
||||
elm.response[index] = info;
|
||||
}
|
||||
if(elm.response[index] == undefined) unset++;
|
||||
}
|
||||
if(unset == 0) {
|
||||
this._callbacks_namefromuid.remove(elm);
|
||||
elm.promise.resolved(elm.response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionCommandHandler {
|
||||
readonly connection: ServerConnection;
|
||||
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
// If the document is clicked somewhere
|
||||
$(document).bind("mousedown", function (e) {
|
||||
// If the clicked element is not the menu
|
||||
if ($(e.target).parents(".contextMenu").length == 0) {
|
||||
// Hide it
|
||||
despawnContextMenu();
|
||||
if ($(e.target).parents(".context-menu").length == 0) {
|
||||
despawn_context_menu();
|
||||
}
|
||||
});
|
||||
|
||||
let contextMenuCloseFn = undefined;
|
||||
function despawnContextMenu() {
|
||||
let menue = $(".contextMenu");
|
||||
if(!menue.is(":visible")) return;
|
||||
menue.hide(100);
|
||||
function despawn_context_menu() {
|
||||
let menu = $(".context-menu");
|
||||
if(!menu.is(":visible")) return;
|
||||
menu.hide(100);
|
||||
if(contextMenuCloseFn) contextMenuCloseFn();
|
||||
}
|
||||
|
||||
|
@ -19,7 +16,7 @@ enum MenuEntryType {
|
|||
CLOSE,
|
||||
ENTRY,
|
||||
HR,
|
||||
EMPTY
|
||||
SUB_MENU
|
||||
}
|
||||
|
||||
class MenuEntry {
|
||||
|
@ -32,65 +29,84 @@ class MenuEntry {
|
|||
};
|
||||
};
|
||||
|
||||
static EMPTY() {
|
||||
return {
|
||||
callback: () => {},
|
||||
type: MenuEntryType.EMPTY,
|
||||
name: "",
|
||||
icon: ""
|
||||
};
|
||||
};
|
||||
|
||||
static CLOSE(callback: () => void) {
|
||||
return {
|
||||
callback: callback,
|
||||
type: MenuEntryType.EMPTY,
|
||||
type: MenuEntryType.CLOSE,
|
||||
name: "",
|
||||
icon: ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function spawnMenu(x, y, ...entries: {
|
||||
callback: () => void;
|
||||
interface ContextMenuEntry {
|
||||
callback?: () => void;
|
||||
type: MenuEntryType;
|
||||
name: (() => string) | string;
|
||||
icon: (() => string) | string;
|
||||
icon?: (() => string) | string | JQuery;
|
||||
disabled?: boolean;
|
||||
invalidPermission?: boolean;
|
||||
}[]) {
|
||||
const menu = $("#contextMenu");
|
||||
menu.empty();
|
||||
menu.hide();
|
||||
|
||||
sub_menu?: ContextMenuEntry[];
|
||||
}
|
||||
|
||||
function generate_tag(entry: ContextMenuEntry) : JQuery {
|
||||
if(entry.type == MenuEntryType.HR) {
|
||||
return $.spawn("hr");
|
||||
} else if(entry.type == MenuEntryType.ENTRY) {
|
||||
console.log(entry.icon);
|
||||
let icon = $.isFunction(entry.icon) ? entry.icon() : entry.icon;
|
||||
if(typeof(icon) === "string") {
|
||||
if(!icon || icon.length == 0) icon = "icon_empty";
|
||||
else icon = "icon " + icon;
|
||||
}
|
||||
|
||||
let tag = $.spawn("div").addClass("entry");
|
||||
tag.append(typeof(icon) === "string" ? $.spawn("div").addClass(icon) : icon);
|
||||
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
|
||||
|
||||
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
|
||||
else {
|
||||
tag.click(function () {
|
||||
if($.isFunction(entry.callback)) entry.callback();
|
||||
despawn_context_menu();
|
||||
});
|
||||
}
|
||||
return tag;
|
||||
} else if(entry.type == MenuEntryType.SUB_MENU) {
|
||||
let icon = $.isFunction(entry.icon) ? entry.icon() : entry.icon;
|
||||
if(typeof(icon) === "string") {
|
||||
if(!icon || icon.length == 0) icon = "icon_empty";
|
||||
else icon = "icon " + icon;
|
||||
}
|
||||
|
||||
let tag = $.spawn("div").addClass("entry").addClass("sub-container");
|
||||
tag.append(typeof(icon) === "string" ? $.spawn("div").addClass(icon) : icon);
|
||||
tag.append($.spawn("div").html($.isFunction(entry.name) ? entry.name() : entry.name));
|
||||
|
||||
tag.append($.spawn("div").addClass("arrow right"));
|
||||
|
||||
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
|
||||
else {
|
||||
let menu = $.spawn("div").addClass("sub-menu").addClass("context-menu");
|
||||
for(let e of entry.sub_menu)
|
||||
menu.append(generate_tag(e));
|
||||
menu.appendTo(tag);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
return $.spawn("div").text("undefined");
|
||||
}
|
||||
|
||||
function spawn_context_menu(x, y, ...entries: ContextMenuEntry[]) {
|
||||
const menu = $("#contextMenu").finish().empty();
|
||||
contextMenuCloseFn = undefined;
|
||||
|
||||
let index = 0;
|
||||
|
||||
for(let entry of entries){
|
||||
if(entry.type == MenuEntryType.HR) {
|
||||
menu.append("<hr>");
|
||||
} else if(entry.type == MenuEntryType.CLOSE) {
|
||||
if(entry.type == MenuEntryType.CLOSE) {
|
||||
contextMenuCloseFn = entry.callback;
|
||||
} else if(entry.type == MenuEntryType.ENTRY) {
|
||||
let icon = $.isFunction(entry.icon) ? entry.icon() : entry.icon;
|
||||
if(icon.length == 0) icon = "icon_empty";
|
||||
else icon = "icon " + icon;
|
||||
|
||||
let tag = $.spawn("li");
|
||||
tag.append("<div class='" + icon + "'></div>");
|
||||
tag.append("<div>" + ($.isFunction(entry.name) ? entry.name() : entry.name) + "</div>");
|
||||
|
||||
menu.append(tag);
|
||||
|
||||
if(entry.disabled || entry.invalidPermission) tag.addClass("disabled");
|
||||
else {
|
||||
tag.click(function () {
|
||||
if($.isFunction(entry.callback)) entry.callback();
|
||||
despawnContextMenu();
|
||||
});
|
||||
}
|
||||
}
|
||||
} else
|
||||
menu.append(generate_tag(entry));
|
||||
}
|
||||
|
||||
menu.show(100);
|
||||
|
|
|
@ -159,6 +159,8 @@ function loadDebug() {
|
|||
"js/ui/modal/ModalChangeVolume.js",
|
||||
"js/ui/modal/ModalBanClient.js",
|
||||
"js/ui/modal/ModalYesNo.js",
|
||||
"js/ui/modal/ModalPermissionEdit.js",
|
||||
"js/ui/modal/ModalServerGroupDialog.js",
|
||||
|
||||
"js/ui/channel.js",
|
||||
"js/ui/client.js",
|
||||
|
@ -263,13 +265,6 @@ function loadTemplates() {
|
|||
});
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
browserSpecs: {
|
||||
name: string,
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
navigator.browserSpecs = (function(){
|
||||
let ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
|
||||
if(/trident/i.test(M[1])){
|
||||
|
|
|
@ -87,6 +87,8 @@ function main() {
|
|||
console.log("Response: " + flag);
|
||||
})
|
||||
*/
|
||||
|
||||
//Modals.spawnPermissionEdit();
|
||||
}
|
||||
|
||||
app.loadedListener.push(() => {
|
||||
|
|
|
@ -18,6 +18,11 @@ class GroupProperties {
|
|||
namemode: number = 0;
|
||||
}
|
||||
|
||||
class GroupPermissionRequest {
|
||||
group_id: number;
|
||||
promise: LaterPromise<PermissionValue[]>;
|
||||
}
|
||||
|
||||
class Group {
|
||||
properties: GroupProperties = new GroupProperties();
|
||||
|
||||
|
@ -31,6 +36,7 @@ class Group {
|
|||
requiredMemberAddPower: number = 0;
|
||||
requiredMemberRemovePower: number = 0;
|
||||
|
||||
|
||||
constructor(handle: GroupManager, id: number, target: GroupTarget, type: GroupType, name: string) {
|
||||
this.handle = handle;
|
||||
this.id = id;
|
||||
|
@ -58,11 +64,15 @@ class GroupManager {
|
|||
serverGroups: Group[] = [];
|
||||
channelGroups: Group[] = [];
|
||||
|
||||
private requests_group_permissions: GroupPermissionRequest[] = [];
|
||||
constructor(client: TSClient) {
|
||||
this.handle = client;
|
||||
|
||||
this.handle.serverConnection.commandHandler["notifyservergrouplist"] = this.onServerGroupList.bind(this);
|
||||
this.handle.serverConnection.commandHandler["notifychannelgrouplist"] = this.onServerGroupList.bind(this);
|
||||
|
||||
this.handle.serverConnection.commandHandler["notifyservergrouppermlist"] = this.onPermissionList.bind(this);
|
||||
this.handle.serverConnection.commandHandler["notifychannelgrouppermlist"] = this.onPermissionList.bind(this);
|
||||
}
|
||||
|
||||
requestGroups(){
|
||||
|
@ -145,4 +155,47 @@ class GroupManager {
|
|||
console.log("Got " + json.length + " new " + target + " groups:");
|
||||
}
|
||||
|
||||
request_permissions(group: Group) : Promise<PermissionValue[]> { //database_empty_result
|
||||
for(let request of this.requests_group_permissions)
|
||||
if(request.group_id == group.id && request.promise.time() + 1000 > Date.now())
|
||||
return request.promise;
|
||||
let req = new GroupPermissionRequest();
|
||||
req.group_id = group.id;
|
||||
req.promise = new LaterPromise<PermissionValue[]>();
|
||||
this.requests_group_permissions.push(req);
|
||||
|
||||
this.handle.serverConnection.sendCommand(group.target == GroupTarget.SERVER ? "servergrouppermlist" : "channelgrouppermlist", {
|
||||
cgid: group.id,
|
||||
sgid: group.id
|
||||
}).catch(error => {
|
||||
if(error instanceof CommandResult && error.id == 0x0501)
|
||||
req.promise.resolved([]);
|
||||
else
|
||||
req.promise.rejected(error);
|
||||
});
|
||||
return req.promise;
|
||||
}
|
||||
|
||||
private onPermissionList(json: any[]) {
|
||||
let group = json[0]["sgid"] ? this.serverGroup(parseInt(json[0]["sgid"])) : this.channelGroup(parseInt(json[0]["cgid"]));
|
||||
if(!group) {
|
||||
log.error(LogCategory.PERMISSIONS, "Got group permissions for group %o/%o, but its not a registered group!", json[0]["sgid"], json[0]["cgid"]);
|
||||
return;
|
||||
}
|
||||
let requests: GroupPermissionRequest[] = [];
|
||||
for(let req of this.requests_group_permissions)
|
||||
if(req.group_id == group.id)
|
||||
requests.push(req);
|
||||
|
||||
if(requests.length == 0) {
|
||||
log.warn(LogCategory.PERMISSIONS, "Got group permissions for group %o/%o, but it was never requested!", json[0]["sgid"], json[0]["cgid"]);
|
||||
return;
|
||||
}
|
||||
|
||||
let permissions = PermissionManager.parse_permission_bulk(json, this.handle.permissions);
|
||||
for(let req of requests) {
|
||||
this.requests_group_permissions.remove(req);
|
||||
req.promise.resolved(permissions);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -294,9 +294,26 @@ class PermissionInfo {
|
|||
description: string;
|
||||
}
|
||||
|
||||
class PermissionGroup {
|
||||
begin: number;
|
||||
end: number;
|
||||
deep: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
class GroupedPermissions {
|
||||
group: PermissionGroup;
|
||||
permissions: PermissionInfo[];
|
||||
children: GroupedPermissions[];
|
||||
parent: GroupedPermissions;
|
||||
}
|
||||
|
||||
class PermissionValue {
|
||||
readonly type: PermissionInfo;
|
||||
value: number;
|
||||
flag_skip: boolean;
|
||||
flag_negate: boolean;
|
||||
granted_value: number;
|
||||
|
||||
constructor(type, value) {
|
||||
this.type = type;
|
||||
|
@ -330,23 +347,103 @@ class ChannelPermissionRequest {
|
|||
callback_error: ((_: any) => any)[] = [];
|
||||
}
|
||||
|
||||
class TeaPermissionRequest {
|
||||
client_id?: number;
|
||||
channel_id?: number;
|
||||
promise: LaterPromise<PermissionValue[]>;
|
||||
}
|
||||
|
||||
class PermissionManager {
|
||||
readonly handle: TSClient;
|
||||
|
||||
permissionList: PermissionInfo[] = [];
|
||||
permissionGroups: PermissionGroup[] = [];
|
||||
neededPermissions: NeededPermissionValue[] = [];
|
||||
|
||||
requests_channel_permissions: ChannelPermissionRequest[] = [];
|
||||
requests_client_permissions: TeaPermissionRequest[] = [];
|
||||
requests_client_channel_permissions: TeaPermissionRequest[] = [];
|
||||
|
||||
initializedListener: ((initialized: boolean) => void)[] = [];
|
||||
private _cacheNeededPermissions: any;
|
||||
|
||||
/* Static info mapping until TeaSpeak implements a detailed info */
|
||||
static readonly group_mapping: {name: string, deep: number}[] = [
|
||||
{name: "Global", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Virtual server management", deep: 1},
|
||||
{name: "Administration", deep: 1},
|
||||
{name: "Settings", deep: 1},
|
||||
{name: "Virtual Server", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Administration", deep: 1},
|
||||
{name: "Settings", deep: 1},
|
||||
{name: "Channel", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Create", deep: 1},
|
||||
{name: "Modify", deep: 1},
|
||||
{name: "Delete", deep: 1},
|
||||
{name: "Access", deep: 1},
|
||||
{name: "Group", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Create", deep: 1},
|
||||
{name: "Modify", deep: 1},
|
||||
{name: "Delete", deep: 1},
|
||||
{name: "Client", deep: 0},
|
||||
{name: "Information", deep: 1},
|
||||
{name: "Admin", deep: 1},
|
||||
{name: "Basics", deep: 1},
|
||||
{name: "Modify", deep: 1},
|
||||
//TODO Music bot
|
||||
{name: "File Transfer", deep: 0},
|
||||
];
|
||||
private _group_mapping;
|
||||
|
||||
public static parse_permission_bulk(json: any[], manager: PermissionManager) : PermissionValue[] {
|
||||
let permissions: PermissionValue[] = [];
|
||||
for(let perm of json) {
|
||||
let perm_id = parseInt(perm["permid"]);
|
||||
let perm_grant = (perm_id & (1 << 15)) > 0;
|
||||
if(perm_grant)
|
||||
perm_id &= ~(1 << 15);
|
||||
|
||||
let perm_info = manager.resolveInfo(perm_id);
|
||||
if(!perm_info) {
|
||||
log.warn(LogCategory.PERMISSIONS, "Got unknown permission id (%o/%o (%o))!", perm["permid"], perm_id, perm["permsid"]);
|
||||
return;
|
||||
}
|
||||
|
||||
let permission: PermissionValue;
|
||||
for(let ref_perm of permissions) {
|
||||
if(ref_perm.type == perm_info) {
|
||||
permission = ref_perm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!permission) {
|
||||
permission = new PermissionValue(perm_info, 0);
|
||||
permission.granted_value = undefined;
|
||||
permission.value = undefined;
|
||||
permissions.push(permission);
|
||||
}
|
||||
if(perm_grant) {
|
||||
permission.granted_value = parseInt(perm["permvalue"]);
|
||||
} else {
|
||||
permission.value = parseInt(perm["permvalue"]);
|
||||
permission.flag_negate = perm["permnegated"] == "1";
|
||||
permission.flag_skip = perm["permskip"] == "1";
|
||||
}
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
constructor(client: TSClient) {
|
||||
this.handle = client;
|
||||
|
||||
this.handle.serverConnection.commandHandler["notifyclientneededpermissions"] = this.onNeededPermissions.bind(this);
|
||||
this.handle.serverConnection.commandHandler["notifypermissionlist"] = this.onPermissionList.bind(this);
|
||||
this.handle.serverConnection.commandHandler["notifychannelpermlist"] = this.onChannelPermList.bind(this);
|
||||
this.handle.serverConnection.commandHandler["notifyclientpermlist"] = this.onClientPermList.bind(this);
|
||||
}
|
||||
|
||||
initialized() : boolean {
|
||||
|
@ -359,10 +456,25 @@ class PermissionManager {
|
|||
|
||||
private onPermissionList(json) {
|
||||
this.permissionList = [];
|
||||
this.permissionGroups = [];
|
||||
this._group_mapping = PermissionManager.group_mapping.slice();
|
||||
|
||||
let group = log.group(log.LogType.TRACE, LogCategory.PERMISSIONS, "Permission mapping");
|
||||
for(let e of json) {
|
||||
if(e["group_id_end"]) continue; //Skip all group ids (may use later?)
|
||||
if(e["group_id_end"]) {
|
||||
let group = new PermissionGroup();
|
||||
group.begin = this.permissionGroups.length ? this.permissionGroups.last().end : 0;
|
||||
group.end = parseInt(e["group_id_end"]);
|
||||
group.deep = 0;
|
||||
group.name = "Group " + e["group_id_end"];
|
||||
|
||||
let info = this._group_mapping.pop_front();
|
||||
if(info) {
|
||||
group.name = info.name;
|
||||
group.deep = info.deep;
|
||||
}
|
||||
this.permissionGroups.push(group);
|
||||
}
|
||||
|
||||
let perm = new PermissionInfo();
|
||||
perm.name = e["permname"];
|
||||
|
@ -430,19 +542,9 @@ class PermissionManager {
|
|||
}
|
||||
|
||||
private onChannelPermList(json) {
|
||||
let permissions: PermissionValue[] = [];
|
||||
let channelId: number = parseInt(json[0]["cid"]);
|
||||
for(let element of json) {
|
||||
let permission = this.resolveInfo(element["permid"]);
|
||||
//TODO granted skipped and negated permissions
|
||||
if(!permission) {
|
||||
log.error(LogCategory.PERMISSIONS, "Failed to parse channel permission with id %o", element["permid"]);
|
||||
continue;
|
||||
}
|
||||
|
||||
permissions.push(new PermissionValue(permission, element["permvalue"]));
|
||||
}
|
||||
|
||||
let permissions = PermissionManager.parse_permission_bulk(json, this.handle.permissions);
|
||||
log.debug(LogCategory.PERMISSIONS, "Got channel permissions for channel %o", channelId);
|
||||
for(let element of this.requests_channel_permissions) {
|
||||
if(element.channel_id == channelId) {
|
||||
|
@ -466,7 +568,7 @@ class PermissionManager {
|
|||
return new Promise<PermissionValue[]>((resolve, reject) => {
|
||||
let request: ChannelPermissionRequest;
|
||||
for(let element of this.requests_channel_permissions)
|
||||
if(element.requested + 1000 < Date.now() && request.channel_id == channelId) {
|
||||
if(element.requested + 1000 < Date.now() && element.channel_id == channelId) {
|
||||
request = element;
|
||||
break;
|
||||
}
|
||||
|
@ -482,6 +584,58 @@ class PermissionManager {
|
|||
});
|
||||
}
|
||||
|
||||
requestClientPermissions(client_id: number) : Promise<PermissionValue[]> {
|
||||
for(let request of this.requests_client_permissions)
|
||||
if(request.client_id == client_id && request.promise.time() + 1000 > Date.now())
|
||||
return request.promise;
|
||||
|
||||
let request: TeaPermissionRequest = {} as any;
|
||||
request.client_id = client_id;
|
||||
request.promise = new LaterPromise<PermissionValue[]>();
|
||||
|
||||
this.handle.serverConnection.sendCommand("clientpermlist", {cldbid: client_id}).catch(error => {
|
||||
if(error instanceof CommandResult && error.id == 0x0501)
|
||||
request.promise.resolved([]);
|
||||
else
|
||||
request.promise.rejected(error);
|
||||
});
|
||||
|
||||
this.requests_client_permissions.push(request);
|
||||
return request.promise;
|
||||
}
|
||||
|
||||
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.sendCommand("channelclientpermlist", {cldbid: client_id, cid: channel_id}).catch(error => {
|
||||
if(error instanceof CommandResult && error.id == 0x0501)
|
||||
request.promise.resolved([]);
|
||||
else
|
||||
request.promise.rejected(error);
|
||||
});
|
||||
|
||||
this.requests_client_channel_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
neededPermission(key: number | string | PermissionType | PermissionInfo) : PermissionValue {
|
||||
for(let perm of this.neededPermissions)
|
||||
if(perm.type.id == key || perm.type.name == key || perm.type == key)
|
||||
|
@ -495,6 +649,43 @@ class PermissionManager {
|
|||
let result = new NeededPermissionValue(info, -2);
|
||||
this.neededPermissions.push(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
groupedPermissions() : GroupedPermissions[] {
|
||||
let result: GroupedPermissions[] = [];
|
||||
let current: GroupedPermissions;
|
||||
|
||||
for(let group of this.permissionGroups) {
|
||||
if(group.deep == 0) {
|
||||
current = new GroupedPermissions();
|
||||
current.group = group;
|
||||
current.parent = undefined;
|
||||
current.children = [];
|
||||
current.permissions = [];
|
||||
result.push(current);
|
||||
} else {
|
||||
if(!current) {
|
||||
throw "invalid order!";
|
||||
} else {
|
||||
while(group.deep <= current.group.deep)
|
||||
current = current.parent;
|
||||
|
||||
let parent = current;
|
||||
current = new GroupedPermissions();
|
||||
current.group = group;
|
||||
current.parent = parent;
|
||||
current.children = [];
|
||||
current.permissions = [];
|
||||
parent.children.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
for(let permission of this.permissionList)
|
||||
if(permission.id > current.group.begin && permission.id <= current.group.end)
|
||||
current.permissions.push(permission);
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
34
js/proto.ts
34
js/proto.ts
|
@ -26,29 +26,29 @@ interface String {
|
|||
}
|
||||
|
||||
if(!JSON.map_to) {
|
||||
JSON.map_to = function<T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number) : T {
|
||||
if(!validator) validator = (a, b) => true;
|
||||
JSON.map_to = function <T>(object: T, json: any, variables?: string | string[], validator?: (map_field: string, map_value: string) => boolean, variable_direction?: number): T {
|
||||
if (!validator) validator = (a, b) => true;
|
||||
|
||||
if(!variables) {
|
||||
if (!variables) {
|
||||
variables = [];
|
||||
|
||||
if(!variable_direction || variable_direction == 0) {
|
||||
for(let field in json)
|
||||
if (!variable_direction || variable_direction == 0) {
|
||||
for (let field in json)
|
||||
variables.push(field);
|
||||
} else if(variable_direction == 1) {
|
||||
for(let field in object)
|
||||
} else if (variable_direction == 1) {
|
||||
for (let field in object)
|
||||
variables.push(field);
|
||||
}
|
||||
} else if(!Array.isArray(variables)) {
|
||||
} else if (!Array.isArray(variables)) {
|
||||
variables = [variables];
|
||||
}
|
||||
|
||||
for(let field of variables) {
|
||||
if(!json[field]) {
|
||||
for (let field of variables) {
|
||||
if (!json[field]) {
|
||||
console.trace("Json does not contains %s", field);
|
||||
continue;
|
||||
}
|
||||
if(!validator(field, json[field])) {
|
||||
if (!validator(field, json[field])) {
|
||||
console.trace("Validator results in false for %s", field);
|
||||
continue;
|
||||
}
|
||||
|
@ -108,7 +108,9 @@ if(typeof ($) !== "undefined") {
|
|||
}
|
||||
if(!$.prototype.renderTag) {
|
||||
$.prototype.renderTag = function (values?: any) : JQuery {
|
||||
return $(this.render(values));
|
||||
let result = $(this.render(values));
|
||||
result.find("node").each((index, element) => { $(element).replaceWith(values[$(element).attr("key")]); });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,4 +184,12 @@ interface Window {
|
|||
readonly OfflineAudioContext: typeof OfflineAudioContext;
|
||||
readonly webkitOfflineAudioContext: typeof webkitOfflineAudioContext;
|
||||
readonly RTCPeerConnection: typeof RTCPeerConnection;
|
||||
readonly Pointer_stringify: any;
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
browserSpecs: {
|
||||
name: string,
|
||||
version: string
|
||||
};
|
||||
}
|
|
@ -341,7 +341,7 @@ class ChannelEntry {
|
|||
flagDelete = this.channelTree.client.permissions.neededPermission(PermissionType.B_CHANNEL_DELETE_TEMPORARY).granted(1);
|
||||
}
|
||||
|
||||
spawnMenu(x, y, {
|
||||
spawn_context_menu(x, y, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_switch",
|
||||
name: "<b>Switch to channel</b>",
|
||||
|
|
114
js/ui/client.ts
114
js/ui/client.ts
|
@ -1,5 +1,6 @@
|
|||
/// <reference path="channel.ts" />
|
||||
/// <reference path="modal/ModalChangeVolume.ts" />
|
||||
/// <reference path="modal/ModalServerGroupDialog.ts" />
|
||||
|
||||
enum ClientType {
|
||||
CLIENT_VOICE,
|
||||
|
@ -106,10 +107,113 @@ class ClientEntry {
|
|||
}
|
||||
}
|
||||
|
||||
protected assignment_context() : ContextMenuEntry[] {
|
||||
let server_groups: ContextMenuEntry[] = [];
|
||||
for(let group of this.channelTree.client.groups.serverGroups.sort(GroupManager.sorter())) {
|
||||
if(group.type == GroupType.NORMAL) continue;
|
||||
|
||||
let entry: ContextMenuEntry = {} as any;
|
||||
|
||||
{
|
||||
let tag = $.spawn("label").addClass("checkbox");
|
||||
$.spawn("input").attr("type", "checkbox").prop("checked", this.groupAssigned(group)).appendTo(tag);
|
||||
$.spawn("span").addClass("checkmark").appendTo(tag);
|
||||
entry.icon = tag;
|
||||
}
|
||||
|
||||
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
|
||||
if(this.groupAssigned(group)) {
|
||||
entry.callback = () => {
|
||||
this.channelTree.client.serverConnection.sendCommand("servergroupdelclient", {
|
||||
sgid: group.id,
|
||||
cldbid: this.properties.client_database_id
|
||||
});
|
||||
};
|
||||
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_ADD_POWER).granted(group.requiredMemberRemovePower);
|
||||
} else {
|
||||
entry.callback = () => {
|
||||
this.channelTree.client.serverConnection.sendCommand("servergroupaddclient", {
|
||||
sgid: group.id,
|
||||
cldbid: this.properties.client_database_id
|
||||
});
|
||||
};
|
||||
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_REMOVE_POWER).granted(group.requiredMemberAddPower);
|
||||
}
|
||||
entry.type = MenuEntryType.ENTRY;
|
||||
server_groups.push(entry);
|
||||
}
|
||||
|
||||
let channel_groups: ContextMenuEntry[] = [];
|
||||
for(let group of this.channelTree.client.groups.channelGroups.sort(GroupManager.sorter())) {
|
||||
if(group.type != GroupType.NORMAL) continue;
|
||||
|
||||
let entry: ContextMenuEntry = {} as any;
|
||||
{
|
||||
let tag = $.spawn("label").addClass("checkbox");
|
||||
$.spawn("input").attr("type", "checkbox").prop("checked", this.assignedChannelGroup() == group.id).appendTo(tag);
|
||||
$.spawn("span").addClass("checkmark").appendTo(tag);
|
||||
entry.icon = tag;
|
||||
}
|
||||
entry.name = group.name + " [" + (group.properties.savedb ? "perm" : "tmp") + "]";
|
||||
entry.callback = () => {
|
||||
this.channelTree.client.serverConnection.sendCommand("setclientchannelgroup", {
|
||||
cldbid: this.properties.client_database_id,
|
||||
cgid: group.id,
|
||||
cid: this.currentChannel().channelId
|
||||
});
|
||||
};
|
||||
entry.disabled = !this.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_ADD_POWER).granted(group.requiredMemberRemovePower);
|
||||
entry.type = MenuEntryType.ENTRY;
|
||||
channel_groups.push(entry);
|
||||
}
|
||||
|
||||
return [{
|
||||
type: MenuEntryType.SUB_MENU,
|
||||
icon: "client-permission_server_groups",
|
||||
name: "Set server group",
|
||||
sub_menu: [
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-permission_server_groups",
|
||||
name: "Server groups dialog",
|
||||
callback: () => {
|
||||
Modals.createServerGroupAssignmentModal(this, (group, flag) => {
|
||||
if(flag) {
|
||||
return this.channelTree.client.serverConnection.sendCommand("servergroupaddclient", {
|
||||
sgid: group.id,
|
||||
cldbid: this.properties.client_database_id
|
||||
}).then(result => true);
|
||||
} else
|
||||
return this.channelTree.client.serverConnection.sendCommand("servergroupdelclient", {
|
||||
sgid: group.id,
|
||||
cldbid: this.properties.client_database_id
|
||||
}).then(result => true);
|
||||
});
|
||||
}
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
...server_groups
|
||||
]
|
||||
},{
|
||||
type: MenuEntryType.SUB_MENU,
|
||||
icon: "client-permission_channel",
|
||||
name: "Set channel group",
|
||||
sub_menu: [
|
||||
...channel_groups
|
||||
]
|
||||
},{
|
||||
type: MenuEntryType.SUB_MENU,
|
||||
icon: "client-permission_client",
|
||||
name: "Permissions",
|
||||
disabled: true,
|
||||
sub_menu: [ ]
|
||||
}];
|
||||
}
|
||||
|
||||
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
|
||||
const _this = this;
|
||||
|
||||
spawnMenu(x, y,
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-change_nickname",
|
||||
|
@ -151,6 +255,8 @@ class ClientEntry {
|
|||
}, { width: 400, maxLength: 1024 }).open();
|
||||
}
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
...this.assignment_context(),
|
||||
MenuEntry.HR(), {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-move_client_to_own_channel",
|
||||
|
@ -524,7 +630,7 @@ class LocalClientEntry extends ClientEntry {
|
|||
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
|
||||
const _self = this;
|
||||
|
||||
spawnMenu(x, y,
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
name: "<b>Change name</b>",
|
||||
icon: "client-change_nickname",
|
||||
|
@ -640,7 +746,7 @@ class MusicClientEntry extends ClientEntry {
|
|||
}
|
||||
|
||||
showContextMenu(x: number, y: number, on_close: () => void = undefined): void {
|
||||
spawnMenu(x, y,
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
name: "<b>Change bot name</b>",
|
||||
icon: "client-change_nickname",
|
||||
|
@ -661,6 +767,8 @@ class MusicClientEntry extends ClientEntry {
|
|||
type: MenuEntryType.ENTRY
|
||||
},
|
||||
MenuEntry.HR(),
|
||||
...super.assignment_context(),
|
||||
MenuEntry.HR(),
|
||||
{
|
||||
name: "Delete bot",
|
||||
icon: "client-delete",
|
||||
|
|
|
@ -35,7 +35,7 @@ class ControlBar {
|
|||
this.htmlTag.find(".btn_mute_input").on('click', this.onInputMute.bind(this));
|
||||
this.htmlTag.find(".btn_mute_output").on('click', this.onOutputMute.bind(this));
|
||||
this.htmlTag.find(".btn_open_settings").on('click', this.onOpenSettings.bind(this));
|
||||
|
||||
this.htmlTag.find(".btn_permissions").on('click', this.onPermission.bind(this));
|
||||
{
|
||||
let tokens = this.htmlTag.find(".btn_token");
|
||||
tokens.find(".button-dropdown").on('click', () => {
|
||||
|
@ -247,4 +247,13 @@ class ControlBar {
|
|||
private on_token_list() {
|
||||
createErrorModal("Not implemented", "Token list is not implemented yet!").open();
|
||||
}
|
||||
|
||||
private onPermission() {
|
||||
let button = this.htmlTag.find(".btn_permissions");
|
||||
button.addClass("activated");
|
||||
setTimeout(() => {
|
||||
Modals.spawnPermissionEdit().open();
|
||||
button.removeClass("activated");
|
||||
}, 0);
|
||||
}
|
||||
}
|
|
@ -117,7 +117,6 @@ class ClientInfoManager extends InfoManager<ClientEntry> {
|
|||
let properties = this.buildProperties(client);
|
||||
|
||||
let rendered = $("#tmpl_selected_client").renderTag([properties]);
|
||||
rendered.find("node").each((index, element) => { $(element).replaceWith(properties[$(element).attr("key")]); });
|
||||
html_tag.append(rendered);
|
||||
|
||||
this.registerInterval(setInterval(() => {
|
||||
|
@ -190,7 +189,6 @@ class ServerInfoManager extends InfoManager<ServerEntry> {
|
|||
properties["property_" + key] = server.properties[key];
|
||||
|
||||
let rendered = $("#tmpl_selected_server").renderTag([properties]);
|
||||
rendered.find("node").each((index, element) => { $(element).replaceWith(properties[$(element).attr("key")]); });
|
||||
|
||||
this.registerInterval(setInterval(() => {
|
||||
html_tag.find(".update_onlinetime").text(formatDate(server.calculateUptime()));
|
||||
|
@ -255,7 +253,6 @@ class ChannelInfoManager extends InfoManager<ChannelEntry> {
|
|||
});
|
||||
|
||||
let rendered = $("#tmpl_selected_channel").renderTag([properties]);
|
||||
rendered.find("node").each((index, element) => { $(element).replaceWith(properties[$(element).attr("key")]); });
|
||||
html_tag.append(rendered);
|
||||
}
|
||||
|
||||
|
@ -478,7 +475,6 @@ class MusicInfoManager extends ClientInfoManager {
|
|||
}
|
||||
|
||||
let rendered = $("#tmpl_selected_music").renderTag([properties]);
|
||||
rendered.find("node").each((index, element) => { $(element).replaceWith(properties[$(element).attr("key")]); });
|
||||
html_tag.append(rendered);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,874 @@
|
|||
/// <reference path="../../utils/modal.ts" />
|
||||
/// <reference path="../../proto.ts" />
|
||||
/// <reference path="../../client.ts" />
|
||||
|
||||
namespace Modals {
|
||||
export function spawnPermissionEdit() : Modal {
|
||||
const connectModal = createModal({
|
||||
header: function() {
|
||||
return "Server Permissions";
|
||||
},
|
||||
body: function () {
|
||||
let properties: any = {};
|
||||
|
||||
let start, end;
|
||||
start = Date.now();
|
||||
{
|
||||
let groups = globalClient.permissions.groupedPermissions();
|
||||
let root_entry: any = {};
|
||||
root_entry.entries = [];
|
||||
let entry_stack: any[] = [root_entry];
|
||||
|
||||
let insert_group = (group: GroupedPermissions) => {
|
||||
let group_entry: any = {};
|
||||
group_entry.type = "group";
|
||||
group_entry.name = group.group.name;
|
||||
group_entry.entries = [];
|
||||
entry_stack.last().entries.push(group_entry);
|
||||
|
||||
entry_stack.push(group_entry);
|
||||
for(let child of group.children)
|
||||
insert_group(child);
|
||||
entry_stack.pop();
|
||||
|
||||
for(let perm of group.permissions) {
|
||||
let entry: any = {};
|
||||
entry.type = "entry";
|
||||
entry.permission_name = perm.name;
|
||||
entry.unset = true;
|
||||
group_entry.entries.push(entry);
|
||||
|
||||
{
|
||||
let tag: JQuery<HTMLElement>;
|
||||
if(perm.name.startsWith("b_")) {
|
||||
tag = $.spawn("label").addClass("checkbox");
|
||||
$.spawn("input").attr("type", "checkbox").appendTo(tag);
|
||||
$.spawn("span").addClass("checkmark").appendTo(tag);
|
||||
} else {
|
||||
tag = $.spawn("input");
|
||||
tag.attr("type", "number");
|
||||
tag.attr("min-value", -1);
|
||||
tag.attr("max-value", 9999999999); //TODO use there may the grant permission
|
||||
}
|
||||
root_entry[perm.name + "_value"] = tag;
|
||||
}
|
||||
{
|
||||
let tag = $.spawn("label").addClass("checkbox");
|
||||
$.spawn("input").attr("type", "checkbox").appendTo(tag);
|
||||
$.spawn("span").addClass("checkmark").appendTo(tag);
|
||||
root_entry[perm.name + "_skip"] = tag;
|
||||
}
|
||||
{
|
||||
let tag = $.spawn("label").addClass("checkbox");
|
||||
$.spawn("input").attr("type", "checkbox").appendTo(tag);
|
||||
$.spawn("span").addClass("checkmark").appendTo(tag);
|
||||
root_entry[perm.name + "_negate"] = tag;
|
||||
}
|
||||
{
|
||||
let tag = $.spawn("input");
|
||||
tag.attr("type", "number");
|
||||
tag.attr("min-value", -1);
|
||||
tag.attr("max-value", 9999999999);
|
||||
root_entry[perm.name + "_grant"] = tag;
|
||||
}
|
||||
//{{>permission_name}}_value
|
||||
}
|
||||
};
|
||||
groups.forEach(entry => insert_group(entry));
|
||||
|
||||
root_entry.permissions = root_entry.entries;
|
||||
properties["permissions_group_server"] = $("#tmpl_permission_explorer").renderTag(root_entry);
|
||||
properties["permissions_group_channel"] = properties["permissions_group_server"].clone();
|
||||
properties["permissions_channel"] = properties["permissions_group_server"].clone();
|
||||
properties["permissions_client"] = properties["permissions_group_server"].clone();
|
||||
properties["permissions_client_channel"] = properties["permissions_group_server"].clone();
|
||||
}
|
||||
end = Date.now();
|
||||
console.log("Generate: %s", end - start);
|
||||
start = end;
|
||||
|
||||
let tag = $.spawn("div").append($("#tmpl_server_permissions").renderTag(properties)).tabify(false);
|
||||
end = Date.now();
|
||||
console.log("Tab: %s", end - start);
|
||||
start = end;
|
||||
|
||||
apply_server_groups(tag.find(".layout-group-server"));
|
||||
apply_channel_groups(tag.find(".layout-group-channel"));
|
||||
apply_channel_permission(tag.find(".layout-channel"));
|
||||
apply_client_permission(tag.find(".layout-client"));
|
||||
apply_client_channel_permission(tag.find(".layout-client-channel"));
|
||||
end = Date.now();
|
||||
console.log("Listeners: %s", end - start);
|
||||
start = end;
|
||||
return tag;
|
||||
},
|
||||
footer: function () {
|
||||
let tag = $.spawn("div");
|
||||
tag.css("text-align", "right");
|
||||
tag.css("margin-top", "3px");
|
||||
tag.css("margin-bottom", "6px");
|
||||
tag.addClass("modal-button-group");
|
||||
|
||||
let buttonOk = $.spawn("button");
|
||||
buttonOk.text("Close").addClass("btn_close");
|
||||
tag.append(buttonOk);
|
||||
return tag;
|
||||
},
|
||||
|
||||
width: "90%"
|
||||
});
|
||||
|
||||
connectModal.htmlTag.find(".btn_close").on('click', () => {
|
||||
connectModal.close();
|
||||
});
|
||||
|
||||
return connectModal;
|
||||
}
|
||||
|
||||
function display_permissions(permission_tag: JQuery, permissions: PermissionValue[]) {
|
||||
permission_tag.find(".permission").addClass("unset").find(".permission-grant input").val("");
|
||||
|
||||
for(let perm of permissions) {
|
||||
let tag = permission_tag.find("." + perm.type.name);
|
||||
if(perm.value != undefined) {
|
||||
tag.removeClass("unset");
|
||||
{
|
||||
let value = tag.find(".permission-value input");
|
||||
if(value.attr("type") == "checkbox")
|
||||
value.prop("checked", perm.value == 1);
|
||||
else
|
||||
value.val(perm.value);
|
||||
}
|
||||
tag.find(".permission-skip input").prop("checked", perm.flag_skip);
|
||||
tag.find(".permission-negate input").prop("checked", perm.flag_negate);
|
||||
}
|
||||
if(perm.granted_value != undefined) {
|
||||
tag.find(".permission-grant input").val(perm.granted_value);
|
||||
}
|
||||
}
|
||||
permission_tag.find(".filter-input").trigger('change');
|
||||
}
|
||||
|
||||
function make_permission_editor(tag: JQuery, default_number: number, cb_edit: (type: PermissionInfo, value?: number, skip?: boolean, negate?: boolean) => Promise<boolean>, cb_grant_edit: (type: PermissionInfo, value?: number) => Promise<boolean>) {
|
||||
tag = tag.hasClass("permission-explorer") ? tag : tag.find(".permission-explorer");
|
||||
const list = tag.find(".list");
|
||||
list.css("max-height", document.body.clientHeight * .7)
|
||||
|
||||
list.find(".arrow").each((idx, _entry) => {
|
||||
let entry = $(_entry);
|
||||
let entries = entry.parentsUntil(".group").first().parent().find("> .group-entries");
|
||||
entry.on('click', () => {
|
||||
if(entry.hasClass("right")) {
|
||||
entries.show();
|
||||
} else {
|
||||
entries.hide();
|
||||
}
|
||||
entry.toggleClass("right down");
|
||||
});
|
||||
});
|
||||
|
||||
tag.find(".filter-input, .filter-granted").on('keyup change', event => {
|
||||
let filter_mask = tag.find(".filter-input").val() as string;
|
||||
let req_granted = tag.find('.filter-granted').prop("checked");
|
||||
|
||||
tag.find(".permission").each((idx, _entry) => {
|
||||
let entry = $(_entry);
|
||||
let key = entry.find("> .filter-key");
|
||||
|
||||
let should_hide = filter_mask.length != 0 && key.text().indexOf(filter_mask) == -1;
|
||||
if(req_granted) {
|
||||
if(entry.hasClass("unset") && entry.find(".permission-grant input").val() == "")
|
||||
should_hide = true;
|
||||
}
|
||||
entry.attr("match", should_hide ? 0 : 1);
|
||||
if(should_hide)
|
||||
entry.hide();
|
||||
else
|
||||
entry.show();
|
||||
});
|
||||
tag.find(".group").each((idx, _entry) => {
|
||||
let entry = $(_entry);
|
||||
let target = entry.find(".entry:not(.group)[match=\"1\"]").length > 0;
|
||||
if(target)
|
||||
entry.show();
|
||||
else
|
||||
entry.hide();
|
||||
});
|
||||
});
|
||||
|
||||
const expend_all = (parent) => {
|
||||
(parent || list).find(".arrow").addClass("right").removeClass("down").trigger('click');
|
||||
};
|
||||
const collapse_all = (parent) => {
|
||||
(parent || list).find(".arrow").removeClass("right").addClass("down").trigger('click');
|
||||
};
|
||||
|
||||
list.on('contextmenu', event => {
|
||||
if (event.isDefaultPrevented()) return;
|
||||
event.preventDefault();
|
||||
|
||||
spawn_context_menu(event.pageX, event.pageY, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Expend all",
|
||||
callback: () => expend_all.bind(this, [undefined])
|
||||
},{
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Collapse all",
|
||||
callback: collapse_all.bind(this, [undefined])
|
||||
});
|
||||
});
|
||||
|
||||
tag.find(".title").each((idx, _entry) => {
|
||||
let entry = $(_entry);
|
||||
entry.on('click', () => {
|
||||
tag.find(".selected").removeClass("selected");
|
||||
$(_entry).addClass("selected");
|
||||
});
|
||||
|
||||
entry.on('contextmenu', event => {
|
||||
if (event.isDefaultPrevented()) return;
|
||||
event.preventDefault();
|
||||
|
||||
spawn_context_menu(event.pageX, event.pageY, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Expend group",
|
||||
callback: () => expend_all.bind(this, entry)
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Expend all",
|
||||
callback: () => expend_all.bind(this, undefined)
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Collapse group",
|
||||
callback: collapse_all.bind(this, entry)
|
||||
}, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Collapse all",
|
||||
callback: () => expend_all.bind(this, undefined)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
tag.find(".permission").each((idx, _entry) => {
|
||||
let entry = $(_entry);
|
||||
|
||||
entry.on('click', () => {
|
||||
tag.find(".selected").removeClass("selected");
|
||||
$(_entry).addClass("selected");
|
||||
});
|
||||
|
||||
entry.on('dblclick', event => {
|
||||
entry.removeClass("unset");
|
||||
|
||||
let value = entry.find("> .permission-value input");
|
||||
if(value.attr("type") == "number")
|
||||
value.focus().val(default_number).trigger('change');
|
||||
else
|
||||
value.prop("checked", true).trigger('change');
|
||||
});
|
||||
|
||||
entry.on('contextmenu', event => {
|
||||
if(event.isDefaultPrevented()) return;
|
||||
event.preventDefault();
|
||||
|
||||
let entries: ContextMenuEntry[] = [];
|
||||
|
||||
if(entry.hasClass("unset")) {
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Add permission",
|
||||
callback: () => entry.trigger('dblclick')
|
||||
});
|
||||
} else {
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Remove permission",
|
||||
callback: () => {
|
||||
entry.addClass("unset");
|
||||
entry.find(".permission-value input").val("").trigger('change');
|
||||
}
|
||||
});
|
||||
}
|
||||
if(entry.find("> .permission-grant input").val() == "") {
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Add grant permission",
|
||||
callback: () => {
|
||||
let value = entry.find("> .permission-grant input");
|
||||
value.focus().val(default_number).trigger('change');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Remove permission",
|
||||
callback: () => {
|
||||
entry.find("> .permission-grant input").val("").trigger('change');
|
||||
}
|
||||
});
|
||||
}
|
||||
entries.push(MenuEntry.HR());
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Expend all",
|
||||
callback: () => expend_all.bind(this, undefined)
|
||||
});
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Collapse all",
|
||||
callback: collapse_all.bind(this, undefined)
|
||||
});
|
||||
entries.push(MenuEntry.HR());
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Show permission description",
|
||||
callback: () => {
|
||||
createErrorModal("Not implemented!", "This function isnt implemented yet!").open();
|
||||
}
|
||||
});
|
||||
entries.push({
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "",
|
||||
name: "Copy permission name",
|
||||
callback: () => {
|
||||
copy_to_clipboard(entry.find(".permission-name").text() as string);
|
||||
}
|
||||
});
|
||||
|
||||
spawn_context_menu(event.pageX, event.pageY, ...entries);
|
||||
});
|
||||
|
||||
entry.find(".permission-value input, .permission-negate input, .permission-skip input").on('change', event => {
|
||||
let permission = globalClient.permissions.resolveInfo(entry.find(".permission-name").text());
|
||||
if(!permission) {
|
||||
console.error("Attempted to edit a not known permission! (%s)", entry.find(".permission-name").text());
|
||||
return;
|
||||
}
|
||||
|
||||
if(entry.hasClass("unset")) {
|
||||
cb_edit(permission, undefined, undefined, undefined).catch(error => {
|
||||
tag.find(".button-update").trigger('click');
|
||||
});
|
||||
} else {
|
||||
let input = entry.find(".permission-value input");
|
||||
let value = input.attr("type") == "number" ? input.val() : (input.prop("checked") ? "1" : "0");
|
||||
if(value == "" || isNaN(value as number)) value = 0;
|
||||
else value = parseInt(value as string);
|
||||
let negate = entry.find(".permission-negate input").prop("checked");
|
||||
let skip = entry.find(".permission-skip input").prop("checked");
|
||||
|
||||
cb_edit(permission, value, skip, negate).catch(error => {
|
||||
tag.find(".button-update").trigger('click');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
entry.find(".permission-grant input").on('change', event => {
|
||||
let permission = globalClient.permissions.resolveInfo(entry.find(".permission-name").text());
|
||||
if(!permission) {
|
||||
console.error("Attempted to edit a not known permission! (%s)", entry.find(".permission-name").text());
|
||||
return;
|
||||
}
|
||||
|
||||
let value = entry.find(".permission-grant input").val();
|
||||
if(value && value != "" && !isNaN(value as number)) {
|
||||
cb_grant_edit(permission, parseInt(value as string)).catch(error => {
|
||||
tag.find(".button-update").trigger('click');
|
||||
});
|
||||
} else cb_grant_edit(permission, undefined).catch(error => {
|
||||
tag.find(".button-update").trigger('click');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function build_channel_tree(channel_list: JQuery, update_button: JQuery) {
|
||||
for(let channel of globalClient.channelTree.channels) {
|
||||
let tag = $.spawn("div").addClass("channel").attr("channel-id", channel.channelId);
|
||||
globalClient.fileManager.icons.generateTag(channel.properties.channel_icon_id).appendTo(tag);
|
||||
{
|
||||
let name = $.spawn("a").text(channel.channelName() + " (" + channel.channelId + ")").addClass("name");
|
||||
//if(globalClient.channelTree.server.properties. == group.id)
|
||||
// name.addClass("default");
|
||||
name.appendTo(tag);
|
||||
}
|
||||
tag.appendTo(channel_list);
|
||||
|
||||
tag.on('click', event => {
|
||||
channel_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
update_button.trigger('click');
|
||||
});
|
||||
}
|
||||
setTimeout(() => channel_list.find('.channel').first().trigger('click'), 0);
|
||||
}
|
||||
|
||||
function apply_client_channel_permission(tag: JQuery) {
|
||||
let permission_tag = tag.find(".permission-explorer");
|
||||
let channel_list = tag.find(".list-channel .entries");
|
||||
permission_tag.addClass("disabled");
|
||||
|
||||
make_permission_editor(permission_tag, 75, (type, value, skip, negate) => {
|
||||
let cldbid = parseInt(tag.find(".client-dbid").val() as string);
|
||||
if(isNaN(cldbid)) return Promise.reject("invalid cldbid");
|
||||
|
||||
let channel_id: number = parseInt(channel_list.find(".selected").attr("channel-id"));
|
||||
let channel = globalClient.channelTree.findChannel(channel_id);
|
||||
if(!channel) {
|
||||
console.warn("Missing selected channel id for permission editor action!");
|
||||
return Promise.reject("invalid channel");
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added permission " + type.name + " with properties: %o %o %o", value, skip, negate);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("channelclientaddperm", {
|
||||
cldbid: cldbid,
|
||||
cid: channel.channelId,
|
||||
permid: type.id,
|
||||
permvalue: value,
|
||||
permskip: skip,
|
||||
permnegate: negate
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed permission " + type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("channelclientdelperm", {
|
||||
cldbid: cldbid,
|
||||
cid: channel.channelId,
|
||||
permid: type.id
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
}, (type, value) => {
|
||||
let cldbid = parseInt(tag.find(".client-dbid").val() as string);
|
||||
if(isNaN(cldbid)) return Promise.reject("invalid cldbid");
|
||||
|
||||
let channel_id: number = parseInt(channel_list.find(".selected").attr("channel-id"));
|
||||
let channel = globalClient.channelTree.findChannel(channel_id);
|
||||
if(!channel) {
|
||||
console.warn("Missing selected channel id for permission editor action!");
|
||||
return Promise.reject("invalid channel");
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added grant of %o for " + type.name, value);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("channelclientaddperm", {
|
||||
cldbid: cldbid,
|
||||
cid: channel.channelId,
|
||||
permid: type.id | (1 << 15),
|
||||
permvalue: value,
|
||||
permskip: false,
|
||||
permnegate: false
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed grant permission for %s", type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("channelclientdelperm", {
|
||||
cldbid: cldbid,
|
||||
cid: channel.channelId,
|
||||
permid: type.id | (1 << 15)
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
build_channel_tree(channel_list, permission_tag.find(".button-update"));
|
||||
permission_tag.find(".button-update").on('click', event => {
|
||||
let val = tag.find('.client-select-uid').val();
|
||||
globalClient.serverConnection.helper.info_from_uid(val as string).then(result => {
|
||||
if(!result || result.length == 0) return Promise.reject("invalid data");
|
||||
permission_tag.removeClass("disabled");
|
||||
|
||||
tag.find(".client-name").val(result[0].client_nickname);
|
||||
tag.find(".client-uid").val(result[0].client_unique_id);
|
||||
tag.find(".client-dbid").val(result[0].client_database_id);
|
||||
|
||||
let channel_id: number = parseInt(channel_list.find(".selected").attr("channel-id"));
|
||||
let channel = globalClient.channelTree.findChannel(channel_id);
|
||||
if(!channel) {
|
||||
console.warn("Missing selected channel id for permission editor action!");
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return globalClient.permissions.requestClientChannelPermissions(channel.channelId, result[0].client_database_id).then(result => display_permissions(permission_tag, result));
|
||||
}).catch(error => {
|
||||
console.log(error); //TODO error handling?
|
||||
permission_tag.addClass("disabled");
|
||||
});
|
||||
});
|
||||
tag.find(".client-select-uid").on('change', event => {
|
||||
tag.find(".button-update").trigger('click');
|
||||
});
|
||||
}
|
||||
|
||||
function apply_client_permission(tag: JQuery) {
|
||||
let permission_tag = tag.find(".permission-explorer");
|
||||
permission_tag.addClass("disabled");
|
||||
|
||||
make_permission_editor(permission_tag, 75, (type, value, skip, negate) => {
|
||||
let cldbid = parseInt(tag.find(".client-dbid").val() as string);
|
||||
if(isNaN(cldbid)) return Promise.reject("invalid cldbid");
|
||||
if(value != undefined) {
|
||||
console.log("Added permission " + type.name + " with properties: %o %o %o", value, skip, negate);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("clientaddperm", {
|
||||
cldbid: cldbid,
|
||||
permid: type.id,
|
||||
permvalue: value,
|
||||
permskip: skip,
|
||||
permnegate: negate
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed permission " + type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("clientdelperm", {
|
||||
cldbid: cldbid,
|
||||
permid: type.id
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
}, (type, value) => {
|
||||
let cldbid = parseInt(tag.find(".client-dbid").val() as string);
|
||||
if(isNaN(cldbid)) return Promise.reject("invalid cldbid");
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added grant of %o for " + type.name, value);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("clientaddperm", {
|
||||
cldbid: cldbid,
|
||||
permid: type.id | (1 << 15),
|
||||
permvalue: value,
|
||||
permskip: false,
|
||||
permnegate: false
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed grant permission for %s", type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("clientdelperm", {
|
||||
cldbid: cldbid,
|
||||
permid: type.id | (1 << 15)
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tag.find(".client-select-uid").on('change', event => {
|
||||
tag.find(".button-update").trigger('click');
|
||||
});
|
||||
|
||||
permission_tag.find(".button-update").on('click', event => {
|
||||
let val = tag.find('.client-select-uid').val();
|
||||
globalClient.serverConnection.helper.info_from_uid(val as string).then(result => {
|
||||
if(!result || result.length == 0) return Promise.reject("invalid data");
|
||||
permission_tag.removeClass("disabled");
|
||||
|
||||
tag.find(".client-name").val(result[0].client_nickname);
|
||||
tag.find(".client-uid").val(result[0].client_unique_id);
|
||||
tag.find(".client-dbid").val(result[0].client_database_id);
|
||||
|
||||
return globalClient.permissions.requestClientPermissions(result[0].client_database_id).then(result => display_permissions(permission_tag, result));
|
||||
}).catch(error => {
|
||||
console.log(error); //TODO error handling?
|
||||
permission_tag.addClass("disabled");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function apply_channel_permission(tag: JQuery) {
|
||||
let channel_list = tag.find(".list-channel .entries");
|
||||
let permission_tag = tag.find(".permission-explorer");
|
||||
|
||||
make_permission_editor(tag, 75, (type, value, skip, negate) => {
|
||||
let channel_id: number = parseInt(channel_list.find(".selected").attr("channel-id"));
|
||||
let channel = globalClient.channelTree.findChannel(channel_id);
|
||||
if(!channel) {
|
||||
console.warn("Missing selected channel id for permission editor action!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added permission " + type.name + " with properties: %o %o %o", value, skip, negate);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("channeladdperm", {
|
||||
cid: channel.channelId,
|
||||
permid: type.id,
|
||||
permvalue: value,
|
||||
permskip: skip,
|
||||
permnegate: negate
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed permission " + type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("channeldelperm", {
|
||||
cid: channel.channelId,
|
||||
permid: type.id
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
}, (type, value) => {
|
||||
let channel_id: number = parseInt(channel_list.find(".selected").attr("channel-id"));
|
||||
let channel = globalClient.channelTree.findChannel(channel_id);
|
||||
if(!channel) {
|
||||
console.warn("Missing selected channel id for permission editor action!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added grant of %o for " + type.name, value);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("channeladdperm", {
|
||||
cid: channel.channelId,
|
||||
permid: type.id | (1 << 15),
|
||||
permvalue: value,
|
||||
permskip: false,
|
||||
permnegate: false
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed grant permission for %s", type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("channeldelperm", {
|
||||
cid: channel.channelId,
|
||||
permid: type.id | (1 << 15)
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
build_channel_tree(channel_list, permission_tag.find(".button-update"));
|
||||
permission_tag.find(".button-update").on('click', event => {
|
||||
let channel_id: number = parseInt(channel_list.find(".selected").attr("channel-id"));
|
||||
let channel = globalClient.channelTree.findChannel(channel_id);
|
||||
if(!channel) {
|
||||
console.warn("Missing selected channel id for permission editor action!");
|
||||
return;
|
||||
}
|
||||
|
||||
globalClient.permissions.requestChannelPermissions(channel.channelId).then(result => display_permissions(permission_tag, result)).catch(error => {
|
||||
console.log(error); //TODO handling?
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function apply_channel_groups(tag: JQuery) {
|
||||
let group_list = tag.find(".list-group-channel .entries");
|
||||
let permission_tag = tag.find(".permission-explorer");
|
||||
|
||||
make_permission_editor(tag, 75, (type, value, skip, negate) => {
|
||||
let group_id: number = parseInt(group_list.find(".selected").attr("group-id"));
|
||||
let group = globalClient.groups.channelGroup(group_id);
|
||||
if(!group) {
|
||||
console.warn("Missing selected group id for permission editor action!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added permission " + type.name + " with properties: %o %o %o", value, skip, negate);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("channelgroupaddperm", {
|
||||
cgid: group.id,
|
||||
permid: type.id,
|
||||
permvalue: value,
|
||||
permskip: skip,
|
||||
permnegate: negate
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed permission " + type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("channelgroupdelperm", {
|
||||
cgid: group.id,
|
||||
permid: type.id
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
}, (type, value) => {
|
||||
let group_id: number = parseInt(group_list.find(".selected").attr("group-id"));
|
||||
let group = globalClient.groups.channelGroup(group_id);
|
||||
if(!group) {
|
||||
console.warn("Missing selected group id for permission editor action!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added grant of %o for " + type.name, value);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("channelgroupaddperm", {
|
||||
cgid: group.id,
|
||||
permid: type.id | (1 << 15),
|
||||
permvalue: value,
|
||||
permskip: false,
|
||||
permnegate: false
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log("Removed grant permission for %s", type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("channelgroupdelperm", {
|
||||
cgid: group.id,
|
||||
permid: type.id | (1 << 15)
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
for(let group of globalClient.groups.channelGroups.sort(GroupManager.sorter())) {
|
||||
let tag = $.spawn("div").addClass("group").attr("group-id", group.id);
|
||||
globalClient.fileManager.icons.generateTag(group.properties.iconid).appendTo(tag);
|
||||
{
|
||||
let name = $.spawn("a").text(group.name + " (" + group.id + ")").addClass("name");
|
||||
if(group.properties.savedb)
|
||||
name.addClass("savedb");
|
||||
if(globalClient.channelTree.server.properties.virtualserver_default_channel_group == group.id)
|
||||
name.addClass("default");
|
||||
name.appendTo(tag);
|
||||
}
|
||||
tag.appendTo(group_list);
|
||||
|
||||
tag.on('click', event => {
|
||||
group_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
permission_tag.find(".button-update").trigger('click');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//button-update
|
||||
permission_tag.find(".button-update").on('click', event => {
|
||||
let group_id: number = parseInt(group_list.find(".selected").attr("group-id"));
|
||||
let group = globalClient.groups.channelGroup(group_id);
|
||||
if(!group) {
|
||||
console.warn("Missing selected group id for permission editor!");
|
||||
return;
|
||||
}
|
||||
globalClient.groups.request_permissions(group).then(result => display_permissions(permission_tag, result)).catch(error => {
|
||||
console.log(error); //TODO handling?
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => group_list.find('.group').first().trigger('click'), 0);
|
||||
}
|
||||
|
||||
function apply_server_groups(tag: JQuery) {
|
||||
let group_list = tag.find(".list-group-server .entries");
|
||||
let permission_tag = tag.find(".permission-explorer");
|
||||
|
||||
make_permission_editor(tag, 75, (type, value, skip, negate) => {
|
||||
let group_id: number = parseInt(group_list.find(".selected").attr("group-id"));
|
||||
let group = globalClient.groups.serverGroup(group_id);
|
||||
if(!group) {
|
||||
console.warn("Missing selected group id for permission editor action!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added permission " + type.name + " with properties: %o %o %o", value, skip, negate);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("servergroupaddperm", {
|
||||
sgid: group.id,
|
||||
permid: type.id,
|
||||
permvalue: value,
|
||||
permskip: skip,
|
||||
permnegate: negate
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
} else {
|
||||
console.log("Removed permission " + type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("servergroupdelperm", {
|
||||
sgid: group.id,
|
||||
permid: type.id
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
}, (type, value) => {
|
||||
let group_id: number = parseInt(group_list.find(".selected").attr("group-id"));
|
||||
let group = globalClient.groups.serverGroup(group_id);
|
||||
if(!group) {
|
||||
console.warn("Missing selected group id for permission editor action!");
|
||||
return;
|
||||
}
|
||||
|
||||
if(value != undefined) {
|
||||
console.log("Added grant of %o for " + type.name, value);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
globalClient.serverConnection.sendCommand("servergroupaddperm", {
|
||||
sgid: group.id,
|
||||
permid: type.id | (1 << 15),
|
||||
permvalue: value,
|
||||
permskip: false,
|
||||
permnegate: false
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log("Removed grant permission for %s", type.name);
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
return globalClient.serverConnection.sendCommand("servergroupdelperm", {
|
||||
sgid: group.id,
|
||||
permid: type.id | (1 << 15)
|
||||
}).then(resolve.bind(undefined, true)).catch(reject);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
for(let group of globalClient.groups.serverGroups.sort(GroupManager.sorter())) {
|
||||
let tag = $.spawn("div").addClass("group").attr("group-id", group.id);
|
||||
globalClient.fileManager.icons.generateTag(group.properties.iconid).appendTo(tag);
|
||||
{
|
||||
let name = $.spawn("a").text(group.name + " (" + group.id + ")").addClass("name");
|
||||
if(group.properties.savedb)
|
||||
name.addClass("savedb");
|
||||
if(globalClient.channelTree.server.properties.virtualserver_default_server_group == group.id)
|
||||
name.addClass("default");
|
||||
name.appendTo(tag);
|
||||
}
|
||||
tag.appendTo(group_list);
|
||||
|
||||
tag.on('click', event => {
|
||||
group_list.find(".selected").removeClass("selected");
|
||||
tag.addClass("selected");
|
||||
permission_tag.find(".button-update").trigger('click');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//button-update
|
||||
permission_tag.find(".button-update").on('click', event => {
|
||||
let group_id: number = parseInt(group_list.find(".selected").attr("group-id"));
|
||||
let group = globalClient.groups.serverGroup(group_id);
|
||||
if(!group) {
|
||||
console.warn("Missing selected group id for permission editor!");
|
||||
return;
|
||||
}
|
||||
globalClient.groups.request_permissions(group).then(result => display_permissions(permission_tag, result)).catch(error => {
|
||||
console.log(error); //TODO handling?
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => group_list.find('.group').first().trigger('click'), 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
namespace Modals {
|
||||
export function createServerGroupAssignmentModal(client: ClientEntry, callback: (group: Group, flag: boolean) => Promise<boolean>) {
|
||||
const modal = createModal({
|
||||
header: "Server Groups",
|
||||
body: () => {
|
||||
let tag: any = {};
|
||||
let groups = tag["groups"] = [];
|
||||
|
||||
tag["client_name"] = client.clientNickName();
|
||||
for(let group of client.channelTree.client.groups.serverGroups) {
|
||||
if(group.type != GroupType.NORMAL) continue;
|
||||
|
||||
let entry = {} as any;
|
||||
entry["id"] = group.id;
|
||||
entry["name"] = group.name;
|
||||
entry["assigned"] = client.groupAssigned(group);
|
||||
entry["disabled"] = !client.channelTree.client.permissions.neededPermission(PermissionType.I_GROUP_MEMBER_ADD_POWER).granted(group.requiredMemberRemovePower);
|
||||
tag["icon_" + group.id] = client.channelTree.client.fileManager.icons.generateTag(group.properties.iconid);
|
||||
groups.push(entry);
|
||||
}
|
||||
|
||||
let template = $("#tmpl_server_group_assignment").renderTag(tag);
|
||||
|
||||
template.find(".group-entry input").each((_idx, _entry) => {
|
||||
let entry = $(_entry);
|
||||
|
||||
entry.on('change', event => {
|
||||
let group_id = parseInt(entry.attr("group-id"));
|
||||
let group = client.channelTree.client.groups.serverGroup(group_id);
|
||||
if(!group) {
|
||||
console.warn("Could not resolve target group!");
|
||||
return false;
|
||||
}
|
||||
|
||||
let target = entry.prop("checked");
|
||||
callback(group, target).then(flag => flag ? Promise.resolve() : Promise.reject()).catch(error => entry.prop("checked", !target));
|
||||
});
|
||||
});
|
||||
|
||||
return template;
|
||||
},
|
||||
footer: () => {
|
||||
let footer = $.spawn("div");
|
||||
footer.addClass("modal-button-group");
|
||||
footer.css("margin", "5px");
|
||||
|
||||
let button_close = $.spawn("button");
|
||||
button_close.text("Close").addClass("button_close");
|
||||
|
||||
footer.append(button_close);
|
||||
|
||||
return footer;
|
||||
},
|
||||
width: "max-content"
|
||||
});
|
||||
|
||||
modal.htmlTag.find(".button_close").click(() => {
|
||||
modal.close();
|
||||
});
|
||||
|
||||
modal.open();
|
||||
}
|
||||
|
||||
}
|
|
@ -121,7 +121,7 @@ class ServerEntry {
|
|||
}
|
||||
|
||||
spawnContextMenu(x: number, y: number, on_close: () => void = () => {}) {
|
||||
spawnMenu(x, y, {
|
||||
spawn_context_menu(x, y, {
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-virtualserver_edit",
|
||||
name: "Edit",
|
||||
|
|
|
@ -36,7 +36,7 @@ class ChannelTree {
|
|||
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_SEMI_PERMANENT).granted(1) ||
|
||||
this.client.permissions.neededPermission(PermissionType.B_CHANNEL_CREATE_PERMANENT).granted(1);
|
||||
|
||||
spawnMenu(x, y,
|
||||
spawn_context_menu(x, y,
|
||||
{
|
||||
type: MenuEntryType.ENTRY,
|
||||
icon: "client-channel_create",
|
||||
|
|
|
@ -8,4 +8,74 @@ namespace helpers {
|
|||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LaterPromise<T> extends Promise<T> {
|
||||
private _handle: Promise<T>;
|
||||
private _resolve: ($: T) => any;
|
||||
private _reject: ($: any) => any;
|
||||
private _time: number;
|
||||
|
||||
constructor() {
|
||||
super((resolve, reject) => {});
|
||||
this._handle = new Promise<T>((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
});
|
||||
this._time = Date.now();
|
||||
}
|
||||
|
||||
resolved(object: T) {
|
||||
this._resolve(object);
|
||||
}
|
||||
|
||||
rejected(reason) {
|
||||
this._reject(reason);
|
||||
}
|
||||
|
||||
function_rejected() {
|
||||
return error => this.rejected(error);
|
||||
}
|
||||
|
||||
time() { return this._time; }
|
||||
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of which ever callback is executed.
|
||||
*/
|
||||
then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2> {
|
||||
return this._handle.then(onfulfilled, onrejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a callback for only the rejection of the Promise.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of the callback.
|
||||
*/
|
||||
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult> {
|
||||
return this._handle.then(onrejected);
|
||||
}
|
||||
}
|
||||
|
||||
const copy_to_clipboard = str => {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = str;
|
||||
el.setAttribute('readonly', '');
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = '-9999px';
|
||||
document.body.appendChild(el);
|
||||
const selected =
|
||||
document.getSelection().rangeCount > 0
|
||||
? document.getSelection().getRangeAt(0)
|
||||
: false;
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
if (selected) {
|
||||
document.getSelection().removeAllRanges();
|
||||
document.getSelection().addRange(selected);
|
||||
}
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
$(document).on("mousedown",function (e) {
|
||||
if($(e.target).parents(".modal").length == 0){
|
||||
if($(e.target).parents("#contextMenu").length == 0 && $(e.target).parents(".modal").length == 0){
|
||||
$(".modal:visible").last().find(".close").trigger("click");
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
interface JQuery {
|
||||
asTabWidget() : JQuery;
|
||||
tabify() : this;
|
||||
asTabWidget(copy?: boolean) : JQuery;
|
||||
tabify(copy?: boolean) : this;
|
||||
|
||||
changeElementType(type: string) : JQuery;
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ if(typeof (customElements) !== "undefined") {
|
|||
}
|
||||
|
||||
var TabFunctions = {
|
||||
tabify(template: JQuery) : JQuery {
|
||||
console.log("Tabify:");
|
||||
tabify(template: JQuery, copy: boolean = true) : JQuery {
|
||||
console.log("Tabify: copy=" + copy);
|
||||
console.log(template);
|
||||
|
||||
let tag = $.spawn("div");
|
||||
|
@ -40,10 +40,13 @@ var TabFunctions = {
|
|||
template.find("x-entry").each(function () {
|
||||
let hentry = $.spawn("div");
|
||||
hentry.addClass("entry");
|
||||
hentry.append($(this).find("x-tag").clone(true, true));
|
||||
if(copy)
|
||||
hentry.append($(this).find("x-tag").clone(true, true));
|
||||
else
|
||||
hentry.append($(this).find("x-tag"));
|
||||
|
||||
const _this = $(this);
|
||||
const _entryContent = _this.find("x-content").clone(true, true);
|
||||
const _entryContent = copy ? _this.find("x-content").clone(true, true) : _this.find("x-content");
|
||||
silentContent.append(_entryContent);
|
||||
hentry.on("click", function () {
|
||||
if(hentry.hasClass("selected")) return;
|
||||
|
@ -72,9 +75,9 @@ var TabFunctions = {
|
|||
}
|
||||
|
||||
if(!$.fn.asTabWidget) {
|
||||
$.fn.asTabWidget = function () : JQuery {
|
||||
$.fn.asTabWidget = function (copy?: boolean) : JQuery {
|
||||
if($(this).prop("tagName") == "X-TAB")
|
||||
return TabFunctions.tabify($(this));
|
||||
return TabFunctions.tabify($(this), typeof(copy) === "boolean" ? copy : true);
|
||||
else {
|
||||
throw "Invalid tag! " + $(this).prop("tagName");
|
||||
}
|
||||
|
@ -82,13 +85,13 @@ if(!$.fn.asTabWidget) {
|
|||
}
|
||||
|
||||
if(!$.fn.tabify) {
|
||||
$.fn.tabify = function () {
|
||||
$.fn.tabify = function (copy?: boolean) {
|
||||
try {
|
||||
let self = this.asTabWidget();
|
||||
let self = this.asTabWidget(copy);
|
||||
this.replaceWith(self);
|
||||
} catch(object) {}
|
||||
this.find("x-tab").each(function () {
|
||||
$(this).replaceWith($(this).asTabWidget());
|
||||
$(this).replaceWith($(this).asTabWidget(copy));
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ class AudioController {
|
|||
}
|
||||
|
||||
static initialized() : boolean {
|
||||
return this.globalContext.state === "running";
|
||||
return (this.globalContext || {state: ""}).state === "running";
|
||||
}
|
||||
|
||||
static on_initialized(callback: () => any) {
|
||||
|
|
303
templates.html
303
templates.html
|
@ -13,6 +13,61 @@
|
|||
<link rel="stylesheet" href="css/general.css" type="text/css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Template for the connect modal -->
|
||||
<script id="tmpl_connect" type="text/html">
|
||||
<div style="margin-top: 5px;">
|
||||
<div style="display: flex; flex-direction: row; width: 100%; justify-content: space-between">
|
||||
<div style="width: 68%; margin-bottom: 5px">
|
||||
<div>Remote address and port:</div>
|
||||
<input type="text" style="width: 100%" class="connect_address" value="unknown">
|
||||
</div>
|
||||
<div style="width: 20%">
|
||||
<div>Server password:</div>
|
||||
<form name="server_password_form" onsubmit="return false;">
|
||||
<input type="password" if="connect_server_password_{{rnd '0~13377331'/}}" autocomplete="off" style="width: 100%" class="connect_password">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>Nickname:</div>
|
||||
<input type="text" style="width: 100%" class="connect_nickname" value="">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="group_box">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<div style="text-align: right;">Identity Settings</div>
|
||||
<select class="identity_select">
|
||||
<option name="identity_type" value="TEAFORO">Forum Account</option>
|
||||
<option name="identity_type" value="TEAMSPEAK">TeamSpeak</option>
|
||||
<option name="identity_type" value="NICKNAME">Nickname (Debug purposes only!)</option> <!-- Only available on localhost for debug -->
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="identity_config_TEAMSPEAK identity_config">
|
||||
Please enter your exported TS3 Identity string bellow or select your exported Identity<br>
|
||||
<div style="width: 100%; display: flex; flex-direction: row">
|
||||
<input placeholder="Identity string" style="width: 70%; margin: 5px;" class="identity_string">
|
||||
<div style="width: 30%; margin: 5px"><input class="identity_file" type="file"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_TEAFORO identity_config">
|
||||
{{if forum_valid}}
|
||||
You're using your forum account as verification
|
||||
{{else}}
|
||||
You cant use your TeaSpeak forum account.<br>
|
||||
You're not connected!<br>
|
||||
Click <a href="{{:forum_path}}login.php">here</a> to login via the TeaSpeak forum.
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="identity_config_NICKNAME identity_config">
|
||||
This is just for debug and uses the name as unique identifier
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message"></div>
|
||||
</div> <!-- <a href="<?php echo authPath() . "login.php"; ?>">Login</a> via the TeaSpeak forum. -->
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Template for chennel create & edit-->
|
||||
<script id="tmpl_channel_edit" type="text/html">
|
||||
<div class="align_column general_properties">
|
||||
|
@ -182,7 +237,6 @@
|
|||
</x-entry>
|
||||
</x-tab>
|
||||
</script>
|
||||
|
||||
<script id="tmpl_server_edit" type="text/html">
|
||||
<div class="align_column properties_general server_properties">
|
||||
<div class="properties">
|
||||
|
@ -414,62 +468,6 @@
|
|||
</x-tab>
|
||||
</script>
|
||||
|
||||
<!-- Template for the connect modal -->
|
||||
<script id="tmpl_connect" type="text/html">
|
||||
<div style="margin-top: 5px;">
|
||||
<div style="display: flex; flex-direction: row; width: 100%; justify-content: space-between">
|
||||
<div style="width: 68%; margin-bottom: 5px">
|
||||
<div>Remote address and port:</div>
|
||||
<input type="text" style="width: 100%" class="connect_address" value="unknown">
|
||||
</div>
|
||||
<div style="width: 20%">
|
||||
<div>Server password:</div>
|
||||
<form name="server_password_form" onsubmit="return false;">
|
||||
<input type="password" if="connect_server_password_{{rnd '0~13377331'/}}" autocomplete="off" style="width: 100%" class="connect_password">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>Nickname:</div>
|
||||
<input type="text" style="width: 100%" class="connect_nickname" value="">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="group_box">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<div style="text-align: right;">Identity Settings</div>
|
||||
<select class="identity_select">
|
||||
<option name="identity_type" value="TEAFORO">Forum Account</option>
|
||||
<option name="identity_type" value="TEAMSPEAK">TeamSpeak</option>
|
||||
<option name="identity_type" value="NICKNAME">Nickname (Debug purposes only!)</option> <!-- Only available on localhost for debug -->
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="identity_config_TEAMSPEAK identity_config">
|
||||
Please enter your exported TS3 Identity string bellow or select your exported Identity<br>
|
||||
<div style="width: 100%; display: flex; flex-direction: row">
|
||||
<input placeholder="Identity string" style="width: 70%; margin: 5px;" class="identity_string">
|
||||
<div style="width: 30%; margin: 5px"><input class="identity_file" type="file"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_TEAFORO identity_config">
|
||||
{{if forum_valid}}
|
||||
You're using your forum account as verification
|
||||
{{else}}
|
||||
You cant use your TeaSpeak forum account.<br>
|
||||
You're not connected!<br>
|
||||
Click <a href="{{:forum_path}}login.php">here</a> to login via the TeaSpeak forum.
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="identity_config_NICKNAME identity_config">
|
||||
This is just for debug and uses the name as unique identifier
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message"></div>
|
||||
</div> <!-- <a href="<?php echo authPath() . "login.php"; ?>">Login</a> via the TeaSpeak forum. -->
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Template for the settings -->
|
||||
<script id="tmpl_settings" type="text/html">
|
||||
<x-tab>
|
||||
|
@ -524,14 +522,12 @@
|
|||
</x-entry>
|
||||
</x-tab>
|
||||
</script>
|
||||
|
||||
<script id="tmpl_change_volume" type="text/html">
|
||||
<div style="display: flex; justify-content: center; vertical-align: center">
|
||||
<input type="range" min="0" max="200" value="100" class="volume_slider" style="width: 100%">
|
||||
<div class="display_volume" style="width: 60px; align-self: center; text-align: center">±0 %</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="tmpl_client_ban" type="text/html">
|
||||
<div class="align_column">
|
||||
<div class="align_column" style="margin: 5px">
|
||||
|
@ -558,6 +554,192 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<!-- Permission overview -->
|
||||
<script id="tmpl_server_permissions" type="text/html">
|
||||
<x-tab>
|
||||
<x-entry>
|
||||
<x-tag>Server Groups</x-tag>
|
||||
<x-content>
|
||||
<div class="layout-group-server">
|
||||
<div class="list-group-server">
|
||||
<div class="entries"></div>
|
||||
</div>
|
||||
<node key="permissions_group_server"/>
|
||||
<div class="list-group-server-clients"></div>
|
||||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
<x-entry>
|
||||
<x-tag>Channel Groups</x-tag>
|
||||
<x-content>
|
||||
<div class="layout-group-channel">
|
||||
<div class="list-group-channel">
|
||||
<div class="entries"></div>
|
||||
</div>
|
||||
<node key="permissions_group_channel"/>
|
||||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
<x-entry>
|
||||
<x-tag>Channel permissions</x-tag>
|
||||
<x-content>
|
||||
<div class="layout-channel">
|
||||
<div class="list-channel">
|
||||
<div class="entries"></div>
|
||||
</div>
|
||||
<node key="permissions_channel"/>
|
||||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
<x-entry>
|
||||
<x-tag>Client permissions</x-tag>
|
||||
<x-content>
|
||||
<div class="layout-client">
|
||||
<div class="client-info">
|
||||
<div class="client-select">
|
||||
<a>Client unique ID:</a>
|
||||
<input type="text" class="client-select-uid">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="client-info">
|
||||
<a>Nickname:</a>
|
||||
<input class="client-name">
|
||||
|
||||
<a>Unique ID:</a>
|
||||
<input class="client-uid">
|
||||
|
||||
<a>Cleint database ID:</a>
|
||||
<input class="client-dbid">
|
||||
</div>
|
||||
</div>
|
||||
<node key="permissions_client"/>
|
||||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
<x-entry>
|
||||
<x-tag>Client channel permissions</x-tag>
|
||||
<x-content>
|
||||
<div class="layout-client-channel">
|
||||
<div class="client-info">
|
||||
<div class="client-select">
|
||||
<a>Client unique ID:</a>
|
||||
<input type="text" class="client-select-uid">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="client-info">
|
||||
<a>Nickname:</a>
|
||||
<input class="client-name">
|
||||
|
||||
<a>Unique ID:</a>
|
||||
<input class="client-uid">
|
||||
|
||||
<a>Cleint database ID:</a>
|
||||
<input class="client-dbid">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="list-channel">
|
||||
<div class="entries"></div>
|
||||
</div>
|
||||
</div>
|
||||
<node key="permissions_client_channel"/>
|
||||
</div>
|
||||
</x-content>
|
||||
</x-entry>
|
||||
</x-tab>
|
||||
</script>
|
||||
<script id="tmpl_server_group_assignment" type="text/html">
|
||||
<div class="group-assignment-list">
|
||||
<a>Changing groups of <b>{{>client_name}}</b></a>
|
||||
<div class="group-list">
|
||||
{{for groups}}
|
||||
<div class="group-entry">
|
||||
<label class="checkbox {{if disabled}}disabled{{/if}}">
|
||||
<input type="checkbox" group-id="{{>id}}" {{if assigned}}checked{{/if}}>
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
<node key="icon_{{>id}}"></node>
|
||||
<a>{{>name}} ({{>id}})</a>
|
||||
</div>
|
||||
{{/for}}
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="tmpl_permission_explorer" type="text/html">
|
||||
<div class="align_row permission-explorer">
|
||||
<div class="bar-filter">
|
||||
<div><a>Filter:</a></div>
|
||||
<div><input type="text" class="filter-input"></div>
|
||||
<div><input type="checkbox" class="filter-granted"><a>Show granted permissions only</a></div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="entry header">
|
||||
<div>Permission Name</div>
|
||||
<div>Value</div>
|
||||
<div>Skip</div>
|
||||
<div>Negate</div>
|
||||
<div>Granted</div>
|
||||
</div>
|
||||
|
||||
<div class="entries">
|
||||
{{for permissions tmpl="#tmpl_permission_entry"/}}
|
||||
</div>
|
||||
<!--
|
||||
<div class="entry">
|
||||
<div>this_is_a_test_permission</div>
|
||||
<div>value</div>
|
||||
<div>skip</div>
|
||||
<div>negate</div>
|
||||
<div>granted</div>
|
||||
</div>
|
||||
<div class="entry group">
|
||||
<div class="title"><div class="arrow right"></div><a>Test group</a></div>
|
||||
<div class="group-entries">
|
||||
<div class="entry">
|
||||
<div>Grouped entry A</div>
|
||||
<div>value</div>
|
||||
<div>skip</div>
|
||||
<div>negate</div>
|
||||
<div>granted</div>
|
||||
</div>
|
||||
<div class="entry">
|
||||
<div>Grouped entry B</div>
|
||||
<div>value</div>
|
||||
<div>skip</div>
|
||||
<div>negate</div>
|
||||
<div>granted</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<div class="overlay-disabled"></div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="button-update"><div class="icon client-check_update"></div> Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script id="tmpl_permission_entry" type="text/html">
|
||||
{{if type == "entry"}}
|
||||
<div class="entry {{if unset}}unset{{/if}} permission {{>permission_name}}">
|
||||
<div class="permission-name filter-key">{{>permission_name}}</div>
|
||||
<div class="permission-value"><node key="{{>permission_name}}_value"></node></div>
|
||||
<div class="permission-skip"><node key="{{>permission_name}}_skip"></node></div>
|
||||
<div class="permission-negate"><node key="{{>permission_name}}_negate"></node></div>
|
||||
<div class="permission-grant"><node key="{{>permission_name}}_grant"></node></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="entry group">
|
||||
<div class="title"><div class="arrow down"></div><a>{{>name}}</a></div>
|
||||
<div class="group-entries">
|
||||
{{for entries tmpl="#tmpl_permission_entry"/}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</script>
|
||||
|
||||
|
||||
<!-- Music interface -->
|
||||
<script id="tmpl_music_frame" type="text/html">
|
||||
<!-- First we want to define some variables if not defined yet. -->
|
||||
{{if !thumbnail}}
|
||||
|
@ -633,7 +815,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<template id="tmpl_music_frame_empty">
|
||||
<div class="music-wrapper empty">
|
||||
<img src="img/music/empty_disk.svg">
|
||||
|
@ -752,7 +933,6 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
</script>
|
||||
|
||||
<script id="tmpl_selected_music" type="text/html">
|
||||
<table class="select_info_table">
|
||||
<tr>
|
||||
|
@ -812,7 +992,6 @@
|
|||
<node key="music_player"/>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="tmpl_selected_server" type="text/html">
|
||||
<div class="select_server">
|
||||
<table class="select_info_table">
|
||||
|
|
Loading…
Reference in New Issue