inizializzazione progetto stub java 8 con react 18
This commit is contained in:
commit
8814f4e123
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/.metadata/
|
||||
RemoteSystemsTempFiles
|
||||
.project
|
||||
.settings
|
||||
.classpath
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@ -0,0 +1 @@
|
||||
java 8.0.342+7
|
12
.vscode/launch.json
vendored
Normal file
12
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Attach debbugger",
|
||||
"request": "attach",
|
||||
"hostName": "localhost",
|
||||
"port": 8787
|
||||
}
|
||||
]
|
||||
}
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"java.project.sourcePaths": [
|
||||
//"C:/Dev2012/source/projects/tech/armtest"
|
||||
]
|
||||
}
|
35
.vscode/tasks.json
vendored
Normal file
35
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Start stub-gui",
|
||||
"type": "shell",
|
||||
"command": "cd stub-gui; npm run watch",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Monitor WAR",
|
||||
"type": "shell",
|
||||
"command": "cd scripts/jboss; ./monitor-war.bat",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"label": "Start JBoss",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "cmd.exe",
|
||||
"args": ["/c", "cd ${cwd}\\scripts\\jboss && start start-jboss.bat"]
|
||||
},
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "Maven Build",
|
||||
"type": "shell",
|
||||
"command": "cd stub-pom; mvn clean install -U -DskipTests",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
]
|
||||
}
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# stub
|
||||
|
||||
Stub per applicativo JBoss Java con React.
|
||||
|
||||
## JUNIT
|
||||
|
||||
Utilizziamo arm-test: https://gitea.armundia.com/tech/armtest
|
||||
|
||||
## H2
|
||||
|
||||
aprire dbeaver e connettersi a (path è il percorso fisico al file):
|
||||
|
||||
jdbc:h2:file:{path};IFEXISTS=TRUE;DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE
|
188
README_armtest.md
Normal file
188
README_armtest.md
Normal file
@ -0,0 +1,188 @@
|
||||
# ARMTEST
|
||||
|
||||
Funzionalità per il run di test su progetti Armundia.
|
||||
|
||||
## Basic usage
|
||||
|
||||
Dipendenza da utilizzare:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.armundia.tech</groupId>
|
||||
<artifactId>armtest</artifactId>
|
||||
<version>1.0.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Utilizzo per una classe di test (con o senza `@Testable`) e metodi `@Test`:
|
||||
|
||||
```java
|
||||
class MioPrimoTest extends ArmUnitTest {
|
||||
|
||||
@Inject
|
||||
private CdiTestService service;
|
||||
|
||||
@Test
|
||||
void test01_base() {
|
||||
String info = service.readInfo();
|
||||
assertNotNull(info, "info must be not null");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
da notare l'uso di una classe base `ArmUnitTest` per i test.
|
||||
Questo permetterà di avere:
|
||||
|
||||
* supporto completo CDI (auto discovery anche su più jar dei bean)
|
||||
* unico limite: i bean devono essere esplicitamente indicati con un'annotazione `@Dependent`, `@RequestScoped`, `@SessionScoped` o `@ApplicationScoped`
|
||||
* supporto `@PersistenceContext` (normalmente disponibile solo in contesto Enterprise)
|
||||
|
||||
## supporto scenari DB via file H2
|
||||
|
||||
```java
|
||||
@Scenario(name = "one", order = 1)
|
||||
class ScenarioOne extends ArmUnitTest {
|
||||
|
||||
@Inject
|
||||
private OneService service;
|
||||
|
||||
@Test
|
||||
void test01_query() {
|
||||
List<ApDom> dom = service.getApDom("CMA_ASSET_CLASS_INFO");
|
||||
assertTrue(!dom.isEmpty(), "dominio CMA_ASSET_CLASS_INFO vuoto/non trovato");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dove il servizio `OneService` è, ad esempio, con accesso al db tramite `EntityManager`:
|
||||
|
||||
```java
|
||||
|
||||
@Dependent
|
||||
public class OneService {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
public List<ApDom> getApDom(String cod) {
|
||||
TypedQuery<ApDom> query = em.createQuery("SELECT a FROM ApDom a WHERE a.id.codCampo = :cod", ApDom.class);
|
||||
query.setParameter("cod", cod);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
userà il database per lo scenario **`one`** che corrisponde al database, H2, in:
|
||||
`resources\META-INF\scenarios`
|
||||
`one.mv.db`
|
||||
a cui corrisponde il file:
|
||||
`one.trace.db`
|
||||
|
||||
Oltre questa modalità con i file `.db`, è supportato il recupero di tali file da un file `.zip` (per ridurre lo spazio utilizzato).
|
||||
|
||||
Altre modalità supportate:
|
||||
|
||||
**script aggiuntivo** a partire da un database esistente:
|
||||
|
||||
```java
|
||||
@Scenario(name = "one", scripts = "META-INF/scenarios/add-datas.sql")
|
||||
```
|
||||
|
||||
**script db completo** a partire da un database vuoto (in memoria):
|
||||
|
||||
```java
|
||||
@Scenario(name = "one", asResource=false, scripts = "META-INF/scenarios/from-scratch.sql")
|
||||
```
|
||||
|
||||
**script db multipli** a partire da un database vuoto in memoria (base non esiste come risorsa):
|
||||
|
||||
```java
|
||||
@Scenario(name = "base", scripts = "META-INF/scenarios/from-scratch.sql,META-INF/scenarios/more.sql")
|
||||
```
|
||||
|
||||
## Auto test per tutti i Pojo di un package (!)
|
||||
|
||||
```java
|
||||
|
||||
List<String> errors = ArmTest.instance().testPojosInPackage("com.armundia.armtest.testpojo");
|
||||
|
||||
// eventualmente aggiungere un controllo, ma già la riga precedente
|
||||
// aumenta la coverage richiamando tanti get/set
|
||||
assertEquals(0, errors.size(), "expected no errors");
|
||||
|
||||
```
|
||||
|
||||
Oppure:
|
||||
|
||||
````java
|
||||
|
||||
List<String> errors = ArmTest.instance().testPojos(
|
||||
null, // nessuna annotation in particolare
|
||||
"com.armundia.armtest.testpojo", "com.armundia.armtest.testpojo");
|
||||
|
||||
// eventualmente aggiungere un controllo, ma già la riga precedente
|
||||
// aumenta la coverage richiamando tanti get/set
|
||||
assertEquals(0, errors.size(), "expected no errors");
|
||||
|
||||
````
|
||||
|
||||
## MAVEN run test commands
|
||||
|
||||
da bash shell:
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
con report coverage:
|
||||
|
||||
```bash
|
||||
mvn test jacoco:report
|
||||
```
|
||||
|
||||
## Future features
|
||||
|
||||
* verifica supporto Rest JAX-RS
|
||||
|
||||
## Release commands
|
||||
|
||||
Follow the steps to release a maven package into Nexus Repository
|
||||
|
||||
Assure that the `pom.xml` has the `<scm>` tag that points to the git developer connection and manage takes the HEAD commit of your repository.
|
||||
|
||||
```xml
|
||||
<scm>
|
||||
<developerConnection>scm:git:{git-http-connection}</developerConnection>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
```
|
||||
|
||||
`mvn release:clean`
|
||||
|
||||
`mvn release:prepare`
|
||||
|
||||
After, if it goes all well, **push** the commits and the tag created by the execution and run:
|
||||
`mvn release:perform`
|
||||
|
||||
If something about `mvn release:perform` goes wrong and we want to revert all the `release` stuff, we need to remove the commit and tag created by the execution and run:
|
||||
`mvn release:rollback`
|
||||
|
||||
## Release
|
||||
|
||||
Qui sono indicate le release finora effettuate.
|
||||
|
||||
### Next Release
|
||||
|
||||
#### 1.0.2
|
||||
|
||||
Major release con:
|
||||
|
||||
* supporto CDI/Junit 5
|
||||
* supporto scenari di test db, basati su h2
|
||||
|
||||
#### 0.0.1
|
||||
|
||||
* Released the package... TODO
|
32
misc/db/h2/dati/001_BASE_SCRIPTS.sql
Normal file
32
misc/db/h2/dati/001_BASE_SCRIPTS.sql
Normal file
@ -0,0 +1,32 @@
|
||||
DELETE FROM SE_AUTHORIZATION WHERE AUTHID = 'ADMIN.APP';
|
||||
INSERT INTO SE_AUTHORIZATION (AUTHID, AUTHDESCR, APPLICATION)
|
||||
VALUES (N'ADMIN.APP', N'Authorization to navigate admin application', NULL);
|
||||
DELETE FROM SE_AUTHORIZATION WHERE AUTHID = 'APPLICATION';
|
||||
INSERT INTO SE_AUTHORIZATION (AUTHID, AUTHDESCR, APPLICATION)
|
||||
VALUES (N'APPLICATION', N'Authorization to navigate the application', NULL);
|
||||
|
||||
DELETE FROM SE_ROLES WHERE ROLEID = 'ADMIN';
|
||||
INSERT INTO SE_ROLES (ROLEID, ROLEDESC, ID_USER_UPD, DATE_UPD, ID_USER_CRE, DATE_CRE, APPLICATION)
|
||||
VALUES (N'ADMIN', N'Admin role', NULL, NULL, NULL, NULL, NULL);
|
||||
DELETE FROM SE_ROLES WHERE ROLEID = 'USER';
|
||||
INSERT INTO SE_ROLES (ROLEID, ROLEDESC, ID_USER_UPD, DATE_UPD, ID_USER_CRE, DATE_CRE, APPLICATION)
|
||||
VALUES (N'USER', N'User role', NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
DELETE FROM SE_ROLES_AUTH WHERE ROLEID = 'ADMIN';
|
||||
INSERT INTO SE_ROLES_AUTH (ROLEID, AUTHID, DTAUTH, ID_USER_UPD, DATE_UPD)
|
||||
VALUES (N'ADMIN', N'ADMIN.APP', NULL, NULL, NULL);
|
||||
INSERT INTO SE_ROLES_AUTH (ROLEID, AUTHID, DTAUTH, ID_USER_UPD, DATE_UPD)
|
||||
VALUES (N'ADMIN', N'APPLICATION', NULL, NULL, NULL);
|
||||
DELETE FROM SE_ROLES_AUTH WHERE ROLEID = 'USER';
|
||||
INSERT INTO SE_ROLES_AUTH (ROLEID, AUTHID, DTAUTH, ID_USER_UPD, DATE_UPD)
|
||||
VALUES (N'USER', N'APPLICATION', NULL, NULL, NULL);
|
||||
|
||||
DELETE FROM SE_USERS WHERE USERID = 'ZUXT2151';
|
||||
INSERT INTO SE_USERS (USERID, NAME, LASTNAME, TITLE, EMAIL, PHONE, PASSWORD, FIRSTNAME, DTPASSWORD, LASTPASSWORD, COD_TYPEENT, USER_ACR, COD_LANG, COD_FIL, STATUS, EXT_KEY, COD_BANK, ID_USER_UPD, DATE_UPD, IS_FAKE, DATE_DELETED, DATE_LAST_LOGIN, TIPO_ACCOUNT, COD_COMPAGNIA, ID_PERSON, COD_PORT, IP_ADDRESS, PASS_TEMP, DATE_CRE, DATE_PREVIOUS_LOGIN, CLIENT_VERSION, DELETED, CONSECUTIVE_FAILURE, LOGIN_FAILURE, MASKED_MODE, LOGIN_SUCCESS, CODICE_INTERNO, IS_ENABLED, DATE_VERIFIED_EMAIL, DATE_VERIFIED_PHONE, EMAIL_TO_VERIFY, PHONE_TO_VERIFY, TOKEN_VERIFY_EMAIL, TOKEN_VERIFY_PHONE, DATE_EXPIRE_TOKEN_EMAIL, DATE_EXPIRE_TOKEN_PHONE, DATE_DISABLED, DATE_BLOCK, USERID_BO, COD_FISCALE, N_ISCRIZIONE_RUI, DT_ISCRIZIONE_RUI, DT_CANCELLAZIONE_RUI, COD_CDR, COD_REG, LICENSE, ACTIVE_JSESSIONID)
|
||||
VALUES (N'ZUXT2151', NULL, N'Rosati', NULL, N'r.rosati@armundia.com', NULL, N'M/HBXZF8u1P6XWm3Lw+EQxrXD+fxZQjDPCbkTJPADYc=', NULL, '2024-01-01 00:00:00', NULL, NULL, NULL, N'IT', NULL, NULL, NULL, NULL, N'c.rosati', '2024-01-01 00:00:00', NULL, NULL, '2024-01-01 00:00:00', NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2024-01-01 00:00:00', NULL, NULL, 0, 4, NULL, 4893, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, N'@', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
DELETE FROM SE_USERS_ROLES WHERE USERID = 'ZUXT2151';
|
||||
INSERT INTO SE_USERS_ROLES (USERID, ROLEID, DTROLE, ENABLED, ID_USER_UPD, DATE_UPD)
|
||||
VALUES (N'ZUXT2151', N'ADMIN', '2024-01-01 00:00:00', 1, NULL, NULL);
|
||||
INSERT INTO SE_USERS_ROLES (USERID, ROLEID, DTROLE, ENABLED, ID_USER_UPD, DATE_UPD)
|
||||
VALUES (N'ZUXT2151', N'USER', '2024-01-01 00:00:00', 1, NULL, NULL);
|
207
misc/db/h2/schema/001_BASE_TABLES.sql
Normal file
207
misc/db/h2/schema/001_BASE_TABLES.sql
Normal file
@ -0,0 +1,207 @@
|
||||
-------------- arm-core-utility ----------------
|
||||
|
||||
-- AP_DOM
|
||||
CREATE TABLE IF NOT EXISTs AP_DOM (
|
||||
COD_CAMPO varchar(30) NOT NULL,
|
||||
VAL_CAMPO varchar(100) NOT NULL,
|
||||
DES_DOM varchar(4000) NULL,
|
||||
COD_ML varchar(100) NULL,
|
||||
PROD varchar(1) NULL,
|
||||
TRASC_VAL varchar(4000) NULL,
|
||||
ORD_VIS numeric(3,0) NULL,
|
||||
TRASC_VAL_1 varchar(4000) NULL,
|
||||
STATUS varchar(1) NULL,
|
||||
INFO varchar(4000) NULL,
|
||||
DATA_AGG TIMESTAMP NULL,
|
||||
HIDE numeric(1,0) DEFAULT 0 NULL,
|
||||
TRASC_VAL_2 varchar(250) NULL,
|
||||
USERID_UPD varchar(30) NULL,
|
||||
CONSTRAINT PK_AP_DOM PRIMARY KEY (COD_CAMPO, VAL_CAMPO)
|
||||
);
|
||||
|
||||
-- AP_LANG
|
||||
CREATE TABLE IF NOT EXISTS AP_LANG (
|
||||
CODE varchar(200) NULL,
|
||||
LANG varchar(2) NOT NULL,
|
||||
DESCR varchar(4000) NULL,
|
||||
DATA_AGG TIMESTAMP NULL,
|
||||
ORIGIN varchar(10) NULL,
|
||||
DESCR_MCHAR CLOB NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS PK_AP_LANG ON AP_LANG (CODE ASC, LANG ASC);
|
||||
|
||||
-- AP_OPZIONI
|
||||
CREATE TABLE IF NOT EXISTS AP_OPZIONI (
|
||||
USERID varchar(30) NOT NULL,
|
||||
SEZIONE varchar(15) NOT NULL,
|
||||
OPZIONE varchar(50) NOT NULL,
|
||||
VALORE CLOB NULL,
|
||||
DESCRIZIONE varchar(255) NULL,
|
||||
TIPO varchar(1) DEFAULT 'S' NULL,
|
||||
OVERRIDE varchar(1) DEFAULT 'N' NULL,
|
||||
DATA_AGG TIMESTAMP NULL,
|
||||
USERID_UPD varchar(30) NULL,
|
||||
CONSTRAINT AP_OPZIONI_PK PRIMARY KEY (USERID, SEZIONE, OPZIONE)
|
||||
);
|
||||
|
||||
-------------- arm-core-users ----------------
|
||||
--SE_UO
|
||||
CREATE TABLE IF NOT EXISTS SE_UO (
|
||||
ID_UO BIGINT NOT NULL,
|
||||
COD_UO varchar(30) NULL,
|
||||
DESC_UO varchar(255) NULL,
|
||||
ID_USER_MANAGER varchar(20) NULL,
|
||||
ID_UO_FATHER BIGINT NULL,
|
||||
NOTE varchar(500) NULL,
|
||||
COD_ML_NOTE varchar(65) NULL,
|
||||
DATE_UPDATE TIMESTAMP NULL,
|
||||
ID_USER_UPD varchar(30) NULL,
|
||||
DELETED TINYINT NULL,
|
||||
COD_ML_DESC varchar(65) NULL,
|
||||
GOAL_CONTRIBUTOR TINYINT NULL,
|
||||
CENTRAL_OFFICE TINYINT NULL,
|
||||
PROSPECT_MANAGER TINYINT NULL,
|
||||
COD_FIL varchar(10) NULL,
|
||||
EMAIL_UO varchar(256) NULL,
|
||||
CONSTRAINT SE_UO_PK PRIMARY KEY (ID_UO)
|
||||
);
|
||||
|
||||
--SE_USERS
|
||||
CREATE TABLE IF NOT EXISTS SE_USERS (
|
||||
USERID varchar(50) NOT NULL,
|
||||
NAME varchar(50) NULL,
|
||||
LASTNAME varchar(70) NULL,
|
||||
TITLE varchar(10) NULL,
|
||||
EMAIL varchar(290) NULL,
|
||||
PHONE varchar(50) NULL,
|
||||
PASSWORD varchar(255) NULL,
|
||||
FIRSTNAME varchar(255) NULL,
|
||||
DTPASSWORD TIMESTAMP NULL,
|
||||
LASTPASSWORD varchar(255) NULL,
|
||||
COD_TYPEENT varchar(10) NULL,
|
||||
USER_ACR varchar(10) NULL,
|
||||
COD_LANG varchar(10) NULL,
|
||||
COD_FIL varchar(10) NULL,
|
||||
STATUS varchar(1) NULL,
|
||||
EXT_KEY varchar(20) NULL,
|
||||
COD_BANK varchar(15) NULL,
|
||||
ID_USER_UPD varchar(20) NULL,
|
||||
DATE_UPD TIMESTAMP NULL,
|
||||
IS_FAKE TINYINT NULL,
|
||||
DATE_DELETED TIMESTAMP NULL,
|
||||
DATE_LAST_LOGIN TIMESTAMP NULL,
|
||||
TIPO_ACCOUNT varchar(20) NULL,
|
||||
COD_COMPAGNIA varchar(30) NULL,
|
||||
ID_PERSON BIGINT NULL,
|
||||
COD_PORT varchar(16) NULL,
|
||||
IP_ADDRESS varchar(100) NULL,
|
||||
PASS_TEMP varchar(255) NULL,
|
||||
DATE_CRE TIMESTAMP NULL,
|
||||
DATE_PREVIOUS_LOGIN TIMESTAMP NULL,
|
||||
CLIENT_VERSION varchar(50) NULL,
|
||||
DELETED TINYINT NULL,
|
||||
CONSECUTIVE_FAILURE BIGINT NULL,
|
||||
LOGIN_FAILURE BIGINT NULL,
|
||||
MASKED_MODE TINYINT NULL,
|
||||
LOGIN_SUCCESS BIGINT NULL,
|
||||
CODICE_INTERNO BIGINT NULL,
|
||||
IS_ENABLED TINYINT NULL,
|
||||
DATE_VERIFIED_EMAIL TIMESTAMP NULL,
|
||||
DATE_VERIFIED_PHONE TIMESTAMP NULL,
|
||||
EMAIL_TO_VERIFY varchar(50) NULL,
|
||||
PHONE_TO_VERIFY varchar(50) NULL,
|
||||
TOKEN_VERIFY_EMAIL varchar(10) NULL,
|
||||
TOKEN_VERIFY_PHONE varchar(10) NULL,
|
||||
DATE_EXPIRE_TOKEN_EMAIL TIMESTAMP NULL,
|
||||
DATE_EXPIRE_TOKEN_PHONE TIMESTAMP NULL,
|
||||
DATE_DISABLED TIMESTAMP NULL,
|
||||
DATE_BLOCK TIMESTAMP NULL,
|
||||
USERID_BO varchar(50) NULL,
|
||||
COD_FISCALE varchar(30) NULL,
|
||||
N_ISCRIZIONE_RUI varchar(30) NULL,
|
||||
DT_ISCRIZIONE_RUI TIMESTAMP NULL,
|
||||
DT_CANCELLAZIONE_RUI TIMESTAMP NULL,
|
||||
COD_CDR varchar(30) NULL,
|
||||
COD_REG varchar(10) NULL,
|
||||
LICENSE TINYINT NULL,
|
||||
ACTIVE_JSESSIONID varchar(255) NULL,
|
||||
CONSTRAINT SE_USERS_PK PRIMARY KEY (USERID)
|
||||
);
|
||||
|
||||
--SE_USERS_ACCESS
|
||||
CREATE TABLE IF NOT EXISTS SE_USERS_ACCESS (
|
||||
ID BIGINT AUTO_INCREMENT NOT NULL,
|
||||
DATA_LOGIN TIMESTAMP NULL,
|
||||
DATA_LOGOUT TIMESTAMP NULL,
|
||||
USER_ID varchar(20) NULL,
|
||||
IP_ADDRESS varchar(100) NULL,
|
||||
USER_AGENT varchar(200) NULL,
|
||||
SESSION_ID varchar(40) NULL,
|
||||
RESULT_MESSAGE varchar(150) NULL,
|
||||
RESULT_STATUS TINYINT NULL,
|
||||
FROM_AVATAR TINYINT NULL,
|
||||
CLIENT_VERSION varchar(50) NULL,
|
||||
APPLICATION_NAME varchar(255) NULL,
|
||||
CONSTRAINT SE_USERS_ACCESS_PK PRIMARY KEY (ID)
|
||||
);
|
||||
|
||||
--SE_USERS_ROLES
|
||||
CREATE TABLE IF NOT EXISTS SE_USERS_ROLES (
|
||||
USERID varchar(20) NOT NULL,
|
||||
ROLEID varchar(30) NOT NULL,
|
||||
DTROLE TIMESTAMP NULL,
|
||||
ENABLED TINYINT NULL,
|
||||
ID_USER_UPD varchar(20) NULL,
|
||||
DATE_UPD TIMESTAMP NULL,
|
||||
CONSTRAINT SE_USERS_ROLES_PK PRIMARY KEY (USERID, ROLEID)
|
||||
);
|
||||
|
||||
--SE_USERS_UO
|
||||
CREATE TABLE IF NOT EXISTS SE_USERS_UO (
|
||||
ID_USER varchar(20) NOT NULL,
|
||||
ID_UO BIGINT NOT NULL,
|
||||
MAIN TINYINT NULL,
|
||||
DATE_ACT TIMESTAMP NULL,
|
||||
CONSTRAINT SE_USERS_UO_PK PRIMARY KEY (ID_USER, ID_UO)
|
||||
);
|
||||
|
||||
-- SE_AUTHORIZATION
|
||||
CREATE TABLE IF NOT EXISTS SE_AUTHORIZATION (
|
||||
AUTHID varchar(50) NOT NULL,
|
||||
AUTHDESCR varchar(255) NULL,
|
||||
APPLICATION varchar(3) NULL,
|
||||
CONSTRAINT SE_AUTHORIZATION_PK PRIMARY KEY (AUTHID)
|
||||
);
|
||||
|
||||
-- SE_HISTORY_USERS_UO
|
||||
CREATE TABLE IF NOT EXISTS SE_HISTORY_USERS_UO (
|
||||
ID_USER varchar(20) NULL,
|
||||
ID_UO BIGINT NULL,
|
||||
DATE_FROM TIMESTAMP NULL,
|
||||
DATE_TO TIMESTAMP NULL,
|
||||
DATE_ACT TIMESTAMP NULL
|
||||
);
|
||||
|
||||
-- SE_ROLES
|
||||
CREATE TABLE IF NOT EXISTS SE_ROLES (
|
||||
ROLEID varchar(30) NOT NULL,
|
||||
ROLEDESC varchar(255) NULL,
|
||||
ID_USER_UPD varchar(20) NULL,
|
||||
DATE_UPD TIMESTAMP NULL,
|
||||
ID_USER_CRE varchar(20) NULL,
|
||||
DATE_CRE TIMESTAMP NULL,
|
||||
APPLICATION varchar(3) NULL,
|
||||
CONSTRAINT SE_ROLES_PK PRIMARY KEY (ROLEID)
|
||||
);
|
||||
|
||||
-- SE_ROLES_AUTH
|
||||
CREATE TABLE IF NOT EXISTS SE_ROLES_AUTH (
|
||||
ROLEID varchar(30) NOT NULL,
|
||||
AUTHID varchar(50) NOT NULL,
|
||||
DTAUTH TIMESTAMP NULL,
|
||||
ID_USER_UPD varchar(20) NULL,
|
||||
DATE_UPD TIMESTAMP NULL,
|
||||
CONSTRAINT SE_ROLES_AUTH_PK PRIMARY KEY (ROLEID, AUTHID)
|
||||
);
|
||||
|
8
scripts/jboss/.env
Normal file
8
scripts/jboss/.env
Normal file
@ -0,0 +1,8 @@
|
||||
# in seconds
|
||||
UPDATE_TIME=5
|
||||
|
||||
# path to your jboss-eap-7.4
|
||||
JBOSS_HOME=C:/Dev2012/servers/jboss-eap-7.4
|
||||
|
||||
# path to your jdk1.8.0_333
|
||||
JAVA_HOME=C:/Program Files/Java/jdk1.8.0_333
|
64
scripts/jboss/monitor-war.bat
Normal file
64
scripts/jboss/monitor-war.bat
Normal file
@ -0,0 +1,64 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Carica le variabili d'ambiente dal file .env
|
||||
if exist .env (
|
||||
for /f "usebackq tokens=*" %%i in (.env) do (
|
||||
set %%i
|
||||
)
|
||||
) else (
|
||||
echo .env file not found. Please create one with UPDATE_TIME and JBOSS_HOME variables.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Controlla se le variabili d'ambiente richieste sono impostate
|
||||
if not defined JBOSS_HOME (
|
||||
echo JBOSS_HOME is not set. Please check your .env file.
|
||||
exit /b 1
|
||||
)
|
||||
if not defined UPDATE_TIME (
|
||||
echo UPDATE_TIME is not set. Please check your .env file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
rem war folder
|
||||
set "SOURCE_DIR=..\..\stub-war\target"
|
||||
|
||||
rem jboss deployment
|
||||
set "DEST_DIR=%JBOSS_HOME%\standalone\deployments"
|
||||
|
||||
rem Imposta UPDATE_TIME come intero
|
||||
set /a "UPDATE_TIME=%UPDATE_TIME%"
|
||||
|
||||
rem war to monitor
|
||||
set "FILE_TO_MONITOR=stub-war.war"
|
||||
|
||||
echo Controllo esistenza delle directory...
|
||||
if not defined DEST_DIR (
|
||||
echo DEST_DIR non esiste: %DEST_DIR%
|
||||
exit /b 1
|
||||
)
|
||||
if not defined SOURCE_DIR (
|
||||
echo %~dp0
|
||||
echo SOURCE_DIR non esiste: %SOURCE_DIR%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
rem Ottieni la data di modifica iniziale del file
|
||||
set LAST_COPY_TIME=""
|
||||
:loop
|
||||
rem Ottieni la data di modifica attuale del file
|
||||
for %%F in ("%SOURCE_DIR%\%FILE_TO_MONITOR%") do set CURRENT_LAST_EDIT_FILE_TIME=%%~tF
|
||||
|
||||
rem Confronta le date di modifica
|
||||
if not "!CURRENT_LAST_EDIT_FILE_TIME!"=="!LAST_COPY_TIME!" (
|
||||
echo CURRENT_LAST_EDIT_FILE_TIME = !CURRENT_LAST_EDIT_FILE_TIME!
|
||||
rem Sposta il file se è stato modificato
|
||||
copy "%SOURCE_DIR%\%FILE_TO_MONITOR%" "%DEST_DIR%\"
|
||||
rem Aggiorna la data di modifica
|
||||
set LAST_COPY_TIME=!CURRENT_LAST_EDIT_FILE_TIME!
|
||||
echo LAST_COPY_TIME = !LAST_COPY_TIME!
|
||||
)
|
||||
|
||||
timeout /t %UPDATE_TIME% >nul
|
||||
goto loop
|
48
scripts/jboss/monitor-war.sh
Normal file
48
scripts/jboss/monitor-war.sh
Normal file
@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
else
|
||||
echo ".env file not found. Please create one with JBOSS_HOME and JAVA_HOME variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# war folder
|
||||
SOURCE_DIR="../../stub-war/target"
|
||||
|
||||
# jboss deployment
|
||||
DEST_DIR="$JBOSS_HOME/standalone/deployments"
|
||||
|
||||
UPDATE_TIME=$(($UPDATE_TIME))
|
||||
|
||||
# war to monitor
|
||||
FILE_TO_MONITOR="stub-war.war"
|
||||
|
||||
echo "Controllo esistenza delle directory..."
|
||||
if [ ! -d "$DEST_DIR" ]; then
|
||||
echo "DEST_DIR non esiste: $DEST_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "SOURCE_DIR non esiste: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ottieni la data di modifica iniziale del file
|
||||
LAST_MODIFIED=$(stat -c %Y "$SOURCE_DIR/$FILE_TO_MONITOR")
|
||||
|
||||
while true; do
|
||||
# Ottieni la data di modifica attuale del file
|
||||
CURRENT_MODIFIED=$(stat -c %Y "$SOURCE_DIR/$FILE_TO_MONITOR")
|
||||
|
||||
# Confronta le date di modifica
|
||||
if [ "$CURRENT_MODIFIED" -ne "$LAST_MODIFIED" ]; then
|
||||
# Sposta il file se è stato modificato
|
||||
cp "$SOURCE_DIR/$FILE_TO_MONITOR" "$DEST_DIR/"
|
||||
echo "File copiato in $DEST_DIR/"
|
||||
# Aggiorna la data di modifica
|
||||
LAST_MODIFIED=$CURRENT_MODIFIED
|
||||
fi
|
||||
|
||||
sleep $UPDATE_TIME
|
||||
done
|
37
scripts/jboss/start-jboss.bat
Normal file
37
scripts/jboss/start-jboss.bat
Normal file
@ -0,0 +1,37 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM Carica le variabili d'ambiente dal file .env
|
||||
if exist .env (
|
||||
for /f "usebackq tokens=*" %%i in (.env) do (
|
||||
set %%i
|
||||
)
|
||||
) else (
|
||||
echo .env file not found. Please create one with JBOSS_HOME and JAVA_HOME variables.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Controlla se le variabili d'ambiente richieste sono impostate
|
||||
if not defined JBOSS_HOME (
|
||||
echo JBOSS_HOME is not set. Please check your .env file.
|
||||
exit /b 1
|
||||
)
|
||||
if not defined JAVA_HOME (
|
||||
echo JAVA_HOME is not set. Please check your .env file.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Imposta la variabile di ambiente per Java
|
||||
set "PATH=%JAVA_HOME%\bin;%PATH%"
|
||||
|
||||
echo JAVA_HOME = %JAVA_HOME%
|
||||
echo JBOSS_HOME = %JBOSS_HOME%
|
||||
|
||||
REM Controlla se JBoss è già in esecuzione
|
||||
tasklist | findstr /I "standalone.bat" >nul
|
||||
if %errorlevel%==0 (
|
||||
echo JBoss è già in esecuzione
|
||||
) else (
|
||||
echo Avvio di JBoss con standalone-full-stub.xml
|
||||
start /B cmd /c "%JBOSS_HOME%\bin\standalone.bat" -Djboss.server.config.dir=../../stub-war/standalone -c standalone-full.xml
|
||||
)
|
29
scripts/jboss/start-jboss.sh
Normal file
29
scripts/jboss/start-jboss.sh
Normal file
@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Load environment variables from .env file
|
||||
if [ -f .env ]; then
|
||||
source .env
|
||||
else
|
||||
echo ".env file not found. Please create one with JBOSS_HOME and JAVA_HOME variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if required environment variables are set
|
||||
if [ -z "$JBOSS_HOME" ] || [ -z "$JAVA_HOME" ]; then
|
||||
echo "JBOSS_HOME or JAVA_HOME is not set. Please check your .env file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Imposta la variabile di ambiente per Java
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
|
||||
echo JAVA_HOME = $JAVA_HOME
|
||||
echo JBOSS_HOME = $JBOSS_HOME
|
||||
|
||||
# Controlla se JBoss è già in esecuzione
|
||||
if grep -f "$JBOSS_HOME" > /dev/null; then
|
||||
echo "JBoss è già in esecuzione"
|
||||
else
|
||||
echo "Avvio di JBoss con standalone-full-stub.xml"
|
||||
nohup "$JBOSS_HOME/bin/standalone.sh" -c ../../stub-war/standalone/standalone-full.xml > /dev/null 2>&1 &
|
||||
fi
|
4
scripts/jboss/template.env
Normal file
4
scripts/jboss/template.env
Normal file
@ -0,0 +1,4 @@
|
||||
# UPDATE_TIME=5 # in seconds
|
||||
|
||||
# JBOSS_HOME="C:/Dev2012/servers/jboss-eap-7.4" # path to your jboss-eap-7.4
|
||||
# JAVA_HOME="C:/Program Files/Java/jdk1.8.0_333" # path to your jdk1.8.0_333
|
4
scripts/run-build-prod.bat
Normal file
4
scripts/run-build-prod.bat
Normal file
@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
cd /d "%~dp0..\stub-pom\"
|
||||
mvn clean install -Pproduction
|
||||
pause
|
14
scripts/run.bat
Normal file
14
scripts/run.bat
Normal file
@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
|
||||
cd /d "%~dp0\jboss"
|
||||
|
||||
REM Esegui il primo script in una nuova finestra
|
||||
start cmd /k "monitor-war.bat"
|
||||
|
||||
REM Attendi 5 secondi
|
||||
timeout /t 5 /nobreak
|
||||
|
||||
REM Esegui il secondo script in una nuova finestra
|
||||
start cmd /k "start-jboss.bat"
|
||||
|
||||
pause
|
5
stub-gui/.gitignore
vendored
Normal file
5
stub-gui/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
release
|
||||
package-lock.json
|
||||
node
|
||||
/target/
|
3
stub-gui/README.md
Normal file
3
stub-gui/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# stub-gui
|
||||
|
||||
Armundia gui stub application with integrated module bundler.
|
10
stub-gui/environments/.env
Normal file
10
stub-gui/environments/.env
Normal file
@ -0,0 +1,10 @@
|
||||
THEME="adv360v2"
|
||||
|
||||
REST_SERVER="localhost"
|
||||
REST_BASE_URL="http://localhost:8080/stub-war/rest/"
|
||||
SESSION_EXPIRED_TIMEOUT=180000
|
||||
KEEP_ALIVE_MINUTES=5
|
||||
|
||||
AUTHENTICATION_METHOD="internal"
|
||||
|
||||
DEBUG_VIEW=true
|
10
stub-gui/environments/.env.production
Normal file
10
stub-gui/environments/.env.production
Normal file
@ -0,0 +1,10 @@
|
||||
THEME="adv360v2"
|
||||
|
||||
REST_SERVER="./"
|
||||
REST_BASE_URL="../rest/"
|
||||
SESSION_EXPIRED_TIMEOUT=180000
|
||||
KEEP_ALIVE_MINUTES=3
|
||||
|
||||
AUTHENTICATION_METHOD="internal"
|
||||
|
||||
DEBUG_VIEW=false
|
5
stub-gui/gulpfile.js
Normal file
5
stub-gui/gulpfile.js
Normal file
@ -0,0 +1,5 @@
|
||||
const gulp = require("gulp");
|
||||
const { release } = require("arm-gulp-builders");
|
||||
|
||||
gulp.task("build", gulp.series(...release.series.build));
|
||||
gulp.task("watch", gulp.series(...release.series.watch));
|
37
stub-gui/package.json
Normal file
37
stub-gui/package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"name": "stub-gui",
|
||||
"description": "Stub - base application",
|
||||
"devDependencies": {
|
||||
"arm-gulp-builders": "3.0.0-13"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amcharts/amcharts5": "5.4.11",
|
||||
"@redux-devtools/extension": "3.3.0",
|
||||
"arm-common": "1.9.0",
|
||||
"arm-core-layouts": "1.5.0",
|
||||
"arm-core-querybuilder": "3.12.3",
|
||||
"arm-file-uploader": "3.3.3",
|
||||
"arm-gui": "4.17.2",
|
||||
"arm-gui-grid-wj": "1.15.0",
|
||||
"arm-router": "1.10.0",
|
||||
"arm-splitter": "0.0.4",
|
||||
"arm-themes": "2.11.0",
|
||||
"arm-visual-component": "3.2.0",
|
||||
"immutability-helper": "3.1.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-redux": "9.1.2",
|
||||
"react-router-dom": "5.3.4",
|
||||
"redux": "5.0.1",
|
||||
"redux-thunk": "3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "npm i && gulp watch",
|
||||
"build": "npm i && gulp build --env=production",
|
||||
"build-prod": "npm i && gulp build --env=production --minified",
|
||||
"watch-m": "npm i && gulp watch --max-old-space-size=4096",
|
||||
"build-m": "npm i && gulp build --env=production --max-old-space-size=4096",
|
||||
"build-prod-m": "npm i && gulp build --env=production --minified --max-old-space-size=4096"
|
||||
}
|
||||
}
|
119
stub-gui/pom.xml
Normal file
119
stub-gui/pom.xml
Normal file
@ -0,0 +1,119 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.armundia</groupId>
|
||||
<artifactId>stub-pom</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../stub-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>stub-gui</artifactId>
|
||||
|
||||
<properties>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<sonar.exclusions>node_modules/**,release/**, node/**, source/libs/**, target/**</sonar.exclusions>
|
||||
<sonar.sources>source</sonar.sources>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>production</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<!-- Gestisce la compilazione del front
|
||||
- Scarica ed installa le versioni corrette di node ed npm (nella cartella node)
|
||||
- lancia il comando npm per il build
|
||||
-->
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<version>1.11.3</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<id>install node and npm</id>
|
||||
<goals>
|
||||
<goal>install-node-and-npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<nodeVersion>v16.13.2</nodeVersion>
|
||||
<npmVersion>8.4.0</npmVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>npm run build</id>
|
||||
<goals>
|
||||
<goal>npm</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>run build-prod-m</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<!-- Gestisco la copia dei file compilati all'interno del progetto web in modo che
|
||||
il jar generato abbia tutto il necessario -->
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${basedir}/../stub-war/src/main/webapp/app</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>release</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<!-- Evitare la generazione del jar visto che siamo in un progetto gui -->
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-jar</id>
|
||||
<phase>none</phase>
|
||||
<configuration>
|
||||
<finalName>unwanted</finalName>
|
||||
<classifier>unwanted</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!-- Evitare lo step di install ...
|
||||
non è un progetto pom vero ma un progetto GUI,
|
||||
ci pensa il copy-resources
|
||||
a spostare il compilato all'interno del progetto web -->
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-install</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
29
stub-gui/source/index.html
Normal file
29
stub-gui/source/index.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
|
||||
<title>@@NAME@@</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, shrink-to-fit=no">
|
||||
|
||||
<link rel="icon" type="image/png" href="./images/favicon/favicon-96x96.png" sizes="96x96" />
|
||||
|
||||
<link rel="stylesheet" href="libs/fontawesome-free-5.15.3/css/all.css"/>
|
||||
|
||||
<script type="module" src="libs/amcharts-5.5.2/index.js"></script>
|
||||
<script type="module" src="libs/amcharts-5.5.2/xy.js"></script>
|
||||
<script type="module" src="libs/amcharts-5.5.2/percent.js"></script>
|
||||
<script type="module" src="libs/amcharts-5.5.2/themes/Animated.js"></script>
|
||||
<script type="module" src="libs/amcharts-5.5.2/locales/it_IT.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main-react-app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
38
stub-gui/source/js/KeepAliveWorkerManager.js
Normal file
38
stub-gui/source/js/KeepAliveWorkerManager.js
Normal file
@ -0,0 +1,38 @@
|
||||
import Rest from './webapi/Rest';
|
||||
|
||||
|
||||
class KeepAliveWorkerManager {
|
||||
worker = null;
|
||||
keepAliveResultHandler = null;
|
||||
|
||||
init = (keepAliveResultHandler, minutes = 5, url = 'session/keep-alive') => {
|
||||
if (window.Worker) {
|
||||
this.keepAliveResultHandler = keepAliveResultHandler;
|
||||
this.worker = new Worker('libs/KeepAliveWorker.js');
|
||||
|
||||
this.worker.postMessage({ type: "KEEP_ALIVE_TEST" });
|
||||
|
||||
const fullUrl = Rest.createRestLink(url);
|
||||
this.worker.postMessage({
|
||||
type: "START_KEEP_ALIVE",
|
||||
data: {
|
||||
url: fullUrl,
|
||||
minutes
|
||||
}
|
||||
});
|
||||
|
||||
this.worker.onmessage = this.onmessage.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
onmessage = (e) => {
|
||||
const { type, data } = e.data;
|
||||
console.log('Message received from [KeepAliveWorker]', e.data);
|
||||
|
||||
if (this.keepAliveResultHandler && type === 'KEEP_ALIVE_RESULT') {
|
||||
this.keepAliveResultHandler(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new KeepAliveWorkerManager();
|
63
stub-gui/source/js/Main.jsx
Normal file
63
stub-gui/source/js/Main.jsx
Normal file
@ -0,0 +1,63 @@
|
||||
import "@babel/polyfill";
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { AppConstants } from './constants/AppConstants.js';
|
||||
import { init } from "./init.js";
|
||||
import { store } from './reducers/store.js';
|
||||
import MasterPageBase from './ui/MasterPageBase.jsx';
|
||||
|
||||
|
||||
function MainReactApp() {
|
||||
useEffect(() => {
|
||||
console.log("\tAppConstants.DEBUG_VIEW: " + AppConstants.DEBUG_VIEW);
|
||||
console.log("\tprocess.env.NODE_ENV: " + process.env.NODE_ENV);
|
||||
console.log("\tReact.version: " + React.version);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<div className={AppConstants.THEME}>
|
||||
<MasterPageBase />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
// You can also log the error to an error reporting service
|
||||
// logErrorToMyService(error, errorInfo);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// You can render any custom fallback UI
|
||||
return <h1>Something went wrong.</h1>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(`Start application: ${AppConstants.APP_NAME}`);
|
||||
init();
|
||||
|
||||
const reactRoot = document.getElementById('main-react-app');
|
||||
const root = ReactDOM.createRoot(reactRoot);
|
||||
root.render(
|
||||
<Provider store={store} >
|
||||
<MainReactApp />
|
||||
</Provider>
|
||||
);
|
100
stub-gui/source/js/SessionWorkerManager.js
Normal file
100
stub-gui/source/js/SessionWorkerManager.js
Normal file
@ -0,0 +1,100 @@
|
||||
const EnumSessionWorker = Object.freeze({
|
||||
MESSAGE: 'SessionWorker.MESSAGE',
|
||||
START: 'SessionWorker.START',
|
||||
CLEAR: 'SessionWorker.CLEAR',
|
||||
TIMEOUT: 'SessionWorker.TIMEOUT',
|
||||
});
|
||||
|
||||
class SessionWorkerManager {
|
||||
/**
|
||||
* worker
|
||||
* @type {(Worker|null)}
|
||||
*/
|
||||
activeWorker = null;
|
||||
/** Milliseconds to expire session */
|
||||
sessionExpiredTimeout = 60000 * 30;
|
||||
setSessionExpiredTimeout(sessionExpiredTimeout){
|
||||
this.sessionExpiredTimeout = sessionExpiredTimeout;
|
||||
}
|
||||
/**
|
||||
* function execute on session timeout
|
||||
* @type {Function}
|
||||
*/
|
||||
expireFunction = null;
|
||||
setExpireFunction(expireFunction){
|
||||
this.expireFunction = expireFunction;
|
||||
}
|
||||
|
||||
name(message){
|
||||
return `[SessionWorkerManager]: ${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the worker and set the expireFunction
|
||||
* @param {Function} expireFunction function execute on session timeout
|
||||
*/
|
||||
start() {
|
||||
if (window.Worker) {
|
||||
console.log(this.name('start Worker conversation'));
|
||||
this.activeWorker = new Worker('sessionWorker.js');
|
||||
this.activeWorker.onmessage = this._messageRecieverHandler.bind(this);
|
||||
|
||||
this._message("SONO WORKER... SESSION WORKER");
|
||||
this._sendData(EnumSessionWorker.START, this.sessionExpiredTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.activeWorker) this._sendData(EnumSessionWorker.CLEAR);
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.activeWorker) {
|
||||
this.activeWorker.terminate();
|
||||
delete this.activeWorker;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Message from Worker
|
||||
* @param {MessageEvent} ev message event
|
||||
*/
|
||||
_messageRecieverHandler(ev){
|
||||
const { type, data } = ev.data;
|
||||
|
||||
switch (type) {
|
||||
case EnumSessionWorker.TIMEOUT:
|
||||
console.log(this.name('session timeout'));
|
||||
if (this.expireFunction) this.expireFunction();
|
||||
this.end();
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(this.name('message recieved'), { type, data });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send string message to active worker
|
||||
* @param {string} message
|
||||
*/
|
||||
_message(message) {
|
||||
this._sendData(EnumSessionWorker.MESSAGE, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to active worker
|
||||
* @param {string} type
|
||||
* @param {*} data
|
||||
*/
|
||||
_sendData(type, data){
|
||||
if (this.activeWorker) {
|
||||
console.log(this.name('send data'), {type, data})
|
||||
this.activeWorker.postMessage({ type, data });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default new SessionWorkerManager();
|
83
stub-gui/source/js/app/AppActions.js
Normal file
83
stub-gui/source/js/app/AppActions.js
Normal file
@ -0,0 +1,83 @@
|
||||
import { i18n } from 'arm-common';
|
||||
import { NotificationManager } from 'arm-core-layouts';
|
||||
|
||||
import { AppConstants } from '../constants/AppConstants.js';
|
||||
import { DOMAINS_PRELOGIN } from '../constants/Domini.js';
|
||||
import OPZIONI from '../constants/Opzioni.js';
|
||||
import AccountActions, { EnumAccountActions } from '../ui/account/AccountActions.js';
|
||||
import Rest from '../webapi/Rest.js';
|
||||
import BaseActions from './BaseActions.js';
|
||||
|
||||
|
||||
export const EnumAppActions = Object.freeze({
|
||||
WINDOW_WIDTH_CHANGED: 'AppActions.WINDOW_WIDTH_CHANGED',
|
||||
NOTIFY_MESSAGE_ADD: 'AppActions.NOTIFY_MESSAGE_ADD',
|
||||
NOTIFY_MESSAGE_DELETE: 'AppActions.NOTIFY_MESSAGE_DELETE',
|
||||
NOTIFY_MESSAGE_CLEAR: 'AppActions.NOTIFY_MESSAGE_CLEAR',
|
||||
LOADER_SHOW: 'AppActions.LOADER_SHOW',
|
||||
LOADER_HIDE: 'AppActions.LOADER_HIDE',
|
||||
LOADER_USER_SHOW: 'AppActions.LOADER_USER_SHOW',
|
||||
LOADER_USER_HIDE: 'AppActions.LOADER_USER_HIDE',
|
||||
LOAD_OPZIONI: 'AppActions.LOAD_OPZIONI',
|
||||
LOAD_DOMINI: 'AppActions.LOAD_DOMINI_APP',
|
||||
LOAD_I18N: 'AppActions.LOAD_I18N',
|
||||
|
||||
LOCATE_CHANGE: 'AppActions.LOCATE_CHANGE',
|
||||
CHANGE_URL: 'AppActions.CHANGE_URL',
|
||||
|
||||
LOADING_DELAY_STREAMS: 'AppActions.app_actions.LOADING_DELAY_STREAMS',
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
export const EnumNotifyType = Object.freeze({
|
||||
Success: 'SUCCESS',
|
||||
Info: 'INFO',
|
||||
Warning: 'WARNING',
|
||||
Danger: 'DANGER'
|
||||
});
|
||||
|
||||
|
||||
class AppActions extends BaseActions {
|
||||
getReducer(getState) {
|
||||
return getState().app;
|
||||
}
|
||||
|
||||
preLoginLoadUtilities() {
|
||||
return function (dispatch) {
|
||||
const utilities = {
|
||||
codiciOpzioni: OPZIONI,
|
||||
codiciDomini: DOMAINS_PRELOGIN
|
||||
};
|
||||
return dispatch(AccountActions.loadUtilities(utilities));
|
||||
}
|
||||
}
|
||||
|
||||
addNotify(tipo, messaggio) {
|
||||
switch (tipo) {
|
||||
case (EnumNotifyType.Info):
|
||||
NotificationManager.info(messaggio);
|
||||
break;
|
||||
case (EnumNotifyType.Success):
|
||||
NotificationManager.success(messaggio);
|
||||
break;
|
||||
case (EnumNotifyType.Warning):
|
||||
NotificationManager.warning(messaggio, { timeOut: 10000 });
|
||||
break;
|
||||
case (EnumNotifyType.Danger):
|
||||
NotificationManager.error(messaggio, { timeOut: 0 });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
deleteNotify(id) {
|
||||
return { type: EnumAppActions.NOTIFY_MESSAGE_DELETE, messageId: id };
|
||||
}
|
||||
|
||||
clearNotify() {
|
||||
return { type: EnumAppActions.NOTIFY_MESSAGE_CLEAR }
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppActions();
|
191
stub-gui/source/js/app/AppReducer.js
Normal file
191
stub-gui/source/js/app/AppReducer.js
Normal file
@ -0,0 +1,191 @@
|
||||
import update from 'immutability-helper';
|
||||
|
||||
import { AppConstants } from '../constants/AppConstants.js';
|
||||
import { EnumAccountActions } from '../ui/account/AccountActions.js';
|
||||
import { EnumAppActions } from './AppActions.js';
|
||||
|
||||
|
||||
const initialState = {
|
||||
locate: AppConstants.DEFAULT_LANG,
|
||||
listOfNotify: null,
|
||||
jsessionId: null,
|
||||
|
||||
environment: null,
|
||||
isSSO: false,
|
||||
serverid: null,
|
||||
|
||||
loadingStack: 0,
|
||||
loadingState: 'hide',
|
||||
|
||||
authorizations: {},
|
||||
|
||||
domini: {},
|
||||
dominiLoaded: false,
|
||||
|
||||
i18nlabels: {},
|
||||
i18nlabelsLoaded: true, // TODO riportare a false e nel loading ri-set to true
|
||||
|
||||
opzioni: {},
|
||||
opzioniLoaded: false,
|
||||
|
||||
loggedOut: false,
|
||||
versionInformation: null,
|
||||
debugObj: null,
|
||||
showPopUp: false,
|
||||
|
||||
alog: null,
|
||||
showAlogPopup: false,
|
||||
|
||||
ritardoCaricamentoFlussi: false
|
||||
};
|
||||
|
||||
const app = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
return initialState
|
||||
}
|
||||
|
||||
let notifyCollection = null;
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case EnumAccountActions.LOGIN_SUCCESS:
|
||||
case EnumAccountActions.SSO:
|
||||
|
||||
let authorizations = action.authorizations;
|
||||
let jsessionId = action.jsessionId;
|
||||
let environment = null;
|
||||
|
||||
let isSSO = false;
|
||||
|
||||
if (action.type === EnumAccountActions.SSO) {
|
||||
isSSO = action.datiUtente.userId !== null && action.datiUtente.userId !== undefined;
|
||||
}
|
||||
|
||||
if (action.datiUtente) {
|
||||
authorizations = action.datiUtente.authorizations;
|
||||
jsessionId = action.datiUtente.jsessionId;
|
||||
environment = action.datiUtente.environment;
|
||||
}
|
||||
|
||||
if (action.user) {
|
||||
authorizations = action.user.authorizations;
|
||||
jsessionId = action.user.jsessionId;
|
||||
}
|
||||
|
||||
let authMap = {};
|
||||
if (authorizations) {
|
||||
authorizations.forEach(element => {
|
||||
authMap[element] = element;
|
||||
});
|
||||
}
|
||||
|
||||
return update(state, {
|
||||
authorizations: { $set: authMap },
|
||||
jsessionId: { $set: jsessionId },
|
||||
environment: { $set: environment },
|
||||
isSSO: { $set: isSSO },
|
||||
listOfNotify: { $set: null }
|
||||
});
|
||||
|
||||
case EnumAccountActions.LOGOUT:
|
||||
return update(state, {
|
||||
loggedOut: { $set: true },
|
||||
datiUtente: { $set: {} }
|
||||
});
|
||||
|
||||
|
||||
case EnumAppActions.NOTIFY_MESSAGE_ADD:
|
||||
if (state.listOfNotify === null) {
|
||||
notifyCollection = new Map();
|
||||
} else {
|
||||
//devo clonare la lista altrimenti non vengono intercettati i cambiamenti
|
||||
notifyCollection = new Map(state.listOfNotify);
|
||||
}
|
||||
|
||||
if (action.messageId === null || action.messageId === undefined) {
|
||||
action.messageId = new Date().getTime();
|
||||
}
|
||||
|
||||
notifyCollection.set(Number(action.messageId), {
|
||||
message: action.message,
|
||||
messageType: action.messageType,
|
||||
messageId: action.messageId
|
||||
});
|
||||
|
||||
return update(state, { listOfNotify: { $set: notifyCollection } });
|
||||
|
||||
case EnumAppActions.NOTIFY_MESSAGE_DELETE:
|
||||
//devo clonare la lista altrimenti non vengono intercettati i cambiamenti
|
||||
notifyCollection = new Map(state.listOfNotify);
|
||||
notifyCollection.delete(action.messageId);
|
||||
|
||||
return update(state, { listOfNotify: { $set: notifyCollection } });
|
||||
|
||||
case EnumAppActions.NOTIFY_MESSAGE_CLEAR:
|
||||
return update(state, { listOfNotify: { $set: null } });
|
||||
|
||||
case EnumAppActions.LOAD_DOMINI:
|
||||
const domini = update(state.domini, { $merge: action.domini });
|
||||
|
||||
return update(state, {
|
||||
domini: { $set: domini },
|
||||
dominiLoaded: { $set: true }
|
||||
});
|
||||
|
||||
case EnumAppActions.LOAD_OPZIONI:
|
||||
const opzioni = update(state.opzioni, { $merge: action.opzioni });
|
||||
|
||||
return update(state, {
|
||||
opzioni: { $set: opzioni },
|
||||
opzioniLoaded: { $set: true }
|
||||
});
|
||||
|
||||
case EnumAppActions.LOAD_I18N:
|
||||
return update(state, {
|
||||
i18nlabels: { $merge: action.i18nlabels }
|
||||
});
|
||||
|
||||
case EnumAppActions.WINDOW_WIDTH_CHANGED:
|
||||
return update(state, { deviceSize: { $set: action.deviceSize } });
|
||||
|
||||
case EnumAppActions.LOADER_SHOW:
|
||||
if (state.loadingState === 'show') {
|
||||
return update(state, { loadingStack: { $set: state.loadingStack + 1 } });
|
||||
}
|
||||
|
||||
return update(state, {
|
||||
loadingState: { $set: "show" },
|
||||
loadingStack: { $set: 1 }
|
||||
});
|
||||
|
||||
case EnumAppActions.LOADER_HIDE:
|
||||
if (state.loadingState === 'hide') {
|
||||
return update(state, {
|
||||
loadingState: { $set: "hide" },
|
||||
loadingStack: { $set: 0 }
|
||||
});
|
||||
}
|
||||
|
||||
if (state.loadingStack > 1) {
|
||||
return update(state, {
|
||||
loadingStack: { $set: state.loadingStack - 1 }
|
||||
});
|
||||
}
|
||||
|
||||
return update(state, {
|
||||
loadingState: { $set: "hide" },
|
||||
loadingStack: { $set: 0 }
|
||||
});
|
||||
|
||||
case EnumAppActions.LOCATE_CHANGE:
|
||||
return update(state, { locate: { $set: action.locate } });
|
||||
|
||||
case EnumAppActions.LOADING_DELAY_STREAMS:
|
||||
return update(state, { ritardoCaricamentoFlussi: { $set: action.ritardoCaricamentoFlussi } });
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default app;
|
41
stub-gui/source/js/app/BaseActions.js
Normal file
41
stub-gui/source/js/app/BaseActions.js
Normal file
@ -0,0 +1,41 @@
|
||||
import { AppConstants } from '../constants/AppConstants.js';
|
||||
import { EnumAppActions } from './AppActions.js';
|
||||
|
||||
|
||||
class BaseActions {
|
||||
constructor() {}
|
||||
|
||||
get AppConstants() {
|
||||
return AppConstants;
|
||||
}
|
||||
|
||||
getErrorMessage(jqxhr, textStatus, error) {
|
||||
let message = null;
|
||||
|
||||
console.log("Request Failed: " + textStatus + ", " + error + ' ' + jqxhr.responseText);
|
||||
if (jqxhr.readyState === 4) {
|
||||
// HTTP error (can be checked by XMLHttpRequest.status and XMLHttpRequest.statusText)
|
||||
message = textStatus + ", " + error + ' ' + jqxhr.responseText;
|
||||
}
|
||||
else if (jqxhr.readyState === 0) {
|
||||
// Network error (i.e. connection refused, access denied due to CORS, etc.)
|
||||
message = textStatus + ", network connection";
|
||||
}
|
||||
else {
|
||||
// something weird is happening
|
||||
message = textStatus + ", " + error + ' ' + jqxhr.responseText;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
showLoader() {
|
||||
return { type: EnumAppActions.LOADER_SHOW }
|
||||
}
|
||||
|
||||
hideLoader() {
|
||||
return { type: EnumAppActions.LOADER_HIDE }
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseActions;
|
18
stub-gui/source/js/constants/AppConstants.js
Normal file
18
stub-gui/source/js/constants/AppConstants.js
Normal file
@ -0,0 +1,18 @@
|
||||
export const AppConstants = Object.freeze({
|
||||
APP_NAME: process.env.DESCRIPTION, //package.json description
|
||||
APP_CODE: process.env.NAME, // package.json name
|
||||
VERSION: process.env.VERSION,
|
||||
|
||||
REST_SERVER: process.env.REST_SERVER,
|
||||
REST_BASE_URL: process.env.REST_BASE_URL,
|
||||
SESSION_EXPIRED_TIMEOUT: process.env.SESSION_EXPIRED_TIMEOUT,
|
||||
KEEP_ALIVE_MINUTES: process.env.KEEP_ALIVE_MINUTES,
|
||||
|
||||
DEFAULT_LANG: process.env.DEFAULT_LANG || 'IT',
|
||||
I18N_CODES: [''],
|
||||
|
||||
AUTHENTICATION_METHOD: process.env.AUTHENTICATION_METHOD,
|
||||
|
||||
DEBUG_VIEW: process.env.DEBUG_VIEW,
|
||||
THEME: process.env.THEME
|
||||
});
|
7
stub-gui/source/js/constants/Domini.js
Normal file
7
stub-gui/source/js/constants/Domini.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** @type {string[]} */
|
||||
const DOMAINS = [];
|
||||
/** @type {string[]} */
|
||||
const DOMAINS_PRELOGIN = [];
|
||||
|
||||
export { DOMAINS_PRELOGIN };
|
||||
export default DOMAINS;
|
10
stub-gui/source/js/constants/Opzioni.js
Normal file
10
stub-gui/source/js/constants/Opzioni.js
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Array of options.
|
||||
*
|
||||
* @type {Array<{ opzione: string, sezione: string }>}
|
||||
*/
|
||||
const OPZIONI = [
|
||||
{ opzione: 'DIVRIF', sezione: 'GLOBALE' }
|
||||
];
|
||||
|
||||
export default OPZIONI;
|
14
stub-gui/source/js/init.js
Normal file
14
stub-gui/source/js/init.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { armundiaInit } from "arm-common";
|
||||
import { setLicenseKey } from "arm-gui-grid-wj";
|
||||
|
||||
//components initialization
|
||||
import { store } from './reducers/store';
|
||||
|
||||
|
||||
export function init(){
|
||||
//connects redux store to arm-common
|
||||
armundiaInit(store, null);
|
||||
|
||||
//set wijmo license key
|
||||
setLicenseKey('armundia-20240430-all,925232318584228#B0Pd55Wavx6bhBnbhNnLyd6cuwWa6NnL9F6dlRXYnBjNzYHZh9yd7dHLt36YukWbp3GbvFGcuF6cuQ7clR7c9NnLlRWZzNXez9CMtVmZjNHLsF6YvxmLtdXatl6bs3WYw9WYz9icnNnLslmdz9SehdXZ4F6ZwYzM6RWYsUXZucmbptmbhJ6bygmLzV6YpZnclNnL4NHduAjNzYHZhNHcixSN68COzEjLx8CMxwSbvNmLvx6bhBnbhNXYzVGdulmLz96YuMXez9CMwZHZhRHL4lmLlNmbhJXdz9Way3GdjlmduoXailXbs46bj9iclZnclt6byJmLl96bsNGdhVHazJXYtxCdp9SbhJXdlRWamF6YuFmYu4WYyVXZklmZuA7ZwYzM6RWYs46bj9Satl6bs3WYw9WYz9CdzVGdzl7cuUGZlN7c9NnLwU6Yud7cs46bj9SYj9WYi3WakVWbuQnbp9Sa6NXLlZmdkFGLsF6YvxmLtdXatl6bs3WYw9WYz9icnNnL4B7Yh9SehdXZ4F6ZwYzM6RWYs46bj9ybs3WYw9WYzF6clRnbp9ycuNmLzl7cuAjd6RWYzxCdp9SYyJXZ4x6b6J7YuYmMmxSbvNmLhlGZuVXbyFmLpFGLsF6YvxmLtdXatl6bs3WYw9WYz9icnNnL4B7Yh9SehdXZ4F6ZwYzM6RWYuc7d7xCdp9SbhJXdlRWamF6YuFmYzl7cu4WYyVXZklmZzl7cuMWMwATYz56brN7dmJGL4VmbusmbhJmbvlGeh9SMwI6YzJWYjBHchdHLt36YukWbp3GbvFGcuF6cuAncvNmLlRWZz9CMlNmb7xSbvNmLhlGZuVXbyFmLpx6bw5WZs46bj9SYj9WYi3WakVWbuQnbp9iMs36YtUmZ6RWYsUXZucmbptmbhJ6bygmLzV6YpZnclNnLlJHcuAjNzYHZhNHcixyNuQjLyMjLycTMs46bj9yYth6cy5mLhdWbtEWcs46bj9ybs3WYw9WYzF6clRnbp9ycuNmLzl7cuAzY6RWY4xSbvNmLhlGZuVXbyFmL4VXbppXYtYXZkxSbvNmLhlGZuVXbyFmLlNmbhJXdzNXYj9WYixCbhN6bs9Sb7lWbp3GbvFGcuF6cuI7Zz9CbpZ7cuAjNzYHZhxSbvNmLvx6bhBnbhNXYzVGdulmLz96YuQ6byBnLwMmdkFGLt36YuEWak9WdtJXYuEDMv5WZkxSbvNmLvx6bhBnbhNXYzVGdulmLz96YuMXez9CMwZHZhNHL4lmLtFmc5VGZpZWYj9WYi9SbhJXdlRWam9iYld7YmxSbvNmLvx6bhBnbhNXYzVGdulmLz96YuQ6byBnLwAndkFGLsF6YvxmLtdXatl6bs3WYw9WYz9icnNnL4B7Yh9CM6MjdkFGLt36YuEWak9WdtJXYuMXZixSMwQGcjZHZhBXYuFTMjNHL4lmLjNmYuk6cjNmYuEDMkJHcyNWbiojIz5GRiwiIsJ7UgAXdvJ7RgEWak9WdtJXQiojIh94QiwiI8IjM4gTN8EzMyMjM5ITOiojIklkIs4nIyYHMyAjMiojIyVmdiwSZzxWYmpjIyNHZisnOiwmbBJye0ICRiwiI34zZpBVaTN5UhpncFdlaxVzRTVme6p6bLdzbyEmQ6N6cOlHcIlDaMZlczwkZoJnaaVnMhhDRFVFcrFHM8hzTwoFSZdTarNUWLpFeRRTUJZjUz4EaZNDbYV7MrsiWFp4V7gENI5mV9d6QyJWTLVTTMFmeSpGZOhHeJ3Eb0B5Ns94Sqh7Y4EUd5VHNjRkWDhmM5cXYmNESPlVRWdmeJtGaoFGdVl4S0JlUZtWYnhjY5Y4NRNGO9FDMM5EULt6KCZzZaJmNXN5RIxGcDhTVClmRuFnNGFUZxpHVNZHVuV4V6U7Ztp6Q7sEOwgGOaFUNylEWL56aTNDSolFbmZVMVVmewgzLlNlWCBlWLZ7V4kDbpBjZvBnU9UWNvEDT5RjaTpnYvYDZ8Vna5smQSZHWYFTe4ETNxQ7V5cDT6UTRQpXYrE4a7FlcUBDdF5EVBlHVzQHOwY7c8VjQIpkI0IyUiwiI7IUNCljNxMjI0ICSiwCMzgzN9MDNzEjM0IicfJye#4Xfd5nIzMEMCJiOiMkIsISZy36Qg2Wbql6ViojIOJyebpjIkJHUiwiIxQzMyEDMgAzM4ADNyAjMiojI4J7QiwiI4lmLlNmbhJXdz9Way3GdjlmduoXailXbuAHZj5SYxxCdp9SZj9WYyV7culmcvR7YpZnL0lmY95mLwR6YtQXY5xSdl9yZul6auFmYvJDauMXZjlmdyV6cuUmcwxSbvNmLhNmbhJ6bpRWZt9CdulmLs36YtUmZ6RWYsQXauQXdtlmeh9iclRmbpZGL4lmLtFmc5VGZpZWYj9WYiNXez9SbhJXdlRWamNXez9CdxADMhNXbvt6chZmYs46bj9SYj9WYi3WakVWbuQnbp9SZmZHZhxSbvNmLhlGZuVXbyFmLyAzbtVGZs46bj9ybs3WYw9WYzF6clRnbp9ycuNmLk3mcw9CM6ZHZhxSbvNmLhlGZuVXbyFmL4AzbtVGZs46bj9SYpRmb55mch9yNwQnbp5mchxSbvNmLvx6bhBnbhNXYzVGdulmLz96YuMXez9CMjZHZhNHL4lmLjNmYuk6cjNmYuEDMsZ7cyNWbswWYj3Gbu46dp5Wavx6bhBnbhNnLyd6cuQ6byBnL9F6dlRXYnBjNzYHZh9yd7dHLt36YuEWak9WdtJXYuYDM49WatJXYs46bj9yYth6cy5mL4lmLlNmbhJXdz9Way3GdjlmduoXailXbuAHch5CckNWLhFHL4VmbusmbhJmbvlGeh9SMwI6YzJ6NjBHchdHL4lmLtFmc5VGZpZWYj9WYiNXez9SbhJXdlRWamNXez9CdxADMhNXbvt6c7ZmYsQXau4WYyVXZklmZhNmbhJmLtFmc5VGZpZmLwFDMwE6ct36azFmZixSbvNmLp5Wavx6bhBnbhNnL4NXZ4NXez9SZkV6czl7cuATblZ6YtUmYppXa6JXZzxCbhN6bs9Sb7lWbp3GbvFGcuF6cuI7Zz9CZvJHcuAjNzYHZhxSbvNmLhlGZuVXbyFmLxADdulWbyFGLt36YukWbp3GbvFGcuF6cuAncvNmLlRWZz9CMtVmZjxSbvNmLvx6bhBnbhNXYzVGdulmLz96YuQ6byBnLwsmdkFGLxADZwZndkFGch9WMxM6csEDMkBHc6RWYwFmbxEzYzxCdp9Cd55Wa0FmLyVGZulmZtYXZkxCdp9SYyJXZ4x6b6J7YuwWY5RncpZHLt36YuIXZ6JXZr3mci9SMw2GcwVHbpZ7cs46bj9SYpRmb55mch9Sau3WbpJHdhBXYj9WYixSbvNmLvx6bhBnbhNXYzVGdulmLz96YuMXez9CMrZHZhRHLt36YuEWak9WdtJXYu8WYtVHasUXZucmbptmbhJ6bygmLzV6YpZnclNnL4NHds46bj9yYth6cy5mL4lmLlNmbhJXdz9Way3GdjlmduoXailXbuAHch5CckNWL4FWds46bj9SYpRmb55mch9CdzVGdhNXbvxSbvNmLhlGZuVXbyFmL4NXZ4RWZtxSbvNmL4VXbppXYu8WYtVHas46bj9Satl6bs3WYw9WYz9CdzVGdzl7cuUGZlN7c9NnLwU6YudHdsUXZucmbptmbhJ6bygmLzV6YpZnclNHL5VmLn9War9WYi3mMo9yclNWa6JXZz9CM6MjdkF6cwJGLsF6YvxmLtdXatl6bs3WYw9WYz9icnNnLk3mcw9SehdXZ4F6ZwYzM6RWYs46bj9SYpRmb55mch9CNxMWan3GbiV6ds46bj9ybs3WYw9WYzF6clRnbp9ycuNmLzl7cuAjd6RWY4xCdp9SZj9WYyV7culmcvR7YpZnL0lmY95mL4FWds46bj9SYpRmb55mch9yMw2WblRGLt36Yu2GbvFGcuF6chNXZ49WauMnbj9yc9NnLwsmdkF6cs46bj9Satl6bs3WYw9WYz9Ccy36YuUGZlNnLw4WZmNWLlJWa0lmdyV6csQXau4WYyVXZklmZhNmbhJ6c9NnLtFmc5VGZpZ6c9NnLjFDMwE6ct36azFmZixCdp9yYjJmLpN7YjJmLxADbvNmcj5GLxADZwtmdkFGch9WMxM6csQXZu9yauFmYu3Wa8FmLzADMkJHcwBXYlJHcuhXYswWYj3Ubg0');
|
||||
}
|
15
stub-gui/source/js/reducers/index.js
Normal file
15
stub-gui/source/js/reducers/index.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { QbLinkReducer, QbReducer } from "arm-core-querybuilder";
|
||||
import { combineReducers } from "redux";
|
||||
|
||||
import app from "../app/AppReducer.js";
|
||||
import account from "../ui/account/AccountReducer.js";
|
||||
|
||||
|
||||
const appReducers = combineReducers({
|
||||
app,
|
||||
account,
|
||||
QbReducer,
|
||||
QbLinkReducer,
|
||||
});
|
||||
|
||||
export default appReducers;
|
15
stub-gui/source/js/reducers/store.js
Normal file
15
stub-gui/source/js/reducers/store.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
|
||||
import { thunk} from 'redux-thunk';
|
||||
import rootReducer from '.';
|
||||
|
||||
import { composeWithDevTools } from '@redux-devtools/extension';
|
||||
|
||||
|
||||
const composeEnhancers = composeWithDevTools({
|
||||
// Specify name here, actionsBlacklist, actionsCreators and other options if needed
|
||||
});
|
||||
|
||||
export const store = createStore(
|
||||
rootReducer,
|
||||
composeEnhancers(applyMiddleware(thunk))
|
||||
);
|
61
stub-gui/source/js/ui/MasterPage.jsx
Normal file
61
stub-gui/source/js/ui/MasterPage.jsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { ApplicationContainer, MasterPageContainer, PageBody, PageContainer, PageHeader, PageHeaderContainer, TopBarRouting } from "arm-core-layouts";
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
|
||||
export function getMasterPageMenuInfo() {
|
||||
return [
|
||||
{ Component: RouteLabelPrinter, pathKey: 'section-1', label: 'Section 1', visible: true, auth: true },
|
||||
{
|
||||
label: 'Section 2', routesInfo: [
|
||||
{ Component: RouteLabelPrinter, pathKey: 'section-2.0', label: 'Section 2.0', visible: true, auth: true },
|
||||
{ Component: RouteLabelPrinter, pathKey: 'section-2.1', label: 'Section 2.1', visible: true, auth: true }
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export default class MasterPage extends PureComponent {
|
||||
state = {
|
||||
menuInfo: getMasterPageMenuInfo(),
|
||||
userMenu: [
|
||||
{
|
||||
label: 'user',
|
||||
className: 'hoverless' //hoverless: unclickable
|
||||
},
|
||||
// {
|
||||
// label: i18n('USER_INFO.LOG_OUT', 'Disconnettiti'),
|
||||
// onClick: () => logoutOnClick && logoutOnClick().then(() => history.replace('logout'))
|
||||
// }
|
||||
]
|
||||
}
|
||||
|
||||
render() {
|
||||
const { menuInfo, userMenu } = this.state;
|
||||
return (
|
||||
<ApplicationContainer>
|
||||
<TopBarRouting
|
||||
menuInfo={menuInfo}
|
||||
userMenu={userMenu}
|
||||
/>
|
||||
</ApplicationContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function RouteLabelPrinter({ routeLabel }) {
|
||||
return (
|
||||
<MasterPageContainer>
|
||||
<PageContainer>
|
||||
<PageHeaderContainer>
|
||||
<PageHeader
|
||||
title={routeLabel}
|
||||
/>
|
||||
</PageHeaderContainer>
|
||||
|
||||
<PageBody>
|
||||
<h1>{routeLabel}</h1>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
</MasterPageContainer>
|
||||
)
|
||||
}
|
53
stub-gui/source/js/ui/MasterPageBase.jsx
Normal file
53
stub-gui/source/js/ui/MasterPageBase.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { NotificationContainer } from "arm-core-layouts";
|
||||
import React, { useEffect } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { HashRouter, Route, Switch } from "react-router-dom";
|
||||
|
||||
import AppActions from "../app/AppActions.js";
|
||||
import { AppConstants } from "../constants/AppConstants.js";
|
||||
import MasterPage from "./MasterPage.jsx";
|
||||
import NotFound from "./NotFoundPage.jsx";
|
||||
import PrivateRoute from "./PrivateRoute.jsx";
|
||||
import Standalone from "./Standalone.jsx";
|
||||
import AliveStatusMessage from "./account/AliveStatusMessage.jsx";
|
||||
import Login from "./account/Login.jsx";
|
||||
import SessionExpired from "./account/SessionExpired.jsx";
|
||||
import Loading from "./shared/Loading.jsx";
|
||||
import Notify from "./shared/Notify.jsx";
|
||||
import TestMasterPage from "./test/TestMasterPage.jsx";
|
||||
|
||||
|
||||
export default function MasterPageBase(props){
|
||||
const { preLoginLoadUtilities } = props;
|
||||
|
||||
useEffect(() => {
|
||||
preLoginLoadUtilities();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<Switch>
|
||||
<Route path="/">
|
||||
<Loading />
|
||||
<Notify />
|
||||
<NotificationContainer />
|
||||
<SessionExpired/>
|
||||
<AliveStatusMessage/>
|
||||
<Switch>
|
||||
<Route path="/login" component={Login} />
|
||||
{AppConstants.DEBUG_VIEW && <Route path="/test" component={TestMasterPage} />}
|
||||
<Route path="/standalone" component={Standalone} />
|
||||
<PrivateRoute path="/" component={MasterPage} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</Route>
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
)
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
preLoginLoadUtilities: () => dispatch(AppActions.preLoginLoadUtilities())
|
||||
})
|
||||
|
||||
MasterPageBase = connect(null, mapDispatchToProps)(MasterPageBase);
|
36
stub-gui/source/js/ui/NotFoundPage.jsx
Normal file
36
stub-gui/source/js/ui/NotFoundPage.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
|
||||
import React from 'react';
|
||||
import agui from 'arm-gui';
|
||||
|
||||
|
||||
class NotFound extends React.Component {
|
||||
constructor(props) {
|
||||
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let url = this.props.history.location.pathname;
|
||||
console.log("[NotFoundPage - new React]PAGE NOT FOUND WITH URL:", url, this.context.router);
|
||||
return (
|
||||
<agui.aCard>
|
||||
{/* <agui.aCardHeader className="stand-alone">
|
||||
<h1>Page not found</h1>
|
||||
</agui.aCardHeader>
|
||||
<agui.aCardBody>
|
||||
<div >
|
||||
<p>The requested URL <b>{'#'}{url}</b> was not found.</p>
|
||||
<p>Please check misspelling and try again.</p>
|
||||
</div>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
</agui.aCardBody> */}
|
||||
</agui.aCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default NotFound;
|
25
stub-gui/source/js/ui/PrivateRoute.jsx
Normal file
25
stub-gui/source/js/ui/PrivateRoute.jsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Route, Redirect } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
export default function PrivateRoute(props) {
|
||||
const { path, utente, component: Component } = props;
|
||||
|
||||
return (
|
||||
<Route path={path} render={(properties) => (
|
||||
utente
|
||||
? <Component {...properties} />
|
||||
: <Redirect to={{
|
||||
pathname: '/login',
|
||||
state: { from: properties.location }
|
||||
}} />
|
||||
)} />
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
utente: state.account.utente
|
||||
});
|
||||
|
||||
PrivateRoute = connect(mapStateToProps)(PrivateRoute);
|
111
stub-gui/source/js/ui/Standalone.jsx
Normal file
111
stub-gui/source/js/ui/Standalone.jsx
Normal file
@ -0,0 +1,111 @@
|
||||
import { ApplicationContainer } from 'arm-core-layouts';
|
||||
import { Routing } from 'arm-router';
|
||||
import React, { PureComponent, useCallback, useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Prompt, Route, Switch, useHistory } from 'react-router-dom';
|
||||
|
||||
import AccountActions from './account/AccountActions.js';
|
||||
import { getMasterPageMenuInfo } from './MasterPage.jsx';
|
||||
|
||||
|
||||
export default class Standalone extends PureComponent {
|
||||
state = { to: '' }
|
||||
|
||||
componentDidMount() {
|
||||
this.initStandalone();
|
||||
AccountActions.getSession().then(this.checkIsLogged);
|
||||
}
|
||||
|
||||
initStandalone() {
|
||||
const { history } = this.props; //route props
|
||||
this.setState(() => ({ to: this.getBasePath() }));
|
||||
history.push('/standalone');
|
||||
}
|
||||
|
||||
getBasePath(){
|
||||
const { pathname } = this.props.location; //route props
|
||||
const crumbs = pathname.split('/');
|
||||
crumbs.splice(0, 1); //rimuovo lo spazio creato dal primo slash
|
||||
|
||||
if (pathname.includes('pk')){
|
||||
return `/${crumbs[0]}/${crumbs[1]}/${crumbs[2]}/${crumbs[3]}/${crumbs[4]}`;
|
||||
}
|
||||
|
||||
return `/${crumbs[0]}/${crumbs[1]}`;
|
||||
}
|
||||
|
||||
checkIsLogged = (utente) => {
|
||||
const {
|
||||
history, match, //route props
|
||||
postLogin //dispatch functions
|
||||
} = this.props;
|
||||
const { path } = match;
|
||||
const { to } = this.state;
|
||||
if (!utente) history.push(`${path}/login`);
|
||||
else {
|
||||
postLogin(utente).then(() => history.push(to));
|
||||
}
|
||||
}
|
||||
|
||||
getPaths() {
|
||||
const { utente } = this.props;
|
||||
|
||||
if (!utente) return null;
|
||||
|
||||
return <Routing routesInfo={getMasterPageMenuInfo()}/>
|
||||
}
|
||||
|
||||
render() {
|
||||
const { utente, opzioni } = this.props;
|
||||
const { path } = this.props.match;
|
||||
const { to, section } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ApplicationContainer>
|
||||
<Switch>
|
||||
<Route path={`${path}/login`} render={(props) => <Login {...props} to={to} />} />
|
||||
{this.getPaths()}
|
||||
{utente && <Route component={NotFound} />}
|
||||
</Switch>
|
||||
</ApplicationContainer>
|
||||
<PromptStandalone />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
utente: state.account.utente,
|
||||
opzioni: state.app.opzioni
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
postLogin: (resolve) => dispatch(AccountActions.postLogin(resolve))
|
||||
});
|
||||
|
||||
Standalone = connect(mapStateToProps, mapDispatchToProps)(Standalone);
|
||||
|
||||
function PromptStandalone(){
|
||||
const history = useHistory();
|
||||
|
||||
const [ nextLocation, setNextLocation ] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (nextLocation) history.push(nextLocation);
|
||||
if (nextLocation !== '') setNextLocation('');
|
||||
}, [nextLocation]);
|
||||
|
||||
const checkIsInStandalone = useCallback((location) => {
|
||||
const pathname = location.pathname;
|
||||
if (pathname.includes('standalone')) return true;
|
||||
|
||||
const newLocation = `/standalone${pathname}`;
|
||||
console.log('[PromptStandalone]: converting location', location, 'to', newLocation);
|
||||
setNextLocation(newLocation);
|
||||
|
||||
return false;
|
||||
}, [history]);
|
||||
|
||||
return <Prompt when={true} message={checkIsInStandalone} />
|
||||
}
|
141
stub-gui/source/js/ui/account/AccountActions.js
Normal file
141
stub-gui/source/js/ui/account/AccountActions.js
Normal file
@ -0,0 +1,141 @@
|
||||
|
||||
import { AppConstants } from '../../constants/AppConstants.js';
|
||||
import SessionWorkerManager from '../../SessionWorkerManager.js';
|
||||
import AppActions, { EnumAppActions } from '../../app/AppActions.js';
|
||||
import BaseActions from '../../app/BaseActions.js';
|
||||
import DOMAINS from '../../constants/Domini.js';
|
||||
import OPZIONI from '../../constants/Opzioni.js';
|
||||
import Rest from '../../webapi/Rest.js';
|
||||
import { NotificationManager } from 'arm-core-layouts';
|
||||
import { i18n } from 'arm-common';
|
||||
import KeepAliveWorkerManager from '../../KeepAliveWorkerManager.js';
|
||||
|
||||
|
||||
export const EnumAccountActions = Object.freeze({
|
||||
LOGIN_SUCCESS: 'EnumAccountActions.LOGIN_SUCCESS',
|
||||
LOGIN_FAILED: 'EnumAccountActions.LOGIN_FAILED',
|
||||
LOGOUT: 'EnumAccountActions.LOGOUT',
|
||||
SSO: 'EnumAccountActions.SSO',
|
||||
UPDATE_FORM: 'EnumAccountActions.UPDATE_FORM',
|
||||
ALIVE_STATUS: 'EnumAccountActions.ALIVE_STATUS',
|
||||
SESSION_EXPIRED: 'account_actions.SESSION_EXPIRED',
|
||||
});
|
||||
|
||||
const BASE_PATH = 'authentication';
|
||||
|
||||
class AccountActions extends BaseActions {
|
||||
getReducer(state) {
|
||||
if (typeof state === 'function') state = state();
|
||||
return state.account;
|
||||
}
|
||||
|
||||
createPath(path) {
|
||||
return `${BASE_PATH}/${path}`;
|
||||
}
|
||||
|
||||
login() {
|
||||
return async (dispatch, getState) => {
|
||||
const { userid, password } = this.getReducer(getState);
|
||||
|
||||
try {
|
||||
const utente = await Rest.postForm(this.createPath('login'), { userid, password, clientVersion: AppConstants.VERSION }, dispatch);
|
||||
|
||||
await dispatch(this.postLogin(utente));
|
||||
|
||||
const username = utente.name || utente.lastName || ' ';
|
||||
|
||||
NotificationManager.success(i18n("APP.ACCOUNT.WELCOME", "Benvenuto #username#!", "username", username));
|
||||
|
||||
return utente;
|
||||
} catch (error) {
|
||||
console.warn("[AccountActions.js]: Authentication failed", error);
|
||||
|
||||
dispatch({ type: EnumAccountActions.LOGIN_FAILED });
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
|
||||
postLogin(resolve) {
|
||||
return async (dispatch) => {
|
||||
console.log("[AccountActions.js]: Authentication success");
|
||||
|
||||
dispatch({ type: EnumAccountActions.LOGIN_SUCCESS, user: resolve });
|
||||
|
||||
console.log("[AccountActions.js]: user language", resolve.codLang);
|
||||
|
||||
KeepAliveWorkerManager.init(
|
||||
(aliveStatus) => dispatch(this.setAliveStatus(aliveStatus)),
|
||||
AppConstants.KEEP_ALIVE_MINUTES
|
||||
);
|
||||
|
||||
await dispatch(this.loadUtilities());
|
||||
|
||||
return resolve;
|
||||
}
|
||||
}
|
||||
|
||||
setAliveStatus(aliveStatus) {
|
||||
return {
|
||||
type: EnumAccountActions.ALIVE_STATUS,
|
||||
aliveStatus: aliveStatus
|
||||
}
|
||||
}
|
||||
|
||||
logout() {
|
||||
return dispatch => Rest.post("authentication/logout", null, dispatch).then(
|
||||
resolve => {
|
||||
dispatch({ type: EnumAccountActions.LOGOUT });
|
||||
return resolve;
|
||||
},
|
||||
reject => reject
|
||||
);
|
||||
}
|
||||
|
||||
loadUtilities(utilities) {
|
||||
console.log("[AccountActions.js]: get OPTIONS, DOMAINS and AP_LANG");
|
||||
|
||||
if (!utilities) {
|
||||
// Utilities di default.
|
||||
utilities = {
|
||||
codiciOpzioni: OPZIONI,
|
||||
codiciDomini: DOMAINS,
|
||||
codiciApLang: AppConstants.I18N_CODES
|
||||
};
|
||||
}
|
||||
|
||||
return (dispatch) => {
|
||||
return Rest.post("utilities/load", utilities, dispatch, null).then(
|
||||
resolve => {
|
||||
dispatch({
|
||||
type: EnumAppActions.LOAD_OPZIONI,
|
||||
opzioni: resolve.opzioni || [],
|
||||
});
|
||||
dispatch({
|
||||
type: EnumAppActions.LOAD_DOMINI,
|
||||
domini: resolve.domini || []
|
||||
});
|
||||
dispatch({
|
||||
type: EnumAppActions.LOAD_I18N,
|
||||
i18nlabels: resolve.apLang
|
||||
});
|
||||
return resolve;
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
updateForm(value, key) {
|
||||
return {
|
||||
type: EnumAccountActions.UPDATE_FORM,
|
||||
value, key
|
||||
}
|
||||
}
|
||||
|
||||
logoutExpiredSession() {
|
||||
return { type: EnumAccountActions.SESSION_EXPIRED }
|
||||
}
|
||||
}
|
||||
|
||||
export default AccountActions = new AccountActions;
|
51
stub-gui/source/js/ui/account/AccountReducer.js
Normal file
51
stub-gui/source/js/ui/account/AccountReducer.js
Normal file
@ -0,0 +1,51 @@
|
||||
|
||||
import update from "immutability-helper";
|
||||
|
||||
import { EnumAccountActions } from "./AccountActions.js";
|
||||
|
||||
|
||||
const initialState = {
|
||||
utente: null,
|
||||
userid: null,
|
||||
password: null,
|
||||
aliveStatus: {
|
||||
application: true,
|
||||
session: true
|
||||
},
|
||||
sessionExpired: false
|
||||
}
|
||||
|
||||
function account(state, action) {
|
||||
if (typeof state === "undefined") {
|
||||
return initialState
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case EnumAccountActions.LOGIN_SUCCESS:
|
||||
console.log("Welcome " + action.user.userId);
|
||||
return update(state, { utente: { $set: action.user } });
|
||||
|
||||
case EnumAccountActions.SSO:
|
||||
return update(state, {
|
||||
userid: { $set: action.datiUtente.userId }
|
||||
});
|
||||
|
||||
case EnumAccountActions.LOGIN_FAILED:
|
||||
case EnumAccountActions.LOGOUT:
|
||||
return update(state, { $set: initialState });
|
||||
|
||||
case EnumAccountActions.UPDATE_FORM:
|
||||
return update(state, { [action.key]: { $set: action.value } });
|
||||
|
||||
case EnumAccountActions.SESSION_EXPIRED:
|
||||
return update(state, { sessionExpired: { $set: true } });
|
||||
|
||||
case EnumAccountActions.ALIVE_STATUS:
|
||||
return update(state, { aliveStatus: { $set: action.aliveStatus } });
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default account;
|
80
stub-gui/source/js/ui/account/AliveStatusMessage.jsx
Normal file
80
stub-gui/source/js/ui/account/AliveStatusMessage.jsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { i18n } from 'arm-common';
|
||||
import agui from 'arm-gui';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
const selectorName = 'stub-gui-alive-status-message';
|
||||
|
||||
export default function AliveStatusMessage(props) {
|
||||
const { aliveStatus } = props;
|
||||
|
||||
const reload = useCallback(() => {
|
||||
location.reload();
|
||||
}, [history])
|
||||
|
||||
const showMessage = !aliveStatus.application || !aliveStatus.session;
|
||||
|
||||
const text = useMemo(() => {
|
||||
if (!aliveStatus.session) {
|
||||
return {
|
||||
header: i18n(`CMA.ALIVE_STATUS.SESSION_EXPIRED`, 'Sessione scaduta'),
|
||||
messages: [
|
||||
i18n(`CMA.ALIVE_STATUS.SESSION_EXPIRED.MSG.USER_DISCONNECTED`, 'Utente disconnesso.'),
|
||||
i18n(`CMA.ALIVE_STATUS.SESSION_EXPIRED.MSG`, 'Per riconnettersi premere il tasto "Ricarica" oppure ricaricare la pagina.')
|
||||
]
|
||||
};
|
||||
}
|
||||
return {
|
||||
header: i18n(`CMA.ALIVE_STATUS.APPLICATION_DOWN`, 'Applicazione non attiva'),
|
||||
messages: [
|
||||
i18n(`CMA.ALIVE_STATUS.APPLICATION_DOWN.NOT_AVAILABLE`, 'Applicazione momentaneamente non disponibile.'),
|
||||
i18n(`CMA.ALIVE_STATUS.APPLICATION_DOWN.MSG`, 'La preghiamo di contattare l\'amministratore, oppure ricaricare la pagina.')
|
||||
]
|
||||
};
|
||||
}, [aliveStatus]);
|
||||
|
||||
return (
|
||||
<agui.PopupDialog
|
||||
show={showMessage}
|
||||
className={selectorName}
|
||||
addBackDrop
|
||||
onClose={reload}
|
||||
>
|
||||
<agui.PopupDialog.Header>
|
||||
{text.header}
|
||||
</agui.PopupDialog.Header>
|
||||
|
||||
<agui.PopupDialog.Body>
|
||||
<p>
|
||||
{text.messages.map((message, index) => (
|
||||
index === text.messages.length - 1 ?
|
||||
<React.Fragment key={`${selectorName}-${index}`}>{message}</React.Fragment> :
|
||||
<React.Fragment key={`${selectorName}-${index}`}>
|
||||
{message}
|
||||
|
||||
<br />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</p>
|
||||
</agui.PopupDialog.Body>
|
||||
|
||||
<agui.PopupDialog.Footer>
|
||||
<div style={{ marginLeft: 'auto' }} />
|
||||
|
||||
<agui.aButton
|
||||
bs='primary'
|
||||
onClick={reload}
|
||||
>
|
||||
{i18n(`CMA.SESSION_EXPIRED.RELOAD`, 'Ricarica')}
|
||||
</agui.aButton>
|
||||
</agui.PopupDialog.Footer>
|
||||
</agui.PopupDialog>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
aliveStatus: state.account.aliveStatus
|
||||
})
|
||||
|
||||
AliveStatusMessage = connect(mapStateToProps)(AliveStatusMessage);
|
5
stub-gui/source/js/ui/account/AliveStatusMessage.less
Normal file
5
stub-gui/source/js/ui/account/AliveStatusMessage.less
Normal file
@ -0,0 +1,5 @@
|
||||
@aliveStatusMessagePrefix: cma-alive-status-message;
|
||||
|
||||
.@{aliveStatusMessagePrefix} {
|
||||
width: min(100%, 40rem);
|
||||
}
|
108
stub-gui/source/js/ui/account/Login.jsx
Normal file
108
stub-gui/source/js/ui/account/Login.jsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { i18n } from 'arm-common';
|
||||
import { HBox, LoginContainer, VBox } from 'arm-core-layouts';
|
||||
import agui, { CoolValidator } from "arm-gui";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { AppConstants } from "../../constants/AppConstants.js";
|
||||
import AccountActions from "./AccountActions.js";
|
||||
|
||||
|
||||
export default class Login extends React.Component {
|
||||
state = {
|
||||
useridRule: {
|
||||
required: i18n("APP.LOGIN.USER", "Nome utente")
|
||||
},
|
||||
passwordRule: {
|
||||
required: i18n("APP.LOGIN.PSW", "Password")
|
||||
},
|
||||
otherErrors: null
|
||||
}
|
||||
|
||||
login = async () => {
|
||||
const { login } = this.props;
|
||||
|
||||
try {
|
||||
await login();
|
||||
this.goThrough();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
goThrough = () => {
|
||||
const { history, to } = this.props;
|
||||
history.push({ pathname: to || "/", state: { showCollapse: true } });
|
||||
}
|
||||
|
||||
goToTest = async () => {
|
||||
const { history } = this.props;
|
||||
|
||||
history.push({ pathname: "/test" });
|
||||
}
|
||||
|
||||
onEnter = (e) => e.key === 'Enter' && this.login()
|
||||
|
||||
render() {
|
||||
const { userid, password, updateForm } = this.props;
|
||||
const { useridRule, passwordRule, otherErrors } = this.state;
|
||||
|
||||
return (
|
||||
<LoginContainer header footer={`V. ${AppConstants.VERSION}`} >
|
||||
<CoolValidator otherErrors={otherErrors} >
|
||||
<VBox>
|
||||
<agui.CoolTextInput
|
||||
onKeyUp={this.onEnter}
|
||||
required
|
||||
id="userid"
|
||||
autocomplete="on"
|
||||
label={i18n("APP.LOGIN.USER", "Nome utente")}
|
||||
placeholder={i18n("APP.LOGIN.USER.PLACEHOLDER", "Inserisci il nome utente")}
|
||||
value={userid}
|
||||
validationRule={useridRule}
|
||||
onChange={updateForm}
|
||||
/>
|
||||
|
||||
<agui.CoolTextInput
|
||||
onKeyUp={this.onEnter}
|
||||
required
|
||||
id="password"
|
||||
type="password"
|
||||
autocomplete="on"
|
||||
label={i18n("APP.LOGIN.PSW", "Password")}
|
||||
placeholder={i18n("APP.LOGIN.PSW.PLACEHOLDER", "Inserisci la password")}
|
||||
value={password}
|
||||
validationRule={passwordRule}
|
||||
onChange={updateForm}
|
||||
/>
|
||||
|
||||
<HBox style={{ justifyContent: 'flex-end' }}>
|
||||
<agui.aButton
|
||||
bs="secondary"
|
||||
visible={!!AppConstants.DEBUG_VIEW}
|
||||
onClick={this.goToTest}
|
||||
icon='fas fa-bug'
|
||||
/>
|
||||
|
||||
<agui.aButton
|
||||
bs="primary"
|
||||
type="submit"
|
||||
onClick={this.login}
|
||||
> {i18n("APP.LOGIN.LOGIN", "Accedi")} </agui.aButton>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</CoolValidator>
|
||||
</LoginContainer>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
userid: state.account.userid,
|
||||
password: state.account.password,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
updateForm: (value, field) => dispatch(AccountActions.updateForm(value, field)),
|
||||
login: () => dispatch(AccountActions.login()),
|
||||
});
|
||||
|
||||
Login = connect(mapStateToProps, mapDispatchToProps)(Login);
|
50
stub-gui/source/js/ui/account/Logout.jsx
Normal file
50
stub-gui/source/js/ui/account/Logout.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { i18n } from "arm-common";
|
||||
import agui from "arm-gui";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { AppConstants } from "../../constants/AppConstants.js";
|
||||
import AccountActions from "./AccountActions.js";
|
||||
|
||||
|
||||
export default class Logout extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
this.props.logout();
|
||||
}
|
||||
|
||||
back = () => {
|
||||
this.props.history.push("login");
|
||||
location.reload();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<LoginContainer header footer={`V. ${AppConstants.VERSIONE}`}>
|
||||
<agui.aCard>
|
||||
<agui.aCardHeader>
|
||||
{i18n("V2.LOGOUT.CARD_TIT.USER_LOGGED_OUT", "Utente Disconnesso")}
|
||||
</agui.aCardHeader>
|
||||
|
||||
<agui.aCardBody>
|
||||
<p>{i18n("V2.LOGOUT.MESS_LOG.SESSION_HAS_BEEN_CLOSED.", "Session has been closed.")}</p>
|
||||
<p>{i18n("V2.LOGOUT.MESS_LOG.TO_WORK_ON_THE_APPLICATION", "To work on the application click on the \"home\" button")}</p>
|
||||
</agui.aCardBody>
|
||||
|
||||
<agui.aCardFooter>
|
||||
<agui.aButton
|
||||
bs="primary"
|
||||
type="submit"
|
||||
onClick={this.back}
|
||||
> {i18n("V2.LOGOUT.BUTTON_TIT_LOG.HOME", "HOME")} </agui.aButton>
|
||||
</agui.aCardFooter>
|
||||
</agui.aCard>
|
||||
</LoginContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
logout: () => dispatch(AccountActions.logout()),
|
||||
});
|
||||
|
||||
Logout = connect(null, mapDispatchToProps)(Logout);
|
52
stub-gui/source/js/ui/account/SessionExpired.jsx
Normal file
52
stub-gui/source/js/ui/account/SessionExpired.jsx
Normal file
@ -0,0 +1,52 @@
|
||||
import { i18n } from 'arm-common';
|
||||
import agui from 'arm-gui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
const getI18n = (code, desc, ...args) => i18n(`ADV.SESSION_EXPIRED.${code}`, desc, ...args)
|
||||
|
||||
export default function SessionExpired(props) {
|
||||
const { sessionExpired } = props;
|
||||
|
||||
const reload = useCallback(() => {
|
||||
location.reload();
|
||||
}, [history])
|
||||
|
||||
return (
|
||||
<agui.PopupDialog
|
||||
show={sessionExpired}
|
||||
addBackDrop
|
||||
onClose={reload}
|
||||
>
|
||||
<agui.PopupDialog.Header>
|
||||
{getI18n('EXPIRED_SESSION', 'Sessione scaduta')}
|
||||
</agui.PopupDialog.Header>
|
||||
|
||||
<agui.PopupDialog.Body>
|
||||
<p>
|
||||
{getI18n('USER_DISCONNECTED', 'Utente disconnesso.')}
|
||||
<br />
|
||||
{getI18n('EXPIRED_SESSION_MESSAGE', 'Per riconnettersi premere il tasto "Ricarica" oppure ricaricare la pagina.')}
|
||||
</p>
|
||||
</agui.PopupDialog.Body>
|
||||
|
||||
<agui.PopupDialog.Footer>
|
||||
<div style={{ marginLeft: 'auto' }}/>
|
||||
|
||||
<agui.aButton
|
||||
bs='primary'
|
||||
onClick={reload}
|
||||
>
|
||||
{getI18n('RELOAD', 'Ricarica')}
|
||||
</agui.aButton>
|
||||
</agui.PopupDialog.Footer>
|
||||
</agui.PopupDialog>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
sessionExpired: state.account.sessionExpired
|
||||
})
|
||||
|
||||
SessionExpired = connect(mapStateToProps)(SessionExpired);
|
40
stub-gui/source/js/ui/settings.jsx
Normal file
40
stub-gui/source/js/ui/settings.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import AppConstants from '../constants/AppConstants';
|
||||
|
||||
|
||||
class Settings extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.componentName = "[Settings.jsx] ";
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(this.componentName + 'render');
|
||||
let properties = Object.getOwnPropertyNames(AppConstants);
|
||||
|
||||
let rows = properties.map(function (key) {
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td>{key}</td>
|
||||
<td>{AppConstants[key]}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Settings</h1>
|
||||
|
||||
<table className="table table-bordered table-striped">
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default Settings;
|
30
stub-gui/source/js/ui/shared/Global.js
Normal file
30
stub-gui/source/js/ui/shared/Global.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { DateFormatter } from "arm-common";
|
||||
|
||||
export function scrollToId(id, yOffset) {
|
||||
const element = document.querySelector("#" + id);
|
||||
if (element) {
|
||||
//const yOffset = -75;
|
||||
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
||||
} else {
|
||||
setTimeout(() => this.scrollToId(id, yOffset), 100);
|
||||
}
|
||||
}
|
||||
|
||||
export function objectIsEmpty(obj) {
|
||||
for (let property in obj) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function addTsToFileName(fileName){
|
||||
const dateString = DateFormatter.formatDate(new Date(), '_YYYYMMDD-HHmmss')
|
||||
const ind = fileName.indexOf('.');
|
||||
if (ind === -1) {
|
||||
fileName = fileName + dateString;
|
||||
} else {
|
||||
const preName = fileName.substring(0, ind);
|
||||
const postName = fileName.substring(ind, fileName.length);
|
||||
fileName = preName + dateString + postName;
|
||||
}
|
||||
return fileName;
|
||||
}
|
36
stub-gui/source/js/ui/shared/Loading.jsx
Normal file
36
stub-gui/source/js/ui/shared/Loading.jsx
Normal file
@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
function Spinner(props){
|
||||
return (
|
||||
<svg className="spinner" viewBox="0 0 50 50">
|
||||
<circle className="path" cx="25" cy="25" r="20" fill="none" strokeWidth="5"></circle>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
module.exports.Spinner = Spinner;
|
||||
|
||||
class Loading extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.componentName = "[Loading.jsx] ";
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(this.componentName + 'render');
|
||||
return (
|
||||
<div className={ "loading " + this.props.loadingState }>
|
||||
<Spinner/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
loadingState: state.app.loadingState,
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, null)(Loading);
|
53
stub-gui/source/js/ui/shared/Loading.less
Normal file
53
stub-gui/source/js/ui/shared/Loading.less
Normal file
@ -0,0 +1,53 @@
|
||||
.loading {
|
||||
position: fixed;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
z-index: -1000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: rotate 2s linear infinite;
|
||||
z-index: 2;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
& .path {
|
||||
stroke: #3e76c8;
|
||||
stroke-linecap: round;
|
||||
animation: dash 1.5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1, 150;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 90, 150;
|
||||
stroke-dashoffset: -35;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 90, 150;
|
||||
stroke-dashoffset: -124;
|
||||
}
|
||||
}
|
65
stub-gui/source/js/ui/shared/Notify.jsx
Normal file
65
stub-gui/source/js/ui/shared/Notify.jsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import AppActions from '../../app/AppActions.js';
|
||||
|
||||
|
||||
class Notify extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.componentLog = (text, elem) => console.log("[Notify.jsx]: " + text, elem ? elem : null);
|
||||
this.closeNotifies = this.closeNotifies.bind(this);
|
||||
}
|
||||
|
||||
closeNotifies(){
|
||||
let item = null;
|
||||
for (let key of this.props.listOfNotify.keys()) {
|
||||
item = this.props.listOfNotify.get(key)
|
||||
this.props.closeNotify(item.messageId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
this.componentLog("render", this);
|
||||
|
||||
const { listOfNotify, closeNotify } = this.props;
|
||||
|
||||
let myClass = "alert hidden";
|
||||
let message = "";
|
||||
let onClick = null;
|
||||
|
||||
if (listOfNotify && listOfNotify !== null && listOfNotify.size > 0){
|
||||
let item = null;
|
||||
message = [];
|
||||
for (let key of listOfNotify.keys()) {
|
||||
item = listOfNotify.get(key)
|
||||
message.push(<div key={key} className={"alert-" + item.messageType.toLowerCase()}>{item.message}</div>);
|
||||
}
|
||||
myClass = "alert";
|
||||
onClick = this.closeNotifies;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="notify">
|
||||
<div className={myClass} role="alert">
|
||||
<button type="button" className="close" onClick={onClick}><i className="fa fa-times"/></button>
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
listOfNotify: state.app.listOfNotify
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
closeNotify: (index) => dispatch(AppActions.deleteNotify(index))
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Notify);
|
36
stub-gui/source/js/ui/shared/Notify.less
Normal file
36
stub-gui/source/js/ui/shared/Notify.less
Normal file
@ -0,0 +1,36 @@
|
||||
/* Usa alert e close di bootstrap*/
|
||||
.notify {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100000;
|
||||
|
||||
div.alert {
|
||||
opacity: 1;
|
||||
max-height: 100%;
|
||||
transition: all 1s ease-in-out;
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
&> * {
|
||||
transition: padding 0.5s ease-in-out;
|
||||
padding:.75rem 1.25rem;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
&> * { padding: 0; }
|
||||
filter: alpha(opacity=0);
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
button {
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
100
stub-gui/source/js/ui/shared/charts/Am5.jsx
Normal file
100
stub-gui/source/js/ui/shared/charts/Am5.jsx
Normal file
@ -0,0 +1,100 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export const am5ClassName = 'armu_ai_chatbot-am5';
|
||||
|
||||
export function useAm5() {
|
||||
const [am5, setAm5] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
function check() {
|
||||
if (window.am5) {
|
||||
window.am5.ready(() => setAm5(window.am5));
|
||||
} else setTimeout(check, 100);
|
||||
}
|
||||
check();
|
||||
}, []);
|
||||
|
||||
return am5;
|
||||
}
|
||||
|
||||
let am5Colors = null;
|
||||
export function useAm5Colors() {
|
||||
const am5 = useAm5();
|
||||
|
||||
if (!am5Colors && am5) {
|
||||
am5Colors = colors.map((color) => am5.color(color));
|
||||
}
|
||||
|
||||
return am5Colors;
|
||||
}
|
||||
|
||||
const roots = {};
|
||||
export function getRoot(id) {
|
||||
let root = roots[id];
|
||||
if (root) root.dispose();
|
||||
|
||||
root = am5.Root.new(id);
|
||||
roots[id] = root;
|
||||
|
||||
root.setThemes([
|
||||
am5themes_Animated.new(root)
|
||||
]);
|
||||
|
||||
root.locale = am5locales_it_IT;
|
||||
root.numberFormatter.set("numberFormat", "#,###.00");
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
export const colors = [
|
||||
'#4CC9C3',
|
||||
'#3D9AA0',
|
||||
'#1B3353',
|
||||
'#637081',
|
||||
'#A790A5',
|
||||
'#EBE1E2',
|
||||
'#EEE8DC',
|
||||
'#D4B483'
|
||||
];
|
||||
|
||||
export function ChartContainer(props) {
|
||||
const { id, className } = props;
|
||||
|
||||
const chartRef = useRef();
|
||||
const [style, setStyle] = useState(null);
|
||||
useEffect(() => {
|
||||
if (chartRef.current) {
|
||||
setStyle({
|
||||
backgroundColor: findFirstBackgroundColor(chartRef.current),
|
||||
...props.style
|
||||
});
|
||||
}
|
||||
}, [chartRef, props.style]);
|
||||
|
||||
return (
|
||||
<div id={id} style={style} className={`${am5ClassName} ${className}`.trimEnd()} ref={chartRef} />
|
||||
)
|
||||
}
|
||||
|
||||
ChartContainer.defaultProps = {
|
||||
id: '',
|
||||
className: '',
|
||||
style: {}
|
||||
}
|
||||
|
||||
function findFirstBackgroundColor(element) {
|
||||
if (element) {
|
||||
const backgroundColor = getComputedStyle(element)?.backgroundColor;
|
||||
|
||||
if (backgroundColor !== "rgba(0, 0, 0, 0)") {
|
||||
return backgroundColor;
|
||||
}
|
||||
|
||||
if (element.parentElement) {
|
||||
return findFirstBackgroundColor(element.parentElement);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
19
stub-gui/source/js/ui/shared/charts/Am5.less
Normal file
19
stub-gui/source/js/ui/shared/charts/Am5.less
Normal file
@ -0,0 +1,19 @@
|
||||
@am5Prefix: armu_ai_chatbot-am5;
|
||||
|
||||
.@{am5Prefix} {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
min-width: 17rem;
|
||||
min-height: 17rem;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 5rem;
|
||||
height: 1.8rem;
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
184
stub-gui/source/js/ui/shared/charts/barChart/BarChart.jsx
Normal file
184
stub-gui/source/js/ui/shared/charts/barChart/BarChart.jsx
Normal file
@ -0,0 +1,184 @@
|
||||
import { Utils } from 'arm-common';
|
||||
import p from 'prop-types';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ChartContainer, useAm5, useAm5Colors, getRoot } from '../Am5.jsx';
|
||||
import { AutoSize } from 'arm-core-layouts';
|
||||
|
||||
|
||||
const classNamePrefix = 'armu_ai_chatbot-bar-chart';
|
||||
|
||||
export default function BarChart(props) {
|
||||
const { id, xAxis, yAxis, series, data } = props;
|
||||
|
||||
const [chartId] = useState(id || Utils.uuid(classNamePrefix));
|
||||
const am5 = useAm5();
|
||||
const am5Colors = useAm5Colors();
|
||||
const allValues = useMemo(() => {
|
||||
if (data?.length) {
|
||||
if (series?.length) {
|
||||
const values = [];
|
||||
for (let item of data) {
|
||||
for (let serie of series) {
|
||||
if (!isNaN(item[serie.yAxis])) {
|
||||
values.push(item[serie.yAxis]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
} else {
|
||||
return data.map(d => d[yAxis]);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}, [data, series, yAxis]);
|
||||
|
||||
const minValue = Math.min(...allValues);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (am5 && data) {
|
||||
const root = getRoot(chartId);
|
||||
|
||||
const chart = root.container.children.push(
|
||||
am5xy.XYChart.new(root, {
|
||||
layout: root.verticalLayout
|
||||
})
|
||||
);
|
||||
|
||||
const yAxisAm5 = chart.yAxes.push(
|
||||
am5xy.CategoryAxis.new(root, {
|
||||
categoryField: xAxis,
|
||||
renderer: am5xy.AxisRendererY.new(root, {
|
||||
inversed: true,
|
||||
cellStartLocation: 0.1,
|
||||
cellEndLocation: 0.9
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
yAxisAm5.data.setAll(data);
|
||||
|
||||
const xAxisAm5 = chart.xAxes.push(
|
||||
am5xy.ValueAxis.new(root, {
|
||||
renderer: am5xy.AxisRendererX.new(root, {
|
||||
strokeOpacity: 0.1
|
||||
}),
|
||||
min: minValue < 0 ? minValue : 0,
|
||||
})
|
||||
);
|
||||
|
||||
chart.set("colors", am5.ColorSet.new(root, {
|
||||
colors: am5Colors
|
||||
}));
|
||||
|
||||
if (series?.length){
|
||||
const legend = chart.children.push(am5.Legend.new(root, {
|
||||
centerX: am5.p50,
|
||||
x: am5.p50,
|
||||
}));
|
||||
|
||||
series.forEach(addSeries.bind({ root, chart, xAxisAm5, yAxisAm5, xAxis, data }));
|
||||
|
||||
legend.data.setAll(chart.series.values);
|
||||
} else if (yAxis){
|
||||
addSeries.call({ root, chart, xAxisAm5, yAxisAm5, xAxis, data }, { yAxis });
|
||||
}
|
||||
|
||||
const cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
|
||||
behavior: "zoomY"
|
||||
}));
|
||||
cursor.lineY.set("forceHidden", true);
|
||||
cursor.lineX.set("forceHidden", true);
|
||||
}
|
||||
}, [am5, allValues]);
|
||||
|
||||
const height = useMemo(() => calcHeight(data, series), [data, series]);
|
||||
|
||||
return (
|
||||
<ChartContainer id={chartId} style={{ height }} className={classNamePrefix} />
|
||||
);
|
||||
}
|
||||
|
||||
BarChart.defaultProps = {
|
||||
id: '',
|
||||
xAxis: '',
|
||||
yAxis: '',
|
||||
series: [],
|
||||
data: null
|
||||
}
|
||||
|
||||
BarChart.propTypes = {
|
||||
id: p.string,
|
||||
xAxis: p.string.isRequired,
|
||||
yAxis: p.string,
|
||||
series: p.arrayOf(p.shape({
|
||||
name: p.string,
|
||||
yAxis: p.string
|
||||
})),
|
||||
data: p.arrayOf(p.object).isRequired
|
||||
}
|
||||
|
||||
function addSeries(seriesConfig) {
|
||||
const { root, chart, xAxisAm5, yAxisAm5, xAxis, data } = this;
|
||||
|
||||
const { name, yAxis } = seriesConfig;
|
||||
|
||||
const series = chart.series.push(
|
||||
am5xy.ColumnSeries.new(root, {
|
||||
name,
|
||||
minBulletDistance: 10,
|
||||
xAxis: xAxisAm5,
|
||||
yAxis: yAxisAm5,
|
||||
valueXField: yAxis,
|
||||
categoryYField: xAxis,
|
||||
sequencedInterpolation: true,
|
||||
tooltip: am5.Tooltip.new(root, {
|
||||
pointerOrientation: "horizontal",
|
||||
labelText: "[bold]{name}[/]\n{categoryY}: {valueX}"
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
series.columns.template.setAll({
|
||||
height: am5.p100,
|
||||
strokeOpacity: 0
|
||||
});
|
||||
|
||||
|
||||
series.bullets.push(function () {
|
||||
return am5.Bullet.new(root, {
|
||||
locationX: 1,
|
||||
locationY: 0.5,
|
||||
sprite: am5.Label.new(root, {
|
||||
centerY: am5.p50,
|
||||
text: "{valueX}",
|
||||
populateText: true
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
series.bullets.push(function () {
|
||||
return am5.Bullet.new(root, {
|
||||
locationX: 1,
|
||||
locationY: 0.5,
|
||||
sprite: am5.Label.new(root, {
|
||||
centerX: am5.p100,
|
||||
centerY: am5.p50,
|
||||
text: "{name}",
|
||||
fill: am5.color(0xffffff),
|
||||
populateText: true
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
series.data.setAll(data);
|
||||
series.columns.template.setAll({ cornerRadiusBR: 5, cornerRadiusTR: 5, strokeOpacity: 0 });
|
||||
series.appear();
|
||||
}
|
||||
|
||||
function calcHeight(data, series) {
|
||||
const lineHeight = AutoSize.convertRemToPixels(3);
|
||||
const barHeight = AutoSize.convertRemToPixels(4);
|
||||
|
||||
return (lineHeight * (data.length + 1)) + (barHeight * ((series?.length || 0) + 1)) + AutoSize.convertRemToPixels(5);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
@barChartPrefix: armu_ai_chatbot-bar-chart;
|
||||
|
||||
.@{barChartPrefix} {
|
||||
height: unset;
|
||||
}
|
126
stub-gui/source/js/ui/shared/charts/lineChart/LineChart.jsx
Normal file
126
stub-gui/source/js/ui/shared/charts/lineChart/LineChart.jsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { Utils } from 'arm-common';
|
||||
import p from 'prop-types';
|
||||
import React, { useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { ChartContainer, getRoot, useAm5, useAm5Colors } from '../Am5.jsx';
|
||||
|
||||
|
||||
const classNamePrefix = 'armu_ai_chatbot-line-chart';
|
||||
|
||||
export default function LineChart(props) {
|
||||
const { id, xAxis, yAxis, series, data } = props;
|
||||
|
||||
const [chartId] = useState(id || Utils.uuid(classNamePrefix));
|
||||
const am5 = useAm5();
|
||||
const am5Colors = useAm5Colors();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (am5 && data) {
|
||||
const root = getRoot(chartId);
|
||||
|
||||
const chart = root.container.children.push(
|
||||
am5xy.XYChart.new(root, {
|
||||
panX: true,
|
||||
panY: true,
|
||||
layout: root.verticalLayout
|
||||
})
|
||||
);
|
||||
|
||||
const cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
|
||||
behavior: "none"
|
||||
}));
|
||||
cursor.lineY.set("visible", false);
|
||||
|
||||
|
||||
const xAxisAm5 = chart.xAxes.push(
|
||||
am5xy.ValueAxis.new(root, {
|
||||
renderer: am5xy.AxisRendererX.new(root, {
|
||||
minGridDistance: 50
|
||||
}),
|
||||
tooltip: am5.Tooltip.new(root, {})
|
||||
})
|
||||
);
|
||||
|
||||
const yAxisAm5 = chart.yAxes.push(
|
||||
am5xy.ValueAxis.new(root, {
|
||||
renderer: am5xy.AxisRendererY.new(root, {}),
|
||||
})
|
||||
);
|
||||
|
||||
chart.set("colors", am5.ColorSet.new(root, {
|
||||
colors: am5Colors
|
||||
}));
|
||||
|
||||
if (series?.length){
|
||||
const legend = chart.children.push(am5.Legend.new(root, {
|
||||
centerX: am5.p50,
|
||||
x: am5.p50,
|
||||
}));
|
||||
|
||||
series.forEach(addSeries.bind({ root, chart, legend, xAxisAm5, yAxisAm5, xAxis, data }));
|
||||
|
||||
legend.data.setAll(chart.series.values);
|
||||
} else if (yAxis){
|
||||
addSeries.call({ root, chart, xAxisAm5, yAxisAm5, xAxis, data }, { yAxis });
|
||||
}
|
||||
}
|
||||
}, [am5, data]);
|
||||
|
||||
return (
|
||||
<ChartContainer id={chartId} className={classNamePrefix} />
|
||||
);
|
||||
}
|
||||
|
||||
LineChart.defaultProps = {
|
||||
id: '',
|
||||
xAxis: '',
|
||||
yAxis: '',
|
||||
series: [],
|
||||
data: null
|
||||
}
|
||||
|
||||
LineChart.propTypes = {
|
||||
id: p.string,
|
||||
xAxis: p.string.isRequired,
|
||||
yAxis: p.string,
|
||||
series: p.arrayOf(p.shape({
|
||||
name: p.string,
|
||||
yAxis: p.string
|
||||
})).isRequired,
|
||||
data: p.arrayOf(p.object).isRequired
|
||||
}
|
||||
|
||||
function addSeries(seriesConfig) {
|
||||
const { root, chart, xAxisAm5, yAxisAm5, xAxis, data } = this;
|
||||
|
||||
const { name, yAxis } = seriesConfig;
|
||||
|
||||
const tooltip = am5.Tooltip.new(root, {
|
||||
labelText: "{valueY}",
|
||||
});
|
||||
|
||||
const series = chart.series.push(
|
||||
am5xy.LineSeries.new(root, {
|
||||
name,
|
||||
minBulletDistance: 10,
|
||||
xAxis: xAxisAm5,
|
||||
yAxis: yAxisAm5,
|
||||
valueXField: xAxis,
|
||||
valueYField: yAxis,
|
||||
tooltip
|
||||
})
|
||||
);
|
||||
|
||||
series.bullets.push(function () {
|
||||
return am5.Bullet.new(root, {
|
||||
sprite: am5.Circle.new(root, {
|
||||
radius: 6,
|
||||
fill: series.get("fill"),
|
||||
stroke: root.interfaceColors.get("background"),
|
||||
strokeWidth: 2,
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
series.data.setAll(data);
|
||||
}
|
73
stub-gui/source/js/ui/shared/charts/pieChart/PieChart.jsx
Normal file
73
stub-gui/source/js/ui/shared/charts/pieChart/PieChart.jsx
Normal file
@ -0,0 +1,73 @@
|
||||
import { Utils } from 'arm-common';
|
||||
import p from 'prop-types';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { HBox } from 'arm-core-layouts';
|
||||
import { ChartContainer, getRoot, useAm5, useAm5Colors } from '../Am5.jsx';
|
||||
import PieChartLegend from './PieChartLegend.jsx';
|
||||
|
||||
|
||||
const classNamePrefix = 'armu_ai_chatbot-pie-chart';
|
||||
|
||||
export default function PieChart(props) {
|
||||
const { id, xAxis, yAxis } = props;
|
||||
|
||||
const [chartId] = useState(id || Utils.uuid(classNamePrefix));
|
||||
const am5 = useAm5();
|
||||
const am5Colors = useAm5Colors();
|
||||
const data = useMemo(() => {
|
||||
if (props.data?.length){
|
||||
const data = [...props.data];
|
||||
data.sort((a, b) => b[yAxis] - a[yAxis]);
|
||||
return data;
|
||||
}
|
||||
return [];
|
||||
}, [props.data])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (am5 && data) {
|
||||
const root = getRoot(chartId);
|
||||
|
||||
const chart = root.container.children.push(am5percent.PieChart.new(root, {
|
||||
layout: root.verticalLayout,
|
||||
innerRadius: am5.percent(85)
|
||||
}));
|
||||
|
||||
const series = chart.series.push(am5percent.PieSeries.new(root, {
|
||||
valueField: yAxis,
|
||||
categoryField: xAxis
|
||||
}));
|
||||
|
||||
series.labels.template.set("visible", false);
|
||||
series.ticks.template.set("visible", false);
|
||||
|
||||
series.set("colors", am5.ColorSet.new(root, {
|
||||
colors: am5Colors
|
||||
}));
|
||||
|
||||
series.data.setAll(data);
|
||||
}
|
||||
}, [am5, data]);
|
||||
|
||||
return (
|
||||
<HBox className={classNamePrefix}>
|
||||
<ChartContainer id={chartId} className={`${classNamePrefix}-chart`}/>
|
||||
|
||||
<PieChartLegend enableTotal xAxis={xAxis} yAxis={yAxis} data={data} />
|
||||
</HBox>
|
||||
);
|
||||
}
|
||||
|
||||
PieChart.defaultProps = {
|
||||
id: '',
|
||||
xAxis: '',
|
||||
yAxis: '',
|
||||
data: null
|
||||
}
|
||||
|
||||
PieChart.propTypes = {
|
||||
id: p.string,
|
||||
xAxis: p.string.isRequired,
|
||||
yAxis: p.string.isRequired,
|
||||
data: p.arrayOf(p.object).isRequired
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
@pieChartPrefix: armu_ai_chatbot-pie-chart;
|
||||
|
||||
.@{pieChartPrefix} {
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import { NumberFormatter } from 'arm-common';
|
||||
import { HBox, VBox } from 'arm-core-layouts';
|
||||
import p from 'prop-types';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { colors } from '../Am5.jsx';
|
||||
|
||||
|
||||
const classNamePrefix = 'armu_ai_chatbot-pie-chart-legend';
|
||||
|
||||
export default function PieChartLegend(props) {
|
||||
const { data, xAxis, yAxis, enableTotal } = props;
|
||||
|
||||
const total = useMemo(() => {
|
||||
if (enableTotal && data?.length) {
|
||||
return data.reduce((total, item) => total + (item[yAxis] || 0), 0);
|
||||
}
|
||||
return null;
|
||||
},[data, enableTotal]);
|
||||
|
||||
if (!data || !data?.length) return null;
|
||||
|
||||
return (
|
||||
<VBox className={`${classNamePrefix}`}>
|
||||
{data.map((item, index) => (
|
||||
<HBox key={`${classNamePrefix}-${item[xAxis]}-${item[yAxis]}`} className={`${classNamePrefix}-item`}>
|
||||
<span style={{ backgroundColor: colors[index] }} className={`${classNamePrefix}-color-ref`}/>
|
||||
|
||||
<span>{item[xAxis]}</span>
|
||||
|
||||
<span className={`${classNamePrefix}-value`}>{NumberFormatter.percent(item[yAxis])}</span>
|
||||
</HBox>
|
||||
))}
|
||||
{total && (
|
||||
<>
|
||||
<span className={`${classNamePrefix}-divisor`}/>
|
||||
|
||||
<HBox className={`${classNamePrefix}-item`}>
|
||||
<span className={`${classNamePrefix}-color-ref`}/>
|
||||
|
||||
<span>Total</span>
|
||||
|
||||
<span className={`${classNamePrefix}-value`}>{NumberFormatter.percent(total)}</span>
|
||||
</HBox>
|
||||
</>
|
||||
)}
|
||||
</VBox>
|
||||
);
|
||||
}
|
||||
|
||||
PieChartLegend.defaultProps = {
|
||||
className: '',
|
||||
children: null
|
||||
}
|
||||
|
||||
PieChartLegend.propTypes = {
|
||||
className: p.string,
|
||||
children: p.node
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
@pieChartLegendPrefix: armu_ai_chatbot-pie-chart-legend;
|
||||
|
||||
.@{pieChartLegendPrefix} {
|
||||
width: auto;
|
||||
flex-grow: 2;
|
||||
max-height: 100%;
|
||||
min-width: 20rem;
|
||||
overflow-y: auto;
|
||||
|
||||
&-item {
|
||||
align-items: center;
|
||||
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&-color-ref {
|
||||
margin-left: 1rem;
|
||||
width: 2px;
|
||||
height: 17px;
|
||||
top: 6px;
|
||||
left: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&-value {
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&-divisor {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #BFBFBF;
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
import { DateFormatter, Utils } from 'arm-common';
|
||||
import p from 'prop-types';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ChartContainer, useAm5, useAm5Colors, getRoot } from '../Am5.jsx';
|
||||
import { AutoSize } from 'arm-core-layouts';
|
||||
|
||||
|
||||
const classNamePrefix = 'armu_ai_chatbot-stacked-chart';
|
||||
|
||||
/**
|
||||
* Generates a stacked chart using the provided data and configuration.
|
||||
*
|
||||
* @param {Object} props - The configuration object for the chart.
|
||||
* @param {string} props.id - The unique identifier for the chart.
|
||||
* @param {string} props.xAxis - The field to use for the x-axis.
|
||||
* @param {string} props.yAxis - The field to use for the y-axis.
|
||||
* @param {Array} props.series - The series configuration for the chart.
|
||||
* @param {Array} props.data - The data to be displayed on the chart.
|
||||
* @return {ReactElement} The rendered stacked chart component.
|
||||
*/
|
||||
export default function StackedChart(props) {
|
||||
const { id, xAxis, series, isDateXAxis } = props;
|
||||
|
||||
const [chartId] = useState(id || Utils.uuid(classNamePrefix));
|
||||
const am5 = useAm5();
|
||||
const am5Colors = useAm5Colors();
|
||||
const data = useMemo(() => {
|
||||
if (isDateXAxis) {
|
||||
return props.data.map((d) => ({...d, [xAxis]: DateFormatter.formatDate(d[xAxis], "DD/MM/YYYY") }))
|
||||
}
|
||||
return props.data;
|
||||
}, [props.data])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (am5 && data) {
|
||||
const root = getRoot(chartId);
|
||||
|
||||
const chart = root.container.children.push(
|
||||
am5xy.XYChart.new(root, {
|
||||
layout: root.verticalLayout
|
||||
})
|
||||
);
|
||||
|
||||
const xRenderer = am5xy.AxisRendererX.new(root, {});
|
||||
|
||||
const xAxisAm5 = chart.xAxes.push(
|
||||
am5xy.CategoryAxis.new(root, {
|
||||
categoryField: xAxis,
|
||||
renderer: xRenderer,
|
||||
tooltip: am5.Tooltip.new(root, {})
|
||||
})
|
||||
);
|
||||
|
||||
xRenderer.grid.template.setAll({
|
||||
location: 1
|
||||
})
|
||||
|
||||
xAxisAm5.data.setAll(data);
|
||||
|
||||
const yAxisAm5 = chart.yAxes.push(
|
||||
am5xy.ValueAxis.new(root, {
|
||||
min: 0,
|
||||
max: 100,
|
||||
renderer: am5xy.AxisRendererY.new(root, {
|
||||
strokeOpacity: 0.1
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
chart.set("colors", am5.ColorSet.new(root, {
|
||||
colors: am5Colors
|
||||
}));
|
||||
|
||||
if (series?.length) {
|
||||
const legend = chart.children.push(am5.Legend.new(root, {
|
||||
centerX: am5.p50,
|
||||
x: am5.p50,
|
||||
}));
|
||||
|
||||
series.forEach(addSeries.bind({ root, chart, xAxisAm5, yAxisAm5, xAxis, data }));
|
||||
|
||||
legend.data.setAll(chart.series.values);
|
||||
}
|
||||
}
|
||||
}, [am5, data]);
|
||||
|
||||
return (
|
||||
<ChartContainer id={chartId} className={classNamePrefix} />
|
||||
);
|
||||
}
|
||||
|
||||
StackedChart.defaultProps = {
|
||||
id: '',
|
||||
xAxis: '',
|
||||
series: [],
|
||||
data: null
|
||||
}
|
||||
|
||||
StackedChart.propTypes = {
|
||||
id: p.string,
|
||||
xAxis: p.string.isRequired,
|
||||
series: p.arrayOf(p.shape({
|
||||
name: p.string,
|
||||
yAxis: p.string
|
||||
})).isRequired,
|
||||
data: p.arrayOf(p.object).isRequired
|
||||
}
|
||||
|
||||
function addSeries(seriesConfig) {
|
||||
const { root, chart, xAxisAm5, yAxisAm5, xAxis, data } = this;
|
||||
|
||||
const { name, yAxis } = seriesConfig;
|
||||
|
||||
const series = chart.series.push(
|
||||
am5xy.ColumnSeries.new(root, {
|
||||
name,
|
||||
stacked: true,
|
||||
xAxis: xAxisAm5,
|
||||
yAxis: yAxisAm5,
|
||||
categoryXField: xAxis,
|
||||
valueYField: yAxis
|
||||
})
|
||||
);
|
||||
|
||||
series.columns.template.setAll({
|
||||
tooltipText: "[bold]{name}[/]: {valueY}",
|
||||
tooltipY: am5.percent(10)
|
||||
});
|
||||
|
||||
series.bullets.push(function () {
|
||||
return am5.Bullet.new(root, {
|
||||
sprite: am5.Label.new(root, {
|
||||
text: "{valueY}",
|
||||
fill: root.interfaceColors.get("alternativeText"),
|
||||
centerY: am5.p50,
|
||||
centerX: am5.p50,
|
||||
populateText: true
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
series.data.setAll(data);
|
||||
addRoundedCorners(series, chart, xAxisAm5);
|
||||
series.appear();
|
||||
}
|
||||
|
||||
//TODO doesnt work
|
||||
function addRoundedCorners(series, chart, xAxisAm5) {
|
||||
series.columns.template.adapters.add("cornerRadiusTL", function (radius, target) {
|
||||
return isTop.call({ chart, series, xAxisAm5 }, target.dataItem) ? 5 : 0;
|
||||
});
|
||||
series.columns.template.adapters.add("cornerRadiusTR", function (radius, target) {
|
||||
return isTop.call({ chart, series, xAxisAm5 }, target.dataItem) ? 5 : 0;
|
||||
});
|
||||
}
|
||||
function getCategoryDataItems(dataItem) {
|
||||
const index = this.xAxisAm5.categoryToIndex(dataItem.get("categoryX"));
|
||||
const items = [];
|
||||
this.chart.series.each((series) => {
|
||||
if (series.get("visible")) {
|
||||
const item = this.series.dataItems[index];
|
||||
if (item.get("valueY") >= 0) {
|
||||
items.push(item);
|
||||
}
|
||||
else {
|
||||
items.unshift(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
function isTop(dataItem) {
|
||||
const items = getCategoryDataItems.call(this, dataItem);
|
||||
if (dataItem.get("valueY") >= 0) {
|
||||
return items.indexOf(dataItem) == (items.length - 1);
|
||||
}
|
||||
else {
|
||||
return items.indexOf(dataItem) == 0;
|
||||
}
|
||||
}
|
163
stub-gui/source/js/ui/shared/charts/timeChart/TimeChart.jsx
Normal file
163
stub-gui/source/js/ui/shared/charts/timeChart/TimeChart.jsx
Normal file
@ -0,0 +1,163 @@
|
||||
import { Utils } from 'arm-common';
|
||||
import p from 'prop-types';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ChartContainer, getRoot, useAm5, useAm5Colors } from '../Am5.jsx';
|
||||
|
||||
|
||||
const classNamePrefix = 'armu_ai_chatbot-time-chart';
|
||||
|
||||
export default function TimeChart(props) {
|
||||
const { id, xAxis, yAxis, series, data } = props;
|
||||
|
||||
const [chartId] = useState(id || Utils.uuid(classNamePrefix));
|
||||
const am5 = useAm5();
|
||||
const am5Colors = useAm5Colors();
|
||||
|
||||
const allValues = useMemo(() => {
|
||||
if (data?.length) {
|
||||
if (series?.length) {
|
||||
const values = [];
|
||||
for (let item of data) {
|
||||
for (let serie of series) {
|
||||
if (!isNaN(item[serie.yAxis])) {
|
||||
values.push(item[serie.yAxis]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
} else {
|
||||
return data.map(d => d[yAxis]);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}, [data, series, yAxis]);
|
||||
|
||||
const minValue = Math.min(...allValues);
|
||||
const maxValue = Math.max(...allValues);
|
||||
const largerValue = Math.max(maxValue, Math.abs(minValue));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (am5 && data) {
|
||||
const root = getRoot(chartId);
|
||||
|
||||
const chart = root.container.children.push(
|
||||
am5xy.XYChart.new(root, {
|
||||
panX: true,
|
||||
panY: true,
|
||||
wheelX: "panX",
|
||||
wheelY: "zoomX",
|
||||
pinchZoomX: true,
|
||||
layout: root.verticalLayout
|
||||
})
|
||||
);
|
||||
|
||||
const cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
|
||||
behavior: "none"
|
||||
}));
|
||||
cursor.lineY.set("visible", false);
|
||||
|
||||
const xAxisAm5 = chart.xAxes.push(
|
||||
am5xy.DateAxis.new(root, {
|
||||
maxDeviation: 0.2,
|
||||
baseInterval: {
|
||||
timeUnit: "day",
|
||||
count: 1
|
||||
},
|
||||
renderer: am5xy.AxisRendererX.new(root, {}),
|
||||
tooltip: am5.Tooltip.new(root, {})
|
||||
})
|
||||
);
|
||||
|
||||
const yAxisAm5 = chart.yAxes.push(
|
||||
am5xy.ValueAxis.new(root, {
|
||||
renderer: am5xy.AxisRendererY.new(root, {
|
||||
pan: "zoom"
|
||||
}),
|
||||
min: minValue - (largerValue * 0.1),
|
||||
max: maxValue + (largerValue * 0.1),
|
||||
strictMinMax: true
|
||||
})
|
||||
);
|
||||
|
||||
chart.set("colors", am5.ColorSet.new(root, {
|
||||
colors: am5Colors
|
||||
}));
|
||||
|
||||
if (series?.length) {
|
||||
const legend = chart.children.push(am5.Legend.new(root, {
|
||||
centerX: am5.p50,
|
||||
x: am5.p50,
|
||||
}));
|
||||
|
||||
series.forEach(addSeries.bind({ root, chart, xAxisAm5, yAxisAm5, xAxis, data }));
|
||||
|
||||
legend.data.setAll(chart.series.values);
|
||||
} else if (yAxis) {
|
||||
addSeries.call({ root, chart, xAxisAm5, yAxisAm5, xAxis, data }, { yAxis });
|
||||
}
|
||||
|
||||
chart.set("scrollbarX", am5.Scrollbar.new(root, {
|
||||
orientation: "horizontal"
|
||||
}));
|
||||
}
|
||||
}, [am5, allValues]);
|
||||
|
||||
return (
|
||||
<ChartContainer id={chartId} className={classNamePrefix} />
|
||||
);
|
||||
}
|
||||
|
||||
TimeChart.defaultProps = {
|
||||
id: '',
|
||||
xAxis: '',
|
||||
yAxis: '',
|
||||
series: [],
|
||||
data: null
|
||||
}
|
||||
|
||||
TimeChart.propTypes = {
|
||||
id: p.string,
|
||||
xAxis: p.string.isRequired,
|
||||
yAxis: p.string,
|
||||
series: p.arrayOf(p.shape({
|
||||
name: p.string,
|
||||
yAxis: p.string
|
||||
})),
|
||||
data: p.arrayOf(p.object).isRequired
|
||||
}
|
||||
|
||||
function addSeries(seriesConfig) {
|
||||
const { root, chart, xAxisAm5, yAxisAm5, xAxis, data } = this;
|
||||
|
||||
const { name, yAxis } = seriesConfig;
|
||||
|
||||
const tooltip = am5.Tooltip.new(root, {
|
||||
labelText: "{valueY}",
|
||||
});
|
||||
|
||||
const series = chart.series.push(
|
||||
am5xy.LineSeries.new(root, {
|
||||
name,
|
||||
minBulletDistance: 10,
|
||||
xAxis: xAxisAm5,
|
||||
yAxis: yAxisAm5,
|
||||
valueXField: xAxis,
|
||||
valueYField: yAxis,
|
||||
tooltip
|
||||
})
|
||||
);
|
||||
|
||||
series.bullets.push(function () {
|
||||
return am5.Bullet.new(root, {
|
||||
sprite: am5.Circle.new(root, {
|
||||
radius: 6,
|
||||
fill: series.get("fill"),
|
||||
stroke: root.interfaceColors.get("background"),
|
||||
strokeWidth: 2,
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
series.data.setAll(data);
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
@timeChartPrefix: armu_ai_chatbot-time-chart;
|
||||
|
||||
.@{timeChartPrefix} {
|
||||
min-width: 5rem;
|
||||
min-height: 24rem;
|
||||
}
|
26
stub-gui/source/js/ui/test/TestMasterPage.jsx
Normal file
26
stub-gui/source/js/ui/test/TestMasterPage.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { i18n } from 'arm-common';
|
||||
import { ApplicationContainer, MasterPageContainer, PageBody, PageContainer, PageHeader, PageHeaderContainer, TopBarRouting } from "arm-core-layouts";
|
||||
import ArmGuiDemo from 'arm-gui/dist/demo/js/ui/Demo.jsx';
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
|
||||
const selectorName = 'stub-test-master-page';
|
||||
|
||||
export default class TestMasterPage extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
menuInfo: [
|
||||
{ pathKey: 'arm-gui', pageTitle: i18n('STUB.AGUI', 'arm-gui'), Component: ArmGuiDemo },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ApplicationContainer>
|
||||
<TopBarRouting menuInfo={this.state.menuInfo} />
|
||||
</ApplicationContainer>
|
||||
);
|
||||
}
|
||||
}
|
133
stub-gui/source/js/webapi/Rest.js
Normal file
133
stub-gui/source/js/webapi/Rest.js
Normal file
@ -0,0 +1,133 @@
|
||||
import { WebRest } from 'arm-common';
|
||||
import { setRestContainer as setQBRestContainer } from 'arm-core-querybuilder';
|
||||
import { setRestContainer as setSPRestContainer } from 'arm-gui-grid-wj';
|
||||
|
||||
import SessionWorkerManager from '../SessionWorkerManager.js';
|
||||
import AppActions, { EnumNotifyType } from '../app/AppActions.js';
|
||||
import { AppConstants } from '../constants/AppConstants.js';
|
||||
import { store } from '../reducers/store.js';
|
||||
import AccountActions from '../ui/account/AccountActions.js';
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} minutes
|
||||
* @returns {number} minutes in milliseconds
|
||||
*/
|
||||
function getMinutesInMilliseconds(minutes){
|
||||
return 1000 * 60 * minutes;
|
||||
}
|
||||
|
||||
function onSessionExpired() {
|
||||
const dispatch = store.dispatch;
|
||||
dispatch(AccountActions.logoutExpiredSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* classe Rest basata su WebRest
|
||||
*/
|
||||
class Rest extends WebRest {
|
||||
constructor() {
|
||||
/** @type {import('arm-common').SessionConfigOptions} */
|
||||
const sessionConf = {};
|
||||
if (window.Worker){
|
||||
SessionWorkerManager.setSessionExpiredTimeout(getMinutesInMilliseconds(AppConstants.SESSION_EXPIRED_TIMEOUT));
|
||||
SessionWorkerManager.setExpireFunction(onSessionExpired);
|
||||
sessionConf.externalSessionTimeoutHandler = SessionWorkerManager.clear.bind(SessionWorkerManager);
|
||||
} else {
|
||||
sessionConf.sessionExpiredHandlerTimeout = AppConstants.SESSION_EXPIRED_TIMEOUT;
|
||||
sessionConf.sessionExpiredHandler = onSessionExpired;
|
||||
}
|
||||
|
||||
super(
|
||||
AppConstants.REST_SERVER, //
|
||||
AppConstants.REST_BASE_URL, //
|
||||
["account/login", "authentication/loginbo"], // to skip
|
||||
|
||||
// recover jsessionId
|
||||
() => {
|
||||
let app = store.getState().app;
|
||||
return app.jsessionId;
|
||||
},
|
||||
|
||||
// loading, show loader
|
||||
(dispatch) => dispatch(AppActions.showLoader()),
|
||||
|
||||
// end load, hide loader
|
||||
(dispatch) => dispatch(AppActions.hideLoader()),
|
||||
|
||||
// handle error message
|
||||
(dispatch, message, statusCode, options, stacktrace) => AppActions.addNotify(EnumNotifyType.Danger, message),
|
||||
|
||||
// do logout
|
||||
(dispatch) => dispatch({ type: 'LOGOUT' }),
|
||||
|
||||
// path iniziale per chiamate stubs
|
||||
"./stubs/",
|
||||
|
||||
// recover serverId
|
||||
() => {
|
||||
let app = store.getState().app;
|
||||
return app.serverid;
|
||||
},
|
||||
sessionConf, // Sessionconf
|
||||
"cid", // default JBOSS string identifier for conversation id
|
||||
{
|
||||
useRequestId: true,
|
||||
base64: true,
|
||||
zip: true,
|
||||
minKbToZip: 500
|
||||
} // attiva encoding in base64 del body delle post.
|
||||
)
|
||||
|
||||
setQBRestContainer(this);
|
||||
setSPRestContainer(this);
|
||||
}
|
||||
|
||||
qs(fields) {
|
||||
let q = "";
|
||||
let sep = "?";
|
||||
Object.keys(fields || {}).forEach((k) => {
|
||||
let v = fields[k];
|
||||
if (v === null || v === undefined) return;
|
||||
v = encodeURIComponent(v);
|
||||
q += sep + k + "=" + v;
|
||||
sep = "&";
|
||||
});
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
parseQueryString(queryString) {
|
||||
const queryStringTruncated = queryString.substring(queryString.indexOf('?') + 1);
|
||||
const unparsedParams = queryStringTruncated.split('&');
|
||||
const params = {};
|
||||
unparsedParams.forEach((param) => {
|
||||
const [key, value] = param.split('=');
|
||||
params[key] = decodeURIComponent(value);
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
saveByteArray(fileName, resultByte, mimeType) {
|
||||
if (!resultByte) return;
|
||||
const bytes = new Uint8Array(resultByte); // pass your byte response to this constructor
|
||||
const blob = new Blob([bytes], { type: mimeType });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = fileName;
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
|
||||
base64ToBlob(base64, type = "application/octet-stream") {
|
||||
const binStr = atob(base64);
|
||||
const len = binStr.length;
|
||||
const arr = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr[i] = binStr.charCodeAt(i);
|
||||
}
|
||||
return new Blob([arr], { type: type });
|
||||
}
|
||||
}
|
||||
|
||||
export default new Rest;
|
53
stub-gui/source/less/main.less
Normal file
53
stub-gui/source/less/main.less
Normal file
@ -0,0 +1,53 @@
|
||||
@import (less) "~arm-gui/dist/less/base.less";
|
||||
@import (less) "~arm-file-uploader/dist/less/base.less";
|
||||
@import (less) "~arm-gui-grid-wj/dist/less/base.less";
|
||||
@import (less) "~arm-core-layouts/dist/less/base.less";
|
||||
@import (less) "~arm-core-querybuilder/dist/less/base.less";
|
||||
@import (less) "~arm-splitter/dist/less/base.less";
|
||||
@import (less) "~arm-router/dist/less/base.less";
|
||||
@import (less) "~arm-visual-component/dist/less/base.less";
|
||||
@import (less) "~arm-themes/dist/@@THEME@@/base.less";
|
||||
|
||||
|
||||
//#region Bootstrap generics
|
||||
*,
|
||||
::after,
|
||||
::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin: 0;
|
||||
}
|
||||
//#endregion Bootstrap generics
|
34
stub-gui/source/libs/KeepAliveWorker.js
Normal file
34
stub-gui/source/libs/KeepAliveWorker.js
Normal file
@ -0,0 +1,34 @@
|
||||
function getCurrentTime(){
|
||||
return Intl.DateTimeFormat('it-IT', {hour: '2-digit', minute: '2-digit', second: '2-digit'}).format(new Date());
|
||||
}
|
||||
|
||||
function mt(m){
|
||||
return `${getCurrentTime()} - [KeepAliveWorker]: ${m}`;
|
||||
}
|
||||
|
||||
onmessage = function (ev) {
|
||||
const { type, data } = ev.data;
|
||||
|
||||
if (type === 'START_KEEP_ALIVE') {
|
||||
const { url, minutes } = data;
|
||||
|
||||
async function fetchKeepAlive() {
|
||||
console.log(mt(`calling ${url}`));
|
||||
|
||||
fetch(url).then(
|
||||
(response) => response.json().then(
|
||||
(json) => postMessage({
|
||||
type: 'KEEP_ALIVE_RESULT',
|
||||
data: json
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
console.log(mt('starting keep alive'), data);
|
||||
fetchKeepAlive();
|
||||
setInterval(fetchKeepAlive, minutes * 60000);
|
||||
} else {
|
||||
postMessage({ type, data: 'OK' });
|
||||
}
|
||||
}
|
3
stub-gui/source/libs/amcharts-5.5.2/deps/markerjs2.js
Normal file
3
stub-gui/source/libs/amcharts-5.5.2/deps/markerjs2.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
stub-gui/source/libs/amcharts-5.5.2/deps/pdfmake.js
Normal file
3
stub-gui/source/libs/amcharts-5.5.2/deps/pdfmake.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/deps/pdfmake.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/deps/pdfmake.js.map
Normal file
File diff suppressed because one or more lines are too long
3
stub-gui/source/libs/amcharts-5.5.2/deps/xlsx.js
Normal file
3
stub-gui/source/libs/amcharts-5.5.2/deps/xlsx.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/deps/xlsx.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/deps/xlsx.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/index.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/index.js
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/ar.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/ar.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/ar.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/ar.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/bg_BG.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/bg_BG.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/bg_BG.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/bg_BG.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/bs_BA.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/bs_BA.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/bs_BA.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/bs_BA.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/ca_ES.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/ca_ES.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/ca_ES.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/ca_ES.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/cs_CZ.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/cs_CZ.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/cs_CZ.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/cs_CZ.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/da_DK.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/da_DK.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/da_DK.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/da_DK.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/de_CH.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/de_CH.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/de_CH.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/de_CH.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/de_DE.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/de_DE.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/de_DE.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/de_DE.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/el_GR.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/el_GR.js
Normal file
File diff suppressed because one or more lines are too long
1
stub-gui/source/libs/amcharts-5.5.2/locales/el_GR.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/el_GR.js.map
Normal file
File diff suppressed because one or more lines are too long
2
stub-gui/source/libs/amcharts-5.5.2/locales/en.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/en.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunk_am5=self.webpackChunk_am5||[]).push([[6933],{3012:function(e,n,u){u.r(n),u.d(n,{am5locales_en:function(){return o}});const o=u(3100).Z}},function(e){var n=(3012,e(e.s=3012)),u=window;for(var o in n)u[o]=n[o];n.__esModule&&Object.defineProperty(u,"__esModule",{value:!0})}]);
|
||||
//# sourceMappingURL=en.js.map
|
1
stub-gui/source/libs/amcharts-5.5.2/locales/en.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/en.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"locales/en.js","mappings":"qJACO,MAAMA,E,QAAgB,C","sources":["webpack://@amcharts/amcharts5/./tmp/webpack/locales/en.js"],"sourcesContent":["import m from \"./../../../dist/es2015/locales/en.js\";\nexport const am5locales_en = m;"],"names":["am5locales_en"],"sourceRoot":""}
|
2
stub-gui/source/libs/amcharts-5.5.2/locales/en_CA.js
Normal file
2
stub-gui/source/libs/amcharts-5.5.2/locales/en_CA.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunk_am5=self.webpackChunk_am5||[]).push([[2012],{2570:function(e,a,o){o.r(a),o.d(a,{am5locales_en_CA:function(){return t}});const t={_decimalSeparator:".",_thousandSeparator:",",_percentPrefix:null,_percentSuffix:"%",_date_millisecond:"mm:ss SSS",_date_millisecond_full:"HH:mm:ss SSS",_date_second:"hh:mm:ss a",_date_second_full:"hh:mm:ss a",_date_minute:"hh:mm a",_date_minute_full:"hh:mm a - MMM dd, yyyy",_date_hour:"hh:mm a",_date_hour_full:"hh:mm a - MMM dd, yyyy",_date_day:"MMM dd",_date_day_full:"MMM dd, yyyy",_date_week:"ww",_date_week_full:"MMM dd, yyyy",_date_month:"MMM",_date_month_full:"MMM, yyyy",_date_year:"yyyy",_duration_millisecond:"SSS",_duration_second:"ss",_duration_minute:"mm",_duration_hour:"hh",_duration_day:"dd",_duration_week:"ww",_duration_month:"MM",_duration_year:"yyyy",_era_ad:"AD",_era_bc:"BC",A:"",P:"",AM:"",PM:"","A.M.":"","P.M.":"",January:"",February:"",March:"",April:"",May:"",June:"",July:"",August:"",September:"",October:"",November:"",December:"",Jan:"",Feb:"",Mar:"",Apr:"","May(short)":"May",Jun:"",Jul:"",Aug:"",Sep:"",Oct:"",Nov:"",Dec:"",Sunday:"",Monday:"",Tuesday:"",Wednesday:"",Thursday:"",Friday:"",Saturday:"",Sun:"",Mon:"",Tue:"",Wed:"",Thu:"",Fri:"",Sat:"",_dateOrd:function(e){let a="th";if(e<11||e>13)switch(e%10){case 1:a="st";break;case 2:a="nd";break;case 3:a="rd"}return a},Play:"",Stop:"","Zoom Out":"",Legend:"","Press ENTER to toggle":"",Loading:"",Home:"",Chart:"","Serial chart":"","X/Y chart":"","Pie chart":"","Gauge chart":"","Radar chart":"","Sankey diagram":"","Chord diagram":"","Flow diagram":"","TreeMap chart":"",Series:"","Candlestick Series":"","Column Series":"","Line Series":"","Pie Slice Series":"","X/Y Series":"",Map:"","Press ENTER to zoom in":"","Press ENTER to zoom out":"","Use arrow keys to zoom in and out":"","Use plus and minus keys on your keyboard to zoom in and out":"",Export:"",Image:"",Data:"",Print:"","Press ENTER to open":"","Press ENTER to print.":"","Press ENTER to export as %1.":"","(Press ESC to close this message)":"","Image Export Complete":"","Export operation took longer than expected. Something might have gone wrong.":"","Saved from":"",PNG:"",JPG:"",GIF:"",SVG:"",PDF:"",JSON:"",CSV:"",XLSX:"",HTML:"","Use TAB to select grip buttons or left and right arrows to change selection":"","Use left and right arrows to move selection":"","Use left and right arrows to move left selection":"","Use left and right arrows to move right selection":"","Use TAB select grip buttons or up and down arrows to change selection":"","Use up and down arrows to move selection":"","Use up and down arrows to move lower selection":"","Use up and down arrows to move upper selection":"","From %1 to %2":"","From %1":"","To %1":"","No parser available for file: %1":"","Error parsing file: %1":"","Unable to load file: %1":"","Invalid date":""}}},function(e){var a=(2570,e(e.s=2570)),o=window;for(var t in a)o[t]=a[t];a.__esModule&&Object.defineProperty(o,"__esModule",{value:!0})}]);
|
||||
//# sourceMappingURL=en_CA.js.map
|
1
stub-gui/source/libs/amcharts-5.5.2/locales/en_CA.js.map
Normal file
1
stub-gui/source/libs/amcharts-5.5.2/locales/en_CA.js.map
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user