Compare commits

...

34 Commits

Author SHA1 Message Date
cdcb02e976 commit 18/10/2025 sync changes from master 2025-10-20 08:53:20 +07:00
17b4485e69 commit 17/10/2025 Add few logs 2025-10-17 17:15:00 +07:00
4d02ab6d07 commit 17/10/2025 Add few logs 2025-10-17 16:55:57 +07:00
8bd57f216a commit 14/10/2025 WebApp fix ScheduleBank ADD & send message 2025-10-17 09:42:50 +07:00
8978a0986d Merge remote-tracking branch 'origin/master' into feature-webapp 2025-10-17 09:12:01 +07:00
bc3f5c5691 commit 16/10/2025
Overview menu beres
2025-10-16 15:54:33 +07:00
e5d8d8059e Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp 2025-10-16 15:42:24 +07:00
1b84ec133b commit 14/10/2025 WebApp Add send language for ScheduleBank 2025-10-16 15:26:59 +07:00
1a6b7de6ec commit 16/10/2025
Overview menu beres
2025-10-16 14:53:26 +07:00
4da5a2fb05 Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp 2025-10-15 16:17:05 +07:00
2ca7004b70 commit 15/10/2025
Overview menu belum beres
2025-10-15 16:13:44 +07:00
1563e233d6 commit 14/10/2025 WebApp Add send language for ScheduleBank 2025-10-15 13:32:08 +07:00
2fe4a46e3e commit 15/10/2025
Network monitoring beres
2025-10-15 12:14:57 +07:00
a53270aaed commit 14/10/2025 WebApp Add send language for ScheduleBank 2025-10-14 16:26:14 +07:00
4d3dc538bd commit 14/10/2025
Messagebank belum beres
2025-10-14 15:14:21 +07:00
d2a2626fd5 Merge remote-tracking branch 'origin/master' into feature-webapp 2025-10-14 14:20:08 +07:00
de54d142ae commit 14/10/2025
Soundbank Menu beres
2025-10-14 13:22:48 +07:00
8c49bb827f Merge remote-tracking branch 'origin/master' into feature-webapp
# Conflicts:
#	src/web/WebApp.kt
2025-10-14 09:11:36 +07:00
b15470845e Merge remote-tracking branch 'origin/master' into feature-webapp
# Conflicts:
#	src/web/WebApp.kt
2025-10-14 09:07:17 +07:00
5f57e1bf2e commit 14/10/2025
combine with coding steph
2025-10-14 08:50:05 +07:00
a133f9a170 commit 14/10/2025
combine with coding steph
2025-10-14 08:21:07 +07:00
1d2c8d1307 commit 08/10/2025 WebApp 2025-10-14 07:21:52 +07:00
e426522380 commit 08/10/2025 WebApp 2025-10-13 16:16:15 +07:00
110e6d5b12 commit 13/10/2025
Soundbank menu
2025-10-13 15:55:09 +07:00
470f61c79d Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp
# Conflicts:
#	src/web/WebApp.kt
2025-10-13 10:52:21 +07:00
e78fb932b2 commit 10/10/2025
Restrukturisasi soundbank path di database
2025-10-10 16:21:28 +07:00
e0cdf74dec commit 10/10/2025
Restrukturisasi soundbank path di database
2025-10-10 16:21:13 +07:00
41d6dd7f47 commit 10/10/2025
Broadcast Zones and Sound Channels
2025-10-10 15:01:09 +07:00
fdc7556dd7 commit 10/10/2025
User Management
2025-10-10 10:03:14 +07:00
7f647fe9c3 commit 10/10/2025
User Management
2025-10-10 09:52:11 +07:00
d549aee42c commit 09/10/2025
User Management belum kelar
2025-10-09 15:49:03 +07:00
6b00bc7eb0 commit 08/10/2025 WebApp 2025-10-08 17:03:17 +07:00
8409307631 Merge branch 'master' of https://gitea.rdkartono.my.id/rdkartono/AAS_NewGeneration into feature-webapp 2025-10-08 17:02:45 +07:00
2ad26c3ef6 commit 08/10/2025
BarixConnection Activate Deactivate Relays
2025-10-08 16:33:19 +07:00
53 changed files with 4214 additions and 1129 deletions

View File

@@ -0,0 +1,88 @@
:root, [data-bs-theme=light] {
--bs-primary: #0d6efd;
--bs-primary-rgb: 13,110,253;
--bs-primary-text-emphasis: #052C65;
--bs-primary-bg-subtle: #CFE2FF;
--bs-primary-border-subtle: #9EC5FE;
}
.btn-primary {
--bs-btn-color: #fff;
--bs-btn-bg: #0d6efd;
--bs-btn-border-color: #0d6efd;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #0B5ED7;
--bs-btn-hover-border-color: #0A58CA;
--bs-btn-focus-shadow-rgb: 219,233,255;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #0A58CA;
--bs-btn-active-border-color: #0A53BE;
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #0d6efd;
--bs-btn-disabled-border-color: #0d6efd;
}
.btn-outline-primary {
--bs-btn-color: #0d6efd;
--bs-btn-border-color: #0d6efd;
--bs-btn-focus-shadow-rgb: 13,110,253;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #0d6efd;
--bs-btn-hover-border-color: #0d6efd;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #0d6efd;
--bs-btn-active-border-color: #0d6efd;
--bs-btn-disabled-color: #0d6efd;
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: #0d6efd;
}
.my-4 {
margin-top: 1.5rem!important;
margin-bottom: 1.5rem!important;
}
.mt-0 {
margin-top: 0!important;
}
.me-2 {
margin-right: .5rem!important;
}
.mb-2 {
margin-bottom: .5rem!important;
}
.mb-3 {
margin-bottom: 1rem!important;
}
.mb-4 {
margin-bottom: 1.5rem!important;
}
.mb-5 {
margin-bottom: 3rem!important;
}
.mb-7 {
margin-bottom: 6rem !important;
}
.mb-auto {
margin-bottom: auto!important;
}
@media (min-width:768px) {
.me-md-auto {
margin-right: auto!important;
}
}
@media (min-width:768px) {
.mb-md-0 {
margin-bottom: 0!important;
}
}

629
html/webpage/assets/css/select2.min.css vendored Normal file
View File

@@ -0,0 +1,629 @@
.select2-container {
box-sizing: border-box;
display: inline-block;
margin: 0;
position: relative;
vertical-align: middle;
}
.select2-container .select2-selection--single {
box-sizing: border-box;
cursor: pointer;
display: block;
height: 28px;
user-select: none;
-webkit-user-select: none;
}
.select2-container .select2-selection--single .select2-selection__rendered {
display: block;
padding-left: 8px;
padding-right: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.select2-container .select2-selection--single .select2-selection__clear {
position: relative;
}
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
padding-right: 8px;
padding-left: 20px;
}
.select2-container .select2-selection--multiple {
box-sizing: border-box;
cursor: pointer;
display: block;
min-height: 32px;
user-select: none;
-webkit-user-select: none;
}
.select2-container .select2-selection--multiple .select2-selection__rendered {
display: inline-block;
overflow: hidden;
padding-left: 8px;
text-overflow: ellipsis;
white-space: nowrap;
}
.select2-container .select2-search--inline {
float: left;
}
.select2-container .select2-search--inline .select2-search__field {
box-sizing: border-box;
border: none;
font-size: 100%;
margin-top: 5px;
padding: 0;
}
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none;
}
.select2-dropdown {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
box-sizing: border-box;
display: block;
position: absolute;
left: -100000px;
width: 100%;
z-index: 1051;
}
.select2-results {
display: block;
}
.select2-results__options {
list-style: none;
margin: 0;
padding: 0;
}
.select2-results__option {
padding: 6px;
user-select: none;
-webkit-user-select: none;
}
.select2-results__option[aria-selected] {
cursor: pointer;
}
.select2-container--open .select2-dropdown {
left: 0;
}
.select2-container--open .select2-dropdown--above {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.select2-container--open .select2-dropdown--below {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.select2-search--dropdown {
display: block;
padding: 4px;
}
.select2-search--dropdown .select2-search__field {
padding: 4px;
width: 100%;
box-sizing: border-box;
}
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
-webkit-appearance: none;
}
.select2-search--dropdown.select2-search--hide {
display: none;
}
.select2-close-mask {
border: 0;
margin: 0;
padding: 0;
display: block;
position: fixed;
left: 0;
top: 0;
min-height: 100%;
min-width: 100%;
height: auto;
width: auto;
opacity: 0;
z-index: 99;
background-color: #fff;
filter: alpha(opacity=0);
}
.select2-hidden-accessible {
border: 0 !important;
clip: rect(0 0 0 0) !important;
-webkit-clip-path: inset(50%) !important;
clip-path: inset(50%) !important;
height: 1px !important;
overflow: hidden !important;
padding: 0 !important;
position: absolute !important;
width: 1px !important;
white-space: nowrap !important;
}
.select2-container--default .select2-selection--single {
background-color: #fff;
border: 1px solid #aaa;
border-radius: 4px;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px;
}
.select2-container--default .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
}
.select2-container--default .select2-selection--single .select2-selection__placeholder {
color: #999;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
}
.select2-container--default .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0;
}
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left;
}
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto;
}
.select2-container--default.select2-container--disabled .select2-selection--single {
background-color: #eee;
cursor: default;
}
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none;
}
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
.select2-container--default .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text;
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 5px;
width: 100%;
}
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
list-style: none;
}
.select2-container--default .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-top: 5px;
margin-right: 10px;
padding: 1px;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px;
}
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333;
}
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right;
}
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto;
}
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto;
}
.select2-container--default.select2-container--focus .select2-selection--multiple {
border: solid black 1px;
outline: 0;
}
.select2-container--default.select2-container--disabled .select2-selection--multiple {
background-color: #eee;
cursor: default;
}
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
display: none;
}
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.select2-container--default .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa;
}
.select2-container--default .select2-search--inline .select2-search__field {
background: transparent;
border: none;
outline: 0;
box-shadow: none;
-webkit-appearance: textfield;
}
.select2-container--default .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
}
.select2-container--default .select2-results__option[role=group] {
padding: 0;
}
.select2-container--default .select2-results__option[aria-disabled=true] {
color: #999;
}
.select2-container--default .select2-results__option[aria-selected=true] {
background-color: #ddd;
}
.select2-container--default .select2-results__option .select2-results__option {
padding-left: 1em;
}
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0;
}
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em;
}
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em;
}
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em;
}
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em;
}
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em;
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
background-color: #5897fb;
color: white;
}
.select2-container--default .select2-results__group {
cursor: default;
display: block;
padding: 6px;
}
.select2-container--classic .select2-selection--single {
background-color: #f7f7f7;
border: 1px solid #aaa;
border-radius: 4px;
outline: 0;
background-image: -webkit-linear-gradient(top, #fff 50%, #eee 100%);
background-image: -o-linear-gradient(top, #fff 50%, #eee 100%);
background-image: linear-gradient(to bottom, #fff 50%, #eee 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0);
}
.select2-container--classic .select2-selection--single:focus {
border: 1px solid #5897fb;
}
.select2-container--classic .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 28px;
}
.select2-container--classic .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin-right: 10px;
}
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
color: #999;
}
.select2-container--classic .select2-selection--single .select2-selection__arrow {
background-color: #ddd;
border: none;
border-left: 1px solid #aaa;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
background-image: -webkit-linear-gradient(top, #eee 50%, #ccc 100%);
background-image: -o-linear-gradient(top, #eee 50%, #ccc 100%);
background-image: linear-gradient(to bottom, #eee 50%, #ccc 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0);
}
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0;
}
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left;
}
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
border: none;
border-right: 1px solid #aaa;
border-radius: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
left: 1px;
right: auto;
}
.select2-container--classic.select2-container--open .select2-selection--single {
border: 1px solid #5897fb;
}
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
background: transparent;
border: none;
}
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-image: -webkit-linear-gradient(top, #fff 0%, #eee 50%);
background-image: -o-linear-gradient(top, #fff 0%, #eee 50%);
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0);
}
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
background-image: -webkit-linear-gradient(top, #eee 50%, #fff 100%);
background-image: -o-linear-gradient(top, #eee 50%, #fff 100%);
background-image: linear-gradient(to bottom, #eee 50%, #fff 100%);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0);
}
.select2-container--classic .select2-selection--multiple {
background-color: white;
border: 1px solid #aaa;
border-radius: 4px;
cursor: text;
outline: 0;
}
.select2-container--classic .select2-selection--multiple:focus {
border: 1px solid #5897fb;
}
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
list-style: none;
margin: 0;
padding: 0 5px;
}
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
display: none;
}
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #aaa;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px;
}
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
color: #888;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px;
}
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #555;
}
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
float: right;
margin-left: 5px;
margin-right: auto;
}
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto;
}
.select2-container--classic.select2-container--open .select2-selection--multiple {
border: 1px solid #5897fb;
}
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom: none;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.select2-container--classic .select2-search--dropdown .select2-search__field {
border: 1px solid #aaa;
outline: 0;
}
.select2-container--classic .select2-search--inline .select2-search__field {
outline: 0;
box-shadow: none;
}
.select2-container--classic .select2-dropdown {
background-color: #fff;
border: 1px solid transparent;
}
.select2-container--classic .select2-dropdown--above {
border-bottom: none;
}
.select2-container--classic .select2-dropdown--below {
border-top: none;
}
.select2-container--classic .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
}
.select2-container--classic .select2-results__option[role=group] {
padding: 0;
}
.select2-container--classic .select2-results__option[aria-disabled=true] {
color: grey;
}
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
background-color: #3875d7;
color: #fff;
}
.select2-container--classic .select2-results__group {
cursor: default;
display: block;
padding: 6px;
}
.select2-container--classic.select2-container--open .select2-dropdown {
border-color: #5897fb;
}

View File

@@ -62,9 +62,9 @@ body {
.btn-round-basic:focus { .btn-round-basic:focus {
background-color: #f5f5f5; background-color: #f5f5f5;
border-radius: 20px; border-radius: 8px;
box-shadow: inset 4px 4px 10px #88a5bf7b, inset -4px -4px 10px #ffffff; box-shadow: inset 4px 4px 10px #88a5bf7b, inset -4px -4px 10px #ffffff;
color: #4d4d4d; /*color: #4d4d4d;*/
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 16px;
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
@@ -72,7 +72,7 @@ body {
} }
.btn-round-basic { .btn-round-basic {
border-radius: 20px; border-radius: 08px;
box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px; box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
--bs-btn-hover-bg: #ffffff; --bs-btn-hover-bg: #ffffff;
} }
@@ -82,29 +82,29 @@ body {
box-shadow: inset 2px 2px 5px #bcbcbc, inset -2px -2px 5px #ffffff, 2px 2px 5px #bcbcbc, -2px -2px 5px #ffffff; box-shadow: inset 2px 2px 5px #bcbcbc, inset -2px -2px 5px #ffffff, 2px 2px 5px #bcbcbc, -2px -2px 5px #ffffff;
} }
.color-import { .color-import, .color-import:hover, .color-import:focus {
color: var(--bs-teal); color: var(--bs-teal);
} }
.color-remove { .color-remove, .color-remove:hover, .color-remove:focus {
color: var(--bs-danger); color: var(--bs-danger);
} }
.color-edit { .color-edit, .color-edit:hover, .color-edit:focus {
color: var(--bs-primary-text-emphasis); color: var(--bs-primary-text-emphasis);
} }
.color-add { .color-add, .color-add:hover, .color-add:focus {
color: var(--bs-primary); color: var(--bs-primary);
} }
.input-login { .input-login {
border-radius: 50px; border-radius: 8px;
box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px, rgba(9, 30, 66, 0.08) 0px 0px 0px 1px; box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px, rgba(9, 30, 66, 0.08) 0px 0px 0px 1px;
} }
.btn-login { .btn-login {
border-radius: 20px; border-radius: 8px;
box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px; box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
--bs-btn-hover-bg: #5780f2; --bs-btn-hover-bg: #5780f2;
background-color: #5278e1; background-color: #5278e1;
@@ -227,25 +227,23 @@ nav-item:focus {
} }
.accordion-item.active { .accordion-item.active {
/*background: rgba(45, 53, 120, 0.15);*/ background: rgba(45, 53, 120, 0.15);
/*box-shadow: inset 4px 4px 10px rgba(45, 53, 120, 0.25), inset -4px -4px 10px rgba(255, 255, 255, 0.8);*/ box-shadow: inset 4px 4px 10px rgba(45, 53, 120, 0.25), inset -4px -4px 10px rgba(255, 255, 255, 0.8);
} }
.bg-accordion { .bg-accordion {
border: white 2px; border: white 2px;
/*border-radius: 10px;*/ border-radius: 8px;
background: #f8f9fd; background: #f8f9fd;
} }
.card-channel { .card-channel {
border-radius: 12px; border-radius: 8px;
background: #ffffff; background: #ffffff;
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
border: #ffffff solid 2px !important; border: #ffffff solid 2px !important;
/*padding-left: 2px;*/
/*padding-right: 5px;*/
padding-top: 12px; padding-top: 12px;
padding-bottom: 12px; padding-bottom: 12px;
} }
@@ -255,3 +253,68 @@ nav-item:focus {
border: white solid 3px; border: white solid 3px;
} }
.progress-bar {
background-color: #172066;
}
table {
border-radius: 8px;
}
.bg-modal-body {
background-color: #f8f9fd;
}
.bg-header-modal {
background-color: #f0f2ff;
}
.pad-row-btn {
margin: 0.5rem;
}
.pad-2 {
border-radius: 0 !important;
}
.p-login {
text-align: left;
font-weight: 600;
margin-bottom: 0;
color: #3E4C66;
}
.h-login {
font-weight: 600;
color: #2E3A59;
}
#file-input {
display: none;
}
.card-setting {
border-radius: 8px;
border: white solid 3px;
}
#drop-area {
border: 2px dashed #ccc;
border-radius: 20px;
width: 400px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
font-family: sans-serif;
color: #666;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
#drop-area.highlight {
background: #f0f8ff;
border-color: #0d6efd;
color: #0d6efd;
}

View File

