Compare commits
92 Commits
30c10f863d
...
feature-we
| Author | SHA1 | Date | |
|---|---|---|---|
| cdcb02e976 | |||
| 17b4485e69 | |||
| 4d02ab6d07 | |||
| 8bd57f216a | |||
| 8978a0986d | |||
| bc3f5c5691 | |||
| e5d8d8059e | |||
| 1b84ec133b | |||
| 1a6b7de6ec | |||
| 4da5a2fb05 | |||
| 2ca7004b70 | |||
| 1563e233d6 | |||
| 2fe4a46e3e | |||
| a53270aaed | |||
| 4d3dc538bd | |||
| d2a2626fd5 | |||
| de54d142ae | |||
| 8c49bb827f | |||
| b15470845e | |||
| 5f57e1bf2e | |||
| a133f9a170 | |||
| 1d2c8d1307 | |||
| e426522380 | |||
| 110e6d5b12 | |||
| 470f61c79d | |||
| e78fb932b2 | |||
| e0cdf74dec | |||
| 41d6dd7f47 | |||
| fdc7556dd7 | |||
| 7f647fe9c3 | |||
| d549aee42c | |||
| 6b00bc7eb0 | |||
| 8409307631 | |||
| 2ad26c3ef6 | |||
| c0e920d1d5 | |||
| 0d6b4aa49e | |||
| efe243e440 | |||
| 86d50bdb6c | |||
| 1318bba397 | |||
| e8695c7a6f | |||
| d04a8bedd1 | |||
| 1c72c7577f | |||
| 748301a5cb | |||
| a8e5b027ef | |||
| 611745439f | |||
| cfb38556b5 | |||
| 13a45b706b | |||
| cf69c72f3c | |||
| 20dbc12b02 | |||
| 3768f4263b | |||
| 1e7adeba25 | |||
|
|
83a6ee9fd0 | ||
|
|
e0f3ac2094 | ||
| c55db5e4f7 | |||
|
|
54775641bb | ||
|
|
85776cce45 | ||
|
|
cf24c06b35 | ||
|
|
f18a0ca9cd | ||
|
|
21592c1405 | ||
|
|
e18a08ab6a | ||
|
|
7db10bd45a | ||
| 09de205afc | |||
| 1fcf64fd99 | |||
| 55f6a24cce | |||
| 59dd67acc8 | |||
| 85ccf05634 | |||
| 7c67fe90ee | |||
| 120c8e5276 | |||
| 09d074aa03 | |||
| b692e2c2c9 | |||
| 34fc71cfbc | |||
| f48ead1b44 | |||
| 40f462ce79 | |||
| c01c4e39fd | |||
| ff4f0fd742 | |||
| ea04f8d316 | |||
| ea1defa78e | |||
| 7100cf826d | |||
| ef9c17a65c | |||
| c73b181ef5 | |||
| 09b65738af | |||
| 4743b47a89 | |||
| 0c84449b77 | |||
| 4f0a7a1560 | |||
| d12a089eda | |||
| b743146a2c | |||
| 49e82aae0e | |||
| 901d553da9 | |||
| 9b1851aaa7 | |||
| 3d94fe3520 | |||
| a32a1d3300 | |||
| f6ae8e14c0 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -29,4 +29,10 @@ bin/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
## Soundbank directories ##
|
||||||
|
PagingResult/
|
||||||
|
SoundBank/
|
||||||
|
SoundbankResult/
|
||||||
|
logs/
|
||||||
6
.idea/copilot.data.migration.agent.xml
generated
Normal file
6
.idea/copilot.data.migration.agent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/copilot.data.migration.ask.xml
generated
Normal file
6
.idea/copilot.data.migration.ask.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AskMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Ask2AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/copilot.data.migration.edit.xml
generated
Normal file
6
.idea/copilot.data.migration.edit.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="EditMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/dataSources.xml
generated
9
.idea/dataSources.xml
generated
@@ -1,15 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
<data-source source="LOCAL" name="mariadB" uuid="a738dd17-8123-478b-81aa-6ecf4f890ccc">
|
<data-source source="LOCAL" name="mysql" uuid="6f68a2ce-92f6-4203-a8c0-f18965b0d627">
|
||||||
<driver-ref>mariadb</driver-ref>
|
<driver-ref>mysql.8</driver-ref>
|
||||||
<synchronize>true</synchronize>
|
<synchronize>true</synchronize>
|
||||||
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
|
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
|
||||||
<jdbc-url>jdbc:mariadb://localhost:3306/aas</jdbc-url>
|
<jdbc-url>jdbc:mysql://localhost:3306/aas</jdbc-url>
|
||||||
<jdbc-additional-properties>
|
<jdbc-additional-properties>
|
||||||
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
<property name="com.intellij.clouds.kubernetes.db.host.port" />
|
||||||
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
|
||||||
<property name="com.intellij.clouds.kubernetes.db.resource.type" value="Deployment" />
|
|
||||||
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
<property name="com.intellij.clouds.kubernetes.db.container.port" />
|
||||||
</jdbc-additional-properties>
|
</jdbc-additional-properties>
|
||||||
<working-dir>$ProjectFileDir$</working-dir>
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
|||||||
13
.idea/deviceManager.xml
generated
Normal file
13
.idea/deviceManager.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeviceTable">
|
||||||
|
<option name="columnSorters">
|
||||||
|
<list>
|
||||||
|
<ColumnSorterState>
|
||||||
|
<option name="column" value="Name" />
|
||||||
|
<option name="order" value="ASCENDING" />
|
||||||
|
</ColumnSorterState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,8 +1,14 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ClassName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ConstPropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="EnumEntryName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="FunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
<inspection_tool class="FunctionName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="LocalVariableName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="MethodNameSameAsClassName" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="MethodNameSameAsClassName" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="PropertyName" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="RedundantCast" enabled="false" level="WARNING" enabled_by_default="false" />
|
<inspection_tool class="RedundantCast" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
<option name="processCode" value="true" />
|
<option name="processCode" value="true" />
|
||||||
|
|||||||
20
.idea/jarRepositories.xml
generated
Normal file
20
.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="0dccb4d0-b5b2-4677-8ad3-caea8077052f" />
|
||||||
|
<option name="name" value="0dccb4d0-b5b2-4677-8ad3-caea8077052f" />
|
||||||
|
<option name="url" value="https://mvnrepository.com/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/libraries/fasterxml_jackson_core_databind.xml
generated
Normal file
12
.idea/libraries/fasterxml_jackson_core_databind.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="fasterxml.jackson.core.databind" type="repository">
|
||||||
|
<properties maven-id="com.fasterxml.jackson.core:jackson-databind:2.17.2" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-databind/2.17.2/jackson-databind-2.17.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-annotations/2.17.2/jackson-annotations-2.17.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-core/2.17.2/jackson-core-2.17.2.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
17
.idea/libraries/fasterxml_jackson_module_kotlin.xml
generated
Normal file
17
.idea/libraries/fasterxml_jackson_module_kotlin.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="fasterxml.jackson.module.kotlin" type="repository">
|
||||||
|
<properties maven-id="com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/module/jackson-module-kotlin/2.17.2/jackson-module-kotlin-2.17.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-databind/2.17.2/jackson-databind-2.17.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-core/2.17.2/jackson-core-2.17.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-annotations/2.17.2/jackson-annotations-2.17.2.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.7.22/kotlin-reflect-1.7.22.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.22/kotlin-stdlib-1.7.22.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.22/kotlin-stdlib-common-1.7.22.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
13
.idea/libraries/github_oshi_core.xml
generated
Normal file
13
.idea/libraries/github_oshi_core.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="github.oshi.core" type="repository">
|
||||||
|
<properties maven-id="com.github.oshi:oshi-core:6.9.0" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/github/oshi/oshi-core/6.9.0/oshi-core-6.9.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.17.0/jna-5.17.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.17.0/jna-platform-5.17.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
11
.idea/libraries/google_code_gson.xml
generated
Normal file
11
.idea/libraries/google_code_gson.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="google.code.gson" type="repository">
|
||||||
|
<properties maven-id="com.google.code.gson:gson:2.13.1" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
49
.idea/libraries/io_javalin.xml
generated
49
.idea/libraries/io_javalin.xml
generated
@@ -1,37 +1,28 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="io.javalin" type="repository">
|
<library name="io.javalin" type="repository">
|
||||||
<properties maven-id="io.javalin:javalin:5.4.2" />
|
<properties maven-id="io.javalin:javalin:LATEST" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/io/javalin/javalin/5.4.2/javalin-5.4.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/io/javalin/javalin/6.7.0/javalin-6.7.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.6/slf4j-api-2.0.6.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-server/11.0.14/jetty-server-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-server/11.0.25/jetty-server-11.0.25.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-http/11.0.25/jetty-http-11.0.25.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-util/11.0.25/jetty-util-11.0.25.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-io/11.0.25/jetty-io-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/toolchain/jetty-jakarta-servlet-api/5.0.2/jetty-jakarta-servlet-api-5.0.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/toolchain/jetty-jakarta-servlet-api/5.0.2/jetty-jakarta-servlet-api-5.0.2.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-http/11.0.14/jetty-http-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-server/11.0.25/websocket-jetty-server-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-util/11.0.14/jetty-util-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-servlet/11.0.25/jetty-servlet-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-io/11.0.14/jetty-io-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-security/11.0.25/jetty-security-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-webapp/11.0.14/jetty-webapp-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-webapp/11.0.25/jetty-webapp-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-servlet/11.0.14/jetty-servlet-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-xml/11.0.25/jetty-xml-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-security/11.0.14/jetty-security-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-api/11.0.25/websocket-jetty-api-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-xml/11.0.14/jetty-xml-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-common/11.0.25/websocket-jetty-common-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-server/11.0.14/websocket-jetty-server-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-core-common/11.0.25/websocket-core-common-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-common/11.0.14/websocket-jetty-common-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-servlet/11.0.25/websocket-servlet-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-core-common/11.0.14/websocket-core-common-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-core-server/11.0.25/websocket-core-server-11.0.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-servlet/11.0.14/websocket-servlet-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.25/kotlin-stdlib-jdk8-1.9.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-core-server/11.0.14/websocket-core-server-11.0.14.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.25/kotlin-stdlib-1.9.25.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-annotations/11.0.14/jetty-annotations-11.0.14.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-plus/11.0.14/jetty-plus-11.0.14.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/jakarta/transaction/jakarta.transaction-api/2.0.0/jakarta.transaction-api-2.0.0.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/jetty-jndi/11.0.14/jetty-jndi-11.0.14.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.4/asm-9.4.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.4/asm-commons-9.4.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.4/asm-tree-9.4.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/websocket/websocket-jetty-api/11.0.14/websocket-jetty-api-11.0.14.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.10/kotlin-stdlib-jdk8-1.7.10.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.7.10/kotlin-stdlib-1.7.10.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.10/kotlin-stdlib-common-1.7.10.jar!/" />
|
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.10/kotlin-stdlib-jdk7-1.7.10.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.25/kotlin-stdlib-jdk7-1.9.25.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
|||||||
22
.idea/libraries/kotlinx_coroutines_core.xml
generated
Normal file
22
.idea/libraries/kotlinx_coroutines_core.xml
generated
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="kotlinx-coroutines-core" type="repository">
|
||||||
|
<properties maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.9.0/kotlinx-coroutines-core-1.9.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.9.0/kotlinx-coroutines-core-jvm-1.9.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.0/kotlin-stdlib-2.0.0.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.9.0/kotlinx-coroutines-core-jvm-1.9.0-javadoc.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0-javadoc.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.0/kotlin-stdlib-2.0.0-javadoc.jar!/" />
|
||||||
|
</JAVADOC>
|
||||||
|
<SOURCES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.9.0/kotlinx-coroutines-core-1.9.0-sources.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.9.0/kotlinx-coroutines-core-jvm-1.9.0-sources.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0-sources.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.0/kotlin-stdlib-2.0.0-sources.jar!/" />
|
||||||
|
</SOURCES>
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
11
.idea/libraries/mysql_connector_j.xml
generated
Normal file
11
.idea/libraries/mysql_connector_j.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="mysql.connector.j" type="repository">
|
||||||
|
<properties maven-id="com.mysql:mysql-connector-j:8.4.0" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/mysql/mysql-connector-j/8.4.0/mysql-connector-j-8.4.0.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/com/google/protobuf/protobuf-java/3.25.1/protobuf-java-3.25.1.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
4
.idea/libraries/net_java_dev_jna.xml
generated
4
.idea/libraries/net_java_dev_jna.xml
generated
@@ -1,8 +1,8 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="net.java.dev.jna" type="repository">
|
<library name="net.java.dev.jna" type="repository">
|
||||||
<properties maven-id="net.java.dev.jna:jna:5.17.0" />
|
<properties maven-id="net.java.dev.jna:jna:5.18.1" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.17.0/jna-5.17.0.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.18.1/jna-5.18.1.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
|||||||
11
.idea/libraries/slf4j_simple.xml
generated
Normal file
11
.idea/libraries/slf4j_simple.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="slf4j.simple" type="repository">
|
||||||
|
<properties maven-id="org.slf4j:slf4j-simple:2.0.17" />
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/2.0.17/slf4j-simple-2.0.17.jar!/" />
|
||||||
|
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
6
.idea/libraries/tinylog_impl.xml
generated
6
.idea/libraries/tinylog_impl.xml
generated
@@ -1,9 +1,9 @@
|
|||||||
<component name="libraryTable">
|
<component name="libraryTable">
|
||||||
<library name="tinylog.impl" type="repository">
|
<library name="tinylog.impl" type="repository">
|
||||||
<properties maven-id="org.tinylog:tinylog-impl:2.6.2" />
|
<properties maven-id="org.tinylog:tinylog-impl:2.7.0" />
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/tinylog/tinylog-impl/2.6.2/tinylog-impl-2.6.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/tinylog/tinylog-impl/2.7.0/tinylog-impl-2.7.0.jar!/" />
|
||||||
<root url="jar://$MAVEN_REPOSITORY$/org/tinylog/tinylog-api/2.6.2/tinylog-api-2.6.2.jar!/" />
|
<root url="jar://$MAVEN_REPOSITORY$/org/tinylog/tinylog-api/2.7.0/tinylog-api-2.7.0.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
|||||||
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectInspectionProfilesVisibleTreeState">
|
<component name="ProjectInspectionProfilesVisibleTreeState">
|
||||||
<entry key="Project Default">
|
<entry key="Project Default">
|
||||||
|
|||||||
3
.idea/sqldialects.xml
generated
3
.idea/sqldialects.xml
generated
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="SqlDialectMappings">
|
<component name="SqlDialectMappings">
|
||||||
<file url="file://$PROJECT_DIR$/src/database/MariaDB.kt" dialect="GenericSQL" />
|
<file url="file://$PROJECT_DIR$/src/database/MariaDB.kt" dialect="MySQL" />
|
||||||
<file url="PROJECT" dialect="MariaDB" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
|
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/html" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/audiofiles" isTestSource="false" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
@@ -15,6 +17,24 @@
|
|||||||
<orderEntry type="library" name="tinylog.impl" level="project" />
|
<orderEntry type="library" name="tinylog.impl" level="project" />
|
||||||
<orderEntry type="library" name="net.java.dev.jna" level="project" />
|
<orderEntry type="library" name="net.java.dev.jna" level="project" />
|
||||||
<orderEntry type="library" name="io.javalin" level="project" />
|
<orderEntry type="library" name="io.javalin" level="project" />
|
||||||
<orderEntry type="library" name="mariadb.jdbc.java.client" level="project" />
|
<orderEntry type="library" name="kotlinx-coroutines-core" level="project" />
|
||||||
|
<orderEntry type="library" name="slf4j.simple" level="project" />
|
||||||
|
<orderEntry type="library" name="fasterxml.jackson.module.kotlin" level="project" />
|
||||||
|
<orderEntry type="library" name="fasterxml.jackson.core.databind" level="project" />
|
||||||
|
<orderEntry type="library" name="github.oshi.core" level="project" />
|
||||||
|
<orderEntry type="library" name="mysql.connector.j" level="project" />
|
||||||
|
<orderEntry type="module-library">
|
||||||
|
<library>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="file://C:/SLC/Apache POI" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="file://C:/SLC/Apache POI" />
|
||||||
|
</SOURCES>
|
||||||
|
<jarDirectory url="file://C:/SLC/Apache POI" recursive="false" />
|
||||||
|
<jarDirectory url="file://C:/SLC/Apache POI" recursive="false" type="SOURCES" />
|
||||||
|
</library>
|
||||||
|
</orderEntry>
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
BIN
audiofiles/chimedown.wav
Normal file
BIN
audiofiles/chimedown.wav
Normal file
Binary file not shown.
BIN
audiofiles/chimeup.wav
Normal file
BIN
audiofiles/chimeup.wav
Normal file
Binary file not shown.
BIN
audiofiles/silence1s.wav
Normal file
BIN
audiofiles/silence1s.wav
Normal file
Binary file not shown.
BIN
audiofiles/silencehalf.wav
Normal file
BIN
audiofiles/silencehalf.wav
Normal file
Binary file not shown.
5
html/webpage/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
5
html/webpage/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
html/webpage/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
6
html/webpage/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
79
html/webpage/assets/css/Login-Form-Basic-icons.css
Normal file
79
html/webpage/assets/css/Login-Form-Basic-icons.css
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
.bs-icon {
|
||||||
|
--bs-icon-size: .75rem;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: var(--bs-icon-size);
|
||||||
|
width: calc(var(--bs-icon-size) * 2);
|
||||||
|
height: calc(var(--bs-icon-size) * 2);
|
||||||
|
color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon-xs {
|
||||||
|
--bs-icon-size: 1rem;
|
||||||
|
width: calc(var(--bs-icon-size) * 1.5);
|
||||||
|
height: calc(var(--bs-icon-size) * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon-sm {
|
||||||
|
--bs-icon-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon-md {
|
||||||
|
--bs-icon-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon-lg {
|
||||||
|
--bs-icon-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon-xl {
|
||||||
|
--bs-icon-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon.bs-icon-primary {
|
||||||
|
color: var(--bs-white);
|
||||||
|
background: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon.bs-icon-primary-light {
|
||||||
|
color: var(--bs-primary);
|
||||||
|
background: rgba(var(--bs-primary-rgb), .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon.bs-icon-semi-white {
|
||||||
|
color: var(--bs-primary);
|
||||||
|
background: rgba(255, 255, 255, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon.bs-icon-rounded {
|
||||||
|
border-radius: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bs-icon.bs-icon-circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-relay {
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-time {
|
||||||
|
margin: 0.7rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-day {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class100 {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
88
html/webpage/assets/css/bss-overrides.css
Normal file
88
html/webpage/assets/css/bss-overrides.css
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
:root, [data-bs-theme=light] {
|
||||||
|
--bs-primary: #0d6efd;
|
||||||
|
--bs-primary-rgb: 13,110,253;
|
||||||
|
--bs-primary-text-emphasis: #052C65;
|
||||||
|
--bs-primary-bg-subtle: #CFE2FF;
|
||||||
|
--bs-primary-border-subtle: #9EC5FE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
--bs-btn-color: #fff;
|
||||||
|
--bs-btn-bg: #0d6efd;
|
||||||
|
--bs-btn-border-color: #0d6efd;
|
||||||
|
--bs-btn-hover-color: #fff;
|
||||||
|
--bs-btn-hover-bg: #0B5ED7;
|
||||||
|
--bs-btn-hover-border-color: #0A58CA;
|
||||||
|
--bs-btn-focus-shadow-rgb: 219,233,255;
|
||||||
|
--bs-btn-active-color: #fff;
|
||||||
|
--bs-btn-active-bg: #0A58CA;
|
||||||
|
--bs-btn-active-border-color: #0A53BE;
|
||||||
|
--bs-btn-disabled-color: #fff;
|
||||||
|
--bs-btn-disabled-bg: #0d6efd;
|
||||||
|
--bs-btn-disabled-border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary {
|
||||||
|
--bs-btn-color: #0d6efd;
|
||||||
|
--bs-btn-border-color: #0d6efd;
|
||||||
|
--bs-btn-focus-shadow-rgb: 13,110,253;
|
||||||
|
--bs-btn-hover-color: #fff;
|
||||||
|
--bs-btn-hover-bg: #0d6efd;
|
||||||
|
--bs-btn-hover-border-color: #0d6efd;
|
||||||
|
--bs-btn-active-color: #fff;
|
||||||
|
--bs-btn-active-bg: #0d6efd;
|
||||||
|
--bs-btn-active-border-color: #0d6efd;
|
||||||
|
--bs-btn-disabled-color: #0d6efd;
|
||||||
|
--bs-btn-disabled-bg: transparent;
|
||||||
|
--bs-btn-disabled-border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-4 {
|
||||||
|
margin-top: 1.5rem!important;
|
||||||
|
margin-bottom: 1.5rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-0 {
|
||||||
|
margin-top: 0!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.me-2 {
|
||||||
|
margin-right: .5rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-2 {
|
||||||
|
margin-bottom: .5rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3 {
|
||||||
|
margin-bottom: 1rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
margin-bottom: 1.5rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-5 {
|
||||||
|
margin-bottom: 3rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-7 {
|
||||||
|
margin-bottom: 6rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-auto {
|
||||||
|
margin-bottom: auto!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:768px) {
|
||||||
|
.me-md-auto {
|
||||||
|
margin-right: auto!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width:768px) {
|
||||||
|
.mb-md-0 {
|
||||||
|
margin-bottom: 0!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
629
html/webpage/assets/css/select2.min.css
vendored
Normal file
629
html/webpage/assets/css/select2.min.css
vendored
Normal file
@@ -0,0 +1,629 @@
|
|||||||
|
.select2-container {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-selection--single {
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
height: 28px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||||
|
display: block;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-selection--single .select2-selection__clear {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-selection--multiple {
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
min-height: 32px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-selection--multiple .select2-selection__rendered {
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-left: 8px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-search--inline {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-search--inline .select2-search__field {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
font-size: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-dropdown {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: -100000px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1051;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__options {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option {
|
||||||
|
padding: 6px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option[aria-selected] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--open .select2-dropdown {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--open .select2-dropdown--above {
|
||||||
|
border-bottom: none;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--open .select2-dropdown--below {
|
||||||
|
border-top: none;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search--dropdown {
|
||||||
|
display: block;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search--dropdown .select2-search__field {
|
||||||
|
padding: 4px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-search--dropdown.select2-search--hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-close-mask {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 99;
|
||||||
|
background-color: #fff;
|
||||||
|
filter: alpha(opacity=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-hidden-accessible {
|
||||||
|
border: 0 !important;
|
||||||
|
clip: rect(0 0 0 0) !important;
|
||||||
|
-webkit-clip-path: inset(50%) !important;
|
||||||
|
clip-path: inset(50%) !important;
|
||||||
|
height: 1px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
position: absolute !important;
|
||||||
|
width: 1px !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||||
|
color: #444;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__clear {
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||||
|
height: 26px;
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: 1px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__arrow b {
|
||||||
|
border-color: #888 transparent transparent transparent;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 5px 4px 0 4px;
|
||||||
|
height: 0;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -4px;
|
||||||
|
margin-top: -2px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
||||||
|
left: 1px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--disabled .select2-selection--single {
|
||||||
|
background-color: #eee;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||||
|
border-color: transparent transparent #888 transparent;
|
||||||
|
border-width: 0 4px 5px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--multiple {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
|
||||||
|
box-sizing: border-box;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--multiple .select2-selection__clear {
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: default;
|
||||||
|
float: left;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--focus .select2-selection--multiple {
|
||||||
|
border: solid black 1px;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--disabled .select2-selection--multiple {
|
||||||
|
background-color: #eee;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-search--dropdown .select2-search__field {
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-search--inline .select2-search__field {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results > .select2-results__options {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option[role=group] {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option[aria-disabled=true] {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option[aria-selected=true] {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option .select2-results__option {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
|
||||||
|
margin-left: -1em;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||||
|
margin-left: -2em;
|
||||||
|
padding-left: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||||
|
margin-left: -3em;
|
||||||
|
padding-left: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||||
|
margin-left: -4em;
|
||||||
|
padding-left: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
||||||
|
margin-left: -5em;
|
||||||
|
padding-left: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
||||||
|
background-color: #5897fb;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-results__group {
|
||||||
|
cursor: default;
|
||||||
|
display: block;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--single {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: 0;
|
||||||
|
background-image: -webkit-linear-gradient(top, #fff 50%, #eee 100%);
|
||||||
|
background-image: -o-linear-gradient(top, #fff 50%, #eee 100%);
|
||||||
|
background-image: linear-gradient(to bottom, #fff 50%, #eee 100%);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--single:focus {
|
||||||
|
border: 1px solid #5897fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--single .select2-selection__rendered {
|
||||||
|
color: #444;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--single .select2-selection__clear {
|
||||||
|
cursor: pointer;
|
||||||
|
float: right;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--single .select2-selection__arrow {
|
||||||
|
background-color: #ddd;
|
||||||
|
border: none;
|
||||||
|
border-left: 1px solid #aaa;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
height: 26px;
|
||||||
|
position: absolute;
|
||||||
|
top: 1px;
|
||||||
|
right: 1px;
|
||||||
|
width: 20px;
|
||||||
|
background-image: -webkit-linear-gradient(top, #eee 50%, #ccc 100%);
|
||||||
|
background-image: -o-linear-gradient(top, #eee 50%, #ccc 100%);
|
||||||
|
background-image: linear-gradient(to bottom, #eee 50%, #ccc 100%);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
|
||||||
|
border-color: #888 transparent transparent transparent;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 5px 4px 0 4px;
|
||||||
|
height: 0;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -4px;
|
||||||
|
margin-top: -2px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid #aaa;
|
||||||
|
border-radius: 0;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
left: 1px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open .select2-selection--single {
|
||||||
|
border: 1px solid #5897fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
||||||
|
border-color: transparent transparent #888 transparent;
|
||||||
|
border-width: 0 4px 5px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
|
||||||
|
border-top: none;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
background-image: -webkit-linear-gradient(top, #fff 0%, #eee 50%);
|
||||||
|
background-image: -o-linear-gradient(top, #fff 0%, #eee 50%);
|
||||||
|
background-image: linear-gradient(to bottom, #fff 0%, #eee 50%);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
|
||||||
|
border-bottom: none;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
background-image: -webkit-linear-gradient(top, #eee 50%, #fff 100%);
|
||||||
|
background-image: -o-linear-gradient(top, #eee 50%, #fff 100%);
|
||||||
|
background-image: linear-gradient(to bottom, #eee 50%, #fff 100%);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--multiple {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: text;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--multiple:focus {
|
||||||
|
border: 1px solid #5897fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
|
||||||
|
background-color: #e4e4e4;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: default;
|
||||||
|
float: left;
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
|
||||||
|
color: #888;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
||||||
|
float: right;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
||||||
|
margin-left: 2px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open .select2-selection--multiple {
|
||||||
|
border: 1px solid #5897fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
|
||||||
|
border-top: none;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
|
||||||
|
border-bottom: none;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-search--dropdown .select2-search__field {
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-search--inline .select2-search__field {
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-dropdown {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-dropdown--above {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-dropdown--below {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-results > .select2-results__options {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-results__option[role=group] {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-results__option[aria-disabled=true] {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
|
||||||
|
background-color: #3875d7;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic .select2-results__group {
|
||||||
|
cursor: default;
|
||||||
|
display: block;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--classic.select2-container--open .select2-dropdown {
|
||||||
|
border-color: #5897fb;
|
||||||
|
}
|
||||||
|
|
||||||
320
html/webpage/assets/css/styles.css
Normal file
320
html/webpage/assets/css/styles.css
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
body {
|
||||||
|
background-color: #f8f9fd;
|
||||||
|
/*background-color: #edf1fb;*/
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-header {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-button {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
/*display: flex;*/
|
||||||
|
/*align-items: center;*/
|
||||||
|
margin-top: -0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-header {
|
||||||
|
color: #2d3578;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-header {
|
||||||
|
background-color: #f0f2ff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-status-1 {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-status-2 {
|
||||||
|
background-color: #dce5f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neu-button {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: inset 4px 4px 10px #88a5bf7b, inset -4px -4px 10px #ffffff;
|
||||||
|
/*color: #4d4d4d;*/
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
/*padding: 15px 40px;*/
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
border: 1px solid #88a5bf7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neu-button:hover {
|
||||||
|
box-shadow: inset 2px 2px 5px #bcbcbc, inset -2px -2px 5px #ffffff, 2px 2px 5px #bcbcbc, -2px -2px 5px #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neu-button:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: inset 2px 2px 5px #bcbcbc, inset -2px -2px 5px #ffffff, 2px 2px 5px #bcbcbc, -2px -2px 5px #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-round-basic:focus {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: inset 4px 4px 10px #88a5bf7b, inset -4px -4px 10px #ffffff;
|
||||||
|
/*color: #4d4d4d;*/
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
border: 1px solid #88a5bf7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-round-basic {
|
||||||
|
border-radius: 08px;
|
||||||
|
box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
|
||||||
|
--bs-btn-hover-bg: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-round-basic:hover {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: inset 2px 2px 5px #bcbcbc, inset -2px -2px 5px #ffffff, 2px 2px 5px #bcbcbc, -2px -2px 5px #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-import, .color-import:hover, .color-import:focus {
|
||||||
|
color: var(--bs-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-remove, .color-remove:hover, .color-remove:focus {
|
||||||
|
color: var(--bs-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-edit, .color-edit:hover, .color-edit:focus {
|
||||||
|
color: var(--bs-primary-text-emphasis);
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-add, .color-add:hover, .color-add:focus {
|
||||||
|
color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-login {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: rgba(9, 30, 66, 0.25) 0px 4px 8px -2px, rgba(9, 30, 66, 0.08) 0px 0px 0px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-login {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: rgba(136, 165, 191, 0.48) 6px 2px 16px 0px, rgba(255, 255, 255, 0.8) -6px -2px 16px 0px;
|
||||||
|
--bs-btn-hover-bg: #5780f2;
|
||||||
|
background-color: #5278e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-add {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: .375rem .75rem;
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-add {
|
||||||
|
margin: 0.6rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class25 {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-login {
|
||||||
|
background-color: white;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.15) 0px 15px 25px, rgba(0, 0, 0, 0.05) 0px 5px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-menu {
|
||||||
|
color: #2c316d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-menu {
|
||||||
|
color: #2c316d !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav-item:hover {
|
||||||
|
background-color: #2d3578;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav-item:focus {
|
||||||
|
background-color: #4450b1;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-indicator {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-top-menu {
|
||||||
|
font-size: 2em!important;
|
||||||
|
font-weight: 200;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-status {
|
||||||
|
background-color: var(--card-color);
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) -4px 9px 25px -6px;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-status {
|
||||||
|
margin-bottom: 0rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-container {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bread-menu {
|
||||||
|
background: #2d3578;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-card {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-search {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-row-search {
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-heading1 {
|
||||||
|
background-color: #c6d8ee;
|
||||||
|
color: #2d3578;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-heading2 {
|
||||||
|
background-color: #dce5f4;
|
||||||
|
color: #2d3578;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-heading3 {
|
||||||
|
background-color: #e9ecf8;
|
||||||
|
color: #2d3578;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-item {
|
||||||
|
/*background: rgba(255, 255, 255, 0.55);*/
|
||||||
|
/*backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);*/
|
||||||
|
/*border-radius: 16px;*/
|
||||||
|
/*box-shadow: 8px 8px 16px rgba(0, 0, 0, 0.12), -8px -8px 16px rgba(255, 255, 255, 0.6);*/
|
||||||
|
/*transition: all 0.3s ease;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-item.active {
|
||||||
|
background: rgba(45, 53, 120, 0.15);
|
||||||
|
box-shadow: inset 4px 4px 10px rgba(45, 53, 120, 0.25), inset -4px -4px 10px rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-accordion {
|
||||||
|
border: white 2px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8f9fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-channel {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
border: #ffffff solid 2px !important;
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-accordion {
|
||||||
|
border-radius: 40px;
|
||||||
|
border: white solid 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
background-color: #172066;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-modal-body {
|
||||||
|
background-color: #f8f9fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-header-modal {
|
||||||
|
background-color: #f0f2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-row-btn {
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pad-2 {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-login {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: #3E4C66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-login {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2E3A59;
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-setting {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: white solid 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#drop-area {
|
||||||
|
border: 2px dashed #ccc;
|
||||||
|
border-radius: 20px;
|
||||||
|
width: 400px;
|
||||||
|
height: 200px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#drop-area.highlight {
|
||||||
|
background: #f0f8ff;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
BIN
html/webpage/assets/img/green_circle.png
Normal file
BIN
html/webpage/assets/img/green_circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
html/webpage/assets/img/logogtc-grey.png
Normal file
BIN
html/webpage/assets/img/logogtc-grey.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
html/webpage/assets/img/logogtc.png
Normal file
BIN
html/webpage/assets/img/logogtc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
html/webpage/assets/img/red_circle.png
Normal file
BIN
html/webpage/assets/img/red_circle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
332
html/webpage/assets/js/broadcastzones.js
Normal file
332
html/webpage/assets/js/broadcastzones.js
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} BroadcastZone
|
||||||
|
* @property {number} index
|
||||||
|
* @property {string} description
|
||||||
|
* @property {String} SoundChannel
|
||||||
|
* @property {String} Box
|
||||||
|
* @property {String} Relay
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of broadcast zones available
|
||||||
|
* @type {BroadcastZone[]}
|
||||||
|
*/
|
||||||
|
window.BroadcastZoneList ??= [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently selected broadcast zone row in the table
|
||||||
|
* @type {JQuery<HTMLElement>|null}
|
||||||
|
*/
|
||||||
|
window.selectedBroadcastZoneRow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of sound channels available
|
||||||
|
* @type {String[]}
|
||||||
|
*/
|
||||||
|
window.SoundChannelList = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill broadcast zone table body with values
|
||||||
|
* @param {BroadcastZone[]} vv values to fill
|
||||||
|
*/
|
||||||
|
function fill_broadcastzonetablebody(vv) {
|
||||||
|
$('#broadcastzonetablebody').empty();
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.description}</td>
|
||||||
|
<td>${item.soundChannel}</td>
|
||||||
|
<td>${item.id}</td>
|
||||||
|
<td>${item.bp}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#broadcastzonetablebody').append(row);
|
||||||
|
let $addedrow = $('#broadcastzonetablebody tr:last');
|
||||||
|
$addedrow.off('click').on('click', function () {
|
||||||
|
if (window.selectedBroadcastZoneRow) {
|
||||||
|
window.selectedBroadcastZoneRow.find('td').css('background-color', '');
|
||||||
|
if (window.selectedBroadcastZoneRow.is($(this))) {
|
||||||
|
window.selectedBroadcastZoneRow = null;
|
||||||
|
$('#btnRemove').prop('disabled', true);
|
||||||
|
$('#btnEdit').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(this).find('td').css('background-color', '#ffeeba');
|
||||||
|
window.selectedBroadcastZoneRow = $(this);
|
||||||
|
$('#btnRemove').prop('disabled', false);
|
||||||
|
$('#btnEdit').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#tablesize').text("Table Size: " + vv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload broadcast zones from server
|
||||||
|
* @param {String} APIURL API URL endpoint (default "BroadcastZones/")
|
||||||
|
*/
|
||||||
|
function reloadBroadcastZones(APIURL = "BroadcastZones/") {
|
||||||
|
window.BroadcastZoneList = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
//console.log("reloadBroadcastZones : ", okdata)
|
||||||
|
window.BroadcastZoneList.push(...okdata);
|
||||||
|
fill_broadcastzonetablebody(window.BroadcastZoneList);
|
||||||
|
} else console.log("reloadBroadcastZones: okdata is not array");
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading broadcast zones : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchSoundChannels(APIURL = "SoundChannel/") {
|
||||||
|
window.SoundChannelList = [];
|
||||||
|
fetchAPI(APIURL + "SoundChannelDescriptions", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
//console.log("fetchSoundChannels : ", okdata)
|
||||||
|
window.SoundChannelList.push(...okdata);
|
||||||
|
} else console.log("fetchSoundChannels: okdata is not array");
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading sound channels : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("broadcastzones.js loaded successfully");
|
||||||
|
window.selectedBroadcastZoneRow = null;
|
||||||
|
let $btnClear = $('#btnClear');
|
||||||
|
let $btnAdd = $('#btnAdd');
|
||||||
|
let $btnEdit = $('#btnEdit');
|
||||||
|
let $btnRemove = $('#btnRemove');
|
||||||
|
let $btnExport = $('#btnExport');
|
||||||
|
let $btnImport = $('#btnImport');
|
||||||
|
$btnEdit.prop('disabled', true);
|
||||||
|
$btnRemove.prop('disabled', true);
|
||||||
|
let APIURL_BroadcastZone = "BroadcastZones/";
|
||||||
|
|
||||||
|
|
||||||
|
let $broadcastzonemodal = $('#broadcastzonemodal');
|
||||||
|
let $broadcastzoneindex = $broadcastzonemodal.find('#broadcastzoneindex');
|
||||||
|
let $broadcastzonedescription = $broadcastzonemodal.find('#broadcastzonedescription');
|
||||||
|
let $broadcastzonesoundchannel = $broadcastzonemodal.find('#broadcastzonesoundchannel');
|
||||||
|
let $broadcastzonebox = $broadcastzonemodal.find('#broadcastzonebox');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let $findzone = $('#findzone');
|
||||||
|
$findzone.off('input').on('input', function () {
|
||||||
|
let searchTerm = $findzone.val().trim().toLowerCase();
|
||||||
|
if (searchTerm.length > 0) {
|
||||||
|
window.selectedBroadcastZoneRow = null;
|
||||||
|
let filtered = window.BroadcastZoneList.filter(item => item.description.toLowerCase().includes(searchTerm) || item.id.toLowerCase().includes(searchTerm) || item.soundChannel.toLowerCase().includes(searchTerm) || item.bp.toLowerCase().includes(searchTerm));
|
||||||
|
fill_broadcastzonetablebody(filtered);
|
||||||
|
} else {
|
||||||
|
window.selectedBroadcastZoneRow = null;
|
||||||
|
fill_broadcastzonetablebody(window.BroadcastZoneList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find Checkbox for relays 1 to 32
|
||||||
|
* Checkbox id is 01 to 32 with leading zero for 1 to 9
|
||||||
|
* @param {number} id 1 - 32
|
||||||
|
* @returns JQuery<HTMLElement>
|
||||||
|
*/
|
||||||
|
function cbRelay(id) {
|
||||||
|
return $broadcastzonemodal.find('#R' + (id < 10 ? '0' : '') + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear broadcast zone modal to default state
|
||||||
|
*/
|
||||||
|
function clearBroadcastZoneModal() {
|
||||||
|
$broadcastzoneindex.prop('disabled', true).val('');
|
||||||
|
$broadcastzonedescription.val('');
|
||||||
|
// fill broadcastzonesoundchannel from SoundChannelList
|
||||||
|
$broadcastzonesoundchannel.empty();
|
||||||
|
console.log("SoundChannelList:", window.SoundChannelList);
|
||||||
|
if (Array.isArray(window.SoundChannelList) && window.SoundChannelList.length > 0) {
|
||||||
|
// SoundChannelList ada isinya
|
||||||
|
window.SoundChannelList.forEach(ch => {
|
||||||
|
if (ch && ch.length>0){
|
||||||
|
$broadcastzonesoundchannel.append($('<option>').val(ch).text(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$broadcastzonebox.val('1').prop('disabled', true);
|
||||||
|
for (let i = 1; i <= 32; i++) {
|
||||||
|
cbRelay(i).prop('checked', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchSoundChannels();
|
||||||
|
reloadBroadcastZones(APIURL_BroadcastZone);
|
||||||
|
|
||||||
|
$btnClear.off('click').on('click', () => {
|
||||||
|
DoClear(APIURL_BroadcastZone, "BroadcastZones", (okdata) => {
|
||||||
|
reloadBroadcastZones(APIURL_BroadcastZone);
|
||||||
|
alert("Success clear broadcast zones: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear broadcast zones: " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$btnAdd.off('click').on('click', () => {
|
||||||
|
$broadcastzonemodal.modal('show');
|
||||||
|
clearBroadcastZoneModal();
|
||||||
|
|
||||||
|
$broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () {
|
||||||
|
|
||||||
|
let description = $broadcastzonedescription.val().trim();
|
||||||
|
let soundChannel = $broadcastzonesoundchannel.val().trim();
|
||||||
|
let box = $broadcastzonebox.val().trim();
|
||||||
|
let relayArray = [];
|
||||||
|
for (let i = 1; i <= 32; i++) {
|
||||||
|
if (cbRelay(i).is(':checked')) {
|
||||||
|
relayArray.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let relay = relayArray.join(';');
|
||||||
|
if (description.length === 0) {
|
||||||
|
alert("Description cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (soundChannel.length === 0) {
|
||||||
|
alert("Sound Channel cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (box.length === 0) {
|
||||||
|
alert("Box cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (relayArray.length === 0) {
|
||||||
|
alert("At least one relay must be selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let bz = {
|
||||||
|
description: description,
|
||||||
|
SoundChannel: soundChannel,
|
||||||
|
Box: box,
|
||||||
|
Relay: relay
|
||||||
|
};
|
||||||
|
fetchAPI(APIURL_BroadcastZone + "Add", "POST", {}, bz, (okdata) => {
|
||||||
|
reloadBroadcastZones(APIURL_BroadcastZone);
|
||||||
|
alert("Success add new broadcast zone: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error add new broadcast zone: " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
$broadcastzonemodal.modal('hide');
|
||||||
|
});
|
||||||
|
$broadcastzonemodal.off('click.broadcastzoneclose').on('click.broadcastzoneclose', '#broadcastzoneclose', function () {
|
||||||
|
$broadcastzonemodal.modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$btnRemove.off('click').on('click', () => {
|
||||||
|
if (window.selectedBroadcastZoneRow) {
|
||||||
|
let cells = window.selectedBroadcastZoneRow.find('td');
|
||||||
|
/** @type {BroadcastZone} */
|
||||||
|
let bz = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
SoundChannel: cells.eq(2).text(),
|
||||||
|
Box: cells.eq(3).text(),
|
||||||
|
Relay: cells.eq(4).text()
|
||||||
|
};
|
||||||
|
if (confirm(`Are you sure to delete broadcast zone [${bz.index}] Description=${bz.description}?`)) {
|
||||||
|
fetchAPI(APIURL_BroadcastZone + "DeleteByIndex/" + bz.index, "DELETE", {}, null, (okdata) => {
|
||||||
|
reloadBroadcastZones(APIURL_BroadcastZone);
|
||||||
|
alert("Success delete broadcast zone: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error delete broadcast zone: " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$btnEdit.off('click').on('click', () => {
|
||||||
|
if (window.selectedBroadcastZoneRow) {
|
||||||
|
let cells = window.selectedBroadcastZoneRow.find('td');
|
||||||
|
/** @type {BroadcastZone} */
|
||||||
|
let bz = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
SoundChannel: cells.eq(2).text(),
|
||||||
|
Box: cells.eq(3).text(),
|
||||||
|
Relay: cells.eq(4).text()
|
||||||
|
};
|
||||||
|
if (confirm(`Are you sure to edit broadcast zone [${bz.index}] Description=${bz.description} SoundChannel=${bz.SoundChannel} Box=${bz.id} Relay=${bz.bp}?`)) {
|
||||||
|
$broadcastzonemodal.modal('show');
|
||||||
|
clearBroadcastZoneModal();
|
||||||
|
$broadcastzoneindex.val(bz.index);
|
||||||
|
$broadcastzonedescription.val(bz.description);
|
||||||
|
$broadcastzonesoundchannel.val(bz.SoundChannel);
|
||||||
|
$broadcastzonebox.val(bz.Box);
|
||||||
|
if (bz.Relay && bz.Relay.length > 0) {
|
||||||
|
bz.Relay.split(';').map(Number).filter(n => !isNaN(n) && n>=1 && n<=8).forEach(relayId => {
|
||||||
|
cbRelay(relayId).prop('checked', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
$broadcastzonemodal.off('click.broadcastzonesave').on('click.broadcastzonesave', '#broadcastzonesave', function () {
|
||||||
|
let description = $broadcastzonedescription.val().trim();
|
||||||
|
let soundChannel = $broadcastzonesoundchannel.val().trim();
|
||||||
|
let box = $broadcastzonebox.val().trim();
|
||||||
|
let relayArray = [];
|
||||||
|
for (let i = 1; i <= 32; i++) {
|
||||||
|
if (cbRelay(i).is(':checked')) {
|
||||||
|
relayArray.push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let relay = relayArray.join(';');
|
||||||
|
if (description.length === 0) {
|
||||||
|
alert("Description cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (soundChannel.length === 0) {
|
||||||
|
alert("Sound Channel cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (box.length === 0) {
|
||||||
|
alert("Box cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (relayArray.length === 0) {
|
||||||
|
alert("At least one relay must be selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let bzUpdate = {
|
||||||
|
description: description,
|
||||||
|
SoundChannel: soundChannel,
|
||||||
|
Box: box,
|
||||||
|
Relay: relay
|
||||||
|
};
|
||||||
|
fetchAPI(APIURL_BroadcastZone + "UpdateByIndex/" + bz.index, "PATCH", {}, bzUpdate, (okdata) => {
|
||||||
|
reloadBroadcastZones(APIURL_BroadcastZone);
|
||||||
|
alert("Success edit broadcast zone: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error edit broadcast zone: " + errdata.message);
|
||||||
|
});
|
||||||
|
$broadcastzonemodal.modal('hide');
|
||||||
|
});
|
||||||
|
$broadcastzonemodal.off('click.broadcastzoneclose').on('click.broadcastzoneclose', '#broadcastzoneclose', function () {
|
||||||
|
$broadcastzonemodal.modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$btnExport.off('click').on('click', () => {
|
||||||
|
DoExport(APIURL_BroadcastZone, "broadcastzones.xlsx", {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$btnImport.off('click').on('click', () => {
|
||||||
|
DoImport(APIURL_BroadcastZone, (okdata) => {
|
||||||
|
reloadBroadcastZones(APIURL_BroadcastZone);
|
||||||
|
alert("Success import broadcast zones: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error importing broadcast zones from XLSX: " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
7
html/webpage/assets/js/bs-init.js
Normal file
7
html/webpage/assets/js/bs-init.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bss-tooltip]'));
|
||||||
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
|
})
|
||||||
|
}, false);
|
||||||
31
html/webpage/assets/js/dragdrop.js
vendored
Normal file
31
html/webpage/assets/js/dragdrop.js
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
const dropArea = document.getElementById("drop-area");
|
||||||
|
const fileInput = document.getElementById("file-input");
|
||||||
|
|
||||||
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||||
|
dropArea.addEventListener(eventName, e => e.preventDefault());
|
||||||
|
dropArea.addEventListener(eventName, e => e.stopPropagation());
|
||||||
|
});
|
||||||
|
|
||||||
|
['dragenter', 'dragover'].forEach(eventName => {
|
||||||
|
dropArea.addEventListener(eventName, () => dropArea.classList.add('highlight'));
|
||||||
|
});
|
||||||
|
|
||||||
|
['dragleave', 'drop'].forEach(eventName => {
|
||||||
|
dropArea.addEventListener(eventName, () => dropArea.classList.remove('highlight'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
dropArea.addEventListener('click', () => fileInput.click());
|
||||||
|
|
||||||
|
dropArea.addEventListener('drop', e => {
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
handleFiles(files);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', e => {
|
||||||
|
handleFiles(e.target.files);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleFiles(files) {
|
||||||
|
console.log("file dropped");
|
||||||
|
}
|
||||||
2
html/webpage/assets/js/jquery-3.7.1.min.js
vendored
Normal file
2
html/webpage/assets/js/jquery-3.7.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
272
html/webpage/assets/js/languagelink.js
Normal file
272
html/webpage/assets/js/languagelink.js
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} LanguageBank
|
||||||
|
* @property {number} index
|
||||||
|
* @property {string} tag
|
||||||
|
* @property {string} language
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** List of Languagebank data loaded from server
|
||||||
|
* @type {LanguageBank[]}
|
||||||
|
*/
|
||||||
|
window.languagebankdata = [];
|
||||||
|
/**
|
||||||
|
* Currently selected languagebank row in the table
|
||||||
|
* @type {JQuery<HTMLElement>|null}
|
||||||
|
*/
|
||||||
|
window.selectedlanguagerow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill languagebank table body with values
|
||||||
|
* @param {LanguageBank[]} vv values to fill
|
||||||
|
*/
|
||||||
|
function fill_languagebanktablebody(vv) {
|
||||||
|
$('#languagebanktablebody').empty();
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.tag}</td>
|
||||||
|
<td>${item.language}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#languagebanktablebody').append(row);
|
||||||
|
let $addedrow = $('#languagebanktablebody tr:last');
|
||||||
|
$addedrow.click(function () {
|
||||||
|
if (window.selectedlanguagerow) {
|
||||||
|
window.selectedlanguagerow.find('td').css('background-color', '');
|
||||||
|
if (window.selectedlanguagerow.is($(this))) {
|
||||||
|
window.selectedlanguagerow = null;
|
||||||
|
$('#btnRemove').prop('disabled', true);
|
||||||
|
$('#btnEdit').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$addedrow.find('td').css('background-color', '#ffeeba');
|
||||||
|
window.selectedlanguagerow = $addedrow;
|
||||||
|
$('#btnRemove').prop('disabled', false);
|
||||||
|
$('#btnEdit').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#tablesize').text("Table Size: " + vv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload language bank from server
|
||||||
|
* @param {string} APIURL API URL endpoint, default "LanguageLink/"
|
||||||
|
*/
|
||||||
|
function reloadLanguageBank(APIURL = "LanguageLink/") {
|
||||||
|
window.languagebankdata = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.languagebankdata.push(...okdata);
|
||||||
|
window.selectedlanguagerow = null;
|
||||||
|
fill_languagebanktablebody(window.languagebankdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading languagebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log('languagebank.js loaded');
|
||||||
|
|
||||||
|
$('#languagebanktablebody').empty();
|
||||||
|
window.selectedlanguagerow = null;
|
||||||
|
let $btnClear = $('#btnClear');
|
||||||
|
let $btnAdd = $('#btnAdd');
|
||||||
|
let $btnRemove = $('#btnRemove');
|
||||||
|
let $btnEdit = $('#btnEdit');
|
||||||
|
let $btnExport = $('#btnExport');
|
||||||
|
let $btnImport = $('#btnImport');
|
||||||
|
$btnRemove.prop('disabled', true);
|
||||||
|
$btnEdit.prop('disabled', true);
|
||||||
|
let APIURL = "LanguageLink/";
|
||||||
|
let $findlanguage = $('#findlanguage');
|
||||||
|
let $modal = $('#languagemodal');
|
||||||
|
let $langid = $modal.find('#languagelinkindex');
|
||||||
|
let $langtag = $modal.find('#languagelinktag');
|
||||||
|
let $cbInd = $modal.find('#langId');
|
||||||
|
let $cbLocal = $modal.find('#langLocal');
|
||||||
|
let $cbEn = $modal.find('#langEn');
|
||||||
|
let $cbArb = $modal.find('#langArb');
|
||||||
|
let $cbJap = $modal.find('#langJap');
|
||||||
|
let $cbChi = $modal.find('#langChi');
|
||||||
|
|
||||||
|
function clearLanguageModal() {
|
||||||
|
$langid.prop('disabled', true).val('');
|
||||||
|
$langtag.val('');
|
||||||
|
$cbInd.prop('checked', false);
|
||||||
|
$cbLocal.prop('checked', false);
|
||||||
|
$cbEn.prop('checked', false);
|
||||||
|
$cbArb.prop('checked', false);
|
||||||
|
$cbJap.prop('checked', false);
|
||||||
|
$cbChi.prop('checked', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$findlanguage.on('input', function () {
|
||||||
|
let searchTerm = $findlanguage.val().toLowerCase();
|
||||||
|
if (searchTerm.length > 0) {
|
||||||
|
window.selectedlanguagerow = null;
|
||||||
|
let filtered = window.languagebankdata.filter(item => item.tag.toLowerCase().includes(searchTerm) || item.language.toLowerCase().includes(searchTerm));
|
||||||
|
fill_languagebanktablebody(filtered);
|
||||||
|
} else {
|
||||||
|
window.selectedlanguagerow = null;
|
||||||
|
fill_languagebanktablebody(window.languagebankdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reloadLanguageBank(APIURL);
|
||||||
|
$btnClear.click(() => {
|
||||||
|
DoClear(APIURL, "LanguageLink", (okdata) => {
|
||||||
|
reloadLanguageBank(APIURL);
|
||||||
|
alert("Success clear languageLink : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear languageLink : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
$btnAdd.click(() => {
|
||||||
|
// show modal with id 'languagemodal'
|
||||||
|
$modal.modal('show');
|
||||||
|
clearLanguageModal();
|
||||||
|
|
||||||
|
// save button click event
|
||||||
|
$modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () {
|
||||||
|
const tag = $langtag.val();
|
||||||
|
const langs = [];
|
||||||
|
if ($cbInd.is(':checked')) langs.push('INDONESIA');
|
||||||
|
if ($cbLocal.is(':checked')) langs.push('LOCAL');
|
||||||
|
if ($cbEn.is(':checked')) langs.push('ENGLISH');
|
||||||
|
if ($cbArb.is(':checked')) langs.push('ARABIC');
|
||||||
|
if ($cbJap.is(':checked')) langs.push('JAPANESE');
|
||||||
|
if ($cbChi.is(':checked')) langs.push('CHINESE');
|
||||||
|
|
||||||
|
if (tag.length === 0) {
|
||||||
|
alert("Tag cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (langs.length === 0) {
|
||||||
|
alert("At least one language must be selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const langString = langs.join(';');
|
||||||
|
let ll = {
|
||||||
|
tag: tag,
|
||||||
|
language: langString
|
||||||
|
}
|
||||||
|
fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => {
|
||||||
|
alert("Success add language : " + okdata.message);
|
||||||
|
reloadLanguageBank(APIURL);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error add language : " + errdata.message);
|
||||||
|
});
|
||||||
|
$modal.modal('hide');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
// close button click event
|
||||||
|
$modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () {
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$btnRemove.click(() => {
|
||||||
|
if (window.selectedlanguagerow) {
|
||||||
|
let cells = window.selectedlanguagerow.find('td');
|
||||||
|
/** @type {Language} */
|
||||||
|
let ll = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
tag: cells.eq(1).text(),
|
||||||
|
language: cells.eq(2).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to delete language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
|
||||||
|
fetchAPI(APIURL + "DeleteByIndex/" + ll.index, "DELETE", {}, null, (okdata) => {
|
||||||
|
reloadLanguageBank(APIURL);
|
||||||
|
alert("Success delete language : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error delete language : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnEdit.click(() => {
|
||||||
|
if (window.selectedlanguagerow) {
|
||||||
|
let cells = window.selectedlanguagerow.find('td');
|
||||||
|
/** @type {Language} */
|
||||||
|
let ll = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
tag: cells.eq(1).text(),
|
||||||
|
language: cells.eq(2).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to edit language [${ll.index}] Tag=${ll.tag} Language=${ll.language}?`)) {
|
||||||
|
|
||||||
|
clearLanguageModal();
|
||||||
|
$langid.val(ll.index);
|
||||||
|
$langtag.val(ll.tag);
|
||||||
|
let langs = ll.language.toUpperCase().split(';');
|
||||||
|
$cbInd.prop('checked', langs.includes('INDONESIA'));
|
||||||
|
$cbLocal.prop('checked', langs.includes('LOCAL'));
|
||||||
|
$cbEn.prop('checked', langs.includes('ENGLISH'));
|
||||||
|
$cbArb.prop('checked', langs.includes('ARABIC'));
|
||||||
|
$cbJap.prop('checked', langs.includes('JAPANESE'));
|
||||||
|
$cbChi.prop('checked', langs.includes('CHINESE'));
|
||||||
|
$modal.modal('show');
|
||||||
|
// save button click event
|
||||||
|
$modal.off('click.languagelinksave').on('click.languagelinksave', '#languagelinksave', function () {
|
||||||
|
const tag = $langtag.val();
|
||||||
|
const langs = [];
|
||||||
|
if ($cbInd.is(':checked')) langs.push('INDONESIA');
|
||||||
|
if ($cbLocal.is(':checked')) langs.push('LOCAL');
|
||||||
|
if ($cbEn.is(':checked')) langs.push('ENGLISH');
|
||||||
|
if ($cbArb.is(':checked')) langs.push('ARABIC');
|
||||||
|
if ($cbJap.is(':checked')) langs.push('JAPANESE');
|
||||||
|
if ($cbChi.is(':checked')) langs.push('CHINESE');
|
||||||
|
if (tag.length === 0) {
|
||||||
|
alert("Tag cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (langs.length === 0) {
|
||||||
|
alert("At least one language must be selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const langString = langs.join(';');
|
||||||
|
if (ll.tag === tag && ll.language === langString) {
|
||||||
|
alert("No changes detected");
|
||||||
|
$modal.modal('hide');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ll.tag = tag;
|
||||||
|
ll.language = langString;
|
||||||
|
fetchAPI(APIURL + "UpdateByIndex/" + ll.index, "PATCH", {}, ll, (okdata) => {
|
||||||
|
reloadLanguageBank(APIURL);
|
||||||
|
alert("Success edit language : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error edit language : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
// close button click event
|
||||||
|
$modal.off('click.languagelinkclose').on('click.languagelinkclose', '#languagelinkclose', function () {
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnExport.click(() => {
|
||||||
|
DoExport(APIURL, "languagebank.xlsx", {});
|
||||||
|
|
||||||
|
});
|
||||||
|
$btnImport.click(() => {
|
||||||
|
DoImport(APIURL, (okdata) => {
|
||||||
|
reloadLanguageBank(APIURL);
|
||||||
|
alert("Success import languagebank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error importing languagebank from XLSX : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
95
html/webpage/assets/js/log.js
Normal file
95
html/webpage/assets/js/log.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} Log
|
||||||
|
* @property {number} index
|
||||||
|
* @property {string} datenya
|
||||||
|
* @property {string} timenya
|
||||||
|
* @property {string} machine
|
||||||
|
* @property {string} description
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** List of Log data loaded from server
|
||||||
|
* @type {Log[]}
|
||||||
|
*/
|
||||||
|
window.logdata = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill log table body with values
|
||||||
|
* @param {Log[]} vv values to fill
|
||||||
|
*/
|
||||||
|
function fill_logtablebody(vv) {
|
||||||
|
$('#logtablebody').empty();
|
||||||
|
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) {
|
||||||
|
$('#btnExport').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.datenya}</td>
|
||||||
|
<td>${item.timenya}</td>
|
||||||
|
<td>${item.machine}</td>
|
||||||
|
<td>${item.description}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#logtablebody').append(row);
|
||||||
|
});
|
||||||
|
$('#tablesize').text("Table Size: " + vv.length);
|
||||||
|
$('#btnExport').prop('disabled', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload logs from server with date and filter
|
||||||
|
* @param {String} APIURL API URL endpoint , default "Log/"
|
||||||
|
* @param {String} date date in format dd-mm-yyyy
|
||||||
|
* @param {String} filter log filter text
|
||||||
|
*/
|
||||||
|
function reloadLogs(APIURL = "Log/", date, filter) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
date: date,
|
||||||
|
filter: filter
|
||||||
|
})
|
||||||
|
window.logdata = [];
|
||||||
|
fetchAPI(APIURL + "List?" + params.toString(), "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.logdata.push(...okdata);
|
||||||
|
fill_logtablebody(window.logdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading logs : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("log.js ready");
|
||||||
|
let selectedlogdate = "";
|
||||||
|
let logfilter = "";
|
||||||
|
let APIURL = "Log/";
|
||||||
|
$('#logtablebody').empty();
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#searchfilter').off('input').on('input', function () {
|
||||||
|
logfilter = $(this).val();
|
||||||
|
reloadLogs(APIURL, selectedlogdate, logfilter);
|
||||||
|
});
|
||||||
|
$('#btnExport').off('click').on('click', function () {
|
||||||
|
DoExport(APIURL, "log.xlsx", { date: selectedlogdate, filter: logfilter });
|
||||||
|
});
|
||||||
|
});
|
||||||
456
html/webpage/assets/js/messagebank.js
Normal file
456
html/webpage/assets/js/messagebank.js
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} MessageBank
|
||||||
|
* @property {number} index
|
||||||
|
* @property {string} description
|
||||||
|
* @property {string} language
|
||||||
|
* @property {number} aNN_ID
|
||||||
|
* @property {string} voice_Type
|
||||||
|
* @property {string} message_Detail
|
||||||
|
* @property {string} message_TAGS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of Messagebank data loaded from server
|
||||||
|
* @type {MessageBank[]}
|
||||||
|
*/
|
||||||
|
window.messagebankdata ??= [];
|
||||||
|
/**
|
||||||
|
* Currently selected messagebank row in the table
|
||||||
|
* @type {JQuery<HTMLElement>|null}
|
||||||
|
*/
|
||||||
|
window.selectedmessagerow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill messagebank table body with values
|
||||||
|
* @param {MessageBank[]} vv values to fill
|
||||||
|
*/
|
||||||
|
function fill_messagebanktablebody(vv) {
|
||||||
|
$('#messagebanktablebody').empty();
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.description}</td>
|
||||||
|
<td>${item.language}</td>
|
||||||
|
<td>${item.aNN_ID}</td>
|
||||||
|
<td>${item.voice_Type}</td>
|
||||||
|
<td>${item.message_Detail}</td>
|
||||||
|
<td>${item.message_TAGS}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#messagebanktablebody').append(row);
|
||||||
|
let $addedrow = $('#messagebanktablebody tr:last');
|
||||||
|
$addedrow.click(function () {
|
||||||
|
if (window.selectedmessagerow) {
|
||||||
|
window.selectedmessagerow.find('td').css('background-color', '');
|
||||||
|
if (window.selectedmessagerow.is($(this))) {
|
||||||
|
window.selectedmessagerow = null;
|
||||||
|
$('#btnRemove').prop('disabled', true);
|
||||||
|
$('#btnEdit').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$addedrow.find('td').css('background-color', '#ffeeba');
|
||||||
|
window.selectedmessagerow = $addedrow;
|
||||||
|
$('#btnRemove').prop('disabled', false);
|
||||||
|
$('#btnEdit').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#tablesize').text("Table Size: " + vv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload message bank from server
|
||||||
|
* @param {string} APIURL API URL endpoint, default "MessageBank/"
|
||||||
|
*/
|
||||||
|
function reloadMessageBank(APIURL = "MessageBank/") {
|
||||||
|
window.messagebankdata ??= [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.messagebankdata.push(...okdata);
|
||||||
|
window.selectedmessagerow = null;
|
||||||
|
fill_messagebanktablebody(window.messagebankdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading messagebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("messagebank.js loaded");
|
||||||
|
$('#messagebanktablebody').empty();
|
||||||
|
window.selectedmessagerow = null;
|
||||||
|
let $btnClear = $('#btnClear');
|
||||||
|
let $btnAdd = $('#btnAdd');
|
||||||
|
let $btnRemove = $('#btnRemove');
|
||||||
|
let $btnEdit = $('#btnEdit');
|
||||||
|
let $btnExport = $('#btnExport');
|
||||||
|
let $btnImport = $('#btnImport');
|
||||||
|
$btnRemove.prop('disabled', true);
|
||||||
|
$btnEdit.prop('disabled', true);
|
||||||
|
let APIURL = "MessageBank/";
|
||||||
|
let $findmessage = $('#findmessage');
|
||||||
|
|
||||||
|
// modal for add / edit messagebank
|
||||||
|
let $modal = $('#messagebankmodal');
|
||||||
|
// text input, disabled by default
|
||||||
|
let $messageindex = $modal.find('#messageindex');
|
||||||
|
// text input
|
||||||
|
let $messagedescription = $modal.find('#messagedescription');
|
||||||
|
// select input, options loaded from languages[]
|
||||||
|
let $messagelanguage = $modal.find('#messagelanguage');
|
||||||
|
// number input from 1 to 100
|
||||||
|
let $messageannid = $modal.find('#messageannid');
|
||||||
|
// select input, options loaded from voiceTypes[]
|
||||||
|
let $messagevoicetype = $modal.find('#messagevoicetype');
|
||||||
|
// list <ul> of available categories and phrases
|
||||||
|
let $messageavailablevariables = $modal.find('#messageavailablevariables');
|
||||||
|
// list <ul> of selected categories and phrases
|
||||||
|
let $messageselectedvariables = $modal.find('#messageselectedvariables');
|
||||||
|
// for clearing messageselectedvariables
|
||||||
|
let $btnclearlist = $modal.find('#btnclearlist');
|
||||||
|
// for removing selected item from messageselectedvariables
|
||||||
|
let $btnremovefromlist = $modal.find('#btnremovefromlist');
|
||||||
|
// for adding selected item from messageavailablevariables to messageselectedvariables
|
||||||
|
let $btnaddtolist = $modal.find('#btnaddtolist');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refill messageavailablevariables options from categories[]
|
||||||
|
* and soundbankdata with category "Phrase" if messagelanguage and messagevoicetype are selected
|
||||||
|
*/
|
||||||
|
function refill_messageavailablevariables() {
|
||||||
|
$messageavailablevariables.empty();
|
||||||
|
categories.forEach(cat => {
|
||||||
|
$messageavailablevariables.append(ListItem(`[${cat}]`));
|
||||||
|
});
|
||||||
|
let lang = $messagelanguage.val();
|
||||||
|
let vt = $messagevoicetype.val();
|
||||||
|
if (lang && lang.length > 0){
|
||||||
|
console.log("Selected Language:", lang);
|
||||||
|
if (vt && vt.length > 0){
|
||||||
|
console.log("Selected Voice Type:", vt);
|
||||||
|
|
||||||
|
fetchAPI(`SoundBank/GetPhrases/${lang}/${vt}`, "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata) && okdata.length > 0) {
|
||||||
|
console.log(`Loaded ${okdata.length} phrases from soundbank for language=${lang} and voiceType=${vt}`);
|
||||||
|
console.log(JSON.stringify(okdata));
|
||||||
|
okdata.forEach(sb => {
|
||||||
|
if (sb.description && sb.description.length > 0) {
|
||||||
|
$messageavailablevariables.append(ListItem(`${sb.description} [${sb.TAG}]`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
//alert("Error loading phrases from soundbank : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear message modal to default state
|
||||||
|
*/
|
||||||
|
function clearMessageModal() {
|
||||||
|
$messageindex.val('').prop('disabled', true);
|
||||||
|
$messagedescription.val('');
|
||||||
|
// fill messagelanguage options from languages[]
|
||||||
|
$messagelanguage.empty();
|
||||||
|
window.languages.forEach(lang => {
|
||||||
|
$messagelanguage.append(new Option(lang, lang));
|
||||||
|
});
|
||||||
|
$messagelanguage.val(null);
|
||||||
|
$messagelanguage.on('change', function () {
|
||||||
|
refill_messageavailablevariables();
|
||||||
|
});
|
||||||
|
// set default annid to 1
|
||||||
|
$messageannid.val(1);
|
||||||
|
// fill messagevoicetype options from voiceTypes[]
|
||||||
|
$messagevoicetype.empty();
|
||||||
|
window.voiceTypes.forEach(vt => {
|
||||||
|
$messagevoicetype.append(new Option(vt, vt));
|
||||||
|
});
|
||||||
|
$messagevoicetype.val(null);
|
||||||
|
$messagevoicetype.on('change', function () {
|
||||||
|
refill_messageavailablevariables();
|
||||||
|
});
|
||||||
|
|
||||||
|
refill_messageavailablevariables();
|
||||||
|
$messageselectedvariables.empty();
|
||||||
|
|
||||||
|
// event on btnclearlist
|
||||||
|
$btnclearlist.off('click').on('click', function () {
|
||||||
|
if ($messageselectedvariables.children().length > 0) {
|
||||||
|
if (confirm("Are you sure want to clear selected variables list?")) {
|
||||||
|
$messageselectedvariables.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// event on btnremovefromlist
|
||||||
|
$btnremovefromlist.off('click').on('click', function () {
|
||||||
|
let $selected = $messageselectedvariables.find('option:selected');
|
||||||
|
if ($selected.length > 0) {
|
||||||
|
$selected.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// event on btnaddtolist
|
||||||
|
$btnaddtolist.off('click').on('click', function () {
|
||||||
|
let $selected = $messageavailablevariables.find('option:selected');
|
||||||
|
if ($selected.length > 0) {
|
||||||
|
$selected.each(function () {
|
||||||
|
$messageselectedvariables.append($(this).clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$findmessage.on('input', function () {
|
||||||
|
let searchTerm = $findmessage.val().toLowerCase();
|
||||||
|
if (searchTerm.length > 0) {
|
||||||
|
window.selectedmessagerow = null;
|
||||||
|
let filtered = window.messagebankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.message_Detail.toLowerCase().includes(searchTerm) || item.message_TAGS.toLowerCase().includes(searchTerm));
|
||||||
|
fill_messagebanktablebody(filtered);
|
||||||
|
} else {
|
||||||
|
window.selectedmessagerow = null;
|
||||||
|
fill_messagebanktablebody(window.messagebankdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
reloadMessageBank(APIURL);
|
||||||
|
$btnClear.click(() => {
|
||||||
|
DoClear(APIURL, "Messagebank", (okdata) => {
|
||||||
|
reloadMessageBank(APIURL);
|
||||||
|
alert("Success clear messagebank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear messagebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
$btnAdd.click(() => {
|
||||||
|
|
||||||
|
$modal.modal('show');
|
||||||
|
clearMessageModal();
|
||||||
|
|
||||||
|
// event on Click save button
|
||||||
|
$modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () {
|
||||||
|
let description = $messagedescription.val().trim();
|
||||||
|
let language = $messagelanguage.val();
|
||||||
|
let annid = parseInt($messageannid.val());
|
||||||
|
let voicetype = $messagevoicetype.val();
|
||||||
|
let messagedetail = "";
|
||||||
|
let messagetags = "";
|
||||||
|
|
||||||
|
// iterate messageselectedvariables children
|
||||||
|
$messageselectedvariables.children().each(function () {
|
||||||
|
let val = $(this).text().trim();
|
||||||
|
if (val.length > 0) {
|
||||||
|
if (val.startsWith('[') && val.endsWith(']')) {
|
||||||
|
// categories
|
||||||
|
messagetags += (messagetags.length > 0 ? " " : "") + val;
|
||||||
|
messagedetail += (messagedetail.length > 0 ? " " : "") + val;
|
||||||
|
} else {
|
||||||
|
// phrases
|
||||||
|
// find in soundbankdata by description with specified language and voicetype
|
||||||
|
let sb = soundbankdata
|
||||||
|
.filter(sb => sb.language.toLowerCase() === language.toLowerCase())
|
||||||
|
.filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase())
|
||||||
|
.find(sb => sb.Description.toLowerCase() === val.toLowerCase());
|
||||||
|
if (sb) {
|
||||||
|
messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description;
|
||||||
|
messagetags += (messagetags.length > 0 ? " " : "") + sb.tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (description.length === 0) {
|
||||||
|
alert("Description cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!language) {
|
||||||
|
alert("Language cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNaN(annid) || annid < 1 || annid > 100) {
|
||||||
|
alert("ANN_ID must be a number between 1 and 100");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!voicetype) {
|
||||||
|
alert("Voice Type cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (messagedetail.length === 0 || messagetags.length === 0) {
|
||||||
|
alert("Message haven't been constructed, please add categories and phrases");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mb = {
|
||||||
|
Description: description,
|
||||||
|
Language: language,
|
||||||
|
ANN_ID: annid,
|
||||||
|
Voice_Type: voicetype,
|
||||||
|
Message_Detail: messagedetail,
|
||||||
|
Message_TAGS: messagetags
|
||||||
|
};
|
||||||
|
// send to server using fetchAPI
|
||||||
|
fetchAPI(APIURL + "Add", "POST", mb, null, (okdata) => {
|
||||||
|
reloadMessageBank(APIURL);
|
||||||
|
alert("Success add new messagebank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error add new messagebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
// event on Click close button
|
||||||
|
$modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () {
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$btnRemove.click(() => {
|
||||||
|
if (window.selectedmessagerow) {
|
||||||
|
let cells = window.selectedmessagerow.find('td');
|
||||||
|
/** @type {MessageBank} */
|
||||||
|
let mb = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
language: cells.eq(2).text(),
|
||||||
|
aNN_ID: parseInt(cells.eq(3).text()),
|
||||||
|
voice_Type: cells.eq(4).text(),
|
||||||
|
message_Detail: cells.eq(5).text(),
|
||||||
|
message_TAGS: cells.eq(6).text()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirm(`Are you sure to delete messagebank [${mb.index}] Description=${mb.description}? ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) {
|
||||||
|
fetchAPI(APIURL + "DeleteByIndex/" + mb.index, "DELETE", {}, null, (okdata) => {
|
||||||
|
reloadMessageBank(APIURL);
|
||||||
|
alert("Success delete messagebank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error delete messagebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnEdit.click(() => {
|
||||||
|
if (window.selectedmessagerow) {
|
||||||
|
let cells = window.selectedmessagerow.find('td');
|
||||||
|
/** @type {MessageBank} */
|
||||||
|
let mb = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
language: cells.eq(2).text(),
|
||||||
|
aNN_ID: parseInt(cells.eq(3).text()),
|
||||||
|
voice_Type: cells.eq(4).text(),
|
||||||
|
message_Detail: cells.eq(5).text(),
|
||||||
|
message_TAGS: cells.eq(6).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to edit messagebank [${mb.index}] Description=${mb.description} ANN_ID=${mb.aNN_ID} Language=${mb.language} Voice_Type=${mb.voice_Type} `)) {
|
||||||
|
$modal.modal('show');
|
||||||
|
|
||||||
|
clearMessageModal();
|
||||||
|
// Fill modal fields with selected messagebank data
|
||||||
|
$messageindex.val(mb.index).prop('disabled', true);
|
||||||
|
$messagedescription.val(mb.description);
|
||||||
|
$messagelanguage.val(mb.language);
|
||||||
|
$messagevoicetype.val(mb.voice_Type);
|
||||||
|
$messageannid.val(mb.aNN_ID);
|
||||||
|
// Refill message available variables
|
||||||
|
refill_messageavailablevariables();
|
||||||
|
// Fill messageselectedvariables from message_Detail and message_TAGS
|
||||||
|
$messageselectedvariables.empty();
|
||||||
|
if (mb.message_Detail) {
|
||||||
|
mb.message_Detail.split(' ').forEach(val => {
|
||||||
|
$messageselectedvariables.append(ListItem(val));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save button event
|
||||||
|
$modal.off('click.messagebanksave').on('click.messagebanksave', '#messagebanksave', function () {
|
||||||
|
let description = $messagedescription.val().trim();
|
||||||
|
let language = $messagelanguage.val();
|
||||||
|
let annid = parseInt($messageannid.val());
|
||||||
|
let voicetype = $messagevoicetype.val();
|
||||||
|
let messagedetail = "";
|
||||||
|
let messagetags = "";
|
||||||
|
$messageselectedvariables.children().each(function () {
|
||||||
|
let val = $(this).text().trim();
|
||||||
|
if (val.length > 0) {
|
||||||
|
if (val.startsWith('[') && val.endsWith(']')) {
|
||||||
|
messagetags += (messagetags.length > 0 ? " " : "") + val;
|
||||||
|
messagedetail += (messagedetail.length > 0 ? " " : "") + val;
|
||||||
|
} else {
|
||||||
|
let sb = soundbankdata
|
||||||
|
.filter(sb => sb.language.toLowerCase() === language.toLowerCase())
|
||||||
|
.filter(sb => sb.voiceType.toLowerCase() === voicetype.toLowerCase())
|
||||||
|
.find(sb => sb.Description && sb.Description.toLowerCase() === val.toLowerCase());
|
||||||
|
if (sb) {
|
||||||
|
messagedetail += (messagedetail.length > 0 ? " " : "") + sb.Description;
|
||||||
|
messagetags += (messagetags.length > 0 ? " " : "") + sb.tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (description.length === 0) {
|
||||||
|
alert("Description cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!language) {
|
||||||
|
alert("Language cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNaN(annid) || annid < 1 || annid > 100) {
|
||||||
|
alert("ANN_ID must be a number between 1 and 100");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!voicetype) {
|
||||||
|
alert("Voice Type cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (messagedetail.length === 0 || messagetags.length === 0) {
|
||||||
|
alert("Message haven't been constructed, please add categories and phrases");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mbUpdate = {
|
||||||
|
Description: description,
|
||||||
|
Language: language,
|
||||||
|
ANN_ID: annid,
|
||||||
|
Voice_Type: voicetype,
|
||||||
|
Message_Detail: messagedetail,
|
||||||
|
Message_TAGS: messagetags
|
||||||
|
};
|
||||||
|
fetchAPI(APIURL + "UpdateByIndex/" + mb.index, "PATCH", mbUpdate, null, (okdata) => {
|
||||||
|
reloadMessageBank(APIURL);
|
||||||
|
alert("Success edit messagebank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error edit messagebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
// Close button event
|
||||||
|
$modal.off('click.messagebankclose').on('click.messagebankclose', '#messagebankclose', function () {
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnExport.click(() => {
|
||||||
|
DoExport(APIURL, "messagebank.xlsx", {});
|
||||||
|
});
|
||||||
|
$btnImport.click(() => {
|
||||||
|
DoImport(APIURL, (okdata) => {
|
||||||
|
reloadMessageBank(APIURL);
|
||||||
|
alert("Success import messagebank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error importing messagebank from XLSX : " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
359
html/webpage/assets/js/overview.js
Normal file
359
html/webpage/assets/js/overview.js
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} StreamerOutputData
|
||||||
|
* @property {number} index - The index of the Barix connection.
|
||||||
|
* @property {string} channel - The channel name of the Barix connection.
|
||||||
|
* @property {string} ipaddress - The IP address of the Barix connection.
|
||||||
|
* @property {number} bufferRemain - The remaining buffer size of the Barix connection.
|
||||||
|
* @property {boolean} isPlaying - true = playback started, false = playback stopped
|
||||||
|
* @property {number} vu - The VU level of the Barix connection, 0 to 100.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PagingQueue
|
||||||
|
* @property {number} index - The index of the paging queue item.
|
||||||
|
* @property {string} Date_Time - The date and time of the paging queue item.
|
||||||
|
* @property {string} Source - The source of the paging queue item.
|
||||||
|
* @property {string} Type - The type of the paging queue item.
|
||||||
|
* @property {string} Message - The message of the paging queue item.
|
||||||
|
* @property {string} BroadcastZones - The broadcast zones of the paging queue item.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} StreamerCard
|
||||||
|
* @property {JQuery<HTMLElement> | null} title - The jQuery result should be <h4> element.
|
||||||
|
* @property {JQuery<HTMLElement> | null} ip - The jQuery result should be <h6> element.
|
||||||
|
* @property {JQuery<HTMLElement> | null} buffer - The jQuery result should be <h6> element.
|
||||||
|
* @property {JQuery<HTMLElement> | null} status - The jQuery result should be <p> element.
|
||||||
|
* @property {JQuery<HTMLElement> | null} vu - The jQuery result should be <progress-bar> element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getCardByIndex(index) {
|
||||||
|
let obj = {
|
||||||
|
// title is <h4> element wiht id `streamertitle${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||||
|
title: $(`#streamertitle${index.toString().padStart(2, '0')}`),
|
||||||
|
// ip is <h6> element with id `streamerip${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||||
|
ip: $(`#streamerip${index.toString().padStart(2, '0')}`),
|
||||||
|
// buffer is <h6> element with id `streamerbuffer${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||||
|
buffer: $(`#streamerbuffer${index.toString().padStart(2, '0')}`),
|
||||||
|
// status is <p> element with id `streamerstatus${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||||
|
status: $(`#streamerstatus${index.toString().padStart(2, '0')}`),
|
||||||
|
// vu is <progress-bar> element with id `streamervu${index}`, with index as two digit number, e.g. 01, 02, 03
|
||||||
|
vu: $(`#streamervu${index.toString().padStart(2, '0')} .progress-bar`),
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the streamer card with the provided values.
|
||||||
|
* @param {StreamerOutputData[]} values
|
||||||
|
*/
|
||||||
|
function UpdateStreamerCard(values) {
|
||||||
|
if (!Array.isArray(values) || values.length === 0) return;
|
||||||
|
|
||||||
|
function setProgress(index, $bar, value, max = 100) {
|
||||||
|
const v = Number(value ?? 0);
|
||||||
|
const pct = Math.max(0, Math.min(100, Math.round((v / max) * 100)));
|
||||||
|
//if (index!==1) return; // only update index 1 for testing
|
||||||
|
//console.log(`setProgress: index=${index}, value=${v}, pct=${pct}`);
|
||||||
|
$bar
|
||||||
|
.attr('aria-valuenow', v) // semantic value
|
||||||
|
.css('width', pct + '%') // visual width
|
||||||
|
.text(pct); // optional label
|
||||||
|
}
|
||||||
|
for (let i = 1; i <= 64; i++) {
|
||||||
|
let vv = values.find(v => v.index === i);
|
||||||
|
let card = getCardByIndex(i);
|
||||||
|
if (vv) {
|
||||||
|
// there is value for this index
|
||||||
|
if (card.title) card.title.text(vv.channel ? vv.channel : `Channel ${i.toString().padStart(2, '0')}`);
|
||||||
|
if (card.ip) card.ip.text(`IP Address: ${vv.ipaddress ? vv.ipaddress : 'N/A'}`);
|
||||||
|
if (card.buffer) card.buffer.text(`Buffer: ${vv.bufferRemain !== undefined && vv.bufferRemain !== null ? vv.bufferRemain.toString() : 'N/A'}`);
|
||||||
|
if (card.status) card.status.text(`Status: ${vv.isPlaying ? 'Playing' : 'Stopped'}`);
|
||||||
|
if (card.vu) {
|
||||||
|
setProgress(i, card.vu, vv.vu, 100);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no value for this index, disable the card
|
||||||
|
if (card.title) card.title.text(`Channel ${i.toString().padStart(2, '0')}`);
|
||||||
|
if (card.ip) card.ip.text(`IP Address: N/A`);
|
||||||
|
if (card.buffer) card.buffer.text(`Buffer: N/A`);
|
||||||
|
if (card.status) card.status.text(`Status: Disconnected`);
|
||||||
|
if (card.vu) {
|
||||||
|
setProgress(i, card.vu, 0, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {PagingQueue[]}
|
||||||
|
*/
|
||||||
|
window.PagingQueue = [];
|
||||||
|
/**
|
||||||
|
* @type {JQuery<HTMLElement> | null}
|
||||||
|
*/
|
||||||
|
window.selectedpagingrow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} QueueTable
|
||||||
|
* @property {number} index - The index of the automatic queue item.
|
||||||
|
* @property {string} Date_Time - The date and time of the automatic queue item.
|
||||||
|
* @property {string} Source - The source of the automatic queue item.
|
||||||
|
* @property {string} Type - The type of the automatic queue item.
|
||||||
|
* @property {string} Message - The message of the automatic queue item.
|
||||||
|
* @property {string} SB_TAGS - The SB_TAGS of the automatic queue item.
|
||||||
|
* @property {string} BroadcastZones - The broadcast zones of the automatic queue item.
|
||||||
|
* @property {number} Repeat - The repeat count of the automatic queue item.
|
||||||
|
* @property {string} Language - The language of the automatic queue item.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {QueueTable[]}
|
||||||
|
*/
|
||||||
|
window.QueueTable = [];
|
||||||
|
/**
|
||||||
|
* @type {JQuery<HTMLElement> | null}
|
||||||
|
*/
|
||||||
|
window.selectedautomaticrow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills the paging queue table body with the provided data.
|
||||||
|
* @param {PagingQueue[]} vv array of PagingQueue objects
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function fill_pagingqueuetablebody(vv) {
|
||||||
|
$('#pagingqueuetable').empty();
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
vv.forEach(item => {
|
||||||
|
// fill index and description columns using item properties
|
||||||
|
$('#pagingqueuetable').append(`<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.date_Time}</td>
|
||||||
|
<td>${item.source}</td>
|
||||||
|
<td>${item.type}</td>
|
||||||
|
<td>${item.message}</td>
|
||||||
|
<td>${item.broadcastZones}</td>
|
||||||
|
</tr>`);
|
||||||
|
let $addedrow = $('#pagingqueuetable tr:last');
|
||||||
|
$addedrow.off('click').on('click', function () {
|
||||||
|
if (window.selectedpagingrow) {
|
||||||
|
window.selectedpagingrow.find('td').css('background-color', '');
|
||||||
|
if (window.selectedpagingrow.is($(this))) {
|
||||||
|
window.selectedpagingrow = null;
|
||||||
|
$('#removepagingqueue').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.selectedpagingrow = $(this);
|
||||||
|
window.selectedpagingrow.find('td').css('background-color', 'lightblue');
|
||||||
|
$('#removepagingqueue').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills the automatic queue table body with the provided data.
|
||||||
|
* @param {QueueTable[]} vv array of QueueTable objects
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function fill_automaticqueuetablebody(vv) {
|
||||||
|
$('#automaticqueuetable').empty();
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
vv.forEach(item => {
|
||||||
|
// fill index and description columns using item properties
|
||||||
|
//console.log("fill_automaticqueuetablebody: item", item);
|
||||||
|
$('#automaticqueuetable').append(`<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.date_Time}</td>
|
||||||
|
<td>${item.source}</td>
|
||||||
|
<td>${item.type}</td>
|
||||||
|
<td>${item.message}</td>
|
||||||
|
<td>${item.broadcastZones}</td>
|
||||||
|
</tr>`);
|
||||||
|
let $addedrow = $('#automaticqueuetable tr:last');
|
||||||
|
$addedrow.off('click').on('click', function () {
|
||||||
|
if (window.selectedautomaticrow) {
|
||||||
|
window.selectedautomaticrow.find('td').css('background-color', '');
|
||||||
|
if (window.selectedautomaticrow.is($(this))) {
|
||||||
|
window.selectedautomaticrow = null;
|
||||||
|
$('#removeautomatictable').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.selectedautomaticrow = $(this);
|
||||||
|
window.selectedautomaticrow.find('td').css('background-color', 'lightblue');
|
||||||
|
$('#removeautomatictable').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadPagingQueue(APIURL = "QueuePaging/") {
|
||||||
|
window.PagingQueue = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata) && okdata.length > 0) {
|
||||||
|
window.PagingQueue.push(...okdata);
|
||||||
|
fill_pagingqueuetablebody(window.PagingQueue);
|
||||||
|
} else {
|
||||||
|
console.log("reloadPagingQueue: okdata is not array");
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
console.log("reloadPagingQueue: errdata", errdata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadAutomaticQueue(APIURL = "QueueTable/") {
|
||||||
|
window.QueueTable = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata) && okdata.length > 0) {
|
||||||
|
window.QueueTable.push(...okdata);
|
||||||
|
fill_automaticqueuetablebody(window.QueueTable);
|
||||||
|
} else {
|
||||||
|
console.log("reloadAutomaticQueue: okdata is not array");
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
console.log("reloadAutomaticQueue: errdata", errdata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemovePagingQueueByIndex(index, APIURL = "QueuePaging/") {
|
||||||
|
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
||||||
|
console.log("RemovePagingQueueByIndex: okdata", okdata);
|
||||||
|
reloadPagingQueue(APIURL);
|
||||||
|
}, (errdata) => {
|
||||||
|
console.log("RemovePagingQueueByIndex: errdata", errdata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function RemoveAutomaticQueueByIndex(index, APIURL = "QueueTable/") {
|
||||||
|
fetchAPI(APIURL + "DeleteByIndex/" + index, "DELETE", {}, null, (okdata) => {
|
||||||
|
console.log("RemoveAutomaticQueueByIndex: okdata", okdata);
|
||||||
|
reloadAutomaticQueue(APIURL);
|
||||||
|
}, (errdata) => {
|
||||||
|
console.log("RemoveAutomaticQueueByIndex: errdata", errdata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("overview.js loaded");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$('#clearpagingqueue').off('click').on('click', function () {
|
||||||
|
DoClear("QueuePaging/", "Paging Queue", (okdata) => {
|
||||||
|
reloadPagingQueue();
|
||||||
|
alert("Success clear Paging Queue: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear Paging Queue: " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#removepagingqueue').off('click').on('click', function () {
|
||||||
|
if (window.selectedpagingrow) {
|
||||||
|
let cells = window.selectedpagingrow.find('td');
|
||||||
|
let index = Number(cells.eq(0).text());
|
||||||
|
let description = cells.eq(1).text();
|
||||||
|
if (!isNaN(index) && description && description.length > 0) {
|
||||||
|
if (confirm(`Are you sure to remove Paging Queue Index: ${index} Description: ${description} ?`)) {
|
||||||
|
RemovePagingQueueByIndex(index);
|
||||||
|
window.selectedpagingrow = null;
|
||||||
|
$('#removepagingqueue').prop('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$('#clearautomatictable').off('click').on('click', function () {
|
||||||
|
DoClear("QueueTable/", "Automatic Queue", (okdata) => {
|
||||||
|
reloadAutomaticQueue();
|
||||||
|
alert("Success clear Automatic Queue: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear Automatic Queue: " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#removeautomatictable').off('click').on('click', function () {
|
||||||
|
if (window.selectedautomaticrow) {
|
||||||
|
let cells = window.selectedautomaticrow.find('td');
|
||||||
|
let index = Number(cells.eq(0).text());
|
||||||
|
let description = cells.eq(1).text();
|
||||||
|
if (!isNaN(index) && description && description.length > 0) {
|
||||||
|
if (confirm(`Are you sure to remove Automatic Queue Index: ${index} Description: ${description} ?`)) {
|
||||||
|
RemoveAutomaticQueueByIndex(index);
|
||||||
|
window.selectedautomaticrow = null;
|
||||||
|
$('#removeautomatictable').prop('disabled', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let intervaljob1 = null;
|
||||||
|
let intervaljob2 = null;
|
||||||
|
function runIntervalJob() {
|
||||||
|
if (intervaljob1) clearInterval(intervaljob1);
|
||||||
|
intervaljob1 = setInterval(() => {
|
||||||
|
sendCommand("getStreamerOutputs", "");
|
||||||
|
}, 100);
|
||||||
|
if (intervaljob2) clearInterval(intervaljob2);
|
||||||
|
intervaljob2 = setInterval(() => {
|
||||||
|
sendCommand("getPagingQueue", "");
|
||||||
|
sendCommand("getAASQueue", "");
|
||||||
|
}, 2000);
|
||||||
|
console.log("overview.js interval job started");
|
||||||
|
}
|
||||||
|
|
||||||
|
runIntervalJob();
|
||||||
|
|
||||||
|
window.addEventListener('ws_connected', () => {
|
||||||
|
console.log("overview.js ws_connected event triggered");
|
||||||
|
runIntervalJob();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('ws_disconnected', () => {
|
||||||
|
console.log("overview.js ws_disconnected event triggered");
|
||||||
|
if (intervaljob) clearInterval(intervaljob);
|
||||||
|
intervaljob = null;
|
||||||
|
});
|
||||||
|
window.addEventListener('ws_message', (event) => {
|
||||||
|
let rep = event.detail;
|
||||||
|
let cmd = rep.reply;
|
||||||
|
let data = rep.data;
|
||||||
|
if (cmd && cmd.length > 0) {
|
||||||
|
switch (cmd) {
|
||||||
|
case "getPagingQueue":
|
||||||
|
let pq = JSON.parse(data);
|
||||||
|
//console.log("getPagingQueue:", pq);
|
||||||
|
window.PagingQueue = [];
|
||||||
|
if (Array.isArray(pq) && pq.length > 0) {
|
||||||
|
window.PagingQueue.push(...pq);
|
||||||
|
}
|
||||||
|
fill_pagingqueuetablebody(window.PagingQueue);
|
||||||
|
break;
|
||||||
|
case "getAASQueue":
|
||||||
|
let aq = JSON.parse(data);
|
||||||
|
//console.log("getAASQueue:", aq);
|
||||||
|
window.QueueTable = [];
|
||||||
|
if (Array.isArray(aq) && aq.length > 0) {
|
||||||
|
window.QueueTable.push(...aq);
|
||||||
|
}
|
||||||
|
fill_automaticqueuetablebody(window.QueueTable);
|
||||||
|
break;
|
||||||
|
case "getStreamerOutputs":
|
||||||
|
/**
|
||||||
|
* @type {StreamerOutputData[]}
|
||||||
|
*/
|
||||||
|
let so = JSON.parse(data);
|
||||||
|
UpdateStreamerCard(so);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('beforeunload', function () {
|
||||||
|
console.log("overview.js beforeunload event triggered");
|
||||||
|
clearInterval(intervaljob1);
|
||||||
|
clearInterval(intervaljob2);
|
||||||
|
intervaljob1 = null;
|
||||||
|
intervaljob2 = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
400
html/webpage/assets/js/schedulebank.js
Normal file
400
html/webpage/assets/js/schedulebank.js
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} ScheduleBank
|
||||||
|
* @property {number} index
|
||||||
|
* @property {string} description
|
||||||
|
* @property {string} day
|
||||||
|
* @property {string} time
|
||||||
|
* @property {string} soundpath
|
||||||
|
* @property {number} repeat
|
||||||
|
* @property {boolean} enable
|
||||||
|
* @property {string} broadcastZones
|
||||||
|
* @property {string} language
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** List of Schedulebank data loaded from server
|
||||||
|
* @type {ScheduleBank[]}
|
||||||
|
*/
|
||||||
|
window.schedulebankdata = [];
|
||||||
|
/**
|
||||||
|
* Currently selected schedulebank row in the table
|
||||||
|
* @type {JQuery<HTMLElement>|null}
|
||||||
|
*/
|
||||||
|
window.selectedschedulerow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill schedulebank table body with values
|
||||||
|
* @param {ScheduleBank[]} vv values to fill
|
||||||
|
*/
|
||||||
|
function fill_schedulebanktablebody(vv) {
|
||||||
|
$('#schedulebanktablebody').empty();
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.description}</td>
|
||||||
|
<td>${item.day}</td>
|
||||||
|
<td>${item.time}</td>
|
||||||
|
<td>${item.soundpath}</td>
|
||||||
|
<td>${item.repeat}</td>
|
||||||
|
<td>${item.enable}</td>
|
||||||
|
<td>${item.broadcastZones}</td>
|
||||||
|
<td>${item.language}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#schedulebanktablebody').append(row);
|
||||||
|
let $addedrow = $('#schedulebanktablebody tr:last');
|
||||||
|
$addedrow.click(function () {
|
||||||
|
if (selectedschedulerow) {
|
||||||
|
selectedschedulerow.find('td').css('background-color', '');
|
||||||
|
if (selectedschedulerow.is($(this))) {
|
||||||
|
selectedschedulerow = null;
|
||||||
|
$('#btnRemove').prop('disabled', true);
|
||||||
|
$('#btnEdit').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$addedrow.find('td').css('background-color', '#ffeeba');
|
||||||
|
selectedschedulerow = $addedrow;
|
||||||
|
$('#btnRemove').prop('disabled', false);
|
||||||
|
$('#btnEdit').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#tablesize').text("Table Size: " + vv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload timer bank from server
|
||||||
|
* @param {string} APIURL API URL endpoint, default "ScheduleBank/"
|
||||||
|
*/
|
||||||
|
function reloadTimerBank(APIURL = "ScheduleBank/") {
|
||||||
|
window.schedulebankdata = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.schedulebankdata.push(...okdata);
|
||||||
|
selectedschedulerow = null;
|
||||||
|
fill_schedulebanktablebody(window.schedulebankdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading schedulebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("schedulebank.js loaded successfully");
|
||||||
|
$('#schedulebanktablebody').empty();
|
||||||
|
selectedschedulerow = null;
|
||||||
|
let $btnClear = $('#btnClear');
|
||||||
|
let $btnAdd = $('#btnAdd');
|
||||||
|
let $btnEdit = $('#btnEdit');
|
||||||
|
let $btnRemove = $('#btnRemove');
|
||||||
|
let $btnExport = $('#btnExport');
|
||||||
|
let $btnImport = $('#btnImport');
|
||||||
|
$btnEdit.prop('disabled', true);
|
||||||
|
$btnRemove.prop('disabled', true);
|
||||||
|
let APIURL = "ScheduleBank/";
|
||||||
|
|
||||||
|
let $schedulemodal = $('#schedulemodal');
|
||||||
|
// text input
|
||||||
|
let $scheduleid = $schedulemodal.find('#scheduleid');
|
||||||
|
// text input
|
||||||
|
let $scheduledescription = $schedulemodal.find('#scheduledescription');
|
||||||
|
// number input 0-23
|
||||||
|
let $schedulehour = $schedulemodal.find('#schedulehour');
|
||||||
|
// number input 0-59
|
||||||
|
let $scheduleminute = $schedulemodal.find('#scheduleminute');
|
||||||
|
// select2 for message
|
||||||
|
let $schedulemessage = $schedulemodal.find('#schedulemessage');
|
||||||
|
$schedulemessage.select2({});
|
||||||
|
// number input 0-5
|
||||||
|
let $schedulerepeat = $schedulemodal.find('#schedulerepeat');
|
||||||
|
// checkbox
|
||||||
|
let $scheduleenable = $schedulemodal.find('#scheduleenable');
|
||||||
|
// select2 for broadcastzones
|
||||||
|
let $schedulezones = $schedulemodal.find('#schedulezones');
|
||||||
|
$schedulezones.select2({});
|
||||||
|
// radio button for everyday
|
||||||
|
let $scheduleeveryday = $schedulemodal.find('#scheduleeveryday');
|
||||||
|
// radio button for weekly
|
||||||
|
let $scheduleweekly = $schedulemodal.find('#scheduleweekly');
|
||||||
|
// select2 for weekly selection
|
||||||
|
let $weeklyselect = $schedulemodal.find('#weeklyselect');
|
||||||
|
$weeklyselect.select2({});
|
||||||
|
// radio button for specific date
|
||||||
|
let $schedulespecialdate = $schedulemodal.find('#schedulespecialdate');
|
||||||
|
// date input
|
||||||
|
let $scheduledate = $schedulemodal.find('#scheduledate');
|
||||||
|
// select2 for language
|
||||||
|
let $languageselect = $schedulemodal.find('#languageselect');
|
||||||
|
$languageselect.select2({});
|
||||||
|
|
||||||
|
$schedulespecialdate.on('change', function () {
|
||||||
|
if ($(this).is(':checked')) {
|
||||||
|
$scheduledate.prop('disabled', false);
|
||||||
|
} else {
|
||||||
|
$scheduledate.prop('disabled', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function clearScheduleModal() {
|
||||||
|
$scheduleid.prop('disabled', true).val('');
|
||||||
|
$scheduledescription.val('');
|
||||||
|
$schedulehour.val('0');
|
||||||
|
$scheduleminute.val('0');
|
||||||
|
$schedulerepeat.val('0');
|
||||||
|
$scheduleenable.prop('checked', true);
|
||||||
|
$scheduleeveryday.prop('checked', false);
|
||||||
|
|
||||||
|
$schedulespecialdate.prop('checked', false);
|
||||||
|
$scheduledate.prop('disabled', true).val('');
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let $findschedule = $('#findschedule');
|
||||||
|
$findschedule.on('input', function () {
|
||||||
|
let searchTerm = $findschedule.val().toLowerCase();
|
||||||
|
if (searchTerm.length > 0) {
|
||||||
|
window.selectedschedulerow = null;
|
||||||
|
let filtered = window.schedulebankdata.filter(item =>
|
||||||
|
item.description.toLowerCase().includes(searchTerm)
|
||||||
|
|| item.soundpath.toLowerCase().includes(searchTerm)
|
||||||
|
|| item.broadcastZones.toLowerCase().includes(searchTerm));
|
||||||
|
fill_schedulebanktablebody(filtered);
|
||||||
|
} else {
|
||||||
|
window.selectedschedulerow = null;
|
||||||
|
fill_schedulebanktablebody(window.schedulebankdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
reloadTimerBank(APIURL);
|
||||||
|
$btnClear.click(() => {
|
||||||
|
DoClear(APIURL, "Timerbank", (okdata) => {
|
||||||
|
reloadTimerBank(APIURL);
|
||||||
|
alert("Success clear schedulebank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear schedulebank : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
$btnAdd.click(() => {
|
||||||
|
$schedulemodal.modal('show');
|
||||||
|
clearScheduleModal();
|
||||||
|
|
||||||
|
$schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () {
|
||||||
|
$schedulemodal.modal('hide');
|
||||||
|
});
|
||||||
|
$schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () {
|
||||||
|
// Gather form values
|
||||||
|
const Description = $scheduledescription.val();
|
||||||
|
const Soundpath = $schedulesoundpath.val();
|
||||||
|
const Repeat = parseInt($schedulerepeat.val(), 10);
|
||||||
|
const Enable = $scheduleenable.is(':checked');
|
||||||
|
// Collect selected days
|
||||||
|
let Day = "";
|
||||||
|
if ($scheduleeveryday.is(':checked')) {
|
||||||
|
Day = "Everyday";
|
||||||
|
} else if ($schedulespecialdate.is(':checked')) {
|
||||||
|
Day = $scheduledate.val();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($schedulesunday.is(':checked')) Day = "Sunday";
|
||||||
|
if ($schedulemonday.is(':checked')) Day = "Monday";
|
||||||
|
if ($scheduletuesday.is(':checked')) Day = "Tuesday";
|
||||||
|
if ($schedulewednesday.is(':checked')) Day = "Wednesday";
|
||||||
|
if ($schedulethursday.is(':checked')) Day = "Thursday";
|
||||||
|
if ($schedulefriday.is(':checked')) Day = "Friday";
|
||||||
|
if ($schedulesaturday.is(':checked')) Day = "Saturday";
|
||||||
|
}
|
||||||
|
// Broadcast zones (assuming comma-separated string)
|
||||||
|
const BroadcastZones = $schedulezones.val();
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!Description || !Soundpath || Day === "") {
|
||||||
|
alert("Description, sound path, and day are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format time as HH:mm
|
||||||
|
const hour = parseInt($schedulehour.val(), 10);
|
||||||
|
const minute = parseInt($scheduleminute.val(), 10);
|
||||||
|
const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// Prepare object
|
||||||
|
const scheduleObj = {
|
||||||
|
Description,
|
||||||
|
Day,
|
||||||
|
Time,
|
||||||
|
Soundpath,
|
||||||
|
Repeat,
|
||||||
|
Enable,
|
||||||
|
BroadcastZones
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAPI(APIURL + "Add", "POST", {}, scheduleObj, (okdata) => {
|
||||||
|
alert("Success add schedule: " + okdata.message);
|
||||||
|
reloadTimerBank(APIURL);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error add schedule: " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
$schedulemodal.modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$btnRemove.click(() => {
|
||||||
|
if (window.selectedschedulerow) {
|
||||||
|
let cells = window.selectedschedulerow.find('td');
|
||||||
|
/** @type {ScheduleBank} */
|
||||||
|
let sr = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
day: cells.eq(2).text(),
|
||||||
|
time: cells.eq(3).text(),
|
||||||
|
soundpath: cells.eq(4).text(),
|
||||||
|
repeat: cells.eq(5).text(),
|
||||||
|
enable: cells.eq(6).text(),
|
||||||
|
broadcastZones: cells.eq(7).text(),
|
||||||
|
language: cells.eq(8).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to delete schedule [${sr.index}] Description=${sr.description}?`)) {
|
||||||
|
fetchAPI(APIURL + "DeleteByIndex/" + sr.index, "DELETE", {}, null, (okdata) => {
|
||||||
|
reloadTimerBank(APIURL);
|
||||||
|
alert("Success delete schedule : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error delete schedule : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnEdit.click(() => {
|
||||||
|
if (window.selectedschedulerow) {
|
||||||
|
let cells = window.selectedschedulerow.find('td');
|
||||||
|
/** @type {ScheduleBank} */
|
||||||
|
let sr = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
day: cells.eq(2).text(),
|
||||||
|
time: cells.eq(3).text(),
|
||||||
|
soundpath: cells.eq(4).text(),
|
||||||
|
repeat: cells.eq(5).text(),
|
||||||
|
enable: cells.eq(6).text(),
|
||||||
|
broadcastZones: cells.eq(7).text(),
|
||||||
|
language: cells.eq(8).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to edit schedule [${sr.index}] Description=${sr.description}?`)) {
|
||||||
|
$schedulemodal.modal('show');
|
||||||
|
// fill the form with existing data
|
||||||
|
$scheduleid.val(sr.index);
|
||||||
|
$scheduledescription.val(sr.description);
|
||||||
|
let [hour, minute] = sr.time.split(':').map(num => parseInt(num, 10));
|
||||||
|
$schedulehour.val(hour.toString());
|
||||||
|
$scheduleminute.val(minute.toString());
|
||||||
|
$schedulesoundpath.val(sr.soundpath);
|
||||||
|
$schedulerepeat.val(sr.repeat.toString());
|
||||||
|
$scheduleenable.prop('checked', sr.enable.toLowerCase() === 'true');
|
||||||
|
switch (sr.day) {
|
||||||
|
case 'Everyday':
|
||||||
|
$scheduleeveryday.click();
|
||||||
|
break;
|
||||||
|
case 'Sunday':
|
||||||
|
$schedulesunday.click();
|
||||||
|
break;
|
||||||
|
case 'Monday':
|
||||||
|
$schedulemonday.click();
|
||||||
|
break;
|
||||||
|
case 'Tuesday':
|
||||||
|
$scheduletuesday.click();
|
||||||
|
break;
|
||||||
|
case 'Wednesday':
|
||||||
|
$schedulewednesday.click();
|
||||||
|
break;
|
||||||
|
case 'Thursday':
|
||||||
|
$schedulethursday.click();
|
||||||
|
break;
|
||||||
|
case 'Friday':
|
||||||
|
$schedulefriday.click();
|
||||||
|
break;
|
||||||
|
case 'Saturday':
|
||||||
|
$schedulesaturday.click();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// check if the day is in format dd/mm/yyyy
|
||||||
|
// and set the special date radio button and date input
|
||||||
|
if (/^\d{2}\/\d{2}\/\d{4}$/.test(sr.day)) {
|
||||||
|
$schedulespecialdate.click();
|
||||||
|
$scheduledate.val(sr.day);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$schedulemodal.off('click.scheduleclose').on('click.scheduleclose', '#scheduleclose', function () {
|
||||||
|
$schedulemodal.modal('hide');
|
||||||
|
});
|
||||||
|
$schedulemodal.off('click.schedulesave').on('click.schedulesave', '#schedulesave', function () {
|
||||||
|
// Gather form values
|
||||||
|
const Description = $scheduledescription.val();
|
||||||
|
const Soundpath = $schedulesoundpath.val();
|
||||||
|
const Repeat = parseInt($schedulerepeat.val(), 10);
|
||||||
|
const Enable = $scheduleenable.is(':checked');
|
||||||
|
// Collect selected days
|
||||||
|
let Day = "";
|
||||||
|
if ($scheduleeveryday.is(':checked')) {
|
||||||
|
Day = "Everyday";
|
||||||
|
} else if ($schedulespecialdate.is(':checked')) {
|
||||||
|
Day = $scheduledate.val();
|
||||||
|
} else {
|
||||||
|
if ($schedulesunday.is(':checked')) Day = "Sunday";
|
||||||
|
if ($schedulemonday.is(':checked')) Day = "Monday";
|
||||||
|
if ($scheduletuesday.is(':checked')) Day = "Tuesday";
|
||||||
|
if ($schedulewednesday.is(':checked')) Day = "Wednesday";
|
||||||
|
if ($schedulethursday.is(':checked')) Day = "Thursday";
|
||||||
|
if ($schedulefriday.is(':checked')) Day = "Friday";
|
||||||
|
if ($schedulesaturday.is(':checked')) Day = "Saturday";
|
||||||
|
}
|
||||||
|
// Broadcast zones (assuming comma-separated string)
|
||||||
|
const BroadcastZones = $schedulezones.val();
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!Description || !Soundpath || Day === "") {
|
||||||
|
alert("Description, sound path, and day are required.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format time as HH:mm
|
||||||
|
const hour = parseInt($schedulehour.val(), 10);
|
||||||
|
const minute = parseInt($scheduleminute.val(), 10);
|
||||||
|
const Time = `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// Prepare object
|
||||||
|
const scheduleObj = {
|
||||||
|
Description,
|
||||||
|
Day,
|
||||||
|
Time,
|
||||||
|
Soundpath,
|
||||||
|
Repeat,
|
||||||
|
Enable,
|
||||||
|
BroadcastZones
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAPI(APIURL + "UpdateByIndex/" + sr.index, "PATCH", {}, scheduleObj, (okdata) => {
|
||||||
|
alert("Success edit schedule: " + okdata.message);
|
||||||
|
reloadTimerBank(APIURL);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error edit schedule: " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
$schedulemodal.modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnExport.click(() => {
|
||||||
|
DoExport(APIURL, "schedulebank.xlsx", {});
|
||||||
|
});
|
||||||
|
$btnImport.click(() => {
|
||||||
|
DoImport(APIURL, (okdata) => {
|
||||||
|
reloadTimerBank(APIURL);
|
||||||
|
alert("Success import schedulebank from XLSX : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error importing schedulebank from XLSX : " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
512
html/webpage/assets/js/script.js
Normal file
512
html/webpage/assets/js/script.js
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
/**
|
||||||
|
* List of voice types available
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
window.voiceTypes = [];
|
||||||
|
/**
|
||||||
|
* List of categories available
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
window.categories = [];
|
||||||
|
/**
|
||||||
|
* List of languages available
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
window.languages = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of scheduled days available
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
window.scheduledays = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a list item element
|
||||||
|
* @param {String} text Text Content for the list item
|
||||||
|
* @param {String} className Specific class name for the list item
|
||||||
|
* @returns {JQuery<HTMLElement>}
|
||||||
|
*/
|
||||||
|
function ListItem(text, className = "") {
|
||||||
|
return $('<li>').addClass(className).text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket connection
|
||||||
|
* @type {WebSocket}
|
||||||
|
*/
|
||||||
|
window.ws = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a command to the WebSocket server.
|
||||||
|
* @param {String} command command to send
|
||||||
|
* @param {String} data data to send
|
||||||
|
*/
|
||||||
|
function sendCommand(command, data) {
|
||||||
|
if (window.ws.readyState === WebSocket.OPEN) {
|
||||||
|
window.ws.send(JSON.stringify({ command, data }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch API helper function
|
||||||
|
* @param {string} endpoint Endpoint URL
|
||||||
|
* @param {string} method Method (GET, POST, etc.)
|
||||||
|
* @param {Object} headers Headers to include in the request
|
||||||
|
* @param {Object} body Body of the request
|
||||||
|
* @param {Function} cbOK Callback function for successful response
|
||||||
|
* @param {Function} cbError Callback function for error response
|
||||||
|
*/
|
||||||
|
function fetchAPI(endpoint, method, headers = {}, body = null, cbOK, cbError) {
|
||||||
|
let url = window.location.origin + "/api/" + endpoint;
|
||||||
|
let options = {
|
||||||
|
method: method,
|
||||||
|
headers: headers
|
||||||
|
}
|
||||||
|
if (body !== null) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
if (!options.headers['Content-Type']) {
|
||||||
|
options.headers['Content-Type'] = 'application/json';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetch(url, options)
|
||||||
|
.then(async (response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
let msg;
|
||||||
|
try {
|
||||||
|
let _xxx = await response.json();
|
||||||
|
msg = _xxx.message || response.statusText;
|
||||||
|
} catch {
|
||||||
|
msg = await response.statusText;
|
||||||
|
}
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
cbOK(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
cbError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch asset file from /assets/img/*
|
||||||
|
* @param {String} url the filename to fetch, relative to /assets/img/
|
||||||
|
* @param {Function} cbOK callback function on success, will receive the object URL
|
||||||
|
* @param {Function} cbError callback function on error, will receive the error object
|
||||||
|
*/
|
||||||
|
function fetchImg(url, cbOK, cbError) {
|
||||||
|
url = "/assets/img/" + url;
|
||||||
|
fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok ' + response.statusText);
|
||||||
|
}
|
||||||
|
return response.blob();
|
||||||
|
})
|
||||||
|
.then(blob => {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
cbOK(url);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
cbError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload voice types from server
|
||||||
|
*/
|
||||||
|
function getVoiceTypes() {
|
||||||
|
window.voiceTypes = [];
|
||||||
|
fetchAPI("VoiceType", "GET", {}, null, (okdata) => {
|
||||||
|
// okdata is a string contains elements separated by semicolon ;
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.voiceTypes = okdata.filter(item => item.trim().length > 0);
|
||||||
|
//console.log("Loaded " + voiceTypes.length + " voice types : " + voiceTypes.join(", "));
|
||||||
|
} else console.log("getVoiceTypes: okdata is not array");
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading voice types : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload categories from server
|
||||||
|
*/
|
||||||
|
function getCategories() {
|
||||||
|
window.categories = [];
|
||||||
|
fetchAPI("Category", "GET", {}, null, (okdata) => {
|
||||||
|
// okdata is a string contains elements separated by semicolon ;
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.categories = okdata.filter(item => item.trim().length > 0);
|
||||||
|
//console.log("Loaded " + categories.length + " categories : " + categories.join(", "));
|
||||||
|
} else console.log("getCategories: okdata is not array");
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading categories : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload languages from server
|
||||||
|
*/
|
||||||
|
function getLanguages() {
|
||||||
|
window.languages = [];
|
||||||
|
fetchAPI("Language", "GET", {}, null, (okdata) => {
|
||||||
|
// okdata is a string contains elements separated by semicolon ;
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.languages = okdata.filter(item => item.trim().length > 0);
|
||||||
|
//console.log("Loaded " + languages.length + " languages : " + languages.join(", ") );
|
||||||
|
} else console.log("getLanguages: okdata is not array");
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading languages : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload scheduled days from server
|
||||||
|
*/
|
||||||
|
function getScheduledDays() {
|
||||||
|
window.scheduledays = [];
|
||||||
|
fetchAPI("ScheduleDay", "GET", {}, null, (okdata) => {
|
||||||
|
// okdata is a string contains elements separated by semicolon ;
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.scheduledays = okdata.filter(item => item.trim().length > 0);
|
||||||
|
//console.log("Loaded " + scheduledays.length + " scheduled days : " + scheduledays.join(", ") );
|
||||||
|
} else console.log("getScheduledDays: okdata is not array");
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading scheduled days : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear database mechanism
|
||||||
|
* @param {String} APIURL API URL endpoint
|
||||||
|
* @param {String} whattoclear what to clear
|
||||||
|
* @param {Function} cbOK callback function on success
|
||||||
|
* @param {Function} cbError callback function on error
|
||||||
|
*/
|
||||||
|
function DoClear(APIURL, whattoclear, cbOK, cbError) {
|
||||||
|
if (confirm(`Are you sure want to clear ${whattoclear} ? This procedure is not reversible`)) {
|
||||||
|
fetchAPI(APIURL + "List", "DELETE", {}, null, (okdata) => {
|
||||||
|
cbOK(okdata);
|
||||||
|
}, (errdata) => {
|
||||||
|
cbError(errdata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export mechanism to XLSX file
|
||||||
|
* @param {String} APIURL API URL endpoint
|
||||||
|
* @param {String} filename target filename
|
||||||
|
* @param {Object} queryParams additional query parameters as object
|
||||||
|
*/
|
||||||
|
function DoExport(APIURL, filename, queryParams = {}) {
|
||||||
|
// send GET request to APIURL + "ExportXLSX"
|
||||||
|
// reply Content-Type is application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
// reply Content-Disposition: attachment; filename=filename
|
||||||
|
// Use fetch to download the XLSX file as a blob and trigger download
|
||||||
|
let url = "/api/" + APIURL + "ExportXLSX";
|
||||||
|
if (queryParams && Object.keys(queryParams).length > 0) {
|
||||||
|
url += "?" + new URLSearchParams(queryParams).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error('Network response was not ok ' + response.statusText);
|
||||||
|
return response.blob();
|
||||||
|
})
|
||||||
|
.then(blob => {
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert("Error export to " + filename + ": " + error.message);
|
||||||
|
});
|
||||||
|
return; // prevent the rest of the function from running
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import mechanism from XLSX file
|
||||||
|
* @param {String} APIURL API URL endpoint
|
||||||
|
* @param {Function<Object>} cbOK function that accept object data
|
||||||
|
* @param {Function<Error>} cbError function that accept error object
|
||||||
|
*/
|
||||||
|
function DoImport(APIURL, cbOK, cbError) {
|
||||||
|
// Open file selection dialog that accepts only .xlsx files
|
||||||
|
// then upload to server using fetchAPI at "api/ImportXLSX" with POST method
|
||||||
|
let fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.accept = '.xlsx';
|
||||||
|
fileInput.onchange = e => {
|
||||||
|
let file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
fetch("/api/" + APIURL + "ImportXLSX", { method: 'POST', body: formData })
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) { throw new Error('Network response was not ok ' + response.statusText); }
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
cbOK(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
cbError(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cbError(new Error("No file selected"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fileInput.click();
|
||||||
|
fileInput.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.greencircle = null;
|
||||||
|
window.redcircle = null;
|
||||||
|
/**
|
||||||
|
* App entry point
|
||||||
|
*/
|
||||||
|
$(document).ready(function () {
|
||||||
|
document.title = "Automatic Announcement System"
|
||||||
|
if (window.greencircle === null) {
|
||||||
|
fetchImg('green_circle.png', (url) => { window.greencircle = url; }, (err) => { console.error("Error loading green_circle.png : ", err); });
|
||||||
|
}
|
||||||
|
if (window.redcircle === null) {
|
||||||
|
fetchImg('red_circle.png', (url) => { window.redcircle = url; }, (err) => { console.error("Error loading red_circle.png : ", err); });
|
||||||
|
}
|
||||||
|
const wsURL = window.location.pathname + '/ws'
|
||||||
|
if (chrome && chrome.runtime && chrome.runtime.lastError) {
|
||||||
|
alert("Runtime error: " + chrome.runtime.lastError.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// reset status indicators
|
||||||
|
function resetStatusIndicators() {
|
||||||
|
$('#onlineindicator').attr('src', window.redcircle);
|
||||||
|
$('#cpustatus').text("CPU : N/A");
|
||||||
|
$('#ramstatus').text("RAM : N/A");
|
||||||
|
$('#diskstatus').text("Disk : N/A");
|
||||||
|
$('#networkstatus').text("Network : N/A");
|
||||||
|
$('#datetimetext').text("Date/Time : N/A");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
resetStatusIndicators();
|
||||||
|
getVoiceTypes();
|
||||||
|
getCategories();
|
||||||
|
getLanguages();
|
||||||
|
getScheduledDays();
|
||||||
|
|
||||||
|
|
||||||
|
// reconnect handle
|
||||||
|
let ws_reconnect;
|
||||||
|
|
||||||
|
function reconnect() {
|
||||||
|
if (window.ws && window.ws.readyState === WebSocket.OPEN) return;
|
||||||
|
const s = new WebSocket(wsURL);
|
||||||
|
s.addEventListener('open', () => {
|
||||||
|
console.log('WebSocket connection established');
|
||||||
|
$('#onlineindicator').attr('src', window.greencircle);
|
||||||
|
|
||||||
|
if (ws_reconnect) {
|
||||||
|
// stop reconnect attempts
|
||||||
|
clearTimeout(ws_reconnect);
|
||||||
|
ws_reconnect = null;
|
||||||
|
}
|
||||||
|
window.dispatchEvent(new Event('ws_connected'));
|
||||||
|
});
|
||||||
|
s.addEventListener('close', () => {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
window.dispatchEvent(new Event('ws_disconnected'));
|
||||||
|
resetStatusIndicators();
|
||||||
|
if (!ws_reconnect) {
|
||||||
|
clearTimeout(ws_reconnect);
|
||||||
|
ws_reconnect = null;
|
||||||
|
}
|
||||||
|
ws_reconnect = setTimeout(reconnect, 5000); // try to reconnect every 5 seconds
|
||||||
|
});
|
||||||
|
s.addEventListener('message', (event) => {
|
||||||
|
if ($('#onlineindicator').attr('src') !== window.greencircle) {
|
||||||
|
$('#onlineindicator').attr('src', window.greencircle);
|
||||||
|
}
|
||||||
|
let rep = JSON.parse(event.data);
|
||||||
|
window.dispatchEvent(new CustomEvent('ws_message', { detail: rep }));
|
||||||
|
let cmd = rep.reply
|
||||||
|
let data = rep.data;
|
||||||
|
if (cmd && cmd.length > 0) {
|
||||||
|
switch (cmd) {
|
||||||
|
case "getCPUStatus":
|
||||||
|
$('#cpustatus').text("CPU : " + data)
|
||||||
|
break;
|
||||||
|
case "getMemoryStatus":
|
||||||
|
$('#ramstatus').text("RAM : " + data)
|
||||||
|
break;
|
||||||
|
case "getDiskStatus":
|
||||||
|
$('#diskstatus').text("Disk : " + data)
|
||||||
|
break;
|
||||||
|
case "getNetworkStatus":
|
||||||
|
let result = "";
|
||||||
|
let json = JSON.parse(data);
|
||||||
|
if (Array.isArray(json) && json.length > 0) {
|
||||||
|
json.forEach((net) => {
|
||||||
|
if (result.length > 0) result += "\n"
|
||||||
|
result += `${net.displayName} (${net.ipV4addr.join(";")}) TX:${(net.txSpeed / 1024).toFixed(1)} KB/s RX:${(net.rxSpeed / 1024).toFixed(1)} KB/s`
|
||||||
|
})
|
||||||
|
} else result = "N/A";
|
||||||
|
$('#networkstatus').text(result)
|
||||||
|
break;
|
||||||
|
case "getSystemTime":
|
||||||
|
$('#datetimetext').text(data)
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.ws = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
reconnect();
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
try{
|
||||||
|
window.ws?.close(1000, "Client closed connection");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error closing WebSocket connection:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
sendCommand("getCPUStatus", "")
|
||||||
|
sendCommand("getMemoryStatus", "")
|
||||||
|
sendCommand("getDiskStatus", "")
|
||||||
|
sendCommand("getNetworkStatus", "")
|
||||||
|
sendCommand("getSystemTime", "")
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
let sidemenu = new bootstrap.Offcanvas('#offcanvas-menu');
|
||||||
|
$('#showmenu').click(() => { sidemenu.show(); })
|
||||||
|
|
||||||
|
$('#homelink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('overview.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Overview content loaded successfully");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#soundbanklink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('soundbank.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Soundbank content loaded successfully");
|
||||||
|
// pindah soundbank.js
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error("Error loading soundbank content : ", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
$('#messagebanklink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('messagebank.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Messagebank content loaded successfully");
|
||||||
|
// pindah messagebank.js
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error("Error loading messagebank content : ", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
|
$('#languagelink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('language.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Language content loaded successfully");
|
||||||
|
// pindah languagelink.js
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error("Error loading language content : ", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
$('#broadcastzonelink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('broadcastzones.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Broadcast Zone content loaded successfully");
|
||||||
|
// pindah ke broadcastzones.js
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error("Error loading broadcast zone content : ", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
$('#timerlink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('timer.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Timer content loaded successfully");
|
||||||
|
// pindah ke schedulebank.js
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error("Error loading timer content : ", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
$('#loglink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('log.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Log content loaded successfully");
|
||||||
|
// pindah ke log.js
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error("Error loading log content:", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
$('#usermanagement').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('usermanagement.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("User Management content loaded successfully");
|
||||||
|
// pindah ke usermanagement.js
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.error("Error loading user management content:", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#settinglink').click(() => {
|
||||||
|
sidemenu.hide();
|
||||||
|
$('#content').load('setting.html', function (response, status, xhr) {
|
||||||
|
if (status === "success") {
|
||||||
|
console.log("Setting content loaded successfully");
|
||||||
|
//sendCommand("getSetting", "");
|
||||||
|
} else {
|
||||||
|
console.error("Error loading setting content:", xhr.status, xhr.statusText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
$('#logoutlink').click(() => {
|
||||||
|
window.location.href = "login.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
6108
html/webpage/assets/js/select2.js
Normal file
6108
html/webpage/assets/js/select2.js
Normal file
File diff suppressed because it is too large
Load Diff
2
html/webpage/assets/js/select2.min.js
vendored
Normal file
2
html/webpage/assets/js/select2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
432
html/webpage/assets/js/soundbank.js
Normal file
432
html/webpage/assets/js/soundbank.js
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Select2item
|
||||||
|
* @property {number} id
|
||||||
|
* @property {string} text
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} SoundBank
|
||||||
|
* @property {number} index
|
||||||
|
* @property {string} description
|
||||||
|
* @property {string} tag
|
||||||
|
* @property {string} category
|
||||||
|
* @property {string} language
|
||||||
|
* @property {string} voiceType
|
||||||
|
* @property {string} path
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of Soundbank data loaded from server
|
||||||
|
* @type {SoundBank[]}
|
||||||
|
*/
|
||||||
|
window.soundbankdata = [];
|
||||||
|
/**
|
||||||
|
* Currently selected soundbank row in the table
|
||||||
|
* @type {JQuery<HTMLElement>|null}
|
||||||
|
*/
|
||||||
|
window.selectedsoundrow = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select2 data source
|
||||||
|
* See https://select2.org/data-sources/formats
|
||||||
|
* @type {Select2item[]}
|
||||||
|
*/
|
||||||
|
window.select2data = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload sound bank from server
|
||||||
|
* @param {String} APIURL API URL endpoint, default "SoundBank/"
|
||||||
|
*/
|
||||||
|
function reloadSoundBank(APIURL = "SoundBank/") {
|
||||||
|
window.soundbankdata = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.soundbankdata.push(...okdata);
|
||||||
|
window.selectedsoundrow = null;
|
||||||
|
fill_soundbanktablebody(window.soundbankdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading soundbank : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill soundbank table body with values
|
||||||
|
* @param {SoundBank[]} vv values to fill
|
||||||
|
*/
|
||||||
|
function fill_soundbanktablebody(vv) {
|
||||||
|
$('#soundbanktablebody').empty();
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.description}</td>
|
||||||
|
<td>${item.tag}</td>
|
||||||
|
<td>${item.category}</td>
|
||||||
|
<td>${item.language}</td>
|
||||||
|
<td>${item.voiceType}</td>
|
||||||
|
<td>${item.path}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#soundbanktablebody').append(row);
|
||||||
|
let $addedrow = $('#soundbanktablebody tr:last');
|
||||||
|
$addedrow.on('click', function () {
|
||||||
|
if (window.selectedsoundrow) {
|
||||||
|
window.selectedsoundrow.find('td').css('background-color', '');
|
||||||
|
if (window.selectedsoundrow.is($(this))) {
|
||||||
|
window.selectedsoundrow = null;
|
||||||
|
$('#btnRemove').prop('disabled', true);
|
||||||
|
$('#btnEdit').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(this).find('td').css('background-color', '#ffeeba');
|
||||||
|
window.selectedsoundrow = $(this);
|
||||||
|
$('#btnRemove').prop('disabled', false);
|
||||||
|
$('#btnEdit').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$('#tablesize').text("Table Size: " + vv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload soundbank files from server and filter by language, category, and voiceType
|
||||||
|
* @param {String} language
|
||||||
|
* @param {String} category
|
||||||
|
* @param {String} voiceType
|
||||||
|
* @param {Function} cb callback function when done
|
||||||
|
*/
|
||||||
|
function reloadSoundbankFiles(language, category, voiceType, cb=null) {
|
||||||
|
window.select2data = [];
|
||||||
|
$('#modalpath').empty().trigger('change');
|
||||||
|
if (language && language.length > 0) {
|
||||||
|
if (category && category.length > 0) {
|
||||||
|
if (voiceType && voiceType.length > 0) {
|
||||||
|
fetchAPI(`ListFiles/${language}/${voiceType}/${category}`, "GET", {}, null, (okdata) => {
|
||||||
|
console.log("reloadSoundbankFiles: got " + okdata.length + " items");
|
||||||
|
if (Array.isArray(okdata)){
|
||||||
|
window.select2data = okdata.map(p => ({id: getFilenameFromPath(p), text: getFilenameFromPath(p)}));
|
||||||
|
|
||||||
|
$('#modalpath').select2({
|
||||||
|
data: window.select2data,
|
||||||
|
placeholder: 'Select a sound file',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
dropdownParent: $('#soundbankmodal')
|
||||||
|
});
|
||||||
|
if (cb) cb();
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading soundbank files : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilenameFromPath(path) {
|
||||||
|
if (!path || path.length === 0) return "";
|
||||||
|
if (path.includes('\\')) {
|
||||||
|
let parts = path.split('\\');
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
} else if (!path.includes('/')) {
|
||||||
|
let parts = path.split('/');
|
||||||
|
return parts[parts.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("soundbank.js loaded successfully");
|
||||||
|
$('#soundbanktablebody').empty();
|
||||||
|
window.selectedsoundrow = null;
|
||||||
|
let $btnClear = $('#btnClear');
|
||||||
|
let $btnAdd = $('#btnAdd');
|
||||||
|
let $btnRemove = $('#btnRemove');
|
||||||
|
let $btnEdit = $('#btnEdit');
|
||||||
|
let $btnExport = $('#btnExport');
|
||||||
|
let $btnImport = $('#btnImport');
|
||||||
|
$btnRemove.prop('disabled', true);
|
||||||
|
$btnEdit.prop('disabled', true);
|
||||||
|
let APIURL = "SoundBank/";
|
||||||
|
let $modal = $('#soundbankmodal');
|
||||||
|
let $modalindex = $modal.find('#modalindex');
|
||||||
|
let $modaldescription = $modal.find('#modaldescription');
|
||||||
|
let $modaltag = $modal.find('#modaltag');
|
||||||
|
let $modalcategory = $modal.find('#modalcategory');
|
||||||
|
let $modallanguage = $modal.find('#modallanguage');
|
||||||
|
let $modalvoicetype = $modal.find('#modalvoicetype');
|
||||||
|
let selected_category = null;
|
||||||
|
let selected_language = null;
|
||||||
|
let selected_voicetype = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear soundbank modal inputs
|
||||||
|
*/
|
||||||
|
function clearSoundbankModal() {
|
||||||
|
$modalindex.val('').prop('disabled', true);
|
||||||
|
$modaldescription.val('');
|
||||||
|
$modaltag.val('');
|
||||||
|
// fill modalcategory options from categories[]
|
||||||
|
$modalcategory.empty();
|
||||||
|
categories.forEach(cat => {
|
||||||
|
$modalcategory.append(new Option(cat, cat));
|
||||||
|
});
|
||||||
|
$modalcategory.val(null);
|
||||||
|
// fill modallanguage options from languages[]
|
||||||
|
$modallanguage.empty();
|
||||||
|
languages.forEach(lang => {
|
||||||
|
$modallanguage.append(new Option(lang, lang));
|
||||||
|
});
|
||||||
|
$modallanguage.val(null);
|
||||||
|
// fill modalvoicetype options from voiceTypes[]
|
||||||
|
$modalvoicetype.empty();
|
||||||
|
voiceTypes.forEach(vt => {
|
||||||
|
$modalvoicetype.append(new Option(vt, vt));
|
||||||
|
});
|
||||||
|
$modalvoicetype.val(null);
|
||||||
|
$('#modalpath').select2()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSoundBank(APIURL);
|
||||||
|
$('#findsoundbank').on('input', function () {
|
||||||
|
let searchTerm = $(this).val().trim().toLowerCase();
|
||||||
|
if (searchTerm.length > 0) {
|
||||||
|
window.selectedsoundrow = null;
|
||||||
|
let filtered = window.soundbankdata.filter(item => item.description.toLowerCase().includes(searchTerm) || item.tag.toLowerCase().includes(searchTerm) || item.path.toLowerCase().includes(searchTerm));
|
||||||
|
fill_soundbanktablebody(filtered);
|
||||||
|
} else {
|
||||||
|
window.selectedsoundrow = null;
|
||||||
|
fill_soundbanktablebody(window.soundbankdata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnClear.click(() => {
|
||||||
|
DoClear(APIURL, "Soundbank", (okdata) => {
|
||||||
|
reloadSoundBank(APIURL);
|
||||||
|
alert("Success clear soundbank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear soundbank : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function SetupEventForCategoryLanguageVoiceType() {
|
||||||
|
$modalcategory.off('change').on('change', function () {
|
||||||
|
selected_category = $(this).val();
|
||||||
|
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype);
|
||||||
|
});
|
||||||
|
$modallanguage.off('change').on('change', function () {
|
||||||
|
selected_language = $(this).val();
|
||||||
|
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype);
|
||||||
|
});
|
||||||
|
$modalvoicetype.off('change').on('change', function () {
|
||||||
|
selected_voicetype = $(this).val();
|
||||||
|
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$btnAdd.click(() => {
|
||||||
|
|
||||||
|
$modal.modal('show');
|
||||||
|
clearSoundbankModal();
|
||||||
|
// event on selection change of language, category, voiceType
|
||||||
|
SetupEventForCategoryLanguageVoiceType();
|
||||||
|
|
||||||
|
|
||||||
|
// event on Click save button
|
||||||
|
$modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
|
||||||
|
let description = $modaldescription.val().trim();
|
||||||
|
let tag = $modaltag.val().trim();
|
||||||
|
let category = $modalcategory.val();
|
||||||
|
let language = $modallanguage.val();
|
||||||
|
let voiceType = $modalvoicetype.val();
|
||||||
|
let path = $('#soundbankmodal #modalpath').val();
|
||||||
|
if (!description || description.length === 0) {
|
||||||
|
alert("Description is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tag || tag.length === 0) {
|
||||||
|
alert("Tag is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!category || category.length === 0) {
|
||||||
|
alert("Category is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!language || language.length === 0) {
|
||||||
|
alert("Language is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!voiceType || voiceType.length === 0) {
|
||||||
|
alert("Voice Type is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!path || path.length === 0) {
|
||||||
|
alert("Path is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$modal.modal('hide');
|
||||||
|
/**
|
||||||
|
* @type {SoundBank}
|
||||||
|
*/
|
||||||
|
let nsb = {
|
||||||
|
index: 0,
|
||||||
|
Description: description,
|
||||||
|
TAG: tag,
|
||||||
|
Category: category,
|
||||||
|
Language: language,
|
||||||
|
VoiceType: voiceType,
|
||||||
|
Path: path
|
||||||
|
}
|
||||||
|
fetchAPI(APIURL + "Add", "POST", {}, nsb, (okdata) => {
|
||||||
|
reloadSoundBank(APIURL);
|
||||||
|
alert("Success add soundbank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error add soundbank : " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// event on Click close button
|
||||||
|
$modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$btnRemove.click(() => {
|
||||||
|
if (window.selectedsoundrow) {
|
||||||
|
let cells = window.selectedsoundrow.find('td');
|
||||||
|
/** @type {SoundBank} */
|
||||||
|
let sb = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
tag: cells.eq(2).text(),
|
||||||
|
category: cells.eq(3).text(),
|
||||||
|
language: cells.eq(4).text(),
|
||||||
|
voiceType: cells.eq(5).text(),
|
||||||
|
path: cells.eq(6).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to delete soundbank [${sb.index}] Description=${sb.description} Tag=${sb.tag}?`)) {
|
||||||
|
fetchAPI(APIURL + "DeleteByIndex/" + sb.index, "DELETE", {}, null, (okdata) => {
|
||||||
|
reloadSoundBank(APIURL);
|
||||||
|
alert("Success delete soundbank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error delete soundbank : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnEdit.click(() => {
|
||||||
|
if (window.selectedsoundrow) {
|
||||||
|
let cells = window.selectedsoundrow.find('td');
|
||||||
|
/** @type {SoundBank} */
|
||||||
|
let sb = {
|
||||||
|
index: Number(cells.eq(0).text()),
|
||||||
|
Description: cells.eq(1).text(),
|
||||||
|
TAG: cells.eq(2).text(),
|
||||||
|
Category: cells.eq(3).text(),
|
||||||
|
Language: cells.eq(4).text(),
|
||||||
|
VoiceType: cells.eq(5).text(),
|
||||||
|
Path: cells.eq(6).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to edit soundbank [${sb.index}] Description=${sb.Description} Tag=${sb.TAG}?`)) {
|
||||||
|
$modal.modal('show');
|
||||||
|
clearSoundbankModal();
|
||||||
|
SetupEventForCategoryLanguageVoiceType();
|
||||||
|
$modalindex.val(sb.index).prop('disabled', true);
|
||||||
|
$modaldescription.val(sb.Description);
|
||||||
|
$modaltag.val(sb.TAG);
|
||||||
|
$modalcategory.val(sb.Category);
|
||||||
|
selected_category = sb.Category;
|
||||||
|
$modallanguage.val(sb.Language);
|
||||||
|
selected_language = sb.Language;
|
||||||
|
$modalvoicetype.val(sb.VoiceType);
|
||||||
|
selected_voicetype = sb.VoiceType;
|
||||||
|
// load soundbank files for selected language, category, voiceType
|
||||||
|
reloadSoundbankFiles(selected_language, selected_category, selected_voicetype, () => {
|
||||||
|
// set selected path
|
||||||
|
$('#modalpath').val(getFilenameFromPath(sb.Path)).trigger('change');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// event on Click save button
|
||||||
|
$modal.off('click.soundbanksave').on('click.soundbanksave', '#soundbanksave', function () {
|
||||||
|
let description = $modaldescription.val().trim();
|
||||||
|
let tag = $modaltag.val().trim();
|
||||||
|
let category = $modalcategory.val();
|
||||||
|
let language = $modallanguage.val();
|
||||||
|
let voiceType = $modalvoicetype.val();
|
||||||
|
let path = $('#soundbankmodal #modalpath').val();
|
||||||
|
if (!description || description.length === 0) {
|
||||||
|
alert("Description is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tag || tag.length === 0) {
|
||||||
|
alert("Tag is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!category || category.length === 0) {
|
||||||
|
alert("Category is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!language || language.length === 0) {
|
||||||
|
alert("Language is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!voiceType || voiceType.length === 0) {
|
||||||
|
alert("Voice Type is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!path || path.length === 0) {
|
||||||
|
alert("Path is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (description === sb.Description && tag === sb.TAG && category === sb.Category && language === sb.Language && voiceType === sb.VoiceType && path === sb.Path) {
|
||||||
|
alert("No changes detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sb.Description = description;
|
||||||
|
sb.TAG = tag;
|
||||||
|
sb.Category = category;
|
||||||
|
sb.Language = language;
|
||||||
|
sb.VoiceType = voiceType;
|
||||||
|
sb.Path = path;
|
||||||
|
fetchAPI(APIURL + "UpdateByIndex/" + sb.index, "PATCH", {}, sb, (okdata) => {
|
||||||
|
reloadSoundBank(APIURL);
|
||||||
|
alert("Success update soundbank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error update soundbank : " + errdata.message);
|
||||||
|
});
|
||||||
|
$modal.modal('hide');
|
||||||
|
|
||||||
|
});
|
||||||
|
// event on Click close button
|
||||||
|
$modal.off('click.soundbankclose').on('click.soundbankclose', '#soundbankclose', function () {
|
||||||
|
$modal.modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnExport.click(() => {
|
||||||
|
DoExport(APIURL, "soundbank.xlsx", {});
|
||||||
|
});
|
||||||
|
$btnImport.click(() => {
|
||||||
|
DoImport(APIURL, (okdata) => {
|
||||||
|
reloadSoundBank(APIURL);
|
||||||
|
alert("Success import soundbank : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error importing soundbank from XLSX : " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
185
html/webpage/assets/js/soundchannel.js
Normal file
185
html/webpage/assets/js/soundchannel.js
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} SoundChannel
|
||||||
|
* @property {number} index - The index of the sound channel.
|
||||||
|
* @property {string} channel - The name of the sound channel.
|
||||||
|
* @property {string} ip - The IP address associated with the sound channel.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {SoundChannel[]}
|
||||||
|
*/
|
||||||
|
window.soundChannels = [];
|
||||||
|
|
||||||
|
// Currently selected sound channel row in the table
|
||||||
|
window.selectedSoundChannel = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fills the sound channel table body with the provided data.
|
||||||
|
* @param {SoundChannel[]} vv Sound channel data to populate the table.
|
||||||
|
*/
|
||||||
|
function fill_soundchanneltablebody(vv) {
|
||||||
|
let $btnEditSoundChannel = $('#btnEditSoundChannel');
|
||||||
|
let $tablesizeSoundChannel = $('#tablesizeSoundChannel');
|
||||||
|
$('#soundchanneltablebody').empty();
|
||||||
|
|
||||||
|
$tablesizeSoundChannel.text('Table Length : N/A');
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) return;
|
||||||
|
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.channel}</td>
|
||||||
|
<td>${item.ip}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#soundchanneltablebody').append(row);
|
||||||
|
let $addedrow = $('#soundchanneltablebody tr:last');
|
||||||
|
$addedrow.off('click').on('click', function () {
|
||||||
|
if (selectedSoundChannel) {
|
||||||
|
selectedSoundChannel.find('td').css('background-color', '');
|
||||||
|
if (selectedSoundChannel.is($(this))) {
|
||||||
|
selectedSoundChannel = null;
|
||||||
|
$btnEditSoundChannel.prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(this).find('td').css('background-color', '#ffeeba');
|
||||||
|
selectedSoundChannel = $(this);
|
||||||
|
$btnEditSoundChannel.prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$tablesizeSoundChannel.text("Table Size: " + vv.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload sound channels from server
|
||||||
|
* @param {String} APIURL API URL endpoint (default "SoundChannel/")
|
||||||
|
*/
|
||||||
|
function reloadSoundChannel(APIURL = "SoundChannel/") {
|
||||||
|
window.soundChannels = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
//console.log("reloadSoundChannel : ", okdata)
|
||||||
|
window.soundChannels.push(...okdata);
|
||||||
|
fill_soundchanneltablebody(window.soundChannels);
|
||||||
|
} else console.log("reloadSoundChannel: okdata is not array");
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading sound channels : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("soundchannel.js loaded successfully");
|
||||||
|
let $soundchannelmodal = $('#soundchannelmodal');
|
||||||
|
let $soundchannelindex = $soundchannelmodal.find('#soundchannelindex');
|
||||||
|
let $soundchanneldescription = $soundchannelmodal.find('#soundchanneldescription');
|
||||||
|
let $soundchannelip = $soundchannelmodal.find('#soundchannelip');
|
||||||
|
|
||||||
|
let $btnReinitializeSoundChannel = $('#btnReinitializeSoundChannel');
|
||||||
|
let $btnEditSoundChannel = $('#btnEditSoundChannel');
|
||||||
|
let $btnExportSoundChannel = $('#btnExportSoundChannel');
|
||||||
|
let $btnImportSoundChannel = $('#btnImportSoundChannel');
|
||||||
|
let $findsoundchannel = $('#findsoundchannel');
|
||||||
|
$btnEditSoundChannel.prop('disabled', true);
|
||||||
|
let API_SoundChannel = "SoundChannel/";
|
||||||
|
|
||||||
|
$findsoundchannel.off('input').on('input', function () {
|
||||||
|
let searchTerm = $(this).val().toLowerCase();
|
||||||
|
if (searchTerm.length==0){
|
||||||
|
window.selectedSoundChannel = null;
|
||||||
|
fill_soundchanneltablebody(window.soundChannels);
|
||||||
|
} else {
|
||||||
|
window.selectedSoundChannel = null;
|
||||||
|
let filteredChannels = window.soundChannels.filter(xx =>
|
||||||
|
xx.index.toString().includes(searchTerm) ||
|
||||||
|
xx.channel.toLowerCase().includes(searchTerm) ||
|
||||||
|
xx.ip.toLowerCase().includes(searchTerm)
|
||||||
|
);
|
||||||
|
fill_soundchanneltablebody(filteredChannels);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear sound channel modal inputs
|
||||||
|
*/
|
||||||
|
function clearSoundChannelModal() {
|
||||||
|
$soundchannelindex.val('');
|
||||||
|
$soundchanneldescription.val('');
|
||||||
|
$soundchannelip.val('');
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSoundChannel(API_SoundChannel);
|
||||||
|
$btnReinitializeSoundChannel.off('click').on('click', () => {
|
||||||
|
DoClear(API_SoundChannel, "SoundChannels", (okdata) => {
|
||||||
|
reloadSoundChannel(API_SoundChannel);
|
||||||
|
alert("Success clear sound channels: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear sound channels: " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$btnEditSoundChannel.off('click').on('click', () => {
|
||||||
|
if (selectedSoundChannel) {
|
||||||
|
let cells = selectedSoundChannel.find('td');
|
||||||
|
/** @type {SoundChannel} */
|
||||||
|
let sc = {
|
||||||
|
index: parseInt(cells.eq(0).text(), 10),
|
||||||
|
description: cells.eq(1).text(),
|
||||||
|
ip: cells.eq(2).text()
|
||||||
|
};
|
||||||
|
if (confirm(`Are you sure to edit sound channel [${sc.index}] Description=${sc.description} IP=${sc.ip}?`)) {
|
||||||
|
$soundchannelmodal.modal('show');
|
||||||
|
clearSoundChannelModal();
|
||||||
|
$soundchannelindex.val(sc.index).prop('disabled', true);
|
||||||
|
$soundchanneldescription.val(sc.description);
|
||||||
|
$soundchannelip.val(sc.ip);
|
||||||
|
|
||||||
|
// Handle save changes
|
||||||
|
$soundchannelmodal.off('click.soundchannelsave').on('click.soundchannelsave', '#soundchannelsave', function () {
|
||||||
|
let newsc = {
|
||||||
|
index: parseInt($soundchannelindex.val(), 10),
|
||||||
|
description: $soundchanneldescription.val(),
|
||||||
|
ip: $soundchannelip.val()
|
||||||
|
};
|
||||||
|
if (newsc.description.trim().length === 0) {
|
||||||
|
alert("Description cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newsc.ip.trim().length === 0) {
|
||||||
|
alert("IP cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newsc.description===sc.description && newsc.ip===sc.ip){
|
||||||
|
alert("No changes detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fetchAPI(API_SoundChannel + "UpdateByIndex/" + newsc.index, "PATCH", {}, newsc, (okdata) => {
|
||||||
|
reloadSoundChannel(API_SoundChannel);
|
||||||
|
alert("Success edit sound channel: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error edit sound channel: " + errdata.message);
|
||||||
|
});
|
||||||
|
$soundchannelmodal.modal('hide');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
$soundchannelmodal.off('click.soundchannelclose').on('click.soundchannelclose', '#soundchannelclose', function () {
|
||||||
|
$soundchannelmodal.modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$btnExportSoundChannel.off('click').on('click', () => {
|
||||||
|
DoExport(API_SoundChannel, "soundchannels.xlsx", {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$btnImportSoundChannel.off('click').on('click', () => {
|
||||||
|
DoImport(API_SoundChannel, (okdata) => {
|
||||||
|
reloadSoundChannel(API_SoundChannel);
|
||||||
|
alert("Success import sound channels: " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error importing sound channels from XLSX: " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
559
html/webpage/assets/js/usermanagement.js
Normal file
559
html/webpage/assets/js/usermanagement.js
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} UserDB
|
||||||
|
* @property {number} index Index number
|
||||||
|
* @property {string} username Username
|
||||||
|
* @property {string} password Password (plain)
|
||||||
|
* @property {string} location Location
|
||||||
|
* @property {string} airline_tags Airline variable tags (string) separated by semicolon ;
|
||||||
|
* @property {string} city_tags City variable tags (string) separated by semicolon ;
|
||||||
|
* @property {string} messagebank_ann_id Messagebank announcement ID (number) separated by semicolon ;
|
||||||
|
* @property {string} broadcastzones Broadcast zones description (string) separated by semicolon ;
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** List of UserDB data loaded from server
|
||||||
|
* @type {UserDB[]}
|
||||||
|
*/
|
||||||
|
window.userdb = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently selected user row in table
|
||||||
|
* @type {JQuery<HTMLElement>|null}
|
||||||
|
*/
|
||||||
|
window.selecteduserrow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} KeyValueMessage
|
||||||
|
* @property {string} key
|
||||||
|
* @property {string} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of airline tags loaded from server
|
||||||
|
* @type {KeyValueMessage[]}
|
||||||
|
*/
|
||||||
|
window.airlinetags = [];
|
||||||
|
/**
|
||||||
|
* List of city tags loaded from server
|
||||||
|
* @type {KeyValueMessage[]}
|
||||||
|
*/
|
||||||
|
window.citytags = [];
|
||||||
|
/**
|
||||||
|
* List of message bank IDs loaded from server
|
||||||
|
* @type {KeyValueMessage[]}
|
||||||
|
*/
|
||||||
|
window.messagebankids = [];
|
||||||
|
/**
|
||||||
|
* List of broadcast zones description loaded from server
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
window.broadcastzones = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Messagebank ANN_IDs from server
|
||||||
|
*/
|
||||||
|
function get_messagebankids() {
|
||||||
|
window.messagebankids = [];
|
||||||
|
fetchAPI("MessageBank/" + "MessageIDs", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.messagebankids.push(...okdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading message bank IDs : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Airline Tags from server
|
||||||
|
*/
|
||||||
|
function get_airlinetags() {
|
||||||
|
window.airlinetags = [];
|
||||||
|
fetchAPI("SoundBank/" + "AirlineTags", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.airlinetags.push(...okdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading airline tags : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get City Tags from server
|
||||||
|
*/
|
||||||
|
function get_citytags() {
|
||||||
|
window.citytags = [];
|
||||||
|
fetchAPI("SoundBank/" + "CityTags", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.citytags.push(...okdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading city tags : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Broadcast Zones descriptions from server
|
||||||
|
*/
|
||||||
|
function get_broadcastzones_descriptions() {
|
||||||
|
window.broadcastzones = [];
|
||||||
|
fetchAPI("BroadcastZones/" + "BroadcastZoneDescriptions", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.broadcastzones.push(...okdata);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading broadcast zones : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill user table body with values
|
||||||
|
* @param {UserDB[]} vv values to fill
|
||||||
|
*/
|
||||||
|
function fill_usertablebody(vv) {
|
||||||
|
$('#usertablebody').empty();
|
||||||
|
|
||||||
|
if (!Array.isArray(vv) || vv.length === 0) {
|
||||||
|
$('#btnExport').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vv.forEach(item => {
|
||||||
|
const row = `<tr>
|
||||||
|
<td>${item.index}</td>
|
||||||
|
<td>${item.username}</td>
|
||||||
|
<td>${item.location}</td>
|
||||||
|
<td>${item.airline_tags}</td>
|
||||||
|
<td>${item.city_tags}</td>
|
||||||
|
<td>${item.messagebank_ann_id}</td>
|
||||||
|
<td>${item.broadcastzones}</td>
|
||||||
|
</tr>`;
|
||||||
|
$('#usertablebody').append(row);
|
||||||
|
let $addedrow = $('#usertablebody tr:last');
|
||||||
|
$addedrow.off('click').on('click', function () {
|
||||||
|
if (window.selecteduserrow) {
|
||||||
|
window.selecteduserrow.find('td').css('background-color', '');
|
||||||
|
if (window.selecteduserrow.is($(this))) {
|
||||||
|
window.selecteduserrow = null;
|
||||||
|
$('#btnRemove').prop('disabled', true);
|
||||||
|
$('#btnEdit').prop('disabled', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(this).find('td').css('background-color', '#ffeeba');
|
||||||
|
window.selecteduserrow = $(this);
|
||||||
|
$('#btnRemove').prop('disabled', false);
|
||||||
|
$('#btnEdit').prop('disabled', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#tablesize').text("Table Size: " + vv.length);
|
||||||
|
$('#btnExport').prop('disabled', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload UserDB from server with date and filter
|
||||||
|
* @param {String} APIURL API URL endpoint , default "UserManagement/"
|
||||||
|
*/
|
||||||
|
function reloaduserDB(APIURL = "UserManagement/") {
|
||||||
|
window.userdb = [];
|
||||||
|
fetchAPI(APIURL + "List", "GET", {}, null, (okdata) => {
|
||||||
|
if (Array.isArray(okdata)) {
|
||||||
|
window.userdb.push(...okdata);
|
||||||
|
fill_usertablebody(window.userdb);
|
||||||
|
}
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error loading user database : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
console.log("usermanagement.js ready");
|
||||||
|
get_airlinetags();
|
||||||
|
get_citytags();
|
||||||
|
get_messagebankids();
|
||||||
|
get_broadcastzones_descriptions();
|
||||||
|
|
||||||
|
let APIURL = "UserManagement/";
|
||||||
|
|
||||||
|
function clearAddModal() {
|
||||||
|
$('#modalindex').val("");
|
||||||
|
$('#modalusername').val("");
|
||||||
|
$('#modalpassword').val("");
|
||||||
|
$('#modalverifypassword').val("");
|
||||||
|
$('#modalairlinetags').val("");
|
||||||
|
$('#modalcitytags').val("");
|
||||||
|
$('#modalmessagebank').val("");
|
||||||
|
$('#modalbroadcastzones').val("");
|
||||||
|
$('#modallocation').val("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fill_citylist() {
|
||||||
|
$('#citylist').empty();
|
||||||
|
citytags.forEach(tag => {
|
||||||
|
let value = `${tag.value} [${tag.key}]`;
|
||||||
|
const row = `<div class="form-check">
|
||||||
|
<input class="form-check-input citytagcheckbox" type="checkbox" value="${tag.key}" id="citytag_${tag.key}">
|
||||||
|
<label class="form-check-label" for="citytag_${tag.key}">
|
||||||
|
${value}
|
||||||
|
</label>
|
||||||
|
</div>`;
|
||||||
|
$('#citylist').append(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fill_airlinelist() {
|
||||||
|
$('#airlinelist').empty();
|
||||||
|
airlinetags.forEach(tag => {
|
||||||
|
let value = `${tag.value} [${tag.key}]`;
|
||||||
|
const row = `<div class="form-check">
|
||||||
|
<input class="form-check-input airlinetagcheckbox" type="checkbox" value="${tag.key}" id="airlinetag_${tag.key}">
|
||||||
|
<label class="form-check-label" for="airlinetag_${tag.key}">
|
||||||
|
${value}
|
||||||
|
</label>
|
||||||
|
</div>`;
|
||||||
|
$('#airlinelist').append(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// broadcast zone selection modal elements
|
||||||
|
|
||||||
|
function fill_broadcastzonelist() {
|
||||||
|
$('#broadcastzonelist').empty();
|
||||||
|
broadcastzones.forEach(desc => {
|
||||||
|
const row = `<div class="form-check">
|
||||||
|
<input class="form-check-input broadcastzonecheckbox" type="checkbox" value="${desc}" id="broadcastzone_${desc}">
|
||||||
|
<label class="form-check-label" for="broadcastzone_${desc}">
|
||||||
|
${desc}
|
||||||
|
</label>
|
||||||
|
</div>`;
|
||||||
|
$('#broadcastzonelist').append(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// messagebank selection modal elements
|
||||||
|
|
||||||
|
function fill_messagebanklist() {
|
||||||
|
$('#messagebanklist').empty();
|
||||||
|
messagebankids.forEach(id => {
|
||||||
|
let value = `${id.value} [${id.key}]`;
|
||||||
|
const row = `<div class="form-check">
|
||||||
|
<input class="form-check-input messagebankidcheckbox" type="checkbox" value="${id.key}" id="messagebankid_${id.key}">
|
||||||
|
<label class="form-check-label" for="messagebankid_${id.key}">
|
||||||
|
${value}
|
||||||
|
</label>
|
||||||
|
</div>`;
|
||||||
|
$('#messagebanklist').append(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$('#usertablebody').empty();
|
||||||
|
reloaduserDB();
|
||||||
|
$('#finduser').off('input').on('input', function () {
|
||||||
|
let searchTerm = $(this).val().toLowerCase();
|
||||||
|
if (searchTerm.length > 0) {
|
||||||
|
let filteredUsers = window.userdb.filter(user =>
|
||||||
|
user.username.toLowerCase().includes(searchTerm) ||
|
||||||
|
user.airline_tags.toLowerCase().includes(searchTerm) ||
|
||||||
|
user.city_tags.toLowerCase().includes(searchTerm)
|
||||||
|
//user.messagebank_ann_id.toLowerCase().includes(searchTerm) ||
|
||||||
|
//user.broadcastzones.toLowerCase().includes(searchTerm)
|
||||||
|
);
|
||||||
|
fill_usertablebody(filteredUsers);
|
||||||
|
} else {
|
||||||
|
fill_usertablebody(window.userdb);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show modal dialog for soundbank, messagebank, broadcastzone selection
|
||||||
|
* @param {boolean} editmode if true, edit mode, else add mode
|
||||||
|
* @param {number} index index of user to edit, default 0
|
||||||
|
*/
|
||||||
|
function modalshow(editmode = false, index=0) {
|
||||||
|
// event on click btnShowSoundbankModal
|
||||||
|
$('#btnShowSoundbankModal').off('click').on('click', function () {
|
||||||
|
$('#soundbankmodal').modal('show');
|
||||||
|
fill_citylist();
|
||||||
|
fill_airlinelist();
|
||||||
|
|
||||||
|
let airline = $('#modalairlinetags').val().trim();
|
||||||
|
let city = $('#modalcitytags').val().trim();
|
||||||
|
if (airline.length > 0) {
|
||||||
|
let airlinekeys = airline.split(";");
|
||||||
|
$('#airlinelist input[type=checkbox]').each(function () {
|
||||||
|
let tag = $(this).val();
|
||||||
|
if (airlinekeys.includes(tag)) {
|
||||||
|
$(this).prop('checked', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (city.length > 0) {
|
||||||
|
let citykeys = city.split(";");
|
||||||
|
$('#citylist input[type=checkbox]').each(function () {
|
||||||
|
let tag = $(this).val();
|
||||||
|
if (citykeys.includes(tag)) {
|
||||||
|
$(this).prop('checked', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#soundbankmodal').off('click.soundbankselectionsave').on('click.soundbankselectionsave', '#soundbankselectionsave', function () {
|
||||||
|
let selected_airlinetags = [];
|
||||||
|
$('#airlinelist input[type=checkbox]:checked').each(function () {
|
||||||
|
selected_airlinetags.push($(this).val());
|
||||||
|
});
|
||||||
|
let selected_citytags = [];
|
||||||
|
$('#citylist input[type=checkbox]:checked').each(function () {
|
||||||
|
selected_citytags.push($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log("Selected airline tags: ", selected_airlinetags);
|
||||||
|
//console.log("Selected city tags: ", selected_citytags);
|
||||||
|
|
||||||
|
if (selected_airlinetags.length == 0 || selected_citytags.length == 0) {
|
||||||
|
alert("Please select at least one airline tag and one city tag.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let airlinevalue = selected_airlinetags.join(";");
|
||||||
|
let cityvalue = selected_citytags.join(";");
|
||||||
|
$('#modalairlinetags').val(airlinevalue);
|
||||||
|
$('#modalcitytags').val(cityvalue);
|
||||||
|
|
||||||
|
|
||||||
|
$('#soundbankmodal').modal('hide');
|
||||||
|
});
|
||||||
|
$('#soundbankmodal').off('click.soundbankselectionclose').on('click.soundbankselectionclose', '#soundbankselectionclose', function () {
|
||||||
|
$('#soundbankmodal').modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// event on click btnShowMessagebankModal
|
||||||
|
$('#btnShowMessagebankModal').off('click').on('click', function () {
|
||||||
|
$('#messagebankmodal').modal('show');
|
||||||
|
fill_messagebanklist();
|
||||||
|
let messagebank = $('#modalmessagebank').val().trim();
|
||||||
|
if (messagebank.length > 0) {
|
||||||
|
let messagebankkeys = messagebank.split(";");
|
||||||
|
$('#messagebanklist input[type=checkbox]').each(function () {
|
||||||
|
let id = $(this).val();
|
||||||
|
if (messagebankkeys.includes(id)) {
|
||||||
|
$(this).prop('checked', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#messagebankmodal').off('click.messagebankselectionsave').on('click.messagebankselectionsave', '#messagebankselectionsave', function () {
|
||||||
|
let selected_messagebankids = [];
|
||||||
|
$('#messagebanklist input[type=checkbox]:checked').each(function () {
|
||||||
|
selected_messagebankids.push($(this).val());
|
||||||
|
});
|
||||||
|
//console.log("Selected message bank IDs: ", selected_messagebankids);
|
||||||
|
if (selected_messagebankids.length == 0) {
|
||||||
|
alert("Please select at least one message bank ID.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let messagebankvalue = selected_messagebankids.join(";");
|
||||||
|
$('#modalmessagebank').val(messagebankvalue);
|
||||||
|
|
||||||
|
$('#messagebankmodal').modal('hide');
|
||||||
|
});
|
||||||
|
$('#messagebankmodal').off('click.messagebankselectionclose').on('click.messagebankselectionclose', '#messagebankselectionclose', function () {
|
||||||
|
$('#messagebankmodal').modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// event on click btnShowBroaadcastZoneModal
|
||||||
|
$('#btnShowBroaadcastZoneModal').off('click').on('click', function () {
|
||||||
|
$('#broadcastzonemodal').modal('show');
|
||||||
|
fill_broadcastzonelist();
|
||||||
|
let broadcastzones = $('#modalbroadcastzones').val().trim();
|
||||||
|
if (broadcastzones.length > 0) {
|
||||||
|
let broadcastzonesvalues = broadcastzones.split(";");
|
||||||
|
$('#broadcastzonelist input[type=checkbox]').each(function () {
|
||||||
|
let desc = $(this).val();
|
||||||
|
if (broadcastzonesvalues.includes(desc)) {
|
||||||
|
$(this).prop('checked', true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$('#broadcastzonemodal').off('click.broadcastzoneselectionsave').on('click.broadcastzoneselectionsave', '#broadcastzoneselectionsave', function () {
|
||||||
|
let selected_broadcastzones = [];
|
||||||
|
$('#broadcastzonelist input[type=checkbox]:checked').each(function () {
|
||||||
|
selected_broadcastzones.push($(this).val());
|
||||||
|
});
|
||||||
|
//console.log("Selected broadcast zones: ", selected_broadcastzones);
|
||||||
|
if (selected_broadcastzones.length == 0) {
|
||||||
|
alert("Please select at least one broadcast zone.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let broadcastzonesvalue = selected_broadcastzones.join(";");
|
||||||
|
$('#modalbroadcastzones').val(broadcastzonesvalue);
|
||||||
|
$('#broadcastzonemodal').modal('hide');
|
||||||
|
});
|
||||||
|
$('#broadcastzonemodal').off('click.broadcastzoneselectionclose').on('click.broadcastzoneselectionclose', '#broadcastzoneselectionclose', function () {
|
||||||
|
$('#broadcastzonemodal').modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// event on Click save button
|
||||||
|
$('#addmodal').off('click.usermanagementsave').on('click.usermanagementsave', '#usermanagementsave', function () {
|
||||||
|
let username = $('#modalusername').val().trim();
|
||||||
|
let password = $('#modalpassword').val();
|
||||||
|
let verifypassword = $('#modalverifypassword').val();
|
||||||
|
let location = $('#modallocation').val().trim();
|
||||||
|
let airline_tags = $('#modalairlinetags').val().trim();
|
||||||
|
let city_tags = $('#modalcitytags').val().trim();
|
||||||
|
let messagebank_ann_id = $('#modalmessagebank').val().trim();
|
||||||
|
let broadcastzones = $('#modalbroadcastzones').val().trim();
|
||||||
|
|
||||||
|
if (username.length === 0) {
|
||||||
|
alert("Username cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password.length === 0 || verifypassword.length === 0) {
|
||||||
|
alert("Password cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (password !== verifypassword) {
|
||||||
|
alert("Password and Verify Password do not match");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (airline_tags.length === 0) {
|
||||||
|
alert("Airline tags cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (city_tags.length === 0) {
|
||||||
|
alert("City tags cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (messagebank_ann_id.length === 0) {
|
||||||
|
alert("Message bank ANN_ID cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (broadcastzones.length === 0) {
|
||||||
|
alert("Broadcast zones cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {UserDB}
|
||||||
|
*/
|
||||||
|
let ll = {
|
||||||
|
index: index,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
location: location,
|
||||||
|
airline_tags: airline_tags,
|
||||||
|
city_tags: city_tags,
|
||||||
|
messagebank_ann_id: messagebank_ann_id,
|
||||||
|
broadcastzones: broadcastzones
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editmode) {
|
||||||
|
fetchAPI(APIURL + "UpdateByIndex/" + index, "PATCH", {}, ll, (okdata) => {
|
||||||
|
alert("Success update User : " + okdata.message);
|
||||||
|
reloaduserDB();
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error update User : " + errdata.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fetchAPI(APIURL + "Add", "POST", {}, ll, (okdata) => {
|
||||||
|
alert("Success add User : " + okdata.message);
|
||||||
|
reloaduserDB();
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error add User : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$('#addmodal').modal('hide');
|
||||||
|
});
|
||||||
|
// event on Click close button
|
||||||
|
$('#addmodal').off('click.usermanagementclose').on('click.usermanagementclose', '#usermanagementclose', function () {
|
||||||
|
$('#addmodal').modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#btnClear').off('click').on('click', function () {
|
||||||
|
DoClear(APIURL, "UserManagement", (okdata) => {
|
||||||
|
reloaduserDB();
|
||||||
|
alert("Success clear user management : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error clear user management : " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$('#btnAdd').off('click').on('click', () => {
|
||||||
|
$('#addmodal').modal('show');
|
||||||
|
clearAddModal();
|
||||||
|
modalshow(false,0);
|
||||||
|
});
|
||||||
|
$('#btnRemove').off('click').on('click', () => {
|
||||||
|
if (window.selecteduserrow) {
|
||||||
|
let cells = window.selecteduserrow.find('td');
|
||||||
|
/** @type {UserDB} */
|
||||||
|
let user = {
|
||||||
|
index: parseInt(cells.eq(0).text()),
|
||||||
|
username: cells.eq(1).text(),
|
||||||
|
password: cells.eq(2).text(),
|
||||||
|
airline_tags: cells.eq(3).text(),
|
||||||
|
city_tags: cells.eq(4).text(),
|
||||||
|
messagebank_ann_id: cells.eq(5).text(),
|
||||||
|
broadcastzones: cells.eq(6).text()
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to delete user [${user.index}] Username=${user.username} ?`)) {
|
||||||
|
fetchAPI(APIURL + "DeleteByIndex/" + user.index, "DELETE", {}, null, (okdata) => {
|
||||||
|
reloaduserDB();
|
||||||
|
alert("Success delete user : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error delete user : " + errdata.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("No user selected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#btnEdit').off('click').on('click', () => {
|
||||||
|
if (window.selecteduserrow) {
|
||||||
|
let cells = window.selecteduserrow.find('td');
|
||||||
|
let index = parseInt(cells.eq(0).text());
|
||||||
|
if (isNaN(index) || index <= 0) {
|
||||||
|
alert("Invalid user index");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/** @type {UserDB} */
|
||||||
|
let user = window.userdb.find(u => u.index === index);
|
||||||
|
if (!user) {
|
||||||
|
alert("User not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (confirm(`Are you sure to edit user [${user.index}] Username=${user.username} ?`)) {
|
||||||
|
$('#addmodal').modal('show');
|
||||||
|
// fill modal with user data
|
||||||
|
$('#modalindex').val(user.index);
|
||||||
|
$('#modalusername').val(user.username);
|
||||||
|
$('#modalpassword').val(user.password);
|
||||||
|
$('#modalverifypassword').val(user.password);
|
||||||
|
$('#modallocation').val(user.location);
|
||||||
|
$('#modalairlinetags').val(user.airline_tags);
|
||||||
|
$('#modalcitytags').val(user.city_tags);
|
||||||
|
$('#modalmessagebank').val(user.messagebank_ann_id);
|
||||||
|
$('#modalbroadcastzones').val(user.broadcastzones);
|
||||||
|
modalshow(true, user.index);
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("No user selected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#btnExport').off('click').on('click', () => {
|
||||||
|
DoExport(APIURL, "user.xlsx", {});
|
||||||
|
});
|
||||||
|
$('#btnImport').off('click').on('click', () => {
|
||||||
|
DoImport(APIURL, (okdata) => {
|
||||||
|
reloaduserDB();
|
||||||
|
alert("Success import user : " + okdata.message);
|
||||||
|
}, (errdata) => {
|
||||||
|
alert("Error importing user from XLSX : " + errdata.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
291
html/webpage/broadcastzones.html
Normal file
291
html/webpage/broadcastzones.html
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">Sound Channel and Broadcast Zones</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="broadcastzonemodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-header-modal">
|
||||||
|
<h4 class="modal-title">Add / Edit Broadcast Zones</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Index</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="broadcastzoneindex" placeholder="index" readonly=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="broadcastzonedescription" class="form-control input-add" placeholder="description"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Sound Channel</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="broadcastzonesoundchannel" class="form-control input-add" placeholder="sound channel"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Box</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="broadcastzonebox" class="form-control input-add" placeholder="Box ID"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Relay</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8 pad-relay">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R01"><label class="form-check-label" for="formCheck-1">01</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R02"><label class="form-check-label" for="formCheck-2">02</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R03"><label class="form-check-label" for="formCheck-7">03</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R04"><label class="form-check-label" for="formCheck-6">04</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R05"><label class="form-check-label" for="formCheck-5">05</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R06"><label class="form-check-label" for="formCheck-4">06</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R07"><label class="form-check-label" for="formCheck-3">07</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R08"><label class="form-check-label" for="formCheck-8">08</label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 invisible">
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R09"><label class="form-check-label" for="formCheck-25">09</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R10"><label class="form-check-label" for="formCheck-26">10</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R11"><label class="form-check-label" for="formCheck-27">11</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R12"><label class="form-check-label" for="formCheck-28">12</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R13"><label class="form-check-label" for="formCheck-29">13</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R14"><label class="form-check-label" for="formCheck-30">14</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R15"><label class="form-check-label" for="formCheck-31">15</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R16"><label class="form-check-label" for="formCheck-32">16</label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 invisible">
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R17"><label class="form-check-label" for="formCheck-17">17</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R18"><label class="form-check-label" for="formCheck-18">18</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R19"><label class="form-check-label" for="formCheck-19">19</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R20"><label class="form-check-label" for="formCheck-20">20</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R21"><label class="form-check-label" for="formCheck-21">21</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R22"><label class="form-check-label" for="formCheck-22">22</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R23"><label class="form-check-label" for="formCheck-23">23</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R24"><label class="form-check-label" for="formCheck-24">24</label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 invisible">
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R25"><label class="form-check-label" for="formCheck-9">25</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R26"><label class="form-check-label" for="formCheck-10">26</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R27"><label class="form-check-label" for="formCheck-11">27</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R28"><label class="form-check-label" for="formCheck-12">28</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R29"><label class="form-check-label" for="formCheck-13">29</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R30"><label class="form-check-label" for="formCheck-14">30</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R31"><label class="form-check-label" for="formCheck-15">31</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="R32"><label class="form-check-label" for="formCheck-16">32</label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="broadcastzoneclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="broadcastzonesave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="accordion" role="tablist" id="accordion-1">
|
||||||
|
<div class="accordion-item pad-accordion">
|
||||||
|
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed bg-heading1" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="false" aria-controls="accordion-1 .item-1">Sound Channel</button></h2>
|
||||||
|
<div class="accordion-collapse collapse item-1" role="tabpanel" data-bs-parent="#accordion-1">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findsoundchannel" placeholder="Search keyword" name="findsoundbank"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row pad-search">
|
||||||
|
<div class="col-6 col-sm-6 col-md-3 col-lg-3 col-xl-3"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnReinitializeSoundChannel" type="button">Re-Initialize</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-3 col-lg-3 col-xl-3"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEditSoundChannel" type="button">Edit</button></div>
|
||||||
|
<div class="col-6 col-md-3 col-lg-3 col-xl-3 col-sm--6"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExportSoundChannel" type="button">Export</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-3 col-lg-3 col-xl-3"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImportSoundChannel" type="button">Import</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesizeSoundChannel" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class05">No</th>
|
||||||
|
<th class="class75">Description</th>
|
||||||
|
<th class="class20">IP Address</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="soundchanneltablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item pad-accordion">
|
||||||
|
<h2 class="accordion-header" role="tab"><button class="accordion-button bg-heading2" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="true" aria-controls="accordion-1 .item-2">Broadcast Zones</button></h2>
|
||||||
|
<div class="accordion-collapse collapse show item-2" role="tabpanel" data-bs-parent="#accordion-1">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findzone" placeholder="Search keyword" name="findsoundbank"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row pad-search">
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class05">No</th>
|
||||||
|
<th class="class40">Description</th>
|
||||||
|
<th class="class20">SoundChannel</th>
|
||||||
|
<th class="class05">ID</th>
|
||||||
|
<th class="class30">BP</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="broadcastzonetablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="soundchannelmodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-header-modal">
|
||||||
|
<h4 class="modal-title">Edit Sound Channel</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Index</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-25 form-control input-add" type="text" id="soundchannelindex" readonly="" placeholder="index"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchanneldescription" placeholder="Description" readonly=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">IP Address</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-xl-8 co-lg-8"><input class="w-100 form-control input-add" type="text" id="soundchannelip" placeholder="IP Address" required="" inputmode="numeric" pattern="^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundchannelclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundchannelsave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/soundchannel.js"></script>
|
||||||
|
<script src="assets/js/broadcastzones.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
108
html/webpage/home.html
Normal file
108
html/webpage/home.html
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS NewGeneration 17092025</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/select2.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="text-dark bg-light pad-header bg-header" id="header_home">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 col-sm-2 col-md-1 col-lg-1 col-xl-1"><button class="btn btn-primary h-100 bread-menu" id="showmenu" type="button"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" style="width: 32px;height: 32px;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path>
|
||||||
|
</svg></button></div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-10 col-lg-10 col-xl-10 col-xxl-10">
|
||||||
|
<h1 class="text-header" style="text-align: left;">Automatic Announcement System</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-1 col-lg-1 col-xl-1 col-xxl-1 offset-xxl-0 align-items-end align-ite"><img id="onlineindicator" class="img-indicator" src="assets/img/red_circle.png" width="24" height="24"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas offcanvas-start bg-body" tabindex="-1" data-bs-backdrop="false" id="offcanvas-menu">
|
||||||
|
<div class="offcanvas-header"><a class="link-body-emphasis d-flex align-items-center me-md-auto mb-3 mb-md-0 text-decoration-none" href="/"><img src="assets/img/logogtc-grey.png" width="48" height="48"><span class="fs-4 font-top-menu">AAS v.2 </span></a><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="offcanvas"></button></div>
|
||||||
|
<div class="offcanvas-body d-flex flex-column justify-content-between pt-0">
|
||||||
|
<div>
|
||||||
|
<hr class="mt-0">
|
||||||
|
<ul class="nav nav-pills flex-column mb-auto">
|
||||||
|
<li class="nav-item"><a class="nav-link active link-light text-menu" id="homelink" href="#" aria-current="page"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-house-door me-2" style="font-size: 20px;">
|
||||||
|
<path d="M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4z"></path>
|
||||||
|
</svg> Overview</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="soundbanklink" href="#"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-soundwave me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5m-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5m12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5"></path>
|
||||||
|
</svg> Sound Bank</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="messagebanklink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"></path>
|
||||||
|
</svg> Message Bank</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="languagelink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"></path>
|
||||||
|
</svg> Language Link</a></li>
|
||||||
|
<li class="nav-item .icon-menu"><a class="nav-link link-body-emphasis text-menu" id="timerlink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path>
|
||||||
|
<path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"></path>
|
||||||
|
</svg> Timer</a></li>
|
||||||
|
<li class="nav-item .icon-menu"><a class="nav-link link-body-emphasis text-menu" id="broadcastzonelink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M18.2 1H9.8C8.81 1 8 1.81 8 2.8v14.4c0 .99.81 1.79 1.8 1.79l8.4.01c.99 0 1.8-.81 1.8-1.8V2.8c0-.99-.81-1.8-1.8-1.8zM14 3c1.1 0 2 .89 2 2s-.9 2-2 2-2-.89-2-2 .9-2 2-2zm0 13.5c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4z"></path>
|
||||||
|
<circle cx="14" cy="12.5" r="2.5"></circle>
|
||||||
|
<path d="M6 5H4v16c0 1.1.89 2 2 2h10v-2H6V5z"></path>
|
||||||
|
</svg> Broadcast Zones</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="loglink" href="#"><svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="icon-menu" style="font-size: 20px;">
|
||||||
|
<g>
|
||||||
|
<rect fill="none" height="24" width="24"></rect>
|
||||||
|
<path d="M19,7H9C7.9,7,7,7.9,7,9v10c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V9C21,7.9,20.1,7,19,7z M19,9v2H9V9H19z M13,15v-2h2v2H13z M15,17v2h-2v-2H15z M11,15H9v-2h2V15z M17,13h2v2h-2V13z M9,17h2v2H9V17z M17,19v-2h2v2H17z M6,17H5c-1.1,0-2-0.9-2-2V5 c0-1.1,0.9-2,2-2h10c1.1,0,2,0.9,2,2v1h-2V5H5v10h1V17z"></path>
|
||||||
|
</g>
|
||||||
|
</svg> Log</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="usermanagement" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"></path>
|
||||||
|
</svg> User Management</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="settinglink" href="#"><svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<g>
|
||||||
|
<path d="M0,0h24v24H0V0z" fill="none"></path>
|
||||||
|
<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"></path>
|
||||||
|
</g>
|
||||||
|
</svg> Setting</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link link-body-emphasis text-menu" id="logoutlink" href="#"><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor" class="me-2 icon-menu" style="font-size: 20px;">
|
||||||
|
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"></path>
|
||||||
|
</svg> Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col card-status">
|
||||||
|
<p class="w-100 h-100 text-status" id="cpustatus">CPU Status : <br><br></p>
|
||||||
|
</div>
|
||||||
|
<div class="col card-status">
|
||||||
|
<p class="w-100 h-100 text-status" id="ramstatus">RAM Status : <br><br></p>
|
||||||
|
</div>
|
||||||
|
<div class="col card-status">
|
||||||
|
<p class="w-100 h-100 text-status" id="diskstatus">Disk Status : <br><br></p>
|
||||||
|
</div>
|
||||||
|
<div class="col card-status">
|
||||||
|
<p class="w-100 h-100 text-status" id="networkstatus">Network </p>
|
||||||
|
</div>
|
||||||
|
<div class="col card-status">
|
||||||
|
<p class="w-100 h-100 text-status" id="datetimetext">Date and Time </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container w-100 pad-container" id="content"></div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/jquery-3.7.1.min.js"></script>
|
||||||
|
<script src="assets/js/script.js"></script>
|
||||||
|
<script src="assets/js/select2.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
96
html/webpage/language.html
Normal file
96
html/webpage/language.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">Language Link</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row pad-row-search">
|
||||||
|
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findlanguage" placeholder="Search keyword" name="findsoundbank"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class05">No</th>
|
||||||
|
<th class="class20">TAG</th>
|
||||||
|
<th class="class75">Languages</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="languagebanktablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="languagemodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-header-modal">
|
||||||
|
<h4 class="modal-title">Add / Edit Language</h4><button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Index</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-control input-add" type="text" id="languagelinkindex" readonly=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Tag</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="languagelinktag" class="form-control input-add"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Language</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8 text-add">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="langId" name="languages[]" value="id"><label class="form-check-label" for="langId">Indonesia</label></div>
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="langLocal" name="languages[]" value="id"><label class="form-check-label" for="langId-1">Local</label></div>
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="langEn" name="languages[]" value="en"><label class="form-check-label" for="langEn">English</label></div>
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="langJap" name="languages[]" value="jap"><label class="form-check-label" for="langJap">Japanese</label></div>
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="langChi" name="languages[]" value="chi"><label class="form-check-label" for="langChi">Chinese</label></div>
|
||||||
|
<div class="form-check"><input class="form-check-input" type="checkbox" id="langArb" name="languages[]" value="arb"><label class="form-check-label" for="langArb">Arabic</label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="languagelinkclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="languagelinksave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/languagelink.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
58
html/webpage/log.html
Normal file
58
html/webpage/log.html
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">Operational Log</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2">
|
||||||
|
<p class="text-add">Select Log Date</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><input id="logdate" class="form-control" type="date"></div>
|
||||||
|
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||||
|
<div class="col-md-2 col-lg-2 col-xl-2"></div>
|
||||||
|
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-sm-4 col-md-2 col-lg-2 col-xl-2"><input type="text" id="searchfilter" class="form-control" placeholder="Search Filter"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class10">No</th>
|
||||||
|
<th class="class15">Date</th>
|
||||||
|
<th class="class15">Time</th>
|
||||||
|
<th class="class15">Machine</th>
|
||||||
|
<th class="class45">Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="logtablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/log.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
43
html/webpage/login.html
Normal file
43
html/webpage/login.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS NewGeneration 17092025</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<section class="position-relative py-4 py-xl-5">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-4"></div>
|
||||||
|
<div class="row d-flex justify-content-center mb-7">
|
||||||
|
<div class="col-md-6 col-xl-4">
|
||||||
|
<div class="card mb-5 card-login">
|
||||||
|
<div class="card-body d-flex flex-column align-items-center">
|
||||||
|
<div class="bs-icon-xl bs-icon-circle bs-icon-primary my-4 bs-icon bg-icon-login"><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 16 16" class="bi bi-person bg-icon-login">
|
||||||
|
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664z"></path>
|
||||||
|
</svg></div>
|
||||||
|
<h2 class="mb-3 h-login">Login</h2>
|
||||||
|
<form class="text-center py-2 bottom-signin" method="post">
|
||||||
|
<p class="p-login">Username</p>
|
||||||
|
<div class="mb-3"><input class="form-control input-login" type="text" name="username" placeholder="Enter your username"></div>
|
||||||
|
<p class="p-login">Password</p>
|
||||||
|
<div class="mb-3"><input class="form-control input-login" type="password" name="password" placeholder="Enter your password"></div>
|
||||||
|
<div class="mb-3 py-2"><button class="btn btn-primary d-block w-100 btn-login" type="submit">Login</button></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
138
html/webpage/messagebank.html
Normal file
138
html/webpage/messagebank.html
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">Message Bank</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row pad-row-search">
|
||||||
|
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control pad-search" type="text" id="findmessage" placeholder="Search keyword" name="findsoundbank"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class05">No</th>
|
||||||
|
<th class="class15">Description</th>
|
||||||
|
<th class="class10">Language</th>
|
||||||
|
<th class="class10">ANN ID</th>
|
||||||
|
<th class="class10">Type</th>
|
||||||
|
<th class="class35">Message Details</th>
|
||||||
|
<th class="class15">Message Tags</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="messagebanktablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="messagebankmodal">
|
||||||
|
<div class="modal-dialog modal-xl" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-header-modal">
|
||||||
|
<h4 class="modal-title">Add / Edit Message</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
|
||||||
|
<p class="text-add">Index</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><input class="w-25 input-add form-control" type="text" id="messageindex" placeholder="Index" readonly=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
|
||||||
|
<p class="text-add">Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><input type="text" id="messagedescription" class="input-add form-control" placeholder="Description"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
|
||||||
|
<p class="text-add">Language</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><select id="messagelanguage" class="input-add form-control">
|
||||||
|
<option value="INDONESIA">Indonesia</option>
|
||||||
|
<option value="LOCAL">Local</option>
|
||||||
|
<option value="ENGLISH">English</option>
|
||||||
|
<option value="JAPANESE">Japanese</option>
|
||||||
|
<option value="CHINESE">Chinese</option>
|
||||||
|
<option value="ARABIC">Arabic</option>
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
|
||||||
|
<p class="text-add">ANN ID</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><input type="number" id="messageannid" class="input-add form-control" min="1" max="100" value="1" step="1" placeholder="Announcement ID"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-3 col-xl-3">
|
||||||
|
<p class="text-add">Voice Type</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-9"><select id="messagevoicetype" class="input-add form-control">
|
||||||
|
<option value="VOICE_1">Voice 1</option>
|
||||||
|
<option value="VOICE_2">Voice 2</option>
|
||||||
|
<option value="VOICE_3">Voice 3</option>
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col bg-light">
|
||||||
|
<ul class="list-unstyled w-100 h-100" id="messageavailablevariables"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2">
|
||||||
|
<div class="row pad-row-btn"><button class="btn btn-round-basic color-remove" data-bs-toggle="tooltip" data-bss-tooltip="" id="btnclearlist" type="button" title="Clear List"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
|
||||||
|
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||||
|
<path d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"></path>
|
||||||
|
</svg></button></div>
|
||||||
|
<div class="row pad-row-btn"><button class="btn btn-round-basic color-edit" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnremovefromlist" type="button" title="Remove"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
|
||||||
|
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||||
|
<path d="M48 256a208 208 0 1 1 416 0A208 208 0 1 1 48 256zm464 0A256 256 0 1 0 0 256a256 256 0 1 0 512 0zM217.4 376.9c4.2 4.5 10.1 7.1 16.3 7.1c12.3 0 22.3-10 22.3-22.3V304h96c17.7 0 32-14.3 32-32V240c0-17.7-14.3-32-32-32H256V150.3c0-12.3-10-22.3-22.3-22.3c-6.2 0-12.1 2.6-16.3 7.1L117.5 242.2c-3.5 3.8-5.5 8.7-5.5 13.8s2 10.1 5.5 13.8l99.9 107.1z"></path>
|
||||||
|
</svg></button></div>
|
||||||
|
<div class="row pad-row-btn"><button class="btn btn-round-basic color-import" data-bs-toggle="tooltip" data-bss-tooltip="" data-bs-placement="right" id="btnaddtolist" type="button" title="Add"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="font-size: 32;">
|
||||||
|
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc. -->
|
||||||
|
<path d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM294.6 135.1c-4.2-4.5-10.1-7.1-16.3-7.1C266 128 256 138 256 150.3V208H160c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32h96v57.7c0 12.3 10 22.3 22.3 22.3c6.2 0 12.1-2.6 16.3-7.1l99.9-107.1c3.5-3.8 5.5-8.7 5.5-13.8s-2-10.1-5.5-13.8L294.6 135.1z"></path>
|
||||||
|
</svg></button></div>
|
||||||
|
</div>
|
||||||
|
<div class="col bg-light">
|
||||||
|
<ul class="list-unstyled w-100 h-100" id="messageselectedvariables"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/messagebank.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
1338
html/webpage/overview.html
Normal file
1338
html/webpage/overview.html
Normal file
File diff suppressed because it is too large
Load Diff
86
html/webpage/setting.html
Normal file
86
html/webpage/setting.html
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">Setting</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card card-setting">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title"><strong>Upload Soundbank</strong></h4>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Path</label></div>
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input class="w-100 form-control" type="text" id="setting_path"></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="save_directory" type="button">Save Directory</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Category</label></div>
|
||||||
|
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_category" class="input-add form-select"></select></div>
|
||||||
|
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Language</label></div>
|
||||||
|
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_language" class="input-add form-select"></select></div>
|
||||||
|
<div class="col-6 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">Voice</label></div>
|
||||||
|
<div class="col-6 col-sm-10 col-md-2 col-lg-2 col-xl-2"><select id="setting_voice" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
|
||||||
|
<div class="bg-white w-100" id="drop-area" multiple=""><input type="file" id="file-input"><label class="form-label d">Drop files here or click to select</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
|
||||||
|
<div class="row"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row py-5">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card card-setting">
|
||||||
|
<div class="card-body pad-accordion">
|
||||||
|
<h4 class="card-title"><strong>FIS CODE</strong></h4>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GOP</label></div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GOP" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GBP</label></div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GBP" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">GFC</label></div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_GFC" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label">FLD</label></div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-10 col-lg-10 col-xl-10"><select id="input_FLD" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2"><label class="col-form-label"></label></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-6 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/dragdrop.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
117
html/webpage/soundbank.html
Normal file
117
html/webpage/soundbank.html
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">Sound Bank</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findsoundbank" placeholder="Search keyword" name="findsoundbank"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class05">No</th>
|
||||||
|
<th class="class20">Description</th>
|
||||||
|
<th class="class10">TAG</th>
|
||||||
|
<th class="class15">Category</th>
|
||||||
|
<th class="class15">Language</th>
|
||||||
|
<th class="class10">Type</th>
|
||||||
|
<th class="class25">Filename</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="soundbanktablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade border-0" role="dialog" tabindex="-1" id="soundbankmodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header show bg-header-modal">
|
||||||
|
<h4 class="modal-title align-content-center">Add / Edit Soundbank</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Index</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="modalindex" readonly=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modaldescription" class="form-control input-add"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">TAG</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modaltag" class="form-control input-add"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Category</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalcategory" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Language</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modallanguage" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Voice Type</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalvoicetype" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Path</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="modalpath" class="input-add form-select cw-100" name="modalpath" data-control="select2"></select></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundbankclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundbanksave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/soundbank.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
36
html/webpage/streamerstatus.html
Normal file
36
html/webpage/streamerstatus.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="card" id="streamercard">
|
||||||
|
<div class="card-body card-channel">
|
||||||
|
<h4 class="card-title" id="streamertitle">Channel 01</h4>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 col-xxl-6">
|
||||||
|
<h6 class="text-muted mb-2" id="streamerip">IP : 192.168.10.10</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-6 col-xxl-6">
|
||||||
|
<h6 class="text-muted mb-2" id="streamerbuffer">Free : 64KB</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="card-text" id="streamerstatus">Status : Idle</p>
|
||||||
|
<div class="progress" id="streamervu">
|
||||||
|
<div class="progress-bar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%;">50%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
181
html/webpage/timer.html
Normal file
181
html/webpage/timer.html
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">Schedule Bank</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row pad-row-search">
|
||||||
|
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="findschedule" placeholder="Search keyword" name="findsoundbank"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class05">No</th>
|
||||||
|
<th class="class15">Description</th>
|
||||||
|
<th class="class15">Day</th>
|
||||||
|
<th class="class10">Time</th>
|
||||||
|
<th class="class15">Sound Path</th>
|
||||||
|
<th class="class10">Repeat</th>
|
||||||
|
<th class="class05">Enable</th>
|
||||||
|
<th class="class15">Broadcast Zones</th>
|
||||||
|
<th class="class10">Language</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="schedulebanktablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="schedulemodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-header-modal">
|
||||||
|
<h4 class="modal-title">Add / Edit Schedule</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Index</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="scheduleid" readonly=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Description</p>
|
||||||
|
</div>
|
||||||
|
<div class="col"><input type="text" id="scheduledescription" class="input-add form-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Day</p>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row pad-day">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="radio" id="scheduleeveryday" name="dayselection" value="Everyday"><label class="form-check-label" for="formCheck-1">Everyday</label></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row pad-day">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="radio" id="scheduleweekly"><label class="form-check-label" for="formCheck-1">Weekly</label></div><select class="w-100 input-add form-select" id="weeklyselect">
|
||||||
|
<optgroup label="This is a group">
|
||||||
|
<option value="12" selected="">This is item 1</option>
|
||||||
|
<option value="13">This is item 2</option>
|
||||||
|
<option value="14">This is item 3</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-7 col-sm-7 col-md-7 col-lg-6 col-xl-6 pad-day">
|
||||||
|
<div class="form-check"><input class="form-check-input" type="radio" id="schedulespecialdate" name="dayselection"><label class="form-check-label" for="formCheck-9">Special Date</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-5 col-md-5 col-lg-6 col-xl-6"><input id="scheduledate" class="form-control" type="date"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Time</p>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row w-100 h-100">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input type="number" id="schedulehour" class="input-add form-control class100" value="0" min="0" max="23" step="1"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
|
||||||
|
<p class="pad-time">(H)</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><input class="w-100 input-add form-control" type="number" id="scheduleminute" value="0" min="0" max="59" step="1"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
|
||||||
|
<p class="pad-time">(M)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Message</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><select id="schedulemessage" class="input-add form-select"></select></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Language</p>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row pad-day">
|
||||||
|
<div class="col"><select class="w-100 input-add form-select" id="languageselect">
|
||||||
|
<optgroup label="This is a group">
|
||||||
|
<option value="12" selected="">This is item 1</option>
|
||||||
|
<option value="13">This is item 2</option>
|
||||||
|
<option value="14">This is item 3</option>
|
||||||
|
</optgroup>
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Repeat</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 form-select input-add" type="number" id="schedulerepeat" min="0" max="5" step="1" value="0"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Enable</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="checkbox" id="scheduleenable" class="form-check-input form-check text-add" checked=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Broadcast Zones</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-9 col-xl-8 border"><select class="w-100 input-add form-select" id="schedulezones">
|
||||||
|
<optgroup label="This is a group">
|
||||||
|
<option value="12" selected="">This is item 1</option>
|
||||||
|
<option value="13">This is item 2</option>
|
||||||
|
<option value="14">This is item 3</option>
|
||||||
|
</optgroup>
|
||||||
|
</select></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="scheduleclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic class25 color-add" id="schedulesave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/schedulebank.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
205
html/webpage/usermanagement.html
Normal file
205
html/webpage/usermanagement.html
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html data-bs-theme="light" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||||
|
<title>AAS_NewGen_17OKT25</title>
|
||||||
|
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/bss-overrides.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/Login-Form-Basic-icons.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col w-100 h-100 pad-header">
|
||||||
|
<h2 style="text-align: center;">User Management</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-7 col-lg-7 col-xl-7"></div>
|
||||||
|
<div class="col-2 col-sm-2 col-md-2 col-lg-2 col-xl-2 search">
|
||||||
|
<p class="text-add">Search</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-10 col-sm-10 col-md-3 col-lg-3 col-xl-3"><input class="w-100 form-control" type="text" id="finduser" placeholder="Search keyword" name="findsoundbank"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<p id="tablesize" class="text-add" style="text-align: center;">Table Length : N/A</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnClear" type="button">Clear</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-add" id="btnAdd" type="button">Add</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-remove" id="btnRemove" type="button">Remove</button></div>
|
||||||
|
<div class="col-3 col-sm-3 col-md-3 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-edit" id="btnEdit" type="button">Edit</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnExport" type="button">Export</button></div>
|
||||||
|
<div class="col-6 col-sm-6 col-md-6 col-lg-2 col-xl-2"><button class="btn w-100 pad-button btn-round-basic color-import" id="btnImport" type="button">Import</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="class05">No</th>
|
||||||
|
<th class="class10">Username</th>
|
||||||
|
<th class="class15">Location</th>
|
||||||
|
<th class="class15">Airline</th>
|
||||||
|
<th class="class15">City</th>
|
||||||
|
<th class="class20">Messagebank</th>
|
||||||
|
<th class="class20">Broadcast Zones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="usertablebody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade border-0" role="dialog" tabindex="-1" id="addmodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header text-start show bg-header-modal">
|
||||||
|
<h4 class="modal-title text-center align-content-center">Add / Edit User</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Index</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input class="w-25 input-add form-control" type="text" id="modalindex" readonly=""></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Username</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalusername" class="form-control input-add"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Password</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="password" id="modalpassword" class="form-control input-add"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Verify Password</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="password" id="modalverifypassword" class="input-add form-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Location</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modallocation" class="input-add form-control"></div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Airline Tags</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8"><input type="text" id="modalairlinetags" class="form-control input-add"></div>
|
||||||
|
<div class="col-4"><button class="btn w-100 btn-select btn-round-basic color-import" id="btnShowSoundbankModal" type="button">Select</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">City Tags</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8"><input type="text" id="modalcitytags" class="form-control input-add"></div>
|
||||||
|
<div class="col-4"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Message Bank</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8"><input type="text" id="modalmessagebank" class="form-control input-add"></div>
|
||||||
|
<div class="col-4"><button class="btn w-100 btn-round-basic color-import" id="btnShowMessagebankModal" type="button">Select</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
|
||||||
|
<p class="text-add">Broadcast Zones</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-8 col-sm-8 col-md-8 col-lg-8 col-xl-8"><input type="text" id="modalbroadcastzones" class="form-control input-add" name="modalpath"></div>
|
||||||
|
<div class="col-4 col-sm-4 col-md-4 col-lg-4 col-xl-4"><button class="btn w-100 btn-round-basic color-import" id="btnShowBroaadcastZoneModal" type="button">Select</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="usermanagementclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="usermanagementsave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="soundbankmodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header form-control input-add">
|
||||||
|
<h4 class="modal-title">Sound Bank Selection</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<div class="accordion" role="tablist" id="accordion-1">
|
||||||
|
<div class="accordion-item pad-accordion">
|
||||||
|
<h2 class="accordion-header" role="tab"><button class="accordion-button bg-heading1" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-1" aria-expanded="true" aria-controls="accordion-1 .item-1">Airline</button></h2>
|
||||||
|
<div class="accordion-collapse collapse show item-1" role="tabpanel" data-bs-parent="#accordion-1">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<ul id="airlinelist"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item pad-accordion">
|
||||||
|
<h2 class="accordion-header" role="tab"><button class="accordion-button collapsed bg-heading2" type="button" data-bs-toggle="collapse" data-bs-target="#accordion-1 .item-2" aria-expanded="false" aria-controls="accordion-1 .item-2">City</button></h2>
|
||||||
|
<div class="accordion-collapse collapse item-2" role="tabpanel" data-bs-parent="#accordion-1">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<ul id="citylist"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="soundbankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="soundbankselectionsave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="broadcastzonemodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header form-control input-add">
|
||||||
|
<h4 class="modal-title">Broadcast Zones Selection</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<ul id="broadcastzonelist"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="broadcastzoneselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="broadcastzoneselectionsave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" role="dialog" tabindex="-1" id="messagebankmodal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header form-control input-add">
|
||||||
|
<h4 class="modal-title">Message Bank Selection</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body bg-modal-body">
|
||||||
|
<ul id="messagebanklist"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer"><button class="btn btn-round-basic color-edit class25" id="messagebankselectionclose" type="button" data-bs-dismiss="modal">Close</button><button class="btn btn-round-basic color-add class25" id="messagebankselectionsave" type="button">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/bs-init.js"></script>
|
||||||
|
<script src="assets/js/usermanagement.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
BIN
libs/linux-aarch64/libbassenc.so
Normal file
BIN
libs/linux-aarch64/libbassenc.so
Normal file
Binary file not shown.
BIN
libs/linux-armhf/libbassenc.so
Normal file
BIN
libs/linux-armhf/libbassenc.so
Normal file
Binary file not shown.
BIN
libs/linux-x86-64/libbassenc.so
Normal file
BIN
libs/linux-x86-64/libbassenc.so
Normal file
Binary file not shown.
BIN
libs/linux-x86/libbassenc.so
Normal file
BIN
libs/linux-x86/libbassenc.so
Normal file
Binary file not shown.
BIN
libs/win32-arm64/bassenc.dll
Normal file
BIN
libs/win32-arm64/bassenc.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86-64/bassenc.dll
Normal file
BIN
libs/win32-x86-64/bassenc.dll
Normal file
Binary file not shown.
BIN
libs/win32-x86/bassenc.dll
Normal file
BIN
libs/win32-x86/bassenc.dll
Normal file
Binary file not shown.
238
src/Main.kt
238
src/Main.kt
@@ -1,11 +1,243 @@
|
|||||||
import audio.AudioPlayer
|
import audio.AudioPlayer
|
||||||
|
import audio.ContentCache
|
||||||
|
import audio.TCPReceiver
|
||||||
|
import audio.UDPReceiver
|
||||||
|
import barix.BarixConnection
|
||||||
|
import barix.TCP_Barix_Command_Server
|
||||||
import codes.Somecodes
|
import codes.Somecodes
|
||||||
|
import com.sun.jna.Platform
|
||||||
|
import commandServer.TCP_Android_Command_Server
|
||||||
|
import content.Category
|
||||||
|
import content.Language
|
||||||
|
import content.VoiceType
|
||||||
|
import database.Log
|
||||||
|
import database.MariaDB
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.tinylog.Logger
|
import org.tinylog.Logger
|
||||||
|
import org.tinylog.provider.ProviderRegistry
|
||||||
|
import oshi.util.GlobalConfig
|
||||||
|
import web.WebApp
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import kotlin.concurrent.fixedRateTimer
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
|
||||||
|
lateinit var db: MariaDB
|
||||||
|
lateinit var audioPlayer: AudioPlayer
|
||||||
|
val StreamerOutputs: MutableMap<String, BarixConnection> = HashMap()
|
||||||
|
lateinit var udpreceiver: UDPReceiver
|
||||||
|
lateinit var tcpreceiver: TCPReceiver
|
||||||
|
const val version = "0.0.8 (16/10/2025)"
|
||||||
|
// AAS 64 channels
|
||||||
|
const val max_channel = 64
|
||||||
|
|
||||||
|
// dipakai untuk pilih voice type, bisa diganti via web nanti
|
||||||
|
var selected_voice = VoiceType.VOICE_1.name
|
||||||
|
|
||||||
|
// dipakai untuk ambil messagebank berdasarkan id
|
||||||
|
val urutan_bahasa = listOf(
|
||||||
|
Language.INDONESIA.name,
|
||||||
|
Language.LOCAL.name,
|
||||||
|
Language.ENGLISH.name,
|
||||||
|
Language.CHINESE.name,
|
||||||
|
Language.JAPANESE.name,
|
||||||
|
Language.ARABIC.name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
val audioPlayer = AudioPlayer()
|
val contentCache = ContentCache()
|
||||||
|
/**
|
||||||
|
* Create necessary folders if not exist
|
||||||
|
*/
|
||||||
|
fun folder_preparation(){
|
||||||
|
// sementara diset begini, nanti pake config file
|
||||||
|
Somecodes.Soundbank_directory = Paths.get("c:\\soundbank")
|
||||||
|
Files.createDirectories(Somecodes.SoundbankResult_directory)
|
||||||
|
Files.createDirectories(Somecodes.PagingResult_directory)
|
||||||
|
Files.createDirectories(Somecodes.Soundbank_directory)
|
||||||
|
Language.entries.forEach { language ->
|
||||||
|
VoiceType.entries.forEach { voice ->
|
||||||
|
Category.entries.forEach { category ->
|
||||||
|
Files.createDirectories(Somecodes.SoundbankDirectory(language, voice, category) )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract necessary wav files from classpath to soundbank directory
|
||||||
|
* and Load them
|
||||||
|
*/
|
||||||
|
fun files_preparation(){
|
||||||
|
val list = listOf("chimeup.wav", "chimedown.wav", "silence1s.wav", "silencehalf.wav")
|
||||||
|
list.forEach {
|
||||||
|
Somecodes.ExtractFilesFromClassPath("/$it", Somecodes.Soundbank_directory)
|
||||||
|
val pp = Somecodes.Soundbank_directory.resolve(it)
|
||||||
|
if (Files.isRegularFile(pp)){
|
||||||
|
val afi = audioPlayer.LoadAudioFile(pp.absolutePathString())
|
||||||
|
if (afi.isValid()){
|
||||||
|
Logger.info { "Common audio $it loaded from ${pp.toAbsolutePath()}" }
|
||||||
|
val key = it.substring(0, it.length - 4) // buang .wav
|
||||||
|
contentCache.addAudioFile(key, afi)
|
||||||
|
} else {
|
||||||
|
Logger.error { "Failed to load common audio $it from ${pp.toAbsolutePath()}" }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.error { "Common audio $it not found at ${pp.toAbsolutePath()}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Application start here
|
||||||
fun main() {
|
fun main() {
|
||||||
Logger.info("Application started" as Any)
|
if (Platform.isWindows()) {
|
||||||
|
// supaya OSHI bisa mendapatkan CPU usage di Windows seperti di Task Manager
|
||||||
|
GlobalConfig.set(GlobalConfig.OSHI_OS_WINDOWS_CPU_UTILITY, true)
|
||||||
|
}
|
||||||
|
Logger.info { "Starting AAS New Generation version $version" }
|
||||||
|
|
||||||
|
folder_preparation()
|
||||||
|
|
||||||
|
audioPlayer = AudioPlayer(44100) // 44100 Hz sampling rate
|
||||||
audioPlayer.InitAudio(1)
|
audioPlayer.InitAudio(1)
|
||||||
|
|
||||||
}
|
files_preparation()
|
||||||
|
|
||||||
|
db = MariaDB()
|
||||||
|
|
||||||
|
val subcode01 = MainExtension01()
|
||||||
|
|
||||||
|
// Coroutine untuk cek Paging Queue dan AAS Queue setiap detik
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
while (isActive) {
|
||||||
|
delay(1000)
|
||||||
|
// prioritas 1 , habisin queue paging
|
||||||
|
subcode01.Read_Queue_Paging()
|
||||||
|
// prioritas 2, habisin queue shalat
|
||||||
|
subcode01.Read_Queue_Shalat()
|
||||||
|
// prioritas 3, habisin queue timer
|
||||||
|
subcode01.Read_Queue_Timer()
|
||||||
|
// prioritas 4, habisin queue soundbank
|
||||||
|
subcode01.Read_Queue_Soundbank()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Coroutine untuk cek Schedulebank tiap menit saat detik 00
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
while (isActive) {
|
||||||
|
delay(1000)
|
||||||
|
subcode01.Read_Schedule_Table()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val web = WebApp(
|
||||||
|
3030,
|
||||||
|
listOf(
|
||||||
|
Pair("admin", "password"),
|
||||||
|
Pair("user", "password")
|
||||||
|
))
|
||||||
|
web.Start()
|
||||||
|
|
||||||
|
udpreceiver = UDPReceiver()
|
||||||
|
if (udpreceiver.Start()) {
|
||||||
|
Logger.info { "UDP Receiver started on port 5002" }
|
||||||
|
} else {
|
||||||
|
Logger.error { "Failed to start UDP Receiver on port 5002" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpreceiver = TCPReceiver()
|
||||||
|
if (tcpreceiver.Start()) {
|
||||||
|
Logger.info { "TCP Receiver started on port 5002" }
|
||||||
|
} else {
|
||||||
|
Logger.error { "Failed to start TCP Receiver on port 5002" }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
val androidserver = TCP_Android_Command_Server()
|
||||||
|
androidserver.StartTcpServer(5003){
|
||||||
|
Logger.info { it }
|
||||||
|
db.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 }
|
||||||
|
if (_streamer == null) {
|
||||||
|
// belum create BarixConnection untuk ipaddress ini
|
||||||
|
//Logger.info { "New Streamer Output connection from ${cmd.ipaddress}" }
|
||||||
|
if (_sc != null) {
|
||||||
|
|
||||||
|
val _bc = BarixConnection(_sc.index, _sc.channel, cmd.ipaddress)
|
||||||
|
// cmd.vu 0 - 32767, kita convert ke 0 - 100
|
||||||
|
_bc.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
|
||||||
|
_bc.bufferRemain = cmd.buffremain
|
||||||
|
_bc.statusData = cmd.statusdata
|
||||||
|
_bc.commandsocket = _tcp
|
||||||
|
|
||||||
|
StreamerOutputs[cmd.ipaddress] = _bc
|
||||||
|
Logger.info { "Created new Streamer Output for channel ${_sc.channel} with IP ${cmd.ipaddress}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// sudah ada, update data
|
||||||
|
if (_sc != null && _sc.channel != _streamer.channel) {
|
||||||
|
_streamer.channel = _sc.channel
|
||||||
|
}
|
||||||
|
// cmd.vu 0 - 32767, kita convert ke 0 - 100
|
||||||
|
_streamer.vu = ((1.0 * cmd.vu / 32767.0)* 100.0).toInt()
|
||||||
|
_streamer.bufferRemain = cmd.buffremain
|
||||||
|
_streamer.statusData = cmd.statusdata
|
||||||
|
|
||||||
|
// cek apakah koneksi TCP nya ganti
|
||||||
|
if (_streamer.commandsocket == null) {
|
||||||
|
_streamer.commandsocket = _tcp
|
||||||
|
} else {
|
||||||
|
if (_streamer.commandsocket != _tcp) {
|
||||||
|
// ganti koneksi
|
||||||
|
try {
|
||||||
|
_streamer.commandsocket?.close()
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Logger.error(ex) { "Error closing previous TCP command socket for ${cmd.ipaddress}" }
|
||||||
|
}
|
||||||
|
_streamer.commandsocket = _tcp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
val onlinechecker = fixedRateTimer(name = "onlinecheck", initialDelay = 1000, period = 1000) {
|
||||||
|
// cek setiap 1 detik, decrement online counter semua BarixConnection
|
||||||
|
StreamerOutputs.values.forEach {
|
||||||
|
it.decrementOnlineCounter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Add_Log("AAS"," Application started")
|
||||||
|
|
||||||
|
// shutdown hook
|
||||||
|
Runtime.getRuntime().addShutdownHook(Thread {
|
||||||
|
db.Add_Log("AAS"," Application stopping")
|
||||||
|
Logger.info { "Shutdown hook called, stopping services..." }
|
||||||
|
barixserver.StopTcpCommand()
|
||||||
|
androidserver.StopTcpCommand()
|
||||||
|
onlinechecker.cancel()
|
||||||
|
web.Stop()
|
||||||
|
udpreceiver.Stop()
|
||||||
|
tcpreceiver.Stop()
|
||||||
|
audioPlayer.Close()
|
||||||
|
db.close()
|
||||||
|
Logger.info { "All services stopped, exiting application." }
|
||||||
|
ProviderRegistry.getLoggingProvider().shutdown()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
1120
src/MainExtension01.kt
Normal file
1120
src/MainExtension01.kt
Normal file
File diff suppressed because it is too large
Load Diff
21
src/audio/AudioFileInfo.kt
Normal file
21
src/audio/AudioFileInfo.kt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used for storing audio file information.
|
||||||
|
*/
|
||||||
|
class AudioFileInfo {
|
||||||
|
var fileName : String = ""
|
||||||
|
var fileSize : Long = 0L
|
||||||
|
var duration : Double = 0.0
|
||||||
|
var bytes : ByteArray = ByteArray(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the audio file information is valid.
|
||||||
|
* A valid audio file must have a non-blank file name, a positive file size,
|
||||||
|
* a positive duration, and non-empty byte array.
|
||||||
|
* @return True if the audio file information is valid, false otherwise.
|
||||||
|
*/
|
||||||
|
fun isValid() : Boolean {
|
||||||
|
return fileName.isNotBlank() && fileSize > 0 && duration > 0.0 && bytes.isNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,33 @@
|
|||||||
package audio
|
package audio
|
||||||
|
|
||||||
import audio.Bass.BASS_ERROR_ALREADY
|
import audio.Bass.BASS_DEVICE_ENABLED
|
||||||
|
import audio.Bass.BASS_DEVICE_INIT
|
||||||
|
import audio.Bass.BASS_POS_BYTE
|
||||||
|
import audio.Bass.BASS_STREAM_DECODE
|
||||||
|
import audio.Bass.BASS_SAMPLE_MONO
|
||||||
|
import audio.BassEnc.BASS_ENCODE_PCM
|
||||||
|
import codes.Result_Boolean_String
|
||||||
|
import codes.Somecodes.Companion.ValidFile
|
||||||
|
import codes.Somecodes.Companion.ValidString
|
||||||
|
import com.sun.jna.Memory
|
||||||
|
import com.sun.jna.Pointer
|
||||||
|
import contentCache
|
||||||
|
|
||||||
import org.tinylog.Logger
|
import org.tinylog.Logger
|
||||||
|
|
||||||
class AudioPlayer {
|
@Suppress("unused")
|
||||||
var bass : Bass = Bass.Instance
|
class AudioPlayer (var samplingrate: Int) {
|
||||||
var initedDevice : Int = -1
|
val bass: Bass = Bass.Instance
|
||||||
|
val bassenc : BassEnc = BassEnc.Instance
|
||||||
|
var initedDevice = -1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Logger.info("Audio version ${Integer.toHexString(bass.BASS_GetVersion())}" as Any)
|
if (samplingrate<1) samplingrate = 44100 // Default sampling rate
|
||||||
|
Logger.info {"Bass version ${Integer.toHexString(bass.BASS_GetVersion())}"}
|
||||||
|
Logger.info { "BassEnc version ${Integer.toHexString(bassenc.BASS_Encode_GetVersion())}" }
|
||||||
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
|
InitAudio(0) // Audio 0 is No Sound, use for reading and writing wav silently
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +39,22 @@ class AudioPlayer {
|
|||||||
*/
|
*/
|
||||||
fun InitAudio(id : Int) : Boolean {
|
fun InitAudio(id : Int) : Boolean {
|
||||||
|
|
||||||
|
val bdi = Bass.BASS_DEVICEINFO()
|
||||||
|
if (bass.BASS_GetDeviceInfo(id, bdi)){
|
||||||
|
Logger.info { "Audio ID=$id Name=${bdi.name} Driver=${bdi.driver}" }
|
||||||
|
if (bdi.flags and BASS_DEVICE_ENABLED == 0) {
|
||||||
|
Logger.error { "Audio ID=$id is not enabled, cannot initialize" }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (bdi.flags and BASS_DEVICE_INIT > 0){
|
||||||
|
Logger.info { "Audio ID=$id is already initialized" }
|
||||||
|
if (id > 0) {
|
||||||
|
initedDevice = id
|
||||||
|
Logger.info { "Real Audio Device reused ID=$initedDevice" }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (bass.BASS_Init(id, 48000, 0)){
|
if (bass.BASS_Init(id, 48000, 0)){
|
||||||
Logger.info { "Audio ID=$id inited succesfully" }
|
Logger.info { "Audio ID=$id inited succesfully" }
|
||||||
@@ -28,26 +64,15 @@ class AudioPlayer {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
val err = bass.BASS_ErrorGetCode()
|
Logger.error { "Audio ID=$id initialization failed: ${bass.BASS_ErrorGetCode()}" }
|
||||||
if (err == BASS_ERROR_ALREADY) {
|
return false
|
||||||
Logger.info {"Audio ID=$id already initialized, reusing existing instance"}
|
|
||||||
if (id > 0) {
|
|
||||||
initedDevice = id
|
|
||||||
Logger.info { "Real Audio Device reused ID=$initedDevice" }
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
Logger.error { "Audio ID=$id initialization failed: ${bass.BASS_ErrorGetCode()}" }
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uninitializes the audio system if it was previously initialized.
|
* Uninitializes the audio system if it was previously initialized.
|
||||||
*/
|
*/
|
||||||
fun UnInitAudio(){
|
fun Close(){
|
||||||
if (initedDevice != -1) {
|
if (initedDevice != -1) {
|
||||||
bass.BASS_SetDevice(initedDevice)
|
bass.BASS_SetDevice(initedDevice)
|
||||||
if (bass.BASS_Free()) {
|
if (bass.BASS_Free()) {
|
||||||
@@ -58,4 +83,227 @@ class AudioPlayer {
|
|||||||
initedDevice = -1
|
initedDevice = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an audio file and retrieves its information.
|
||||||
|
* Check for isValid() method in AudioFileInfo class to verify the loaded file.
|
||||||
|
* @param fileName The name of the audio file to load.
|
||||||
|
* @return An AudioFileInfo object containing the file information.
|
||||||
|
*/
|
||||||
|
fun LoadAudioFile(fileName: String) : AudioFileInfo {
|
||||||
|
val result = AudioFileInfo()
|
||||||
|
if (ValidFile(fileName)){
|
||||||
|
result.fileName = fileName
|
||||||
|
bass.BASS_SetDevice(0) // Set to No Sound device for reading
|
||||||
|
val handle = bass.BASS_StreamCreateFile(false, fileName, 0L, 0L, BASS_STREAM_DECODE or BASS_SAMPLE_MONO)
|
||||||
|
if (handle!=0){
|
||||||
|
// successfully opened the file
|
||||||
|
// read file size
|
||||||
|
val size = bass.BASS_ChannelGetLength(handle, BASS_POS_BYTE)
|
||||||
|
if (size > 0) {
|
||||||
|
result.fileSize = size
|
||||||
|
}
|
||||||
|
// read file duration
|
||||||
|
val duration = bass.BASS_ChannelBytes2Seconds(handle, size)
|
||||||
|
if (duration > 0) {
|
||||||
|
result.duration = duration
|
||||||
|
}
|
||||||
|
// read file bytes
|
||||||
|
val mem = Memory(size)
|
||||||
|
val bytesRead = bass.BASS_ChannelGetData(handle, mem, size.toInt())
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
result.bytes = mem.getByteArray(0, bytesRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the handle
|
||||||
|
bass.BASS_StreamFree(handle)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the audio data from a byte array to a WAV file.
|
||||||
|
* @param data The byte array containing the audio data.
|
||||||
|
* @param target The target file name for the WAV file.
|
||||||
|
* @param withChime If true, adds a chime sound at the beginning and end of the audio.
|
||||||
|
* @return A Result_Boolean_String indicating success or failure and a message.
|
||||||
|
*/
|
||||||
|
fun WavWriter(data: ByteArray, target: String, withChime: Boolean = true) : Result_Boolean_String {
|
||||||
|
bass.BASS_SetDevice(0) // Set to No Sound device for writing
|
||||||
|
val streamhandle = bass.BASS_StreamCreate(samplingrate, 1, BASS_STREAM_DECODE, Pointer(-1), null)
|
||||||
|
|
||||||
|
if (streamhandle!=0){
|
||||||
|
val encodehandle = bassenc.BASS_Encode_Start(streamhandle, target, BASS_ENCODE_PCM, null, null)
|
||||||
|
if (encodehandle!=0){
|
||||||
|
|
||||||
|
fun pushData(data: ByteArray): Boolean {
|
||||||
|
val mem = Memory(data.size.toLong())
|
||||||
|
mem.write(0, data, 0, data.size)
|
||||||
|
val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size)
|
||||||
|
if (pushresult==-1){
|
||||||
|
Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" }
|
||||||
|
}
|
||||||
|
return pushresult != -1
|
||||||
|
}
|
||||||
|
|
||||||
|
var all_success = true
|
||||||
|
|
||||||
|
if (withChime){
|
||||||
|
val chup = contentCache.getAudioFile("chimeup")
|
||||||
|
if (chup!=null && chup.isValid()){
|
||||||
|
if (!pushData(chup.bytes)){
|
||||||
|
all_success = false
|
||||||
|
Logger.error { "Failed to push Chime Up" }
|
||||||
|
}
|
||||||
|
} else Logger.error { "withChime=true, but Chime Up not available" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pushData(data)){
|
||||||
|
all_success = false
|
||||||
|
Logger.error { "Failed to push Data ByteArray" }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (withChime){
|
||||||
|
val chdn = contentCache.getAudioFile("chimedown")
|
||||||
|
if (chdn!=null && chdn.isValid()){
|
||||||
|
if (!pushData(chdn.bytes)){
|
||||||
|
all_success = false
|
||||||
|
Logger.error { "Failed to push Chime Down" }
|
||||||
|
}
|
||||||
|
} else Logger.error { "withChime=true, but Chime Down not available" }
|
||||||
|
}
|
||||||
|
|
||||||
|
val readsize: Long = 1024 * 1024 // read 1 MB at a time
|
||||||
|
var totalread: Long = 0
|
||||||
|
do{
|
||||||
|
val p = Memory(readsize)
|
||||||
|
val read = bass.BASS_ChannelGetData(streamhandle, p, 4096)
|
||||||
|
if (read > 0) {
|
||||||
|
totalread += read
|
||||||
|
}
|
||||||
|
} while (read > 0)
|
||||||
|
bassenc.BASS_Encode_Stop(encodehandle)
|
||||||
|
bass.BASS_StreamFree(streamhandle)
|
||||||
|
|
||||||
|
return if (all_success){
|
||||||
|
Result_Boolean_String(true, "WAV file written successfully: $target")
|
||||||
|
} else {
|
||||||
|
Result_Boolean_String(false, "Failed to write some data to WAV file: $target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bass.BASS_StreamFree(streamhandle)
|
||||||
|
return Result_Boolean_String(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return Result_Boolean_String(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the audio data from the sources to a WAV file.
|
||||||
|
* @param sources List of AudioFileInfo objects containing the audio data to write.
|
||||||
|
* @param target The target file name for the WAV file.
|
||||||
|
* @param withChime If true, adds a chime sound at the beginning and end of the audio.
|
||||||
|
* @return A Result_Boolean_String indicating success or failure and a message.
|
||||||
|
*/
|
||||||
|
fun WavWriter(sources: List<AudioFileInfo>, target: String, withChime: Boolean = true) : Result_Boolean_String {
|
||||||
|
if (sources.isEmpty()) {
|
||||||
|
return Result_Boolean_String(false,"Invalid Source")
|
||||||
|
}
|
||||||
|
if (!ValidString(target)) {
|
||||||
|
return Result_Boolean_String(false, " Invalid target file name")
|
||||||
|
}
|
||||||
|
|
||||||
|
bass.BASS_SetDevice(0) // Set to No Sound device for writing
|
||||||
|
val streamhandle = bass.BASS_StreamCreate(samplingrate, 1, BASS_STREAM_DECODE, Pointer(-1), null)
|
||||||
|
if (streamhandle==0){
|
||||||
|
return Result_Boolean_String(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}")
|
||||||
|
}
|
||||||
|
val encodehandle = bassenc.BASS_Encode_Start(streamhandle, target, BASS_ENCODE_PCM, null, null)
|
||||||
|
if (encodehandle==0){
|
||||||
|
bass.BASS_StreamFree(streamhandle)
|
||||||
|
return Result_Boolean_String(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pushData(data: ByteArray): Boolean {
|
||||||
|
val mem = Memory(data.size.toLong())
|
||||||
|
mem.write(0, data, 0, data.size)
|
||||||
|
val pushresult = bass.BASS_StreamPutData(streamhandle, mem, data.size)
|
||||||
|
if (pushresult==-1){
|
||||||
|
Logger.error { "BASS_StreamPutData failed: ${bass.BASS_ErrorGetCode()}" }
|
||||||
|
}
|
||||||
|
return pushresult != -1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var allsuccess = true
|
||||||
|
if (withChime){
|
||||||
|
val chup = contentCache.getAudioFile("chimeup")
|
||||||
|
if (chup!=null && chup.isValid()){
|
||||||
|
if (!pushData(chup.bytes)){
|
||||||
|
allsuccess = false
|
||||||
|
Logger.error { "Failed to push Chime Up" }
|
||||||
|
}
|
||||||
|
} else Logger.error { "withChime=true, but Chime Up not available" }
|
||||||
|
}
|
||||||
|
sources.forEach { source ->
|
||||||
|
if (source.isValid()) {
|
||||||
|
// write the bytes to the stream
|
||||||
|
if (!pushData(source.bytes)){
|
||||||
|
allsuccess = false
|
||||||
|
Logger.error { "Source ${source.fileName} push failed" }
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
allsuccess = false
|
||||||
|
Logger.error { "Not pushing Source=${source.fileName} because invalid" }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (withChime){
|
||||||
|
val chdn = contentCache.getAudioFile("chimedown")
|
||||||
|
if (chdn!=null && chdn.isValid()){
|
||||||
|
if (!pushData(chdn.bytes)){
|
||||||
|
allsuccess = false
|
||||||
|
Logger.error { "Failed to push Chime Down" }
|
||||||
|
}
|
||||||
|
} else Logger.error { "withChime=true, but Chime Down not available"}
|
||||||
|
}
|
||||||
|
|
||||||
|
val readsize: Long = 1024 * 1024 // read 1 MB at a time
|
||||||
|
var totalread: Long = 0
|
||||||
|
do{
|
||||||
|
val p = Memory(readsize)
|
||||||
|
val read = bass.BASS_ChannelGetData(streamhandle, p, 4096)
|
||||||
|
if (read > 0) {
|
||||||
|
totalread += read
|
||||||
|
}
|
||||||
|
} while (read > 0)
|
||||||
|
|
||||||
|
|
||||||
|
// close the encoding handle
|
||||||
|
bassenc.BASS_Encode_Stop(encodehandle)
|
||||||
|
bass.BASS_ChannelFree(streamhandle)
|
||||||
|
return if (allsuccess){
|
||||||
|
|
||||||
|
Result_Boolean_String(true, "WAV file written successfully: $target")
|
||||||
|
} else {
|
||||||
|
Result_Boolean_String(false, "Failed to write some data to WAV file: $target")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -416,6 +416,8 @@ public interface Bass extends Library {
|
|||||||
int BASS_SYNC_MIXTIME = 0x40000000; // flag: sync at mixtime, else at playtime
|
int BASS_SYNC_MIXTIME = 0x40000000; // flag: sync at mixtime, else at playtime
|
||||||
int BASS_SYNC_ONETIME = 0x80000000; // flag: sync only once, else continuously
|
int BASS_SYNC_ONETIME = 0x80000000; // flag: sync only once, else continuously
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface SYNCPROC extends Callback
|
interface SYNCPROC extends Callback
|
||||||
{
|
{
|
||||||
void SYNCPROC(int handle, int channel, int data, Pointer user);
|
void SYNCPROC(int handle, int channel, int data, Pointer user);
|
||||||
@@ -714,6 +716,7 @@ public interface Bass extends Library {
|
|||||||
boolean BASS_SampleStop(int handle);
|
boolean BASS_SampleStop(int handle);
|
||||||
|
|
||||||
int BASS_StreamCreate(int freq, int chans, int flags, STREAMPROC proc, Pointer user);
|
int BASS_StreamCreate(int freq, int chans, int flags, STREAMPROC proc, Pointer user);
|
||||||
|
int BASS_StreamCreate(int freq, int chans, int flags, Pointer proc, Pointer user); // for STREAMPROC_DUMMY
|
||||||
int BASS_StreamCreateFile(boolean mem, String file, long offset, long length, int flags);
|
int BASS_StreamCreateFile(boolean mem, String file, long offset, long length, int flags);
|
||||||
int BASS_StreamCreateFile(Pointer file, long offset, long length, int flags);
|
int BASS_StreamCreateFile(Pointer file, long offset, long length, int flags);
|
||||||
int BASS_StreamCreateURL(String url, int offset, int flags, DOWNLOADPROC proc, Pointer user);
|
int BASS_StreamCreateURL(String url, int offset, int flags, DOWNLOADPROC proc, Pointer user);
|
||||||
@@ -783,8 +786,6 @@ public interface Bass extends Library {
|
|||||||
boolean BASS_FXGetParameters(int handle, Object params);
|
boolean BASS_FXGetParameters(int handle, Object params);
|
||||||
boolean BASS_FXSetPriority(int handle, int priority);
|
boolean BASS_FXSetPriority(int handle, int priority);
|
||||||
boolean BASS_FXReset(int handle);
|
boolean BASS_FXReset(int handle);
|
||||||
// gak bisa
|
|
||||||
int BASS_StreamCreate(int freq, int chans, int flags, int proc, Pointer user);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
130
src/audio/BassEnc.java
Normal file
130
src/audio/BassEnc.java
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package audio;
|
||||||
|
|
||||||
|
import com.sun.jna.*;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public interface BassEnc extends Library {
|
||||||
|
|
||||||
|
BassEnc Instance = (BassEnc) Native.load("bassenc", BassEnc.class);
|
||||||
|
|
||||||
|
// Additional error codes returned by BASS_ErrorGetCode
|
||||||
|
int BASS_ERROR_CAST_DENIED = 2100; // access denied (invalid password)
|
||||||
|
int BASS_ERROR_SERVER_CERT = 2101; // missing/invalid certificate
|
||||||
|
|
||||||
|
// Additional BASS_SetConfig options
|
||||||
|
int BASS_CONFIG_ENCODE_PRIORITY = 0x10300;
|
||||||
|
int BASS_CONFIG_ENCODE_QUEUE = 0x10301;
|
||||||
|
int BASS_CONFIG_ENCODE_CAST_TIMEOUT = 0x10310;
|
||||||
|
|
||||||
|
// Additional BASS_SetConfigPtr options
|
||||||
|
int BASS_CONFIG_ENCODE_CAST_PROXY = 0x10311;
|
||||||
|
int BASS_CONFIG_ENCODE_CAST_BIND = 0x10312;
|
||||||
|
int BASS_CONFIG_ENCODE_SERVER_CERT = 0x10320;
|
||||||
|
int BASS_CONFIG_ENCODE_SERVER_KEY = 0x10321;
|
||||||
|
|
||||||
|
// BASS_Encode_Start flags
|
||||||
|
int BASS_ENCODE_NOHEAD = 1; // don't send a WAV header to the encoder
|
||||||
|
int BASS_ENCODE_FP_8BIT = 2; // convert floating-point sample data to 8-bit integer
|
||||||
|
int BASS_ENCODE_FP_16BIT = 4; // convert floating-point sample data to 16-bit integer
|
||||||
|
int BASS_ENCODE_FP_24BIT = 6; // convert floating-point sample data to 24-bit integer
|
||||||
|
int BASS_ENCODE_FP_32BIT = 8; // convert floating-point sample data to 32-bit integer
|
||||||
|
int BASS_ENCODE_FP_AUTO = 14; // convert floating-point sample data back to channel's format
|
||||||
|
int BASS_ENCODE_BIGEND = 16; // big-endian sample data
|
||||||
|
int BASS_ENCODE_PAUSE = 32; // start encording paused
|
||||||
|
int BASS_ENCODE_PCM = 64; // write PCM sample data (no encoder)
|
||||||
|
int BASS_ENCODE_RF64 = 128; // send an RF64 header
|
||||||
|
int BASS_ENCODE_QUEUE = 0x200; // queue data to feed encoder asynchronously
|
||||||
|
int BASS_ENCODE_WFEXT = 0x400; // WAVEFORMATEXTENSIBLE "fmt" chunk
|
||||||
|
int BASS_ENCODE_CAST_NOLIMIT = 0x1000; // don't limit casting data rate
|
||||||
|
int BASS_ENCODE_LIMIT = 0x2000; // limit data rate to real-time
|
||||||
|
int BASS_ENCODE_AIFF = 0x4000; // send an AIFF header rather than WAV
|
||||||
|
int BASS_ENCODE_DITHER = 0x8000; // apply dither when converting floating-point sample data to integer
|
||||||
|
int BASS_ENCODE_AUTOFREE = 0x40000; // free the encoder when the channel is freed
|
||||||
|
|
||||||
|
// BASS_Encode_GetCount counts
|
||||||
|
int BASS_ENCODE_COUNT_IN = 0; // sent to encoder
|
||||||
|
int BASS_ENCODE_COUNT_OUT = 1; // received from encoder
|
||||||
|
int BASS_ENCODE_COUNT_CAST = 2; // sent to cast server
|
||||||
|
int BASS_ENCODE_COUNT_QUEUE = 3; // queued
|
||||||
|
int BASS_ENCODE_COUNT_QUEUE_LIMIT = 4; // queue limit
|
||||||
|
int BASS_ENCODE_COUNT_QUEUE_FAIL = 5; // failed to queue
|
||||||
|
int BASS_ENCODE_COUNT_IN_FP = 6; // sent to encoder before floating-point conversion
|
||||||
|
|
||||||
|
// BASS_Encode_CastInit content MIME types
|
||||||
|
String BASS_ENCODE_TYPE_MP3 = "audio/mpeg";
|
||||||
|
String BASS_ENCODE_TYPE_OGG = "audio/ogg";
|
||||||
|
String BASS_ENCODE_TYPE_AAC = "audio/aacp";
|
||||||
|
|
||||||
|
// BASS_Encode_CastInit flags
|
||||||
|
int BASS_ENCODE_CAST_PUBLIC = 1; // add to public directory
|
||||||
|
int BASS_ENCODE_CAST_PUT = 2; // use PUT method
|
||||||
|
int BASS_ENCODE_CAST_SSL = 4; // use SSL/TLS encryption
|
||||||
|
|
||||||
|
// BASS_Encode_CastGetStats types
|
||||||
|
int BASS_ENCODE_STATS_SHOUT = 0; // Shoutcast stats
|
||||||
|
int BASS_ENCODE_STATS_ICE = 1; // Icecast mount-point stats
|
||||||
|
int BASS_ENCODE_STATS_ICESERV = 2; // Icecast server stats
|
||||||
|
|
||||||
|
// BASS_Encode_ServerInit flags
|
||||||
|
int BASS_ENCODE_SERVER_NOHTTP = 1; // no HTTP headers
|
||||||
|
int BASS_ENCODE_SERVER_META = 2; // Shoutcast metadata
|
||||||
|
int BASS_ENCODE_SERVER_SSL = 4; // support SSL/TLS encryption
|
||||||
|
int BASS_ENCODE_SERVER_SSLONLY = 8; // require SSL/TLS encryption
|
||||||
|
|
||||||
|
// Encoder notifications
|
||||||
|
int BASS_ENCODE_NOTIFY_ENCODER = 1; // encoder died
|
||||||
|
int BASS_ENCODE_NOTIFY_CAST = 2; // cast server connection died
|
||||||
|
int BASS_ENCODE_NOTIFY_SERVER = 3; // server died
|
||||||
|
int BASS_ENCODE_NOTIFY_CAST_TIMEOUT = 0x10000; // cast timeout
|
||||||
|
int BASS_ENCODE_NOTIFY_QUEUE_FULL = 0x10001; // queue is out of space
|
||||||
|
int BASS_ENCODE_NOTIFY_FREE = 0x10002; // encoder has been freed
|
||||||
|
|
||||||
|
interface ENCODEPROC extends Callback {
|
||||||
|
/**
|
||||||
|
* Encoding Callback function.
|
||||||
|
* @param encoderhandle Encoder handle
|
||||||
|
* @param channelhandle Channel handle
|
||||||
|
* @param encodedData Buffer containing the encoded data
|
||||||
|
* @param length number of bytes
|
||||||
|
* @param user the user pointer passed to BASS_Encode_Start
|
||||||
|
*/
|
||||||
|
void ENCODEPROC(int encoderhandle, int channelhandle, Memory encodedData, int length, Pointer user);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ENCODEPROCEX extends Callback {
|
||||||
|
/**
|
||||||
|
* Encoding Callback function
|
||||||
|
* @param handle Encoder handle
|
||||||
|
* @param channel Channel handle
|
||||||
|
* @param buffer Buffer containing the encoded data
|
||||||
|
* @param length number of bytes
|
||||||
|
* @param offset file offset of the data
|
||||||
|
* @param user the user pointer passed to BASS_Encode_Start
|
||||||
|
*/
|
||||||
|
void ENCODEPROCEX(int handle, int channel, Memory buffer, int length, long offset, Object user);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ENCODERPROC extends Callback {
|
||||||
|
/**
|
||||||
|
* Encoder Callback function.
|
||||||
|
* @param encoderHandle Encoder handle
|
||||||
|
* @param channelHandle Channel handle
|
||||||
|
* @param encodedData Buffer containing the PCM Data (input) and receiving the encoded data (output)
|
||||||
|
* @param length Number of bytes in (-1 = closing)
|
||||||
|
* @param maxOut Maximum number of bytes out
|
||||||
|
* @param user the user pointer passed to BASS_Encode_Start
|
||||||
|
* @return the amount of encoded data (-1 = stop)
|
||||||
|
*/
|
||||||
|
int ENCODERPROC(int encoderHandle, int channelHandle, Memory encodedData, int length, int maxOut, Pointer user);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int BASS_Encode_GetVersion();
|
||||||
|
int BASS_Encode_Start(int handle, String cmdline, int flags, ENCODEPROC proc, Pointer user);
|
||||||
|
boolean BASS_Encode_Stop(int handle);
|
||||||
|
boolean BASS_Encode_Write(int handle, Pointer buffer, int length);
|
||||||
|
int BASS_Encode_IsActive(int handle);
|
||||||
|
boolean BASS_Encode_SetPaused(int handle, boolean paused);
|
||||||
|
int BASS_Encode_GetChannel(int handle);
|
||||||
|
boolean BASS_Encode_StopEx(int handle, boolean queue);
|
||||||
|
}
|
||||||
55
src/audio/ContentCache.kt
Normal file
55
src/audio/ContentCache.kt
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache for audio content to avoid reloading from disk
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
class ContentCache {
|
||||||
|
|
||||||
|
private val map: MutableMap<String, AudioFileInfo> = HashMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cache, but keep essential sounds : chimeup, chimedown, silence1s, silencehalf
|
||||||
|
*/
|
||||||
|
fun clear(){
|
||||||
|
// dont clear chimeup, chimedown, silence1s, silencehalf
|
||||||
|
val keysToKeep = setOf("chimeup", "chimedown", "silence1s", "silencehalf")
|
||||||
|
map.keys.retainAll(keysToKeep)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an audio file to the cache
|
||||||
|
* @param key The key to identify the audio file
|
||||||
|
* @param audioFile The AudioFileInfo object
|
||||||
|
*/
|
||||||
|
fun addAudioFile(key: String, audioFile: AudioFileInfo) {
|
||||||
|
map[key] = audioFile
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an audio file from the cache
|
||||||
|
* @param key The key to identify the audio file
|
||||||
|
* @return The AudioFileInfo object, or null if not found
|
||||||
|
*/
|
||||||
|
fun getAudioFile(key: String): AudioFileInfo? {
|
||||||
|
return map[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an audio file from the cache
|
||||||
|
* @param key The key to identify the audio file
|
||||||
|
*/
|
||||||
|
fun removeAudioFile(key: String) {
|
||||||
|
map.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the cache contains the specified key
|
||||||
|
* @param key The key to check in the cache
|
||||||
|
* @return True if the key exists, false otherwise
|
||||||
|
*/
|
||||||
|
fun haveKey(key: String): Boolean {
|
||||||
|
return map.containsKey(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
104
src/audio/TCPReceiver.kt
Normal file
104
src/audio/TCPReceiver.kt
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCPReceiver is a class that listens for TCP connections on a specified port.
|
||||||
|
* for receiving PCMFILE from Android SAMI
|
||||||
|
*/
|
||||||
|
class TCPReceiver(val portnumber: Int = 5002){
|
||||||
|
private lateinit var server: ServerSocket
|
||||||
|
private var isRunning = false
|
||||||
|
private val dataCallback = mutableMapOf<String, Consumer<ByteArray>>()
|
||||||
|
private val isfinishd = mutableMapOf<String, Boolean>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start listening for TCP connections on the specified port.
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
fun Start() : Boolean{
|
||||||
|
try{
|
||||||
|
server = ServerSocket(portnumber)
|
||||||
|
isRunning = true
|
||||||
|
Logger.info { "Server started at port $portnumber" }
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
while(isRunning){
|
||||||
|
try {
|
||||||
|
val client = server.accept()
|
||||||
|
val clientAddress = client.inetAddress.hostAddress
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
|
||||||
|
isfinishd[clientAddress] = false
|
||||||
|
var totalbytes = 0L
|
||||||
|
try{
|
||||||
|
val din = client.getInputStream()
|
||||||
|
Logger.info{ "Start receiving PCMFILE from Android with IP=${clientAddress}" }
|
||||||
|
|
||||||
|
do {
|
||||||
|
val buffer = ByteArray(16384)
|
||||||
|
val bytesRead = din.read(buffer)
|
||||||
|
if (bytesRead>0){
|
||||||
|
val data = ByteArray(bytesRead)
|
||||||
|
System.arraycopy(buffer, 0, data, 0, bytesRead)
|
||||||
|
//println("Received $bytesRead bytes from $clientAddress")
|
||||||
|
totalbytes+=bytesRead
|
||||||
|
dataCallback[clientAddress].let {
|
||||||
|
it?.accept(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (bytesRead > 0)
|
||||||
|
} catch (e : Exception){
|
||||||
|
Logger.error { "Failed receiving data from $clientAddress, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
Logger.info { "Connection from $clientAddress ended, total bytesRead=$totalbytes" }
|
||||||
|
isfinishd[clientAddress] = true
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to accept socket, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e : Exception){
|
||||||
|
Logger.error { "Failed to Start Server at port $portnumber, Message : ${e.message}" }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RequestDataFrom(ipaddress: String, cb: Consumer<ByteArray>){
|
||||||
|
dataCallback[ipaddress] = cb
|
||||||
|
}
|
||||||
|
|
||||||
|
fun StopRequestDataFrom(ipaddress: String){
|
||||||
|
if (isfinishd[ipaddress] != null){
|
||||||
|
if (isfinishd[ipaddress]==false){
|
||||||
|
// belum selesai
|
||||||
|
//println("Waiting for receiving from $ipaddress to finish...")
|
||||||
|
runBlocking {
|
||||||
|
while (isfinishd[ipaddress] == false){
|
||||||
|
kotlinx.coroutines.delay(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//println("Removing callback for $ipaddress")
|
||||||
|
dataCallback.remove(ipaddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop listening for TCP connections and close the server socket.
|
||||||
|
*/
|
||||||
|
fun Stop(){
|
||||||
|
if (isRunning){
|
||||||
|
isRunning = false
|
||||||
|
server.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/audio/UDPReceiver.kt
Normal file
79
src/audio/UDPReceiver.kt
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
/**
|
||||||
|
* UDPReceiver is a class that listens for UDP packets on a specified port.
|
||||||
|
* It is designed to run in a separate thread and can be stopped when no longer needed.
|
||||||
|
* @param portnumber The port to listen for incoming UDP packets (default is 5002
|
||||||
|
*/
|
||||||
|
class UDPReceiver(val portnumber: Int = 5002) {
|
||||||
|
private lateinit var socket : DatagramSocket
|
||||||
|
private var isRunning = false
|
||||||
|
private val dataCallback = mutableMapOf<String, Consumer<ByteArray>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start listening for UDP packets on the specified port.
|
||||||
|
* @return true if successful, false otherwise
|
||||||
|
*/
|
||||||
|
fun Start() : Boolean{
|
||||||
|
return try {
|
||||||
|
socket = DatagramSocket(portnumber)
|
||||||
|
isRunning = true
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
while(isRunning){
|
||||||
|
try {
|
||||||
|
val buffer = ByteArray(2048)
|
||||||
|
val packet = java.net.DatagramPacket(buffer, buffer.size)
|
||||||
|
socket.receive(packet)
|
||||||
|
val data = ByteArray(packet.length)
|
||||||
|
System.arraycopy(packet.data, 0, data, 0, packet.length)
|
||||||
|
dataCallback[packet.address.hostAddress].let {
|
||||||
|
it?.accept(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (isRunning) {
|
||||||
|
println("Error receiving UDP packet: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback function to be called when data is received from the specified IP address.
|
||||||
|
* @param ipaddress The IP address to listen for incoming UDP packets.
|
||||||
|
* @param callback A callback function that will be called when data is received from the specified IP address.
|
||||||
|
*/
|
||||||
|
fun RequestDataFrom(ipaddress: String, callback: Consumer<ByteArray>){
|
||||||
|
dataCallback[ipaddress] = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister the callback function for the specified IP address.
|
||||||
|
* @param ipaddress The IP address to stop listening for incoming UDP packets.
|
||||||
|
*/
|
||||||
|
fun StopRequestDataFrom(ipaddress: String){
|
||||||
|
dataCallback.remove(ipaddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop listening for UDP packets and close the socket.
|
||||||
|
*/
|
||||||
|
fun Stop(){
|
||||||
|
if (isRunning){
|
||||||
|
isRunning = false
|
||||||
|
socket.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/audio/UDPReceiverToFile.kt
Normal file
157
src/audio/UDPReceiverToFile.kt
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
import audio.Bass.BASS_STREAMPROC_END
|
||||||
|
import audio.BassEnc.BASS_ENCODE_PCM
|
||||||
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import java.io.PipedInputStream
|
||||||
|
import java.io.PipedOutputStream
|
||||||
|
import java.net.DatagramPacket
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.util.function.BiConsumer
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Deprecated("Sepertinya gak jadi pake")
|
||||||
|
@Suppress("unused")
|
||||||
|
/**
|
||||||
|
* UDPReceiverToFile is a class that listens for UDP packets on a specified address and port
|
||||||
|
* and writes the received data to a specified file.
|
||||||
|
* It is designed to run in a separate thread and can be stopped when no longer needed.
|
||||||
|
* @param listeningAddress The address to listen for incoming UDP packets.
|
||||||
|
* @param listeningPort The port to listen for incoming UDP packets.
|
||||||
|
* @param samplingrate The sampling rate for the audio data, default is 44,100 Hz.
|
||||||
|
* @param channel The number of audio channels, default is 1 (mono).
|
||||||
|
* @param outputFilePath The path to the file where the received data will be written.
|
||||||
|
* @param senderIP The IP address of the sender from which to accept packets.
|
||||||
|
*/
|
||||||
|
class UDPReceiverToFile(listeningAddress: String, listeningPort: Int, val samplingrate: Int=44100, val channel: Int=1, val outputFilePath: String, val senderIP: String) {
|
||||||
|
|
||||||
|
private var socket: DatagramSocket? = null
|
||||||
|
private val bass : Bass = Bass.Instance
|
||||||
|
private val bassenc : BassEnc = BassEnc.Instance
|
||||||
|
private var isReceiving: Boolean = false
|
||||||
|
private val pipeIn= PipedInputStream(16*1024) // 16K
|
||||||
|
var isReady: Boolean = false; private set
|
||||||
|
var bytesReceived: Long = 0; private set
|
||||||
|
var bytesWritten: Long = 0; private set
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
try{
|
||||||
|
val socketaddress = InetSocketAddress(listeningAddress, listeningPort)
|
||||||
|
socket = DatagramSocket(socketaddress)
|
||||||
|
isReady = true
|
||||||
|
} catch (e : Exception) {
|
||||||
|
Logger.error {"Failed to create UDP socket: ${e.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val streamProc = Bass.STREAMPROC { handle, buffer, length, user ->
|
||||||
|
try{
|
||||||
|
val dd = ByteArray(length)
|
||||||
|
val bytesread = pipeIn.read(dd)
|
||||||
|
|
||||||
|
if (bytesread>0){
|
||||||
|
buffer?.write(0, dd, 0, bytesread) // Write the data to the buffer
|
||||||
|
bytesWritten += bytesread
|
||||||
|
// Return the number of bytes read
|
||||||
|
bytesread
|
||||||
|
} else {
|
||||||
|
// if bytesread is 0, it means the pipe is empty, return BASS_STREAMPROC_END
|
||||||
|
BASS_STREAMPROC_END
|
||||||
|
}
|
||||||
|
} catch (e : Exception){
|
||||||
|
// If an error occurs, log it and return BASS_STREAMPROC_END
|
||||||
|
Logger.error { "STREAMPROC exception on UDPReceiverToFile $senderIP $outputFilePath" }
|
||||||
|
BASS_STREAMPROC_END
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts receiving data from the UDP socket and writing it to the specified file.
|
||||||
|
* This method runs in a separate thread.
|
||||||
|
* @param callback A BiConsumer that accepts a Boolean indicating success or failure and a String message.
|
||||||
|
* @param udpIsReceiving A Consumer that accepts a Boolean indicating whether UDP is currently receiving
|
||||||
|
*/
|
||||||
|
fun startReceiving(callback : BiConsumer<Boolean, String>, udpIsReceiving: Consumer<Boolean>) {
|
||||||
|
var isReceiving = false
|
||||||
|
if (isReady){
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
scope.launch(CoroutineName("UDPReceiverToFile UDP $senderIP $outputFilePath")) {
|
||||||
|
Logger.info { "UDPReceiverToFile started, listening on ${socket?.localSocketAddress} , saving to $outputFilePath" }
|
||||||
|
|
||||||
|
PipedOutputStream(pipeIn).use { pipeOut ->
|
||||||
|
while (isReceiving) {
|
||||||
|
try{
|
||||||
|
val xx = DatagramPacket(ByteArray(1500),1500)
|
||||||
|
socket?.receive(xx)
|
||||||
|
if (xx.address.hostAddress!= senderIP) continue
|
||||||
|
if (xx.length < 1) continue
|
||||||
|
pipeOut.write(xx.data, 0, xx.length)
|
||||||
|
bytesReceived += xx.length
|
||||||
|
if (!isReceiving){
|
||||||
|
isReceiving = true
|
||||||
|
udpIsReceiving.accept(true)
|
||||||
|
}
|
||||||
|
} catch (e : Exception){
|
||||||
|
Logger.error { "Error receiving UDP packet: ${e.message}" }
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.info { "UDPReceiverToFile ended" }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
scope.launch(CoroutineName("UDPReceiverToFile BASS $senderIP $outputFilePath")) {
|
||||||
|
bass.BASS_SetDevice(0) // Set to No Sound device, we are not playing audio
|
||||||
|
val streamhandle = bass.BASS_StreamCreate(samplingrate, channel, 0, streamProc, null)
|
||||||
|
if (streamhandle!=0){
|
||||||
|
bass.BASS_ChannelPlay(streamhandle,false)
|
||||||
|
val encodehandle = bassenc.BASS_Encode_Start(streamhandle, outputFilePath, BASS_ENCODE_PCM, null, null)
|
||||||
|
if (encodehandle!=0){
|
||||||
|
Logger.info { "UDPReceiverToFile started writing to $outputFilePath" }
|
||||||
|
callback.accept(true, "UDPReceiverToFile started successfully, writing to $outputFilePath")
|
||||||
|
while (isReceiving) {
|
||||||
|
try {
|
||||||
|
delay(1000)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
Logger.error { "UDPReceiverToFile thread interrupted: ${e.message}" }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bassenc.BASS_Encode_Stop(encodehandle)
|
||||||
|
bass.BASS_StreamFree(streamhandle)
|
||||||
|
Logger.info { "UDPReceiverToFile stopped writing to $outputFilePath" }
|
||||||
|
callback.accept(false, "UDPReceiverToFile stopped successfully, written bytes: $bytesWritten")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
callback.accept(false, "Failed to start encoding: ${bass.BASS_ErrorGetCode()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
callback.accept(false, "Failed to create stream: ${bass.BASS_ErrorGetCode()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} else callback.accept(false, "UDPReceiverToFile is not ready. Check if the socket was created successfully.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop UDPReceiverToFile from receiving data.
|
||||||
|
*/
|
||||||
|
fun stopReceiving() {
|
||||||
|
isReceiving = false
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/audio/UDPSenderFromFile.kt
Normal file
100
src/audio/UDPSenderFromFile.kt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package audio
|
||||||
|
|
||||||
|
import audio.Bass.BASS_STREAM_DECODE
|
||||||
|
import codes.Somecodes.Companion.ValidFile
|
||||||
|
import com.sun.jna.Memory
|
||||||
|
import java.net.DatagramPacket
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.SocketAddress
|
||||||
|
import java.util.function.BiConsumer
|
||||||
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Deprecated("Sepertinya gak jadi pake ini")
|
||||||
|
@Suppress("unused")
|
||||||
|
class UDPSenderFromFile(val fileName: String, val bytesPerPackage: Int=1024, targetIP: Array<String>, targetPort: Int ) {
|
||||||
|
val bass: Bass = Bass.Instance
|
||||||
|
var filehandle: Int = 0
|
||||||
|
var listSocketAddress = ArrayList<SocketAddress>()
|
||||||
|
|
||||||
|
var initialized: Boolean = false; private set
|
||||||
|
var isRunning: Boolean = false; private set
|
||||||
|
var bytesSent: Int = 0; private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (ValidFile(fileName)){
|
||||||
|
bass.BASS_SetDevice(0)
|
||||||
|
val handle = bass.BASS_StreamCreateFile(false, fileName, 0,0, BASS_STREAM_DECODE)
|
||||||
|
if (handle!=0){
|
||||||
|
// test buka file berhasil, tutup lagi
|
||||||
|
bass.BASS_StreamFree(handle)
|
||||||
|
if (targetPort>0 && targetPort<65535){
|
||||||
|
if (targetIP.isNotEmpty()){
|
||||||
|
var validIPs = true
|
||||||
|
for(ip in targetIP){
|
||||||
|
try{
|
||||||
|
var so = InetSocketAddress(ip, targetPort)
|
||||||
|
listSocketAddress.add(so)
|
||||||
|
} catch (e : Exception){
|
||||||
|
validIPs = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validIPs){
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Start(callback: BiConsumer<Boolean, String>){
|
||||||
|
if (initialized){
|
||||||
|
|
||||||
|
val scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
scope.launch(CoroutineName("UDPSenderFromFile $fileName")) {
|
||||||
|
try {
|
||||||
|
val socket = DatagramSocket()
|
||||||
|
bass.BASS_SetDevice(0) // Set to No Sound Device
|
||||||
|
val handle = bass.BASS_StreamCreateFile(false, fileName, 0, 0, BASS_STREAM_DECODE)
|
||||||
|
if (handle!=0){
|
||||||
|
isRunning = true
|
||||||
|
bytesSent = 0
|
||||||
|
callback.accept(true,"UDPSenderFromFile started, sending $fileName to ${listSocketAddress.size} targets")
|
||||||
|
while(isRunning){
|
||||||
|
val buffer = Memory(bytesPerPackage.toLong())
|
||||||
|
val bytesRead = bass.BASS_ChannelGetData(handle, buffer, bytesPerPackage)
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
for(so in listSocketAddress){
|
||||||
|
val bytes = buffer.getByteArray(0, bytesRead)
|
||||||
|
socket.send(DatagramPacket(bytes, bytes.size, so))
|
||||||
|
bytesSent += bytes.size
|
||||||
|
}
|
||||||
|
|
||||||
|
} else isRunning = false
|
||||||
|
}
|
||||||
|
callback.accept(false,"UDPSenderFromFile finished sending $fileName")
|
||||||
|
bass.BASS_StreamFree(handle)
|
||||||
|
socket.close()
|
||||||
|
} else callback.accept(false, "Failed to open file $fileName for reading")
|
||||||
|
} catch (e : Exception){
|
||||||
|
callback.accept(false, "Error in UDPSenderFromFile: ${e.message}")
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else callback.accept(false, "UDP Sender not initialized, check file and target IP/Port")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Stop(){
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
230
src/barix/BarixConnection.kt
Normal file
230
src/barix/BarixConnection.kt
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
package barix
|
||||||
|
|
||||||
|
import codes.Somecodes
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import java.net.DatagramPacket
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Socket
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class BarixConnection(val index: UInt, var channel: String, val ipaddress: String, val port: Int = 5002) {
|
||||||
|
private var _bR: Int = 0
|
||||||
|
private var _sd: Int = 0
|
||||||
|
private var _vu: Int = 0
|
||||||
|
private var _onlinecounter = 0
|
||||||
|
private val inet = InetSocketAddress(ipaddress, port)
|
||||||
|
private val maxUDPsize = 1000
|
||||||
|
private var _tcp: Socket? = null
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer remain in bytes
|
||||||
|
*/
|
||||||
|
var bufferRemain: Int
|
||||||
|
get() = _bR
|
||||||
|
set(value) {
|
||||||
|
_bR = value
|
||||||
|
_onlinecounter = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status data, 0 = playback idle, 1 = playback running
|
||||||
|
*/
|
||||||
|
var statusData: Int
|
||||||
|
get() = _sd
|
||||||
|
set(value) {
|
||||||
|
_sd = if (value < 0) 0 else if (value > 1) 1 else value
|
||||||
|
_onlinecounter = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VU level 0-100
|
||||||
|
*/
|
||||||
|
var vu: Int
|
||||||
|
get() = _vu
|
||||||
|
set(value) {
|
||||||
|
_vu = if (value < 0) 0 else if (value > 100) 100 else value
|
||||||
|
_onlinecounter = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCP command socket for communication with this Barix device
|
||||||
|
*/
|
||||||
|
var commandsocket: Socket?
|
||||||
|
get() = _tcp
|
||||||
|
set(value){
|
||||||
|
_tcp = value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrement online counter, if counter reaches 0, the device is considered offline
|
||||||
|
*/
|
||||||
|
fun decrementOnlineCounter() {
|
||||||
|
if (_onlinecounter > 0) {
|
||||||
|
_onlinecounter--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Barix device is online
|
||||||
|
* @return true if online
|
||||||
|
*/
|
||||||
|
fun isOnline(): Boolean {
|
||||||
|
return _onlinecounter > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Barix device is idle (not playing)
|
||||||
|
* @return true if idle
|
||||||
|
*/
|
||||||
|
fun isIdle() : Boolean{
|
||||||
|
return statusData == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Barix device is playing
|
||||||
|
* @return true if playing
|
||||||
|
*/
|
||||||
|
fun isPlaying() : Boolean{
|
||||||
|
return statusData == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data to Barix device via UDP
|
||||||
|
* @param data The data to send
|
||||||
|
*/
|
||||||
|
fun SendData(data: ByteArray, cbOK: Consumer<String>, cbFail: Consumer<String>) {
|
||||||
|
if (data.isNotEmpty()) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
DatagramSocket().use{ udp ->
|
||||||
|
val bb = ByteBuffer.wrap(data)
|
||||||
|
while(bb.hasRemaining()){
|
||||||
|
try {
|
||||||
|
val chunk = ByteArray(if (bb.remaining() > maxUDPsize) maxUDPsize else bb.remaining())
|
||||||
|
bb.get(chunk)
|
||||||
|
//println("Buffer remain: $bufferRemain, sending chunk size: ${chunk.size}")
|
||||||
|
while(bufferRemain<chunk.size){
|
||||||
|
delay(10)
|
||||||
|
//println("Waiting until buffer enough..")
|
||||||
|
}
|
||||||
|
udp.send(DatagramPacket(chunk, chunk.size, inet))
|
||||||
|
delay(2)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
cbFail.accept("SendData to $ipaddress:$port failed, message: ${e.message}")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cbOK.accept("SendData to $channel ($ipaddress:$port) succeeded, ${data.size} bytes sent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else cbFail.accept("SendData to $ipaddress:$port failed, data is empty")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert BarixConnection to JsonNode
|
||||||
|
* @return JsonNode representation of BarixConnection
|
||||||
|
*/
|
||||||
|
fun toJsonNode(): JsonNode {
|
||||||
|
// make json node from index, channel, ipaddress, port, bufferRemain, statusData, vu
|
||||||
|
return Somecodes.objectmapper.createObjectNode().apply {
|
||||||
|
put("index", index.toInt())
|
||||||
|
put("channel", channel)
|
||||||
|
put("ipaddress", ipaddress)
|
||||||
|
put("port", port)
|
||||||
|
put("bufferRemain", bufferRemain)
|
||||||
|
put("statusData", statusData)
|
||||||
|
put("vu", vu)
|
||||||
|
put("isOnline", isOnline())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert BarixConnection to JSON string
|
||||||
|
* @return JSON string representation of BarixConnection
|
||||||
|
*/
|
||||||
|
fun toJsonString(): String {
|
||||||
|
return Somecodes.toJsonString(toJsonNode())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate relay on Barix device
|
||||||
|
* @param relays The relay numbers to activate (1-8)
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
fun ActivateRelay(vararg relays: Int){
|
||||||
|
val command = StringBuilder("RELAY;")
|
||||||
|
var binary = 0
|
||||||
|
relays.forEach {
|
||||||
|
if (it in 1..8) {
|
||||||
|
binary = binary or (1 shl (it - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command.append(binary.toString()).append("@")
|
||||||
|
SendCommand(command.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ActivateRelay(relays: List<Int>){
|
||||||
|
val command = StringBuilder("RELAY;")
|
||||||
|
var binary = 0
|
||||||
|
relays.forEach {
|
||||||
|
if (it in 1..8) {
|
||||||
|
binary = binary or (1 shl (it - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command.append(binary.toString()).append("@")
|
||||||
|
SendCommand(command.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivate relay on Barix device
|
||||||
|
*/
|
||||||
|
fun DeactivateRelay(){
|
||||||
|
SendCommand("RELAY;0@")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send command to Barix device
|
||||||
|
* @param command The command to send
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
fun SendCommand(command: String): Boolean {
|
||||||
|
try {
|
||||||
|
if (_tcp!=null){
|
||||||
|
if (!_tcp!!.isClosed){
|
||||||
|
val bb = command.toByteArray()
|
||||||
|
val size = bb.size + 4
|
||||||
|
val b4 = byteArrayOf(
|
||||||
|
(size shr 24 and 0xFF).toByte(),
|
||||||
|
(size shr 16 and 0xFF).toByte(),
|
||||||
|
(size shr 8 and 0xFF).toByte(),
|
||||||
|
(size and 0xFF).toByte()
|
||||||
|
)
|
||||||
|
val out = _tcp!!.getOutputStream()
|
||||||
|
out.write(b4)
|
||||||
|
out.write(bb)
|
||||||
|
out.flush()
|
||||||
|
Logger.info { "SendCommand to $ipaddress : $command" }
|
||||||
|
return true
|
||||||
|
}else {
|
||||||
|
Logger.error { "Socket to $ipaddress is not connected" }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.error { "Socket to $ipaddress is null" }
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to SendCommand to $ipaddress, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/barix/BarixStatus.kt
Normal file
7
src/barix/BarixStatus.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package barix
|
||||||
|
@Suppress("unused")
|
||||||
|
data class BarixStatus(val ipaddress: String, val vu: Int, val buffremain: Int, val statusdata: Int){
|
||||||
|
override fun toString(): String {
|
||||||
|
return "BarixStatus(ipaddress='$ipaddress', vu=$vu, buffremain=$buffremain, statusdata=$statusdata)"
|
||||||
|
}
|
||||||
|
}
|
||||||
128
src/barix/TCP_Barix_Command_Server.kt
Normal file
128
src/barix/TCP_Barix_Command_Server.kt
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package barix
|
||||||
|
|
||||||
|
import codes.Somecodes.Companion.ValidString
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import java.io.DataInputStream
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.net.Socket
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class TCP_Barix_Command_Server {
|
||||||
|
lateinit var tcpserver: ServerSocket
|
||||||
|
lateinit var job: Job
|
||||||
|
private val socketMap = mutableMapOf<String, Socket>()
|
||||||
|
|
||||||
|
private val regex = """STATUSBARIX;(\d+);(\d+);?(\d)?"""
|
||||||
|
private val pattern = Regex(regex)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start TCP Command Server
|
||||||
|
* @param port The port number to listen on (default is 5001)
|
||||||
|
* @param cb A callback function that will be called when a valid command is received
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
fun StartTcpServer(port: Int = 5001, cb: Consumer<BarixStatus>): Boolean {
|
||||||
|
try {
|
||||||
|
val tcp = ServerSocket(port)
|
||||||
|
tcpserver = tcp
|
||||||
|
job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Logger.info { "TCP StreamerOutput server started on port $port" }
|
||||||
|
while (isActive) {
|
||||||
|
if (tcpserver.isClosed) break
|
||||||
|
try {
|
||||||
|
|
||||||
|
val socket = tcpserver.accept()
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val key : String = socket.inetAddress.hostAddress
|
||||||
|
socketMap[key] = socket
|
||||||
|
Logger.info { "Start communicating with Streamer Output with IP : $key" }
|
||||||
|
try{
|
||||||
|
|
||||||
|
val din = DataInputStream(socket.getInputStream())
|
||||||
|
while (isActive) {
|
||||||
|
val length = ByteArray(4)
|
||||||
|
din.readFully(length)
|
||||||
|
val readlength = ByteBuffer.wrap(length).getInt()
|
||||||
|
//println("Read Length : $readlength")
|
||||||
|
val bb = ByteArray(readlength)
|
||||||
|
din.readFully(bb)
|
||||||
|
// B4A format, 4 bytes di depan adalah size
|
||||||
|
val str = String(bb)
|
||||||
|
//println("Received from $key : $str")
|
||||||
|
if (ValidString(str)) {
|
||||||
|
// Valid command from Barix is in format $"STATUSBARIX;VU;BuffRemain;StatusData"$
|
||||||
|
pattern.find(str)?.let { matchResult ->
|
||||||
|
val (vu, buffremain, statusdata) = matchResult.destructured
|
||||||
|
val status = BarixStatus(
|
||||||
|
socket.inetAddress.hostAddress,
|
||||||
|
vu.toInt(),
|
||||||
|
buffremain.toInt(),
|
||||||
|
statusdata.toIntOrNull() ?: 0
|
||||||
|
)
|
||||||
|
//Logger.info { "Received valid command from $key : $status" }
|
||||||
|
cb.accept(status)
|
||||||
|
} ?: run {
|
||||||
|
Logger.warn { "Invalid command format from $key : $str" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (ex:Exception){
|
||||||
|
Logger.error { "Error in communication with Streamer Output with IP $key, Message : ${ex.message}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.info { "Finished communicating with Streamer Output with IP $key" }
|
||||||
|
socketMap.remove(key)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Logger.error { "Failed accepting TCP Socket, Message : ${ex.message}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Logger.info { "TCP server stopped" }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to StartTcpServer, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop TCP Command Server
|
||||||
|
* @return true if succesful
|
||||||
|
*/
|
||||||
|
fun StopTcpCommand(): Boolean {
|
||||||
|
try {
|
||||||
|
tcpserver.close()
|
||||||
|
runBlocking {
|
||||||
|
socketMap.values.forEach {
|
||||||
|
it.close()
|
||||||
|
}
|
||||||
|
socketMap.clear()
|
||||||
|
job.join()
|
||||||
|
}
|
||||||
|
Logger.info { "StopTcpCommand success" }
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Socket by IP address
|
||||||
|
* @param ip The IP address of the client
|
||||||
|
* @return Socket if found, null otherwise
|
||||||
|
*/
|
||||||
|
fun getSocket(ip: String): Socket? {
|
||||||
|
return socketMap[ip]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
14
src/codes/QuadConsumer.kt
Normal file
14
src/codes/QuadConsumer.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package codes
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
interface QuadConsumer<A,B,C,D> {
|
||||||
|
/**
|
||||||
|
* Performs this operation on the given arguments.
|
||||||
|
*
|
||||||
|
* @param a the first input argument
|
||||||
|
* @param b the second input argument
|
||||||
|
* @param c the third input argument
|
||||||
|
* @param d the fourth input argument
|
||||||
|
*/
|
||||||
|
fun accept(a: A, b: B, c: C, d: D)
|
||||||
|
}
|
||||||
4
src/codes/Result_Boolean_String.kt
Normal file
4
src/codes/Result_Boolean_String.kt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package codes
|
||||||
|
|
||||||
|
class Result_Boolean_String(val success: Boolean, val message: String) {
|
||||||
|
}
|
||||||
3
src/codes/Result_GetSoundbankFiles.kt
Normal file
3
src/codes/Result_GetSoundbankFiles.kt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package codes
|
||||||
|
|
||||||
|
class Result_GetSoundbankFiles(val success: Boolean, val message: String , val files: List<String> = emptyList())
|
||||||
@@ -1,12 +1,530 @@
|
|||||||
package codes
|
package codes
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import content.Category
|
||||||
|
import content.Language
|
||||||
|
import content.NetworkInformation
|
||||||
|
import content.ScheduleDay
|
||||||
|
import content.VoiceType
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import oshi.SystemInfo
|
||||||
|
import oshi.hardware.CentralProcessor
|
||||||
|
import oshi.hardware.GlobalMemory
|
||||||
|
import oshi.hardware.NetworkIF
|
||||||
|
import oshi.hardware.Sensors
|
||||||
|
import oshi.software.os.OperatingSystem
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import kotlin.io.path.name
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class Somecodes {
|
class Somecodes {
|
||||||
companion object {
|
companion object {
|
||||||
|
val current_directory : String = System.getProperty("user.dir")
|
||||||
|
var Soundbank_directory : Path = Path.of(current_directory,"Soundbank")
|
||||||
|
|
||||||
|
val SoundbankResult_directory : Path = Path.of(current_directory,"SoundbankResult")
|
||||||
|
val PagingResult_directory : Path = Path.of(current_directory,"PagingResult")
|
||||||
|
|
||||||
|
val si = SystemInfo()
|
||||||
|
val processor: CentralProcessor = si.hardware.processor
|
||||||
|
val memory : GlobalMemory = si.hardware.memory
|
||||||
|
val NetworkInfoMap = mutableMapOf<String, NetworkInformation>()
|
||||||
|
val sensor : Sensors = si.hardware.sensors
|
||||||
|
val os : OperatingSystem = si.operatingSystem
|
||||||
|
|
||||||
|
val datetimeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")
|
||||||
|
val dateformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy")
|
||||||
|
val dateformat2: DateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
|
||||||
|
val timeformat1: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm:ss")
|
||||||
|
val timeformat2: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm")
|
||||||
|
val filenameformat: DateTimeFormatter = DateTimeFormatter.ofPattern("ddMMyyyy_HHmmss")
|
||||||
|
const val KB_threshold = 1024.0
|
||||||
|
const val MB_threshold = KB_threshold * 1024.0
|
||||||
|
const val GB_threshold = MB_threshold * 1024.0
|
||||||
|
const val TB_threshold = GB_threshold * 1024.0
|
||||||
|
val objectmapper = jacksonObjectMapper()
|
||||||
|
|
||||||
|
// regex for getting ann_id from Message, which is the number inside []
|
||||||
|
private val ann_id_regex = Regex("\\[(\\d+)]")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the directory path for a specific language, voice type, and category.
|
||||||
|
* @param language The language.
|
||||||
|
* @param voice The voice type.
|
||||||
|
* @param category The category.
|
||||||
|
* @return The path to the directory.
|
||||||
|
*/
|
||||||
|
fun SoundbankDirectory(language: Language, voice: VoiceType, category: Category) : Path{
|
||||||
|
return Soundbank_directory.resolve(language.name).resolve(voice.name).resolve(category.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SoundbankDirectory(language: String, voice: String, category: String) : Path{
|
||||||
|
return SoundbankDirectory(
|
||||||
|
Language.valueOf(language),
|
||||||
|
VoiceType.valueOf(voice),
|
||||||
|
Category.valueOf(category)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ExtractFilesFromClassPath(resourcePath: String, outputDir: Path) {
|
||||||
|
try {
|
||||||
|
val resource = Somecodes::class.java.getResource(resourcePath)
|
||||||
|
if (resource != null) {
|
||||||
|
val uri = resource.toURI()
|
||||||
|
val path = if (uri.scheme == "jar") {
|
||||||
|
val fileSystem = java.nio.file.FileSystems.newFileSystem(uri, emptyMap<String, Any>())
|
||||||
|
fileSystem.getPath(resourcePath)
|
||||||
|
} else {
|
||||||
|
Path.of(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.walk(path).use { stream ->
|
||||||
|
stream.forEach { sourcePath ->
|
||||||
|
if (Files.isRegularFile(sourcePath)) {
|
||||||
|
val fn = sourcePath.fileName
|
||||||
|
val targetPath = outputDir.resolve(fn)
|
||||||
|
if (Files.isRegularFile(targetPath) && Files.size(targetPath) > 0) {
|
||||||
|
Logger.info { "File $targetPath already exists, skipping extraction." }
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
Files.copy(sourcePath, targetPath)
|
||||||
|
Logger.info { "Extracted ${sourcePath.name} to $targetPath" }
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else Logger.error { "Resource $resource not found" }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Exception while extracting $resourcePath, Message = ${e.message}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid number.
|
||||||
|
*/
|
||||||
|
fun IsNumber(value: String) : Boolean {
|
||||||
|
return value.toIntOrNull() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is alphabetic (contains only letters).
|
||||||
|
*/
|
||||||
|
fun IsAlphabethic(value: String) : Boolean {
|
||||||
|
return value.all { it.isLetter() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract ANN ID from a message string.
|
||||||
|
* The ANN ID is expected to be a number enclosed in square brackets (e.g., "[123]").
|
||||||
|
* @param message The message string to extract the ANN ID from.
|
||||||
|
* @return The extracted ANN ID as an integer, or -1 if not found or invalid.
|
||||||
|
*/
|
||||||
|
fun Get_ANN_ID(message: String) : Int {
|
||||||
|
val matchResult = ann_id_regex.find(message)
|
||||||
|
return matchResult?.groups?.get(1)?.value?.toInt() ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an object to a JSON string.
|
||||||
|
* @param data The object to convert.
|
||||||
|
* @return A JSON string representation of the object, or "{}" if conversion fails.
|
||||||
|
*/
|
||||||
|
fun toJsonString(data: Any) : String {
|
||||||
|
return try {
|
||||||
|
objectmapper.writeValueAsString(data)
|
||||||
|
} catch (e: Exception){
|
||||||
|
"{}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a JSON string to an object of the specified class.
|
||||||
|
* @param json The JSON string to convert.
|
||||||
|
* @param clazz The class of the object to convert to.
|
||||||
|
* @return An object of the specified class, or null if conversion fails.
|
||||||
|
*/
|
||||||
|
fun <T> fromJsonString(json: String, clazz: Class<T>) : T? {
|
||||||
|
return try {
|
||||||
|
objectmapper.readValue(json, clazz)
|
||||||
|
} catch (e: Exception){
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a JSON string to a JsonNode.
|
||||||
|
* @param data The JSON string to convert.
|
||||||
|
* @return A JsonNode representation of the JSON string, or empty JsonNode if conversion fails.
|
||||||
|
*/
|
||||||
|
fun toJsonNode(data: String) : JsonNode {
|
||||||
|
return try {
|
||||||
|
objectmapper.readTree(data)
|
||||||
|
} catch (e: Exception){
|
||||||
|
objectmapper.createObjectNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all audio files (.mp3 and .wav) in the specified directory and its subdirectories.
|
||||||
|
* Only files larger than 1KB are included.
|
||||||
|
* @param p The directory Path to search in
|
||||||
|
* @return A list of absolute paths to the audio files found.
|
||||||
|
*/
|
||||||
|
fun ListAudioFiles(p: Path) : List<String>{
|
||||||
|
return try{
|
||||||
|
// find all files that ends with .mp3 or .wav
|
||||||
|
// and find them recursively
|
||||||
|
if (Files.exists(p) && Files.isDirectory(p)){
|
||||||
|
Files.walk(p)
|
||||||
|
// cari file regular saja
|
||||||
|
.filter { Files.isRegularFile(it)}
|
||||||
|
// size lebih dari 1KB
|
||||||
|
.filter { Files.size(it) > 1024}
|
||||||
|
// extension .mp3 atau .wav
|
||||||
|
.filter { it.name.endsWith(".mp3",true) || it.name.endsWith(".wav",true) }
|
||||||
|
.map { it.toAbsolutePath().toString() }
|
||||||
|
.toList()
|
||||||
|
} else throw Exception()
|
||||||
|
|
||||||
|
} catch (_ : Exception){
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a size in bytes to a human-readable format.
|
||||||
|
* @param size Size in bytes.
|
||||||
|
* @return A string representing the size in a human-readable format.
|
||||||
|
*/
|
||||||
|
fun SizetoHuman(size: Long): String {
|
||||||
|
return when {
|
||||||
|
size < KB_threshold -> "${size}B"
|
||||||
|
size < MB_threshold -> String.format("%.2f KB", size / KB_threshold)
|
||||||
|
size < GB_threshold -> String.format("%.2f MB", size / MB_threshold)
|
||||||
|
size < TB_threshold -> String.format("%.2f GB", size / GB_threshold)
|
||||||
|
else -> String.format("%.2f TB", size / TB_threshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Disk usage using OSHI library.
|
||||||
|
* @param path The path to check disk usage, defaults to the current working directory.
|
||||||
|
* @return A string representing the disk usage of the file system in a human-readable format.
|
||||||
|
*/
|
||||||
|
fun getDiskUsage(path: String = current_directory) : String {
|
||||||
|
return try{
|
||||||
|
val p = Path.of(path).toFile()
|
||||||
|
if (p.exists() && p.isDirectory){
|
||||||
|
val total = p.totalSpace
|
||||||
|
val free = p.freeSpace
|
||||||
|
val used = total - free
|
||||||
|
String.format("Total: %s, Used: %s, Free: %s, Usage: %.2f%%",
|
||||||
|
SizetoHuman(total),
|
||||||
|
SizetoHuman(used),
|
||||||
|
SizetoHuman(free),
|
||||||
|
(used.toDouble() / total * 100)
|
||||||
|
)
|
||||||
|
} else throw Exception()
|
||||||
|
|
||||||
|
} catch (_ : Exception){
|
||||||
|
"N/A"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get CPU usage using OSHI library.
|
||||||
|
* @param cb A callback function that receives the CPU usage as a string.
|
||||||
|
*/
|
||||||
|
fun getCPUUsage(cb : Consumer<String>){
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val prev = processor.systemCpuLoadTicks
|
||||||
|
delay(1000)
|
||||||
|
val current = processor.systemCpuLoadTicks
|
||||||
|
|
||||||
|
fun delta(t: CentralProcessor.TickType) = current[t.index] - prev[t.index]
|
||||||
|
|
||||||
|
val idle = delta(CentralProcessor.TickType.IDLE) + delta(CentralProcessor.TickType.IOWAIT)
|
||||||
|
val busy = delta(CentralProcessor.TickType.USER) + delta(CentralProcessor.TickType.SYSTEM) +
|
||||||
|
delta(CentralProcessor.TickType.NICE) + delta(CentralProcessor.TickType.IRQ) +
|
||||||
|
delta(CentralProcessor.TickType.SOFTIRQ)+ delta(CentralProcessor.TickType.STEAL)
|
||||||
|
|
||||||
|
val total = idle + busy
|
||||||
|
val usage = if (total > 0) {
|
||||||
|
(busy.toDouble() / total) * 100
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
cb.accept(String.format("%.2f%%", usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get RAM usage using OSHI library.
|
||||||
|
* @return A string representing the total, used, and available memory in a human-readable format.
|
||||||
|
*/
|
||||||
|
fun getMemoryUsage() : String{
|
||||||
|
val totalMemory = memory.total
|
||||||
|
val availableMemory = memory.available
|
||||||
|
val usedMemory = totalMemory - availableMemory
|
||||||
|
return String.format("Total: %s, Used: %s, Available: %s, Usage: %.2f%%",
|
||||||
|
SizetoHuman(totalMemory),
|
||||||
|
SizetoHuman(usedMemory),
|
||||||
|
SizetoHuman(availableMemory)
|
||||||
|
, (usedMemory.toDouble() / totalMemory * 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun GetNetworkStatus(cb : Consumer<List<NetworkInformation>>) {
|
||||||
|
val networks: List<NetworkIF> = si.hardware.networkIFs.toList()
|
||||||
|
networks.forEach { net ->
|
||||||
|
|
||||||
|
if (net.ifOperStatus==NetworkIF.IfOperStatus.UP){
|
||||||
|
if (net.iPv4addr.size>0 || net.iPv6addr.size>0){
|
||||||
|
var ni = NetworkInfoMap[net.name]
|
||||||
|
if (ni == null){
|
||||||
|
ni = NetworkInformation(net.name, net.displayName, net.macaddr)
|
||||||
|
NetworkInfoMap[net.name] = ni
|
||||||
|
}
|
||||||
|
ni.ipV4addr = net.iPv4addr.toMutableList()
|
||||||
|
ni.ipV6addr = net.iPv6addr.toMutableList()
|
||||||
|
ni.speed = net.speed
|
||||||
|
ni.packetsSent = net.packetsSent
|
||||||
|
ni.packetsRecv = net.packetsRecv
|
||||||
|
if (ni.updateStamp==0L){
|
||||||
|
ni.bytesSent = net.bytesSent
|
||||||
|
ni.bytesRecv = net.bytesRecv
|
||||||
|
ni.txSpeed = 0
|
||||||
|
ni.rxSpeed = 0
|
||||||
|
ni.updateStamp = System.currentTimeMillis()
|
||||||
|
} else {
|
||||||
|
// tx speed = (current bytesSent - previous bytesSent) / (current time - previous time) * 1000
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
ni.txSpeed = ((net.bytesSent - ni.bytesSent) * 1000 / (currentTime - ni.updateStamp))
|
||||||
|
ni.rxSpeed = ((net.bytesRecv - ni.bytesRecv) * 1000 / (currentTime - ni.updateStamp))
|
||||||
|
ni.bytesSent = net.bytesSent
|
||||||
|
ni.bytesRecv = net.bytesRecv
|
||||||
|
ni.updateStamp = currentTime
|
||||||
|
}
|
||||||
|
} else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name)
|
||||||
|
} else if (NetworkInfoMap.contains(net.name)) NetworkInfoMap.remove(net.name)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
cb.accept(NetworkInfoMap.values.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a value is a valid non-blank string.
|
||||||
|
* @param value The value to check.
|
||||||
|
* @return True if the value is a non-blank string, false otherwise.
|
||||||
|
*/
|
||||||
fun ValidString(value: Any) : Boolean {
|
fun ValidString(value: Any) : Boolean {
|
||||||
return value is String && value.isNotBlank()
|
return value is String && value.isNotBlank()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if all strings in a list are valid non-blank strings.
|
||||||
|
* @param values The list of strings to check.
|
||||||
|
* @return True if all strings in the list are valid non-blank strings, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidStrings(values: List<String>) : Boolean{
|
||||||
|
if (values.isNotEmpty()){
|
||||||
|
for (v in values){
|
||||||
|
if (!ValidString(v)){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid file path and the file exists.
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid file path, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidFile(value : String) : Boolean {
|
||||||
|
if (value.isNotBlank()){
|
||||||
|
return Files.exists(Path.of(value))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid date in the format "dd/MM/yyyy".
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid date, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidDate(value: String): Boolean{
|
||||||
|
return try{
|
||||||
|
if (ValidString(value)){
|
||||||
|
dateformat1.parse(value)
|
||||||
|
true
|
||||||
|
} else throw Exception()
|
||||||
|
|
||||||
|
} catch (_: Exception){
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid IPv4 address.
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid IPv4 address, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidIPV4(value: String): Boolean{
|
||||||
|
return try{
|
||||||
|
if (ValidString(value)){
|
||||||
|
val parts = value.split(".")
|
||||||
|
if (parts.size != 4) return false
|
||||||
|
for (part in parts){
|
||||||
|
val num = part.toInt()
|
||||||
|
if (num !in 0..255) return false
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else throw Exception()
|
||||||
|
|
||||||
|
} catch (_: Exception){
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid date in the format "dd-MM-yyyy".
|
||||||
|
* This format is used for log HTML files.
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid date, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValiDateForLogHtml(value: String): Boolean{
|
||||||
|
return try{
|
||||||
|
if (ValidString(value)){
|
||||||
|
dateformat2.parse(value)
|
||||||
|
true
|
||||||
|
} else throw Exception()
|
||||||
|
|
||||||
|
} catch (_: Exception){
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid time in the format "hh:mm:ss".
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid time, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidTime(value: String): Boolean{
|
||||||
|
return try{
|
||||||
|
if (ValidString(value)){
|
||||||
|
timeformat1.parse(value)
|
||||||
|
true
|
||||||
|
} else throw Exception()
|
||||||
|
|
||||||
|
} catch (_: Exception){
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid schedule time in the format "HH:mm".
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid schedule time, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidScheduleTime(value: String): Boolean{
|
||||||
|
// format HH:mm
|
||||||
|
try {
|
||||||
|
if (ValidString(value)){
|
||||||
|
timeformat2.parse(value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (_ : Exception){
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a schedule day by its name.
|
||||||
|
* @param value The name of the schedule day to find.
|
||||||
|
* @return The name of the schedule day if found, null otherwise.
|
||||||
|
*/
|
||||||
|
fun FindScheduleDay(value: String) : String? {
|
||||||
|
val sd = ScheduleDay.entries.find { sd -> sd.name == value }
|
||||||
|
return sd?.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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".
|
||||||
|
* @param value The string to check.
|
||||||
|
* @return True if the string is a valid schedule day or date, false otherwise.
|
||||||
|
*/
|
||||||
|
fun ValidScheduleDay(value: String) : Boolean {
|
||||||
|
if (ValidString(value)){
|
||||||
|
// check if value is one of ScheduleDay enum name
|
||||||
|
if (FindScheduleDay(value) != null){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// check if value is in format dd/MM/yyyy
|
||||||
|
return ValidDate(value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a WAV file name with the current date and time.
|
||||||
|
* The file name format is: [prefix]_ddMMyyyy_HHmmss_[postfix].wav
|
||||||
|
* @param prefix An optional prefix to add before the date and time.
|
||||||
|
* @param postfix An optional postfix to add after the date and time.
|
||||||
|
* @return A string representing the generated WAV file name.
|
||||||
|
*/
|
||||||
|
fun Make_WAV_FileName(prefix: String, postfix: String) : String{
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (prefix.isNotEmpty()){sb.append(prefix).append("_")}
|
||||||
|
sb.append(filenameformat.format(LocalDateTime.now()))
|
||||||
|
if (postfix.isNotEmpty()){sb.append("_").append(postfix)}
|
||||||
|
sb.append(".wav")
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sensors information using OSHI library.
|
||||||
|
* @return A string representing the CPU temperature, fan speeds, and CPU voltage, or an empty string if not available.
|
||||||
|
*/
|
||||||
|
fun GetSensorsInfo() : String {
|
||||||
|
val cputemp = sensor.cpuTemperature
|
||||||
|
val cpuvolt = sensor.cpuVoltage
|
||||||
|
val fanspeed = sensor.fanSpeeds
|
||||||
|
return if (cpuvolt>0 && cputemp > 0 && fanspeed.isNotEmpty()){
|
||||||
|
String.format("CPU Temp: %.1f °C\nFan Speeds: %s RPM\nCPU Voltage: %.2f V",
|
||||||
|
sensor.cpuTemperature,
|
||||||
|
sensor.fanSpeeds.joinToString("/"),
|
||||||
|
sensor.cpuVoltage
|
||||||
|
)
|
||||||
|
} else ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun GetUptime() : String {
|
||||||
|
val value = os.systemUptime
|
||||||
|
return if (value>0){
|
||||||
|
// number of seconds since system boot
|
||||||
|
val hours = value / 3600
|
||||||
|
val minutes = (value % 3600) / 60
|
||||||
|
val seconds = value % 60
|
||||||
|
String.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
} else ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
13
src/codes/TriConsumer.kt
Normal file
13
src/codes/TriConsumer.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package codes
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
interface TriConsumer<A,B,C> {
|
||||||
|
/**
|
||||||
|
* Performs this operation on the given arguments.
|
||||||
|
*
|
||||||
|
* @param a the first input argument
|
||||||
|
* @param b the second input argument
|
||||||
|
* @param c the third input argument
|
||||||
|
*/
|
||||||
|
fun accept(a: A, b: B, c: C)
|
||||||
|
}
|
||||||
48
src/commandServer/PagingJob.kt
Normal file
48
src/commandServer/PagingJob.kt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package commandServer
|
||||||
|
|
||||||
|
import codes.Somecodes.Companion.PagingResult_directory
|
||||||
|
import codes.Somecodes.Companion.filenameformat
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to handle a paging job, storing incoming audio data and metadata.
|
||||||
|
* @param fromIP The IP address from which the paging data is received.
|
||||||
|
* @param broadcastzones The zones to which the paging is broadcasted, is a semicolon-separated string.
|
||||||
|
*/
|
||||||
|
class PagingJob(val fromIP: String, val broadcastzones: String) {
|
||||||
|
val filePath : Path = PagingResult_directory.resolve("PAGING_"+fromIP+"_"+LocalDateTime.now().format(filenameformat)+".wav")
|
||||||
|
private val bos : ByteArrayOutputStream = ByteArrayOutputStream()
|
||||||
|
var totalBytesReceived = 0; private set
|
||||||
|
var isRunning = true; private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected Size from PCMFILE android
|
||||||
|
*/
|
||||||
|
var expectedSize = 0
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds incoming audio data to the job.
|
||||||
|
* @param data The byte array containing audio data.
|
||||||
|
* @param length The number of bytes to write from the data array.
|
||||||
|
*/
|
||||||
|
fun addData(data: ByteArray, length: Int) {
|
||||||
|
bos.write(data, 0, length)
|
||||||
|
totalBytesReceived += length
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the accumulated audio data as a byte array.
|
||||||
|
* @return A byte array containing all received audio data.
|
||||||
|
*/
|
||||||
|
fun GetData(): ByteArray {
|
||||||
|
return bos.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Close(){
|
||||||
|
bos.close()
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
673
src/commandServer/TCP_Android_Command_Server.kt
Normal file
673
src/commandServer/TCP_Android_Command_Server.kt
Normal file
@@ -0,0 +1,673 @@
|
|||||||
|
package commandServer
|
||||||
|
|
||||||
|
import audioPlayer
|
||||||
|
import codes.Somecodes.Companion.ValidString
|
||||||
|
import codes.Somecodes.Companion.datetimeformat1
|
||||||
|
import content.Category
|
||||||
|
import content.Language
|
||||||
|
import database.Messagebank
|
||||||
|
import database.QueuePaging
|
||||||
|
import database.QueueTable
|
||||||
|
import database.Soundbank
|
||||||
|
import db
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import tcpreceiver
|
||||||
|
import udpreceiver
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.net.Socket
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class TCP_Android_Command_Server {
|
||||||
|
lateinit var tcpserver: ServerSocket
|
||||||
|
lateinit var job: Job
|
||||||
|
private val socketMap = mutableMapOf<String, Socket>()
|
||||||
|
lateinit var logcb: Consumer<String>
|
||||||
|
private val listUserLogin = mutableListOf<userLogin>()
|
||||||
|
private val listOnGoingPaging = mutableMapOf<String, PagingJob>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start TCP Command Server
|
||||||
|
* @param port The port to listen on, default is 5003
|
||||||
|
* @param logCB Callback to handle Log messages
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
fun StartTcpServer(port: Int = 5003, logCB: Consumer<String>): Boolean {
|
||||||
|
logcb = logCB
|
||||||
|
try {
|
||||||
|
val tcp = ServerSocket(port)
|
||||||
|
tcpserver = tcp
|
||||||
|
job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Logger.info { "TCP Android server started on port $port" }
|
||||||
|
while (isActive) {
|
||||||
|
if (tcpserver.isClosed) break
|
||||||
|
try {
|
||||||
|
val socket = tcpserver.accept()
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
if (socket != null) {
|
||||||
|
// key is IP address only
|
||||||
|
val key: String = socket.inetAddress.hostAddress
|
||||||
|
socketMap[key] = socket
|
||||||
|
Logger.info { "Start communicating with IPMT/IPM with IP $key" }
|
||||||
|
val din = socket.getInputStream()
|
||||||
|
val dout = socket.getOutputStream()
|
||||||
|
try{
|
||||||
|
while (isActive) {
|
||||||
|
if (din.available() > 0) {
|
||||||
|
val bb = ByteArray(din.available())
|
||||||
|
din.read(bb)
|
||||||
|
// B4A format, 4 bytes di depan adalah size
|
||||||
|
val str = String(bb, 4, bb.size - 4)
|
||||||
|
//println("Received command from $key : $str")
|
||||||
|
str.split("@").map { it.trim() }.filter { ValidString(it) }
|
||||||
|
.forEach {
|
||||||
|
process_command(key,it) { reply ->
|
||||||
|
try {
|
||||||
|
dout.write(String_to_Byte_Android(reply))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcb.accept("Failed to send reply to $key, Message : $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e : Exception){
|
||||||
|
logcb.accept("Exception in communication with $key, Message : ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
logcb.accept("Finished communicatiing with $key")
|
||||||
|
CloseSocket(socket)
|
||||||
|
socketMap.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
logcb.accept("Failed accepting TCP Socket, Message : ${ex.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
logcb.accept("TCP server stopped")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logcb.accept("Failed to StartTcpServer, Message : ${e.message}")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CloseSocket(socket : Socket) {
|
||||||
|
try {
|
||||||
|
socket.close()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to close socket, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a String to ByteArray in prefix AsyncStream format in B4X
|
||||||
|
* @param str The input string
|
||||||
|
* @return ByteArray with 4 bytes prefix length + string bytes
|
||||||
|
*/
|
||||||
|
private fun String_to_Byte_Android(str: String): ByteArray {
|
||||||
|
if (ValidString(str)) {
|
||||||
|
val bytes = str.toByteArray(Charsets.UTF_8)
|
||||||
|
val len = bytes.size
|
||||||
|
return ByteBuffer.allocate(len + 4)
|
||||||
|
.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
.putInt(len)
|
||||||
|
.put(bytes)
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
return ByteArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process command from Android client
|
||||||
|
* @param key The client IP address
|
||||||
|
* @param cmd The command string
|
||||||
|
* @param cb Callback to send reply string
|
||||||
|
*/
|
||||||
|
private fun process_command(key: String, cmd: String, cb: Consumer<String>) {
|
||||||
|
Logger.info { "Command from $key : $cmd" }
|
||||||
|
val parts = cmd.split(";").map { it.trim() }.filter { it.isNotBlank() }
|
||||||
|
when (parts[0]) {
|
||||||
|
"GETLOGIN" -> {
|
||||||
|
// Android login request
|
||||||
|
val username = parts.getOrElse(1) { "" }
|
||||||
|
val password = parts.getOrElse(2) { "" }
|
||||||
|
if (ValidString(username) && ValidString(password)) {
|
||||||
|
if (db.userDB.List.any{
|
||||||
|
it.username==username && it.password==password}) {
|
||||||
|
val existing = listUserLogin.find { it.ip == key}
|
||||||
|
if (existing!=null){
|
||||||
|
existing.username = username
|
||||||
|
} else{
|
||||||
|
listUserLogin.add(userLogin(key, username))
|
||||||
|
}
|
||||||
|
cb.accept("LOGIN;TRUE@")
|
||||||
|
logcb.accept("Android Login success from $key as $username")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logcb.accept("Android Login failed from $key as $username")
|
||||||
|
cb.accept("LOGIN;FALSE@")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logcb.accept("Android Login failed from $key with empty username or password")
|
||||||
|
cb.accept("LOGIN;FALSE@")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"PCMFILE_START" ->{
|
||||||
|
// start sending PCM data from Android for paging
|
||||||
|
val size = parts.getOrElse(1) { "0" }.toInt()
|
||||||
|
val filename = parts.getOrElse(2) { "" }
|
||||||
|
val zones = parts.getOrElse(3) { "" }.replace(",",";")
|
||||||
|
if (size>0){
|
||||||
|
if (ValidString(filename)){
|
||||||
|
if (ValidString(zones)){
|
||||||
|
// create paging job
|
||||||
|
val pj = PagingJob(key, zones)
|
||||||
|
// ada expected size
|
||||||
|
pj.expectedSize = size
|
||||||
|
// masukin ke list
|
||||||
|
listOnGoingPaging[key] = pj
|
||||||
|
Logger.info{"PagingJob created for Android $key, zones: $zones, file: ${pj.filePath.absolutePathString()}"}
|
||||||
|
|
||||||
|
tcpreceiver.RequestDataFrom(key) {
|
||||||
|
// push data ke paging job
|
||||||
|
pj.addData(it, it.size)
|
||||||
|
}
|
||||||
|
cb.accept("PCMFILE_START;OK@")
|
||||||
|
Logger.info{"Android $key start sending PCM data, expecting $size bytes"}
|
||||||
|
return
|
||||||
|
|
||||||
|
} else logcb.accept("PCMFILE_START from Android $key failed, empty zones")
|
||||||
|
} else logcb.accept("PCMFILE_START from Android $key failed, empty filename")
|
||||||
|
} else logcb.accept("PCMFILE_START from Android $key failed, invalid size")
|
||||||
|
cb.accept("PCMFILE_START;NG@")
|
||||||
|
}
|
||||||
|
"PCMFILE_STOP" -> {
|
||||||
|
// stop sending PCM data from Android for paging
|
||||||
|
val pj = listOnGoingPaging[key]
|
||||||
|
if (pj!=null) {
|
||||||
|
listOnGoingPaging.remove(key)
|
||||||
|
tcpreceiver.StopRequestDataFrom(key)
|
||||||
|
// get remaining data
|
||||||
|
val data = pj.GetData()
|
||||||
|
pj.Close()
|
||||||
|
if (data.size==pj.expectedSize){
|
||||||
|
Logger.info { "Paging job closed from Android $key, total bytes received ${data.size}, writing to file ${pj.filePath.absolutePathString()}" }
|
||||||
|
val result = audioPlayer.WavWriter(data, pj.filePath.absolutePathString(), true)
|
||||||
|
if (result.success) {
|
||||||
|
val qp = QueuePaging(
|
||||||
|
0u,
|
||||||
|
LocalDateTime.now().format(datetimeformat1),
|
||||||
|
"ANDROID",
|
||||||
|
"PAGING",
|
||||||
|
pj.filePath.absolutePathString(),
|
||||||
|
pj.broadcastzones
|
||||||
|
)
|
||||||
|
if (db.queuepagingDB.Add(qp)) {
|
||||||
|
db.queuepagingDB.Resort()
|
||||||
|
logcb.accept("Paging audio inserted to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
|
||||||
|
cb.accept("PCMFILE_STOP;OK@")
|
||||||
|
return
|
||||||
|
} else logcb.accept("Failed to insert paging audio to queue paging table from Android $key, file ${pj.filePath.absolutePathString()}")
|
||||||
|
} else logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : ${result.message}")
|
||||||
|
} else logcb.accept("PCMFILE_STOP from Android $key received size ${data.size} does not match expected ${pj.expectedSize}")
|
||||||
|
} else logcb.accept("PCMFILE_STOP from Android $key failed, no ongoing PCM data receiving")
|
||||||
|
cb.accept("PCMFILE_STOP;NG@")
|
||||||
|
}
|
||||||
|
|
||||||
|
"STARTPAGINGAND" -> {
|
||||||
|
// Start Paging request from IPM
|
||||||
|
val zones = parts.getOrElse(1) { "" }.replace(",",";")
|
||||||
|
if (ValidString(zones)){
|
||||||
|
// create pagingjob
|
||||||
|
val pj = PagingJob(key, zones)
|
||||||
|
// masukin ke list
|
||||||
|
listOnGoingPaging[key] = pj
|
||||||
|
Logger.info{"PagingJob created for IPM $key, zones: $zones, file: ${pj.filePath.absolutePathString()}"}
|
||||||
|
|
||||||
|
// start minta data dari udpreceiver
|
||||||
|
udpreceiver.RequestDataFrom(key){
|
||||||
|
// push data ke paging job
|
||||||
|
pj.addData(it, it.size)
|
||||||
|
}
|
||||||
|
logcb.accept("Paging started from IPM $key")
|
||||||
|
cb.accept("STARTPAGINGAND;OK@")
|
||||||
|
return
|
||||||
|
} else logcb.accept("Paging start from IPM $key failed, empty zones")
|
||||||
|
cb.accept("STARTPAGINGAND;NG@")
|
||||||
|
}
|
||||||
|
|
||||||
|
"STOPPAGINGAND" -> {
|
||||||
|
// stop paging request from IPM
|
||||||
|
val pj = listOnGoingPaging[key]
|
||||||
|
if (pj!=null){
|
||||||
|
listOnGoingPaging.remove(key)
|
||||||
|
udpreceiver.StopRequestDataFrom(key)
|
||||||
|
logcb.accept("Paging stopped from IPM $key")
|
||||||
|
// get remaining data
|
||||||
|
val data = pj.GetData()
|
||||||
|
pj.Close()
|
||||||
|
Logger.info{"Paging job closed from IPM $key, total bytes received ${data.size}, writing to file ${pj.filePath.absolutePathString()}"}
|
||||||
|
val result = audioPlayer.WavWriter(data, pj.filePath.absolutePathString(), true)
|
||||||
|
if (result.success){
|
||||||
|
val qp = QueuePaging(
|
||||||
|
0u,
|
||||||
|
LocalDateTime.now().format(datetimeformat1),
|
||||||
|
"IPM",
|
||||||
|
"PAGING",
|
||||||
|
pj.filePath.absolutePathString(),
|
||||||
|
pj.broadcastzones
|
||||||
|
)
|
||||||
|
if (db.queuepagingDB.Add(qp)){
|
||||||
|
db.queuepagingDB.Resort()
|
||||||
|
logcb.accept("Paging audio inserted to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}")
|
||||||
|
cb.accept("STOPPAGINGAND;OK@")
|
||||||
|
return
|
||||||
|
} else logcb.accept("Failed to insert paging audio to queue paging table from IPM $key, file ${pj.filePath.absolutePathString()}")
|
||||||
|
} else logcb.accept("Failed to write paging audio to file ${pj.filePath.absolutePathString()}, Message : ${result.message}")
|
||||||
|
} else logcb.accept("Paging stop from IPM $key failed, no ongoing paging")
|
||||||
|
cb.accept("STOPPAGINGAND;NG@")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"CANCELPAGINGAND" -> {
|
||||||
|
// cancel paging request from IPM
|
||||||
|
val pj = listOnGoingPaging[key]
|
||||||
|
if (pj!=null){
|
||||||
|
pj.Close()
|
||||||
|
listOnGoingPaging.remove(key)
|
||||||
|
udpreceiver.StopRequestDataFrom(key)
|
||||||
|
logcb.accept("Paging from IPM $key cancelled")
|
||||||
|
cb.accept("CANCELPAGINGAND;OK@")
|
||||||
|
return
|
||||||
|
} else logcb.accept("Paging cancel from IPM $key failed, no ongoing paging")
|
||||||
|
cb.accept("CANCELPAGINGAND;NG@")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
"STARTINITIALIZE" -> {
|
||||||
|
val username = parts.getOrElse(1) { "" }
|
||||||
|
if (ValidString(username)){
|
||||||
|
val userlogin = listUserLogin.find { it.username == username }
|
||||||
|
if (userlogin != null){
|
||||||
|
val userdb = db.userDB.List.find { it.username == username }
|
||||||
|
if (userdb != null){
|
||||||
|
//println("Sending initialization data to $key with username $username")
|
||||||
|
val result = StringBuilder()
|
||||||
|
// kirim Zone
|
||||||
|
result.append("ZONE")
|
||||||
|
userdb.broadcastzones.split(";").map { it.trim() }.filter { it.isNotBlank() }.forEach {
|
||||||
|
result.append(";")
|
||||||
|
result.append(it)
|
||||||
|
}
|
||||||
|
result.append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim MSGTOTAL
|
||||||
|
result.clear()
|
||||||
|
val VARMESSAGES = mutableListOf<Messagebank>()
|
||||||
|
result.append("MSGTOTAL;")
|
||||||
|
userdb.messagebank_ann_id
|
||||||
|
// messagebank_ann_id adalah rentengan ANN_ID (digit) yang dipisah dengan ;
|
||||||
|
.split(";")
|
||||||
|
// trim dulu
|
||||||
|
.map { it.trim() }
|
||||||
|
// bukan string kosong antar dua tanda ;
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
// iterasi setiap ANN_ID
|
||||||
|
.forEach { annid ->
|
||||||
|
// masukin ke VARMESSAGES yang unik secara ANN_ID dan Language
|
||||||
|
val xx = db.messageDB.List
|
||||||
|
.filter{ it.ANN_ID == annid.toUInt() }
|
||||||
|
.distinctBy { it.ANN_ID }
|
||||||
|
VARMESSAGES.addAll(xx)
|
||||||
|
}
|
||||||
|
result.append(VARMESSAGES.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARAPTOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARAPTOTAL;")
|
||||||
|
val VARAPTOTAL = mutableListOf<Soundbank>()
|
||||||
|
userdb.airline_tags
|
||||||
|
.split(";")
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.forEach { al ->
|
||||||
|
val sb = db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Airplane_Name.name, true) }
|
||||||
|
.filter { it.TAG.equals(al, true)}
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
VARAPTOTAL.addAll(sb)
|
||||||
|
}
|
||||||
|
result.append(VARAPTOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARCITYTOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARCITYTOTAL;")
|
||||||
|
val VARCITYTOTAL = mutableListOf<Soundbank>()
|
||||||
|
userdb.city_tags
|
||||||
|
.split(";")
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.forEach { ct ->
|
||||||
|
val sb = db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.City.name, true) }
|
||||||
|
.filter { it.TAG.equals(ct, true)}
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
VARCITYTOTAL.addAll(sb)
|
||||||
|
}
|
||||||
|
result.append(VARCITYTOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARPLACESTOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARPLACESTOTAL;")
|
||||||
|
val VARPLACESTOTAL = mutableListOf<Soundbank>()
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Places.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARPLACESTOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARPLACESTOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARSHALATTOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARSHALATTOTAL;")
|
||||||
|
val VARSHALATTOTAL = mutableListOf<Soundbank>()
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Shalat.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARSHALATTOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARSHALATTOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARSEQUENCETOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARSEQUENCETOTAL;")
|
||||||
|
val VARSEQUENCETOTAL = mutableListOf<Soundbank>()
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Sequence.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARSEQUENCETOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARSEQUENCETOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARREASONTOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARREASONTOTAL;")
|
||||||
|
val VARREASONTOTAL = mutableListOf<Soundbank>()
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Reason.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARREASONTOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARREASONTOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARPROCEDURETOTAL
|
||||||
|
val VARPROCEDURETOTAL = mutableListOf<Soundbank>()
|
||||||
|
result.clear()
|
||||||
|
result.append("VARPROCEDURETOTAL;")
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Procedure.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARPROCEDURETOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARPROCEDURETOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARGATETOTAL
|
||||||
|
val VARGATETOTAL = mutableListOf<Soundbank>()
|
||||||
|
result.clear()
|
||||||
|
result.append("VARGATETOTAL;")
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Gate.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARGATETOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARGATETOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARCOMPENSATIONTOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARCOMPENSATIONTOTAL;")
|
||||||
|
val VARCOMPENSATIONTOTAL = mutableListOf<Soundbank>()
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Compensation.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARCOMPENSATIONTOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARCOMPENSATIONTOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
// kirim VARGREETINGTOTAL
|
||||||
|
result.clear()
|
||||||
|
result.append("VARGREETINGTOTAL;")
|
||||||
|
val VARGREETINGTOTAL = mutableListOf<Soundbank>()
|
||||||
|
db.soundDB.List
|
||||||
|
.filter { it.Category.equals(Category.Greeting.name, true) }
|
||||||
|
.distinctBy { it.TAG }
|
||||||
|
.forEach {
|
||||||
|
VARGREETINGTOTAL.add(it)
|
||||||
|
}
|
||||||
|
result.append(VARGREETINGTOTAL.size).append("@")
|
||||||
|
cb.accept(result.toString())
|
||||||
|
|
||||||
|
//Append MSG, for Android only Indonesia and English
|
||||||
|
if (VARMESSAGES.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARMESSAGES.forEachIndexed { index, msg ->
|
||||||
|
|
||||||
|
val ann_id = msg.ANN_ID
|
||||||
|
val msg_indo = db.messageDB.List.find {
|
||||||
|
it.ANN_ID == ann_id && it.Language.equals(
|
||||||
|
Language.INDONESIA.name,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val msg_eng = db.messageDB.List.find {
|
||||||
|
it.ANN_ID == ann_id && it.Language.equals(
|
||||||
|
Language.ENGLISH.name,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val description = msg_indo?.Description ?: msg_eng?.Description ?: "UNKNOWN"
|
||||||
|
result.append("MSG;$index;$ann_id;$description;")
|
||||||
|
result.append(msg_indo?.Message_Detail ?:"").append(";")
|
||||||
|
result.append(msg_eng?.Message_Detail ?:"").append("@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARAP
|
||||||
|
if (VARAPTOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARAPTOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARAP;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARCITY
|
||||||
|
if (VARCITYTOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARCITYTOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARCITY;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARPLACES
|
||||||
|
if (VARPLACESTOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARPLACESTOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARPLACES;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
// append VARSHALAT
|
||||||
|
if (VARSHALATTOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARSHALATTOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARSHALAT;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARSEQUENCE
|
||||||
|
if (VARSEQUENCETOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARSEQUENCETOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARSEQUENCE;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARREASON
|
||||||
|
if (VARREASONTOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARREASONTOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARREASON;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARPROCEDURE
|
||||||
|
if (VARPROCEDURETOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARPROCEDURETOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARPROCEDURE;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARGATE
|
||||||
|
if (VARGATETOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARGATETOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARGATE;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARCOMPENSATION
|
||||||
|
if (VARCOMPENSATIONTOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARCOMPENSATIONTOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARCOMPENSATION;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// append VARGREETING
|
||||||
|
if (VARGREETINGTOTAL.isNotEmpty()) {
|
||||||
|
result.clear()
|
||||||
|
VARGREETINGTOTAL.forEachIndexed { index, sb ->
|
||||||
|
result.append("VARGREETING;$index;${sb.TAG};${sb.Description}@")
|
||||||
|
}
|
||||||
|
cb.accept(result.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
logcb.accept("All variables sent to $key with username $username")
|
||||||
|
return
|
||||||
|
} else logcb.accept("STARTINITIALIZE failed from $key with username $username not found in userDB")
|
||||||
|
} else logcb.accept("STARTINITIALIZE failed from $key with unregistered username $username")
|
||||||
|
} else logcb.accept("STARTINITIALIZE failed from $key with empty username")
|
||||||
|
cb.accept("STARTINITIALIZE;FALSE@")
|
||||||
|
}
|
||||||
|
|
||||||
|
"BROADCASTAND" -> {
|
||||||
|
// semi auto dari android, masukin ke queue table
|
||||||
|
val desc = parts.getOrElse(1) { "" }
|
||||||
|
// language bisa lebih dari satu, dipisah dengan koma
|
||||||
|
val lang = parts.getOrElse(2) { "" }.replace(",",";")
|
||||||
|
// tags bisa lebih dari satu, dipisah dengan spasi
|
||||||
|
val tags = parts.getOrElse(3) { "" }.replace(",",";")
|
||||||
|
// zone bisa lebih dari satu, dipisah dengan koma
|
||||||
|
val zone = parts.getOrElse(4) { "" }.replace(",",";")
|
||||||
|
if (ValidString(desc)){
|
||||||
|
if (ValidString(lang)){
|
||||||
|
if (ValidString(tags)){
|
||||||
|
if (ValidString(zone)){
|
||||||
|
val qt = QueueTable(
|
||||||
|
0u,
|
||||||
|
LocalDateTime.now().format(datetimeformat1),
|
||||||
|
"ANDROID",
|
||||||
|
"SOUNDBANK",
|
||||||
|
desc,
|
||||||
|
tags,
|
||||||
|
zone,
|
||||||
|
1u,
|
||||||
|
lang
|
||||||
|
)
|
||||||
|
if (db.queuetableDB.Add(qt)){
|
||||||
|
db.queuetableDB.Resort()
|
||||||
|
logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} inserted. Message: $desc;$lang;$tags;$zone")
|
||||||
|
cb.accept("BROADCASTAND;OK@")
|
||||||
|
return
|
||||||
|
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, cannot add to queue table")
|
||||||
|
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty zone")
|
||||||
|
} else logcb.accept("Broadcsast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty tags")
|
||||||
|
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty language")
|
||||||
|
} else logcb.accept("Broadcast request from Android $key username=${listUserLogin.find { it.ip==key }?.username ?: "UNKNOWN"} failed, empty description")
|
||||||
|
cb.accept("BROADCASTAND;NG@")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
logcb.accept("Unknown command from Android: $cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop TCP Command Server
|
||||||
|
* @return true if succesful
|
||||||
|
*/
|
||||||
|
fun StopTcpCommand(): Boolean {
|
||||||
|
try {
|
||||||
|
tcpserver.close()
|
||||||
|
runBlocking {
|
||||||
|
socketMap.values.forEach {
|
||||||
|
it.close()
|
||||||
|
}
|
||||||
|
socketMap.clear()
|
||||||
|
job.join()
|
||||||
|
}
|
||||||
|
Logger.info { "StopTcpCommand success" }
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
99
src/commandServer/TCP_PC_Command_Server.kt
Normal file
99
src/commandServer/TCP_PC_Command_Server.kt
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package commandServer
|
||||||
|
|
||||||
|
import codes.Somecodes.Companion.ValidString
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.tinylog.Logger
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.net.Socket
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class TCP_PC_Command_Server {
|
||||||
|
lateinit var tcpserver: ServerSocket
|
||||||
|
lateinit var job: Job
|
||||||
|
private val socketMap = mutableMapOf<String, Socket>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start TCP Command Server
|
||||||
|
* @param port The port number to listen on (default is 5000)
|
||||||
|
* @param cb A callback function that will be called when a valid command is received
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
fun StartTcpServer(port: Int = 5000, cb: Consumer<String>): Boolean {
|
||||||
|
try {
|
||||||
|
val tcp = ServerSocket(port)
|
||||||
|
tcpserver = tcp
|
||||||
|
job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Logger.info { "TCP server started" }
|
||||||
|
while (isActive) {
|
||||||
|
if (tcpserver.isClosed) break
|
||||||
|
try {
|
||||||
|
tcpserver.accept().use { socket ->
|
||||||
|
{
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
if (socket != null) {
|
||||||
|
val key : String = socket.inetAddress.hostAddress+":"+socket.port
|
||||||
|
socketMap[key] = socket
|
||||||
|
Logger.info { "Start communicating with $key" }
|
||||||
|
socket.getInputStream().use { din ->
|
||||||
|
{
|
||||||
|
while (isActive) {
|
||||||
|
if (din.available()>0){
|
||||||
|
val bb = ByteArray(din.available())
|
||||||
|
din.read(bb)
|
||||||
|
// B4A format, 4 bytes di depan adalah size
|
||||||
|
val str = String(bb)
|
||||||
|
if (ValidString(str)) cb.accept(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.info { "Finished communicating with $key" }
|
||||||
|
socketMap.remove(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Logger.error { "Failed accepting TCP Socket, Message : ${ex.message}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Logger.info { "TCP server stopped" }
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to StartTcpServer, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop TCP Command Server
|
||||||
|
* @return true if succesful
|
||||||
|
*/
|
||||||
|
fun StopTcpCommand(): Boolean {
|
||||||
|
try {
|
||||||
|
tcpserver.close()
|
||||||
|
runBlocking {
|
||||||
|
socketMap.values.forEach {
|
||||||
|
it.close()
|
||||||
|
}
|
||||||
|
socketMap.clear()
|
||||||
|
job.join()
|
||||||
|
}
|
||||||
|
Logger.info { "StopTcpCommand success" }
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Logger.error { "Failed to StopTcpServer, Message : ${e.message}" }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/commandServer/userLogin.kt
Normal file
3
src/commandServer/userLogin.kt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package commandServer
|
||||||
|
|
||||||
|
data class userLogin(val ip : String, var username: String)
|
||||||
21
src/content/Category.kt
Normal file
21
src/content/Category.kt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package content
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
enum class Category(name: String) {
|
||||||
|
Airline_Code("Airline_Code"),
|
||||||
|
Airplane_Name("Airplane_Name"),
|
||||||
|
AlphabetNumeric("AlphabetNumeric"),
|
||||||
|
City("City"),
|
||||||
|
Phrase("Phrase"),
|
||||||
|
Places("Places"),
|
||||||
|
PlatNomor("PlatNomor"),
|
||||||
|
Shalat("Shalat"),
|
||||||
|
Year("Year"),
|
||||||
|
Birthday("Birthday"),
|
||||||
|
Reason("Reason"),
|
||||||
|
Sequence("Sequence"),
|
||||||
|
Procedure("Procedure"),
|
||||||
|
Gate("Gate"),
|
||||||
|
Greeting("Greeting"),
|
||||||
|
Compensation("Compensation");
|
||||||
|
}
|
||||||
16
src/content/Language.kt
Normal file
16
src/content/Language.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package content
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum class representing different languages.
|
||||||
|
*
|
||||||
|
* @property name The name of the language, as in Soundbank Database
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
enum class Language(name: String) {
|
||||||
|
INDONESIA("INDONESIA"),
|
||||||
|
ENGLISH("ENGLISH"),
|
||||||
|
LOCAL("LOCAL"),
|
||||||
|
JAPANESE("JAPANESE"),
|
||||||
|
CHINESE("CHINESE"),
|
||||||
|
ARABIC("ARABIC");
|
||||||
|
}
|
||||||
15
src/content/NetworkInformation.kt
Normal file
15
src/content/NetworkInformation.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package content
|
||||||
|
|
||||||
|
class NetworkInformation(val name: String, val displayName: String, val macAddress: String) {
|
||||||
|
var ipV4addr: MutableList<String> = mutableListOf()
|
||||||
|
var ipV6addr: MutableList<String> = mutableListOf()
|
||||||
|
var bytesSent: Long = 0
|
||||||
|
var bytesRecv: Long = 0
|
||||||
|
var packetsSent: Long = 0
|
||||||
|
var packetsRecv: Long = 0
|
||||||
|
var speed: Long = 0 // in bits per second
|
||||||
|
var updateStamp : Long = 0 // epoch time in milliseconds, buat hitung speed
|
||||||
|
var txSpeed: Long = 0
|
||||||
|
var rxSpeed: Long = 0
|
||||||
|
|
||||||
|
}
|
||||||
13
src/content/ScheduleDay.kt
Normal file
13
src/content/ScheduleDay.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package content
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
enum class ScheduleDay(val day: String) {
|
||||||
|
Sunday("Sunday"),
|
||||||
|
Monday("Monday"),
|
||||||
|
Tuesday("Tuesday"),
|
||||||
|
Wednesday("Wednesday"),
|
||||||
|
Thursday("Thursday"),
|
||||||
|
Friday("Friday"),
|
||||||
|
Saturday("Saturday"),
|
||||||
|
Everyday("Everyday")
|
||||||
|
}
|
||||||
6
src/content/SoundbankData.kt
Normal file
6
src/content/SoundbankData.kt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package content
|
||||||
|
|
||||||
|
import audio.AudioFileInfo
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
data class SoundbankData(val TAG: String, val Category: Category, val Language: Language, val VoiceType: VoiceType, val audio: AudioFileInfo)
|
||||||
13
src/content/VoiceType.kt
Normal file
13
src/content/VoiceType.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package content
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum class representing different voice types.
|
||||||
|
*
|
||||||
|
* @property voicename The name of the voice type.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
enum class VoiceType(voicename: String) {
|
||||||
|
VOICE_1("VOICE_1"),
|
||||||
|
VOICE_2("VOICE_2"),
|
||||||
|
VOICE_3("VOICE_3");
|
||||||
|
}
|
||||||
28
src/database/BroadcastZones.kt
Normal file
28
src/database/BroadcastZones.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
data class BroadcastZones(var index: UInt, var description: String, var SoundChannel: String, var id: String, var bp: String){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if all fields are not empty
|
||||||
|
*/
|
||||||
|
fun isNotEmpty(): Boolean{
|
||||||
|
if (description.isNotEmpty()){
|
||||||
|
if (SoundChannel.isNotEmpty()){
|
||||||
|
if (id.isNotEmpty()){
|
||||||
|
if (bp.isNotEmpty()){
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string representation of the BroadcastZones object.
|
||||||
|
*/
|
||||||
|
override fun toString(): String {
|
||||||
|
return "BroadcastZones(index=$index, description='$description', SoundChannel='$SoundChannel', id='$id', bp='$bp')"
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user