From 546f2e27affe9e524d90f4df3a2fae8705fae4c4 Mon Sep 17 00:00:00 2001 From: rdkartono Date: Tue, 10 Feb 2026 17:05:39 +0700 Subject: [PATCH] commit 09/02/2026 --- html/webpage/assets/css/litepicker.css | 431 ++++++++++++++++++ html/webpage/assets/js/litepicker.js | 12 + html/webpage/assets/js/log.js | 73 ++- html/webpage/assets/js/schedulebank.js | 98 +++- html/webpage/broadcastzones.html | 2 +- html/webpage/homeadmin.html | 6 +- html/webpage/homeviewer.html | 6 +- html/webpage/language.html | 2 +- html/webpage/log.html | 30 +- html/webpage/messagebank.html | 2 +- html/webpage/soundbank.html | 2 +- html/webpage/timer.html | 117 +++-- src/Main.kt | 18 +- src/MainExtension01.kt | 138 +----- src/codes/Somecodes.kt | 23 + .../TCP_Android_Command_Server.kt | 7 +- src/content/ScheduleDay.kt | 23 +- src/database/MariaDB.kt | 103 +++-- src/database/dbFunctions.kt | 24 +- src/database/table/Table_BroadcastZones.kt | 130 ++++++ src/database/table/Table_Logs.kt | 110 +++-- src/database/table/Table_Messagebank.kt | 22 + src/database/table/Table_Schedule.kt | 51 +++ src/web/WebApp.kt | 159 ++++--- 24 files changed, 1205 insertions(+), 384 deletions(-) create mode 100644 html/webpage/assets/css/litepicker.css create mode 100644 html/webpage/assets/js/litepicker.js diff --git a/html/webpage/assets/css/litepicker.css b/html/webpage/assets/css/litepicker.css new file mode 100644 index 0000000..71275a3 --- /dev/null +++ b/html/webpage/assets/css/litepicker.css @@ -0,0 +1,431 @@ +/* ! + * + * ../css/litepicker.css + * Litepicker v2.0.12 (https://github.com/wakirin/Litepicker) + * Package: litepicker (https://www.npmjs.com/package/litepicker) + * License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md) + * Copyright 2019-2021 Rinat G. + * + * Hash: 2f11f1f0300ea13b17b5 + * */ + +:root { + --litepicker-container-months-color-bg: #fff; + --litepicker-container-months-box-shadow-color: #ddd; + --litepicker-footer-color-bg: #fafafa; + --litepicker-footer-box-shadow-color: #ddd; + --litepicker-tooltip-color-bg: #fff; + --litepicker-month-header-color: #333; + --litepicker-button-prev-month-color: #9e9e9e; + --litepicker-button-next-month-color: #9e9e9e; + --litepicker-button-prev-month-color-hover: #2196f3; + --litepicker-button-next-month-color-hover: #2196f3; + --litepicker-month-width: calc(var(--litepicker-day-width) * 7); + --litepicker-month-weekday-color: #9e9e9e; + --litepicker-month-week-number-color: #9e9e9e; + --litepicker-day-width: 38px; + --litepicker-day-color: #333; + --litepicker-day-color-hover: #2196f3; + --litepicker-is-today-color: #f44336; + --litepicker-is-in-range-color: #bbdefb; + --litepicker-is-locked-color: #9e9e9e; + --litepicker-is-start-color: #fff; + --litepicker-is-start-color-bg: #2196f3; + --litepicker-is-end-color: #fff; + --litepicker-is-end-color-bg: #2196f3; + --litepicker-button-cancel-color: #fff; + --litepicker-button-cancel-color-bg: #9e9e9e; + --litepicker-button-apply-color: #fff; + --litepicker-button-apply-color-bg: #2196f3; + --litepicker-button-reset-color: #909090; + --litepicker-button-reset-color-hover: #2196f3; + --litepicker-highlighted-day-color: #333; + --litepicker-highlighted-day-color-bg: #ffeb3b; +} + +.show-week-numbers { + --litepicker-month-width: calc(var(--litepicker-day-width) * 8); +} + +.litepicker { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: 0.8em; + display: none; +} + +.litepicker button { + border: none; + background: none; +} + +.litepicker .container__main { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +} + +.litepicker .container__months { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + background-color: var(--litepicker-container-months-color-bg); + border-radius: 5px; + -webkit-box-shadow: 0 0 5px var(--litepicker-container-months-box-shadow-color); + box-shadow: 0 0 5px var(--litepicker-container-months-box-shadow-color); + width: calc(var(--litepicker-month-width) + 10px); + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.litepicker .container__months.columns-2 { + width: calc((var(--litepicker-month-width) * 2) + 20px); +} + +.litepicker .container__months.columns-3 { + width: calc((var(--litepicker-month-width) * 3) + 30px); +} + +.litepicker .container__months.columns-4 { + width: calc((var(--litepicker-month-width) * 4) + 40px); +} + +.litepicker .container__months.split-view .month-item-header .button-previous-month, .litepicker .container__months.split-view .month-item-header .button-next-month { + visibility: visible; +} + +.litepicker .container__months .month-item { + padding: 5px; + width: var(--litepicker-month-width); + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.litepicker .container__months .month-item-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + font-weight: 500; + padding: 10px 5px; + text-align: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + color: var(--litepicker-month-header-color); +} + +.litepicker .container__months .month-item-header div { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.litepicker .container__months .month-item-header div > .month-item-name { + margin-right: 5px; +} + +.litepicker .container__months .month-item-header div > .month-item-year { + padding: 0; +} + +.litepicker .container__months .month-item-header .reset-button { + color: var(--litepicker-button-reset-color); +} + +.litepicker .container__months .month-item-header .reset-button > svg { + fill: var(--litepicker-button-reset-color); +} + +.litepicker .container__months .month-item-header .reset-button * { + pointer-events: none; +} + +.litepicker .container__months .month-item-header .reset-button:hover { + color: var(--litepicker-button-reset-color-hover); +} + +.litepicker .container__months .month-item-header .reset-button:hover > svg { + fill: var(--litepicker-button-reset-color-hover); +} + +.litepicker .container__months .month-item-header .button-previous-month, .litepicker .container__months .month-item-header .button-next-month { + visibility: hidden; + text-decoration: none; + padding: 3px 5px; + border-radius: 3px; + -webkit-transition: color 0.3s, border 0.3s; + transition: color 0.3s, border 0.3s; + cursor: default; +} + +.litepicker .container__months .month-item-header .button-previous-month *, .litepicker .container__months .month-item-header .button-next-month * { + pointer-events: none; +} + +.litepicker .container__months .month-item-header .button-previous-month { + color: var(--litepicker-button-prev-month-color); +} + +.litepicker .container__months .month-item-header .button-previous-month > svg, .litepicker .container__months .month-item-header .button-previous-month > img { + fill: var(--litepicker-button-prev-month-color); +} + +.litepicker .container__months .month-item-header .button-previous-month:hover { + color: var(--litepicker-button-prev-month-color-hover); +} + +.litepicker .container__months .month-item-header .button-previous-month:hover > svg { + fill: var(--litepicker-button-prev-month-color-hover); +} + +.litepicker .container__months .month-item-header .button-next-month { + color: var(--litepicker-button-next-month-color); +} + +.litepicker .container__months .month-item-header .button-next-month > svg, .litepicker .container__months .month-item-header .button-next-month > img { + fill: var(--litepicker-button-next-month-color); +} + +.litepicker .container__months .month-item-header .button-next-month:hover { + color: var(--litepicker-button-next-month-color-hover); +} + +.litepicker .container__months .month-item-header .button-next-month:hover > svg { + fill: var(--litepicker-button-next-month-color-hover); +} + +.litepicker .container__months .month-item-weekdays-row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + justify-self: center; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + color: var(--litepicker-month-weekday-color); +} + +.litepicker .container__months .month-item-weekdays-row > div { + padding: 5px 0; + font-size: 85%; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + width: var(--litepicker-day-width); + text-align: center; +} + +.litepicker .container__months .month-item:first-child .button-previous-month { + visibility: visible; +} + +.litepicker .container__months .month-item:last-child .button-next-month { + visibility: visible; +} + +.litepicker .container__months .month-item.no-previous-month .button-previous-month { + visibility: hidden; +} + +.litepicker .container__months .month-item.no-next-month .button-next-month { + visibility: hidden; +} + +.litepicker .container__days { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + justify-self: center; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + text-align: center; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.litepicker .container__days > div, .litepicker .container__days > a { + padding: 5px 0; + width: var(--litepicker-day-width); +} + +.litepicker .container__days .day-item { + color: var(--litepicker-day-color); + text-align: center; + text-decoration: none; + border-radius: 3px; + -webkit-transition: color 0.3s, border 0.3s; + transition: color 0.3s, border 0.3s; + cursor: default; +} + +.litepicker .container__days .day-item:hover { + color: var(--litepicker-day-color-hover); + -webkit-box-shadow: inset 0 0 0 1px var(--litepicker-day-color-hover); + box-shadow: inset 0 0 0 1px var(--litepicker-day-color-hover); +} + +.litepicker .container__days .day-item.is-today { + color: var(--litepicker-is-today-color); +} + +.litepicker .container__days .day-item.is-locked { + color: var(--litepicker-is-locked-color); +} + +.litepicker .container__days .day-item.is-locked:hover { + color: var(--litepicker-is-locked-color); + -webkit-box-shadow: none; + box-shadow: none; + cursor: default; +} + +.litepicker .container__days .day-item.is-in-range { + background-color: var(--litepicker-is-in-range-color); + border-radius: 0; +} + +.litepicker .container__days .day-item.is-start-date { + color: var(--litepicker-is-start-color); + background-color: var(--litepicker-is-start-color-bg); + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.litepicker .container__days .day-item.is-start-date.is-flipped { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +.litepicker .container__days .day-item.is-end-date { + color: var(--litepicker-is-end-color); + background-color: var(--litepicker-is-end-color-bg); + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +.litepicker .container__days .day-item.is-end-date.is-flipped { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.litepicker .container__days .day-item.is-start-date.is-end-date { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +.litepicker .container__days .day-item.is-highlighted { + color: var(--litepicker-highlighted-day-color); + background-color: var(--litepicker-highlighted-day-color-bg); +} + +.litepicker .container__days .week-number { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + color: var(--litepicker-month-week-number-color); + font-size: 85%; +} + +.litepicker .container__footer { + text-align: right; + padding: 10px 5px; + margin: 0 5px; + background-color: var(--litepicker-footer-color-bg); + -webkit-box-shadow: inset 0px 3px 3px 0px var(--litepicker-footer-box-shadow-color); + box-shadow: inset 0px 3px 3px 0px var(--litepicker-footer-box-shadow-color); + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} + +.litepicker .container__footer .preview-date-range { + margin-right: 10px; + font-size: 90%; +} + +.litepicker .container__footer .button-cancel { + background-color: var(--litepicker-button-cancel-color-bg); + color: var(--litepicker-button-cancel-color); + border: 0; + padding: 3px 7px 4px; + border-radius: 3px; +} + +.litepicker .container__footer .button-cancel * { + pointer-events: none; +} + +.litepicker .container__footer .button-apply { + background-color: var(--litepicker-button-apply-color-bg); + color: var(--litepicker-button-apply-color); + border: 0; + padding: 3px 7px 4px; + border-radius: 3px; + margin-left: 10px; + margin-right: 10px; +} + +.litepicker .container__footer .button-apply:disabled { + opacity: 0.7; +} + +.litepicker .container__footer .button-apply * { + pointer-events: none; +} + +.litepicker .container__tooltip { + position: absolute; + margin-top: -4px; + padding: 4px 8px; + border-radius: 4px; + background-color: var(--litepicker-tooltip-color-bg); + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.25); + box-shadow: 0 1px 3px rgba(0,0,0,0.25); + white-space: nowrap; + font-size: 11px; + pointer-events: none; + visibility: hidden; +} + +.litepicker .container__tooltip:before { + position: absolute; + bottom: -5px; + left: calc(50% - 5px); + border-top: 5px solid rgba(0,0,0,0.12); + border-right: 5px solid transparent; + border-left: 5px solid transparent; + content: ""; +} + +.litepicker .container__tooltip:after { + position: absolute; + bottom: -4px; + left: calc(50% - 4px); + border-top: 4px solid var(--litepicker-tooltip-color-bg); + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; +} + diff --git a/html/webpage/assets/js/litepicker.js b/html/webpage/assets/js/litepicker.js new file mode 100644 index 0000000..f5906f8 --- /dev/null +++ b/html/webpage/assets/js/litepicker.js @@ -0,0 +1,12 @@ +/*! + * + * litepicker.js + * Litepicker v2.0.12 (https://github.com/wakirin/Litepicker) + * Package: litepicker (https://www.npmjs.com/package/litepicker) + * License: MIT (https://github.com/wakirin/Litepicker/blob/master/LICENCE.md) + * Copyright 2019-2021 Rinat G. + * + * Hash: a5019ade4d1fcf3e6277 + * + */ +var Litepicker=function(t){var e={};function i(n){if(e[n])return e[n].exports;var o=e[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,i),o.l=!0,o.exports}return i.m=t,i.c=e,i.d=function(t,e,n){i.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},i.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},i.t=function(t,e){if(1&e&&(t=i(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)i.d(n,o,function(e){return t[e]}.bind(null,o));return n},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=4)}([function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=function(){function t(e,i,n){void 0===e&&(e=null),void 0===i&&(i=null),void 0===n&&(n="en-US"),this.dateInstance="object"==typeof i&&null!==i?i.parse(e instanceof t?e.clone().toJSDate():e):"string"==typeof i?t.parseDateTime(e,i,n):e?t.parseDateTime(e):t.parseDateTime(new Date),this.lang=n}return t.parseDateTime=function(e,i,n){if(void 0===i&&(i="YYYY-MM-DD"),void 0===n&&(n="en-US"),!e)return new Date(NaN);if(e instanceof Date)return new Date(e);if(e instanceof t)return e.clone().toJSDate();if(/^-?\d{10,}$/.test(e))return t.getDateZeroTime(new Date(Number(e)));if("string"==typeof e){for(var o=[],s=null;null!=(s=t.regex.exec(i));)"\\"!==s[1]&&o.push(s);if(o.length){var r={year:null,month:null,shortMonth:null,longMonth:null,day:null,value:""};o[0].index>0&&(r.value+=".*?");for(var a=0,l=Object.entries(o);at.getTime()&&this.timestamp()=t.getTime()&&this.timestamp()t.getTime()&&this.timestamp()<=e.getTime();case"[]":return this.timestamp()>=t.getTime()&&this.timestamp()<=e.getTime()}},t.prototype.isBefore=function(t,e){switch(void 0===e&&(e="seconds"),e){case"second":case"seconds":return t.getTime()>this.getTime();case"day":case"days":return new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()>new Date(this.getFullYear(),this.getMonth(),this.getDate()).getTime();case"month":case"months":return new Date(t.getFullYear(),t.getMonth(),1).getTime()>new Date(this.getFullYear(),this.getMonth(),1).getTime();case"year":case"years":return t.getFullYear()>this.getFullYear()}throw new Error("isBefore: Invalid unit!")},t.prototype.isSameOrBefore=function(t,e){switch(void 0===e&&(e="seconds"),e){case"second":case"seconds":return t.getTime()>=this.getTime();case"day":case"days":return new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()>=new Date(this.getFullYear(),this.getMonth(),this.getDate()).getTime();case"month":case"months":return new Date(t.getFullYear(),t.getMonth(),1).getTime()>=new Date(this.getFullYear(),this.getMonth(),1).getTime()}throw new Error("isSameOrBefore: Invalid unit!")},t.prototype.isAfter=function(t,e){switch(void 0===e&&(e="seconds"),e){case"second":case"seconds":return this.getTime()>t.getTime();case"day":case"days":return new Date(this.getFullYear(),this.getMonth(),this.getDate()).getTime()>new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime();case"month":case"months":return new Date(this.getFullYear(),this.getMonth(),1).getTime()>new Date(t.getFullYear(),t.getMonth(),1).getTime();case"year":case"years":return this.getFullYear()>t.getFullYear()}throw new Error("isAfter: Invalid unit!")},t.prototype.isSameOrAfter=function(t,e){switch(void 0===e&&(e="seconds"),e){case"second":case"seconds":return this.getTime()>=t.getTime();case"day":case"days":return new Date(this.getFullYear(),this.getMonth(),this.getDate()).getTime()>=new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime();case"month":case"months":return new Date(this.getFullYear(),this.getMonth(),1).getTime()>=new Date(t.getFullYear(),t.getMonth(),1).getTime()}throw new Error("isSameOrAfter: Invalid unit!")},t.prototype.isSame=function(t,e){switch(void 0===e&&(e="seconds"),e){case"second":case"seconds":return this.getTime()===t.getTime();case"day":case"days":return new Date(this.getFullYear(),this.getMonth(),this.getDate()).getTime()===new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime();case"month":case"months":return new Date(this.getFullYear(),this.getMonth(),1).getTime()===new Date(t.getFullYear(),t.getMonth(),1).getTime()}throw new Error("isSame: Invalid unit!")},t.prototype.add=function(t,e){switch(void 0===e&&(e="seconds"),e){case"second":case"seconds":this.setSeconds(this.getSeconds()+t);break;case"day":case"days":this.setDate(this.getDate()+t);break;case"month":case"months":this.setMonth(this.getMonth()+t)}return this},t.prototype.subtract=function(t,e){switch(void 0===e&&(e="seconds"),e){case"second":case"seconds":this.setSeconds(this.getSeconds()-t);break;case"day":case"days":this.setDate(this.getDate()-t);break;case"month":case"months":this.setMonth(this.getMonth()-t)}return this},t.prototype.diff=function(t,e){void 0===e&&(e="seconds");switch(e){default:case"second":case"seconds":return this.getTime()-t.getTime();case"day":case"days":return Math.round((this.timestamp()-t.getTime())/864e5);case"month":case"months":}},t.prototype.format=function(e,i){if(void 0===i&&(i="en-US"),"object"==typeof e)return e.output(this.clone().toJSDate());for(var n="",o=[],s=null;null!=(s=t.regex.exec(e));)"\\"!==s[1]&&o.push(s);if(o.length){o[0].index>0&&(n+=e.substring(0,o[0].index));for(var r=0,a=Object.entries(o);r1&&i.isAfter(e)&&i.setMonth(i.getMonth()-(this.options.numberOfMonths-1)),this.calendars[0]=i.clone()):(e.setDate(1),this.calendars[0]=e.clone())}},e.prototype.bindEvents=function(){document.addEventListener("click",this.onClick.bind(this),!0),this.ui=document.createElement("div"),this.ui.className=l.litepicker,this.ui.style.display="none",this.ui.addEventListener("mouseenter",this.onMouseEnter.bind(this),!0),this.ui.addEventListener("mouseleave",this.onMouseLeave.bind(this),!1),this.options.autoRefresh?(this.options.element instanceof HTMLElement&&this.options.element.addEventListener("keyup",this.onInput.bind(this),!0),this.options.elementEnd instanceof HTMLElement&&this.options.elementEnd.addEventListener("keyup",this.onInput.bind(this),!0)):(this.options.element instanceof HTMLElement&&this.options.element.addEventListener("change",this.onInput.bind(this),!0),this.options.elementEnd instanceof HTMLElement&&this.options.elementEnd.addEventListener("change",this.onInput.bind(this),!0)),this.options.parentEl?this.options.parentEl instanceof HTMLElement?this.options.parentEl.appendChild(this.ui):document.querySelector(this.options.parentEl).appendChild(this.ui):this.options.inlineMode?this.options.element instanceof HTMLInputElement?this.options.element.parentNode.appendChild(this.ui):this.options.element.appendChild(this.ui):document.body.appendChild(this.ui),this.updateInput(),this.init(),"function"==typeof this.options.setup&&this.options.setup.call(this,this),this.render(),this.options.inlineMode&&this.show()},e.prototype.updateInput=function(){if(this.options.element instanceof HTMLInputElement){var t=this.options.startDate,e=this.options.endDate;if(this.options.singleMode&&t)this.options.element.value=t.format(this.options.format,this.options.lang);else if(!this.options.singleMode&&t&&e){var i=t.format(this.options.format,this.options.lang),n=e.format(this.options.format,this.options.lang);this.options.elementEnd instanceof HTMLInputElement?(this.options.element.value=i,this.options.elementEnd.value=n):this.options.element.value=""+i+this.options.delimiter+n}t||e||(this.options.element.value="",this.options.elementEnd instanceof HTMLInputElement&&(this.options.elementEnd.value=""))}},e.prototype.isSamePicker=function(t){return t.closest("."+l.litepicker)===this.ui},e.prototype.shouldShown=function(t){return!t.disabled&&(t===this.options.element||this.options.elementEnd&&t===this.options.elementEnd)},e.prototype.shouldResetDatePicked=function(){return this.options.singleMode||2===this.datePicked.length},e.prototype.shouldSwapDatePicked=function(){return 2===this.datePicked.length&&this.datePicked[0].getTime()>this.datePicked[1].getTime()},e.prototype.shouldCheckLockDays=function(){return this.options.disallowLockDaysInRange&&2===this.datePicked.length},e.prototype.onClick=function(t){var e=t.target;if(t.target.shadowRoot&&(e=t.composedPath()[0]),e&&this.ui)if(this.shouldShown(e))this.show(e);else if(e.closest("."+l.litepicker)||!this.isShowning()){if(this.isSamePicker(e))if(this.emit("before:click",e),this.preventClick)this.preventClick=!1;else{if(e.classList.contains(l.dayItem)){if(t.preventDefault(),e.classList.contains(l.isLocked))return;if(this.shouldResetDatePicked()&&(this.datePicked.length=0),this.datePicked[this.datePicked.length]=new a.DateTime(e.dataset.time),this.shouldSwapDatePicked()){var i=this.datePicked[1].clone();this.datePicked[1]=this.datePicked[0].clone(),this.datePicked[0]=i.clone()}if(this.shouldCheckLockDays())c.rangeIsLocked(this.datePicked,this.options)&&(this.emit("error:range",this.datePicked),this.datePicked.length=0);return this.render(),this.emit.apply(this,s(["preselect"],s(this.datePicked).map((function(t){return t.clone()})))),void(this.options.autoApply&&(this.options.singleMode&&this.datePicked.length?(this.setDate(this.datePicked[0]),this.hide()):this.options.singleMode||2!==this.datePicked.length||(this.setDateRange(this.datePicked[0],this.datePicked[1]),this.hide())))}if(e.classList.contains(l.buttonPreviousMonth)){t.preventDefault();var n=0,o=this.options.switchingMonths||this.options.numberOfMonths;if(this.options.splitView){var r=e.closest("."+l.monthItem);n=c.findNestedMonthItem(r),o=1}return this.calendars[n].setMonth(this.calendars[n].getMonth()-o),this.gotoDate(this.calendars[n],n),void this.emit("change:month",this.calendars[n],n)}if(e.classList.contains(l.buttonNextMonth)){t.preventDefault();n=0,o=this.options.switchingMonths||this.options.numberOfMonths;if(this.options.splitView){r=e.closest("."+l.monthItem);n=c.findNestedMonthItem(r),o=1}return this.calendars[n].setMonth(this.calendars[n].getMonth()+o),this.gotoDate(this.calendars[n],n),void this.emit("change:month",this.calendars[n],n)}e.classList.contains(l.buttonCancel)&&(t.preventDefault(),this.hide(),this.emit("button:cancel")),e.classList.contains(l.buttonApply)&&(t.preventDefault(),this.options.singleMode&&this.datePicked.length?this.setDate(this.datePicked[0]):this.options.singleMode||2!==this.datePicked.length||this.setDateRange(this.datePicked[0],this.datePicked[1]),this.hide(),this.emit("button:apply",this.options.startDate,this.options.endDate))}}else this.hide()},e.prototype.showTooltip=function(t,e){var i=this.ui.querySelector("."+l.containerTooltip);i.style.visibility="visible",i.innerHTML=e;var n=this.ui.getBoundingClientRect(),o=i.getBoundingClientRect(),s=t.getBoundingClientRect(),r=s.top,a=s.left;if(this.options.inlineMode&&this.options.parentEl){var c=this.ui.parentNode.getBoundingClientRect();r-=c.top,a-=c.left}else r-=n.top,a-=n.left;r-=o.height,a-=o.width/2,a+=s.width/2,i.style.top=r+"px",i.style.left=a+"px",this.emit("tooltip",i,t)},e.prototype.hideTooltip=function(){this.ui.querySelector("."+l.containerTooltip).style.visibility="hidden"},e.prototype.shouldAllowMouseEnter=function(t){return!this.options.singleMode&&!t.classList.contains(l.isLocked)},e.prototype.shouldAllowRepick=function(){return this.options.elementEnd&&this.options.allowRepick&&this.options.startDate&&this.options.endDate},e.prototype.isDayItem=function(t){return t.classList.contains(l.dayItem)},e.prototype.onMouseEnter=function(t){var e=this,i=t.target;if(this.isDayItem(i)&&this.shouldAllowMouseEnter(i)){if(this.shouldAllowRepick()&&(this.triggerElement===this.options.element?this.datePicked[0]=this.options.endDate.clone():this.triggerElement===this.options.elementEnd&&(this.datePicked[0]=this.options.startDate.clone())),1!==this.datePicked.length)return;var n=this.ui.querySelector("."+l.dayItem+'[data-time="'+this.datePicked[0].getTime()+'"]'),o=this.datePicked[0].clone(),s=new a.DateTime(i.dataset.time),r=!1;if(o.getTime()>s.getTime()){var c=o.clone();o=s.clone(),s=c.clone(),r=!0}if(Array.prototype.slice.call(this.ui.querySelectorAll("."+l.dayItem)).forEach((function(t){var i=new a.DateTime(t.dataset.time),n=e.renderDay(i);i.isBetween(o,s)&&n.classList.add(l.isInRange),t.className=n.className})),i.classList.add(l.isEndDate),r?(n&&n.classList.add(l.isFlipped),i.classList.add(l.isFlipped)):(n&&n.classList.remove(l.isFlipped),i.classList.remove(l.isFlipped)),this.options.showTooltip){var h=s.diff(o,"day")+1;if("function"==typeof this.options.tooltipNumber&&(h=this.options.tooltipNumber.call(this,h)),h>0){var p=this.pluralSelector(h),d=h+" "+(this.options.tooltipText[p]?this.options.tooltipText[p]:"["+p+"]");this.showTooltip(i,d);var u=window.navigator.userAgent,m=/(iphone|ipad)/i.test(u),f=/OS 1([0-2])/i.test(u);m&&f&&i.dispatchEvent(new Event("click"))}else this.hideTooltip()}}},e.prototype.onMouseLeave=function(t){t.target;this.options.allowRepick&&(!this.options.allowRepick||this.options.startDate||this.options.endDate)&&(this.datePicked.length=0,this.render())},e.prototype.onInput=function(t){var e=this.parseInput(),i=e[0],n=e[1],o=this.options.format;if(this.options.elementEnd?i instanceof a.DateTime&&n instanceof a.DateTime&&i.format(o)===this.options.element.value&&n.format(o)===this.options.elementEnd.value:this.options.singleMode?i instanceof a.DateTime&&i.format(o)===this.options.element.value:i instanceof a.DateTime&&n instanceof a.DateTime&&""+i.format(o)+this.options.delimiter+n.format(o)===this.options.element.value){if(n&&i.getTime()>n.getTime()){var s=i.clone();i=n.clone(),n=s.clone()}this.options.startDate=new a.DateTime(i,this.options.format,this.options.lang),n&&(this.options.endDate=new a.DateTime(n,this.options.format,this.options.lang)),this.updateInput(),this.render();var r=i.clone(),l=0;(this.options.elementEnd?i.format(o)===t.target.value:t.target.value.startsWith(i.format(o)))||(r=n.clone(),l=this.options.numberOfMonths-1),this.emit("selected",this.getStartDate(),this.getEndDate()),this.gotoDate(r,l)}},e}(r.Calendar);e.Litepicker=h},function(t,e,i){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.findNestedMonthItem=function(t){for(var e=t.parentNode.childNodes,i=0;iy)(u=document.createElement("option")).value=String(t.getFullYear()),u.text=String(t.getFullYear()),u.selected=!0,u.disabled=!0,g.appendChild(u);for(d=y;d>=v;d-=1){var u=document.createElement("option"),b=new r.DateTime(new Date(d,0,1,0,0,0));u.value=String(d),u.text=String(d),u.disabled=this.options.minDate&&b.isBefore(new r.DateTime(this.options.minDate),"year")||this.options.maxDate&&b.isAfter(new r.DateTime(this.options.maxDate),"year"),u.selected=t.getFullYear()===d,g.appendChild(u)}if(t.getFullYear()W");for(var _=1;_<=7;_+=1){var T=3+this.options.firstDay+_,L=document.createElement("div");L.innerHTML=this.weekdayName(T),L.title=this.weekdayName(T,"long"),M.appendChild(L)}var E=document.createElement("div");E.className=a.containerDays;var S=this.calcSkipDays(n);this.options.showWeekNumbers&&S&&E.appendChild(this.renderWeekNumber(n));for(var I=0;I1&&1===this.datePicked.length){var o=this.options.minDays-1,s=this.datePicked[0].clone().subtract(o,"day"),c=this.datePicked[0].clone().add(o,"day");t.isBetween(s,this.datePicked[0],"(]")&&e.classList.add(a.isLocked),t.isBetween(this.datePicked[0],c,"[)")&&e.classList.add(a.isLocked)}if(this.options.maxDays&&1===this.datePicked.length){var h=this.options.maxDays;s=this.datePicked[0].clone().subtract(h,"day"),c=this.datePicked[0].clone().add(h,"day");t.isSameOrBefore(s)&&e.classList.add(a.isLocked),t.isSameOrAfter(c)&&e.classList.add(a.isLocked)}(this.options.selectForward&&1===this.datePicked.length&&t.isBefore(this.datePicked[0])&&e.classList.add(a.isLocked),this.options.selectBackward&&1===this.datePicked.length&&t.isAfter(this.datePicked[0])&&e.classList.add(a.isLocked),l.dateIsLocked(t,this.options,this.datePicked)&&e.classList.add(a.isLocked),this.options.highlightedDays.length)&&(this.options.highlightedDays.filter((function(e){return e instanceof Array?t.isBetween(e[0],e[1],"[]"):e.isSame(t,"day")})).length&&e.classList.add(a.isHighlighted));return e.tabIndex=e.classList.contains("is-locked")?-1:0,this.emit("render:day",e,t),e},e.prototype.renderFooter=function(){var t=document.createElement("div");if(t.className=a.containerFooter,this.options.footerHTML?t.innerHTML=this.options.footerHTML:t.innerHTML='\n \n \n \n ",this.options.singleMode){if(1===this.datePicked.length){var e=this.datePicked[0].format(this.options.format,this.options.lang);t.querySelector("."+a.previewDateRange).innerHTML=e}}else if(1===this.datePicked.length&&t.querySelector("."+a.buttonApply).setAttribute("disabled",""),2===this.datePicked.length){e=this.datePicked[0].format(this.options.format,this.options.lang);var i=this.datePicked[1].format(this.options.format,this.options.lang);t.querySelector("."+a.previewDateRange).innerHTML=""+e+this.options.delimiter+i}return this.emit("render:footer",t),t},e.prototype.renderWeekNumber=function(t){var e=document.createElement("div"),i=t.getWeek(this.options.firstDay);return e.className=a.weekNumber,e.innerHTML=53===i&&0===t.getMonth()?"53 / 1":i,e},e.prototype.renderTooltip=function(){var t=document.createElement("div");return t.className=a.containerTooltip,t},e.prototype.weekdayName=function(t,e){return void 0===e&&(e="short"),new Date(1970,0,t,12,0,0,0).toLocaleString(this.options.lang,{weekday:e})},e.prototype.calcSkipDays=function(t){var e=t.getDay()-this.options.firstDay;return e<0&&(e+=7),e},e}(s.LPCore);e.Calendar=c},function(t,e,i){"use strict";var n,o=this&&this.__extends||(n=function(t,e){return(n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i])})(t,e)},function(t,e){function i(){this.constructor=t}n(t,e),t.prototype=null===e?Object.create(e):(i.prototype=e.prototype,new i)}),s=this&&this.__assign||function(){return(s=Object.assign||function(t){for(var e,i=1,n=arguments.length;i',nextMonth:'',reset:'\n \n \n '},tooltipText:{one:"day",other:"days"}},i.options=s(s({},i.options),e.element.dataset),Object.keys(i.options).forEach((function(t){"true"!==i.options[t]&&"false"!==i.options[t]||(i.options[t]="true"===i.options[t])}));var n=s(s({},i.options.dropdowns),e.dropdowns),o=s(s({},i.options.buttonText),e.buttonText),r=s(s({},i.options.tooltipText),e.tooltipText);i.options=s(s({},i.options),e),i.options.dropdowns=s({},n),i.options.buttonText=s({},o),i.options.tooltipText=s({},r),i.options.elementEnd||(i.options.allowRepick=!1),i.options.lockDays.length&&(i.options.lockDays=a.DateTime.convertArray(i.options.lockDays,i.options.lockDaysFormat)),i.options.highlightedDays.length&&(i.options.highlightedDays=a.DateTime.convertArray(i.options.highlightedDays,i.options.highlightedDaysFormat));var l=i.parseInput(),c=l[0],h=l[1];i.options.startDate&&(i.options.singleMode||i.options.endDate)&&(c=new a.DateTime(i.options.startDate,i.options.format,i.options.lang)),c&&i.options.endDate&&(h=new a.DateTime(i.options.endDate,i.options.format,i.options.lang)),c instanceof a.DateTime&&!isNaN(c.getTime())&&(i.options.startDate=c),i.options.startDate&&h instanceof a.DateTime&&!isNaN(h.getTime())&&(i.options.endDate=h),!i.options.singleMode||i.options.startDate instanceof a.DateTime||(i.options.startDate=null),i.options.singleMode||i.options.startDate instanceof a.DateTime&&i.options.endDate instanceof a.DateTime||(i.options.startDate=null,i.options.endDate=null);for(var p=0;pwindow.innerHeight,c=e.top+s-i.height>=i.height;l&&c&&(r=e.top+s-i.height)}if(/left|right/.test(n[0])||n[1]&&"auto"!==n[1]&&/left|right/.test(n[1]))a=/left|right/.test(n[0])?e[n[0]]+o:e[n[1]]+o,"right"!==n[0]&&"right"!==n[1]||(a-=i.width);else{a=e.left+o;l=e.left+i.width>window.innerWidth;var h=e.right+o-i.width>=0;l&&h&&(a=e.right+o-i.width)}return{left:a,top:r}},e}(r.EventEmitter);e.LPCore=c},function(t,e,i){"use strict";var n,o="object"==typeof Reflect?Reflect:null,s=o&&"function"==typeof o.apply?o.apply:function(t,e,i){return Function.prototype.apply.call(t,e,i)};n=o&&"function"==typeof o.ownKeys?o.ownKeys:Object.getOwnPropertySymbols?function(t){return Object.getOwnPropertyNames(t).concat(Object.getOwnPropertySymbols(t))}:function(t){return Object.getOwnPropertyNames(t)};var r=Number.isNaN||function(t){return t!=t};function a(){a.init.call(this)}t.exports=a,a.EventEmitter=a,a.prototype._events=void 0,a.prototype._eventsCount=0,a.prototype._maxListeners=void 0;var l=10;function c(t){return void 0===t._maxListeners?a.defaultMaxListeners:t._maxListeners}function h(t,e,i,n){var o,s,r,a;if("function"!=typeof i)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof i);if(void 0===(s=t._events)?(s=t._events=Object.create(null),t._eventsCount=0):(void 0!==s.newListener&&(t.emit("newListener",e,i.listener?i.listener:i),s=t._events),r=s[e]),void 0===r)r=s[e]=i,++t._eventsCount;else if("function"==typeof r?r=s[e]=n?[i,r]:[r,i]:n?r.unshift(i):r.push(i),(o=c(t))>0&&r.length>o&&!r.warned){r.warned=!0;var l=new Error("Possible EventEmitter memory leak detected. "+r.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");l.name="MaxListenersExceededWarning",l.emitter=t,l.type=e,l.count=r.length,a=l,console&&console.warn&&console.warn(a)}return t}function p(){for(var t=[],e=0;e0&&(r=e[0]),r instanceof Error)throw r;var a=new Error("Unhandled error."+(r?" ("+r.message+")":""));throw a.context=r,a}var l=o[t];if(void 0===l)return!1;if("function"==typeof l)s(l,this,e);else{var c=l.length,h=f(l,c);for(i=0;i=0;s--)if(i[s]===e||i[s].listener===e){r=i[s].listener,o=s;break}if(o<0)return this;0===o?i.shift():function(t,e){for(;e+1=0;n--)this.removeListener(t,e[n]);return this},a.prototype.listeners=function(t){return u(this,t,!0)},a.prototype.rawListeners=function(t){return u(this,t,!1)},a.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):m.call(t,e)},a.prototype.listenerCount=m,a.prototype.eventNames=function(){return this._eventsCount>0?n(this._events):[]}},function(t,e,i){(e=i(9)(!1)).push([t.i,':root{--litepicker-container-months-color-bg: #fff;--litepicker-container-months-box-shadow-color: #ddd;--litepicker-footer-color-bg: #fafafa;--litepicker-footer-box-shadow-color: #ddd;--litepicker-tooltip-color-bg: #fff;--litepicker-month-header-color: #333;--litepicker-button-prev-month-color: #9e9e9e;--litepicker-button-next-month-color: #9e9e9e;--litepicker-button-prev-month-color-hover: #2196f3;--litepicker-button-next-month-color-hover: #2196f3;--litepicker-month-width: calc(var(--litepicker-day-width) * 7);--litepicker-month-weekday-color: #9e9e9e;--litepicker-month-week-number-color: #9e9e9e;--litepicker-day-width: 38px;--litepicker-day-color: #333;--litepicker-day-color-hover: #2196f3;--litepicker-is-today-color: #f44336;--litepicker-is-in-range-color: #bbdefb;--litepicker-is-locked-color: #9e9e9e;--litepicker-is-start-color: #fff;--litepicker-is-start-color-bg: #2196f3;--litepicker-is-end-color: #fff;--litepicker-is-end-color-bg: #2196f3;--litepicker-button-cancel-color: #fff;--litepicker-button-cancel-color-bg: #9e9e9e;--litepicker-button-apply-color: #fff;--litepicker-button-apply-color-bg: #2196f3;--litepicker-button-reset-color: #909090;--litepicker-button-reset-color-hover: #2196f3;--litepicker-highlighted-day-color: #333;--litepicker-highlighted-day-color-bg: #ffeb3b}.show-week-numbers{--litepicker-month-width: calc(var(--litepicker-day-width) * 8)}.litepicker{font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;font-size:0.8em;display:none}.litepicker button{border:none;background:none}.litepicker .container__main{display:-webkit-box;display:-ms-flexbox;display:flex}.litepicker .container__months{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;background-color:var(--litepicker-container-months-color-bg);border-radius:5px;-webkit-box-shadow:0 0 5px var(--litepicker-container-months-box-shadow-color);box-shadow:0 0 5px var(--litepicker-container-months-box-shadow-color);width:calc(var(--litepicker-month-width) + 10px);-webkit-box-sizing:content-box;box-sizing:content-box}.litepicker .container__months.columns-2{width:calc((var(--litepicker-month-width) * 2) + 20px)}.litepicker .container__months.columns-3{width:calc((var(--litepicker-month-width) * 3) + 30px)}.litepicker .container__months.columns-4{width:calc((var(--litepicker-month-width) * 4) + 40px)}.litepicker .container__months.split-view .month-item-header .button-previous-month,.litepicker .container__months.split-view .month-item-header .button-next-month{visibility:visible}.litepicker .container__months .month-item{padding:5px;width:var(--litepicker-month-width);-webkit-box-sizing:content-box;box-sizing:content-box}.litepicker .container__months .month-item-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-weight:500;padding:10px 5px;text-align:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:var(--litepicker-month-header-color)}.litepicker .container__months .month-item-header div{-webkit-box-flex:1;-ms-flex:1;flex:1}.litepicker .container__months .month-item-header div>.month-item-name{margin-right:5px}.litepicker .container__months .month-item-header div>.month-item-year{padding:0}.litepicker .container__months .month-item-header .reset-button{color:var(--litepicker-button-reset-color)}.litepicker .container__months .month-item-header .reset-button>svg{fill:var(--litepicker-button-reset-color)}.litepicker .container__months .month-item-header .reset-button *{pointer-events:none}.litepicker .container__months .month-item-header .reset-button:hover{color:var(--litepicker-button-reset-color-hover)}.litepicker .container__months .month-item-header .reset-button:hover>svg{fill:var(--litepicker-button-reset-color-hover)}.litepicker .container__months .month-item-header .button-previous-month,.litepicker .container__months .month-item-header .button-next-month{visibility:hidden;text-decoration:none;padding:3px 5px;border-radius:3px;-webkit-transition:color 0.3s, border 0.3s;transition:color 0.3s, border 0.3s;cursor:default}.litepicker .container__months .month-item-header .button-previous-month *,.litepicker .container__months .month-item-header .button-next-month *{pointer-events:none}.litepicker .container__months .month-item-header .button-previous-month{color:var(--litepicker-button-prev-month-color)}.litepicker .container__months .month-item-header .button-previous-month>svg,.litepicker .container__months .month-item-header .button-previous-month>img{fill:var(--litepicker-button-prev-month-color)}.litepicker .container__months .month-item-header .button-previous-month:hover{color:var(--litepicker-button-prev-month-color-hover)}.litepicker .container__months .month-item-header .button-previous-month:hover>svg{fill:var(--litepicker-button-prev-month-color-hover)}.litepicker .container__months .month-item-header .button-next-month{color:var(--litepicker-button-next-month-color)}.litepicker .container__months .month-item-header .button-next-month>svg,.litepicker .container__months .month-item-header .button-next-month>img{fill:var(--litepicker-button-next-month-color)}.litepicker .container__months .month-item-header .button-next-month:hover{color:var(--litepicker-button-next-month-color-hover)}.litepicker .container__months .month-item-header .button-next-month:hover>svg{fill:var(--litepicker-button-next-month-color-hover)}.litepicker .container__months .month-item-weekdays-row{display:-webkit-box;display:-ms-flexbox;display:flex;justify-self:center;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;color:var(--litepicker-month-weekday-color)}.litepicker .container__months .month-item-weekdays-row>div{padding:5px 0;font-size:85%;-webkit-box-flex:1;-ms-flex:1;flex:1;width:var(--litepicker-day-width);text-align:center}.litepicker .container__months .month-item:first-child .button-previous-month{visibility:visible}.litepicker .container__months .month-item:last-child .button-next-month{visibility:visible}.litepicker .container__months .month-item.no-previous-month .button-previous-month{visibility:hidden}.litepicker .container__months .month-item.no-next-month .button-next-month{visibility:hidden}.litepicker .container__days{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-self:center;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:center;-webkit-box-sizing:content-box;box-sizing:content-box}.litepicker .container__days>div,.litepicker .container__days>a{padding:5px 0;width:var(--litepicker-day-width)}.litepicker .container__days .day-item{color:var(--litepicker-day-color);text-align:center;text-decoration:none;border-radius:3px;-webkit-transition:color 0.3s, border 0.3s;transition:color 0.3s, border 0.3s;cursor:default}.litepicker .container__days .day-item:hover{color:var(--litepicker-day-color-hover);-webkit-box-shadow:inset 0 0 0 1px var(--litepicker-day-color-hover);box-shadow:inset 0 0 0 1px var(--litepicker-day-color-hover)}.litepicker .container__days .day-item.is-today{color:var(--litepicker-is-today-color)}.litepicker .container__days .day-item.is-locked{color:var(--litepicker-is-locked-color)}.litepicker .container__days .day-item.is-locked:hover{color:var(--litepicker-is-locked-color);-webkit-box-shadow:none;box-shadow:none;cursor:default}.litepicker .container__days .day-item.is-in-range{background-color:var(--litepicker-is-in-range-color);border-radius:0}.litepicker .container__days .day-item.is-start-date{color:var(--litepicker-is-start-color);background-color:var(--litepicker-is-start-color-bg);border-top-left-radius:5px;border-bottom-left-radius:5px;border-top-right-radius:0;border-bottom-right-radius:0}.litepicker .container__days .day-item.is-start-date.is-flipped{border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:5px;border-bottom-right-radius:5px}.litepicker .container__days .day-item.is-end-date{color:var(--litepicker-is-end-color);background-color:var(--litepicker-is-end-color-bg);border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:5px;border-bottom-right-radius:5px}.litepicker .container__days .day-item.is-end-date.is-flipped{border-top-left-radius:5px;border-bottom-left-radius:5px;border-top-right-radius:0;border-bottom-right-radius:0}.litepicker .container__days .day-item.is-start-date.is-end-date{border-top-left-radius:5px;border-bottom-left-radius:5px;border-top-right-radius:5px;border-bottom-right-radius:5px}.litepicker .container__days .day-item.is-highlighted{color:var(--litepicker-highlighted-day-color);background-color:var(--litepicker-highlighted-day-color-bg)}.litepicker .container__days .week-number{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:var(--litepicker-month-week-number-color);font-size:85%}.litepicker .container__footer{text-align:right;padding:10px 5px;margin:0 5px;background-color:var(--litepicker-footer-color-bg);-webkit-box-shadow:inset 0px 3px 3px 0px var(--litepicker-footer-box-shadow-color);box-shadow:inset 0px 3px 3px 0px var(--litepicker-footer-box-shadow-color);border-bottom-left-radius:5px;border-bottom-right-radius:5px}.litepicker .container__footer .preview-date-range{margin-right:10px;font-size:90%}.litepicker .container__footer .button-cancel{background-color:var(--litepicker-button-cancel-color-bg);color:var(--litepicker-button-cancel-color);border:0;padding:3px 7px 4px;border-radius:3px}.litepicker .container__footer .button-cancel *{pointer-events:none}.litepicker .container__footer .button-apply{background-color:var(--litepicker-button-apply-color-bg);color:var(--litepicker-button-apply-color);border:0;padding:3px 7px 4px;border-radius:3px;margin-left:10px;margin-right:10px}.litepicker .container__footer .button-apply:disabled{opacity:0.7}.litepicker .container__footer .button-apply *{pointer-events:none}.litepicker .container__tooltip{position:absolute;margin-top:-4px;padding:4px 8px;border-radius:4px;background-color:var(--litepicker-tooltip-color-bg);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25);box-shadow:0 1px 3px rgba(0,0,0,0.25);white-space:nowrap;font-size:11px;pointer-events:none;visibility:hidden}.litepicker .container__tooltip:before{position:absolute;bottom:-5px;left:calc(50% - 5px);border-top:5px solid rgba(0,0,0,0.12);border-right:5px solid transparent;border-left:5px solid transparent;content:""}.litepicker .container__tooltip:after{position:absolute;bottom:-4px;left:calc(50% - 4px);border-top:4px solid var(--litepicker-tooltip-color-bg);border-right:4px solid transparent;border-left:4px solid transparent;content:""}\n',""]),e.locals={showWeekNumbers:"show-week-numbers",litepicker:"litepicker",containerMain:"container__main",containerMonths:"container__months",columns2:"columns-2",columns3:"columns-3",columns4:"columns-4",splitView:"split-view",monthItemHeader:"month-item-header",buttonPreviousMonth:"button-previous-month",buttonNextMonth:"button-next-month",monthItem:"month-item",monthItemName:"month-item-name",monthItemYear:"month-item-year",resetButton:"reset-button",monthItemWeekdaysRow:"month-item-weekdays-row",noPreviousMonth:"no-previous-month",noNextMonth:"no-next-month",containerDays:"container__days",dayItem:"day-item",isToday:"is-today",isLocked:"is-locked",isInRange:"is-in-range",isStartDate:"is-start-date",isFlipped:"is-flipped",isEndDate:"is-end-date",isHighlighted:"is-highlighted",weekNumber:"week-number",containerFooter:"container__footer",previewDateRange:"preview-date-range",buttonCancel:"button-cancel",buttonApply:"button-apply",containerTooltip:"container__tooltip"},t.exports=e},function(t,e,i){"use strict";t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var i=function(t,e){var i=t[1]||"",n=t[3];if(!n)return i;if(e&&"function"==typeof btoa){var o=(r=n,a=btoa(unescape(encodeURIComponent(JSON.stringify(r)))),l="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(a),"/*# ".concat(l," */")),s=n.sources.map((function(t){return"/*# sourceURL=".concat(n.sourceRoot||"").concat(t," */")}));return[i].concat(s).concat([o]).join("\n")}var r,a,l;return[i].join("\n")}(e,t);return e[2]?"@media ".concat(e[2]," {").concat(i,"}"):i})).join("")},e.i=function(t,i,n){"string"==typeof t&&(t=[[null,t,""]]);var o={};if(n)for(var s=0;sthis.options.endDate.getTime()&&(this.options.endDate=this.options.startDate.clone(),this.options.startDate=new o.DateTime(t,this.options.format,this.options.lang)),this.updateInput())},s.Litepicker.prototype.setDateRange=function(t,e,i){void 0===i&&(i=!1),this.triggerElement=void 0;var n=new o.DateTime(t,this.options.format,this.options.lang),s=new o.DateTime(e,this.options.format,this.options.lang);(this.options.disallowLockDaysInRange?r.rangeIsLocked([n,s],this.options):r.dateIsLocked(n,this.options,[n,s])||r.dateIsLocked(s,this.options,[n,s]))&&!i?this.emit("error:range",[n,s]):(this.setStartDate(n),this.setEndDate(s),this.options.inlineMode&&this.render(),this.updateInput(),this.emit("selected",this.getStartDate(),this.getEndDate()))},s.Litepicker.prototype.gotoDate=function(t,e){void 0===e&&(e=0);var i=new o.DateTime(t);i.setDate(1),this.calendars[e]=i.clone(),this.render()},s.Litepicker.prototype.setLockDays=function(t){this.options.lockDays=o.DateTime.convertArray(t,this.options.lockDaysFormat),this.render()},s.Litepicker.prototype.setHighlightedDays=function(t){this.options.highlightedDays=o.DateTime.convertArray(t,this.options.highlightedDaysFormat),this.render()},s.Litepicker.prototype.setOptions=function(t){delete t.element,delete t.elementEnd,delete t.parentEl,t.startDate&&(t.startDate=new o.DateTime(t.startDate,this.options.format,this.options.lang)),t.endDate&&(t.endDate=new o.DateTime(t.endDate,this.options.format,this.options.lang));var e=n(n({},this.options.dropdowns),t.dropdowns),i=n(n({},this.options.buttonText),t.buttonText),s=n(n({},this.options.tooltipText),t.tooltipText);this.options=n(n({},this.options),t),this.options.dropdowns=n({},e),this.options.buttonText=n({},i),this.options.tooltipText=n({},s),!this.options.singleMode||this.options.startDate instanceof o.DateTime||(this.options.startDate=null,this.options.endDate=null),this.options.singleMode||this.options.startDate instanceof o.DateTime&&this.options.endDate instanceof o.DateTime||(this.options.startDate=null,this.options.endDate=null);for(var r=0;r { + console.log("Logs loaded: " + okdata.length); if (Array.isArray(okdata)) { window.logdata.push(...okdata); fill_logtablebody(window.logdata); @@ -54,16 +56,33 @@ function reloadLogs(APIURL = "Log/", date, filter) { }); } - +datepicker = null; +$btnGet = null; $(document).ready(function () { console.log("log.js ready"); let selectedlogdate = ""; let logfilter = ""; let APIURL = "Log/"; + $btnGet = $('#btnGet'); + + datepicker = new Litepicker({ + element: document.getElementById('logdate'), + format: 'DD/MM/YYYY', + lang: 'en-US', + autoApply: true, + singleMode: true, + startDate: new Date(), + onSelect: (date) => { + selectedlogdate = date.format('DD/MM/YYYY'); + console.log("Selected date: " + selectedlogdate); + } + }) + if (dtLog === null) { dtLog = new DataTable('#logtable', { + dom: 'Bfrtip', data: [], pageLength: 25, columns: [ @@ -72,36 +91,42 @@ $(document).ready(function () { { title: "Time", data: "timenya" }, { title: "Machine", data: "machine" }, { title: "Description", data: "description" } - ] + ], + buttons: ['print', 'pdf', 'excel'] }); } - - if (!$('#logdate').val()) { - const today = new Date(); - const dd = String(today.getDate()).padStart(2, '0'); - const mm = String(today.getMonth() + 1).padStart(2, '0'); - const yyyy = today.getFullYear(); - $('#logdate').val(`${yyyy}-${mm}-${dd}`); - selectedlogdate = `${dd}-${mm}-${yyyy}`; - reloadLogs(APIURL, selectedlogdate, logfilter); - } - $('#logdate').off('change').on('change', function () { - const selected = $(this).val(); - if (selected) { - const [year, month, day] = selected.split('-'); - selectedlogdate = `${day}-${month}-${year}`; - reloadLogs(APIURL, selectedlogdate, logfilter); + // findalldate is checkbox, if checked will disable datepicker + $('#findalldate').off('change').on('change', function () { + if ($(this).is(':checked')) { + datepicker.disabled = true; + selectedlogdate = "alldate"; + console.log("Find all date checked, omitting date filter"); + } else { + datepicker.disabled = false; + const date = datepicker.getDate(); + selectedlogdate = date.format('DD/MM/YYYY'); + console.log("Find all date unchecked, selected date: " + selectedlogdate); } }); $('#searchfilter').off('input').on('input', function () { logfilter = $(this).val(); + //reloadLogs(APIURL, selectedlogdate, logfilter); + }); + $btnGet.click(function () { + let checked = $('#findalldate').is(':checked'); + if (checked && logfilter.trim() === "") { + alert("Please enter a filter when 'Find All Date' is checked to avoid large data load."); + return; + } + //$(this).data('selectedlogdate', selectedlogdate); + //$(this).data('logfilter', logfilter); reloadLogs(APIURL, selectedlogdate, logfilter); }); - $('#btnExport').off('click').on('click', function () { - DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: logfilter }); - }); - + selectedlogdate = datepicker.getDate().format('DD/MM/YYYY'); + console.log("Initial selected date: " + selectedlogdate); + $btnGet.trigger('click'); // load logs on page load + }); \ No newline at end of file diff --git a/html/webpage/assets/js/schedulebank.js b/html/webpage/assets/js/schedulebank.js index 0f0f936..daf0e0a 100644 --- a/html/webpage/assets/js/schedulebank.js +++ b/html/webpage/assets/js/schedulebank.js @@ -22,6 +22,15 @@ window.schedulebankdata = []; window.selectedschedulerow = null; dtScheduleBank = null; +dtTodaySchedule = null; + +function fill_todayscheduletablebody(vv) { + dtTodaySchedule.clear(); + if (!Array.isArray(vv) || vv.length === 0) return; + + dtTodaySchedule.rows.add(vv); + dtTodaySchedule.draw(); +} /** * Fill schedulebank table body with values @@ -117,7 +126,19 @@ function reloadTimerBank(APIURL = "ScheduleBank/") { }); } +function reloadTodaySchedule(APIURL = "ScheduleBank/") { + fetchAPI(APIURL + "TodaySchedule", "GET", {}, null, (okdata) => { + if (Array.isArray(okdata)) { + console.log("Today's Schedule: ", okdata); + fill_todayscheduletablebody(okdata); + } + }, (errdata) => { + alert("Error loading today's schedule : " + errdata.message); + }); +} + dayViewMode = 'all'; // all, everyday, monday, tuesday, wednesday, thursday, friday, saturday, sunday +scheduledate = null; // Litepicker instance for schedule date selection $(document).ready(function () { console.log("schedulebank.js loaded successfully"); @@ -169,6 +190,26 @@ $(document).ready(function () { }); } + if (dtTodaySchedule === null) { + dtTodaySchedule = new DataTable('#todaytable', { + dom: 'Bfrtip', + data: [], + pageLength: 25, + columns: [ + { title: "No", data: "index" }, + { title: "Description", data: "description" }, + { title: "Day", data: "day" }, + { title: "Time", data: "time" }, + { title: "Message", data: "soundpath" }, + { title: "Repeat", data: "repeat" }, + { title: "Enable", data: "enable" }, + { title: "Broadcast Zones", data: "broadcastZones" }, + { title: "Language", data: "language" } + ], + buttons: ['print', 'pdf'] + }); + } + $.fn.dataTable.ext.search.push(function (settings, data, dataIndex, rowData) { if (settings.nTable.id !== 'schedulebanktable') return true; switch (dayViewMode) { @@ -223,16 +264,30 @@ $(document).ready(function () { let $weeklyselect = $schedulemodal.find('#weeklyselect'); // radio button for specific date let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate'); + + scheduledate = new Litepicker({ + element: document.getElementById('scheduledate'), + format: 'DD/MM/YYYY', + lang: 'en-US', + autoApply: true, + singleMode: true, + startDate: new Date(), + onSelect: (date) => { + console.log("Selected special date: " + date.format('DD/MM/YYYY')); + } + }) // date input - let $scheduledate = $schedulemodal.find('#scheduledate'); + //let $scheduledate = $schedulemodal.find('#scheduledate'); // select2 for language let $languageselect = $schedulemodal.find('#languageselect'); $schedulespecialdate.off('change').on('change', function () { if ($(this).is(':checked')) { - $scheduledate.prop('disabled', false); + //$scheduledate.prop('disabled', false); + scheduledate.disabled = false } else { - $scheduledate.prop('disabled', true); + //$scheduledate.prop('disabled', true); + scheduledate.disabled = true } }); @@ -261,7 +316,8 @@ $(document).ready(function () { dropdownParent: $('#schedulemodal') }); - $scheduledate.prop('disabled', true).val(''); + //$scheduledate.prop('disabled', true).val(''); + scheduledate.disabled = true; $schedulezones.empty().select2({ data: window.BroadcastZoneList.map(zone => ({ id: zone.description, text: zone.description })), placeholder: 'Select broadcast zones', @@ -284,24 +340,28 @@ $(document).ready(function () { $scheduleeveryday.off('change').on('change', function () { if ($(this).is(':checked')) { $weeklyselect.prop('disabled', true); - $scheduledate.prop('disabled', true); + //$scheduledate.prop('disabled', true); + scheduledate.disabled = true; } }); $scheduleweekly.off('change').on('change', function () { if ($(this).is(':checked')) { $weeklyselect.prop('disabled', false); - $scheduledate.prop('disabled', true); + //$scheduledate.prop('disabled', true); + scheduledate.disabled = true; } }); $schedulespecialdate.off('change').on('change', function () { if ($(this).is(':checked')) { $weeklyselect.prop('disabled', true); - $scheduledate.prop('disabled', false); + //$scheduledate.prop('disabled', false); + scheduledate.disabled = false; } }); } reloadTimerBank(APIURL); + reloadTodaySchedule(APIURL); reloadBroadcastZones(); getLanguages(); getScheduledDays(); @@ -309,6 +369,7 @@ $(document).ready(function () { $btnClear.click(() => { DoClear(APIURL, "Timerbank", (okdata) => { reloadTimerBank(APIURL); + reloadTodaySchedule(APIURL); alert("Success clear schedulebank : " + okdata.message); }, (errdata) => { alert("Error clear schedulebank : " + errdata.message); @@ -317,7 +378,9 @@ $(document).ready(function () { }); $btnAdd.click(() => { $schedulemodal.modal('show'); + clearScheduleModal(); + $scheduleeveryday.prop('checked', true).trigger('click'); $schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () { $schedulemodal.modal('hide'); @@ -333,7 +396,7 @@ $(document).ready(function () { if ($scheduleeveryday.is(':checked')) { _Day = "Everyday"; } else if ($schedulespecialdate.is(':checked')) { - _Day = Convert_input_date_to_string($scheduledate.val()); + _Day = Convert_input_date_to_string(scheduledate.getDate().format('DD/MM/YYYY')); } else if ($scheduleweekly.is(':checked')) { _Day = $weeklyselect.val(); } @@ -363,6 +426,7 @@ $(document).ready(function () { fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => { reloadTimerBank(APIURL); + reloadTodaySchedule(APIURL); alert("Success add schedule: " + okdata.message); }, (errdata) => { alert("Error add schedule: " + errdata.message); @@ -393,6 +457,7 @@ $(document).ready(function () { if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) { fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => { reloadTimerBank(APIURL); + reloadTodaySchedule(APIURL); alert("Success delete schedule : " + okdata.message); }, (errdata) => { alert("Error delete schedule : " + errdata.message); @@ -438,7 +503,8 @@ $(document).ready(function () { $weeklyselect.val(null).trigger('change'); $weeklyselect.prop('disabled', true); - $scheduledate.prop('disabled', true); + //$scheduledate.prop('disabled', true); + scheduledate.disabled = true; break; case 'Sunday': case 'Monday': @@ -452,7 +518,8 @@ $(document).ready(function () { $weeklyselect.val(sr.Day).trigger('change'); $weeklyselect.prop('disabled', false); - $scheduledate.prop('disabled', true); + //$scheduledate.prop('disabled', true); + scheduledate.disabled = true; break; default: console.log("Assuming special date for Day: ", sr.Day); @@ -461,8 +528,10 @@ $(document).ready(function () { if (/^\d{2}\/\d{2}\/\d{4}$/.test(sr.Day)) { $schedulespecialdate.prop('checked', true); - $scheduledate.val(Convert_string_to_input_date(sr.Day)); - $scheduledate.prop('disabled', false); + //$scheduledate.val(Convert_string_to_input_date(sr.Day)); + // $scheduledate.prop('disabled', false); + scheduledate.setDate(dayjs(sr.Day,'DD/MM/YYYY')); + scheduledate.disabled = false; $weeklyselect.val(null).trigger('change'); $weeklyselect.prop('disabled', true); @@ -484,7 +553,8 @@ $(document).ready(function () { Day = "Everyday"; } else if ($schedulespecialdate.is(':checked')) { // convert date from yyyy-mm-dd to dd/mm/yyyy - Day = Convert_input_date_to_string($scheduledate.val()); + //Day = Convert_input_date_to_string($scheduledate.val()); + Day = scheduledate.getDate().format('DD/MM/YYYY'); } else if ($scheduleweekly.is(':checked')) { Day = $weeklyselect.val(); } @@ -516,6 +586,7 @@ $(document).ready(function () { fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => { alert("Success edit schedule: " + okdata.message); reloadTimerBank(APIURL); + reloadTodaySchedule(APIURL); }, (errdata) => { alert("Error edit schedule: " + errdata.message); }); @@ -537,6 +608,7 @@ $(document).ready(function () { $btnImport.click(() => { DoImport(APIURL, (okdata) => { reloadTimerBank(APIURL); + reloadTodaySchedule(APIURL); alert("Success import schedulebank from XLSX : " + okdata.message); }, (errdata) => { alert("Error importing schedulebank from XLSX : " + errdata.message); diff --git a/html/webpage/broadcastzones.html b/html/webpage/broadcastzones.html index 90614b6..862e661 100644 --- a/html/webpage/broadcastzones.html +++ b/html/webpage/broadcastzones.html @@ -292,9 +292,9 @@ + - \ No newline at end of file diff --git a/html/webpage/homeadmin.html b/html/webpage/homeadmin.html index 7db4e1d..4d1af64 100644 --- a/html/webpage/homeadmin.html +++ b/html/webpage/homeadmin.html @@ -159,10 +159,10 @@ - - - + + + \ No newline at end of file diff --git a/html/webpage/homeviewer.html b/html/webpage/homeviewer.html index 33a2efe..0f83e99 100644 --- a/html/webpage/homeviewer.html +++ b/html/webpage/homeviewer.html @@ -119,10 +119,10 @@ - - - + + + \ No newline at end of file diff --git a/html/webpage/language.html b/html/webpage/language.html index eef28ac..345ee21 100644 --- a/html/webpage/language.html +++ b/html/webpage/language.html @@ -99,8 +99,8 @@ - + \ No newline at end of file diff --git a/html/webpage/log.html b/html/webpage/log.html index d769499..0614a38 100644 --- a/html/webpage/log.html +++ b/html/webpage/log.html @@ -15,6 +15,7 @@ + @@ -26,16 +27,26 @@
-
-

Select Log Date

+
+
+
+

Select Log Date

+
+
+
+
+
+
-
-
-
-
-

Search

+
+
+
+

Search

+
+
+
-
+
@@ -60,8 +71,9 @@
- + + \ No newline at end of file diff --git a/html/webpage/messagebank.html b/html/webpage/messagebank.html index 6096de1..e549314 100644 --- a/html/webpage/messagebank.html +++ b/html/webpage/messagebank.html @@ -136,8 +136,8 @@
- + \ No newline at end of file diff --git a/html/webpage/soundbank.html b/html/webpage/soundbank.html index 5b13cff..8cb912e 100644 --- a/html/webpage/soundbank.html +++ b/html/webpage/soundbank.html @@ -120,8 +120,8 @@
- + \ No newline at end of file diff --git a/html/webpage/timer.html b/html/webpage/timer.html index ee8b920..4ee22bf 100644 --- a/html/webpage/timer.html +++ b/html/webpage/timer.html @@ -15,6 +15,7 @@ + @@ -25,46 +26,6 @@

Schedule Bank

- -
-
-
-
-
-
-
-
-
-
-

Table Length : N/A

-
-
-
-
- - - - - - - - - - - - - - - -
NoDescriptionDayTimeSound PathRepeatEnableBroadcast ZonesLanguage
-
-
@@ -181,10 +142,80 @@ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+

Table Length : N/A

+
+
+
+
+ + + + + + + + + + + + + + + +
NoDescriptionDayTimeSound PathRepeatEnableBroadcast ZonesLanguage
+
+
+
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + + +
NoDescriptionDayTimeSound PathRepeatEnableBroadcast ZonesLanguage
+
+
+
+
+
+
- + + \ No newline at end of file diff --git a/src/Main.kt b/src/Main.kt index fa952f0..7cae864 100644 --- a/src/Main.kt +++ b/src/Main.kt @@ -16,6 +16,10 @@ import content.Language import content.VoiceType import database.data.Log import database.MariaDB +import database.table.Table_BroadcastZones +import database.table.Table_Logs +import database.table.Table_Messagebank +import database.table.Table_SoundChannel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -44,8 +48,12 @@ const val version = "0.0.29 (09/02/2026)" const val max_channel = 64 val apptick : Long = System.currentTimeMillis() -// dipakai untuk ambil messagebank berdasarkan id +// 4 tabel utama , kepakai dimana-mana, ditaruh di root biar gampang aksesnya +lateinit var broadcastDB: Table_BroadcastZones +lateinit var soundchannelDB: Table_SoundChannel +lateinit var messageDB: Table_Messagebank +lateinit var logDB: Table_Logs val contentCache = ContentCache() @@ -235,14 +243,14 @@ fun main(args: Array) { val androidserver = TCP_Android_Command_Server() androidserver.StartTcpServer(5003){ Logger.info { it } - db.logDB.Add(Log.NewLog("ANDROID", it)) + logDB.Add(Log.NewLog("ANDROID", it)) } val barixserver = TCP_Barix_Command_Server() barixserver.StartTcpServer { cmd -> val _tcp = barixserver.getSocket(cmd.ipaddress) val _streamer = StreamerOutputs[cmd.ipaddress] - val _sc = db.soundchannelDB.List.find { it.ip == cmd.ipaddress } + val _sc = soundchannelDB.List.find { it.ip == cmd.ipaddress } if (_streamer == null) { // belum create BarixConnection untuk ipaddress ini @@ -297,13 +305,13 @@ fun main(args: Array) { } - db.logDB.Add("AAS"," Application started") + logDB.Add("AAS"," Application started") // shutdown hook Runtime.getRuntime().addShutdownHook(Thread ({ - db.logDB.Add("AAS"," Application stopping") + logDB.Add("AAS"," Application stopping") Logger.info { "Shutdown hook called, stopping services..." } barixserver.StopTcpCommand() androidserver.StopTcpCommand() diff --git a/src/MainExtension01.kt b/src/MainExtension01.kt index b66e05b..39757cb 100644 --- a/src/MainExtension01.kt +++ b/src/MainExtension01.kt @@ -7,7 +7,6 @@ import codes.Somecodes.Companion.IsNumber import codes.Somecodes.Companion.Make_WAV_FileName import codes.Somecodes.Companion.SoundbankResult_directory import codes.Somecodes.Companion.ValidFile -import codes.Somecodes.Companion.ValidIPV4 import codes.Somecodes.Companion.ValidString import codes.Somecodes.Companion.dateformat1 import codes.Somecodes.Companion.datetimeformat1 @@ -20,7 +19,6 @@ import content.VoiceType import database.data.Messagebank import database.data.QueueTable import database.data.Soundbank - import org.tinylog.Logger import java.time.DayOfWeek import java.time.LocalDate @@ -37,93 +35,7 @@ import java.time.LocalTime */ class MainExtension01 { - data class InvalidZoneDetail(val zonename: String, val reason: String) - data class ValidZoneDetail( - val zonename: String, - val soundchanel: String, - val ip: String, - val boxid: String, - val contacts: String - ) - class CheckBroadcastZoneResult { - var allvalid: Boolean = false - var message: String? = null - var validzones = mutableListOf() - var invalidzones = mutableListOf() - } - - /** - * Fungsi untuk cek apakah semua broadcast zone valid - * Valid berarti nama broadcast zone ada di tabel BroadcastZones, dan SoundChannel-nya ada di tabel SoundChannel, dan IP-nya valid - * @param bz List of broadcast zone (SoundChannel) - * @return CheckBroadcastZoneResult object containing allvalid flag and list of invalid zones - */ - fun AllBroadcastZonesValid(bz: List): CheckBroadcastZoneResult { - val result = CheckBroadcastZoneResult() - if (bz.isNotEmpty()) { - bz.forEach { zz -> - if (ValidString(zz)) { // string tidak kosong - val findzone = db.broadcastDB.List.find { - ValidString(it.description) && ValidString(it.SoundChannel) && zz.equals( - it.description, - true - ) - } - if (findzone != null) { // ketemu zona dengan deskripsi sesuai - val findsc = db.soundchannelDB.List.find { - findzone.SoundChannel.equals( - it.channel, - true - ) && ValidIPV4(it.ip) - } - if (findsc != null) { // ketemu soundchannel dengan channel sesuai dan IP valid - // check apakah offline atau online - if (StreamerOutputs.containsKey(findsc.ip)) { - val bc = StreamerOutputs[findsc.ip] - if (bc != null && bc.isOnline()) { - result.validzones.add( - ValidZoneDetail( - zz, - findzone.SoundChannel, - findsc.ip, - findzone.id, - findzone.bp - ) - ) - } else result.invalidzones.add( - InvalidZoneDetail( - zz, - "SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is offline" - ) - ) - } else result.invalidzones.add( - InvalidZoneDetail( - zz, - "SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is not connected in StreamerOutputs" - ) - ) - } else result.invalidzones.add( - InvalidZoneDetail( - zz, - "SoundChannel ${findzone.SoundChannel} not found or has invalid IP in SoundChannel table" - ) - ) - } else result.invalidzones.add(InvalidZoneDetail(zz, "Zone $zz not found in BroadcastZones table")) - } else result.invalidzones.add(InvalidZoneDetail(zz, "Invalid broadcast zone string")) - } - - if (result.validzones.size == bz.size) { - result.allvalid = true - result.message = "All requested broadcast zones are valid" - } else { - result.message = "Some requested broadcast zones are not registered in BroadcastZone table" - } - } else { - result.message = "No Broadcast Zones checked for validity" - } - return result - } /** @@ -168,7 +80,7 @@ class MainExtension01 { var selected_voice = config.Get(configKeys.DEFAULT_VOICE_TYPE.key) if (selected_voice.isEmpty()) selected_voice = VoiceType.VOICE_1.name languages.forEach { lang -> - db.messageDB.List + messageDB.List .find { mb -> mb.ANN_ID == id.toUInt() && mb.Language.equals(lang, true) && mb.Voice_Type.equals( selected_voice, @@ -771,7 +683,7 @@ class MainExtension01 { if (!ValidFile(qp.Message)) throw Exception("Invalid audio file ${qp.Message}") if (qp.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") val zz = qp.BroadcastZones.split(";", ",").map { it.trim() }.filter { ValidString(it) } - AllBroadcastZonesValid(zz).let { checkresult -> + broadcastDB.AllBroadcastZonesValid(zz).let { checkresult -> if (!checkresult.allvalid) { val reasons = checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } @@ -793,10 +705,10 @@ class MainExtension01 { so.ActivateRelay(relays) so.SendData(afi.bytes, cbOK = { so.SetAudioFileInfo(null) - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, cbFail = { so.SetAudioFileInfo(null) - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, cbPlaying = { isplaying -> if (!isplaying) { so.ClearUsedByBroadcastZones() @@ -809,7 +721,7 @@ class MainExtension01 { val logmessage = "Broadcast started PAGING with Filename '${qp.Message}' to zones: ${qp.BroadcastZones}" Logger.info { logmessage } - db.logDB.Add("AAS", logmessage) + logDB.Add("AAS", logmessage) db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.Resort() return true @@ -819,7 +731,7 @@ class MainExtension01 { db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.Resort() val msg = "Canceled paging message $qp due to exception: ${e.message}" - db.logDB.Add("AAS", msg) + logDB.Add("AAS", msg) Logger.error { msg } } return false @@ -840,7 +752,7 @@ class MainExtension01 { if (mblist.isEmpty()) throw Exception("ANN_ID $ann_id not found in Messagebank") if (qp.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") val zz = qp.BroadcastZones.split(";", ",").map { it.trim() }.filter { ValidString(it) } - AllBroadcastZonesValid(zz).let { checkresult -> + broadcastDB.AllBroadcastZonesValid(zz).let { checkresult -> if (!checkresult.allvalid) { val reasons = checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } @@ -880,7 +792,7 @@ class MainExtension01 { listafi, targetfile, true, ) - db.logDB.Add("AAS", result.message) + logDB.Add("AAS", result.message) if (!result.success) { throw Exception(result.message) } @@ -899,11 +811,11 @@ class MainExtension01 { targetafi.bytes, { so.SetAudioFileInfo(null) - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, { so.SetAudioFileInfo(null) - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, cbPlaying = { isplaying -> if (!isplaying) { so.ClearUsedByBroadcastZones() @@ -916,7 +828,7 @@ class MainExtension01 { val logmsg = "Broadcast started SHALAT message with generated file '$targetfile' to zones: ${qp.BroadcastZones}" Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) + logDB.Add("AAS", logmsg) db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.Resort() return true @@ -926,7 +838,7 @@ class MainExtension01 { db.queuepagingDB.DeleteByIndex(qp.index.toInt()) db.queuepagingDB.Resort() val msg = "Canceled shalat message $qp due to exception: ${e.message}" - db.logDB.Add("AAS", msg) + logDB.Add("AAS", msg) Logger.error { msg } } return false @@ -947,7 +859,7 @@ class MainExtension01 { if (mblist.isEmpty()) throw Exception("ANN_ID $ann_id not found in Messagebank") if (qa.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") val zz = qa.BroadcastZones.split(";") - AllBroadcastZonesValid(zz).let { checkresult -> + broadcastDB.AllBroadcastZonesValid(zz).let { checkresult -> if (!checkresult.allvalid) { val reasons = checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } @@ -997,13 +909,13 @@ class MainExtension01 { so.SetAudioFileInfo(null) so.ClearUsedByBroadcastZones() so.DeactivateRelay() - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, { so.SetAudioFileInfo(null) so.ClearUsedByBroadcastZones() so.DeactivateRelay() - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, cbPlaying = { isplaying -> if (!isplaying) { so.ClearUsedByBroadcastZones() @@ -1015,7 +927,7 @@ class MainExtension01 { val logmsg = "Broadcast started TIMER message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) + logDB.Add("AAS", logmsg) db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() return true @@ -1025,7 +937,7 @@ class MainExtension01 { db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() val msg = "Canceled TIMER message $qa due to exception: ${e.message}" - db.logDB.Add("AAS", msg) + logDB.Add("AAS", msg) Logger.error { msg } } return false @@ -1045,7 +957,7 @@ class MainExtension01 { if (qa.BroadcastZones.isEmpty()) throw Exception("Empty broadcast zones") val zz = qa.BroadcastZones.split(";") - AllBroadcastZonesValid(zz).let { checkresult -> + broadcastDB.AllBroadcastZonesValid(zz).let { checkresult -> if (!checkresult.allvalid) { val reasons = checkresult.invalidzones.joinToString("; ") { iz -> "Zone '${iz.zonename}': ${iz.reason}" } @@ -1071,7 +983,7 @@ class MainExtension01 { // not available from variables, try to get from Message column // ada ini, karena protokol FIS dulu tidak ada ANN_ID tapi pake Remark val remark = variables?.get("REMARK").orEmpty() - db.logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") + logDB.Add("AAS", "Trying to get ANN_ID from REMARK field: $remark") Logger.info { "Trying to get ANN_ID from REMARK field: $remark" } when (remark) { "GOP" -> { @@ -1099,9 +1011,9 @@ class MainExtension01 { } } Logger.info { "Found ANN_ID from REMARK field: $ann_id" } - db.logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") + logDB.Add("AAS", "Found ANN_ID from REMARK field: $ann_id") } else { - db.logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") + logDB.Add("AAS", "Found ANN_ID from SB_TAGS variables: $ann_id") Logger.info { "Found ANN_ID from SB_TAGS variables: $ann_id" } } @@ -1152,13 +1064,13 @@ class MainExtension01 { so.SetAudioFileInfo(null) so.ClearUsedByBroadcastZones() so.DeactivateRelay() - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, { so.SetAudioFileInfo(null) so.ClearUsedByBroadcastZones() so.DeactivateRelay() - db.logDB.Add("AAS", it) + logDB.Add("AAS", it) }, cbPlaying = { isplaying -> if (!isplaying) { so.ClearUsedByBroadcastZones() @@ -1171,7 +1083,7 @@ class MainExtension01 { val logmsg = "Broadcast started SOUNDBANK message with generated file '$targetfile' to zones: ${qa.BroadcastZones}" Logger.info { logmsg } - db.logDB.Add("AAS", logmsg) + logDB.Add("AAS", logmsg) db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() return true @@ -1180,7 +1092,7 @@ class MainExtension01 { } catch (e: Exception) { val msg = "Canceled SOUNDBANK message $qa due to exception: ${e.message}" - db.logDB.Add("AAS", msg) + logDB.Add("AAS", msg) Logger.error { msg } db.queuetableDB.DeleteByIndex(qa.index.toInt()) db.queuetableDB.Resort() diff --git a/src/codes/Somecodes.kt b/src/codes/Somecodes.kt index b68cb3e..d7b0f4b 100644 --- a/src/codes/Somecodes.kt +++ b/src/codes/Somecodes.kt @@ -664,6 +664,14 @@ class Somecodes { } } + /** + * Get today's date as a string in the format "dd/MM/yyyy". + * @return A string representing today's date. + */ + fun Today_to_DateString() : String { + return dateformat1.format(LocalDateTime.now()) + } + /** * Check if a string is a valid time in the format "hh:mm:ss". * @param value The string to check. @@ -709,6 +717,21 @@ class Somecodes { return sd?.name } + /** + * Check if a string is a valid language code. + * A valid language code is one that matches any of the Language enum values. + * @param value The string to check. + * @return True if the string is a valid language code, false otherwise. + */ + fun ValidLanguage(value: String) : Boolean{ + value.split(";",",").forEach { ll -> + Language.entries.forEach { l -> + if (l.value.equals(ll,true)) return true + } + } + return false + } + /** * Check if a string is a valid schedule day or a valid date. * A valid schedule day is either one of the ScheduleDay enum names or a date in the format "dd/MM/yyyy". diff --git a/src/commandServer/TCP_Android_Command_Server.kt b/src/commandServer/TCP_Android_Command_Server.kt index 4928291..2b6c095 100644 --- a/src/commandServer/TCP_Android_Command_Server.kt +++ b/src/commandServer/TCP_Android_Command_Server.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import messageDB import org.tinylog.Logger import tcpreceiver import udpreceiver @@ -347,7 +348,7 @@ class TCP_Android_Command_Server { // iterasi setiap ANN_ID .forEach { annid -> // masukin ke VARMESSAGES yang unik secara ANN_ID dan Language - val xx = db.messageDB.List + val xx = messageDB.List .asSequence() .filter{it.ANN_ID == annid.toUInt()} .distinctBy { it.ANN_ID } @@ -447,13 +448,13 @@ class TCP_Android_Command_Server { VARMESSAGES.forEachIndexed { index, msg -> val ann_id = msg.ANN_ID - val msg_indo = db.messageDB.List.find { + val msg_indo = messageDB.List.find { it.ANN_ID == ann_id && it.Language.equals( Language.INDONESIA.name, true ) } - val msg_eng = db.messageDB.List.find { + val msg_eng = messageDB.List.find { it.ANN_ID == ann_id && it.Language.equals( Language.ENGLISH.name, true diff --git a/src/content/ScheduleDay.kt b/src/content/ScheduleDay.kt index 600ad5d..e067ae4 100644 --- a/src/content/ScheduleDay.kt +++ b/src/content/ScheduleDay.kt @@ -1,5 +1,7 @@ package content +import java.time.DayOfWeek + @Suppress("unused") enum class ScheduleDay(val day: String) { Sunday("Sunday"), @@ -9,5 +11,22 @@ enum class ScheduleDay(val day: String) { Thursday("Thursday"), Friday("Friday"), Saturday("Saturday"), - Everyday("Everyday") -} \ No newline at end of file + Everyday("Everyday"); + + companion object { + + /** + * Converts a DayOfWeek to a ScheduleDay + */ + fun from_LocalDate_DOW(value : DayOfWeek) : ScheduleDay{ + return when(value){ + DayOfWeek.SUNDAY -> Sunday + DayOfWeek.MONDAY -> Monday + DayOfWeek.TUESDAY -> Tuesday + DayOfWeek.WEDNESDAY -> Wednesday + DayOfWeek.THURSDAY -> Thursday + DayOfWeek.FRIDAY -> Friday + DayOfWeek.SATURDAY -> Saturday + }} + } + } diff --git a/src/database/MariaDB.kt b/src/database/MariaDB.kt index 9e8580d..75ccdf1 100644 --- a/src/database/MariaDB.kt +++ b/src/database/MariaDB.kt @@ -21,34 +21,21 @@ import database.table.Table_SoundChannel import database.table.Table_Soundbank import database.table.Table_Users +import messageDB +import broadcastDB +import logDB +import soundchannelDB -/** - * A class to manage a connection to a MariaDB database. - * - * @property address The address of the MariaDB server. - * @property port The port number of the MariaDB server. - * @property dbName The name of the database to connect to. - * @property username The username for the database connection. - * @property password The password for the database connection. - */ -class MariaDB( - address: String = config.Get(configKeys.DATABASE_HOST.key), - port: Int = config.Get(configKeys.DATABASE_PORT.key).toInt(), - dbName: String = config.Get(configKeys.DATABASE_NAME.key), - username: String = config.Get(configKeys.DATABASE_USER.key), - password: String = config.Get(configKeys.DATABASE_PASSWORD.key) -) { +class MariaDB { var connected: Boolean = false - lateinit var connection: Connection + var connection: Connection? = null lateinit var soundDB: Table_Soundbank - lateinit var messageDB: Table_Messagebank lateinit var languageDB: Table_LanguageLink lateinit var scheduleDB: Table_Schedule - lateinit var broadcastDB: Table_BroadcastZones lateinit var queuetableDB: Table_QueueSoundbank lateinit var queuepagingDB: Table_QueuePaging - lateinit var soundchannelDB: Table_SoundChannel - lateinit var logDB: Table_Logs + + lateinit var userDB: Table_Users lateinit var logSemiAuto: Table_LogSemiAuto @@ -79,20 +66,10 @@ class MariaDB( } init { - try { - connection = - DriverManager.getConnection( - "jdbc:mysql://$address:$port/$dbName?sslMode=REQUIRED", - username, - password - ) as Connection - Logger.info("Connected to MySQL" as Any) - connected = true - - // create databas 'aas' if not exists - val statement = connection.createStatement() - statement.executeUpdate("CREATE DATABASE IF NOT EXISTS aas") - statement.executeUpdate("USE aas") + CreateConnection()?.let { connection -> + this.connection = connection + Logger.info { "Connected to MariaDB successfully"} + CreateDatabase(connection) soundDB = Table_Soundbank(connection) messageDB = Table_Messagebank(connection) @@ -123,9 +100,10 @@ class MariaDB( messageDB.Get() soundDB.Get() languageDB.Get() - scheduleDB.Get() broadcastDB.Get() soundchannelDB.Get() + scheduleDB.Get() + userDB.Get() } } @@ -140,10 +118,6 @@ class MariaDB( Logger.info { "BroadcastZones count: ${broadcastDB.List.size}" } Logger.info { "SoundChannel count: ${soundchannelDB.List.size}" } Logger.info { "User count: ${userDB.List.size}" } - - - } catch (e: Exception) { - Logger.error("Failed to connect to MariaDB: ${e.message}" as Any) } } @@ -152,13 +126,58 @@ class MariaDB( */ fun close() { try { - connection.close() - + this.connection?.close() Logger.info("Connection to MariaDB closed" as Any) } catch (e: Exception) { Logger.error("Error closing MariaDB connection: ${e.message}" as Any) + } finally { + this.connection = null + connected = false } + + } + + /** + * Creates a new connection to the MariaDB database. + * @return A Connection object if successful, null otherwise. + */ + fun CreateConnection(): Connection? { + val address: String = config.Get(configKeys.DATABASE_HOST.key) + val port: Int = config.Get(configKeys.DATABASE_PORT.key).toInt() + val dbName: String = config.Get(configKeys.DATABASE_NAME.key) + val username: String = config.Get(configKeys.DATABASE_USER.key) + val password: String = config.Get(configKeys.DATABASE_PASSWORD.key) connected = false + try{ + val cc = DriverManager.getConnection( + "jdbc:mysql://$address:$port/$dbName?sslMode=REQUIRED", + username, + password + ) + connected = true + return cc + } catch (e : Exception){ + Logger.error("Failed to create connection to MariaDB: ${e.message}" as Any) + return null + } + } + + /** + * Creates the database if it does not exist. + * @param connection The Connection object to use for creating the database. + * @return True if the database was created or already exists, false otherwise. + */ + fun CreateDatabase(connection: Connection) : Boolean{ + try { + // create databas 'aas' if not exists + val statement = connection.createStatement() + statement.executeUpdate("CREATE DATABASE IF NOT EXISTS aas") + statement.executeUpdate("USE aas") + return true + } catch (e : Exception){ + Logger.error { "Error creating database: ${e.message}" } + } + return false } diff --git a/src/database/dbFunctions.kt b/src/database/dbFunctions.kt index 347b1a8..09988fc 100644 --- a/src/database/dbFunctions.kt +++ b/src/database/dbFunctions.kt @@ -6,8 +6,30 @@ import java.sql.Connection import java.util.function.Consumer @Suppress("unused", "SqlDialectInspection", "SqlSourceToSinkFlow") -abstract class dbFunctions(val dbName: String, val connection: Connection, requiredcolumns: List) { +abstract class dbFunctions(val dbName: String, conn: Connection, requiredcolumns: List) { var List : ArrayList = ArrayList() + var connection = conn + + /** + * Check if the database connection is valid + * @return true if valid, false otherwise + */ + fun IsConnected() : Boolean{ + return try { + connection.isValid(2) + } catch (e: Exception) { + Logger.error("Database connection is not valid: ${e.message}" as Any) + false + } + } + + /** + * Change the database connection + * @param newcon The new Connection object + */ + fun ChangeConnection(newcon: Connection){ + connection = newcon + } init{ val columns = GetColumnInfo() diff --git a/src/database/table/Table_BroadcastZones.kt b/src/database/table/Table_BroadcastZones.kt index f989c14..0df8d5e 100644 --- a/src/database/table/Table_BroadcastZones.kt +++ b/src/database/table/Table_BroadcastZones.kt @@ -1,9 +1,14 @@ package database.table +import StreamerOutputs +import broadcastDB +import codes.Somecodes.Companion.ValidIPV4 +import codes.Somecodes.Companion.ValidString import database.data.BroadcastZones import database.dbFunctions import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.tinylog.Logger +import soundchannelDB import java.sql.Connection import java.util.function.Consumer @@ -200,4 +205,129 @@ class Table_BroadcastZones(connection: Connection) : dbFunctions .map { it.description } .sorted() } + + data class InvalidZoneDetail(val zonename: String, val reason: String) + data class ValidZoneDetail( + val zonename: String, + val soundchanel: String, + val ip: String, + val boxid: String, + val contacts: String + ) + + class CheckBroadcastZoneResult { + var allvalid: Boolean = false + var message: String? = null + var validzones = mutableListOf() + var invalidzones = mutableListOf() + } + + /** + * Check if all broadcast zones in a comma-separated string are valid + * Valid means the broadcast zone name exists in the BroadcastZones table, its SoundChannel exists in the SoundChannel table, and its IP is valid + * @param zones Comma-separated string of broadcast zones + * @param checkOnline Whether to check if the sound channel is really online, recorded in StreamerOutputs map + * @return true if all broadcast zones are valid, false otherwise + */ + fun AllBroadcastZonesValid(zones: String, checkOnline: Boolean = true) : Boolean{ + val bzlist = zones.split(",",";").map { it.trim() }.filter { it.isNotEmpty() } + //println("Checking all broadcast zones validity for: $bzlist") + AllBroadcastZonesValid(bzlist, checkOnline).let { result -> + if (result.allvalid) { + //Logger.info("All broadcast zones are valid: ${bzlist.joinToString(", ")}" as Any) + return true + } else { + //Logger.warn("Some broadcast zones are invalid:" as Any) + result.invalidzones.forEach { iz -> + Logger.warn(" - Zone '${iz.zonename}' is invalid: ${iz.reason}" as Any) + } + return false + } + } + //return AllBroadcastZonesValid(bzlist).allvalid + } + + /** + * Fungsi untuk cek apakah semua broadcast zone valid + * Valid berarti nama broadcast zone ada di tabel BroadcastZones, dan SoundChannel-nya ada di tabel SoundChannel, dan IP-nya valid + * @param bz List of broadcast zone (SoundChannel) + * @param checkOnline Whether to check if the sound channel is really online, recorded in StreamerOutputs map + * @return CheckBroadcastZoneResult object containing allvalid flag and list of invalid zones + */ + fun AllBroadcastZonesValid(bz: List, checkOnline: Boolean = true): CheckBroadcastZoneResult { + val result = CheckBroadcastZoneResult() + if (bz.isNotEmpty()) { + bz.forEach { zz -> + if (ValidString(zz)) { // string tidak kosong + val findzone = List.find{ + ValidString(it.description) && ValidString(it.SoundChannel) && it.description.equals(zz,true) + } + + if (findzone != null) { // ketemu zona dengan deskripsi sesuai + val findsc = soundchannelDB.List.find { + findzone.SoundChannel.equals( + it.channel, + true + ) && ValidIPV4(it.ip) + } + if (findsc != null) { // ketemu soundchannel dengan channel sesuai dan IP valid + // check apakah offline atau online + if (checkOnline){ + if (StreamerOutputs.containsKey(findsc.ip)) { + val bc = StreamerOutputs[findsc.ip] + if (bc != null && bc.isOnline()) { + result.validzones.add( + ValidZoneDetail( + zz, + findzone.SoundChannel, + findsc.ip, + findzone.id, + findzone.bp + ) + ) + } else result.invalidzones.add( + InvalidZoneDetail( + zz, + "SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is offline" + ) + ) + } else result.invalidzones.add( + InvalidZoneDetail( + zz, + "SoundChannel ${findzone.SoundChannel} with IP ${findsc.ip} is not connected in StreamerOutputs" + ) + ) + } else { + result.validzones.add( + ValidZoneDetail( + zz, + findzone.SoundChannel, + findsc.ip, + findzone.id, + findzone.bp + ) + ) + } + + } else result.invalidzones.add( + InvalidZoneDetail( + zz, + "SoundChannel ${findzone.SoundChannel} not found or has invalid IP in SoundChannel table" + ) + ) + } else result.invalidzones.add(InvalidZoneDetail(zz, "Zone $zz not found in BroadcastZones table")) + } else result.invalidzones.add(InvalidZoneDetail(zz, "Invalid broadcast zone string")) + } + + if (result.validzones.size == bz.size) { + result.allvalid = true + result.message = "All requested broadcast zones are valid" + } else { + result.message = "Some requested broadcast zones are not registered in BroadcastZone table" + } + } else { + result.message = "No Broadcast Zones checked for validity" + } + return result + } } \ No newline at end of file diff --git a/src/database/table/Table_Logs.kt b/src/database/table/Table_Logs.kt index 1c52c1a..41ee889 100644 --- a/src/database/table/Table_Logs.kt +++ b/src/database/table/Table_Logs.kt @@ -2,10 +2,14 @@ package database.table import database.data.Log import database.dbFunctions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.tinylog.Logger import java.sql.Connection import java.sql.Date +import java.sql.Statement import java.time.LocalDate import java.time.format.DateTimeFormatter import java.util.function.Consumer @@ -47,33 +51,42 @@ class Table_Logs(connection: Connection) : dbFunctions("logs", connection,l fun GetLogForHtml(date: String, filter: String?, cbOK: Consumer>?, cbFail: Consumer?){ try{ - val valid_date : Date? = when{ - dateformat1.matches(date) -> { - Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy"))) + var statement : Statement? + if ("alldate" == date){ + if (filter.isNullOrEmpty()) throw Exception("Filter is required when date is 'alldate'") + statement = connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE description LIKE ?") + statement?.setString(1, "%$filter%") + } else { + val valid_date : Date? = when{ + dateformat1.matches(date) -> { + Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd/MM/yyyy"))) + } + dateformat2.matches(date) -> { + Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd-MM-yyyy"))) + } + dateformat3.matches(date) -> { + Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy/MM/dd"))) + } + dateformat4.matches(date) -> { + Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"))) + } + else -> null } - dateformat2.matches(date) -> { - Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("dd-MM-yyyy"))) - } - dateformat3.matches(date) -> { - Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy/MM/dd"))) - } - dateformat4.matches(date) -> { - Date.valueOf(LocalDate.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd"))) - } - else -> null + if (valid_date!=null){ + // use coalescing for different datenya formats + statement = if (filter.isNullOrEmpty()){ + connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ?") + } else { + connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ? AND description LIKE ?") + } + statement?.setDate(1, valid_date) + if (!filter.isNullOrEmpty()){ + statement?.setString(2, "%$filter%") + } + } else throw Exception("Invalid date") } - if (valid_date!=null){ - // use coalescing for different datenya formats - val statement = if (filter.isNullOrEmpty()){ - connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ?") - } else { - connection.prepareStatement("SELECT * FROM ${super.dbName} WHERE COALESCE(STR_TO_DATE(datenya,'%d/%m/%Y'), STR_TO_DATE(datenya,'%d-%m-%Y'), STR_TO_DATE(datenya,'%Y/%m/%d'), STR_TO_DATE(datenya,'%Y-%m-%d')) = ? AND description LIKE ?") - } - statement?.setDate(1, valid_date) - if (!filter.isNullOrEmpty()){ - statement?.setString(2, "%$filter%") - } - val resultSet = statement?.executeQuery() + if (statement!=null){ + val resultSet = statement.executeQuery() val tempList = ArrayList() while (resultSet?.next() == true) { val log = Log( @@ -86,7 +99,7 @@ class Table_Logs(connection: Connection) : dbFunctions("logs", connection,l tempList.add(log) } cbOK?.accept(tempList) - } else throw Exception("Invalid date") + } else throw Exception("Failed to prepare statement") } catch (e : Exception){ if (filter.isNullOrEmpty()){ cbFail?.accept("Failed to Get logs for date $date: ${e.message}") @@ -94,29 +107,38 @@ class Table_Logs(connection: Connection) : dbFunctions("logs", connection,l cbFail?.accept("Failed to Get logs for date $date with filter $filter: ${e.message}") } } + } + /**** + * Fetches all log entries from the database and populates the local List. + * @param cbOK Optional callback invoked upon successful retrieval. + * @param cbFail Optional callback invoked upon failure with an error message. + */ override fun Get(cbOK: Consumer?, cbFail: Consumer?) { - List.clear() - try { - val statement = connection.createStatement() - val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}") - while (resultSet?.next() == true) { - val log = Log( - resultSet.getLong("index").toULong(), - resultSet.getString("datenya"), - resultSet.getString("timenya"), - resultSet.getString("machine"), - resultSet.getString("description") - ) - List.add(log) - } - cbOK?.accept(Unit) - } catch (e: Exception) { - cbFail?.accept("Error fetching ${super.dbName}: ${e.message}") - Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any) + CoroutineScope(Dispatchers.IO).launch { + List.clear() + try { + val statement = connection.createStatement() + val resultSet = statement?.executeQuery("SELECT * FROM ${super.dbName}") + while (resultSet?.next() == true) { + val log = Log( + resultSet.getLong("index").toULong(), + resultSet.getString("datenya"), + resultSet.getString("timenya"), + resultSet.getString("machine"), + resultSet.getString("description") + ) + List.add(log) + } + cbOK?.accept(Unit) + } catch (e: Exception) { + cbFail?.accept("Error fetching ${super.dbName}: ${e.message}") + Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any) + } } + } fun Add(machine: String, description: String): Boolean { diff --git a/src/database/table/Table_Messagebank.kt b/src/database/table/Table_Messagebank.kt index 8f6f3ba..cfc2a90 100644 --- a/src/database/table/Table_Messagebank.kt +++ b/src/database/table/Table_Messagebank.kt @@ -236,4 +236,26 @@ class Table_Messagebank(connection: Connection) : dbFunctions("mess .map { it.ANN_ID } .sorted() } + + // valid messagedetail is message_name [ann_id] + // so we need regex to check if messagedetail matches that format + private val messageDetailRegex = """^(.*?)\s*\[(\d+)]$""".toRegex() + + /** + * Check if a messagebank entry exists based on messagedetail and languages + * @param messagedetail the messagedetail in format "message_name [ann_id]" + * @param languages a comma or semicolon separated string of languages to check + * @return true if the messagebank entry exists for all specified languages, false otherwise + */ + fun Messagebank_Exists(messagedetail: String, languages: String) : Boolean{ + val match = messageDetailRegex.find(messagedetail) + val ll = languages.split(",",";").map { it.trim() } + if (match != null){ + val msg = match.groupValues[1].trim() + val annid = match.groupValues[2].toUIntOrNull() ?: return false // kalau bukan number, return false + val ff = List.filter{ it.ANN_ID == annid && it.Description == msg } + return ll.all{ lang -> ff.any{ it.Language.equals(lang, ignoreCase = true) } } + } + return false + } } \ No newline at end of file diff --git a/src/database/table/Table_Schedule.kt b/src/database/table/Table_Schedule.kt index 3ef7cd7..d9c6f47 100644 --- a/src/database/table/Table_Schedule.kt +++ b/src/database/table/Table_Schedule.kt @@ -1,6 +1,9 @@ package database.table +import codes.Somecodes.Companion.ValidLanguage import codes.Somecodes.Companion.ValidScheduleDay +import codes.Somecodes.Companion.ValidScheduleTime +import content.ScheduleDay import database.MariaDB.Companion.ValidTime import database.data.ScheduleBank import database.dbFunctions @@ -9,7 +12,54 @@ import org.tinylog.Logger import java.sql.Connection import java.util.function.Consumer +import broadcastDB +import codes.Somecodes +import messageDB +import java.time.LocalDate + class Table_Schedule(connection: Connection) : dbFunctions("schedulebank", connection, listOf("index", "Description", "Day", "Time", "Soundpath", "Repeat", "Enable", "BroadcastZones", "Language")) { + + /** + * A list to hold today's schedule entries. + */ + val todaySchedule = ArrayList() + + /** + * Update today's schedule + */ + fun UpdateTodaySchedule(){ + todaySchedule.clear() + + fun Find_Enabled_Schedules() : List{ + return List + .filter{it.Enable} // yang enabled saja + .filter{ValidScheduleTime(it.Time)} // yang timenya dalam format HH:MM + .filter{ValidLanguage(it.Language)} // yang bahasanya valid + .filter{broadcastDB.AllBroadcastZonesValid(it.BroadcastZones, false)} // yang broadcastzonesnya valid + // Soundpath ini coding typo, aslinya Messagebank description + .filter{messageDB.Messagebank_Exists(it.Soundpath, it.Language)} + } + val eligibleSchedule = Find_Enabled_Schedules() + + val tempMap = mutableMapOf() + + // prioritas paling rendah adalah everyday + eligibleSchedule.filter { it.Day == ScheduleDay.Everyday.day }.forEach { tempMap[it.Time] = it } + + // lebih tinggi adalah weekly, akan replace everyday jika time nya sama + val today_DOW = ScheduleDay.from_LocalDate_DOW(LocalDate.now().dayOfWeek) + eligibleSchedule.filter { it.Day == today_DOW.day }.forEach { tempMap[it.Time] = it } + + // paling tinggi adalah specific date, akan replace yang lain jika time nya sama + val today = Somecodes.Today_to_DateString() + eligibleSchedule.filter { it.Day == today }.forEach { tempMap[it.Time] = it } + + // masukin ke todaySchedule yang sudah di sort by Time + todaySchedule.addAll(tempMap.values.sortedBy { it.Time }) + + println("Todays schedule : $todaySchedule") + } + override fun Create() { val tabledefinition = "CREATE TABLE IF NOT EXISTS ${super.dbName} (" + "`index` INT AUTO_INCREMENT PRIMARY KEY," + @@ -44,6 +94,7 @@ class Table_Schedule(connection: Connection) : dbFunctions("schedu ) List.add(schedulebank) } + UpdateTodaySchedule() cbOK?.accept(Unit) } catch (e: Exception) { Logger.error("Error fetching ${super.dbName}: ${e.message}" as Any) diff --git a/src/web/WebApp.kt b/src/web/WebApp.kt index 690acf6..7307af2 100644 --- a/src/web/WebApp.kt +++ b/src/web/WebApp.kt @@ -2,6 +2,7 @@ package web import StreamerOutputs import barix.BarixConnection +import broadcastDB import codes.Somecodes import codes.Somecodes.Companion.GetAppUpTime import codes.Somecodes.Companion.GetSensorsInfo @@ -49,7 +50,10 @@ import google.GoogleTTS import google.autoadd import google.fileoperation import io.javalin.websocket.WsCloseStatus +import logDB +import messageDB import org.tinylog.Logger +import soundchannelDB import version import java.io.File import java.nio.ByteBuffer @@ -730,7 +734,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val db.soundDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted sound bank with index $index") + logDB.Add("AAS", "Deleted sound bank with index $index") } else { it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to delete soundbank with index $index"))) @@ -863,15 +867,15 @@ class WebApp(val listenPort: Int, var userlist: List>, val path("MessageBank") { get("List") { ctx -> // get messagebank list - db.messageDB.Get({ - ctx.result(MariaDB.ArrayListtoString(db.messageDB.List)) + messageDB.Get({ + ctx.result(MariaDB.ArrayListtoString(messageDB.List)) }, { msgFail -> ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail))) }) } get("MessageIDs") { ctx -> - val value = db.messageDB.List + val value = messageDB.List .distinctBy { it.ANN_ID } .sortedBy { it.ANN_ID } .map { KeyValueMessage(it.ANN_ID.toString(), it.Description) } @@ -902,10 +906,10 @@ class WebApp(val listenPort: Int, var userlist: List>, val message_tags ) val existed = - db.messageDB.List.any { it.ANN_ID == mb.ANN_ID && it.Language == mb.Language && it.Voice_Type == mb.Voice_Type } + messageDB.List.any { it.ANN_ID == mb.ANN_ID && it.Language == mb.Language && it.Voice_Type == mb.Voice_Type } if (!existed) { - if (db.messageDB.Add(mb)) { - db.messageDB.Resort() + if (messageDB.Add(mb)) { + messageDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to add messagebank to database"))) @@ -926,8 +930,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val } delete("List") { // truncate messagebank table - if (db.messageDB.Clear()) { - db.messageDB.Get() + if (messageDB.Clear()) { + messageDB.Get() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else { it.status(500) @@ -941,10 +945,10 @@ class WebApp(val listenPort: Int, var userlist: List>, val it.status(400) .result(objectmapper.writeValueAsString(resultMessage("Invalid index"))) } else { - if (db.messageDB.DeleteByIndex(index.toInt())) { - db.messageDB.Resort() + if (messageDB.DeleteByIndex(index.toInt())) { + messageDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted message bank with index $index") + logDB.Add("AAS", "Deleted message bank with index $index") } else { it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to delete messagebank with index $index"))) @@ -958,7 +962,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val it.status(400) .result(objectmapper.writeValueAsString(resultMessage("Invalid index"))) } else { - val mb = db.messageDB.List.find { xx -> xx.index == index } + val mb = messageDB.List.find { xx -> xx.index == index } if (mb == null) { it.status(404) .result(objectmapper.writeValueAsString(resultMessage("Messagebank with index $index not found"))) @@ -1007,8 +1011,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val changed = true } if (changed) { - if (db.messageDB.UpdateByIndex(index.toInt(), mb)) { - db.messageDB.Resort() + if (messageDB.UpdateByIndex(index.toInt(), mb)) { + messageDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to update messagebank with index $index"))) @@ -1019,7 +1023,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val } } get("ExportXLSX") { - val xlsxdata = db.messageDB.Export_XLSX() + val xlsxdata = messageDB.Export_XLSX() if (xlsxdata != null) { it.header( "Content-Type", @@ -1043,8 +1047,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val } try { val xlsx = XSSFWorkbook(uploaded.content()) - if (db.messageDB.Import_XLSX(xlsx)) { - db.messageDB.Resort() + if (messageDB.Import_XLSX(xlsx)) { + messageDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else { it.status(500) @@ -1133,7 +1137,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val if (db.languageDB.DeleteByIndex(index.toInt())) { db.languageDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted language link with index $index") + logDB.Add("AAS", "Deleted language link with index $index") } else { it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to delete language link with index $index"))) @@ -1219,11 +1223,14 @@ class WebApp(val listenPort: Int, var userlist: List>, val } } path("ScheduleBank") { + get("TodaySchedule"){ ctx -> + ctx.json(db.scheduleDB.todaySchedule) + } get("List") { ctx -> db.scheduleDB.Get({ // get timer list - ctx.result(MariaDB.ArrayListtoString(db.scheduleDB.List)) + ctx.json(db.scheduleDB.List) }, { msgFail -> ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail))) }) @@ -1257,7 +1264,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val if (ValidString(broadcast_zones)) { val zones = broadcast_zones.split(";") if (zones.all { zz -> - db.broadcastDB.List.any { xx -> + broadcastDB.List.any { xx -> xx.description.equals( zz, true @@ -1318,7 +1325,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val if (db.scheduleDB.DeleteByIndex(index.toInt())) { db.scheduleDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted schedule bank with index $index") + logDB.Add("AAS", "Deleted schedule bank with index $index") } else { it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to delete schedule with index $index"))) @@ -1407,21 +1414,21 @@ class WebApp(val listenPort: Int, var userlist: List>, val } get("GetMessageAndBroadcastZones") { val result = object { - val messages = db.messageDB.List + val messages = messageDB.List .filter { mb -> !mb.Message_Detail.contains("[") && !mb.Message_Detail.contains( "]" ) } .map { mb -> "${mb.Description} [${mb.ANN_ID}]" } - val broadcastzones = db.broadcastDB.List + val broadcastzones = broadcastDB.List } it.result(objectmapper.writeValueAsString(result)) } // Kirim list language dari Messagebank berdasarkan ANN_ID get("GetLanguageList/{ANN_ID}") { get1 -> - val langlist = db.messageDB.List + val langlist = messageDB.List .filter { it.ANN_ID == get1.pathParam("ANN_ID").toInt().toUInt() } .map { it.Language }.distinct() get1.result(objectmapper.writeValueAsString(langlist)) @@ -1525,7 +1532,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val .map { it.trim() } .filter { it.isNotEmpty() }.distinct() .mapNotNull { it.toUIntOrNull() } - val mbankids = db.messageDB.Get_MessageID_List() + val mbankids = messageDB.Get_MessageID_List() if (!mbids.all { id -> mbankids.any { it == id } }) { ctx.status(400) .result(objectmapper.writeValueAsString(resultMessage("Some ANN_ID not found in Messagebank"))) @@ -1537,7 +1544,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val val bzdesc = broadcastzones.split(";").map { it.trim() } .filter { it.isNotEmpty() }.distinct() - val bzlist = db.broadcastDB.Get_BroadcastZone_List() + val bzlist = broadcastDB.Get_BroadcastZone_List() val missing_broadcastzones = ArrayList() bzdesc.forEach { bz -> if (!bzlist.any { it.equals(bz, true) }) { @@ -1594,7 +1601,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val if (db.userDB.DeleteByIndex(index.toInt())) { db.userDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted user with index $index") + logDB.Add("AAS", "Deleted user with index $index") } else it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to delete user with index $index"))) } @@ -1700,7 +1707,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val .map { it.trim() } .filter { it.isNotEmpty() }.distinct() .mapNotNull { it.toUIntOrNull() } - val mbankids = db.messageDB.Get_MessageID_List() + val mbankids = messageDB.Get_MessageID_List() val missing_broadcastids = ArrayList() mbids.forEach { mbid -> if (!mbankids.any { it == mbid }) { @@ -1727,7 +1734,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val val bzdesc = _broadcastzones.split(";").map { it.trim() } .filter { it.isNotEmpty() }.distinct() - val bzlist = db.broadcastDB.Get_BroadcastZone_List() + val bzlist = broadcastDB.Get_BroadcastZone_List() val missing_broadcastzones = ArrayList() bzdesc.forEach { bz -> if (!bzlist.any { it.equals(bz, true) }) { @@ -1832,8 +1839,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val get("GetMessageAndBroadcastZones") { val result = object { - val messages = db.messageDB.List - val broadcastzones = db.broadcastDB.List + val messages = messageDB.List + val broadcastzones = broadcastDB.List } it.result(objectmapper.writeValueAsString(result)) } @@ -1842,8 +1849,10 @@ class WebApp(val listenPort: Int, var userlist: List>, val get("List") { get1 -> val logdate = get1.queryParam("date") ?: "" val logfilter = get1.queryParam("filter") - db.logDB.GetLogForHtml(logdate, logfilter, { loglist -> - get1.result(objectmapper.writeValueAsString(loglist)) + Logger.info{"Client ${get1.ip()} requested log list for date $logdate with filter $logfilter"} + logDB.GetLogForHtml(logdate, logfilter, { loglist -> + get1.contentType("application/json") + get1.json(loglist) }, { msgFail -> get1.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail))) }) @@ -1853,9 +1862,9 @@ class WebApp(val listenPort: Int, var userlist: List>, val val logfilter = get1.queryParam("filter") ?: "" if (ValiDateForLogHtml(logdate)) { val xlsxdata = if (ValidString(logfilter)) { - db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), logfilter) + logDB.Export_Log_XLSX(logdate.replace('-', '/'), logfilter) } else { - db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), "") + logDB.Export_Log_XLSX(logdate.replace('-', '/'), "") } if (xlsxdata != null) { get1.header( @@ -1876,22 +1885,22 @@ class WebApp(val listenPort: Int, var userlist: List>, val } path("BroadcastZones") { get("List") { ctx -> - db.broadcastDB.Get({ - ctx.result(MariaDB.ArrayListtoString(db.broadcastDB.List)) + broadcastDB.Get({ + ctx.json(broadcastDB.List) }, { msgFail -> ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail))) }) } get("BroadcastZoneDescriptions") { ctx -> - val value = db.broadcastDB.List + val value = broadcastDB.List .distinctBy { it.description } .map { it.description } ctx.result(objectmapper.writeValueAsString(value)) } delete("List") { // truncate broadcast zones table - if (db.broadcastDB.Clear()) { - db.broadcastDB.Get() + if (broadcastDB.Clear()) { + broadcastDB.Get() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else { it.status(500) @@ -1910,8 +1919,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val if (ValidString(_relay)) { val newbp = BroadcastZones(0u, _description, _soundchannel, _box, _relay) - if (db.broadcastDB.Add(newbp)) { - db.broadcastDB.Resort() + if (broadcastDB.Add(newbp)) { + broadcastDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to add broadcast zone to database"))) @@ -1931,10 +1940,10 @@ class WebApp(val listenPort: Int, var userlist: List>, val it.status(400) .result(objectmapper.writeValueAsString(resultMessage("Invalid index"))) } else { - if (db.broadcastDB.DeleteByIndex(index.toInt())) { - db.broadcastDB.Resort() + if (broadcastDB.DeleteByIndex(index.toInt())) { + broadcastDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted broadcast zone with index $index") + logDB.Add("AAS", "Deleted broadcast zone with index $index") } else { it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to delete broadcast zone with index $index"))) @@ -1948,7 +1957,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val it.status(400) .result(objectmapper.writeValueAsString(resultMessage("Invalid index"))) } else { - val bz = db.broadcastDB.List.find { xx -> xx.index == index } + val bz = broadcastDB.List.find { xx -> xx.index == index } if (bz == null) { it.status(404) .result(objectmapper.writeValueAsString(resultMessage("Broadcast zone with index $index not found"))) @@ -1980,8 +1989,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val changed = true } if (changed) { - if (db.broadcastDB.UpdateByIndex(index.toInt(), bz)) { - db.broadcastDB.Resort() + if (broadcastDB.UpdateByIndex(index.toInt(), bz)) { + broadcastDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to update broadcast zone with index $index"))) @@ -1993,7 +2002,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val } get("ExportXLSX") { - val xlsxdata = db.broadcastDB.Export_XLSX() + val xlsxdata = broadcastDB.Export_XLSX() if (xlsxdata != null) { it.header( "Content-Type", @@ -2017,8 +2026,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val } try { val xlsx = XSSFWorkbook(uploaded.content()) - if (db.broadcastDB.Import_XLSX(xlsx)) { - db.broadcastDB.Resort() + if (broadcastDB.Import_XLSX(xlsx)) { + broadcastDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else { it.status(500) @@ -2033,20 +2042,20 @@ class WebApp(val listenPort: Int, var userlist: List>, val } path("SoundChannel") { get("List") { ctx -> - db.soundchannelDB.Get({ - ctx.result(MariaDB.ArrayListtoString(db.soundchannelDB.List)) + soundchannelDB.Get({ + ctx.json(soundchannelDB.List) }, { msgFail -> ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail))) }) } get("SoundChannelDescriptions") { - it.result(objectmapper.writeValueAsString(db.soundchannelDB.Get_SoundChannel_List())) + it.result(objectmapper.writeValueAsString(soundchannelDB.Get_SoundChannel_List())) } delete("List") { // truncate sound channel table - if (db.soundchannelDB.Clear()) { - db.soundchannelDB.Get() + if (soundchannelDB.Clear()) { + soundchannelDB.Get() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else { it.status(500) @@ -2060,7 +2069,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val it.status(400) .result(objectmapper.writeValueAsString(resultMessage("Invalid index"))) } else { - val sc = db.soundchannelDB.List.find { xx -> xx.index == index } + val sc = soundchannelDB.List.find { xx -> xx.index == index } if (sc == null) { it.status(404) .result(objectmapper.writeValueAsString(resultMessage("Sound channel with index $index not found"))) @@ -2082,7 +2091,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val // cek apakah ada soundchannel lain yang pakai ip dan channel yang sama val othersc = - db.soundchannelDB.List.filter { sc -> sc.ip == _ip } + soundchannelDB.List.filter { sc -> sc.ip == _ip } .filter { sc -> sc.index != index } if (othersc.isNotEmpty()) { it.status(400) @@ -2090,8 +2099,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val } else { // ada sesuatu yang ganti val newsc = SoundChannel(0u, _channel, _ip) - if (db.soundchannelDB.UpdateByIndex(index.toInt(), newsc)) { - db.soundchannelDB.Resort() + if (soundchannelDB.UpdateByIndex(index.toInt(), newsc)) { + soundchannelDB.Resort() it.result( objectmapper.writeValueAsString( resultMessage( @@ -2128,7 +2137,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val } get("ExportXLSX") { - val xlsxdata = db.soundchannelDB.Export_XLSX() + val xlsxdata = soundchannelDB.Export_XLSX() if (xlsxdata != null) { it.header( "Content-Type", @@ -2152,8 +2161,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val } try { val xlsx = XSSFWorkbook(uploaded.content()) - if (db.soundchannelDB.Import_XLSX(xlsx)) { - db.soundchannelDB.Resort() + if (soundchannelDB.Import_XLSX(xlsx)) { + soundchannelDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) } else { it.status(500) @@ -2169,7 +2178,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val path("QueuePaging") { get("List") { ctx -> db.queuepagingDB.Get({ - ctx.result(MariaDB.ArrayListtoString(db.queuepagingDB.List)) + ctx.json(db.queuepagingDB.List) }, { msgFail -> ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail))) }) @@ -2195,7 +2204,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val if (db.queuepagingDB.DeleteByIndex(index.toInt())) { db.queuepagingDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted queue paging with index $index") + logDB.Add("AAS", "Deleted queue paging with index $index") } else { it.status(500) .result(objectmapper.writeValueAsString(resultMessage("Failed to delete queue paging with index $index"))) @@ -2207,7 +2216,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val path("QueueTable") { get("List") { ctx -> db.queuetableDB.Get({ - ctx.result(MariaDB.ArrayListtoString(db.queuetableDB.List)) + ctx.json(db.queuetableDB.List) }, { msgFail -> ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(msgFail))) }) @@ -2233,7 +2242,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val if (db.queuetableDB.DeleteByIndex(index.toInt())) { db.queuetableDB.Resort() it.result(objectmapper.writeValueAsString(resultMessage("OK"))) - db.logDB.Add("AAS", "Deleted queue sound with index $index") + logDB.Add("AAS", "Deleted queue sound with index $index") } else { it.status(500) @@ -2521,7 +2530,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val _config.Set(configKeys.DEFAULT_VOICE_TYPE.key, defaultvoice) _config.Save() Logger.info { "Changed FIS Codes" } - db.logDB.Add( + logDB.Add( "AAS", "Save FIS Codes Message: GOP=$_gop, GBD=$_gbd, GFC=$_gfc, FLD=$_fld, DefaultVoice=$defaultvoice" ) @@ -2636,7 +2645,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val // messages String_To_List(user.messagebank_ann_id).forEach { msg -> //println("Looking for message ANN_ID: $msg") - db.messageDB.List.filter { it.ANN_ID == msg.toUInt() }.forEach { xx -> + messageDB.List.filter { it.ANN_ID == msg.toUInt() }.forEach { xx -> //println("Adding message: ${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}") result.messages.add("${xx.ANN_ID};${xx.Description};${xx.Language};${xx.Message_Detail}") } @@ -2755,9 +2764,9 @@ class WebApp(val listenPort: Int, var userlist: List>, val val logfilter = get1.queryParam("filter") ?: "" if (ValiDateForLogHtml(logdate)) { val xlsxdata = if (ValidString(logfilter)) { - db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), logfilter) + logDB.Export_Log_XLSX(logdate.replace('-', '/'), logfilter) } else { - db.logDB.Export_Log_XLSX(logdate.replace('-', '/'), "") + logDB.Export_Log_XLSX(logdate.replace('-', '/'), "") } if (xlsxdata != null) { get1.header( @@ -2780,7 +2789,7 @@ class WebApp(val listenPort: Int, var userlist: List>, val val datelog = ctx.pathParam("datelog") println("Request log for date: $datelog") db.logSemiAuto.GetLogSemiAutoForHtml(datelog, null, { - ctx.result(MariaDB.ArrayListtoString(it)) + ctx.json(it) }, { err -> ctx.status(500).result(objectmapper.writeValueAsString(resultMessage(err))) }) @@ -2829,8 +2838,8 @@ class WebApp(val listenPort: Int, var userlist: List>, val 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 bz = broadcastDB.List.find { it.description == zonename } + val sc = if (bz != null) soundchannelDB.List.find { it.channel == bz.SoundChannel } else null val ip = sc?.ip ?: "" if (ValidIPV4(ip)) { // ketemu ip-nya