@@ -1,4 +1,17 @@
/**
* @typedef {Object} BroadcastZone
* @property {number} index
* @property {string} description
* @property {String} SoundChannel
* @property {String} Box
* @property {String} Relay
*/
/**
* List of broadcast zones available
* @type {BroadcastZone[]}
*/
window.BroadcastZoneList ??= [];
/** /**
* Currently selected broadcast zone row in the table * Currently selected broadcast zone row in the table
@@ -6,6 +19,12 @@
*/ */
window.selectedBroadcastZoneRow = null; window.selectedBroadcastZoneRow = null;
/**
* List of sound channels available
* @type {String[]}
*/
window.SoundChannelList = []
/** /**
* Fill broadcast zone table body with values * Fill broadcast zone table body with values
* @param {BroadcastZone[]} vv values to fill * @param {BroadcastZone[]} vv values to fill
@@ -23,7 +42,7 @@ function fill_broadcastzonetablebody(vv) {
</tr>`; </tr>`;
$('#broadcastzonetablebody').append(row); $('#broadcastzonetablebody').append(row);
let $addedrow = $('#broadcastzonetablebody tr:last'); let $addedrow = $('#broadcastzonetablebody tr:last');
$addedrow.click(function () { $addedrow.off('click').on('click', function () {
if (window.selectedBroadcastZoneRow) { if (window.selectedBroadcastZoneRow) {
window.selectedBroadcastZoneRow.find('td').css('background-color', ''); window.selectedBroadcastZoneRow.find('td').css('background-color', '');
if (window.selectedBroadcastZoneRow.is($(this))) { if (window.selectedBroadcastZoneRow.is($(this))) {
@@ -42,7 +61,34 @@ function fill_broadcastzonetablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#tablesize').text("Table Size: " + vv.length);
} }
/**
* Reload broadcast zones from server
* @param {String} APIURL API URL endpoint (default "BroadcastZones/")
*/
function reloadBroadcastZones(APIURL = "BroadcastZones/") {
window.BroadcastZoneList = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("reloadBroadcastZones : ", okdata)
window.BroadcastZoneList.push(...okdata);
fill_broadcastzonetablebody(window.BroadcastZoneList);
} else console.log("reloadBroadcastZones: okdata is not array");
}, (errdata) => {
alert("Error loading broadcast zones : " + errdata.message);
});
}
function fetchSoundChannels(APIURL = "SoundChannel/") {
window.SoundChannelList = [];
fetchAPI(APIURL + "SoundChannelDescriptions", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("fetchSoundChannels : ", okdata)
window.SoundChannelList.push(...okdata);
} else console.log("fetchSoundChannels: okdata is not array");
}, (errdata) => {
alert("Error loading sound channels : " + errdata.message);
});
}
$(document).ready(function () { $(document).ready(function () {
console.log("broadcastzones.js loaded successfully"); console.log("broadcastzones.js loaded successfully");
@@ -67,7 +113,7 @@ $(document).ready(function () {
let $findzone = $('#findzone'); let $findzone = $('#findzone');
$findzone.on('input', function () { $findzone.off('input').on('input', function () {
let searchTerm = $findzone.val().trim().toLowerCase(); let searchTerm = $findzone.val().trim().toLowerCase();
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
window.selectedBroadcastZoneRow = null; window.selectedBroadcastZoneRow = null;
@@ -97,36 +143,35 @@ $(document).ready(function () {
$broadcastzonedescription.val(''); $broadcastzonedescription.val('');
// fill broadcastzonesoundchannel from SoundChannelList // fill broadcastzonesoundchannel from SoundChannelList
$broadcastzonesoundchannel.empty(); $broadcastzonesoundchannel.empty();
if (Array.isArray(SoundChannelList) && SoundChannelList.length > 0) { console.log("SoundChannelList:", window.SoundChannelList);
if (Array.isArray(window.SoundChannelList) && window.SoundChannelList.length > 0) {
// SoundChannelList ada isinya // SoundChannelList ada isinya
SoundChannelList.forEach(ch => { window.SoundChannelList.forEach(ch => {
if (ch.channel && ch.channel.length > 0){ if (ch && ch.length>0){
// hanya yang punya channel saja $broadcastzonesoundchannel.append($('<option>').val(ch).text(ch));
$broadcastzonesoundchannel.append($('<option>').val(ch.channel).text(ch.channel));
} }
}); });
} }
$broadcastzonebox.val(''); $broadcastzonebox.val('1').prop('disabled', true);
for (let i = 1; i <= 32; i++) { for (let i = 1; i <= 32; i++) {
cbRelay(i).prop('checked', false); cbRelay(i).prop('checked', false);
} }
} }
reloadBroadcastZones(APIURL_BroadcastZone, () => { fetchSoundChannels();
fill_broadcastzonetablebody(window.BroadcastZoneList); reloadBroadcastZones(APIURL_BroadcastZone);
});
$btnClear.click(() => { $btnClear.off('click').on('click', () => {
DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => { DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone); reloadBroadcastZones(APIURL_BroadcastZone);
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success clear broadcast zones: " + okdata.message); alert("Success clear broadcast zones: " + okdata.message);
}, (errdata) => { }, (errdata) => {
alert("Error clear broadcast zones: " + errdata.message); alert("Error clear broadcast zones: " + errdata.message);
}); });
}); });
$btnAdd.click(() => { $btnAdd.off('click').on('click', () => {
$broadcastzonemodal.modal('show'); $broadcastzonemodal.modal('show');
clearBroadcastZoneModal(); clearBroadcastZoneModal();
@@ -165,10 +210,8 @@ $(document).ready(function () {
Relay: relay Relay: relay
}; };
fetchAPI(APIURL_BroadcastZone + "Add", "POST", {}, bz, (okdata) => { fetchAPI(APIURL_BroadcastZone + "Add", "POST", {}, bz, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone, () => { reloadBroadcastZones(APIURL_BroadcastZone);
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success add new broadcast zone: " + okdata.message); alert("Success add new broadcast zone: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error add new broadcast zone: " + errdata.message); alert("Error add new broadcast zone: " + errdata.message);
}); });
@@ -180,12 +223,12 @@ $(document).ready(function () {
}); });
}); });
$btnRemove.click(() => { $btnRemove.off('click').on('click', () => {
if (window.selectedBroadcastZoneRow) { if (window.selectedBroadcastZoneRow) {
let cells = window.selectedBroadcastZoneRow.find('td'); let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */ /** @type {BroadcastZone} */
let bz = { let bz = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), description: cells.eq(1).text(),
SoundChannel: cells.eq(2).text(), SoundChannel: cells.eq(2).text(),
Box: cells.eq(3).text(), Box: cells.eq(3).text(),
@@ -193,10 +236,8 @@ $(document).ready(function () {
}; };
if (confirm(`Are you sure to delete broadcast zone [${bz.index}] Description=${bz.description}?`)) { if (confirm(`Are you sure to delete broadcast zone [${bz.index}] Description=${bz.description}?`)) {
fetchAPI(APIURL_BroadcastZone + "DeleteByIndex/" + bz.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL_BroadcastZone + "DeleteByIndex/" + bz.index, "DELETE", {}, null, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone, () => { reloadBroadcastZones(APIURL_BroadcastZone);
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success delete broadcast zone: " + okdata.message); alert("Success delete broadcast zone: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete broadcast zone: " + errdata.message); alert("Error delete broadcast zone: " + errdata.message);
}); });
@@ -204,12 +245,12 @@ $(document).ready(function () {
} }
}); });
$btnEdit.click(() => { $btnEdit.off('click').on('click', () => {
if (window.selectedBroadcastZoneRow) { if (window.selectedBroadcastZoneRow) {
let cells = window.selectedBroadcastZoneRow.find('td'); let cells = window.selectedBroadcastZoneRow.find('td');
/** @type {BroadcastZone} */ /** @type {BroadcastZone} */
let bz = { let bz = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), description: cells.eq(1).text(),
SoundChannel: cells.eq(2).text(), SoundChannel: cells.eq(2).text(),
Box: cells.eq(3).text(), Box: cells.eq(3).text(),
@@ -221,12 +262,12 @@ $(document).ready(function () {
$broadcastzoneindex.val(bz.index); $broadcastzoneindex.val(bz.index);
$broadcastzonedescription.val(bz.description); $broadcastzonedescription.val(bz.description);
$broadcastzonesoundchannel.val(bz.SoundChannel); $broadcastzonesoundchannel.val(bz.SoundChannel);
$broadcastzonebox.val(bz.id); $broadcastzonebox.val(bz.Box);
if (bz.bp) { if (bz.Relay && bz.Relay.length > 0) {
bz.bp.split(';').forEach(relayId => { bz.Relay.split(';').map(Number).filter(n => !isNaN(n) && n>=1 && n<=8).forEach(relayId => {
let id = parseInt(relayId, 10); cbRelay(relayId).prop('checked', true);
cbRelay(id).prop('checked', true);
}); });
} }
$broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () { $broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () {
let description = $broadcastzonedescription.val().trim(); let description = $broadcastzonedescription.val().trim();
@@ -262,10 +303,8 @@ $(document).ready(function () {
Relay: relay Relay: relay
}; };
fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", {}, bzUpdate, (okdata) => { fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", {}, bzUpdate, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone, () => { reloadBroadcastZones(APIURL_BroadcastZone);
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success edit broadcast zone: " + okdata.message); alert("Success edit broadcast zone: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit broadcast zone: " + errdata.message); alert("Error edit broadcast zone: " + errdata.message);
}); });
@@ -278,16 +317,14 @@ $(document).ready(function () {
} }
}); });
$btnExport.click(() => { $btnExport.off('click').on('click', () => {
DoExport(APIURL_BroadcastZone, "broadcastzones.xlsx", {}); DoExport(APIURL_BroadcastZone, "broadcastzones.xlsx", {});
}); });
$btnImport.click(() => { $btnImport.off('click').on('click', () => {
DoImport(APIURL_BroadcastZone, (okdata) => { DoImport(APIURL_BroadcastZone, (okdata) => {
reloadBroadcastZones(APIURL_BroadcastZone, () => { reloadBroadcastZones(APIURL_BroadcastZone);
fill_broadcastzonetablebody(window.BroadcastZoneList);
alert("Success import broadcast zones: " + okdata.message); alert("Success import broadcast zones: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing broadcast zones from XLSX: " + errdata.message); alert("Error importing broadcast zones from XLSX: " + errdata.message);
}); });

View File

@@ -0,0 +1,7 @@
document.addEventListener('DOMContentLoaded', function() {
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bss-tooltip]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
})
}, false);

31
html/webpage/assets/js/dragdrop.js vendored Normal file
View File

@@ -0,0 +1,31 @@
const dropArea = document.getElementById("drop-area");
const fileInput = document.getElementById("file-input");
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, e => e.preventDefault());
dropArea.addEventListener(eventName, e => e.stopPropagation());
});
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, () => dropArea.classList.add('highlight'));
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, () => dropArea.classList.remove('highlight'));
});
dropArea.addEventListener('click', () => fileInput.click());
dropArea.addEventListener('drop', e => {
const files = e.dataTransfer.files;
handleFiles(files);
});
fileInput.addEventListener('change', e => {
handleFiles(e.target.files);
});
function handleFiles(files) {
console.log("file dropped");
}

View File

@@ -1,4 +1,15 @@
/**
* @typedef {Object} LanguageBank
* @property {number} index
* @property {string} tag
* @property {string} language
*
*/
/** List of Languagebank data loaded from server
* @type {LanguageBank[]}
*/
window.languagebankdata = [];
/** /**
* Currently selected languagebank row in the table * Currently selected languagebank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
@@ -39,7 +50,22 @@ function fill_languagebanktablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#tablesize').text("Table Size: " + vv.length);
} }
/**
* Reload language bank from server
* @param {string} APIURL API URL endpoint, default "LanguageLink/"
*/
function reloadLanguageBank(APIURL = "LanguageLink/") {
window.languagebankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.languagebankdata.push(...okdata);
window.selectedlanguagerow = null;
fill_languagebanktablebody(window.languagebankdata);
}
}, (errdata) => {
alert("Error loading languagebank : " + errdata.message);
});
}
$(document).ready(function () { $(document).ready(function () {
console.log('languagebank.js loaded'); console.log('languagebank.js loaded');
@@ -89,15 +115,11 @@ $(document).ready(function () {
} }
}); });
reloadLanguageBank(APIURL, () => { reloadLanguageBank(APIURL);
fill_languagebanktablebody(window.languagebankdata);
});
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "LanguageLink", (okdata) => { DoClear(APIURL, "LanguageLink", (okdata) => {
reloadLanguageBank(APIURL, () => { reloadLanguageBank(APIURL);
fill_languagebanktablebody(window.languagebankdata);
alert("Success clear languageLink : " + okdata.message); alert("Success clear languageLink : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear languageLink : " + errdata.message); alert("Error clear languageLink : " + errdata.message);
}); });
@@ -134,10 +156,8 @@ $(document).ready(function () {
language: langString language: langString
} }
fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => { fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => {
reloadLanguageBank(APIURL, () => {
fill_languagebanktablebody(window.languagebankdata);
alert("Success add language : " + okdata.message); alert("Success add language : " + okdata.message);
}); reloadLanguageBank(APIURL);
}, (errdata) => { }, (errdata) => {
alert("Error add language : " + errdata.message); alert("Error add language : " + errdata.message);
}); });
@@ -155,16 +175,14 @@ $(document).ready(function () {
let cells = window.selectedlanguagerow.find('td'); let cells = window.selectedlanguagerow.find('td');
/** @type {Language} */ /** @type {Language} */
let ll = { let ll = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
tag: cells.eq(1).text(), tag: cells.eq(1).text(),
language: cells.eq(2).text() language: cells.eq(2).text()
} }
if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) { if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => {
reloadLanguageBank(APIURL, () => { reloadLanguageBank(APIURL);
fill_languagebanktablebody(window.languagebankdata);
alert("Success delete language : " + okdata.message); alert("Success delete language : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete language : " + errdata.message); alert("Error delete language : " + errdata.message);
}); });
@@ -176,7 +194,7 @@ $(document).ready(function () {
let cells = window.selectedlanguagerow.find('td'); let cells = window.selectedlanguagerow.find('td');
/** @type {Language} */ /** @type {Language} */
let ll = { let ll = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
tag: cells.eq(1).text(), tag: cells.eq(1).text(),
language: cells.eq(2).text() language: cells.eq(2).text()
} }
@@ -221,10 +239,8 @@ $(document).ready(function () {
ll.tag = tag; ll.tag = tag;
ll.language = langString; ll.language = langString;
fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => { fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => {
reloadLanguageBank(APIURL, () => { reloadLanguageBank(APIURL);
fill_languagebanktablebody(window.languagebankdata);
alert("Success edit language : " + okdata.message); alert("Success edit language : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit language : " + errdata.message); alert("Error edit language : " + errdata.message);
}); });
@@ -246,10 +262,8 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadLanguageBank(APIURL, () => { reloadLanguageBank(APIURL);
fill_languagebanktablebody(window.languagebankdata);
alert("Success import languagebank : " + okdata.message); alert("Success import languagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing languagebank from XLSX : " + errdata.message); alert("Error importing languagebank from XLSX : " + errdata.message);
}); });

View File

@@ -1,4 +1,19 @@
/**
* @typedef {Object} MessageBank
* @property {number} index
* @property {string} description
* @property {string} language
* @property {number} aNN_ID
* @property {string} voice_Type
* @property {string} message_Detail
* @property {string} message_TAGS
*/
/**
* List of Messagebank data loaded from server
* @type {MessageBank[]}
*/
window.messagebankdata ??= [];
/** /**
* Currently selected messagebank row in the table * Currently selected messagebank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
@@ -44,7 +59,22 @@ function fill_messagebanktablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#tablesize').text("Table Size: " + vv.length);
} }
/**
* Reload message bank from server
* @param {string} APIURL API URL endpoint, default "MessageBank/"
*/
function reloadMessageBank(APIURL = "MessageBank/") {
window.messagebankdata ??= [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.messagebankdata.push(...okdata);
window.selectedmessagerow = null;
fill_messagebanktablebody(window.messagebankdata);
}
}, (errdata) => {
alert("Error loading messagebank : " + errdata.message);
});
}
$(document).ready(function () { $(document).ready(function () {
console.log("messagebank.js loaded"); console.log("messagebank.js loaded");
@@ -91,17 +121,32 @@ $(document).ready(function () {
function refill_messageavailablevariables() { function refill_messageavailablevariables() {
$messageavailablevariables.empty(); $messageavailablevariables.empty();
categories.forEach(cat => { categories.forEach(cat => {
$messageavailablevariables.append(ListItem(`{${cat}}`)); $messageavailablevariables.append(ListItem(`[${cat}]`));
}); });
if ($messagelanguage.val() && $messagevoicetype.val()) { let lang = $messagelanguage.val();
soundbankdata let vt = $messagevoicetype.val();
.filter(sb => sb.language.toLowerCase() === $messagelanguage.val().toLowerCase()) if (lang && lang.length > 0){
.filter(sb => sb.voiceType.toLowerCase() === $messagevoicetype.val().toLowerCase()) console.log("Selected Language:", lang);
.filter(sb => sb.category.toLowerCase() === "phrase") if (vt && vt.length > 0){
.forEach(sb => { console.log("Selected Voice Type:", vt);
$messageavailablevariables.append(ListItem(`[${sb.Description}]`));
fetchAPI(`SoundBank/GetPhrases/${lang}/${vt}`, "GET", {}, null, (okdata) => {
if (Array.isArray(okdata) && okdata.length > 0) {
console.log(`Loaded ${okdata.length} phrases from soundbank for language=${lang} and voiceType=${vt}`);
console.log(JSON.stringify(okdata));
okdata.forEach(sb => {
if (sb.description && sb.description.length > 0) {
$messageavailablevariables.append(ListItem(`${sb.description} [${sb.TAG}]`));
}
}); });
} }
}, (errdata) => {
//alert("Error loading phrases from soundbank : " + errdata.message);
});
}
}
} }
@@ -113,7 +158,7 @@ $(document).ready(function () {
$messagedescription.val(''); $messagedescription.val('');
// fill messagelanguage options from languages[] // fill messagelanguage options from languages[]
$messagelanguage.empty(); $messagelanguage.empty();
languages.forEach(lang => { window.languages.forEach(lang => {
$messagelanguage.append(new Option(lang, lang)); $messagelanguage.append(new Option(lang, lang));
}); });
$messagelanguage.val(null); $messagelanguage.val(null);
@@ -124,7 +169,7 @@ $(document).ready(function () {
$messageannid.val(1); $messageannid.val(1);
// fill messagevoicetype options from voiceTypes[] // fill messagevoicetype options from voiceTypes[]
$messagevoicetype.empty(); $messagevoicetype.empty();
voiceTypes.forEach(vt => { window.voiceTypes.forEach(vt => {
$messagevoicetype.append(new Option(vt, vt)); $messagevoicetype.append(new Option(vt, vt));
}); });
$messagevoicetype.val(null); $messagevoicetype.val(null);
@@ -178,15 +223,11 @@ $(document).ready(function () {
}); });
reloadMessageBank(APIURL, () => { reloadMessageBank(APIURL);
fill_messagebanktablebody(window.messagebankdata);
});
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "Messagebank", (okdata) => { DoClear(APIURL, "Messagebank", (okdata) => {
reloadMessageBank(APIURL, () => { reloadMessageBank(APIURL);
fill_messagebanktablebody(window.messagebankdata);
alert("Success clear messagebank : " + okdata.message); alert("Success clear messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear messagebank : " + errdata.message); alert("Error clear messagebank : " + errdata.message);
}); });
@@ -261,10 +302,8 @@ $(document).ready(function () {
}; };
// send to server using fetchAPI // send to server using fetchAPI
fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => { fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => {
reloadMessageBank(APIURL, () => { reloadMessageBank(APIURL);
fill_messagebanktablebody(window.messagebankdata);
alert("Success add new messagebank : " + okdata.message); alert("Success add new messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error add new messagebank : " + errdata.message); alert("Error add new messagebank : " + errdata.message);
}); });
@@ -281,7 +320,7 @@ $(document).ready(function () {
let cells = window.selectedmessagerow.find('td'); let cells = window.selectedmessagerow.find('td');
/** @type {MessageBank} */ /** @type {MessageBank} */
let mb = { let mb = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), description: cells.eq(1).text(),
language: cells.eq(2).text(), language: cells.eq(2).text(),
aNN_ID: parseInt(cells.eq(3).text()), aNN_ID: parseInt(cells.eq(3).text()),
@@ -292,10 +331,8 @@ $(document).ready(function () {
if (confirm(`Are you sure to delete messagebank [${mb.index}] Description=${mb.description}? ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) { if (confirm(`Are you sure to delete messagebank [${mb.index}] Description=${mb.description}? ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) {
fetchAPI(APIURL + "DeleteByIndex/" + mb.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL + "DeleteByIndex/" + mb.index, "DELETE", {}, null, (okdata) => {
reloadMessageBank(APIURL, () => { reloadMessageBank(APIURL);
fill_messagebanktablebody(window.messagebankdata);
alert("Success delete messagebank : " + okdata.message); alert("Success delete messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete messagebank : " + errdata.message); alert("Error delete messagebank : " + errdata.message);
}); });
@@ -307,7 +344,7 @@ $(document).ready(function () {
let cells = window.selectedmessagerow.find('td'); let cells = window.selectedmessagerow.find('td');
/** @type {MessageBank} */ /** @type {MessageBank} */
let mb = { let mb = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), description: cells.eq(1).text(),
language: cells.eq(2).text(), language: cells.eq(2).text(),
aNN_ID: parseInt(cells.eq(3).text()), aNN_ID: parseInt(cells.eq(3).text()),
@@ -322,19 +359,8 @@ $(document).ready(function () {
// Fill modal fields with selected messagebank data // Fill modal fields with selected messagebank data
$messageindex.val(mb.index).prop('disabled', true); $messageindex.val(mb.index).prop('disabled', true);
$messagedescription.val(mb.description); $messagedescription.val(mb.description);
// Fill messagelanguage options and select current
$messagelanguage.empty();
languages.forEach(lang => {
$messagelanguage.append(new Option(lang, lang));
});
$messagelanguage.val(mb.language); $messagelanguage.val(mb.language);
// Fill messagevoicetype options and select current
$messagevoicetype.empty();
voiceTypes.forEach(vt => {
$messagevoicetype.append(new Option(vt, vt));
});
$messagevoicetype.val(mb.voice_Type); $messagevoicetype.val(mb.voice_Type);
// Set annid
$messageannid.val(mb.aNN_ID); $messageannid.val(mb.aNN_ID);
// Refill message available variables // Refill message available variables
refill_messageavailablevariables(); refill_messageavailablevariables();
@@ -401,10 +427,8 @@ $(document).ready(function () {
Message_TAGS: messagetags Message_TAGS: messagetags
}; };
fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => { fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => {
reloadMessageBank(APIURL, () => { reloadMessageBank(APIURL);
fill_messagebanktablebody(window.messagebankdata);
alert("Success edit messagebank : " + okdata.message); alert("Success edit messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit messagebank : " + errdata.message); alert("Error edit messagebank : " + errdata.message);
}); });
@@ -423,11 +447,8 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadMessageBank(APIURL, () => { reloadMessageBank(APIURL);
fill_messagebanktablebody(window.messagebankdata);
alert("Success import messagebank : " + okdata.message); alert("Success import messagebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing messagebank from XLSX : " + errdata.message); alert("Error importing messagebank from XLSX : " + errdata.message);
}); });

View File

@@ -0,0 +1,359 @@
/**
* @typedef {Object} StreamerOutputData
* @property {number} index - The index of the Barix connection.
* @property {string} channel - The channel name of the Barix connection.
* @property {string} ipaddress - The IP address of the Barix connection.
* @property {number} bufferRemain - The remaining buffer size of the Barix connection.
* @property {boolean} isPlaying - true = playback started, false = playback stopped
* @property {number} vu - The VU level of the Barix connection, 0 to 100.
*/
/**
* @typedef {Object} PagingQueue
* @property {number} index - The index of the paging queue item.
* @property {string} Date_Time - The date and time of the paging queue item.
* @property {string} Source - The source of the paging queue item.
* @property {string} Type - The type of the paging queue item.
* @property {string} Message - The message of the paging queue item.
* @property {string} BroadcastZones - The broadcast zones of the paging queue item.
*/
/**
* @typedef {Object} StreamerCard
* @property {JQuery<HTMLElement> | null} title - The jQuery result should be <h4> element.
* @property {JQuery<HTMLElement> | null} ip - The jQuery result should be <h6> element.
* @property {JQuery<HTMLElement> | null} buffer - The jQuery result should be <h6> element.
* @property {JQuery<HTMLElement> | null} status - The jQuery result should be <p> element.
* @property {JQuery<HTMLElement> | null} vu - The jQuery result should be <progress-bar> element.
*/
function getCardByIndex(index) {
let obj = {
// title is <h4> element wiht id `streamertitle${index}`, with index as two digit number, e.g. 01, 02, 03
title: $(`#streamertitle${index.toString().padStart(2, '0')}`),
// ip is <h6> element with id `streamerip${index}`, with index as two digit number, e.g. 01, 02, 03
ip: $(`#streamerip${index.toString().padStart(2, '0')}`),
// buffer is <h6> element with id `streamerbuffer${index}`, with index as two digit number, e.g. 01, 02, 03
buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`),
// status is <p> element with id `streamerstatus${index}`, with index as two digit number, e.g. 01, 02, 03
status: $(`#streamerstatus${index.toString().padStart(2, '0')}`),
// vu is <progress-bar> element with id `streamervu${index}`, with index as two digit number, e.g. 01, 02, 03
vu: $(`#streamervu${index.toString().padStart(2, '0')} .progress-bar`),
}
return obj;
}
/**
* Updates the streamer card with the provided values.
* @param {StreamerOutputData[]} values
*/
function UpdateStreamerCard(values) {
if (!Array.isArray(values) || values.length === 0) return;
function setProgress(index, $bar, value, max = 100) {
const v = Number(value ?? 0);
const pct = Math.max(0, Math.min(100, Math.round((v / max) * 100)));
//if (index!==1) return; // only update index 1 for testing
//console.log(`setProgress: index=${index}, value=${v}, pct=${pct}`);
$bar
.attr('aria-valuenow', v) // semantic value
.css('width', pct + '%') // visual width
.text(pct); // optional label
}
for (let i = 1; i <= 64; i++) {
let vv = values.find(v => v.index === i);
let card = getCardByIndex(i);
if (vv) {
// there is value for this index
if (card.title) card.title.text(vv.channel ? vv.channel : `Channel ${i.toString().padStart(2, '0')}`);
if (card.ip) card.ip.text(`IP Address: ${vv.ipaddress ? vv.ipaddress : 'N/A'}`);
if (card.buffer) card.buffer.text(`Buffer: ${vv.bufferRemain !== undefined && vv.bufferRemain !== null ? vv.bufferRemain.toString() : 'N/A'}`);
if (card.status) card.status.text(`Status: ${vv.isPlaying ? 'Playing' : 'Stopped'}`);
if (card.vu) {
setProgress(i, card.vu, vv.vu, 100);
}
} else {
// no value for this index, disable the card
if (card.title) card.title.text(`Channel ${i.toString().padStart(2, '0')}`);
if (card.ip) card.ip.text(`IP Address: N/A`);
if (card.buffer) card.buffer.text(`Buffer: N/A`);
if (card.status) card.status.text(`Status: Disconnected`);
if (card.vu) {
setProgress(i, card.vu, 0, 100);
}
}
}
}
/**
* @type {PagingQueue[]}
*/
window.PagingQueue = [];
/**
* @type {JQuery<HTMLElement> | null}
*/
window.selectedpagingrow = null;
/**
* @typedef {Object} QueueTable
* @property {number} index - The index of the automatic queue item.
* @property {string} Date_Time - The date and time of the automatic queue item.
* @property {string} Source - The source of the automatic queue item.
* @property {string} Type - The type of the automatic queue item.
* @property {string} Message - The message of the automatic queue item.
* @property {string} SB_TAGS - The SB_TAGS of the automatic queue item.
* @property {string} BroadcastZones - The broadcast zones of the automatic queue item.
* @property {number} Repeat - The repeat count of the automatic queue item.
* @property {string} Language - The language of the automatic queue item.
*/
/**
* @type {QueueTable[]}
*/
window.QueueTable = [];
/**
* @type {JQuery<HTMLElement> | null}
*/
window.selectedautomaticrow = null;
/**
* Fills the paging queue table body with the provided data.
* @param {PagingQueue[]} vv array of PagingQueue objects
* @returns
*/
function fill_pagingqueuetablebody(vv) {
$('#pagingqueuetable').empty();
if (!Array.isArray(vv) || vv.length === 0) return;
vv.forEach(item => {
// fill index and description columns using item properties
$('#pagingqueuetable').append(`<tr>
<td>${item.index}</td>
<td>${item.date_Time}</td>
<td>${item.source}</td>
<td>${item.type}</td>
<td>${item.message}</td>
<td>${item.broadcastZones}</td>
</tr>`);
let $addedrow = $('#pagingqueuetable tr:last');
$addedrow.off('click').on('click', function () {
if (window.selectedpagingrow) {
window.selectedpagingrow.find('td').css('background-color', '');
if (window.selectedpagingrow.is($(this))) {
window.selectedpagingrow = null;
$('#removepagingqueue').prop('disabled', true);
return;
}
}
window.selectedpagingrow = $(this);
window.selectedpagingrow.find('td').css('background-color', 'lightblue');
$('#removepagingqueue').prop('disabled', false);
});
});
}
/**
* Fills the automatic queue table body with the provided data.
* @param {QueueTable[]} vv array of QueueTable objects
* @returns
*/
function fill_automaticqueuetablebody(vv) {
$('#automaticqueuetable').empty();
if (!Array.isArray(vv) || vv.length === 0) return;
vv.forEach(item => {
// fill index and description columns using item properties
//console.log("fill_automaticqueuetablebody: item", item);
$('#automaticqueuetable').append(`<tr>
<td>${item.index}</td>
<td>${item.date_Time}</td>
<td>${item.source}</td>
<td>${item.type}</td>
<td>${item.message}</td>
<td>${item.broadcastZones}</td>
</tr>`);
let $addedrow = $('#automaticqueuetable tr:last');
$addedrow.off('click').on('click', function () {
if (window.selectedautomaticrow) {
window.selectedautomaticrow.find('td').css('background-color', '');
if (window.selectedautomaticrow.is($(this))) {
window.selectedautomaticrow = null;
$('#removeautomatictable').prop('disabled', true);
return;
}
}
window.selectedautomaticrow = $(this);
window.selectedautomaticrow.find('td').css('background-color', 'lightblue');
$('#removeautomatictable').prop('disabled', false);
});
});
}
function reloadPagingQueue(APIURL = "QueuePaging/") {
window.PagingQueue = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata) && okdata.length > 0) {
window.PagingQueue.push(...okdata);
fill_pagingqueuetablebody(window.PagingQueue);
} else {
console.log("reloadPagingQueue: okdata is not array");
}
}, (errdata) => {
console.log("reloadPagingQueue: errdata", errdata);
});
}
function reloadAutomaticQueue(APIURL = "QueueTable/") {
window.QueueTable = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata) && okdata.length > 0) {
window.QueueTable.push(...okdata);
fill_automaticqueuetablebody(window.QueueTable);
} else {
console.log("reloadAutomaticQueue: okdata is not array");
}
}, (errdata) => {
console.log("reloadAutomaticQueue: errdata", errdata);
});
}
function RemovePagingQueueByIndex(index, APIURL = "QueuePaging/") {
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
console.log("RemovePagingQueueByIndex: okdata", okdata);
reloadPagingQueue(APIURL);
}, (errdata) => {
console.log("RemovePagingQueueByIndex: errdata", errdata);
});
}
function RemoveAutomaticQueueByIndex(index, APIURL = "QueueTable/") {
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
console.log("RemoveAutomaticQueueByIndex: okdata", okdata);
reloadAutomaticQueue(APIURL);
}, (errdata) => {
console.log("RemoveAutomaticQueueByIndex: errdata", errdata);
});
}
$(document).ready(function () {
console.log("overview.js loaded");
$('#clearpagingqueue').off('click').on('click', function () {
DoClear("QueuePaging/", "Paging Queue", (okdata) => {
reloadPagingQueue();
alert("Success clear Paging Queue: " + okdata.message);
}, (errdata) => {
alert("Error clear Paging Queue: " + errdata.message);
});
});
$('#removepagingqueue').off('click').on('click', function () {
if (window.selectedpagingrow) {
let cells = window.selectedpagingrow.find('td');
let index = Number(cells.eq(0).text());
let description = cells.eq(1).text();
if (!isNaN(index) && description && description.length > 0) {
if (confirm(`Are you sure to remove Paging Queue Index: ${index} Description: ${description} ?`)) {
RemovePagingQueueByIndex(index);
window.selectedpagingrow = null;
$('#removepagingqueue').prop('disabled', true);
}
}
}
});
$('#clearautomatictable').off('click').on('click', function () {
DoClear("QueueTable/", "Automatic Queue", (okdata) => {
reloadAutomaticQueue();
alert("Success clear Automatic Queue: " + okdata.message);
}, (errdata) => {
alert("Error clear Automatic Queue: " + errdata.message);
});
});
$('#removeautomatictable').off('click').on('click', function () {
if (window.selectedautomaticrow) {
let cells = window.selectedautomaticrow.find('td');
let index = Number(cells.eq(0).text());
let description = cells.eq(1).text();
if (!isNaN(index) && description && description.length > 0) {
if (confirm(`Are you sure to remove Automatic Queue Index: ${index} Description: ${description} ?`)) {
RemoveAutomaticQueueByIndex(index);
window.selectedautomaticrow = null;
$('#removeautomatictable').prop('disabled', true);
}
}
}
});
let intervaljob1 = null;
let intervaljob2 = null;
function runIntervalJob() {
if (intervaljob1) clearInterval(intervaljob1);
intervaljob1 = setInterval(() => {
sendCommand("getStreamerOutputs", "");
}, 100);
if (intervaljob2) clearInterval(intervaljob2);
intervaljob2 = setInterval(() => {
sendCommand("getPagingQueue", "");
sendCommand("getAASQueue", "");
}, 2000);
console.log("overview.js interval job started");
}
runIntervalJob();
window.addEventListener('ws_connected', () => {
console.log("overview.js ws_connected event triggered");
runIntervalJob();
});
window.addEventListener('ws_disconnected', () => {
console.log("overview.js ws_disconnected event triggered");
if (intervaljob) clearInterval(intervaljob);
intervaljob = null;
});
window.addEventListener('ws_message', (event) => {
let rep = event.detail;
let cmd = rep.reply;
let data = rep.data;
if (cmd && cmd.length > 0) {
switch (cmd) {
case "getPagingQueue":
let pq = JSON.parse(data);
//console.log("getPagingQueue:", pq);
window.PagingQueue = [];
if (Array.isArray(pq) && pq.length > 0) {
window.PagingQueue.push(...pq);
}
fill_pagingqueuetablebody(window.PagingQueue);
break;
case "getAASQueue":
let aq = JSON.parse(data);
//console.log("getAASQueue:", aq);
window.QueueTable = [];
if (Array.isArray(aq) && aq.length > 0) {
window.QueueTable.push(...aq);
}
fill_automaticqueuetablebody(window.QueueTable);
break;
case "getStreamerOutputs":
/**
* @type {StreamerOutputData[]}
*/
let so = JSON.parse(data);
UpdateStreamerCard(so);
break;
}
}
});
$(window).on('beforeunload', function () {
console.log("overview.js beforeunload event triggered");
clearInterval(intervaljob1);
clearInterval(intervaljob2);
intervaljob1 = null;
intervaljob2 = null;
});
});

View File

@@ -1,4 +1,20 @@
/**
* @typedef {Object} ScheduleBank
* @property {number} index
* @property {string} description
* @property {string} day
* @property {string} time
* @property {string} soundpath
* @property {number} repeat
* @property {boolean} enable
* @property {string} broadcastZones
* @property {string} language
*/
/** List of Schedulebank data loaded from server
* @type {ScheduleBank[]}
*/
window.schedulebankdata = [];
/** /**
* Currently selected schedulebank row in the table * Currently selected schedulebank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
@@ -45,7 +61,22 @@ function fill_schedulebanktablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#tablesize').text("Table Size: " + vv.length);
} }
/**
* Reload timer bank from server
* @param {string} APIURL API URL endpoint, default "ScheduleBank/"
*/
function reloadTimerBank(APIURL = "ScheduleBank/") {
window.schedulebankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.schedulebankdata.push(...okdata);
selectedschedulerow = null;
fill_schedulebanktablebody(window.schedulebankdata);
}
}, (errdata) => {
alert("Error loading schedulebank : " + errdata.message);
});
}
$(document).ready(function () { $(document).ready(function () {
console.log("schedulebank.js loaded successfully"); console.log("schedulebank.js loaded successfully");
@@ -70,28 +101,30 @@ $(document).ready(function () {
let $schedulehour = $schedulemodal.find('#schedulehour'); let $schedulehour = $schedulemodal.find('#schedulehour');
// number input 0-59 // number input 0-59
let $scheduleminute = $schedulemodal.find('#scheduleminute'); let $scheduleminute = $schedulemodal.find('#scheduleminute');
// select for messagebank // select2 for message
let $schedulesoundpath = $schedulemodal.find('#schedulesoundpath'); let $schedulemessage = $schedulemodal.find('#schedulemessage');
$schedulemessage.select2({});
// number input 0-5 // number input 0-5
let $schedulerepeat = $schedulemodal.find('#schedulerepeat'); let $schedulerepeat = $schedulemodal.find('#schedulerepeat');
// checkbox // checkbox
let $scheduleenable = $schedulemodal.find('#scheduleenable'); let $scheduleenable = $schedulemodal.find('#scheduleenable');
// div for list of checkboxes // select2 for broadcastzones
let $schedulezones = $schedulemodal.find('#schedulezones'); let $schedulezones = $schedulemodal.find('#schedulezones');
$schedulezones.select2({});
// radio button for everyday // radio button for everyday
let $scheduleeveryday = $schedulemodal.find('#scheduleeveryday'); let $scheduleeveryday = $schedulemodal.find('#scheduleeveryday');
// radio button for weekdays // radio button for weekly
let $schedulesunday = $schedulemodal.find('#schedulesunday'); let $scheduleweekly = $schedulemodal.find('#scheduleweekly');
let $schedulemonday = $schedulemodal.find('#schedulemonday'); // select2 for weekly selection
let $scheduletuesday = $schedulemodal.find('#scheduletuesday'); let $weeklyselect = $schedulemodal.find('#weeklyselect');
let $schedulewednesday = $schedulemodal.find('#schedulewednesday'); $weeklyselect.select2({});
let $schedulethursday = $schedulemodal.find('#schedulethursday');
let $schedulefriday = $schedulemodal.find('#schedulefriday');
let $schedulesaturday = $schedulemodal.find('#schedulesaturday');
// radio button for specific date // radio button for specific date
let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate'); let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
// date input // date input
let $scheduledate = $schedulemodal.find('#scheduledate'); let $scheduledate = $schedulemodal.find('#scheduledate');
// select2 for language
let $languageselect = $schedulemodal.find('#languageselect');
$languageselect.select2({});
$schedulespecialdate.on('change', function () { $schedulespecialdate.on('change', function () {
if ($(this).is(':checked')) { if ($(this).is(':checked')) {
@@ -106,26 +139,10 @@ $(document).ready(function () {
$scheduledescription.val(''); $scheduledescription.val('');
$schedulehour.val('0'); $schedulehour.val('0');
$scheduleminute.val('0'); $scheduleminute.val('0');
$schedulerepeat.val('0');
$schedulesoundpath.empty();
if (Array.isArray(window.messagebankdata) && window.messagebankdata.length > 0) {
window.messagebankdata.forEach(item => {
let str = item.description+" ["+item.aNN_ID+"]";
let option = `<option value="${str}">${str}</option>`;
if ($schedulesoundpath.find(`option[value="${str}"]`).length === 0) $schedulesoundpath.append(option); // check if $schedulesoundpath already has this option
})
}
$schedulerepeat.val('1');
$scheduleenable.prop('checked', true); $scheduleenable.prop('checked', true);
$scheduleeveryday.prop('checked', false); $scheduleeveryday.prop('checked', false);
$schedulesunday.prop('checked', false);
$schedulemonday.prop('checked', false);
$scheduletuesday.prop('checked', false);
$schedulewednesday.prop('checked', false);
$schedulethursday.prop('checked', false);
$schedulefriday.prop('checked', false);
$schedulesaturday.prop('checked', false);
$schedulespecialdate.prop('checked', false); $schedulespecialdate.prop('checked', false);
$scheduledate.prop('disabled', true).val(''); $scheduledate.prop('disabled', true).val('');
@@ -149,15 +166,11 @@ $(document).ready(function () {
}); });
reloadTimerBank(APIURL, () => { reloadTimerBank(APIURL);
fill_schedulebanktablebody(window.schedulebankdata);
});
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "Timerbank", (okdata) => { DoClear(APIURL, "Timerbank", (okdata) => {
reloadTimerBank(APIURL,() => { reloadTimerBank(APIURL);
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success clear schedulebank : " + okdata.message); alert("Success clear schedulebank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear schedulebank : " + errdata.message); alert("Error clear schedulebank : " + errdata.message);
}); });
@@ -218,10 +231,8 @@ $(document).ready(function () {
}; };
fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => { fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => {
reloadTimerBank(APIURL, () => {
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success add schedule: " + okdata.message); alert("Success add schedule: " + okdata.message);
}); reloadTimerBank(APIURL);
}, (errdata) => { }, (errdata) => {
alert("Error add schedule: " + errdata.message); alert("Error add schedule: " + errdata.message);
}); });
@@ -234,7 +245,7 @@ $(document).ready(function () {
let cells = window.selectedschedulerow.find('td'); let cells = window.selectedschedulerow.find('td');
/** @type {ScheduleBank} */ /** @type {ScheduleBank} */
let sr = { let sr = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), description: cells.eq(1).text(),
day: cells.eq(2).text(), day: cells.eq(2).text(),
time: cells.eq(3).text(), time: cells.eq(3).text(),
@@ -246,10 +257,8 @@ $(document).ready(function () {
} }
if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) { if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) {
fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => {
reloadTimerBank(APIURL, () => { reloadTimerBank(APIURL);
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success delete schedule : " + okdata.message); alert("Success delete schedule : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete schedule : " + errdata.message); alert("Error delete schedule : " + errdata.message);
}); });
@@ -261,7 +270,7 @@ $(document).ready(function () {
let cells = window.selectedschedulerow.find('td'); let cells = window.selectedschedulerow.find('td');
/** @type {ScheduleBank} */ /** @type {ScheduleBank} */
let sr = { let sr = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), description: cells.eq(1).text(),
day: cells.eq(2).text(), day: cells.eq(2).text(),
time: cells.eq(3).text(), time: cells.eq(3).text(),
@@ -366,10 +375,8 @@ $(document).ready(function () {
}; };
fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => { fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => {
reloadTimerBank(APIURL, () => {
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success edit schedule: " + okdata.message); alert("Success edit schedule: " + okdata.message);
}); reloadTimerBank(APIURL);
}, (errdata) => { }, (errdata) => {
alert("Error edit schedule: " + errdata.message); alert("Error edit schedule: " + errdata.message);
}); });
@@ -384,10 +391,8 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadTimerBank(APIURL, () => { reloadTimerBank(APIURL);
fill_schedulebanktablebody(window.schedulebankdata);
alert("Success import schedulebank from XLSX : " + okdata.message); alert("Success import schedulebank from XLSX : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing schedulebank from XLSX : " + errdata.message); alert("Error importing schedulebank from XLSX : " + errdata.message);
}); });

View File

@@ -20,263 +20,6 @@ window.languages = [];
*/ */
window.scheduledays = [] window.scheduledays = []
/**
* @typedef {Object} MessageBank
* @property {number} index
* @property {string} description
* @property {string} language
* @property {number} aNN_ID
* @property {string} voice_Type
* @property {string} message_Detail
* @property {string} message_TAGS
*/
/**
* List of Messagebank data loaded from server
* taruh di sini karena dipakai di banyak tempat
* @type {MessageBank[]}
*/
window.messagebankdata ??= [];
/**
* Reload message bank from server
* @param {string} APIURL API URL endpoint, default "MessageBank/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadMessageBank(APIURL = "MessageBank/", callback=null) {
window.messagebankdata ??= [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.messagebankdata.push(...okdata);
console.log("Loaded " + window.messagebankdata.length + " message bank items");
window.selectedmessagerow = null;
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading messagebank : " + errdata.message);
});
}
/**
* @typedef {Object} ScheduleBank
* @property {number} index
* @property {string} description
* @property {string} day
* @property {string} time
* @property {string} soundpath
* @property {number} repeat
* @property {boolean} enable
* @property {string} broadcastZones
* @property {string} language
*/
/** List of Schedulebank data loaded from server
* @type {ScheduleBank[]}
*/
window.schedulebankdata = [];
/**
* Reload timer bank from server
* taruh di sini karena dipakai di banyak tempat
* @param {string} APIURL API URL endpoint, default "ScheduleBank/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadTimerBank(APIURL = "ScheduleBank/", callback=null) {
window.schedulebankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.schedulebankdata.push(...okdata);
selectedschedulerow = null;
console.log("Loaded " + window.schedulebankdata.length + " schedule bank items");
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading schedulebank : " + errdata.message);
});
}
/**
* @typedef {Object} BroadcastZone
* @property {number} index
* @property {string} description
* @property {String} SoundChannel
* @property {String} Box
* @property {String} Relay
*/
/**
* List of broadcast zones available
* @type {BroadcastZone[]}
*/
window.BroadcastZoneList ??= [];
/**
* Reload broadcast zones from server
* taruh di sini karena dipakai di banyak tempat
* @param {String} APIURL API URL endpoint (default "BroadcastZones/")
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadBroadcastZones(APIURL = "BroadcastZones/", callback=null) {
window.BroadcastZoneList = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("reloadBroadcastZones : ", okdata)
window.BroadcastZoneList.push(...okdata);
console.log("Loaded " + window.BroadcastZoneList.length + " broadcast zone items");
window.selectedbroadcastzonerow = null;
if (callback && typeof callback === 'function') callback();
} else console.log("reloadBroadcastZones: okdata is not array");
}, (errdata) => {
alert("Error loading broadcast zones : " + errdata.message);
});
}
/**
* @typedef {Object} SoundBank
* @property {number} index
* @property {string} description
* @property {string} tag
* @property {string} category
* @property {string} language
* @property {string} voiceType
* @property {string} path
*/
/**
* @typedef {Object} Select2item
* @property {number} id
* @property {string} text
*/
/**
* List of Soundbank data loaded from server
* taruh di sini karena dipakai di banyak tempat
* @type {SoundBank[]}
*/
window.soundbankdata = [];
/**
* List of sound files in the soundbank directory, that ends with .wav or .mp3
* taruh di sini karena dipakai di banyak tempat
* @type {string[]}
*/
window.soundbankfiles = [];
/**
* Select2 data source
* See https://select2.org/data-sources/formats
* @type {Select2item[]}
*/
window.select2data = [];
/**
* Reload sound bank from server
* @param {String} APIURL API URL endpoint, default "SoundBank/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadSoundBank(APIURL = "SoundBank/", callback=null) {
window.soundbankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.soundbankdata.push(...okdata);
window.selectedsoundrow = null;
console.log("Loaded " + window.soundbankdata.length + " sound bank items");
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading soundbank : " + errdata.message);
});
}
/**
* Reload soundbank files from server
* @param {String} APIURL API URL endpoint (default "SoundBank/")
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadSoundbankFiles(APIURL = "SoundBank/",callback=null) {
window.soundbankfiles = [];
fetchAPI(APIURL + "ListFiles", "GET", {}, null, (okdata) => {
// okdata is a string contains elements separated by semicolon ;
if (Array.isArray(okdata)) {
window.soundbankfiles = okdata.filter(item => item.trim().length > 0);
// refill select2data
window.select2data = window.soundbankfiles.map((item, index) => ({ id: index + 1, text: item }));
console.log("Loaded " + window.soundbankfiles.length + " sound bank files");
if (callback && typeof callback === 'function') callback();
} else console.log("reloadSoundbankFiles: okdata is not array");
}, (errdata) => {
alert("Error loading soundbank files : " + errdata.message);
});
}
/**
* @typedef {Object} SoundChannel
* @property {number} index - The index of the sound channel.
* @property {string} channel - The name of the sound channel.
* @property {string} ip - The IP address associated with the sound channel.
*/
/**
* @type {SoundChannel[]}
*/
window.soundChannels = [];
/**
* Reload sound channels from server
* @param {String} APIURL API URL endpoint (default "SoundChannel/")
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadSoundChannel(APIURL = "SoundChannel/", callback=null) {
window.soundChannels = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("reloadSoundChannel : ", okdata)
window.soundChannels.push(...okdata);
window.selectedsoundchannelrow = null;
console.log("Loaded " + window.soundChannels.length + " sound channel items");
if (callback && typeof callback === 'function') callback();
} else console.log("reloadSoundChannel: okdata is not array");
}, (errdata) => {
alert("Error loading sound channels : " + errdata.message);
});
}
/**
* @typedef {Object} LanguageBank
* @property {number} index
* @property {string} tag
* @property {string} language
*
*/
/** List of Languagebank data loaded from server
* @type {LanguageBank[]}
*/
window.languagebankdata = [];
/**
* Reload language bank from server
* @param {string} APIURL API URL endpoint, default "LanguageLink/"
* @param {Function|null} callback Optional callback function to execute after loading
*/
function reloadLanguageBank(APIURL = "LanguageLink/", callback=null) {
window.languagebankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.languagebankdata.push(...okdata);
window.selectedlanguagerow = null;
console.log("Loaded " + window.languagebankdata.length + " language bank items");
if (callback && typeof callback === 'function') callback();
}
}, (errdata) => {
alert("Error loading languagebank : " + errdata.message);
});
}
/** /**
* Create a list item element * Create a list item element
* @param {String} text Text Content for the list item * @param {String} text Text Content for the list item
@@ -568,26 +311,41 @@ $(document).ready(function () {
getCategories(); getCategories();
getLanguages(); getLanguages();
getScheduledDays(); getScheduledDays();
reloadMessageBank();
reloadTimerBank();
reloadBroadcastZones();
reloadSoundBank();
reloadSoundbankFiles();
reloadSoundChannel();
reloadLanguageBank();
// Initialize WebSocket connection
window.ws = new WebSocket(wsURL);
window.ws.onopen = () => { // reconnect handle
let ws_reconnect;
function reconnect() {
if (window.ws && window.ws.readyState === WebSocket.OPEN) return;
const s = new WebSocket(wsURL);
s.addEventListener('open', () => {
console.log('WebSocket connection established'); console.log('WebSocket connection established');
$('#onlineindicator').attr('src', window.greencircle); $('#onlineindicator').attr('src', window.greencircle);
};
window.ws.onmessage = (event) => { if (ws_reconnect) {
// stop reconnect attempts
clearTimeout(ws_reconnect);
ws_reconnect = null;
}
window.dispatchEvent(new Event('ws_connected'));
});
s.addEventListener('close', () => {
console.log('WebSocket connection closed');
window.dispatchEvent(new Event('ws_disconnected'));
resetStatusIndicators();
if (!ws_reconnect) {
clearTimeout(ws_reconnect);
ws_reconnect = null;
}
ws_reconnect = setTimeout(reconnect, 5000); // try to reconnect every 5 seconds
});
s.addEventListener('message', (event) => {
if ($('#onlineindicator').attr('src') !== window.greencircle) { if ($('#onlineindicator').attr('src') !== window.greencircle) {
$('#onlineindicator').attr('src', window.greencircle); $('#onlineindicator').attr('src', window.greencircle);
} }
let rep = JSON.parse(event.data); let rep = JSON.parse(event.data);
window.dispatchEvent(new CustomEvent('ws_message', { detail: rep }));
let cmd = rep.reply let cmd = rep.reply
let data = rep.data; let data = rep.data;
if (cmd && cmd.length > 0) { if (cmd && cmd.length > 0) {
@@ -602,7 +360,15 @@ $(document).ready(function () {
$('#diskstatus').text("Disk : " + data) $('#diskstatus').text("Disk : " + data)
break; break;
case "getNetworkStatus": case "getNetworkStatus":
$('#networkstatus').text("Network : " + data) let result = "";
let json = JSON.parse(data);
if (Array.isArray(json) && json.length > 0) {
json.forEach((net) => {
if (result.length > 0) result += "\n"
result += `${net.displayName} (${net.ipV4addr.join(";")}) TX:${(net.txSpeed / 1024).toFixed(1)} KB/s RX:${(net.rxSpeed / 1024).toFixed(1)} KB/s`
})
} else result = "N/A";
$('#networkstatus').text(result)
break; break;
case "getSystemTime": case "getSystemTime":
$('#datetimetext').text(data) $('#datetimetext').text(data)
@@ -610,18 +376,18 @@ $(document).ready(function () {
} }
} }
}; });
window.ws.onclose = () => { window.ws = s;
console.log('WebSocket connection closed'); }
resetStatusIndicators();
};
// window.ws.onerror = (error) => {
// console.error('WebSocket error:', error);
// };
reconnect();
window.addEventListener('beforeunload', () => {
try{
window.ws?.close(1000, "Client closed connection");
} catch (error) {
console.error("Error closing WebSocket connection:", error);
}
});
setInterval(() => { setInterval(() => {
sendCommand("getCPUStatus", "") sendCommand("getCPUStatus", "")

2
html/webpage/assets/js/select2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,27 @@
/**
* @typedef {Object} Select2item
* @property {number} id
* @property {string} text
*/
/**
* @typedef {Object} SoundBank
* @property {number} index
* @property {string} description
* @property {string} tag
* @property {string} category
* @property {string} language
* @property {string} voiceType
* @property {string} path
*/
/**
* List of Soundbank data loaded from server
* @type {SoundBank[]}
*/
window.soundbankdata = [];
/** /**
* Currently selected soundbank row in the table * Currently selected soundbank row in the table
* @type {JQuery<HTMLElement>|null} * @type {JQuery<HTMLElement>|null}
@@ -7,13 +30,37 @@ window.selectedsoundrow = null;
/**
* Select2 data source
* See https://select2.org/data-sources/formats
* @type {Select2item[]}
*/
window.select2data = [];
/**
* Reload sound bank from server
* @param {String} APIURL API URL endpoint, default "SoundBank/"
*/
function reloadSoundBank(APIURL = "SoundBank/") {
window.soundbankdata = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.soundbankdata.push(...okdata);
window.selectedsoundrow = null;
fill_soundbanktablebody(window.soundbankdata);
}
}, (errdata) => {
alert("Error loading soundbank : " + errdata.message);
});
}
/** /**
* Fill soundbank table body with values * Fill soundbank table body with values
* @param {SoundBank[]} vv values to fill * @param {SoundBank[]} vv values to fill
*/ */
function fill_soundbanktablebody(vv) { function fill_soundbanktablebody(vv) {
$('#soundbanktablebody').empty(); $('#soundbanktablebody').empty();
console.log("Filling soundbank table with " + vv.length + " items");
if (!Array.isArray(vv) || vv.length === 0) return; if (!Array.isArray(vv) || vv.length === 0) return;
vv.forEach(item => { vv.forEach(item => {
const row = `<tr> const row = `<tr>
@@ -48,12 +95,60 @@ function fill_soundbanktablebody(vv) {
$('#tablesize').text("Table Size: " + vv.length); $('#tablesize').text("Table Size: " + vv.length);
} }
/**
* Reload soundbank files from server and filter by language, category, and voiceType
* @param {String} language
* @param {String} category
* @param {String} voiceType
* @param {Function} cb callback function when done
*/
function reloadSoundbankFiles(language, category, voiceType, cb=null) {
window.select2data = [];
$('#modalpath').empty().trigger('change');
if (language && language.length > 0) {
if (category && category.length > 0) {
if (voiceType && voiceType.length > 0) {
fetchAPI(`ListFiles/${language}/${voiceType}/${category}`, "GET", {}, null, (okdata) => {
console.log("reloadSoundbankFiles: got " + okdata.length + " items");
if (Array.isArray(okdata)){
window.select2data = okdata.map(p => ({id: getFilenameFromPath(p), text: getFilenameFromPath(p)}));
$('#modalpath').select2({
data: window.select2data,
placeholder: 'Select a sound file',
allowClear: true,
width: '100%',
dropdownParent: $('#soundbankmodal')
});
if (cb) cb();
}
}, (errdata) => {
alert("Error loading soundbank files : " + errdata.message);
});
}
}
}
}
function getFilenameFromPath(path) {
if (!path || path.length === 0) return "";
if (path.includes('\\')) {
let parts = path.split('\\');
return parts[parts.length - 1];
} else if (!path.includes('/')) {
let parts = path.split('/');
return parts[parts.length - 1];
}
}
$(document).ready(function () { $(document).ready(function () {
console.log("soundbank.js loaded successfully"); console.log("soundbank.js loaded successfully");
reloadSoundbankFiles();
$('#soundbanktablebody').empty(); $('#soundbanktablebody').empty();
window.selectedsoundrow = null; window.selectedsoundrow = null;
let $btnClear = $('#btnClear'); let $btnClear = $('#btnClear');
@@ -72,7 +167,9 @@ $(document).ready(function () {
let $modalcategory = $modal.find('#modalcategory'); let $modalcategory = $modal.find('#modalcategory');
let $modallanguage = $modal.find('#modallanguage'); let $modallanguage = $modal.find('#modallanguage');
let $modalvoicetype = $modal.find('#modalvoicetype'); let $modalvoicetype = $modal.find('#modalvoicetype');
let $modalpath = $modal.find('#modalpath'); let selected_category = null;
let selected_language = null;
let selected_voicetype = null;
/** /**
* Clear soundbank modal inputs * Clear soundbank modal inputs
@@ -99,18 +196,11 @@ $(document).ready(function () {
$modalvoicetype.append(new Option(vt, vt)); $modalvoicetype.append(new Option(vt, vt));
}); });
$modalvoicetype.val(null); $modalvoicetype.val(null);
// fill modalpath options from soundbankfiles[] $('#modalpath').select2()
// TODO read https://jeesite.com/front/jquery-select2/4.0/index.htm
console.log("window.select2data has " + window.select2data.length + " items");
$('#modalpath').select2({
data: window.select2data
})
} }
reloadSoundBank(APIURL, () => { reloadSoundBank(APIURL);
fill_soundbanktablebody(window.soundbankdata);
});
$('#findsoundbank').on('input', function () { $('#findsoundbank').on('input', function () {
let searchTerm = $(this).val().trim().toLowerCase(); let searchTerm = $(this).val().trim().toLowerCase();
if (searchTerm.length > 0) { if (searchTerm.length > 0) {
@@ -124,23 +214,88 @@ $(document).ready(function () {
}); });
$btnClear.click(() => { $btnClear.click(() => {
DoClear(APIURL, "Soundbank", (okdata) => { DoClear(APIURL, "Soundbank", (okdata) => {
reloadSoundBank(APIURL, () => { reloadSoundBank(APIURL);
fill_soundbanktablebody(window.soundbankdata);
alert("Success clear soundbank : " + okdata.message); alert("Success clear soundbank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear soundbank : " + errdata.message); alert("Error clear soundbank : " + errdata.message);
}); });
}); });
function SetupEventForCategoryLanguageVoiceType() {
$modalcategory.off('change').on('change', function () {
selected_category = $(this).val();
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype);
});
$modallanguage.off('change').on('change', function () {
selected_language = $(this).val();
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype);
});
$modalvoicetype.off('change').on('change', function () {
selected_voicetype = $(this).val();
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype);
});
}
$btnAdd.click(() => { $btnAdd.click(() => {
$modal.modal('show'); $modal.modal('show');
clearSoundbankModal(); clearSoundbankModal();
// event on selection change of language, category, voiceType
SetupEventForCategoryLanguageVoiceType();
// event on Click save button // event on Click save button
$modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () { $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
//TODO Add soundbank save process here let description = $modaldescription.val().trim();
let tag = $modaltag.val().trim();
let category = $modalcategory.val();
let language = $modallanguage.val();
let voiceType = $modalvoicetype.val();
let path = $('#soundbankmodal #modalpath').val();
if (!description || description.length === 0) {
alert("Description is required");
return;
}
if (!tag || tag.length === 0) {
alert("Tag is required");
return;
}
if (!category || category.length === 0) {
alert("Category is required");
return;
}
if (!language || language.length === 0) {
alert("Language is required");
return;
}
if (!voiceType || voiceType.length === 0) {
alert("Voice Type is required");
return;
}
if (!path || path.length === 0) {
alert("Path is required");
return;
}
$modal.modal('hide'); $modal.modal('hide');
/**
* @type {SoundBank}
*/
let nsb = {
index: 0,
Description: description,
TAG: tag,
Category: category,
Language: language,
VoiceType: voiceType,
Path: path
}
fetchAPI(APIURL + "Add", "POST", {}, nsb, (okdata) => {
reloadSoundBank(APIURL);
alert("Success add soundbank : " + okdata.message);
}, (errdata) => {
alert("Error add soundbank : " + errdata.message);
});
}); });
// event on Click close button // event on Click close button
$modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () { $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
@@ -152,7 +307,7 @@ $(document).ready(function () {
let cells = window.selectedsoundrow.find('td'); let cells = window.selectedsoundrow.find('td');
/** @type {SoundBank} */ /** @type {SoundBank} */
let sb = { let sb = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), description: cells.eq(1).text(),
tag: cells.eq(2).text(), tag: cells.eq(2).text(),
category: cells.eq(3).text(), category: cells.eq(3).text(),
@@ -162,10 +317,8 @@ $(document).ready(function () {
} }
if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) { if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) {
fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => { fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => {
reloadSoundBank(APIURL, () => { reloadSoundBank(APIURL);
fill_soundbanktablebody(window.soundbankdata);
alert("Success delete soundbank : " + okdata.message); alert("Success delete soundbank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error delete soundbank : " + errdata.message); alert("Error delete soundbank : " + errdata.message);
}); });
@@ -177,28 +330,91 @@ $(document).ready(function () {
let cells = window.selectedsoundrow.find('td'); let cells = window.selectedsoundrow.find('td');
/** @type {SoundBank} */ /** @type {SoundBank} */
let sb = { let sb = {
index: cells.eq(0).text(), index: Number(cells.eq(0).text()),
description: cells.eq(1).text(), Description: cells.eq(1).text(),
tag: cells.eq(2).text(), TAG: cells.eq(2).text(),
category: cells.eq(3).text(), Category: cells.eq(3).text(),
language: cells.eq(4).text(), Language: cells.eq(4).text(),
voiceType: cells.eq(5).text(), VoiceType: cells.eq(5).text(),
path: cells.eq(6).text() Path: cells.eq(6).text()
} }
if (confirm(`Are you sure to edit soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) { if (confirm(`Are you sure to edit soundbank [${sb.index}] Description=${sb.Description} Tag=${sb.TAG}?`)) {
$modal.modal('show'); $modal.modal('show');
$modal.off('hidden.bs.modal').on('hidden.bs.modal', function () { clearSoundbankModal();
SetupEventForCategoryLanguageVoiceType();
$modalindex.val(sb.index).prop('disabled', true);
$modaldescription.val(sb.Description);
$modaltag.val(sb.TAG);
$modalcategory.val(sb.Category);
selected_category = sb.Category;
$modallanguage.val(sb.Language);
selected_language = sb.Language;
$modalvoicetype.val(sb.VoiceType);
selected_voicetype = sb.VoiceType;
// load soundbank files for selected language, category, voiceType
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype, () => {
// set selected path
$('#modalpath').val(getFilenameFromPath(sb.Path)).trigger('change');
});
// event on Click save button // event on Click save button
$modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () { $modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
//TODO Add soundbank save process here let description = $modaldescription.val().trim();
let tag = $modaltag.val().trim();
let category = $modalcategory.val();
let language = $modallanguage.val();
let voiceType = $modalvoicetype.val();
let path = $('#soundbankmodal #modalpath').val();
if (!description || description.length === 0) {
alert("Description is required");
return;
}
if (!tag || tag.length === 0) {
alert("Tag is required");
return;
}
if (!category || category.length === 0) {
alert("Category is required");
return;
}
if (!language || language.length === 0) {
alert("Language is required");
return;
}
if (!voiceType || voiceType.length === 0) {
alert("Voice Type is required");
return;
}
if (!path || path.length === 0) {
alert("Path is required");
return;
}
if (description === sb.Description && tag === sb.TAG && category === sb.Category && language === sb.Language && voiceType === sb.VoiceType && path === sb.Path) {
alert("No changes detected");
return;
}
sb.Description = description;
sb.TAG = tag;
sb.Category = category;
sb.Language = language;
sb.VoiceType = voiceType;
sb.Path = path;
fetchAPI(APIURL + "UpdateByIndex/" + sb.index, "PATCH", {}, sb, (okdata) => {
reloadSoundBank(APIURL);
alert("Success update soundbank : " + okdata.message);
}, (errdata) => {
alert("Error update soundbank : " + errdata.message);
});
$modal.modal('hide'); $modal.modal('hide');
}); });
// event on Click close button // event on Click close button
$modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () { $modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
$modal.modal('hide'); $modal.modal('hide');
}); });
});
} }
} }
}); });
@@ -207,10 +423,8 @@ $(document).ready(function () {
}); });
$btnImport.click(() => { $btnImport.click(() => {
DoImport(APIURL, (okdata) => { DoImport(APIURL, (okdata) => {
reloadSoundBank(APIURL, () => { reloadSoundBank(APIURL);
fill_soundbanktablebody(window.soundbankdata);
alert("Success import soundbank : " + okdata.message); alert("Success import soundbank : " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing soundbank from XLSX : " + errdata.message); alert("Error importing soundbank from XLSX : " + errdata.message);
}); });

View File

@@ -1,4 +1,14 @@
/**
* @typedef {Object} SoundChannel
* @property {number} index - The index of the sound channel.
* @property {string} channel - The name of the sound channel.
* @property {string} ip - The IP address associated with the sound channel.
*/
/**
* @type {SoundChannel[]}
*/
window.soundChannels = [];
// Currently selected sound channel row in the table // Currently selected sound channel row in the table
window.selectedSoundChannel = null; window.selectedSoundChannel = null;
@@ -8,10 +18,9 @@ window.selectedSoundChannel = null;
* @param {SoundChannel[]} vv Sound channel data to populate the table. * @param {SoundChannel[]} vv Sound channel data to populate the table.
*/ */
function fill_soundchanneltablebody(vv) { function fill_soundchanneltablebody(vv) {
let $tbody = $('#soundchanneltablebody');
let $btnEditSoundChannel = $('#btnEditSoundChannel'); let $btnEditSoundChannel = $('#btnEditSoundChannel');
let $tablesizeSoundChannel = $('#tablesizeSoundChannel'); let $tablesizeSoundChannel = $('#tablesizeSoundChannel');
$tbody.empty(); $('#soundchanneltablebody').empty();
$tablesizeSoundChannel.text('Table Length : N/A'); $tablesizeSoundChannel.text('Table Length : N/A');
if (!Array.isArray(vv) || vv.length === 0) return; if (!Array.isArray(vv) || vv.length === 0) return;
@@ -22,9 +31,9 @@ function fill_soundchanneltablebody(vv) {
<td>${item.channel}</td> <td>${item.channel}</td>
<td>${item.ip}</td> <td>${item.ip}</td>
</tr>`; </tr>`;
$tbody.append(row); $('#soundchanneltablebody').append(row);
let $addedrow = $('#soundchanneltablebody tr:last'); let $addedrow = $('#soundchanneltablebody tr:last');
$addedrow.click(function () { $addedrow.off('click').on('click', function () {
if (selectedSoundChannel) { if (selectedSoundChannel) {
selectedSoundChannel.find('td').css('background-color', ''); selectedSoundChannel.find('td').css('background-color', '');
if (selectedSoundChannel.is($(this))) { if (selectedSoundChannel.is($(this))) {
@@ -41,7 +50,22 @@ function fill_soundchanneltablebody(vv) {
$tablesizeSoundChannel.text("Table Size: " + vv.length); $tablesizeSoundChannel.text("Table Size: " + vv.length);
} }
/**
* Reload sound channels from server
* @param {String} APIURL API URL endpoint (default "SoundChannel/")
*/
function reloadSoundChannel(APIURL = "SoundChannel/") {
window.soundChannels = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
//console.log("reloadSoundChannel : ", okdata)
window.soundChannels.push(...okdata);
fill_soundchanneltablebody(window.soundChannels);
} else console.log("reloadSoundChannel: okdata is not array");
}, (errdata) => {
alert("Error loading sound channels : " + errdata.message);
});
}
$(document).ready(function () { $(document).ready(function () {
console.log("soundchannel.js loaded successfully"); console.log("soundchannel.js loaded successfully");
@@ -58,7 +82,7 @@ $(document).ready(function () {
$btnEditSoundChannel.prop('disabled', true); $btnEditSoundChannel.prop('disabled', true);
let API_SoundChannel = "SoundChannel/"; let API_SoundChannel = "SoundChannel/";
$findsoundchannel.on('input', function () { $findsoundchannel.off('input').on('input', function () {
let searchTerm = $(this).val().toLowerCase(); let searchTerm = $(this).val().toLowerCase();
if (searchTerm.length==0){ if (searchTerm.length==0){
window.selectedSoundChannel = null; window.selectedSoundChannel = null;
@@ -83,20 +107,16 @@ $(document).ready(function () {
$soundchannelip.val(''); $soundchannelip.val('');
} }
reloadSoundChannel(API_SoundChannel, () => { reloadSoundChannel(API_SoundChannel);
fill_soundchanneltablebody(window.soundChannels); $btnReinitializeSoundChannel.off('click').on('click', () => {
});
$btnReinitializeSoundChannel.click(() => {
DoClear(API_SoundChannel, "SoundChannels", (okdata) => { DoClear(API_SoundChannel, "SoundChannels", (okdata) => {
reloadSoundChannel(API_SoundChannel, () => { reloadSoundChannel(API_SoundChannel);
fill_soundchanneltablebody(window.soundChannels);
alert("Success clear sound channels: " + okdata.message); alert("Success clear sound channels: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error clear sound channels: " + errdata.message); alert("Error clear sound channels: " + errdata.message);
}); });
}); });
$btnEditSoundChannel.click(() => { $btnEditSoundChannel.off('click').on('click', () => {
if (selectedSoundChannel) { if (selectedSoundChannel) {
let cells = selectedSoundChannel.find('td'); let cells = selectedSoundChannel.find('td');
/** @type {SoundChannel} */ /** @type {SoundChannel} */
@@ -135,10 +155,8 @@ $(document).ready(function () {
fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", {}, newsc, (okdata) => { fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", {}, newsc, (okdata) => {
reloadSoundChannel(API_SoundChannel, () => { reloadSoundChannel(API_SoundChannel);
fill_soundchanneltablebody(window.soundChannels);
alert("Success edit sound channel: " + okdata.message); alert("Success edit sound channel: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error edit sound channel: " + errdata.message); alert("Error edit sound channel: " + errdata.message);
}); });
@@ -152,16 +170,14 @@ $(document).ready(function () {
} }
} }
}); });
$btnExportSoundChannel.click(() => { $btnExportSoundChannel.off('click').on('click', () => {
DoExport(API_SoundChannel, "soundchannels.xlsx", {}); DoExport(API_SoundChannel, "soundchannels.xlsx", {});
}); });
$btnImportSoundChannel.click(() => { $btnImportSoundChannel.off('click').on('click', () => {
DoImport(API_SoundChannel, (okdata) => { DoImport(API_SoundChannel, (okdata) => {
reloadSoundChannel(API_SoundChannel, () => { reloadSoundChannel(API_SoundChannel);
fill_soundchanneltablebody(window.soundChannels);
alert("Success import sound channels: " + okdata.message); alert("Success import sound channels: " + okdata.message);
});
}, (errdata) => { }, (errdata) => {
alert("Error importing sound channels from XLSX: " + errdata.message); alert("Error importing sound channels from XLSX: " + errdata.message);
}); });

View File

@@ -4,9 +4,10 @@
* @property {string} username Username * @property {string} username Username
* @property {string} password Password (plain) * @property {string} password Password (plain)
* @property {string} location Location * @property {string} location Location
* @property {string} soundbank_tags Soundbank variable tags separated by semicolon ; * @property {string} airline_tags Airline variable tags (string) separated by semicolon ;
* @property {string} messagebank_ann_id Messagebank announcement ID separated by semicolon ; * @property {string} city_tags City variable tags (string) separated by semicolon ;
* @property {string} broadcastzones Broadcast zones separated by semicolon ; * @property {string} messagebank_ann_id Messagebank announcement ID (number) separated by semicolon ;
* @property {string} broadcastzones Broadcast zones description (string) separated by semicolon ;
*/ */
/** List of UserDB data loaded from server /** List of UserDB data loaded from server
@@ -20,6 +21,89 @@ window.userdb = [];
*/ */
window.selecteduserrow = null; window.selecteduserrow = null;
/**
* @typedef {Object} KeyValueMessage
* @property {string} key
* @property {string} value
*/
/**
* List of airline tags loaded from server
* @type {KeyValueMessage[]}
*/
window.airlinetags = [];
/**
* List of city tags loaded from server
* @type {KeyValueMessage[]}
*/
window.citytags = [];
/**
* List of message bank IDs loaded from server
* @type {KeyValueMessage[]}
*/
window.messagebankids = [];
/**
* List of broadcast zones description loaded from server
* @type {string[]}
*/
window.broadcastzones = [];
/**
* Get Messagebank ANN_IDs from server
*/
function get_messagebankids() {
window.messagebankids = [];
fetchAPI("MessageBank/" + "MessageIDs", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.messagebankids.push(...okdata);
}
}, (errdata) => {
alert("Error loading message bank IDs : " + errdata.message);
});
}
/**
* Get Airline Tags from server
*/
function get_airlinetags() {
window.airlinetags = [];
fetchAPI("SoundBank/" + "AirlineTags", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.airlinetags.push(...okdata);
}
}, (errdata) => {
alert("Error loading airline tags : " + errdata.message);
});
}
/**
* Get City Tags from server
*/
function get_citytags() {
window.citytags = [];
fetchAPI("SoundBank/" + "CityTags", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.citytags.push(...okdata);
}
}, (errdata) => {
alert("Error loading city tags : " + errdata.message);
});
}
/**
* Get Broadcast Zones descriptions from server
*/
function get_broadcastzones_descriptions() {
window.broadcastzones = [];
fetchAPI("BroadcastZones/" + "BroadcastZoneDescriptions", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) {
window.broadcastzones.push(...okdata);
}
}, (errdata) => {
alert("Error loading broadcast zones : " + errdata.message);
});
}
/** /**
* Fill user table body with values * Fill user table body with values
* @param {UserDB[]} vv values to fill * @param {UserDB[]} vv values to fill
@@ -34,14 +118,16 @@ function fill_usertablebody(vv) {
vv.forEach(item => { vv.forEach(item => {
const row = `<tr> const row = `<tr>
<td>${item.index}</td> <td>${item.index}</td>
<td>${item.datenya}</td> <td>${item.username}</td>
<td>${item.timenya}</td> <td>${item.location}</td>
<td>${item.machine}</td> <td>${item.airline_tags}</td>
<td>${item.description}</td> <td>${item.city_tags}</td>
<td>${item.messagebank_ann_id}</td>
<td>${item.broadcastzones}</td>
</tr>`; </tr>`;
$('#usertablebody').append(row); $('#usertablebody').append(row);
let $addedrow = $('#usertablebody tr:last'); let $addedrow = $('#usertablebody tr:last');
$addedrow.on('click', function () { $addedrow.off('click').on('click', function () {
if (window.selecteduserrow) { if (window.selecteduserrow) {
window.selecteduserrow.find('td').css('background-color', ''); window.selecteduserrow.find('td').css('background-color', '');
if (window.selecteduserrow.is($(this))) { if (window.selecteduserrow.is($(this))) {
@@ -63,9 +149,9 @@ function fill_usertablebody(vv) {
/** /**
* Reload UserDB from server with date and filter * Reload UserDB from server with date and filter
* @param {String} APIURL API URL endpoint , default "User/" * @param {String} APIURL API URL endpoint , default "UserManagement/"
*/ */
function reloaduserDB(APIURL = "User/") { function reloaduserDB(APIURL = "UserManagement/") {
window.userdb = []; window.userdb = [];
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => { fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
if (Array.isArray(okdata)) { if (Array.isArray(okdata)) {
@@ -79,50 +165,395 @@ function reloaduserDB(APIURL = "User/") {
$(document).ready(function () { $(document).ready(function () {
console.log("usermanagement.js ready"); console.log("usermanagement.js ready");
let $usertablebody = $('#usertablebody'); get_airlinetags();
let $finduser = $('#finduser'); get_citytags();
let $btnClear = $('#btnClear'); get_messagebankids();
let $btnAdd = $('#btnAdd'); get_broadcastzones_descriptions();
let $btnRemove = $('#btnRemove');
let $btnEdit = $('#btnEdit');
let $btnExport = $('#btnExport');
let $btnImport = $('#btnImport');
let APIURL = "User/";
// add / edit modal elements let APIURL = "UserManagement/";
let $addmodal = $('#addmodal');
let $modalindex = $('#modalindex');
let $modalusername = $('#modalusername');
let $modalpassword = $('#modalpassword');
let $modalverifypassword = $('#modalverifypassword');
let $modalsoundbank = $('#modalsoundbank');
let $modalmessagebank = $('#modalmessagebank');
let $modalbroadcastzones = $('#modalbroadcastzones');
let $btnShowSoundbankModal = $('#btnShowSoundbankModal');
let $btnShowMessagebankModal = $('#btnShowMessagebankModal');
let $btnShowBroaadcastZoneModal = $('#btnShowBroaadcastZoneModal');
let $usermanagementsave = $('#usermanagementsave');
let $usermanagementclose = $('#usermanagementclose');
// soundbank selection modal elements function clearAddModal() {
let $soundbankmodal = $('#soundbankmodal'); $('#modalindex').val("");
let $soundbankselection = $('#soundbankselection'); $('#modalusername').val("");
let $soundbankselectionsave = $('#soundbankselectionsave'); $('#modalpassword').val("");
let $soundbankselectionclose = $('#soundbankselectionclose'); $('#modalverifypassword').val("");
$('#modalairlinetags').val("");
$('#modalcitytags').val("");
$('#modalmessagebank').val("");
$('#modalbroadcastzones').val("");
$('#modallocation').val("");
}
function fill_citylist() {
$('#citylist').empty();
citytags.forEach(tag => {
let value = `${tag.value} [${tag.key}]`;
const row = `<div class="form-check">
<input class="form-check-input citytagcheckbox" type="checkbox" value="${tag.key}" id="citytag_${tag.key}">
<label class="form-check-label" for="citytag_${tag.key}">
${value}
</label>
</div>`;
$('#citylist').append(row);
});
}
function fill_airlinelist() {
$('#airlinelist').empty();
airlinetags.forEach(tag => {
let value = `${tag.value} [${tag.key}]`;
const row = `<div class="form-check">
<input class="form-check-input airlinetagcheckbox" type="checkbox" value="${tag.key}" id="airlinetag_${tag.key}">
<label class="form-check-label" for="airlinetag_${tag.key}">
${value}
</label>
</div>`;
$('#airlinelist').append(row);
});
}
// broadcast zone selection modal elements // broadcast zone selection modal elements
let $broadcastzonemodal = $('#broadcastzonemodal');
let $broadcastzoneselection = $('#broadcastzoneselection'); function fill_broadcastzonelist() {
let $broadcastzoneselectionsave = $('#broadcastzoneselectionsave'); $('#broadcastzonelist').empty();
let $broadcastzoneselectionclose = $('#broadcastzoneselectionclose'); broadcastzones.forEach(desc => {
const row = `<div class="form-check">
<input class="form-check-input broadcastzonecheckbox" type="checkbox" value="${desc}" id="broadcastzone_${desc}">
<label class="form-check-label" for="broadcastzone_${desc}">
${desc}
</label>
</div>`;
$('#broadcastzonelist').append(row);
});
}
// messagebank selection modal elements // messagebank selection modal elements
let $messagebankmodal = $('#messagebankmodal');
let $messagebankselection = $('#messagebankselection');
let $messagebankselectionsave = $('#messagebankselectionsave');
let $messagebankselectionclose = $('#messagebankselectionclose');
$usertablebody.empty(); function fill_messagebanklist() {
$finduser.on('input', function () { $('#messagebanklist').empty();
messagebankids.forEach(id => {
let value = `${id.value} [${id.key}]`;
const row = `<div class="form-check">
<input class="form-check-input messagebankidcheckbox" type="checkbox" value="${id.key}" id="messagebankid_${id.key}">
<label class="form-check-label" for="messagebankid_${id.key}">
${value}
</label>
</div>`;
$('#messagebanklist').append(row);
});
}
$('#usertablebody').empty();
reloaduserDB();
$('#finduser').off('input').on('input', function () {
let searchTerm = $(this).val().toLowerCase();
if (searchTerm.length > 0) {
let filteredUsers = window.userdb.filter(user =>
user.username.toLowerCase().includes(searchTerm) ||
user.airline_tags.toLowerCase().includes(searchTerm) ||
user.city_tags.toLowerCase().includes(searchTerm)
//user.messagebank_ann_id.toLowerCase().includes(searchTerm) ||
//user.broadcastzones.toLowerCase().includes(searchTerm)
);
fill_usertablebody(filteredUsers);
} else {
fill_usertablebody(window.userdb);
}
});
/**
* Show modal dialog for soundbank, messagebank, broadcastzone selection
* @param {boolean} editmode if true, edit mode, else add mode
* @param {number} index index of user to edit, default 0
*/
function modalshow(editmode = false, index=0) {
// event on click btnShowSoundbankModal
$('#btnShowSoundbankModal').off('click').on('click', function () {
$('#soundbankmodal').modal('show');
fill_citylist();
fill_airlinelist();
let airline = $('#modalairlinetags').val().trim();
let city = $('#modalcitytags').val().trim();
if (airline.length > 0) {
let airlinekeys = airline.split(";");
$('#airlinelist input[type=checkbox]').each(function () {
let tag = $(this).val();
if (airlinekeys.includes(tag)) {
$(this).prop('checked', true);
}
});
}
if (city.length > 0) {
let citykeys = city.split(";");
$('#citylist input[type=checkbox]').each(function () {
let tag = $(this).val();
if (citykeys.includes(tag)) {
$(this).prop('checked', true);
}
});
}
$('#soundbankmodal').off('click.soundbankselectionsave').on('click.soundbankselectionsave', '#soundbankselectionsave', function () {
let selected_airlinetags = [];
$('#airlinelist input[type=checkbox]:checked').each(function () {
selected_airlinetags.push($(this).val());
});
let selected_citytags = [];
$('#citylist input[type=checkbox]:checked').each(function () {
selected_citytags.push($(this).val());
});
//console.log("Selected airline tags: ", selected_airlinetags);
//console.log("Selected city tags: ", selected_citytags);
if (selected_airlinetags.length == 0 || selected_citytags.length == 0) {
alert("Please select at least one airline tag and one city tag.");
return;
}
let airlinevalue = selected_airlinetags.join(";");
let cityvalue = selected_citytags.join(";");
$('#modalairlinetags').val(airlinevalue);
$('#modalcitytags').val(cityvalue);
$('#soundbankmodal').modal('hide');
});
$('#soundbankmodal').off('click.soundbankselectionclose').on('click.soundbankselectionclose', '#soundbankselectionclose', function () {
$('#soundbankmodal').modal('hide');
});
});
// event on click btnShowMessagebankModal
$('#btnShowMessagebankModal').off('click').on('click', function () {
$('#messagebankmodal').modal('show');
fill_messagebanklist();
let messagebank = $('#modalmessagebank').val().trim();
if (messagebank.length > 0) {
let messagebankkeys = messagebank.split(";");
$('#messagebanklist input[type=checkbox]').each(function () {
let id = $(this).val();
if (messagebankkeys.includes(id)) {
$(this).prop('checked', true);
}
});
}
$('#messagebankmodal').off('click.messagebankselectionsave').on('click.messagebankselectionsave', '#messagebankselectionsave', function () {
let selected_messagebankids = [];
$('#messagebanklist input[type=checkbox]:checked').each(function () {
selected_messagebankids.push($(this).val());
});
//console.log("Selected message bank IDs: ", selected_messagebankids);
if (selected_messagebankids.length == 0) {
alert("Please select at least one message bank ID.");
return;
}
let messagebankvalue = selected_messagebankids.join(";");
$('#modalmessagebank').val(messagebankvalue);
$('#messagebankmodal').modal('hide');
});
$('#messagebankmodal').off('click.messagebankselectionclose').on('click.messagebankselectionclose', '#messagebankselectionclose', function () {
$('#messagebankmodal').modal('hide');
});
});
// event on click btnShowBroaadcastZoneModal
$('#btnShowBroaadcastZoneModal').off('click').on('click', function () {
$('#broadcastzonemodal').modal('show');
fill_broadcastzonelist();
let broadcastzones = $('#modalbroadcastzones').val().trim();
if (broadcastzones.length > 0) {
let broadcastzonesvalues = broadcastzones.split(";");
$('#broadcastzonelist input[type=checkbox]').each(function () {
let desc = $(this).val();
if (broadcastzonesvalues.includes(desc)) {
$(this).prop('checked', true);
}
});
}
$('#broadcastzonemodal').off('click.broadcastzoneselectionsave').on('click.broadcastzoneselectionsave', '#broadcastzoneselectionsave', function () {
let selected_broadcastzones = [];
$('#broadcastzonelist input[type=checkbox]:checked').each(function () {
selected_broadcastzones.push($(this).val());
});
//console.log("Selected broadcast zones: ", selected_broadcastzones);
if (selected_broadcastzones.length == 0) {
alert("Please select at least one broadcast zone.");
return;
}
let broadcastzonesvalue = selected_broadcastzones.join(";");
$('#modalbroadcastzones').val(broadcastzonesvalue);
$('#broadcastzonemodal').modal('hide');
});
$('#broadcastzonemodal').off('click.broadcastzoneselectionclose').on('click.broadcastzoneselectionclose', '#broadcastzoneselectionclose', function () {
$('#broadcastzonemodal').modal('hide');
});
});
// event on Click save button
$('#addmodal').off('click.usermanagementsave').on('click.usermanagementsave', '#usermanagementsave', function () {
let username = $('#modalusername').val().trim();
let password = $('#modalpassword').val();
let verifypassword = $('#modalverifypassword').val();
let location = $('#modallocation').val().trim();
let airline_tags = $('#modalairlinetags').val().trim();
let city_tags = $('#modalcitytags').val().trim();
let messagebank_ann_id = $('#modalmessagebank').val().trim();
let broadcastzones = $('#modalbroadcastzones').val().trim();
if (username.length === 0) {
alert("Username cannot be empty");
return;
}
if (password.length === 0 || verifypassword.length === 0) {
alert("Password cannot be empty");
return;
}
if (password !== verifypassword) {
alert("Password and Verify Password do not match");
return;
}
if (airline_tags.length === 0) {
alert("Airline tags cannot be empty");
return;
}
if (city_tags.length === 0) {
alert("City tags cannot be empty");
return;
}
if (messagebank_ann_id.length === 0) {
alert("Message bank ANN_ID cannot be empty");
return;
}
if (broadcastzones.length === 0) {
alert("Broadcast zones cannot be empty");
return;
}
/**
* @type {UserDB}
*/
let ll = {
index: index,
username: username,
password: password,
location: location,
airline_tags: airline_tags,
city_tags: city_tags,
messagebank_ann_id: messagebank_ann_id,
broadcastzones: broadcastzones
}
if (editmode) {
fetchAPI(APIURL + "UpdateByIndex/" + index, "PATCH", {}, ll, (okdata) => {
alert("Success update User : " + okdata.message);
reloaduserDB();
}, (errdata) => {
alert("Error update User : " + errdata.message);
});
} else {
fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => {
alert("Success add User : " + okdata.message);
reloaduserDB();
}, (errdata) => {
alert("Error add User : " + errdata.message);
});
}
$('#addmodal').modal('hide');
});
// event on Click close button
$('#addmodal').off('click.usermanagementclose').on('click.usermanagementclose', '#usermanagementclose', function () {
$('#addmodal').modal('hide');
});
}
$('#btnClear').off('click').on('click', function () {
DoClear(APIURL, "UserManagement", (okdata) => {
reloaduserDB();
alert("Success clear user management : " + okdata.message);
}, (errdata) => {
alert("Error clear user management : " + errdata.message);
});
});
$('#btnAdd').off('click').on('click', () => {
$('#addmodal').modal('show');
clearAddModal();
modalshow(false,0);
});
$('#btnRemove').off('click').on('click', () => {
if (window.selecteduserrow) {
let cells = window.selecteduserrow.find('td');
/** @type {UserDB} */
let user = {
index: parseInt(cells.eq(0).text()),
username: cells.eq(1).text(),
password: cells.eq(2).text(),
airline_tags: cells.eq(3).text(),
city_tags: cells.eq(4).text(),
messagebank_ann_id: cells.eq(5).text(),
broadcastzones: cells.eq(6).text()
}
if (confirm(`Are you sure to delete user [${user.index}] Username=${user.username} ?`)) {
fetchAPI(APIURL + "DeleteByIndex/" + user.index, "DELETE", {}, null, (okdata) => {
reloaduserDB();
alert("Success delete user : " + okdata.message);
}, (errdata) => {
alert("Error delete user : " + errdata.message);
});
}
} else {
alert("No user selected");
}
});
$('#btnEdit').off('click').on('click', () => {
if (window.selecteduserrow) {
let cells = window.selecteduserrow.find('td');
let index = parseInt(cells.eq(0).text());
if (isNaN(index) || index <= 0) {
alert("Invalid user index");
return;
}
/** @type {UserDB} */
let user = window.userdb.find(u => u.index === index);
if (!user) {
alert("User not found");
return;
}
if (confirm(`Are you sure to edit user [${user.index}] Username=${user.username} ?`)) {
$('#addmodal').modal('show');
// fill modal with user data
$('#modalindex').val(user.index);
$('#modalusername').val(user.username);
$('#modalpassword').val(user.password);
$('#modalverifypassword').val(user.password);
$('#modallocation').val(user.location);
$('#modalairlinetags').val(user.airline_tags);
$('#modalcitytags').val(user.city_tags);
$('#modalmessagebank').val(user.messagebank_ann_id);
$('#modalbroadcastzones').val(user.broadcastzones);
modalshow(true, user.index);
}
} else {
alert("No user selected");
}
});
$('#btnExport').off('click').on('click', () => {
DoExport(APIURL, "user.xlsx", {});
});
$('#btnImport').off('click').on('click', () => {
DoImport(APIURL, (okdata) => {
reloaduserDB();
alert("Success import user : " + okdata.message);
}, (errdata) => {
alert("Error importing user from XLSX : " + errdata.message);
});
}); });
}); });

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -19,15 +20,15 @@
<div class="modal fade" role="dialog" tabindex="-1" id="broadcastzonemodal"> <div class="modal fade" role="dialog" tabindex="-1" id="broadcastzonemodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-header-modal">
<h4 class="modal-title">Add / Edit Broadcast Zones</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button> <h4 class="modal-title">Add / Edit Broadcast Zones</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="broadcastzoneindex" placeholder="index"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="broadcastzoneindex" placeholder="index" readonly=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
@@ -79,7 +80,7 @@
<div class="form-check"><input class="form-check-input" type="checkbox" id="R08"><label class="form-check-label" for="formCheck-8">08</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="R08"><label class="form-check-label" for="formCheck-8">08</label></div>
</div> </div>
</div> </div>
<div class="col-3"> <div class="col-3 invisible">
<div class="row"> <div class="row">
<div class="form-check"><input class="form-check-input" type="checkbox" id="R09"><label class="form-check-label" for="formCheck-25">09</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="R09"><label class="form-check-label" for="formCheck-25">09</label></div>
</div> </div>
@@ -105,7 +106,7 @@
<div class="form-check"><input class="form-check-input" type="checkbox" id="R16"><label class="form-check-label" for="formCheck-32">16</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="R16"><label class="form-check-label" for="formCheck-32">16</label></div>
</div> </div>
</div> </div>
<div class="col-3"> <div class="col-3 invisible">
<div class="row"> <div class="row">
<div class="form-check"><input class="form-check-input" type="checkbox" id="R17"><label class="form-check-label" for="formCheck-17">17</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="R17"><label class="form-check-label" for="formCheck-17">17</label></div>
</div> </div>
@@ -131,7 +132,7 @@
<div class="form-check"><input class="form-check-input" type="checkbox" id="R24"><label class="form-check-label" for="formCheck-24">24</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="R24"><label class="form-check-label" for="formCheck-24">24</label></div>
</div> </div>
</div> </div>
<div class="col-3"> <div class="col-3 invisible">
<div class="row"> <div class="row">
<div class="form-check"><input class="form-check-input" type="checkbox" id="R25"><label class="form-check-label" for="formCheck-9">25</label></div> <div class="form-check"><input class="form-check-input" type="checkbox" id="R25"><label class="form-check-label" for="formCheck-9">25</label></div>
</div> </div>
@@ -194,9 +195,9 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">No</th> <th class="class05">No</th>
<th class="col-sm-3">Description</th> <th class="class75">Description</th>
<th class="col">IP Address</th> <th class="class20">IP Address</th>
</tr> </tr>
</thead> </thead>
<tbody id="soundchanneltablebody"></tbody> <tbody id="soundchanneltablebody"></tbody>
@@ -235,11 +236,11 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">No</th> <th class="class05">No</th>
<th class="col-sm-2">Description</th> <th class="class40">Description</th>
<th class="col-sm-2">SoundChannel</th> <th class="class20">SoundChannel</th>
<th class="col-sm-2">ID</th> <th class="class05">ID</th>
<th class="col">BP</th> <th class="class30">BP</th>
</tr> </tr>
</thead> </thead>
<tbody id="broadcastzonetablebody"></tbody> <tbody id="broadcastzonetablebody"></tbody>
@@ -254,10 +255,10 @@
<div class="modal fade" role="dialog" tabindex="-1" id="soundchannelmodal"> <div class="modal fade" role="dialog" tabindex="-1" id="soundchannelmodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-header-modal">
<h4 class="modal-title">Edit Sound Channel</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button> <h4 class="modal-title">Edit Sound Channel</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
@@ -268,13 +269,13 @@
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Description</p> <p class="text-add">Description</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchanneldescription" placeholder="Description"></div> <div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchanneldescription" placeholder="Description" readonly=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">IP Address</p> <p class="text-add">IP Address</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchannelip" placeholder="IP Address"></div> <div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchannelip" placeholder="IP Address" required="" inputmode="numeric" pattern="^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$"></div>
</div> </div>
</div> </div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundchannelclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundchannelsave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundchannelclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundchannelsave" type="button">Save</button></div>
@@ -282,6 +283,7 @@
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/soundchannel.js"></script> <script src="assets/js/soundchannel.js"></script>
<script src="assets/js/broadcastzones.js"></script> <script src="assets/js/broadcastzones.js"></script>
</body> </body>

View File

@@ -6,6 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS NewGeneration 17092025</title> <title>AAS NewGeneration 17092025</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/select2.min.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -97,9 +99,10 @@
</div> </div>
<div class="container w-100 pad-container" id="content"></div> <div class="container w-100 pad-container" id="content"></div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/jquery-3.7.1.min.js"></script> <script src="assets/js/jquery-3.7.1.min.js"></script>
<script src="assets/js/select2.js"></script>
<script src="assets/js/script.js"></script> <script src="assets/js/script.js"></script>
<script src="assets/js/select2.min.js"></script>
</body> </body>
</html> </html>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -41,9 +42,9 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">No</th> <th class="class05">No</th>
<th class="col-sm-2">TAG</th> <th class="class20">TAG</th>
<th class="col">Languages</th> <th class="class75">Languages</th>
</tr> </tr>
</thead> </thead>
<tbody id="languagebanktablebody"></tbody> <tbody id="languagebanktablebody"></tbody>
@@ -53,10 +54,10 @@
<div class="modal fade" role="dialog" tabindex="-1" id="languagemodal"> <div class="modal fade" role="dialog" tabindex="-1" id="languagemodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-header-modal">
<h4 class="modal-title">Add / Edit Language</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button> <h4 class="modal-title">Add / Edit Language</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
@@ -88,6 +89,7 @@
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/languagelink.js"></script> <script src="assets/js/languagelink.js"></script>
</body> </body>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -38,11 +39,11 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-1 col-md-1">No</th> <th class="class10">No</th>
<th class="col-2 col-md-2 col-lg-2">Date</th> <th class="class15">Date</th>
<th class="col-2 col-md-2 col-lg-2">Time</th> <th class="class15">Time</th>
<th class="col-2 col-md-2 col-lg-2">Machine</th> <th class="class15">Machine</th>
<th>Description</th> <th class="class45">Description</th>
</tr> </tr>
</thead> </thead>
<tbody id="logtablebody"></tbody> <tbody id="logtablebody"></tbody>
@@ -50,6 +51,7 @@
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/log.js"></script> <script src="assets/js/log.js"></script>
</body> </body>

View File

@@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS NewGeneration 17092025</title> <title>AAS NewGeneration 17092025</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -13,22 +14,21 @@
<body> <body>
<section class="position-relative py-4 py-xl-5"> <section class="position-relative py-4 py-xl-5">
<div class="container"> <div class="container">
<div class="row mb-5"> <div class="row mb-4"></div>
<div class="col-md-8 col-xl-6 text-center mx-auto"> <div class="row d-flex justify-content-center mb-7">
<h2>Sign In</h2>
</div>
</div>
<div class="row d-flex justify-content-center">
<div class="col-md-6 col-xl-4"> <div class="col-md-6 col-xl-4">
<div class="card mb-5 card-login"> <div class="card mb-5 card-login">
<div class="card-body d-flex flex-column align-items-center"> <div class="card-body d-flex flex-column align-items-center">
<div class="bs-icon-xl bs-icon-circle bs-icon-primary my-4 bs-icon"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-person bg-icon-login"> <div class="bs-icon-xl bs-icon-circle bs-icon-primary my-4 bs-icon bg-icon-login"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-person bg-icon-login">
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664z"></path> <path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664z"></path>
</svg></div> </svg></div>
<form class="text-center" method="post"> <h2 class="mb-3 h-login">Login</h2>
<div class="mb-3"><input class="form-control input-login" type="text" name="username" placeholder="Username"></div> <form class="text-center py-2 bottom-signin" method="post">
<div class="mb-3"><input class="form-control input-login" type="password" name="password" placeholder="Password"></div> <p class="p-login">Username</p>
<div class="mb-3"><button class="btn btn-primary d-block w-100 btn-login" type="submit">Sign In</button></div> <div class="mb-3"><input class="form-control input-login" type="text" name="username" placeholder="Enter your username"></div>
<p class="p-login">Password</p>
<div class="mb-3"><input class="form-control input-login" type="password" name="password" placeholder="Enter your password"></div>
<div class="mb-3 py-2"><button class="btn btn-primary d-block w-100 btn-login" type="submit">Login</button></div>
</form> </form>
</div> </div>
</div> </div>
@@ -37,6 +37,7 @@
</div> </div>
</section> </section>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
</body> </body>
</html> </html>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -41,13 +42,13 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">No</th> <th class="class05">No</th>
<th class="col-sm-2">Description</th> <th class="class15">Description</th>
<th class="col-sm-1">Language</th> <th class="class10">Language</th>
<th class="col-sm-1">ANN ID</th> <th class="class10">ANN ID</th>
<th class="col-sm-1">Type</th> <th class="class10">Type</th>
<th class="col-sm-3">Message Details</th> <th class="class35">Message Details</th>
<th class="col-sm-3">Message Tags</th> <th class="class15">Message Tags</th>
</tr> </tr>
</thead> </thead>
<tbody id="messagebanktablebody"></tbody> <tbody id="messagebanktablebody"></tbody>
@@ -55,29 +56,29 @@
</div> </div>
</div> </div>
<div class="modal fade" role="dialog" tabindex="-1" id="messagebankmodal"> <div class="modal fade" role="dialog" tabindex="-1" id="messagebankmodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog modal-xl" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-header-modal">
<h4 class="modal-title">Add / Edit Message</h4> <h4 class="modal-title">Add / Edit Message</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
<p class="text-add">Index</p> <p class="text-add">Index</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="messageindex" placeholder="Index" readonly=""></div> <div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><input class="w-25 input-add form-control" type="text" id="messageindex" placeholder="Index" readonly=""></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
<p class="text-add">Description</p> <p class="text-add">Description</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="messagedescription" class="input-add form-control" placeholder="Description"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><input type="text" id="messagedescription" class="input-add form-control" placeholder="Description"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
<p class="text-add">Language</p> <p class="text-add">Language</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="messagelanguage" class="input-add form-control"> <div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><select id="messagelanguage" class="input-add form-control">
<option value="INDONESIA">Indonesia</option> <option value="INDONESIA">Indonesia</option>
<option value="LOCAL">Local</option> <option value="LOCAL">Local</option>
<option value="ENGLISH">English</option> <option value="ENGLISH">English</option>
@@ -87,40 +88,40 @@
</select></div> </select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
<p class="text-add">ANN ID</p> <p class="text-add">ANN ID</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="number" id="messageannid" class="input-add form-control" min="1" max="100" value="1" step="1" placeholder="Announcement ID"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><input type="number" id="messageannid" class="input-add form-control" min="1" max="100" value="1" step="1" placeholder="Announcement ID"></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
<p class="text-add">Voice Type</p> <p class="text-add">Voice Type</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="messagevoicetype" class="input-add form-control"> <div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><select id="messagevoicetype" class="input-add form-control">
<option value="VOICE_1">Voice 1</option> <option value="VOICE_1">Voice 1</option>
<option value="VOICE_2">Voice 2</option> <option value="VOICE_2">Voice 2</option>
<option value="VOICE_3">Voice 3</option> <option value="VOICE_3">Voice 3</option>
</select></div> </select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col bg-light">
<ul class="list-unstyled w-100 h-100" id="messageavailablevariables"></ul> <ul class="list-unstyled w-100 h-100" id="messageavailablevariables"></ul>
</div> </div>
<div class="col-2"> <div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2">
<div class="row"><button class="btn btn-danger" id="btnclearlist" type="button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;"> <div class="row pad-row-btn"><button class="btn btn-round-basic color-remove" data-bs-toggle="tooltip" data-bss-tooltip="" id="btnclearlist" type="button" title="Clear List"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. --> <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path> <path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>
</svg></button></div> </svg></button></div>
<div class="row"><button class="btn btn-warning" id="btnremovefromlist" type="button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;"> <div class="row pad-row-btn"><button class="btn btn-round-basic color-edit" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnremovefromlist" type="button" title="Remove"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. --> <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M48 256a208 208 0 1 1 416 0A208 208 0 1 1 48 256zm464 0A256 256 0 1 0 0 256a256 256 0 1 0 512 0zM217.4 376.9c4.2 4.5 10.1 7.1 16.3 7.1c12.3 0 22.3-10 22.3-22.3V304h96c17.7 0 32-14.3 32-32V240c0-17.7-14.3-32-32-32H256V150.3c0-12.3-10-22.3-22.3-22.3c-6.2 0-12.1 2.6-16.3 7.1L117.5 242.2c-3.5 3.8-5.5 8.7-5.5 13.8s2 10.1 5.5 13.8l99.9 107.1z"></path> <path d="M48 256a208 208 0 1 1 416 0A208 208 0 1 1 48 256zm464 0A256 256 0 1 0 0 256a256 256 0 1 0 512 0zM217.4 376.9c4.2 4.5 10.1 7.1 16.3 7.1c12.3 0 22.3-10 22.3-22.3V304h96c17.7 0 32-14.3 32-32V240c0-17.7-14.3-32-32-32H256V150.3c0-12.3-10-22.3-22.3-22.3c-6.2 0-12.1 2.6-16.3 7.1L117.5 242.2c-3.5 3.8-5.5 8.7-5.5 13.8s2 10.1 5.5 13.8l99.9 107.1z"></path>
</svg></button></div> </svg></button></div>
<div class="row"><button class="btn btn-success" id="btnaddtolist" type="button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;"> <div class="row pad-row-btn"><button class="btn btn-round-basic color-import" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnaddtolist" type="button" title="Add"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. --> <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
<path d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM294.6 135.1c-4.2-4.5-10.1-7.1-16.3-7.1C266 128 256 138 256 150.3V208H160c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h96v57.7c0 12.3 10 22.3 22.3 22.3c6.2 0 12.1-2.6 16.3-7.1l99.9-107.1c3.5-3.8 5.5-8.7 5.5-13.8s-2-10.1-5.5-13.8L294.6 135.1z"></path> <path d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM294.6 135.1c-4.2-4.5-10.1-7.1-16.3-7.1C266 128 256 138 256 150.3V208H160c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h96v57.7c0 12.3 10 22.3 22.3 22.3c6.2 0 12.1-2.6 16.3-7.1l99.9-107.1c3.5-3.8 5.5-8.7 5.5-13.8s-2-10.1-5.5-13.8L294.6 135.1z"></path>
</svg></button></div> </svg></button></div>
</div> </div>
<div class="col"> <div class="col bg-light">
<ul class="list-unstyled w-100 h-100" id="messageselectedvariables"></ul> <ul class="list-unstyled w-100 h-100" id="messageselectedvariables"></ul>
</div> </div>
</div> </div>
@@ -130,6 +131,7 @@
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/messagebank.js"></script> <script src="assets/js/messagebank.js"></script>
</body> </body>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -101,7 +102,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-4"> <div class="card card-channel" id="streamercard-4">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle05">Channel 05</h4> <h4 class="card-title" id="streamertitle05">Channel 05</h4>
<div class="row"> <div class="row">
@@ -120,7 +121,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-5"> <div class="card card-channel" id="streamercard-5">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle06">Channel 06</h4> <h4 class="card-title" id="streamertitle06">Channel 06</h4>
<div class="row"> <div class="row">
@@ -139,7 +140,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-6"> <div class="card card-channel" id="streamercard-6">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle07">Channel 07</h4> <h4 class="card-title" id="streamertitle07">Channel 07</h4>
<div class="row"> <div class="row">
@@ -158,7 +159,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 pad-card">
<div class="card" id="streamercard-7"> <div class="card card-channel" id="streamercard-7">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle08">Channel 08</h4> <h4 class="card-title" id="streamertitle08">Channel 08</h4>
<div class="row"> <div class="row">
@@ -179,7 +180,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-8"> <div class="card card-channel" id="streamercard-8">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle09">Channel 09</h4> <h4 class="card-title" id="streamertitle09">Channel 09</h4>
<div class="row"> <div class="row">
@@ -198,7 +199,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-9"> <div class="card card-channel" id="streamercard-9">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle10">Channel 10</h4> <h4 class="card-title" id="streamertitle10">Channel 10</h4>
<div class="row"> <div class="row">
@@ -217,7 +218,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-10"> <div class="card card-channel" id="streamercard-10">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle11">Channel 11</h4> <h4 class="card-title" id="streamertitle11">Channel 11</h4>
<div class="row"> <div class="row">
@@ -236,7 +237,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-11"> <div class="card card-channel" id="streamercard-11">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle12">Channel 12</h4> <h4 class="card-title" id="streamertitle12">Channel 12</h4>
<div class="row"> <div class="row">
@@ -257,7 +258,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-12"> <div class="card card-channel" id="streamercard-12">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle13">Channel 13</h4> <h4 class="card-title" id="streamertitle13">Channel 13</h4>
<div class="row"> <div class="row">
@@ -276,7 +277,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-13"> <div class="card card-channel" id="streamercard-13">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle14">Channel 14</h4> <h4 class="card-title" id="streamertitle14">Channel 14</h4>
<div class="row"> <div class="row">
@@ -295,7 +296,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-14"> <div class="card card-channel" id="streamercard-14">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle15">Channel 15</h4> <h4 class="card-title" id="streamertitle15">Channel 15</h4>
<div class="row"> <div class="row">
@@ -314,7 +315,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-15"> <div class="card card-channel" id="streamercard-15">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle16">Channel 16</h4> <h4 class="card-title" id="streamertitle16">Channel 16</h4>
<div class="row"> <div class="row">
@@ -335,7 +336,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-16"> <div class="card card-channel" id="streamercard-16">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle17">Channel 17</h4> <h4 class="card-title" id="streamertitle17">Channel 17</h4>
<div class="row"> <div class="row">
@@ -354,7 +355,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-17"> <div class="card card-channel" id="streamercard-17">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle18">Channel 18</h4> <h4 class="card-title" id="streamertitle18">Channel 18</h4>
<div class="row"> <div class="row">
@@ -373,7 +374,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-18"> <div class="card card-channel" id="streamercard-18">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle19">Channel 19</h4> <h4 class="card-title" id="streamertitle19">Channel 19</h4>
<div class="row"> <div class="row">
@@ -392,7 +393,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-19"> <div class="card card-channel" id="streamercard-19">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle20">Channel 20</h4> <h4 class="card-title" id="streamertitle20">Channel 20</h4>
<div class="row"> <div class="row">
@@ -413,7 +414,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-20"> <div class="card card-channel" id="streamercard-20">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle21">Channel 21</h4> <h4 class="card-title" id="streamertitle21">Channel 21</h4>
<div class="row"> <div class="row">
@@ -432,7 +433,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-21"> <div class="card card-channel" id="streamercard-21">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle22">Channel 22</h4> <h4 class="card-title" id="streamertitle22">Channel 22</h4>
<div class="row"> <div class="row">
@@ -451,7 +452,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-22"> <div class="card card-channel" id="streamercard-22">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle23">Channel 23</h4> <h4 class="card-title" id="streamertitle23">Channel 23</h4>
<div class="row"> <div class="row">
@@ -470,7 +471,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-23"> <div class="card card-channel" id="streamercard-23">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle24">Channel 24</h4> <h4 class="card-title" id="streamertitle24">Channel 24</h4>
<div class="row"> <div class="row">
@@ -491,7 +492,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-24"> <div class="card card-channel" id="streamercard-24">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle25">Channel 25</h4> <h4 class="card-title" id="streamertitle25">Channel 25</h4>
<div class="row"> <div class="row">
@@ -510,7 +511,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-25"> <div class="card card-channel" id="streamercard-25">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle26">Channel 26</h4> <h4 class="card-title" id="streamertitle26">Channel 26</h4>
<div class="row"> <div class="row">
@@ -529,7 +530,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-26"> <div class="card card-channel" id="streamercard-26">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle27">Channel 27</h4> <h4 class="card-title" id="streamertitle27">Channel 27</h4>
<div class="row"> <div class="row">
@@ -548,7 +549,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-27"> <div class="card card-channel" id="streamercard-27">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle28">Channel 28</h4> <h4 class="card-title" id="streamertitle28">Channel 28</h4>
<div class="row"> <div class="row">
@@ -569,7 +570,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-28"> <div class="card card-channel" id="streamercard-28">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle29">Channel 29</h4> <h4 class="card-title" id="streamertitle29">Channel 29</h4>
<div class="row"> <div class="row">
@@ -588,7 +589,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-29"> <div class="card card-channel" id="streamercard-29">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle30">Channel 30</h4> <h4 class="card-title" id="streamertitle30">Channel 30</h4>
<div class="row"> <div class="row">
@@ -607,7 +608,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-30"> <div class="card card-channel" id="streamercard-30">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle31">Channel 31</h4> <h4 class="card-title" id="streamertitle31">Channel 31</h4>
<div class="row"> <div class="row">
@@ -626,7 +627,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-31"> <div class="card card-channel" id="streamercard-31">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle32">Channel 32</h4> <h4 class="card-title" id="streamertitle32">Channel 32</h4>
<div class="row"> <div class="row">
@@ -647,7 +648,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-32"> <div class="card card-channel" id="streamercard-32">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle33">Channel 33</h4> <h4 class="card-title" id="streamertitle33">Channel 33</h4>
<div class="row"> <div class="row">
@@ -666,7 +667,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-33"> <div class="card card-channel" id="streamercard-33">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle34">Channel 34</h4> <h4 class="card-title" id="streamertitle34">Channel 34</h4>
<div class="row"> <div class="row">
@@ -685,7 +686,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-34"> <div class="card card-channel" id="streamercard-34">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle35">Channel 35</h4> <h4 class="card-title" id="streamertitle35">Channel 35</h4>
<div class="row"> <div class="row">
@@ -704,7 +705,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-35"> <div class="card card-channel" id="streamercard-35">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle36">Channel 36</h4> <h4 class="card-title" id="streamertitle36">Channel 36</h4>
<div class="row"> <div class="row">
@@ -725,7 +726,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-36"> <div class="card card-channel" id="streamercard-36">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle37">Channel 37</h4> <h4 class="card-title" id="streamertitle37">Channel 37</h4>
<div class="row"> <div class="row">
@@ -744,7 +745,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-37"> <div class="card card-channel" id="streamercard-37">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle38">Channel 38</h4> <h4 class="card-title" id="streamertitle38">Channel 38</h4>
<div class="row"> <div class="row">
@@ -763,7 +764,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-38"> <div class="card card-channel" id="streamercard-38">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle39">Channel 39</h4> <h4 class="card-title" id="streamertitle39">Channel 39</h4>
<div class="row"> <div class="row">
@@ -782,7 +783,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-39"> <div class="card card-channel" id="streamercard-39">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle40">Channel 40</h4> <h4 class="card-title" id="streamertitle40">Channel 40</h4>
<div class="row"> <div class="row">
@@ -803,7 +804,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-40"> <div class="card card-channel" id="streamercard-40">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle41">Channel 41</h4> <h4 class="card-title" id="streamertitle41">Channel 41</h4>
<div class="row"> <div class="row">
@@ -822,7 +823,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-41"> <div class="card card-channel" id="streamercard-41">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle42">Channel 42</h4> <h4 class="card-title" id="streamertitle42">Channel 42</h4>
<div class="row"> <div class="row">
@@ -841,7 +842,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-42"> <div class="card card-channel" id="streamercard-42">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle43">Channel 43</h4> <h4 class="card-title" id="streamertitle43">Channel 43</h4>
<div class="row"> <div class="row">
@@ -860,7 +861,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-43"> <div class="card card-channel" id="streamercard-43">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle44">Channel 44</h4> <h4 class="card-title" id="streamertitle44">Channel 44</h4>
<div class="row"> <div class="row">
@@ -881,7 +882,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-44"> <div class="card card-channel" id="streamercard-44">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle-44">Channel 45</h4> <h4 class="card-title" id="streamertitle-44">Channel 45</h4>
<div class="row"> <div class="row">
@@ -900,7 +901,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-45"> <div class="card card-channel" id="streamercard-45">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle46">Channel 46</h4> <h4 class="card-title" id="streamertitle46">Channel 46</h4>
<div class="row"> <div class="row">
@@ -919,7 +920,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-46"> <div class="card card-channel" id="streamercard-46">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle47">Channel 47</h4> <h4 class="card-title" id="streamertitle47">Channel 47</h4>
<div class="row"> <div class="row">
@@ -938,7 +939,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-47"> <div class="card card-channel" id="streamercard-47">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle48">Channel 48</h4> <h4 class="card-title" id="streamertitle48">Channel 48</h4>
<div class="row"> <div class="row">
@@ -959,7 +960,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-48"> <div class="card card-channel" id="streamercard-48">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle49">Channel 49</h4> <h4 class="card-title" id="streamertitle49">Channel 49</h4>
<div class="row"> <div class="row">
@@ -978,7 +979,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-49"> <div class="card card-channel" id="streamercard-49">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle50">Channel 50</h4> <h4 class="card-title" id="streamertitle50">Channel 50</h4>
<div class="row"> <div class="row">
@@ -997,7 +998,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-50"> <div class="card card-channel" id="streamercard-50">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle51">Channel 51</h4> <h4 class="card-title" id="streamertitle51">Channel 51</h4>
<div class="row"> <div class="row">
@@ -1016,7 +1017,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-51"> <div class="card card-channel" id="streamercard-51">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle52">Channel 52</h4> <h4 class="card-title" id="streamertitle52">Channel 52</h4>
<div class="row"> <div class="row">
@@ -1037,7 +1038,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-52"> <div class="card card-channel" id="streamercard-52">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle53">Channel 53</h4> <h4 class="card-title" id="streamertitle53">Channel 53</h4>
<div class="row"> <div class="row">
@@ -1056,7 +1057,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-53"> <div class="card card-channel" id="streamercard-53">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle54">Channel 54</h4> <h4 class="card-title" id="streamertitle54">Channel 54</h4>
<div class="row"> <div class="row">
@@ -1075,7 +1076,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-54"> <div class="card card-channel" id="streamercard-54">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle55">Channel 55</h4> <h4 class="card-title" id="streamertitle55">Channel 55</h4>
<div class="row"> <div class="row">
@@ -1094,7 +1095,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-55"> <div class="card card-channel" id="streamercard-55">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle56">Channel 56</h4> <h4 class="card-title" id="streamertitle56">Channel 56</h4>
<div class="row"> <div class="row">
@@ -1115,7 +1116,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-56"> <div class="card card-channel" id="streamercard-56">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle57">Channel 57</h4> <h4 class="card-title" id="streamertitle57">Channel 57</h4>
<div class="row"> <div class="row">
@@ -1134,7 +1135,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-57"> <div class="card card-channel" id="streamercard-57">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle58">Channel 58</h4> <h4 class="card-title" id="streamertitle58">Channel 58</h4>
<div class="row"> <div class="row">
@@ -1153,7 +1154,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-58"> <div class="card card-channel" id="streamercard-58">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle59">Channel 59</h4> <h4 class="card-title" id="streamertitle59">Channel 59</h4>
<div class="row"> <div class="row">
@@ -1172,7 +1173,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-59"> <div class="card card-channel" id="streamercard-59">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle60">Channel 60</h4> <h4 class="card-title" id="streamertitle60">Channel 60</h4>
<div class="row"> <div class="row">
@@ -1193,7 +1194,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-60"> <div class="card card-channel" id="streamercard-60">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle61">Channel 61</h4> <h4 class="card-title" id="streamertitle61">Channel 61</h4>
<div class="row"> <div class="row">
@@ -1212,7 +1213,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-61"> <div class="card card-channel" id="streamercard-61">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle62">Channel 62</h4> <h4 class="card-title" id="streamertitle62">Channel 62</h4>
<div class="row"> <div class="row">
@@ -1231,7 +1232,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-62"> <div class="card card-channel" id="streamercard-62">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle63">Channel 63</h4> <h4 class="card-title" id="streamertitle63">Channel 63</h4>
<div class="row"> <div class="row">
@@ -1250,7 +1251,7 @@
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card"> <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3 col-xxl-3 pad-card">
<div class="card" id="streamercard-63"> <div class="card card-channel" id="streamercard-63">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" id="streamertitle64">Channel 64</h4> <h4 class="card-title" id="streamertitle64">Channel 64</h4>
<div class="row"> <div class="row">
@@ -1262,7 +1263,7 @@
</div> </div>
</div> </div>
<p class="card-text" id="streamerstatus64">Status : Idle</p> <p class="card-text" id="streamerstatus64">Status : Idle</p>
<div class="progress" id="streamervu-63"> <div class="progress" id="streamervu64">
<div class="progress-bar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;">50%</div> <div class="progress-bar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;">50%</div>
</div> </div>
</div> </div>
@@ -1272,17 +1273,21 @@
</div> </div>
</div> </div>
</div> </div>
<div class="accordion-item pad-accordion"> <div class="accordion-item pad-accordion pad-2">
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed bg-heading2" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="false" aria-controls="accordion-1 .item-2">Paging Queue</button></h2> <h2 class="accordion-header" role="tab"><button class="accordion-button collapsed bg-heading2" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="false" aria-controls="accordion-1 .item-2">Paging Queue</button></h2>
<div class="accordion-collapse collapse item-2" role="tabpanel" data-bs-parent="#accordion-1"> <div class="accordion-collapse collapse item-2 pad-2" role="tabpanel" data-bs-parent="#accordion-1">
<div class="accordion-body"> <div class="accordion-body">
<div class="row"> <div class="row">
<div class="table-responsive"> <div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Index</th> <th class="class05">Index</th>
<th>Description</th> <th class="class10">Date Time</th>
<th class="class15">Source</th>
<th class="class10">Type</th>
<th class="class30">Message</th>
<th class="class30">Broadcast Zones</th>
</tr> </tr>
</thead> </thead>
<tbody id="pagingqueuetable"></tbody> <tbody id="pagingqueuetable"></tbody>
@@ -1291,6 +1296,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col"><button class="btn w-100 pad-button btn-round-basic color-add" id="clearpagingqueue" type="button">Clear Queue</button></div> <div class="col"><button class="btn w-100 pad-button btn-round-basic color-add" id="clearpagingqueue" type="button">Clear Queue</button></div>
<div class="col"><button class="btn w-100 pad-button btn-round-basic color-remove" id="removepagingqueue" type="button">Remove Queue</button></div>
</div> </div>
</div> </div>
</div> </div>
@@ -1304,8 +1310,12 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Index</th> <th class="class05">Index</th>
<th>Description</th> <th class="class10">Date Time</th>
<th class="class15">Source</th>
<th class="class10">Type</th>
<th class="class30">Message</th>
<th class="class30">Broadcast Zones</th>
</tr> </tr>
</thead> </thead>
<tbody id="automaticqueuetable"></tbody> <tbody id="automaticqueuetable"></tbody>
@@ -1314,12 +1324,15 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col"><button class="btn w-100 pad-button btn-round-basic color-add" id="clearautomatictable" type="button">Clear Queue</button></div> <div class="col"><button class="btn w-100 pad-button btn-round-basic color-add" id="clearautomatictable" type="button">Clear Queue</button></div>
<div class="col"><button class="btn w-100 pad-button btn-round-basic color-remove" id="removeautomatictable" type="button">Remove Queue</button></div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/overview.js"></script>
</body> </body>
</html> </html>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -16,7 +17,70 @@
<h2 style="text-align: center;">Setting</h2> <h2 style="text-align: center;">Setting</h2>
</div> </div>
</div> </div>
<div class="row">
<div class="col">
<div class="card card-setting">
<div class="card-body">
<h4 class="card-title"><strong>Upload Soundbank</strong></h4>
<hr>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Path</label></div>
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input class="w-100 form-control" type="text" id="setting_path"></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="save_directory" type="button">Save Directory</button></div>
</div>
<div class="row">
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Category</label></div>
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_category" class="input-add form-select"></select></div>
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Language</label></div>
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_language" class="input-add form-select"></select></div>
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Voice</label></div>
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_voice" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<div class="bg-white w-100" id="drop-area" multiple=""><input type="file" id="file-input"><label class="form-label d">Drop files here or click to select</label></div>
</div>
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
<div class="row"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row py-5">
<div class="col">
<div class="card card-setting">
<div class="card-body pad-accordion">
<h4 class="card-title"><strong>FIS CODE</strong></h4>
<hr>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GOP</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GOP" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GBP</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GBP" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GFC</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GFC" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">FLD</label></div>
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_FLD" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label"></label></div>
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" type="button">Save</button></div>
</div>
</div>
</div>
</div>
</div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/dragdrop.js"></script>
</body> </body>
</html> </html>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -41,13 +42,13 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">No</th> <th class="class05">No</th>
<th>Description</th> <th class="class20">Description</th>
<th class="col-sm-1">TAG</th> <th class="class10">TAG</th>
<th class="col-sm-1">Category</th> <th class="class15">Category</th>
<th class="col-sm-1">Language</th> <th class="class15">Language</th>
<th class="col-sm-1">Type</th> <th class="class10">Type</th>
<th class="col-sm-3">Filename</th> <th class="class25">Filename</th>
</tr> </tr>
</thead> </thead>
<tbody id="soundbanktablebody"></tbody> <tbody id="soundbanktablebody"></tbody>
@@ -57,10 +58,10 @@
<div class="modal fade border-0" role="dialog" tabindex="-1" id="soundbankmodal"> <div class="modal fade border-0" role="dialog" tabindex="-1" id="soundbankmodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header show"> <div class="modal-header show bg-header-modal">
<h4 class="modal-title align-content-center">Add / Edit Soundbank</h4> <h4 class="modal-title align-content-center">Add / Edit Soundbank</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
@@ -97,11 +98,11 @@
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalvoicetype" class="input-add form-select"></select></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalvoicetype" class="input-add form-select"></select></div>
</div> </div>
<div class="row" style="height: 100px;"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Path</p> <p class="text-add">Path</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select class="w-100 js-example-basic-single input-add form-select" id="modalpath" name="modalpath"></select></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalpath" class="input-add form-select cw-100" name="modalpath" data-control="select2"></select></div>
</div> </div>
</div> </div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundbankclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundbanksave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundbankclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundbanksave" type="button">Save</button></div>
@@ -109,6 +110,7 @@
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/soundbank.js"></script> <script src="assets/js/soundbank.js"></script>
</body> </body>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -29,6 +30,7 @@
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
</body> </body>
</html> </html>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -41,14 +42,15 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">No</th> <th class="class05">No</th>
<th class="col-sm-2">Description</th> <th class="class15">Description</th>
<th class="col-sm-1">Day</th> <th class="class15">Day</th>
<th class="col-sm-1">Time</th> <th class="class10">Time</th>
<th class="col-sm-2">Sound Path</th> <th class="class15">Sound Path</th>
<th class="col-sm-1">Repeat</th> <th class="class10">Repeat</th>
<th class="col-sm-1">Enable</th> <th class="class05">Enable</th>
<th>Broadcast Zones</th> <th class="class15">Broadcast Zones</th>
<th class="class10">Language</th>
</tr> </tr>
</thead> </thead>
<tbody id="schedulebanktablebody"></tbody> <tbody id="schedulebanktablebody"></tbody>
@@ -58,10 +60,10 @@
<div class="modal fade" role="dialog" tabindex="-1" id="schedulemodal"> <div class="modal fade" role="dialog" tabindex="-1" id="schedulemodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header bg-header-modal">
<h4 class="modal-title">Add / Edit Schedule</h4> <h4 class="modal-title">Add / Edit Schedule</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
@@ -86,37 +88,13 @@
</div> </div>
<div class="row pad-day"> <div class="row pad-day">
<div class="col"> <div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulesunday" name="dayselection" value="Sunday"><label class="form-check-label" for="formCheck-8">Sunday</label></div> <div class="form-check"><input class="form-check-input" type="radio" id="scheduleweekly"><label class="form-check-label" for="formCheck-1">Weekly</label></div><select class="w-100 input-add form-select" id="weeklyselect">
</div> <optgroup label="This is a group">
</div> <option value="12" selected="">This is item 1</option>
<div class="row pad-day"> <option value="13">This is item 2</option>
<div class="col"> <option value="14">This is item 3</option>
<div class="form-check"><input class="form-check-input" type="radio" id="schedulemonday" name="dayselection" value="Monday"><label class="form-check-label" for="formCheck-7">Monday</label></div> </optgroup>
</div> </select>
</div>
<div class="row pad-day">
<div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="scheduletuesday" name="dayselection" value="Tuesday"><label class="form-check-label" for="formCheck-6">Tuesday</label></div>
</div>
</div>
<div class="row pad-day">
<div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulewednesday" name="dayselection" value="Wednesday"><label class="form-check-label" for="formCheck-5">Wednesday</label></div>
</div>
</div>
<div class="row pad-day">
<div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulethursday" name="dayselection" value="Thursday"><label class="form-check-label" for="formCheck-4">Thursday</label></div>
</div>
</div>
<div class="row pad-day">
<div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulefriday" name="dayselection" value="Friday"><label class="form-check-label" for="formCheck-3">Friday</label></div>
</div>
</div>
<div class="row pad-day">
<div class="col">
<div class="form-check"><input class="form-check-input" type="radio" id="schedulesaturday" name="dayselection" value="Saturday"><label class="form-check-label" for="formCheck-2">Saturday</label></div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@@ -133,12 +111,12 @@
</div> </div>
<div class="col"> <div class="col">
<div class="row w-100 h-100"> <div class="row w-100 h-100">
<div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3"><input type="number" id="schedulehour" class="input-add form-control class100" value="0" min="0" max="23" step="1"></div> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input type="number" id="schedulehour" class="input-add form-control class100" value="0" min="0" max="23" step="1"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-3 col-xl-3"> <div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<p class="pad-time">(H)</p> <p class="pad-time">(H)</p>
</div> </div>
<div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3"><input class="w-100 input-add form-control" type="number" id="scheduleminute" value="0" min="0" max="59" step="1"></div> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input class="w-100 input-add form-control" type="number" id="scheduleminute" value="0" min="0" max="59" step="1"></div>
<div class="col-2 col-sm-2 col-md-2 col-lg-3 col-xl-3"> <div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<p class="pad-time">(M)</p> <p class="pad-time">(M)</p>
</div> </div>
</div> </div>
@@ -146,9 +124,25 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Sound Path</p> <p class="text-add">Message</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="schedulemessage" class="input-add form-select"></select></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Language</p>
</div>
<div class="col">
<div class="row pad-day">
<div class="col"><select class="w-100 input-add form-select" id="languageselect">
<optgroup label="This is a group">
<option value="12" selected="">This is item 1</option>
<option value="13">This is item 2</option>
<option value="14">This is item 3</option>
</optgroup>
</select></div>
</div>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="schedulesoundpath" class="input-add form-control"></select></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
@@ -166,7 +160,13 @@
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Broadcast Zones</p> <p class="text-add">Broadcast Zones</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8 border" id="schedulezones"></div> <div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-8 border"><select class="w-100 input-add form-select" id="schedulezones">
<optgroup label="This is a group">
<option value="12" selected="">This is item 1</option>
<option value="13">This is item 2</option>
<option value="14">This is item 3</option>
</optgroup>
</select></div>
</div> </div>
</div> </div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="scheduleclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic class25 color-add" id="schedulesave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="scheduleclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic class25 color-add" id="schedulesave" type="button">Save</button></div>
@@ -174,6 +174,7 @@
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/schedulebank.js"></script> <script src="assets/js/schedulebank.js"></script>
</body> </body>

View File

@@ -4,8 +4,9 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>AAS_NewGen_30Sept25</title> <title>AAS_NewGen_17OKT25</title>
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/bss-overrides.css">
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css"> <link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
<link rel="stylesheet" href="assets/css/styles.css"> <link rel="stylesheet" href="assets/css/styles.css">
</head> </head>
@@ -41,12 +42,13 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th class="col-sm-1">No</th> <th class="class05">No</th>
<th class="col-sm-1">Username</th> <th class="class10">Username</th>
<th class="col-sm-1">Location</th> <th class="class15">Location</th>
<th class="col">Soundbank</th> <th class="class15">Airline</th>
<th class="col-sm-2">Messagebank</th> <th class="class15">City</th>
<th class="col-sm-2">Broadcast Zones</th> <th class="class20">Messagebank</th>
<th class="class20">Broadcast Zones</th>
</tr> </tr>
</thead> </thead>
<tbody id="usertablebody"></tbody> <tbody id="usertablebody"></tbody>
@@ -56,10 +58,10 @@
<div class="modal fade border-0" role="dialog" tabindex="-1" id="addmodal"> <div class="modal fade border-0" role="dialog" tabindex="-1" id="addmodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header show"> <div class="modal-header text-start show bg-header-modal">
<h4 class="modal-title align-content-center">Add / Edit User</h4> <h4 class="modal-title text-center align-content-center">Add / Edit User</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Index</p> <p class="text-add">Index</p>
@@ -86,21 +88,53 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Sound Bank</p> <p class="text-add">Location</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modallocation" class="input-add form-control"></div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Airline Tags</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
<div class="row">
<div class="col-8"><input type="text" id="modalairlinetags" class="form-control input-add"></div>
<div class="col-4"><button class="btn w-100 btn-select btn-round-basic color-import" id="btnShowSoundbankModal" type="button">Select</button></div>
</div>
</div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">City Tags</p>
</div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
<div class="row">
<div class="col-8"><input type="text" id="modalcitytags" class="form-control input-add"></div>
<div class="col-4"></div>
</div>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalsoundbank" class="input-add"><button class="btn btn-primary" id="btnShowSoundbankModal" type="button">Select</button></div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Message Bank</p> <p class="text-add">Message Bank</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalmessagebank" class="input-add"><button class="btn btn-primary" id="btnShowMessagebankModal" type="button">Select</button></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
<div class="row">
<div class="col-8"><input type="text" id="modalmessagebank" class="form-control input-add"></div>
<div class="col-4"><button class="btn w-100 btn-round-basic color-import" id="btnShowMessagebankModal" type="button">Select</button></div>
</div> </div>
<div class="row" style="height: 100px;"> </div>
</div>
<div class="row">
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"> <div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<p class="text-add">Broadcast Zones</p> <p class="text-add">Broadcast Zones</p>
</div> </div>
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalbroadcastzones" class="input-add" name="modalpath"><button class="btn btn-primary" id="btnShowBroaadcastZoneModal" type="button">Select</button></div> <div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
<div class="row">
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalbroadcastzones" class="form-control input-add" name="modalpath"></div>
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><button class="btn w-100 btn-round-basic color-import" id="btnShowBroaadcastZoneModal" type="button">Select</button></div>
</div>
</div>
</div> </div>
</div> </div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="usermanagementclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="usermanagementsave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="usermanagementclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="usermanagementsave" type="button">Save</button></div>
@@ -110,43 +144,61 @@
<div class="modal fade" role="dialog" tabindex="-1" id="soundbankmodal"> <div class="modal fade" role="dialog" tabindex="-1" id="soundbankmodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header form-control input-add">
<h4 class="modal-title">Sound Bank Selection</h4> <h4 class="modal-title">Sound Bank Selection</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<p>(mon, ganti list checkbox, dengan id=soundbankselection</p> <div class="accordion" role="tablist" id="accordion-1">
<div class="accordion-item pad-accordion">
<h2 class="accordion-header" role="tab"><button class="accordion-button bg-heading1" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="true" aria-controls="accordion-1 .item-1">Airline</button></h2>
<div class="accordion-collapse collapse show item-1" role="tabpanel" data-bs-parent="#accordion-1">
<div class="accordion-body">
<ul id="airlinelist"></ul>
</div> </div>
<div class="modal-footer"><button class="btn btn-light" id="soundbankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-primary" id="soundbankselectionsave" type="button">Save</button></div> </div>
</div>
<div class="accordion-item pad-accordion">
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed bg-heading2" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="false" aria-controls="accordion-1 .item-2">City</button></h2>
<div class="accordion-collapse collapse item-2" role="tabpanel" data-bs-parent="#accordion-1">
<div class="accordion-body">
<ul id="citylist"></ul>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundbankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundbankselectionsave" type="button">Save</button></div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" role="dialog" tabindex="-1" id="broadcastzonemodal"> <div class="modal fade" role="dialog" tabindex="-1" id="broadcastzonemodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header form-control input-add">
<h4 class="modal-title">Broadcast Zones Selection</h4> <h4 class="modal-title">Broadcast Zones Selection</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<p>(mon, ganti list checkbox, dengan id=broadcastzoneselection</p> <ul id="broadcastzonelist"></ul>
</div> </div>
<div class="modal-footer"><button class="btn btn-light" id="broadcastzoneselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-primary" id="broadcastzoneselectionsave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="broadcastzoneselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="broadcastzoneselectionsave" type="button">Save</button></div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" role="dialog" tabindex="-1" id="messagebankmodal"> <div class="modal fade" role="dialog" tabindex="-1" id="messagebankmodal">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header form-control input-add">
<h4 class="modal-title">Message Bank Selection</h4> <h4 class="modal-title">Message Bank Selection</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body bg-modal-body">
<p>(mon, ganti list checkbox, dengan id=messagebankselection</p> <ul id="messagebanklist"></ul>
</div> </div>
<div class="modal-footer"><button class="btn btn-light" id="messagebankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-primary" id="messagebankselectionsave" type="button">Save</button></div> <div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="messagebankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="messagebankselectionsave" type="button">Save</button></div>
</div> </div>
</div> </div>
</div> </div>
<script src="assets/bootstrap/js/bootstrap.min.js"></script> <script src="assets/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/js/bs-init.js"></script>
<script src="assets/js/usermanagement.js"></script> <script src="assets/js/usermanagement.js"></script>
</body> </body>

View File

@@ -7,6 +7,7 @@ import barix.TCP_Barix_Command_Server
import codes.Somecodes import codes.Somecodes
import com.sun.jna.Platform import com.sun.jna.Platform
import commandServer.TCP_Android_Command_Server import commandServer.TCP_Android_Command_Server
import content.Category
import content.Language import content.Language
import content.VoiceType import content.VoiceType
import database.Log import database.Log
@@ -21,6 +22,7 @@ import org.tinylog.provider.ProviderRegistry
import oshi.util.GlobalConfig import oshi.util.GlobalConfig
import web.WebApp import web.WebApp
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths
import kotlin.concurrent.fixedRateTimer import kotlin.concurrent.fixedRateTimer
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
@@ -29,7 +31,7 @@ lateinit var audioPlayer: AudioPlayer
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap() val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
lateinit var udpreceiver: UDPReceiver lateinit var udpreceiver: UDPReceiver
lateinit var tcpreceiver: TCPReceiver lateinit var tcpreceiver: TCPReceiver
const val version = "0.0.2 (23/09/2025)" const val version = "0.0.8 (16/10/2025)"
// AAS 64 channels // AAS 64 channels
const val max_channel = 64 const val max_channel = 64
@@ -52,11 +54,17 @@ val contentCache = ContentCache()
* Create necessary folders if not exist * Create necessary folders if not exist
*/ */
fun folder_preparation(){ fun folder_preparation(){
// sementara diset begini, nanti pake config file
Somecodes.Soundbank_directory = Paths.get("c:\\soundbank")
Files.createDirectories(Somecodes.SoundbankResult_directory) Files.createDirectories(Somecodes.SoundbankResult_directory)
Files.createDirectories(Somecodes.PagingResult_directory) Files.createDirectories(Somecodes.PagingResult_directory)
Files.createDirectories(Somecodes.Soundbank_directory) Files.createDirectories(Somecodes.Soundbank_directory)
Somecodes.Soundbank_Languages_directory.forEach { Language.entries.forEach { language ->
Files.createDirectories(it) VoiceType.entries.forEach { voice ->
Category.entries.forEach { category ->
Files.createDirectories(Somecodes.SoundbankDirectory(language, voice, category) )
}
}
} }
} }
@@ -159,29 +167,49 @@ fun main() {
val barixserver = TCP_Barix_Command_Server() val barixserver = TCP_Barix_Command_Server()
barixserver.StartTcpServer { cmd -> barixserver.StartTcpServer { cmd ->
//Logger.info { cmd } val _tcp = barixserver.getSocket(cmd.ipaddress)
val _streamer = StreamerOutputs[cmd.ipaddress] val _streamer = StreamerOutputs[cmd.ipaddress]
val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress } val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress }
if (_streamer == null) { if (_streamer == null) {
// belum create BarixConnection untuk ipaddress ini // belum create BarixConnection untuk ipaddress ini
//Logger.info { "New Streamer Output connection from ${cmd.ipaddress}" } //Logger.info { "New Streamer Output connection from ${cmd.ipaddress}" }
if (_sc != null) { if (_sc != null) {
val _bc = BarixConnection(_sc.index, _sc.channel, cmd.ipaddress) val _bc = BarixConnection(_sc.index, _sc.channel, cmd.ipaddress)
_bc.vu = cmd.vu // cmd.vu 0 - 32767, kita convert ke 0 - 100
_bc.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
_bc.bufferRemain = cmd.buffremain _bc.bufferRemain = cmd.buffremain
_bc.statusData = cmd.statusdata _bc.statusData = cmd.statusdata
_bc.commandsocket = _tcp
StreamerOutputs[cmd.ipaddress] = _bc StreamerOutputs[cmd.ipaddress] = _bc
Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" } Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" }
} else Logger.warn { "soundChannelDB doesn't have soundchannel with IP ${cmd.ipaddress}" } }
} else { } else {
// sudah ada, update data // sudah ada, update data
if (_sc != null && _sc.channel != _streamer.channel) { if (_sc != null && _sc.channel != _streamer.channel) {
_streamer.channel = _sc.channel _streamer.channel = _sc.channel
} }
_streamer.vu = cmd.vu // cmd.vu 0 - 32767, kita convert ke 0 - 100
_streamer.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
_streamer.bufferRemain = cmd.buffremain _streamer.bufferRemain = cmd.buffremain
_streamer.statusData = cmd.statusdata _streamer.statusData = cmd.statusdata
// cek apakah koneksi TCP nya ganti
if (_streamer.commandsocket == null) {
_streamer.commandsocket = _tcp
} else {
if (_streamer.commandsocket != _tcp) {
// ganti koneksi
try {
_streamer.commandsocket?.close()
} catch (ex: Exception) {
Logger.error(ex) { "Error closing previous TCP command socket for ${cmd.ipaddress}" }
}
_streamer.commandsocket = _tcp
}
}
} }
} }

View File

@@ -1,4 +1,5 @@
import audio.AudioFileInfo import audio.AudioFileInfo
import barix.BarixConnection
import codes.Result_GetSoundbankFiles import codes.Result_GetSoundbankFiles
import codes.Somecodes.Companion.Get_ANN_ID import codes.Somecodes.Companion.Get_ANN_ID
import codes.Somecodes.Companion.IsAlphabethic import codes.Somecodes.Companion.IsAlphabethic
@@ -89,6 +90,25 @@ class MainExtension01 {
return false return false
} }
/**
* Find BarixConnection by BroadcastZones description
* @param zonename BroadcastZones description
* @return BarixConnection if found, null if not found or invalid
*/
fun Get_Barix_Connection_by_ZoneName(zonename: String) : BarixConnection? {
if (ValidString(zonename)){
val bz = db.broadcastDB.List.find{ it.description == zonename }
val sc = if (bz!=null) db.soundchannelDB.List.find { it.channel == bz.SoundChannel } else null
val ip = sc?.ip ?: ""
if (ValidIPV4(ip)){
// ketemu ip-nya
return StreamerOutputs[ip]
}
}
return null
}
/** /**
* find SoundChannel IP from BroadcastZones description * find SoundChannel IP from BroadcastZones description
* @param bz List of BroadcastZones description * @param bz List of BroadcastZones description
@@ -541,7 +561,7 @@ class MainExtension01 {
.filter { it.Type=="PAGING" } .filter { it.Type=="PAGING" }
list.forEach { qp -> list.forEach { qp ->
println("Processing $qp") //println("Processing $qp")
if (qp.BroadcastZones.isNotBlank()){ if (qp.BroadcastZones.isNotBlank()){
if (ValidFile(qp.Message)){ if (ValidFile(qp.Message)){
val zz = qp.BroadcastZones.split(";") val zz = qp.BroadcastZones.split(";")
@@ -550,19 +570,30 @@ class MainExtension01 {
if (AllStreamerOutputIdle(ips)){ if (AllStreamerOutputIdle(ips)){
val afi = audioPlayer.LoadAudioFile(qp.Message) val afi = audioPlayer.LoadAudioFile(qp.Message)
if (afi.isValid()){ if (afi.isValid()){
// file bisa di load, kirim ke masing masing Streamer Output by IP address // file bisa di load, kirim ke masing-masing Streamer Output by IP address
ips.forEach { ip -> StreamerOutputs[ip]?.SendData(afi.bytes, { db.Add_Log("AAS", it) }, { db.Add_Log("AAS", it) }) } Activate_Relays(zz)
ips.forEach { ip ->
// send byte array to streamer output
StreamerOutputs[ip]?.SendData(afi.bytes,
{
Deactivate_Relays(zz)
db.Add_Log("AAS", it) },
{
Deactivate_Relays(zz)
db.Add_Log("AAS", it) })
}
val logmessage = val logmessage =
"Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}"
Logger.info { logmessage } Logger.info { logmessage }
db.Add_Log("AAS", logmessage) db.Add_Log("AAS", logmessage)
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
return true return true
} else { } else {
// file tidak valid, delete from queue paging // file tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log( db.Add_Log(
"AAS", "AAS",
"Cancelled paging message with index ${qp.index} due to invalid audio file" "Cancelled paging message with index ${qp.index} due to invalid audio file"
@@ -572,11 +603,13 @@ class MainExtension01 {
} else { } else {
// ada broadcast zone yang tidak valid, delete from queue paging // ada broadcast zone yang tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled paging message with index ${qp.index} due to invalid broadcast zone") db.Add_Log("AAS", "Cancelled paging message with index ${qp.index} due to invalid broadcast zone")
} }
} else { } else {
// file tidak valid, delete from queue paging // file tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log( db.Add_Log(
"AAS", "AAS",
"Cancelled paging message with index ${qp.index} due to invalid audio file" "Cancelled paging message with index ${qp.index} due to invalid audio file"
@@ -585,6 +618,7 @@ class MainExtension01 {
} else { } else {
// invalid broadcast zone, delete from queue paging // invalid broadcast zone, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled paging message with index ${qp.index} due to empty broadcast zone") db.Add_Log("AAS", "Cancelled paging message with index ${qp.index} due to empty broadcast zone")
} }
} }
@@ -640,13 +674,25 @@ class MainExtension01 {
// file siap broadcast // file siap broadcast
val targetafi = audioPlayer.LoadAudioFile(targetfile) val targetafi = audioPlayer.LoadAudioFile(targetfile)
if (targetafi.isValid()) { if (targetafi.isValid()) {
ips.forEach { ip -> StreamerOutputs[ip]?.SendData(targetafi.bytes, { db.Add_Log("AAS", it) }, {db.Add_Log("AAS", it) }) } // activate relays from broadcast zone
Activate_Relays(zz)
ips.forEach { ip ->
// send byte array to streamer output
StreamerOutputs[ip]?.SendData(targetafi.bytes,
{
Deactivate_Relays(zz)
db.Add_Log("AAS", it) },
{
Deactivate_Relays(zz)
db.Add_Log("AAS", it) })
}
val logmsg = val logmsg =
"Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}"
Logger.info { logmsg } Logger.info { logmsg }
db.Add_Log("AAS", logmsg) db.Add_Log("AAS", logmsg)
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
return true return true
} else { } else {
db.Add_Log( db.Add_Log(
@@ -657,6 +703,7 @@ class MainExtension01 {
} }
} else{ } else{
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", result.message) db.Add_Log("AAS", result.message)
} }
} }
@@ -665,6 +712,7 @@ class MainExtension01 {
} else { } else {
// tidak ada messagebank dengan ann_id ini, delete from queue paging // tidak ada messagebank dengan ann_id ini, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log( db.Add_Log(
"AAS", "AAS",
"Cancelled Shalat message with index ${qp.index} due to ANN_ID $ann_id not found in Messagebank" "Cancelled Shalat message with index ${qp.index} due to ANN_ID $ann_id not found in Messagebank"
@@ -675,16 +723,19 @@ class MainExtension01 {
} else { } else {
// invalid ann_id, delete from queue paging // invalid ann_id, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to invalid ANN_ID") db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to invalid ANN_ID")
} }
} else { } else {
// ada broadcast zone yang tidak valid, delete from queue paging // ada broadcast zone yang tidak valid, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to invalid broadcast zone") db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to invalid broadcast zone")
} }
} else { } else {
// invalid broadcast zone, delete from queue paging // invalid broadcast zone, delete from queue paging
db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.DeleteByIndex(qp.index.toInt())
db.queuepagingDB.Resort()
db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to empty broadcast zone") db.Add_Log("AAS", "Cancelled shalat message with index ${qp.index} due to empty broadcast zone")
} }
} }
@@ -730,21 +781,35 @@ class MainExtension01 {
// file siap broadcast // file siap broadcast
val targetafi = audioPlayer.LoadAudioFile(targetfile) val targetafi = audioPlayer.LoadAudioFile(targetfile)
if (targetafi.isValid()) { if (targetafi.isValid()) {
ips.forEach { ip -> StreamerOutputs[ip]?.SendData(targetafi.bytes, { db.Add_Log("AAS", it) }, { db.Add_Log("AAS", it) }) } // activate relays from broadcast zone
Activate_Relays(zz)
ips.forEach { ip ->
// send byte array to streamer output
StreamerOutputs[ip]?.SendData(targetafi.bytes,
{
Deactivate_Relays(zz)
db.Add_Log("AAS", it) },
{
Deactivate_Relays(zz)
db.Add_Log("AAS", it) })
}
val logmsg = val logmsg =
"Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}"
Logger.info { logmsg } Logger.info { logmsg }
db.Add_Log("AAS", logmsg) db.Add_Log("AAS", logmsg)
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
return true return true
} }
} else{ } else{
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", result.message) db.Add_Log("AAS", result.message)
} }
} else { } else {
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", result.message) db.Add_Log("AAS", result.message)
} }
@@ -753,6 +818,7 @@ class MainExtension01 {
} else { } else {
// tidak ada messagebank dengan ann_id ini, delete from queue table // tidak ada messagebank dengan ann_id ini, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log( db.Add_Log(
"AAS", "AAS",
"Cancelled TIMER with index ${qa.index} due to ANN_ID $ann_id not found in Messagebank" "Cancelled TIMER with index ${qa.index} due to ANN_ID $ann_id not found in Messagebank"
@@ -763,27 +829,61 @@ class MainExtension01 {
} else { } else {
// invalid ann_id, delete from queue table // invalid ann_id, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to invalid ANN_ID") db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to invalid ANN_ID")
} }
} else { } else {
// ada broadcast zone yang tidak valid, delete from queue table // ada broadcast zone yang tidak valid, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to invalid broadcast zone") db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to invalid broadcast zone")
} }
} else { } else {
// invalid broadcast zone, delete from queue table // invalid broadcast zone, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to empty broadcast zone") db.Add_Log("AAS", "Cancelled TIMER message with index ${qa.index} due to empty broadcast zone")
} }
} }
return false return false
} }
fun Activate_Relays(zz: List<String>){
zz.forEach { zonename ->
val bz = db.broadcastDB.List.find { it.description == zonename }
if (bz!=null){
val bc = Get_Barix_Connection_by_ZoneName(zonename)
if (bc!=null){
// ada barix connection
val relays = bz.bp.split(",")
.map { it.trim()}
.filter { it.isNotBlank() }
.filter { IsNumber(it) && it.toInt() in 1..8 }
.map{ it.toInt() }
if (relays.isNotEmpty()) bc.ActivateRelay(relays)
}
}
}
}
fun Deactivate_Relays(zz: List<String>){
zz.forEach { zonename ->
val bz = db.broadcastDB.List.find { it.description == zonename }
if (bz!=null){
val bc = Get_Barix_Connection_by_ZoneName(zonename)
bc?.DeactivateRelay()
}
}
}
fun Read_Queue_Soundbank() : Boolean{ fun Read_Queue_Soundbank() : Boolean{
db.queuetableDB.Get() db.queuetableDB.Get()
val list = db.queuetableDB.List.filter { it.Type=="SOUNDBANK" } val list = db.queuetableDB.List.filter { it.Type=="SOUNDBANK" }
list.forEach { qa -> list.forEach { qa ->
println("Processing $qa") //println("Processing $qa")
if (qa.BroadcastZones.isNotEmpty()){ if (qa.BroadcastZones.isNotEmpty()){
val zz = qa.BroadcastZones.split(";") val zz = qa.BroadcastZones.split(";")
if (AllBroadcastZonesValid(zz)){ if (AllBroadcastZonesValid(zz)){
@@ -820,6 +920,7 @@ class MainExtension01 {
"Cancelled SOUNDBANK message with index ${qa.index} due to missing or invalid ANN_ID in SB_TAGS" "Cancelled SOUNDBANK message with index ${qa.index} due to missing or invalid ANN_ID in SB_TAGS"
) )
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
return@forEach return@forEach
} }
// sampe sini punya ann_id valid // sampe sini punya ann_id valid
@@ -847,12 +948,14 @@ class MainExtension01 {
} else { } else {
db.Add_Log("AAS", result.message) db.Add_Log("AAS", result.message)
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
} }
} }
if (listafi.isNotEmpty()){ if (listafi.isNotEmpty()){
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
// bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore // bikin nama file ada postifx nya dari SB_TAGS, tapi spasi diganti underscore
// dan titik dua diganti dash // dan titik dua diganti dash
val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-") val postfix = qa.SB_TAGS.replace(" ","_").replace(":","-")
@@ -864,7 +967,20 @@ class MainExtension01 {
//println("Successfully wrote WAV file: $targetfile") //println("Successfully wrote WAV file: $targetfile")
val targetafi = audioPlayer.LoadAudioFile(targetfile) val targetafi = audioPlayer.LoadAudioFile(targetfile)
if (targetafi.isValid()) { if (targetafi.isValid()) {
ips.forEach { ip -> StreamerOutputs[ip]?.SendData(targetafi.bytes, { db.Add_Log("AAS", it) }, { db.Add_Log("AAS", it) }) } // activate relays from broadcast zone
Activate_Relays(zz)
ips.forEach { ip ->
// send byte array to streamer output
StreamerOutputs[ip]?.SendData(targetafi.bytes,
{
Deactivate_Relays(zz)
db.Add_Log("AAS", it)
}, {
Deactivate_Relays(zz)
db.Add_Log("AAS", it)
})
}
val logmsg = val logmsg =
"Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}"
@@ -876,6 +992,7 @@ class MainExtension01 {
} else { } else {
println("Failed to write WAV file: ${result.message}") println("Failed to write WAV file: ${result.message}")
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", result.message) db.Add_Log("AAS", result.message)
} }
@@ -883,6 +1000,7 @@ class MainExtension01 {
} else { } else {
// tidak ada messagebank dengan ann_id ini, delete from queue table // tidak ada messagebank dengan ann_id ini, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log( db.Add_Log(
"AAS", "AAS",
"Cancelled SOUNDBANK message with index ${qa.index} due to ANN_ID $ann_id not found in Messagebank" "Cancelled SOUNDBANK message with index ${qa.index} due to ANN_ID $ann_id not found in Messagebank"
@@ -894,11 +1012,13 @@ class MainExtension01 {
} else { } else {
// ada broadcast zone yang tidak valid, delete from queue table // ada broadcast zone yang tidak valid, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled SOUNDBANK message with index ${qa.index} due to invalid broadcast zone") db.Add_Log("AAS", "Cancelled SOUNDBANK message with index ${qa.index} due to invalid broadcast zone")
} }
} else { } else {
// invalid broadcast zone, delete from queue table // invalid broadcast zone, delete from queue table
db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.DeleteByIndex(qa.index.toInt())
db.queuetableDB.Resort()
db.Add_Log("AAS", "Cancelled SOUNDBANK message with index ${qa.index} due to empty broadcast zone") db.Add_Log("AAS", "Cancelled SOUNDBANK message with index ${qa.index} due to empty broadcast zone")
} }
} }

View File

@@ -6,9 +6,11 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tinylog.Logger
import java.net.DatagramPacket import java.net.DatagramPacket
import java.net.DatagramSocket import java.net.DatagramSocket
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.util.function.Consumer import java.util.function.Consumer
@@ -18,9 +20,9 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
private var _sd: Int = 0 private var _sd: Int = 0
private var _vu: Int = 0 private var _vu: Int = 0
private var _onlinecounter = 0 private var _onlinecounter = 0
private val udp = DatagramSocket(0)
private val inet = InetSocketAddress(ipaddress, port) private val inet = InetSocketAddress(ipaddress, port)
private val maxUDPsize = 1000 private val maxUDPsize = 1000
private var _tcp: Socket? = null
/** /**
@@ -53,6 +55,15 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
_onlinecounter = 5 _onlinecounter = 5
} }
/**
* TCP command socket for communication with this Barix device
*/
var commandsocket: Socket?
get() = _tcp
set(value){
_tcp = value
}
/** /**
* Decrement online counter, if counter reaches 0, the device is considered offline * Decrement online counter, if counter reaches 0, the device is considered offline
*/ */
@@ -93,24 +104,26 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>) { fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>) {
if (data.isNotEmpty()) { if (data.isNotEmpty()) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
DatagramSocket().use{ udp ->
val bb = ByteBuffer.wrap(data) val bb = ByteBuffer.wrap(data)
while(bb.hasRemaining()){ while(bb.hasRemaining()){
try { try {
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining()) val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
bb.get(chunk) bb.get(chunk)
//println("Buffer remain: $bufferRemain, sending chunk size: ${chunk.size}")
while(bufferRemain<chunk.size){ while(bufferRemain<chunk.size){
delay(10) delay(10)
//println("Waiting until buffer enough..")
} }
udp.send(DatagramPacket(chunk, chunk.size, inet)) udp.send(DatagramPacket(chunk, chunk.size, inet))
delay(1) delay(2)
} catch (e: Exception) { } catch (e: Exception) {
cbFail.accept("SendData to $ipaddress:$port failed, message: ${e.message}") cbFail.accept("SendData to $ipaddress:$port failed, message: ${e.message}")
return@launch return@launch
} }
} }
cbOK.accept("SendData to $channel ($ipaddress:$port) succeeded, ${data.size} bytes sent") cbOK.accept("SendData to $channel ($ipaddress:$port) succeeded, ${data.size} bytes sent")
}
} }
} else cbFail.accept("SendData to $ipaddress:$port failed, data is empty") } else cbFail.accept("SendData to $ipaddress:$port failed, data is empty")
@@ -142,4 +155,76 @@ class BarixConnection(val index: UInt, var channel: String, val ipaddress: Strin
fun toJsonString(): String { fun toJsonString(): String {
return Somecodes.toJsonString(toJsonNode()) return Somecodes.toJsonString(toJsonNode())
} }
/**
* Activate relay on Barix device
* @param relays The relay numbers to activate (1-8)
* @return true if successful
*/
fun ActivateRelay(vararg relays: Int){
val command = StringBuilder("RELAY;")
var binary = 0
relays.forEach {
if (it in 1..8) {
binary = binary or (1 shl (it - 1))
}
}
command.append(binary.toString()).append("@")
SendCommand(command.toString())
}
fun ActivateRelay(relays: List<Int>){
val command = StringBuilder("RELAY;")
var binary = 0
relays.forEach {
if (it in 1..8) {
binary = binary or (1 shl (it - 1))
}
}
command.append(binary.toString()).append("@")
SendCommand(command.toString())
}
/**
* Deactivate relay on Barix device
*/
fun DeactivateRelay(){
SendCommand("RELAY;0@")
}
/**
* Send command to Barix device
* @param command The command to send
* @return true if successful
*/
fun SendCommand(command: String): Boolean {
try {
if (_tcp!=null){
if (!_tcp!!.isClosed){
val bb = command.toByteArray()
val size = bb.size + 4
val b4 = byteArrayOf(
(size shr 24 and 0xFF).toByte(),
(size shr 16 and 0xFF).toByte(),
(size shr 8 and 0xFF).toByte(),
(size and 0xFF).toByte()
)
val out = _tcp!!.getOutputStream()
out.write(b4)
out.write(bb)
out.flush()
Logger.info { "SendCommand to $ipaddress : $command" }
return true
}else {
Logger.error { "Socket to $ipaddress is not connected" }
}
} else {
Logger.error { "Socket to $ipaddress is null" }
}
} catch (e: Exception) {
Logger.error { "Failed to SendCommand to $ipaddress, Message : ${e.message}" }
}
return false
}
} }

View File

@@ -3,8 +3,10 @@ package barix
import codes.Somecodes.Companion.ValidString import codes.Somecodes.Companion.ValidString
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.tinylog.Logger import org.tinylog.Logger
import java.io.DataInputStream
import java.net.ServerSocket import java.net.ServerSocket
import java.net.Socket import java.net.Socket
import java.nio.ByteBuffer
import java.util.function.Consumer import java.util.function.Consumer
@Suppress("unused") @Suppress("unused")
@@ -37,13 +39,19 @@ class TCP_Barix_Command_Server {
val key : String = socket.inetAddress.hostAddress val key : String = socket.inetAddress.hostAddress
socketMap[key] = socket socketMap[key] = socket
Logger.info { "Start communicating with Streamer Output with IP : $key" } Logger.info { "Start communicating with Streamer Output with IP : $key" }
val din = socket.getInputStream() try{
val din = DataInputStream(socket.getInputStream())
while (isActive) { while (isActive) {
if (din.available()>0){ val length = ByteArray(4)
val bb = ByteArray(din.available()) din.readFully(length)
din.read(bb) val readlength = ByteBuffer.wrap(length).getInt()
//println("Read Length : $readlength")
val bb = ByteArray(readlength)
din.readFully(bb)
// B4A format, 4 bytes di depan adalah size // B4A format, 4 bytes di depan adalah size
val str = String(bb, 4, bb.size - 4) val str = String(bb)
//println("Received from $key : $str")
if (ValidString(str)) { if (ValidString(str)) {
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$ // Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
pattern.find(str)?.let { matchResult -> pattern.find(str)?.let { matchResult ->
@@ -60,8 +68,12 @@ class TCP_Barix_Command_Server {
Logger.warn { "Invalid command format from $key : $str" } Logger.warn { "Invalid command format from $key : $str" }
} }
} }
} }
} catch (ex:Exception){
Logger.error { "Error in communication with Streamer Output with IP $key, Message : ${ex.message}" }
} }
Logger.info { "Finished communicating with Streamer Output with IP $key" } Logger.info { "Finished communicating with Streamer Output with IP $key" }
socketMap.remove(key) socketMap.remove(key)
@@ -102,4 +114,15 @@ class TCP_Barix_Command_Server {
} }
return false return false
} }
/**
* Get Socket by IP address
* @param ip The IP address of the client
* @return Socket if found, null otherwise
*/
fun getSocket(ip: String): Socket? {
return socketMap[ip]
}
} }

View File

@@ -2,8 +2,11 @@ package codes
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import content.Category
import content.Language import content.Language
import content.NetworkInformation
import content.ScheduleDay import content.ScheduleDay
import content.VoiceType
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -13,6 +16,8 @@ import oshi.SystemInfo
import oshi.hardware.CentralProcessor import oshi.hardware.CentralProcessor
import oshi.hardware.GlobalMemory import oshi.hardware.GlobalMemory
import oshi.hardware.NetworkIF import oshi.hardware.NetworkIF
import oshi.hardware.Sensors
import oshi.software.os.OperatingSystem
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -25,21 +30,17 @@ import kotlin.io.path.name
class Somecodes { class Somecodes {
companion object { companion object {
val current_directory : String = System.getProperty("user.dir") val current_directory : String = System.getProperty("user.dir")
val Soundbank_directory : Path = Path.of(current_directory,"Soundbank") var Soundbank_directory : Path = Path.of(current_directory,"Soundbank")
val Soundbank_Languages_directory : List<Path> = listOf(
Soundbank_directory.resolve(Language.INDONESIA.name),
Soundbank_directory.resolve(Language.LOCAL.name),
Soundbank_directory.resolve(Language.ENGLISH.name),
Soundbank_directory.resolve(Language.CHINESE.name),
Soundbank_directory.resolve(Language.JAPANESE.name),
Soundbank_directory.resolve(Language.ARABIC.name)
)
val SoundbankResult_directory : Path = Path.of(current_directory,"SoundbankResult") val SoundbankResult_directory : Path = Path.of(current_directory,"SoundbankResult")
val PagingResult_directory : Path = Path.of(current_directory,"PagingResult") val PagingResult_directory : Path = Path.of(current_directory,"PagingResult")
val si = SystemInfo() val si = SystemInfo()
val processor: CentralProcessor = si.hardware.processor val processor: CentralProcessor = si.hardware.processor
val memory : GlobalMemory = si.hardware.memory val memory : GlobalMemory = si.hardware.memory
val NetworkInfoMap = mutableMapOf<String, NetworkInformation>()
val sensor : Sensors = si.hardware.sensors
val os : OperatingSystem = si.operatingSystem
val datetimeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss") val datetimeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")
val dateformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy") val dateformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
@@ -56,6 +57,25 @@ class Somecodes {
// regex for getting ann_id from Message, which is the number inside [] // regex for getting ann_id from Message, which is the number inside []
private val ann_id_regex = Regex("\\[(\\d+)]") private val ann_id_regex = Regex("\\[(\\d+)]")
/**
* Get the directory path for a specific language, voice type, and category.
* @param language The language.
* @param voice The voice type.
* @param category The category.
* @return The path to the directory.
*/
fun SoundbankDirectory(language: Language, voice: VoiceType, category: Category) : Path{
return Soundbank_directory.resolve(language.name).resolve(voice.name).resolve(category.name)
}
fun SoundbankDirectory(language: String, voice: String, category: String) : Path{
return SoundbankDirectory(
Language.valueOf(language),
VoiceType.valueOf(voice),
Category.valueOf(category)
)
}
fun ExtractFilesFromClassPath(resourcePath: String, outputDir: Path) { fun ExtractFilesFromClassPath(resourcePath: String, outputDir: Path) {
try { try {
val resource = Somecodes::class.java.getResource(resourcePath) val resource = Somecodes::class.java.getResource(resourcePath)
@@ -157,14 +177,13 @@ class Somecodes {
/** /**
* List all audio files (.mp3 and .wav) in the specified directory and its subdirectories. * List all audio files (.mp3 and .wav) in the specified directory and its subdirectories.
* Only files larger than 1KB are included. * Only files larger than 1KB are included.
* @param dir The directory to search in (default is the current working directory). * @param p The directory Path to search in
* @return A list of absolute paths to the audio files found. * @return A list of absolute paths to the audio files found.
*/ */
fun ListAudioFiles(dir: String = current_directory) : List<String>{ fun ListAudioFiles(p: Path) : List<String>{
return try{ return try{
// find all files that ends with .mp3 or .wav // find all files that ends with .mp3 or .wav
// and find them recursively // and find them recursively
val p = Path.of(dir)
if (Files.exists(p) && Files.isDirectory(p)){ if (Files.exists(p) && Files.isDirectory(p)){
Files.walk(p) Files.walk(p)
// cari file regular saja // cari file regular saja
@@ -265,16 +284,43 @@ class Somecodes {
, (usedMemory.toDouble() / totalMemory * 100)) , (usedMemory.toDouble() / totalMemory * 100))
} }
fun GetNetworkStatus(networkname: String) : String{ fun GetNetworkStatus(cb : Consumer<List<NetworkInformation>>) {
val networks: List<NetworkIF> = si.hardware.networkIFs.toList() val networks: List<NetworkIF> = si.hardware.networkIFs.toList()
//TODO cari network yang sesuai dengan networkname networks.forEach { net ->
// networks.forEach { net ->
// if (net.ifOperStatus==NetworkIF.IfOperStatus.UP){
// println(net.name+" "+net.displayName+" ") if (net.iPv4addr.size>0 || net.iPv6addr.size>0){
// net.updateAttributes() var ni = NetworkInfoMap[net.name]
// println("upload: ${SizetoHuman(net.bytesSent)} sent, download: ${SizetoHuman(net.bytesRecv)} received") if (ni == null){
// } ni = NetworkInformation(net.name, net.displayName, net.macaddr)
return "" NetworkInfoMap[net.name] = ni
}
ni.ipV4addr = net.iPv4addr.toMutableList()
ni.ipV6addr = net.iPv6addr.toMutableList()
ni.speed = net.speed
ni.packetsSent = net.packetsSent
ni.packetsRecv = net.packetsRecv
if (ni.updateStamp==0L){
ni.bytesSent = net.bytesSent
ni.bytesRecv = net.bytesRecv
ni.txSpeed = 0
ni.rxSpeed = 0
ni.updateStamp = System.currentTimeMillis()
} else {
// tx speed = (current bytesSent - previous bytesSent) / (current time - previous time) * 1000
val currentTime = System.currentTimeMillis()
ni.txSpeed = ((net.bytesSent - ni.bytesSent) * 1000 / (currentTime - ni.updateStamp))
ni.rxSpeed = ((net.bytesRecv - ni.bytesRecv) * 1000 / (currentTime - ni.updateStamp))
ni.bytesSent = net.bytesSent
ni.bytesRecv = net.bytesRecv
ni.updateStamp = currentTime
}
} else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name)
} else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name)
}
cb.accept(NetworkInfoMap.values.toList())
} }
/** /**
@@ -286,6 +332,23 @@ class Somecodes {
return value is String && value.isNotBlank() return value is String && value.isNotBlank()
} }
/**
* Check if all strings in a list are valid non-blank strings.
* @param values The list of strings to check.
* @return True if all strings in the list are valid non-blank strings, false otherwise.
*/
fun ValidStrings(values: List<String>) : Boolean{
if (values.isNotEmpty()){
for (v in values){
if (!ValidString(v)){
return false
}
}
return true
}
return false
}
/** /**
* Check if a string is a valid file path and the file exists. * Check if a string is a valid file path and the file exists.
* @param value The string to check. * @param value The string to check.
@@ -433,6 +496,35 @@ class Somecodes {
sb.append(".wav") sb.append(".wav")
return sb.toString() return sb.toString()
} }
/**
* Get sensors information using OSHI library.
* @return A string representing the CPU temperature, fan speeds, and CPU voltage, or an empty string if not available.
*/
fun GetSensorsInfo() : String {
val cputemp = sensor.cpuTemperature
val cpuvolt = sensor.cpuVoltage
val fanspeed = sensor.fanSpeeds
return if (cpuvolt>0 && cputemp > 0 && fanspeed.isNotEmpty()){
String.format("CPU Temp: %.1f °C\nFan Speeds: %s RPM\nCPU Voltage: %.2f V",
sensor.cpuTemperature,
sensor.fanSpeeds.joinToString("/"),
sensor.cpuVoltage
)
} else ""
}
fun GetUptime() : String {
val value = os.systemUptime
return if (value>0){
// number of seconds since system boot
val hours = value / 3600
val minutes = (value % 3600) / 60
val seconds = value % 60
String.format("%02d:%02d:%02d", hours, minutes, seconds)
} else ""
}
} }

View File

@@ -69,7 +69,7 @@ class TCP_Android_Command_Server {
din.read(bb) din.read(bb)
// B4A format, 4 bytes di depan adalah size // B4A format, 4 bytes di depan adalah size
val str = String(bb, 4, bb.size - 4) val str = String(bb, 4, bb.size - 4)
println("Received command from $key : $str") //println("Received command from $key : $str")
str.split("@").map { it.trim() }.filter { ValidString(it) } str.split("@").map { it.trim() }.filter { ValidString(it) }
.forEach { .forEach {
process_command(key,it) { reply -> process_command(key,it) { reply ->
@@ -220,6 +220,7 @@ class TCP_Android_Command_Server {
pj.broadcastzones pj.broadcastzones
) )
if (db.queuepagingDB.Add(qp)) { if (db.queuepagingDB.Add(qp)) {
db.queuepagingDB.Resort()
logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}") logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
cb.accept("PCMFILE_STOP;OK@") cb.accept("PCMFILE_STOP;OK@")
return return
@@ -274,6 +275,7 @@ class TCP_Android_Command_Server {
pj.broadcastzones pj.broadcastzones
) )
if (db.queuepagingDB.Add(qp)){ if (db.queuepagingDB.Add(qp)){
db.queuepagingDB.Resort()
logcb.accept("Paging audio inserted to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}") logcb.accept("Paging audio inserted to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}")
cb.accept("STOPPAGINGAND;OK@") cb.accept("STOPPAGINGAND;OK@")
return return
@@ -306,7 +308,7 @@ class TCP_Android_Command_Server {
if (userlogin != null){ if (userlogin != null){
val userdb = db.userDB.List.find { it.username == username } val userdb = db.userDB.List.find { it.username == username }
if (userdb != null){ if (userdb != null){
println("Sending initialization data to $key with username $username") //println("Sending initialization data to $key with username $username")
val result = StringBuilder() val result = StringBuilder()
// kirim Zone // kirim Zone
result.append("ZONE") result.append("ZONE")
@@ -627,6 +629,7 @@ class TCP_Android_Command_Server {
lang lang
) )
if (db.queuetableDB.Add(qt)){ if (db.queuetableDB.Add(qt)){
db.queuetableDB.Resort()
logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} inserted. Message: $desc;$lang;$tags;$zone") logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} inserted. Message: $desc;$lang;$tags;$zone")
cb.accept("BROADCASTAND;OK@") cb.accept("BROADCASTAND;OK@")
return return

View File

@@ -0,0 +1,15 @@
package content
class NetworkInformation(val name: String, val displayName: String, val macAddress: String) {
var ipV4addr: MutableList<String> = mutableListOf()
var ipV6addr: MutableList<String> = mutableListOf()
var bytesSent: Long = 0
var bytesRecv: Long = 0
var packetsSent: Long = 0
var packetsRecv: Long = 0
var speed: Long = 0 // in bits per second
var updateStamp : Long = 0 // epoch time in milliseconds, buat hitung speed
var txSpeed: Long = 0
var rxSpeed: Long = 0
}

View File

@@ -1,4 +1,28 @@
package database package database
@Suppress("unused") @Suppress("unused")
data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var id: String, var bp: String) data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var id: String, var bp: String){
/**
* Check if all fields are not empty
*/
fun isNotEmpty(): Boolean{
if (description.isNotEmpty()){
if (SoundChannel.isNotEmpty()){
if (id.isNotEmpty()){
if (bp.isNotEmpty()){
return true
}
}
}
}
return false
}
/**
* Return a string representation of the BroadcastZones object.
*/
override fun toString(): String {
return "BroadcastZones(index=$index, description='$description', SoundChannel='$SoundChannel', id='$id', bp='$bp')"
}
}

View File

@@ -1,3 +0,0 @@
package database
data class BroadcastZonesHtml(var index: UInt, var description: String, var SoundChannel: String, var Box: String, var Relay: String)

View File

@@ -1,4 +0,0 @@
package database
@Suppress("unused")
data class IpZones(var index:UInt, var description: String, var ip: String)

View File

@@ -1,4 +1,22 @@
package database package database
@Suppress("unused") data class LanguageLink(var index: UInt, var TAG: String, var Language: String){
data class LanguageLink(var index: UInt, var TAG: String, var Language: String)
/**
* Check if the LanguageLink object has non-empty TAG and Language fields.
* @return true if both TAG and Language are not empty, false otherwise.
*/
fun isNotEmpty() : Boolean {
return TAG.isNotEmpty() && Language.isNotEmpty()
}
/**
* Returns a string representation of the LanguageLink object.
* @return A string in the format "LanguageLink(index=..., TAG='...', Language='...')".
*/
override fun toString(): String {
return "LanguageLink(index=$index, TAG='$TAG', Language='$Language')"
}
}

View File

@@ -1,4 +1,9 @@
package database package database
@Suppress("unused") data class LogSemiauto(val index: UInt, val date: String, val time: String, val source: String, val description: String){
data class LogSemiauto(val index: UInt, val date: String, val time: String, val source: String, val description: String)
override fun toString(): String {
return "$date $time [$source] $description"
}
}

View File

@@ -12,7 +12,6 @@ import org.tinylog.Logger
import java.sql.Connection import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.util.function.Consumer import java.util.function.Consumer
import kotlin.math.max
/** /**
* A class to manage a connection to a MariaDB database. * A class to manage a connection to a MariaDB database.
@@ -105,7 +104,7 @@ class MariaDB(
List.clear() List.clear()
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}") val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName} ORDER BY Category, Language, VoiceType, TAG")
while (resultSet?.next() == true) { while (resultSet?.next() == true) {
val soundbank = Soundbank( val soundbank = Soundbank(
resultSet.getLong("index").toUInt(), resultSet.getLong("index").toUInt(),
@@ -221,13 +220,15 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM ${super.dbName} ORDER BY Description ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM ${super.dbName} ORDER BY Category, Language, VoiceType, TAG ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, TAG, Category, Language, VoiceType, Path) SELECT Description, TAG, Category, Language, VoiceType, Path FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by Description" as Any) Logger.info("${super.dbName} table resorted by Description" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -421,12 +422,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) SELECT Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS FROM ${super.dbName} ORDER BY ANN_ID ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) SELECT Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS FROM ${super.dbName} ORDER BY ANN_ID ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) SELECT Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS) SELECT Description, Language, ANN_ID, Voice_Type, Message_Detail, Message_TAGS FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by Description" as Any) Logger.info("${super.dbName} table resorted by Description" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -616,12 +619,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (TAG, Language) SELECT TAG, Language FROM ${super.dbName} ORDER BY TAG") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (TAG, Language) SELECT TAG, Language FROM ${super.dbName} ORDER BY TAG")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (TAG, Language) SELECT TAG, Language FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (TAG, Language) SELECT TAG, Language FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by TAG" as Any) Logger.info("${super.dbName} table resorted by TAG" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -834,12 +839,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language) SELECT Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language FROM ${super.dbName} ORDER BY Day , Time ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language) SELECT Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language FROM ${super.dbName} ORDER BY Day , Time ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language) SELECT Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language) SELECT Description, Day, Time, Soundpath, Repeat, Enable, BroadcastZones, Language FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by Day and Time" as Any) Logger.info("${super.dbName} table resorted by Day and Time" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -988,7 +995,7 @@ class MariaDB(
override fun Add(data: BroadcastZones): Boolean { override fun Add(data: BroadcastZones): Boolean {
try { try {
val statement = val statement =
connection.prepareStatement("INSERT INTO ${super.dbName} (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)") connection.prepareStatement("INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) VALUES (?, ?, ?, ?)")
statement?.setString(1, data.description) statement?.setString(1, data.description)
statement?.setString(2, data.SoundChannel) statement?.setString(2, data.SoundChannel)
statement?.setString(3, data.id) statement?.setString(3, data.id)
@@ -1010,7 +1017,7 @@ class MariaDB(
try { try {
connection.autoCommit = false connection.autoCommit = false
val sql = val sql =
"INSERT INTO ${super.dbName} (description, SoundChannel, Box, Relay) VALUES (?, ?, ?, ?)" "INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) VALUES (?, ?, ?, ?)"
val statement = connection.prepareStatement(sql) val statement = connection.prepareStatement(sql)
for (bz in data) { for (bz in data) {
statement.setString(1, bz.description) statement.setString(1, bz.description)
@@ -1033,7 +1040,7 @@ class MariaDB(
override fun UpdateByIndex(index: Int, data: BroadcastZones): Boolean { override fun UpdateByIndex(index: Int, data: BroadcastZones): Boolean {
try { try {
val statement = val statement =
connection.prepareStatement("UPDATE ${super.dbName} SET description = ?, SoundChannel = ?, Box = ?, Relay = ? WHERE `index` = ?") connection.prepareStatement("UPDATE ${super.dbName} SET description = ?, SoundChannel = ?, id = ?, bp = ? WHERE `index` = ?")
statement?.setString(1, data.description) statement?.setString(1, data.description)
statement?.setString(2, data.SoundChannel) statement?.setString(2, data.SoundChannel)
statement?.setString(3, data.id) statement?.setString(3, data.id)
@@ -1055,12 +1062,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM ${super.dbName} ORDER BY description ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (description, SoundChannel, id, bp) SELECT description, SoundChannel, id, bp FROM ${super.dbName} ORDER BY description ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (description, SoundChannel, Box, Relay) SELECT description, SoundChannel, Box, Relay FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (description, SoundChannel, id, bp) SELECT description, SoundChannel, id, bp FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by description" as Any) Logger.info("${super.dbName} table resorted by description" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -1076,7 +1085,7 @@ class MariaDB(
val sheet = workbook.getSheet("BroadcastZones") val sheet = workbook.getSheet("BroadcastZones")
?: throw Exception("No sheet named 'BroadcastZones' found") ?: throw Exception("No sheet named 'BroadcastZones' found")
val headerRow = sheet.getRow(0) ?: throw Exception("No header row found") val headerRow = sheet.getRow(0) ?: throw Exception("No header row found")
val headers = arrayOf("Index", "description", "SoundChannel", "Box", "Relay") val headers = arrayOf("Index", "description", "SoundChannel", "id", "bp")
for ((colIndex, header) in headers.withIndex()) { for ((colIndex, header) in headers.withIndex()) {
val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found") val cell = headerRow.getCell(colIndex) ?: throw Exception("Header '$header' not found")
if (cell.stringCellValue != header) throw Exception("Header '$header' not found") if (cell.stringCellValue != header) throw Exception("Header '$header' not found")
@@ -1089,9 +1098,9 @@ class MariaDB(
val row = sheet.getRow(rowIndex) ?: continue val row = sheet.getRow(rowIndex) ?: continue
val description = row.getCell(1)?.stringCellValue ?: continue val description = row.getCell(1)?.stringCellValue ?: continue
val soundChannel = row.getCell(2)?.stringCellValue ?: continue val soundChannel = row.getCell(2)?.stringCellValue ?: continue
val box = row.getCell(3)?.stringCellValue ?: continue val id = row.getCell(3)?.stringCellValue ?: continue
val relay = row.getCell(4)?.stringCellValue ?: continue val bp = row.getCell(4)?.stringCellValue ?: continue
val broadcastZone = BroadcastZones(0u, description, soundChannel, box, relay) val broadcastZone = BroadcastZones(0u, description, soundChannel, id, bp)
_broadcastZonesList.add(broadcastZone) _broadcastZonesList.add(broadcastZone)
} }
return AddAll(_broadcastZonesList) return AddAll(_broadcastZonesList)
@@ -1108,7 +1117,7 @@ class MariaDB(
val workbook = XSSFWorkbook() val workbook = XSSFWorkbook()
val sheet = workbook.createSheet("BroadcastZones") val sheet = workbook.createSheet("BroadcastZones")
val headerRow = sheet.createRow(0) val headerRow = sheet.createRow(0)
val headers = arrayOf("Index", "description", "SoundChannel", "Box", "Relay") val headers = arrayOf("Index", "description", "SoundChannel", "id", "bp")
for ((colIndex, header) in headers.withIndex()) { for ((colIndex, header) in headers.withIndex()) {
val cell = headerRow.createCell(colIndex) val cell = headerRow.createCell(colIndex)
cell.setCellValue(header) cell.setCellValue(header)
@@ -1119,8 +1128,8 @@ class MariaDB(
row.createCell(0).setCellValue(resultSet.getString("index")) row.createCell(0).setCellValue(resultSet.getString("index"))
row.createCell(1).setCellValue(resultSet.getString("description")) row.createCell(1).setCellValue(resultSet.getString("description"))
row.createCell(2).setCellValue(resultSet.getString("SoundChannel")) row.createCell(2).setCellValue(resultSet.getString("SoundChannel"))
row.createCell(3).setCellValue(resultSet.getString("Box")) row.createCell(3).setCellValue(resultSet.getString("id"))
row.createCell(4).setCellValue(resultSet.getString("Relay")) row.createCell(4).setCellValue(resultSet.getString("bp"))
} }
for (i in headers.indices) { for (i in headers.indices) {
sheet.autoSizeColumn(i) sheet.autoSizeColumn(i)
@@ -1237,12 +1246,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language FROM ${super.dbName} ORDER BY `index` ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language FROM ${super.dbName} ORDER BY `index` ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language) SELECT Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, `Repeat`, Language FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by index" as Any) Logger.info("${super.dbName} table resorted by index" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -1396,12 +1407,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS) SELECT Date_Time, Source, Type, Message, SB_TAGS FROM ${super.dbName} ORDER BY `index` ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (Date_Time, Source, Type, Message, BroadcastZones) SELECT Date_Time, Source, Type, Message, BroadcastZones FROM ${super.dbName} ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, SB_TAGS) SELECT Date_Time, Source, Type, Message, SB_TAGS FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (Date_Time, Source, Type, Message, BroadcastZones) SELECT Date_Time, Source, Type, Message, BroadcastZones FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by index" as Any) Logger.info("${super.dbName} table resorted by index" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -1423,7 +1436,7 @@ class MariaDB(
val workbook = XSSFWorkbook() val workbook = XSSFWorkbook()
val sheet = workbook.createSheet("QueuePaging") val sheet = workbook.createSheet("QueuePaging")
val headerRow = sheet.createRow(0) val headerRow = sheet.createRow(0)
val headers = arrayOf("Index", "Date_Time", "Source", "Type", "Message", "SB_TAGS") val headers = arrayOf("Index", "Date_Time", "Source", "Type", "Message", "BroadcastZones")
for ((colIndex, header) in headers.withIndex()) { for ((colIndex, header) in headers.withIndex()) {
val cell = headerRow.createCell(colIndex) val cell = headerRow.createCell(colIndex)
cell.setCellValue(header) cell.setCellValue(header)
@@ -1436,7 +1449,7 @@ class MariaDB(
row.createCell(2).setCellValue(resultSet.getString("Source")) row.createCell(2).setCellValue(resultSet.getString("Source"))
row.createCell(3).setCellValue(resultSet.getString("Type")) row.createCell(3).setCellValue(resultSet.getString("Type"))
row.createCell(4).setCellValue(resultSet.getString("Message")) row.createCell(4).setCellValue(resultSet.getString("Message"))
row.createCell(5).setCellValue(resultSet.getString("SB_TAGS")) row.createCell(5).setCellValue(resultSet.getString("BroadcastZones"))
} }
for (i in headers.indices) { for (i in headers.indices) {
sheet.autoSizeColumn(i) sheet.autoSizeColumn(i)
@@ -1557,12 +1570,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (channel, ip) SELECT channel, ip FROM ${super.dbName} ORDER BY `index` ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (channel, ip) SELECT channel, ip FROM ${super.dbName} ORDER BY `index` ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (channel, ip) SELECT channel, ip FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (channel, ip) SELECT channel, ip FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by index" as Any) Logger.info("${super.dbName} table resorted by index" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -1770,19 +1785,21 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate( statement?.executeUpdate(
"INSERT INTO temp_${super.dbName} (datenya, timenya, machine, description) " + "INSERT INTO $tempdb_name (datenya, timenya, machine, description) " +
"SELECT datenya, timenya, machine, description FROM ${super.dbName} " + "SELECT datenya, timenya, machine, description FROM ${super.dbName} " +
"ORDER BY datenya , timenya , machine " "ORDER BY datenya , timenya , machine "
) )
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate( statement?.executeUpdate(
"INSERT INTO ${super.dbName} (datenya, timenya, machine, description) " + "INSERT INTO ${super.dbName} (datenya, timenya, machine, description) " +
"SELECT datenya, timenya, machine, description FROM temp_${super.dbName}" "SELECT datenya, timenya, machine, description FROM $tempdb_name"
) )
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by datenya, timenya, machine" as Any) Logger.info("${super.dbName} table resorted by datenya, timenya, machine" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -1947,12 +1964,14 @@ class MariaDB(
override fun Resort(): Boolean { override fun Resort(): Boolean {
try { try {
val statement = connection.createStatement() val statement = connection.createStatement()
val tempdb_name = "temp_${super.dbName}"
// use a temporary table to reorder the index // use a temporary table to reorder the index
statement?.executeUpdate("CREATE TABLE IF NOT EXISTS temp_${super.dbName} LIKE ${super.dbName}") statement?.executeUpdate("CREATE TABLE IF NOT EXISTS $tempdb_name LIKE ${super.dbName}")
statement?.executeUpdate("INSERT INTO temp_${super.dbName} (username, password, location, soundbank_tags, messagebank_ann_id, broadcastzones) SELECT username, password, location, soundbank_tags, messagebank_ann_id, broadcastzones FROM ${super.dbName} ORDER BY username ") statement?.executeUpdate("TRUNCATE TABLE $tempdb_name")
statement?.executeUpdate("INSERT INTO $tempdb_name (username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones) SELECT username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones FROM ${super.dbName} ORDER BY username ")
statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}") statement?.executeUpdate("TRUNCATE TABLE ${super.dbName}")
statement?.executeUpdate("INSERT INTO ${super.dbName} (username, password, location, soundbank_tags, messagebank_ann_id, broadcastzones) SELECT username, password, location, soundbank_tags, messagebank_ann_id, broadcastzones FROM temp_${super.dbName}") statement?.executeUpdate("INSERT INTO ${super.dbName} (username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones) SELECT username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones FROM $tempdb_name")
statement?.executeUpdate("DROP TABLE temp_${super.dbName}") statement?.executeUpdate("DROP TABLE $tempdb_name")
Logger.info("${super.dbName} table resorted by index" as Any) Logger.info("${super.dbName} table resorted by index" as Any)
// reload the local list // reload the local list
Get() Get()
@@ -2254,24 +2273,21 @@ class MariaDB(
* @return a list of Soundbank with Category City and matching tag * @return a list of Soundbank with Category City and matching tag
*/ */
fun Find_Soundbank_City(tag: String) : List<Soundbank>{ fun Find_Soundbank_City(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List return soundDB.List
.filter{ it.Category== Category.City.name } .filter{ it.Category== Category.City.name }
.filter { it.TAG.lowercase()==lowerTag} .filter { it.TAG.equals(tag,true)}
} }
fun Find_Soundbank_AirplaneName(tag: String) : List<Soundbank>{ fun Find_Soundbank_AirplaneName(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List return soundDB.List
.filter{ it.Category== Category.Airplane_Name.name } .filter{ it.Category== Category.Airplane_Name.name }
.filter { it.TAG.lowercase()==lowerTag} .filter { it.TAG.equals(tag,true)}
} }
fun Find_Soundbank_Places(tag: String) : List<Soundbank>{ fun Find_Soundbank_Places(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List return soundDB.List
.filter{ it.Category== Category.Places.name } .filter{ it.Category== Category.Places.name }
.filter { it.TAG.lowercase()==lowerTag} .filter { it.TAG.equals(tag,true)}
} }
fun Find_Soundbank_Shalat(tag: String) : List<Soundbank>{ fun Find_Soundbank_Shalat(tag: String) : List<Soundbank>{
@@ -2282,25 +2298,85 @@ class MariaDB(
} }
fun Find_Soundbank_Sequence(tag: String) : List<Soundbank>{ fun Find_Soundbank_Sequence(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List return soundDB.List
.filter{ it.Category== Category.Sequence.name } .filter{ it.Category== Category.Sequence.name }
.filter { it.TAG.lowercase()==lowerTag} .filter { it.TAG.equals(tag,true)}
} }
fun Find_Soundbank_Reason(tag: String) : List<Soundbank>{ fun Find_Soundbank_Reason(tag: String) : List<Soundbank>{
val lowerTag = tag.lowercase()
return soundDB.List return soundDB.List
.filter{ it.Category== Category.Reason.name } .filter{ it.Category== Category.Reason.name }
.filter { it.TAG.lowercase()==lowerTag} .filter { it.TAG.equals(tag,true)}
} }
fun Find_Soundbank_Procedure(tag: String) : List<Soundbank> { fun Find_Soundbank_Procedure(tag: String) : List<Soundbank> {
val lowerTag = tag.lowercase()
return soundDB.List return soundDB.List
.filter { it.Category == Category.Procedure.name } .filter { it.Category == Category.Procedure.name }
.filter { it.TAG.lowercase() == lowerTag } .filter { it.TAG.equals(tag,true) }
}
/**
* Get all distinct airline code tags from soundbank
* @return a list of distinct airline code tags sorted alphabetically
*/
fun Get_AirlineCode_Tags(): List<String> {
return soundDB.List
.filter { it.Category == Category.Airline_Code.name }
.map { it.TAG }
.distinct()
.sorted()
}
/**
* Get all distinct city tags from soundbank
* @return a list of distinct city tags sorted alphabetically
*/
fun Get_City_Tags(): List<String> {
return soundDB.List
.filter { it.Category == Category.City.name }
.map { it.TAG }
.distinct()
.sorted()
}
/**
* Get all distinct message ID from messagebank
* @return a list of distinct ANN_ID sorted numerically
*/
fun Get_MessageID_List(): List<UInt> {
return messageDB.List
.distinctBy { it.ANN_ID }
.map { it.ANN_ID }
.sorted()
}
/**
* Get all distinct broadcast zone descriptions from broadcastDB
* @return a list of distinct broadcast zone descriptions sorted alphabetically
*/
fun Get_BroadcastZone_List(): List<String> {
return broadcastDB.List
.distinctBy { it.description }
.map { it.description }
.sorted()
}
/**
* Get all distinct sound channel from soundchannelDB
* @return a list of distinct sound channel sorted alphabetically
*/
fun Get_SoundChannel_List(): List<String> {
return soundchannelDB.List
.distinctBy { it.channel }
.map { it.channel }
.sorted()
}
/**
* Check if a username already exists in the userDB (case-insensitive)
*/
fun Username_exists(username: String): Boolean {
return userDB.List.any { it.username.equals(username, ignoreCase = true) }
} }

View File

@@ -8,4 +8,27 @@ data class Messagebank(
var Voice_Type: String, var Voice_Type: String,
var Message_Detail: String, var Message_Detail: String,
var Message_TAGS: String var Message_TAGS: String
) ) {
/**
* Check if all fields are not empty and ANN_ID > 0
*/
fun isNotEmpty(): Boolean{
if (Description.isNotEmpty()){
if (Language.isNotEmpty()){
if (Voice_Type.isNotEmpty()){
if (Message_Detail.isNotEmpty()){
if (Message_TAGS.isNotEmpty()){
return true
}
}
}
}
}
return false
}
override fun toString(): String {
return "Messagebank(index=$index, Description='$Description', Language='$Language', ANN_ID=$ANN_ID, Voice_Type='$Voice_Type', Message_Detail='$Message_Detail', Message_TAGS='$Message_TAGS')"
}
}

View File

@@ -1,4 +1,13 @@
package database package database
@Suppress("unused") @Suppress("unused")
data class QueueFids(var index: UInt, var ALCODE: String, var FLNUM: String, var ORIGIN: String, var ETAD: String, var FREMARK: String) data class QueueFids(var index: UInt, var ALCODE: String, var FLNUM: String, var ORIGIN: String, var ETAD: String, var FREMARK: String){
fun isNotEmpty(): Boolean {
return ALCODE.isNotEmpty() && FLNUM.isNotEmpty() && ORIGIN.isNotEmpty() && ETAD.isNotEmpty() && FREMARK.isNotEmpty()
}
override fun toString(): String {
return "QueueFids(index=$index, ALCODE='$ALCODE', FLNUM='$FLNUM', ORIGIN='$ORIGIN', ETAD='$ETAD', FREMARK='$FREMARK')"
}
}

View File

@@ -1,6 +1,10 @@
package database package database
data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String){ data class QueuePaging(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var BroadcastZones: String){
fun isNotEmpty(): Boolean {
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && BroadcastZones.isNotEmpty()
}
override fun toString(): String { override fun toString(): String {
return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')" return "QueuePaging(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', BroadcastZones='$BroadcastZones')"
} }

View File

@@ -2,6 +2,12 @@ package database
data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String){ data class QueueTable(var index: UInt, var Date_Time: String, var Source: String, var Type: String, var Message: String, var SB_TAGS: String, var BroadcastZones: String, var Repeat: UInt, var Language: String){
/**
* Check if all fields are not empty
*/
fun isNotEmpty(): Boolean {
return Date_Time.isNotEmpty() && Source.isNotEmpty() && Type.isNotEmpty() && Message.isNotEmpty() && SB_TAGS.isNotEmpty() && BroadcastZones.isNotEmpty() && Language.isNotEmpty()
}
override fun toString(): String { override fun toString(): String {
return "QueueTable(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', SB_TAGS='$SB_TAGS', BroadcastZones='$BroadcastZones', Repeat=$Repeat, Language='$Language')" return "QueueTable(index=$index, Date_Time='$Date_Time', Source='$Source', Type='$Type', Message='$Message', SB_TAGS='$SB_TAGS', BroadcastZones='$BroadcastZones', Repeat=$Repeat, Language='$Language')"
} }

View File

@@ -10,4 +10,13 @@ data class ScheduleBank(
var Repeat: UByte, var Repeat: UByte,
var Enable: Boolean, var Enable: Boolean,
var BroadcastZones: String, var BroadcastZones: String,
var Language: String) var Language: String) {
override fun toString(): String {
return "ScheduleBank(index=$index, Description='$Description', Day='$Day', Time='$Time', Soundpath='$Soundpath', Repeat=$Repeat, Enable=$Enable, BroadcastZones='$BroadcastZones', Language='$Language')"
}
fun isNotEmpty() : Boolean{
return Description.isNotEmpty() && Day.isNotEmpty() && Time.isNotEmpty() && Soundpath.isNotEmpty() && BroadcastZones.isNotEmpty() && Language.isNotEmpty()
}
}

View File

@@ -1,4 +1,17 @@
package database package database
@Suppress("unused") data class SoundChannel(val index: UInt, val channel: String, val ip: String) {
data class SoundChannel(val index: UInt, val channel: String, val ip: String)
override fun toString(): String {
return "SoundChannel(index=$index, channel='$channel', ip='$ip')"
}
/**
* Check if both channel and ip are not empty
* @return true if both channel and ip are not empty, false otherwise
*/
fun isNotEmpty() : Boolean {
return channel.isNotEmpty() && ip.isNotEmpty()
}
}

View File

@@ -1,5 +1,6 @@
package database package database
data class Soundbank( data class Soundbank(
var index: UInt, var index: UInt,
var Description: String, var Description: String,
@@ -8,4 +9,32 @@ data class Soundbank(
var Language: String, var Language: String,
var VoiceType: String, var VoiceType: String,
var Path: String, var Path: String,
) ){
/**
* Check if all fields are not empty
*/
fun isNotEmpty(): Boolean{
return Description.isNotEmpty() && TAG.isNotEmpty() && Category.isNotEmpty() && Language.isNotEmpty() && VoiceType.isNotEmpty() && Path.isNotEmpty()
}
/**
* Check if Category is valid
*/
fun ValidCategory() : Boolean{
return content.Category.entries.any{ it.name == this.Category}
}
/**
* Check if VoiceType is valid
*/
fun ValidVoiceType() : Boolean{
return content.VoiceType.entries.any{ it.name == this.VoiceType}
}
/**
* Check if Language is valid
*/
fun ValidLanguage() : Boolean{
return content.Language.entries.any{ it.name == this.Language}
}
}

View File

@@ -5,6 +5,26 @@ data class UserDB(var index: UInt, var username: String, var password: String, v
override fun toString(): String { override fun toString(): String {
return "UserDB(index=$index, username='$username', location='$location', airline_tags='$airline_tags', city_tags='$city_tags', messagebank_ann_id='$messagebank_ann_id', broadcastzones='$broadcastzones')" return "UserDB(index=$index, username='$username', location='$location', airline_tags='$airline_tags', city_tags='$city_tags', messagebank_ann_id='$messagebank_ann_id', broadcastzones='$broadcastzones')"
} }
fun isNotEmpty() : Boolean {
return username.isNotEmpty() && password.isNotEmpty() && location.isNotEmpty() && airline_tags.isNotEmpty() && city_tags.isNotEmpty() && messagebank_ann_id.isNotEmpty() && broadcastzones.isNotEmpty()
}
/**
* Compares this UserDB object with another UserDB object for equality.
* Two UserDB objects are considered equal if all their properties are equal, except for the index property.
*/
fun isEqual(other: UserDB?): Boolean {
if (other == null) return false
return this.username == other.username &&
this.password == other.password &&
this.location == other.location &&
this.airline_tags == other.airline_tags &&
this.city_tags == other.city_tags &&
this.messagebank_ann_id == other.messagebank_ann_id &&
this.broadcastzones == other.broadcastzones
}
} }

266
src/toa/Vx3K.kt Normal file
View File

@@ -0,0 +1,266 @@
package toa
import codes.Somecodes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.tinylog.Logger
import java.net.Inet4Address
import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.function.BiConsumer
/**
* VX3K Protocol
* @param ipaddress IP address of the VX3K device, default to 192.168.14.1
* @param port Port number of the VX3K device, from 50050-50053 default to 50053
*/
class Vx3K(val ipaddress : String = "192.168.14.1", val port : Int = 50053) {
private val remotesocket : InetSocketAddress
init{
if (port !in 50050..50053){
throw IllegalArgumentException("Port number must be between 50050 and 50053")
}
try{
val inet = Inet4Address.getByName(ipaddress)
remotesocket = InetSocketAddress(inet, port)
} catch (_ : Exception){
throw IllegalArgumentException("Invalid IP address: $ipaddress")
}
}
/**
* Connect to the VX3K device
* @param timeout Connection timeout in milliseconds, default to 30000 ms
*/
fun Connect(timeout: Int = 30000){
try{
val socket = Socket()
// read timeout 5 seconds
socket.soTimeout = 5000
socket.connect(remotesocket, timeout)
} catch (e : Exception){
Logger.error { "Failed to connect with ${remotesocket.hostName}:${remotesocket.port}, Message: ${e.message}" }
}
}
/**
* Virtual Contact Input (Commmand 0x1001)
* @param ID : Device ID for VX3K, range 0 - 31
* @param CIN : Contact Input Number, range 0 - 15 for normal terminal, 16 for emergency contact input1, 17 for emergency contact input2
* @param isON : true for ON, false for OFF
* @param cb : Callback function with parameters (success: Boolean, message: String)
*/
fun VirtualCIN(ID: Short, CIN: Short, isON: Boolean, cb : BiConsumer<Boolean, String>){
val commandID = 0x1001.toShort()
if (ID !in 0..31){
cb.accept(false, "ID must be between 0 and 31")
return
}
if (CIN !in 0..17){
cb.accept(false, "CIN must be between 0 and 17")
return
}
val payload = ByteBuffer.allocate(6).order(ByteOrder.BIG_ENDIAN)
payload.putShort(ID)
payload.putShort(CIN)
payload.putShort(if (isON) 1 else 0)
val command = Make_Request_Command(commandID, payload.array())
Send_Receive(command,8){
success, reply ->
if (success){
val bb = ByteBuffer.wrap(reply).order(ByteOrder.BIG_ENDIAN)
val resp_commandID = bb.short
val resp_code = bb.short
if (resp_commandID==commandID){
if (resp_code.toInt() == 0){
cb.accept(true, "Virtual CIN command sent successfully to ${remotesocket.hostName}:${remotesocket.port}")
} else {
cb.accept(false, "Virtual CIN command failed with response code $resp_code from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Invalid response command ID $resp_commandID from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Failed to send Virtual CIN command to ${remotesocket.hostName}:${remotesocket.port}")
}
}
}
/**
* Open / Close Audio Input in Broadcast Pattern (Command 0x1003 for old firmware, 0x1101 for new firmware)
* @param NewFirmware Set to true if using new firmware version
* @param ID Device ID for VX3K, range 0 - 39 for new firmware, 0 - 31 for old firmware
* @param Channel Audio Input Channel, range 0 - 3
* @param BroadcastZones Vx3K_BroadcastZone object with selected broadcast zones
* @param cb Callback function with parameters (success: Boolean, message: String)
*/
fun AudioInput_BroadcastPattern(NewFirmware : Boolean, ID: Short, Channel: Short, BroadcastZones: Vx3K_BroadcastZone, cb: BiConsumer<Boolean, String>){
val CommandID = if (NewFirmware) 0x1101.toShort() else 0x1003.toShort()
if (NewFirmware){
if (ID !in 0..39){
cb.accept(false, "ID must be between 0 and 39")
return
}
} else {
if (ID !in 0..31){
cb.accept(false, "ID must be between 0 and 31")
return
}
}
if (Channel !in 0..3){
cb.accept(false, "Channel must be between 0 and 3")
return
}
val payload = ByteBuffer.allocate(if (NewFirmware) 86 else 70).order(ByteOrder.BIG_ENDIAN)
// Audio Input = 1
payload.putShort(1)
// VX3K ID
payload.putShort(ID)
// Audio Input Channel
payload.putShort(Channel)
// Broadcast Pattern Zones
payload.put(BroadcastZones.payload)
val command = Make_Request_Command(CommandID, payload.array())
Send_Receive(command,8){
success, reply ->
if (success){
val bb = ByteBuffer.wrap(reply).order(ByteOrder.BIG_ENDIAN)
val resp_commandID = bb.short
val resp_code = bb.short
if (resp_commandID==CommandID){
if (resp_code.toInt() == 0){
cb.accept(true, "Open Audio Input Broadcast command sent successfully to ${remotesocket.hostName}:${remotesocket.port}")
} else {
cb.accept(false, "Open Audio Input Broadcast command failed with response code $resp_code from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Invalid response command ID $resp_commandID from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Failed to send Open Audio Input Broadcast command to ${remotesocket.hostName}:${remotesocket.port}")
}
}
}
/**
* Open / Close Network Broadcast Pattern (Command 0x1102 for old firmware, 0x1104 for new firmware)
* @param NewFirmware Set to true if using new firmware version
* @param multicastIP Multicast IP address in string format, from 224.0.0.0 ~ 239.255.255.255, default to 224.0.0.1
* @param port UDP Port number, port 5000-5255 is invalid, default to 5300
* @param priority Broadcast Priority value, range 1 - 1024, default to 512 (to be checked later)
* @param isBGM true for BGM, false for Paging
* @param SSRC SSRC value, range 1 - 65535, default to 1 (to be checked later)
* @param payloadType RTP Payload Type, range 0 - 127, default to 0 (to be checked later)
* @param payloadSize RTP Payload Size, range 0 - 1500, default to 1000 (to be checked later)
* @param BroadcastZones Vx3K_BroadcastZone object with selected broadcast zones
* @param cb Callback function with parameters (success: Boolean, message: String)
*/
fun Network_BroadcastPattern(NewFirmware: Boolean,multicastIP: String = "224.0.0.1", port: Int = 5300, priority: Int = 512, isBGM: Boolean, SSRC: UShort = 1u, payloadType: Short = 0, payloadSize: Short = 1000, BroadcastZones: Vx3K_BroadcastZone, cb:BiConsumer<Boolean, String>){
val CommandID = if (NewFirmware) 0x1104.toShort() else 0x1102.toShort()
if (Somecodes.ValidIPV4(multicastIP)){
val inet = Inet4Address.getByName(multicastIP)
if (inet.isMulticastAddress){
if (port in 5256..65535){
if (priority in 1..1024){
if (SSRC in 1u..65535u){
if (payloadType in 0..127){
if (payloadSize in 0..1500){
val jitterbuffer = 1000 //TODO 32 signed integer, to be checked later
val payload = ByteBuffer.allocate(if (NewFirmware) 100 else 84)
payload.put(inet.address)
payload.putShort(port.toShort())
payload.putShort(priority.toShort())
payload.putShort(if (isBGM) 1 else 0)
payload.putShort(SSRC.toShort())
payload.putShort(payloadType)
payload.putShort(payloadSize)
payload.putInt(jitterbuffer)
payload.put(BroadcastZones.payload)
val command = Make_Request_Command(CommandID, payload.array())
Send_Receive(command,10){
success, reply ->
if (success){
val bb = ByteBuffer.wrap(reply).order(ByteOrder.BIG_ENDIAN)
val resp_commandID = bb.short
val resp_code = bb.short
// buang 2 short
bb.short
bb.short
val sound_source_number = bb.short
if (resp_commandID==CommandID){
if (resp_code.toInt() == 0){
cb.accept(true, "Open Network Broadcast command sent successfully to ${remotesocket.hostName}:${remotesocket.port}, Sound Source Number: $sound_source_number")
} else {
cb.accept(false, "Open Network Broadcast command failed with response code $resp_code from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Invalid response command ID $resp_commandID from ${remotesocket.hostName}:${remotesocket.port}")
}
} else {
cb.accept(false, "Failed to send Open Network Broadcast command to ${remotesocket.hostName}:${remotesocket.port}")
}
}
} else cb.accept(false, "Payload Size must be between 0 and 1500")
} else cb.accept(false, "Payload Type must be between 0 and 127")
} else cb.accept(false, "SSRC must be between 1 and 65535")
} else cb.accept(false, "Priority must be between 1 and 1024")
} else cb.accept(false, "Port must be greater than 5255 and less than 65536")
} else cb.accept(false, "multicastIP must between 224.0.0.0 ~ 239.255.255.255")
} else cb.accept(false, "Multicast IP address invalid")
}
/**
* Send command and receive reply from VX3K device
* @param command Command byte array to send
* @param expectedlength Expected length of the reply byte array
* @param cb Callback function with parameters (success: Boolean, reply: ByteArray)
*/
private fun Send_Receive(command: ByteArray, expectedlength: Int, cb : BiConsumer<Boolean, ByteArray>){
CoroutineScope(Dispatchers.IO).launch {
try{
val tcp = Socket()
tcp.soTimeout = 5000
tcp.connect(remotesocket, 30000)
val outstream = tcp.getOutputStream()
val instream = tcp.getInputStream()
outstream.write(command)
outstream.flush()
val reply = ByteArray(expectedlength)
instream.read(reply)
outstream.close()
instream.close()
tcp.close()
cb.accept(true, reply)
} catch (_: Exception){
cb.accept(false, ByteArray(0))
}
}
}
/**
* Wrap payload into VX3K Request Command format
* @param commandID Command ID
* @param Payload Payload data
* @return ByteArray of the complete command
*/
private fun Make_Request_Command(commandID: Short, Payload: ByteArray) : ByteArray {
val result = ByteBuffer.allocate(8+Payload.size).order(ByteOrder.BIG_ENDIAN)
// command ID (2 bytes)
result.putShort(commandID)
// Response code (2 bytes), set 0 for Request
result.putShort(0)
// command length = command header (8 bytes) + payload size
result.putShort((Payload.size+8).toShort())
// flag and reserver = 0 (2 bytes)
result.putShort(0)
result.put(Payload)
// read to byte array
return result.array()
}
}

View File

@@ -0,0 +1,67 @@
package toa
import kotlin.experimental.and
import kotlin.experimental.or
/**
* VX3K Broadcast Zone Configuration
* Old Firmware: Broadcast Zone 1 - 512
* New Firmware: Broadcast Zone 1 - 640
* @param NewFirmware Set to true if using new firmware version
*/
@Suppress("unused")
class Vx3K_BroadcastZone(val NewFirmware: Boolean = false) {
val payload: ByteArray = if (NewFirmware) ByteArray(80) else ByteArray(64)
/**
* Set a zone as active
* @param zonenumber Zone number to set (1 to 512 for old firmware, 1 to 640 for new firmware)
*/
fun SetZone(zonenumber: Int){
if (zonenumber<1) throw Exception("Minimum zone number is 1")
if (NewFirmware){
if (zonenumber>640) throw Exception("Maximum zone number is 640 for new firmware")
} else {
if (zonenumber>512) throw Exception("Maximum zone number is 512 for old firmware")
}
val byteIndex = (zonenumber - 1) / 8
val bitIndex = (zonenumber - 1) % 8
payload[byteIndex] = payload[byteIndex] or (1 shl bitIndex).toByte()
}
/**
* Clear a zone (set as inactive)
* @param zonenumber Zone number to clear (1 to 512 for old firmware, 1 to 640 for new firmware)
*/
fun ClearZone(zonenumber: Int){
if (zonenumber<1) throw Exception("Minimum zone number is 1")
if (NewFirmware){
if (zonenumber>640) throw Exception("Maximum zone number is 640 for new firmware")
} else {
if (zonenumber>512) throw Exception("Maximum zone number is 512 for old firmware")
}
val byteIndex = (zonenumber - 1) / 8
val bitIndex = (zonenumber - 1) % 8
payload[byteIndex] = payload[byteIndex] and ((1 shl bitIndex).inv().toByte())
}
/**
* Set all zones as active
*/
fun SetAllZones(){
for (i in payload.indices){
payload[i] = 0xFF.toByte()
}
}
/**
* Clear all zones (set all as inactive)
*/
fun ClearAllZones(){
for (i in payload.indices){
payload[i] = 0
}
}
}

View File

@@ -0,0 +1,4 @@
package web
class KeyValueMessage(val key: String, val value: String) {
}

View File

@@ -0,0 +1,9 @@
package web
class StreamerOutputData(val index: UInt, val channel: String, val ipaddress: String, val vu: Int, val bufferRemain: Int, var isPlaying: Boolean) {
companion object{
fun fromBarixConnection(bc: barix.BarixConnection): StreamerOutputData {
return StreamerOutputData(bc.index, bc.channel, bc.ipaddress, bc.vu, bc.bufferRemain, bc.isPlaying())
}
}
}

View File

@@ -2,6 +2,8 @@ package web
import StreamerOutputs import StreamerOutputs
import codes.Somecodes import codes.Somecodes
import codes.Somecodes.Companion.GetSensorsInfo
import codes.Somecodes.Companion.GetUptime
import codes.Somecodes.Companion.ListAudioFiles import codes.Somecodes.Companion.ListAudioFiles
import codes.Somecodes.Companion.ValiDateForLogHtml import codes.Somecodes.Companion.ValiDateForLogHtml
import codes.Somecodes.Companion.ValidFile import codes.Somecodes.Companion.ValidFile
@@ -9,13 +11,21 @@ import codes.Somecodes.Companion.ValidIPV4
import codes.Somecodes.Companion.ValidScheduleDay import codes.Somecodes.Companion.ValidScheduleDay
import codes.Somecodes.Companion.ValidScheduleTime import codes.Somecodes.Companion.ValidScheduleTime
import codes.Somecodes.Companion.ValidString import codes.Somecodes.Companion.ValidString
import codes.Somecodes.Companion.ValidStrings
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import content.Category import content.Category
import content.Language import content.Language
import content.ScheduleDay import content.ScheduleDay
import content.VoiceType import content.VoiceType
import database.* import database.BroadcastZones
import database.LanguageLink
import database.MariaDB
import database.Messagebank
import database.ScheduleBank
import database.SoundChannel
import database.Soundbank
import database.UserDB
import db import db
import io.javalin.Javalin import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.before import io.javalin.apibuilder.ApiBuilder.before
@@ -29,6 +39,7 @@ import io.javalin.http.Context
import io.javalin.json.JavalinJackson import io.javalin.json.JavalinJackson
import io.javalin.websocket.WsMessageContext import io.javalin.websocket.WsMessageContext
import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.apache.poi.xssf.usermodel.XSSFWorkbook
import java.nio.file.Files
import java.time.LocalDateTime import java.time.LocalDateTime
@Suppress("unused") @Suppress("unused")
@@ -78,7 +89,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} }
// Set user session // Set user session
it.sessionAttribute("user", user.first) it.sessionAttribute("user", user.first)
println("User ${user.first} logged in") //println("User ${user.first} logged in")
// Redirect to home page // Redirect to home page
it.redirect("home.html") it.redirect("home.html")
} }
@@ -88,28 +99,32 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
before { CheckUsers(it) } before { CheckUsers(it) }
ws("/ws") { ws -> ws("/ws") { ws ->
// WebSocket endpoint for home // WebSocket endpoint for home
ws.onClose { wsCloseContext -> //
// TODO Handle WebSocket close event
println("WebSocket closed: ${wsCloseContext.session.remoteAddress}")
}
ws.onMessage { wsMessageContext -> ws.onMessage { wsMessageContext ->
try { try {
val cmd = val cmd =
objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java) objectmapper.readValue(wsMessageContext.message(), WebsocketCommand::class.java)
when (cmd.command) { when (cmd.command) {
"getSystemTime" -> { "getSystemTime" -> {
val systemtime = LocalDateTime.now().format(Somecodes.datetimeformat1)
val uptime = GetUptime()
SendReply( SendReply(
wsMessageContext, wsMessageContext,
cmd.command, cmd.command,
LocalDateTime.now().format(Somecodes.datetimeformat1) if (uptime.isNotEmpty()) "Date & Time : $systemtime\nSystem Uptime : $uptime" else "Date & Time : $systemtime"
) )
} }
"getCPUStatus" -> { "getCPUStatus" -> {
Somecodes.getCPUUsage { vv -> Somecodes.getCPUUsage { vv ->
val sv = GetSensorsInfo()
if (sv.isNotEmpty()){
SendReply(wsMessageContext, cmd.command, vv+"\n"+sv)
} else {
SendReply(wsMessageContext, cmd.command, vv) SendReply(wsMessageContext, cmd.command, vv)
} }
} }
}
"getMemoryStatus" -> { "getMemoryStatus" -> {
SendReply(wsMessageContext, cmd.command, Somecodes.getMemoryUsage()) SendReply(wsMessageContext, cmd.command, Somecodes.getMemoryUsage())
@@ -120,9 +135,9 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} }
"getNetworkStatus" -> { "getNetworkStatus" -> {
// TODO Get Network status Somecodes.GetNetworkStatus { nn ->
Somecodes.GetNetworkStatus("") SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(nn))
SendReply(wsMessageContext, cmd.command, "OK") }
} }
"getPagingQueue" ->{ "getPagingQueue" ->{
@@ -134,7 +149,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} }
"getStreamerOutputs" -> { "getStreamerOutputs" -> {
SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(StreamerOutputs.values.toList())) val reply : List<StreamerOutputData> = StreamerOutputs.map { so -> StreamerOutputData.fromBarixConnection(so.value) }
SendReply(wsMessageContext, cmd.command, objectmapper.writeValueAsString(reply))
} }
else -> { else -> {
@@ -146,10 +162,9 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} }
} }
ws.onConnect { wsConnectContext -> // ws.onConnect { wsConnectContext ->
// TODO Handle WebSocket connect event // println("WebSocket connected: ${wsConnectContext.session.remoteAddress}")
println("WebSocket connected: ${wsConnectContext.session.remoteAddress}") // }
}
} }
} }
@@ -192,43 +207,99 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
it.result(objectmapper.writeValueAsString(ScheduleDay.entries.map { day -> day.toString() })) it.result(objectmapper.writeValueAsString(ScheduleDay.entries.map { day -> day.toString() }))
} }
} }
path("ListFiles/{Language}/{VoiceType}/{Category}") {
get { ctx ->
val language = ctx.pathParam("Language")
val voiceType = ctx.pathParam("VoiceType")
val category = ctx.pathParam("Category")
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
if (ValidString(voiceType) && VoiceType.entries.any { vt -> vt.name == voiceType }) {
if (ValidString(category) && Category.entries.any { cat -> cat.name == category }) {
val dir = Somecodes.SoundbankDirectory(
Language.valueOf(language),
VoiceType.valueOf(voiceType),
Category.valueOf(category)
)
if (Files.isDirectory(dir)){
val list = ListAudioFiles(dir)
ctx.result(objectmapper.writeValueAsString(list))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Directory does not exist")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
} else ctx.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
}
}
path("SoundBank") { path("SoundBank") {
get("List") { get("List") {
it.result(MariaDB.ArrayListtoString(db.soundDB.List)) it.result(MariaDB.ArrayListtoString(db.soundDB.List))
} }
get("ListFiles") { get("ListFiles") {
it.result(objectmapper.writeValueAsString(ListAudioFiles("C:\\soundbank"))) it.result(objectmapper.writeValueAsString(ListAudioFiles(Somecodes.Soundbank_directory)))
}
get("GetPhrases/{Language}/{VoiceType}"){
val language = it.pathParam("Language")
val voiceType = it.pathParam("VoiceType")
if (ValidString(language) && Language.entries.any { lang -> lang.name == language }) {
if (ValidString(voiceType) && VoiceType.entries.any { vt -> vt.name == voiceType }) {
val phrases = db.soundDB.List
.filter { sb -> sb.Language == language }
.filter {sb -> sb.VoiceType == voiceType}
.filter { sb -> sb.Category == Category.Phrase.name}
.distinctBy { sb -> sb.TAG }
.sortedBy { sb -> sb.TAG }
it.result(objectmapper.writeValueAsString(phrases))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
} else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
}
get("AirlineTags") { ctx ->
val value = db.soundDB.List
.filter { it.Category == Category.Airplane_Name.name }
.distinctBy { it.TAG }
.sortedBy { it.TAG }
.map { KeyValueMessage(it.TAG, it.Description) }
ctx.result(objectmapper.writeValueAsString(value))
}
get("CityTags") { ctx ->
val value = db.soundDB.List
.filter { it.Category == Category.City.name }
.distinctBy { it.TAG }
.sortedBy { it.TAG }
.map { KeyValueMessage(it.TAG, it.Description) }
ctx.result(objectmapper.writeValueAsString(value))
} }
post("Add") { post("Add") {
try { try {
val addvalue = objectmapper.readValue(it.body(), Soundbank::class.java) val addvalue = objectmapper.readValue(it.body(), Soundbank::class.java)
if (ValidString(addvalue.Description)) { if (addvalue.isNotEmpty()){
if (ValidString(addvalue.TAG)) { // check apakah TAG sudah ada untuk language, category dan voicetype yang sama
if (ValidString(addvalue.Category)) {
if (ValidString(addvalue.Language)) {
if (ValidString(addvalue.Path)) {
// check apakah TAG sudah ada untuk language dan category yang sama
val exists = db.soundDB.List.any { sb -> val exists = db.soundDB.List.any { sb ->
sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category sb.TAG == addvalue.TAG && sb.Language == addvalue.Language && sb.Category == addvalue.Category && sb.VoiceType == addvalue.VoiceType
} }
if (!exists) { if (!exists) {
if (ValidFile(addvalue.Path)) { val absolutepath = Somecodes.SoundbankDirectory(Language.valueOf(addvalue.Language), VoiceType.valueOf(addvalue.VoiceType), Category.valueOf(addvalue.Category)).resolve(addvalue.Path)
if (Files.isRegularFile(absolutepath)) {
addvalue.Path = absolutepath.toAbsolutePath().toString()
if (db.soundDB.Add(addvalue)) { if (db.soundDB.Add(addvalue)) {
db.soundDB.Resort() db.soundDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Added Sound Bank: $addvalue")
} else it.status(500) } else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to add soundbank to database"))) .result(objectmapper.writeValueAsString(resultMessage("Failed to add soundbank to database")))
} else it.status(400) } else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid Path, file does not exist"))) .result(objectmapper.writeValueAsString(resultMessage("Invalid Path, file does not exist")))
} else it.status(400) } else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("TAG=${addvalue.TAG} already exists for the same Language=${addvalue.Language} and Category=${addvalue.Category}"))) .result(objectmapper.writeValueAsString(resultMessage("TAG=${addvalue.TAG} already exists for the same Language=${addvalue.Language} and Category=${addvalue.Category}")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Path"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Some fields are empty")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Category"))) } catch (e: Exception) {
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid TAG"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid request body, Message: ${e.message}")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Description")))
} catch (_: Exception) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid request body")))
} }
} }
delete("List") { delete("List") {
@@ -236,6 +307,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.soundDB.Clear()) { if (db.soundDB.Clear()) {
db.soundDB.Get() db.soundDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear Sound Bank table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate soundbank table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate soundbank table")))
} }
@@ -249,6 +321,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.soundDB.DeleteByIndex(index.toInt())) { if (db.soundDB.DeleteByIndex(index.toInt())) {
db.soundDB.Resort() db.soundDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted Sound Bank with index $index")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete soundbank with index $index"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete soundbank with index $index")))
} }
@@ -257,68 +330,79 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
patch("UpdateByIndex/{index}") { patch("UpdateByIndex/{index}") {
// update by index // update by index
val index = it.pathParam("index").toUIntOrNull() val index = it.pathParam("index").toUIntOrNull()
if (index == null) { if (index!=null){
// tidak ada path param index
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
val sb = db.soundDB.List.find { xx -> xx.index == index } val sb = db.soundDB.List.find { xx -> xx.index == index }
if (sb == null) { if (sb!=null){
// soundbank dengan index tersebut tidak ditemukan try{
it.status(404).result(objectmapper.writeValueAsString(resultMessage("Soundbank with index $index not found"))) //println("index=$index, body=${it.body()}")
} else { val newsb = objectmapper.readValue(it.body(), Soundbank::class.java)
// soundbank dengan index tersebut ditemukan, sekarang update //println("newsb=$newsb")
val json: JsonNode = objectmapper.readTree(it.body()) if (newsb.isNotEmpty()){
if (json.isEmpty) { var varchanged = false
it.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body"))) if (newsb.Description != sb.Description) {
} else { sb.Description = newsb.Description
val _description = json.get("Description").asText("") varchanged = true
val _tag = json.get("TAG").asText("")
val _category = json.get("Category").asText("")
val _language = json.get("Language").asText("")
val _path = json.get("Path").asText("")
var changed = false
if (ValidString(_description) && _description != sb.Description) {
sb.Description = _description
changed = true
} }
if (ValidString(_tag) && _tag != sb.TAG) { if (newsb.TAG != sb.TAG) {
sb.TAG = _tag sb.TAG = newsb.TAG
changed = true varchanged = true
}
if (newsb.ValidCategory()) {
if (newsb.Category != sb.Category){
sb.Category = newsb.Category
varchanged = true
} }
if (ValidString(_category) && _category != sb.Category) {
if (Category.entries.any { cat ->
cat.name == _category
}) {
sb.Category = _category
changed = true
} else { } else {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Category"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Category")))
return@patch return@patch
} }
if (newsb.ValidLanguage()) {
if (newsb.Language != sb.Language){
sb.Language = newsb.Language
varchanged = true
} }
if (ValidString(_language) && _language != sb.Language) {
sb.Language = _language
changed = true
}
if (ValidString(_path) && _path != sb.Path) {
if (ValidFile(_path)) {
sb.Path = _path
changed = true
} else { } else {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Path, file does not exist"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
return@patch return@patch
} }
if (newsb.ValidVoiceType()) {
if (newsb.VoiceType != sb.VoiceType){
sb.VoiceType = newsb.VoiceType
varchanged = true
} }
if (changed) { } else {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid VoiceType")))
return@patch
}
val newpath = Somecodes.SoundbankDirectory(newsb.Language, newsb.VoiceType, newsb.Category).resolve(newsb.Path)
if (Files.isRegularFile(newpath)){
if (newpath.toAbsolutePath().toString() != sb.Path) {
sb.Path = newpath.toAbsolutePath().toString()
varchanged = true
}
} else {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid path")))
return@patch
}
if (varchanged) {
//println("Some fields changed for index=$index, updating...")
if (db.soundDB.UpdateByIndex(index.toInt(), sb)) { if (db.soundDB.UpdateByIndex(index.toInt(), sb)) {
db.soundDB.Resort() db.soundDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Updated Soundbank $sb")
} else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to update soundbank with index $index"))) } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to update soundbank with index $index")))
} else it.status(400) } else it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for soundbank with index $index"))) .result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for soundbank with index $index")))
} else Exception("Some fields are empty")
} catch(e: Exception){
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Unable to parse request body to Soundbank, Message: ${e.message}")))
} }
}
} } else it.status(404).result(objectmapper.writeValueAsString(resultMessage("Soundbank with index $index not found")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} }
get("ExportXLSX") { get("ExportXLSX") {
val xlsxdata = db.soundDB.Export_XLSX() val xlsxdata = db.soundDB.Export_XLSX()
@@ -359,6 +443,13 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
// get messagebank list // get messagebank list
it.result(MariaDB.ArrayListtoString(db.messageDB.List)) it.result(MariaDB.ArrayListtoString(db.messageDB.List))
} }
get("MessageIDs") { ctx ->
val value = db.messageDB.List
.distinctBy { it.ANN_ID }
.sortedBy { it.ANN_ID }
.map { KeyValueMessage(it.ANN_ID.toString(), it.Description) }
ctx.result(objectmapper.writeValueAsString(value))
}
post("Add") { post("Add") {
val json: JsonNode = objectmapper.readTree(it.body()) val json: JsonNode = objectmapper.readTree(it.body())
val description = json.get("Description")?.asText("") ?: "" val description = json.get("Description")?.asText("") ?: ""
@@ -377,6 +468,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.messageDB.Add(mb)){ if (db.messageDB.Add(mb)){
db.messageDB.Resort() db.messageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Added Messagebank: $mb")
} else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add messagebank to database"))) } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add messagebank to database")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Message_TAGS"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Message_TAGS")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Message_Detail"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Message_Detail")))
@@ -390,6 +482,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.messageDB.Clear()) { if (db.messageDB.Clear()) {
db.messageDB.Get() db.messageDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear Message Bank table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate messagebank table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate messagebank table")))
} }
@@ -403,6 +496,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.messageDB.DeleteByIndex(index.toInt())) { if (db.messageDB.DeleteByIndex(index.toInt())) {
db.messageDB.Resort() db.messageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted Message Bank with index $index")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete messagebank with index $index"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete messagebank with index $index")))
} }
@@ -463,6 +557,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.messageDB.UpdateByIndex(index.toInt(), mb)) { if (db.messageDB.UpdateByIndex(index.toInt(), mb)) {
db.messageDB.Resort() db.messageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Updated Messagebank $mb")
} else it.status(500) } else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update messagebank with index $index"))) .result(objectmapper.writeValueAsString(resultMessage("Failed to update messagebank with index $index")))
} else it.status(400) } else it.status(400)
@@ -515,7 +610,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
val json: JsonNode = objectmapper.readTree(it.body()) val json: JsonNode = objectmapper.readTree(it.body())
val tag = json.get("tag").asText("") val tag = json.get("tag").asText("")
val languages = json.get("language").asText("").split(";") val languages = json.get("language").asText("").split(";")
println("Add Language Link, tag=$tag, languages=$languages") //println("Add Language Link, tag=$tag, languages=$languages")
if (ValidString(tag)){ if (ValidString(tag)){
if (languages.all { xx -> Language.entries.any { yy -> yy.name.equals(xx,true)} }){ if (languages.all { xx -> Language.entries.any { yy -> yy.name.equals(xx,true)} }){
if (!db.languageDB.List.any { ll -> ll.TAG.equals(tag,true) }) { if (!db.languageDB.List.any { ll -> ll.TAG.equals(tag,true) }) {
@@ -523,13 +618,14 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.languageDB.Add(newvalue)){ if (db.languageDB.Add(newvalue)){
db.languageDB.Resort() db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Added Language Link: $newvalue")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add language link to database"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add language link to database")))
println("Failed to add language link to database") //println("Failed to add language link to database")
} }
} else { } else {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("TAG=$tag already exists"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("TAG=$tag already exists")))
println("TAG=$tag already exists") //println("TAG=$tag already exists")
} }
} else { } else {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Contains unsupported language"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("Contains unsupported language")))
@@ -545,6 +641,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.languageDB.Clear()) { if (db.languageDB.Clear()) {
db.languageDB.Resort() db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear Language Link table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate language link table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate language link table")))
} }
@@ -558,6 +655,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.languageDB.DeleteByIndex(index.toInt())) { if (db.languageDB.DeleteByIndex(index.toInt())) {
db.languageDB.Resort() db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted Language Link with index $index")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete language link with index $index"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete language link with index $index")))
} }
@@ -592,6 +690,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.languageDB.UpdateByIndex(index.toInt(), ll)) { if (db.languageDB.UpdateByIndex(index.toInt(), ll)) {
db.languageDB.Resort() db.languageDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Updated Language Link $ll")
} else it.status(500) } else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update language link with index $index"))) .result(objectmapper.writeValueAsString(resultMessage("Failed to update language link with index $index")))
} else it.status(400) } else it.status(400)
@@ -644,12 +743,12 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.scheduleDB.Clear()) { if (db.scheduleDB.Clear()) {
db.scheduleDB.Get() db.scheduleDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear Schedule Bank table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate schedulebank table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate schedulebank table")))
} }
} }
post("Add"){ post("Add"){
// TODO add new schedule // TODO add new schedule
// recheck lagi tambahan steph // recheck lagi tambahan steph
val json: JsonNode = objectmapper.readTree(it.body()) val json: JsonNode = objectmapper.readTree(it.body())
@@ -670,10 +769,21 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
val zones = broadcast_zones.split(";") val zones = broadcast_zones.split(";")
if (zones.all { zz -> db.broadcastDB.List.any { xx -> xx.description.equals(zz,true) } }){ if (zones.all { zz -> db.broadcastDB.List.any { xx -> xx.description.equals(zz,true) } }){
if (ValidString(language) && Language.entries.any{ lang -> lang.name == language }){ if (ValidString(language) && Language.entries.any{ lang -> lang.name == language }){
val newvalue = ScheduleBank(0u, description, day, time, soundpath, repeat, enable, broadcast_zones, language) val newvalue = ScheduleBank(
0u,
description,
day,
time,
soundpath,
repeat,
enable,
broadcast_zones,
language
)
if (db.scheduleDB.Add(newvalue)){ if (db.scheduleDB.Add(newvalue)){
db.scheduleDB.Resort() db.scheduleDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Added Schedule Bank: $newvalue")
} else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add schedule to database"))) } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add schedule to database")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Language")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Contains unsupported BroadcastZones"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Contains unsupported BroadcastZones")))
@@ -683,7 +793,6 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Time format, must be HH:mm"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Time format, must be HH:mm")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Day format"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Day format")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Description"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Description")))
} }
delete("DeleteByIndex/{index}") { delete("DeleteByIndex/{index}") {
// delete by index // delete by index
@@ -694,6 +803,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.scheduleDB.DeleteByIndex(index.toInt())) { if (db.scheduleDB.DeleteByIndex(index.toInt())) {
db.scheduleDB.Resort() db.scheduleDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted Schedule Bank with index $index")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete schedule with index $index"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete schedule with index $index")))
} }
@@ -773,6 +883,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.scheduleDB.UpdateByIndex(index.toInt(), sb)) { if (db.scheduleDB.UpdateByIndex(index.toInt(), sb)) {
db.scheduleDB.Resort() db.scheduleDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Updated Schedule : $sb")
} else it.status(500) } else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update schedule with index $index"))) .result(objectmapper.writeValueAsString(resultMessage("Failed to update schedule with index $index")))
} else it.status(400) } else it.status(400)
@@ -808,12 +919,204 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
db.scheduleDB.Resort() db.scheduleDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import schedulebank from XLSX"))) it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import schedulebank from XLSX")))
}
} catch (e: Exception) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
}
}
}
path("UserManagement") {
get("List") {
it.result(objectmapper.writeValueAsString(db.userDB.List))
}
delete("List") {
if (db.userDB.Clear()) {
db.userDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear User Management table")
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to truncate user table")))
}
}
post("Add") { ctx ->
val json: JsonNode = objectmapper.readTree(ctx.body())
val username = json.get("username").asText("")
val password = json.get("password").asText("")
val location = json.get("location").asText("")
val airline_tags = json.get("airline_tags").asText("")
val city_tags = json.get("city_tags").asText("")
val messagebank_ann_id = json.get("messagebank_ann_id").asText("")
val broadcastzones = json.get("broadcastzones").asText("")
if (ValidStrings(listOf(username, password, location, airline_tags, city_tags, messagebank_ann_id, broadcastzones))) {
if (!db.Username_exists(username)) {
// check apakah ada airline tag yang tidak ada di soundbank
val atags = airline_tags.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val airlinetags = db.Get_AirlineCode_Tags()
if (atags.all { tag -> airlinetags.any { it == tag } }) {
// check apakah ada city tag yang tidak ada di soundbank
val ctags = city_tags.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val citytags = db.Get_City_Tags()
if (ctags.all { tag -> citytags.any { it == tag } }) {
val bzdesc =
broadcastzones.split(";").map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
val bzlist = db.Get_BroadcastZone_List()
if (bzdesc.all { desc -> bzlist.any { it == desc } }) {
val mbids = messagebank_ann_id.split(";")
.map { it.trim() }
.filter { it.isNotEmpty() }.distinct()
.mapNotNull { it.toUIntOrNull() }
val mbankids = db.Get_MessageID_List()
if (mbids.all { id -> mbankids.any { it == id } }) {
// semua valid, tambain ke database
val newuser = UserDB(
0u,
username,
password,
location,
airline_tags,
city_tags,
messagebank_ann_id,
broadcastzones
)
if (db.userDB.Add(newuser)) {
db.userDB.Resort()
ctx.result(objectmapper.writeValueAsString(resultMessage("OK") ))
db.Add_Log("AAS", "Added User: $newuser")
} else ctx.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add user to database")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some ANN_ID not found in Messagebank") ))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some broadcast zone tags not found in soundbank")) )
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some city tags not found in soundbank")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some airline tags not found in soundbank")))
} else ctx.status(400).result(objectmapper.writeValueAsString("Username already exists"))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Not all fields have value")))
}
delete("DeleteByIndex/{index}") {
// delete by index
val index = it.pathParam("index").toUIntOrNull()
if (index == null) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
} else {
if (db.userDB.DeleteByIndex(index.toInt())) {
db.userDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted User with index $index")
} else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete user with index $index")))
}
}
patch("UpdateByIndex/{index}"){ ctx ->
// update by index
val index = ctx.pathParam("index").toUIntOrNull()
if (index!=null){
val user = db.userDB.List.find { xx -> xx.index == index }
if (user!=null){
val json : JsonNode = objectmapper.readTree(ctx.body())
if (!json.isEmpty){
val _username = json.get("username").asText("")
val _password = json.get("password").asText("")
val _location = json.get("location").asText("")
val _airline_tags = json.get("airline_tags").asText("")
val _city_tags = json.get("city_tags").asText("")
val _messagebank_ann_id = json.get("messagebank_ann_id").asText("")
val _broadcastzones = json.get("broadcastzones").asText("")
if (ValidStrings(listOf(_username, _password, _location, _airline_tags, _city_tags, _messagebank_ann_id, _broadcastzones))) {
val _otherusername = db.userDB.List.find { xx -> xx.username.equals(_username, true) && xx.index != index }
if (_otherusername == null) {
// belum ada user lain yang pakai username ini
val atags = _airline_tags.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct()
val ctags = _city_tags.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct()
val msgids = _messagebank_ann_id.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct().mapNotNull { it.toUIntOrNull() }
val bzdesc = _broadcastzones.split(";").map { it.trim() }.filter { it.isNotEmpty() }.distinct()
val airline_tags = db.Get_AirlineCode_Tags()
val city_tags = db.Get_City_Tags()
val msgbankids = db.Get_MessageID_List()
val bzlist = db.Get_BroadcastZone_List()
if (atags.all { tag -> airline_tags.any { it == tag } }) {
if (ctags.all { tag -> city_tags.any { it == tag } }) {
if (msgids.all { tag -> msgbankids.any { it == tag } }) {
if (bzdesc.all { tag -> bzlist.any { it == tag } }) {
val editeduser = UserDB(index, _username, _password, _location, _airline_tags, _city_tags, _messagebank_ann_id, _broadcastzones)
if (!user.isEqual(editeduser)){
if (db.userDB.UpdateByIndex(index.toInt(), editeduser)){
db.userDB.Resort()
ctx.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Updated User : $editeduser")
} else ctx.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to update user with index $index")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Nothing has changed for user with index $index")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some broadcast zone is not found in Broadcast Zone list")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some ANN_ID not found in Messagebank")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Some city tags not found in soundbank")))
} else ctx.status(400).result(objectmapper.writeValueAsString("Some airline tags not found in soundbank"))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Username already exists for another user")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Not all fiels have value")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("UpdateByIndex with index=$index has empty body")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("User with index $index not found")))
} else ctx.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid index")))
}
get("ExportXLSX") {
val xlsxdata = db.userDB.Export_XLSX()
if (xlsxdata != null) {
it.header(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
it.header("Content-Disposition", "attachment; filename=\"userdb.xlsx\"")
it.outputStream().use { out ->
xlsxdata.write(out)
}
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to export user table to XLSX")))
}
}
post("ImportXLSX") {
val uploaded = it.uploadedFile("file")
if (uploaded == null) {
it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("No file uploaded")))
return@post
}
try {
val xlsx = XSSFWorkbook(uploaded.content())
if (db.userDB.Import_XLSX(xlsx)) {
db.userDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK")))
} else {
it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to import user table from XLSX")))
} }
} catch (e: Exception) { } catch (e: Exception) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
} }
} }
//TODO kirim list message dan broadcast zones untuk ADD/Edit schedule
get("GetMessageAndBroadcastZones") {
val result = object {
val messages = db.messageDB.List
.filter { mb -> !mb.Message_Detail.contains("[") && !mb.Message_Detail.contains("]")}
.map { mb -> "${mb.Description} [${mb.ANN_ID}]" }
val broadcastzones = db.broadcastDB.List.map { it.description }
}
it.result(objectmapper.writeValueAsString(result))
}
// Kirim list language dari Messagebank berdasarkan ANN_ID
get("GetLanguageList/{ANN_ID}") { get1 ->
//kirim list language dari Messagebank
val langlist = db.messageDB.List
.filter { it.ANN_ID == get1.pathParam("ANN_ID").toInt().toUInt() }
.map { it.Language }.distinct()
get1.result(objectmapper.writeValueAsString(langlist))
}
} }
path("Log") { path("Log") {
get("List") { get1 -> get("List") { get1 ->
@@ -863,24 +1166,20 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} }
path("BroadcastZones"){ path("BroadcastZones"){
get("List") { get("List") {
// TODO : temporary, convert BroadcastZones to BroadcastZonesHtml, karena harus revisi javascript di Bootstrap Studio
// val newlist: ArrayList<BroadcastZonesHtml> = db.broadcastDB.List.map { xx ->
// BroadcastZonesHtml(
// xx.index,
// xx.description,
// xx.SoundChannel,
// xx.id,
// xx.bp
// )
// } as ArrayList<BroadcastZonesHtml>
it.result(MariaDB.ArrayListtoString(db.broadcastDB.List)) it.result(MariaDB.ArrayListtoString(db.broadcastDB.List))
} }
get("BroadcastZoneDescriptions") { ctx ->
val value = db.broadcastDB.List
.distinctBy { it.description }
.map { it.description }
ctx.result(objectmapper.writeValueAsString(value))
}
delete("List") { delete("List") {
// truncate broadcast zones table // truncate broadcast zones table
if (db.broadcastDB.Clear()) { if (db.broadcastDB.Clear()) {
db.broadcastDB.Get() db.broadcastDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear Broadcast Zones table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate broadcast zones table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate broadcast zones table")))
} }
@@ -899,6 +1198,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.broadcastDB.Add(newbp)){ if (db.broadcastDB.Add(newbp)){
db.broadcastDB.Resort() db.broadcastDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS","Added Broadcast Zone: $newbp")
} else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add broadcast zone to database"))) } else it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to add broadcast zone to database")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Relay"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Relay")))
} else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Box"))) } else it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid Box")))
@@ -914,6 +1214,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.broadcastDB.DeleteByIndex(index.toInt())) { if (db.broadcastDB.DeleteByIndex(index.toInt())) {
db.broadcastDB.Resort() db.broadcastDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted Broadcast Zone with index $index")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete broadcast zone with index $index"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete broadcast zone with index $index")))
} }
@@ -958,6 +1259,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.broadcastDB.UpdateByIndex(index.toInt(), bz)) { if (db.broadcastDB.UpdateByIndex(index.toInt(), bz)) {
db.broadcastDB.Resort() db.broadcastDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Updated Broadcast Zone : $bz")
} else it.status(500) } else it.status(500)
.result(objectmapper.writeValueAsString(resultMessage("Failed to update broadcast zone with index $index"))) .result(objectmapper.writeValueAsString(resultMessage("Failed to update broadcast zone with index $index")))
} else it.status(400) } else it.status(400)
@@ -1006,11 +1308,15 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
get("List"){ get("List"){
it.result(MariaDB.ArrayListtoString(db.soundchannelDB.List)) it.result(MariaDB.ArrayListtoString(db.soundchannelDB.List))
} }
get("SoundChannelDescriptions") {
it.result(objectmapper.writeValueAsString(db.Get_SoundChannel_List()))
}
delete("List") { delete("List") {
// truncate sound channel table // truncate sound channel table
if (db.soundchannelDB.Clear()) { if (db.soundchannelDB.Clear()) {
db.soundchannelDB.Get() db.soundchannelDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear Sound Channel table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate sound channel table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate sound channel table")))
} }
@@ -1044,25 +1350,28 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} else { } else {
// cek apakah ada soundchannel lain yang pakai ip dan channel yang sama // cek apakah ada soundchannel lain yang pakai ip dan channel yang sama
if (db.soundchannelDB.List.any { xx -> xx.index != index && _ip.equals(xx.ip) && _channel.equals(xx.channel, true) }) { val othersc = db.soundchannelDB.List.filter { sc -> sc.ip == _ip }.filter { sc -> sc.index != index }
println("Another sound channel already uses IP $_ip and channel $_channel") if (othersc.isNotEmpty()){
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Another sound channel already uses IP $_ip and channel $_channel"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("This IP address is already used by another sound channel")))
return@patch } else {
}
// ada sesuatu yang ganti // ada sesuatu yang ganti
val newsc = SoundChannel(0u, _channel, _ip) val newsc = SoundChannel(0u, _channel, _ip)
if (db.soundchannelDB.UpdateByIndex(index.toInt(), newsc)) { if (db.soundchannelDB.UpdateByIndex(index.toInt(), newsc)) {
println("Updated sound channel with index $index") println("Updated sound channel with index $index")
db.soundchannelDB.Resort() db.soundchannelDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Updated Sound Channel : $newsc")
} else { } else {
println("Failed to update sound channel with index $index") println("Failed to update sound channel with index $index")
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to update sound channel with index $index"))) it.status(500)
return@patch .result(objectmapper.writeValueAsString(resultMessage("Failed to update sound channel with index $index")))
} }
} }
}
} else { } else {
println("Invalid IP address") println("Invalid IP address")
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid IP address"))) it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid IP address")))
@@ -1077,7 +1386,6 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} }
} }
get("ExportXLSX"){ get("ExportXLSX"){
val xlsxdata = db.soundchannelDB.Export_XLSX() val xlsxdata = db.soundchannelDB.Export_XLSX()
@@ -1109,7 +1417,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import sound channel from XLSX"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to import sound channel from XLSX")))
} }
} catch (e: Exception) { } catch (e: Exception) {
it.status(400).result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file"))) it.status(400)
.result(objectmapper.writeValueAsString(resultMessage("Invalid XLSX file")))
} }
} }
} }
@@ -1124,6 +1433,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.queuepagingDB.Clear()) { if (db.queuepagingDB.Clear()) {
db.queuepagingDB.Get() db.queuepagingDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear queue paging table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue paging table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue paging table")))
} }
@@ -1137,6 +1447,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.queuepagingDB.DeleteByIndex(index.toInt())) { if (db.queuepagingDB.DeleteByIndex(index.toInt())) {
db.queuepagingDB.Resort() db.queuepagingDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted queue paging with index $index")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue paging with index $index"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue paging with index $index")))
} }
@@ -1145,6 +1456,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
} }
// Steph : coba tambah untuk QueueTable Table. Belum ada di JS file(?) // Steph : coba tambah untuk QueueTable Table. Belum ada di JS file(?)
path("QueueTable"){ path("QueueTable"){
get("List"){ get("List"){
@@ -1155,6 +1467,8 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.queuetableDB.Clear()) { if (db.queuetableDB.Clear()) {
db.queuetableDB.Get() db.queuetableDB.Get()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Clear Automatic Queue Table")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue sound table"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to truncate queue sound table")))
} }
@@ -1168,6 +1482,7 @@ class WebApp(val listenPort: Int, val userlist: List<Pair<String, String>>) {
if (db.queuetableDB.DeleteByIndex(index.toInt())) { if (db.queuetableDB.DeleteByIndex(index.toInt())) {
db.queuetableDB.Resort() db.queuetableDB.Resort()
it.result(objectmapper.writeValueAsString(resultMessage("OK"))) it.result(objectmapper.writeValueAsString(resultMessage("OK")))
db.Add_Log("AAS", "Deleted Automatic Queue Table with index $index")
} else { } else {
it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue sound with index $index"))) it.status(500).result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue sound with index $index")))
} }