update project with new architecture

This commit is contained in:
2023-04-13 07:46:35 +02:00
parent d0a3b10cfe
commit 67a02c33a2
333 changed files with 3764 additions and 1254 deletions

111
asupsetup/ASUPconfig.jsold Executable file
View File

@@ -0,0 +1,111 @@
'use strict';
const path = require( 'path' );
const fs = require( 'fs' );
const config = {};
if( !process.env.NODE_ENV ) process.env.NODE_ENV = "dev"
console.log( 'apxtrib process.env.NODE_ENV: ', process.env.NODE_ENV );
// VOIR l'ancien fichier de cnfig au cas ou il manque des chemins dans config
// voir la doc http://gitlab.ndda.fr/philc/apiamaildigitfr/wikis/InstallConf
config.prod = {
mainDir: __dirname,
tmp: path.join( __dirname, '/tmp' ),
public: path.join( __dirname, '/public' ),
archivefolder: path.join( __dirname, '/archive' ),
rootURL: '{{setupdns}}',
domain: path.join( __dirname, '/data/tribee' ),
withssl: "{{withssl}}",
SSLCredentials: {
key: path.join( __dirname, '/data/certs/{{setupdns}}.key' ),
cert: path.join( __dirname, '/data/certs/{{setupdns}}.crt' ),
ca: path.join( __dirname, '/data/certs/{{setupdns}}.csr' )
},
port: {
http: "{{httpport}}",
https: "{{httpsport}}"
},
jwtSecret: '{{jwtsecretkey}}',
saltRounds: 10,
languagesAvailable: [ 'fr', 'en', 'it', 'de', 'ru' ],
lg: {},
exposedHeaders: {
'x-auth': 'xauth',
'x-uuid': 'xuuid',
'x-language': 'xlang',
'x-client-id': 'xtribeid',
'x-workOn': 'xworkOn',
'x-app': 'xapp'
},
bodyparse: {
urlencoded: {
limit: '50mb',
extended: true
},
json: { limit: '500mb' }
}
};
// Development and test config
// apxtrib.local.fr
config.dev = {
mainDir: __dirname,
tmp: path.join( __dirname, '/tmp' ),
public: path.join( __dirname, '/public' ),
//public allow to serve on /public file into folder /public with or without login
archivefolder: path.join( __dirname, '/archive' ),
rootURL: 'apxtrib.local.fr',
domain: path.join( __dirname, '/data/tribee' ),
withssl: "YES",
SSLCredentials: {
key: path.join( __dirname, '/setup/data/certs/apxtrib.local.fr.key' ),
cert: path.join( __dirname, '/setup/data/certs/apxtrib.local.fr.crt' ),
ca: path.join( __dirname, '/setup/data/certs/apxtrib.local.fr.csr' )
},
port: {
http: "{{httpport}}",
https: "{{httpsport}}"
},
jwtSecret: 'dnsqd515+npsc^dsqdsqd^d$qdd$$$dŝqdze154615ae.Dsd:sqd!',
// Avoid authentification for uuid2 and this auth token
// Always remove this from production
devnoauthxuuid: "2",
devnoauthxauth: "autoriserparlapatrouille",
saltRounds: 10,
languagesAvailable: [ 'fr', 'en', 'it', 'de', 'ru' ],
lg: {},
exposedHeaders: {
'x-auth': 'xauth',
'x-uuid': 'xuuid',
'x-language': 'xlang',
'x-client-id': 'xtribeid',
'x-workOn': 'xworkOn',
'x-app': 'xapp'
},
bodyparse: {
urlencoded: {
limit: '50mb',
extended: true
},
json: { limit: '500mb' }
}
};
if( !config[ process.env.NODE_ENV ] ) {
console.log( 'config.js -> Exit setup due to node_ENV have to be set as prod or dev instead of ', process.env.NODE_ENV )
process.exit();
}
const confuse = config[ process.env.NODE_ENV ];
if( confuse.withssl == "YES" ) {
if( !fs.existsSync( confuse.SSLCredentials.key ) ) {
const prompt = require( 'prompt-sync' )( { sigint: true } );
//const repdata = ( process.NODE_ENV == "dev" ) ? 'devdata' : 'data';
const reinit = prompt( `Missing file to ssl ${confuse.SSLCredentials.key}, please run the process letsencrypt to get a ssl certificat or reinit project (type reinit will erase ) and answer no to question: Do you want to manage ssl : ` );
if( reinit == 'reinit' ) {
fs.removeSync( `${__dirname}/config.js` );
fs.removeSync( `${__dirname}/data` )
}
process.exit();
}
confuse.SSLCredentials.key = fs.readFileSync( confuse.SSLCredentials.key, 'utf8' );
confuse.SSLCredentials.cert = fs.readFileSync( confuse.SSLCredentials.cert, 'utf8' );
confuse.SSLCredentials.ca = fs.readFileSync( confuse.SSLCredentials.ca, 'utf8' );
}
module.exports = confuse;

View File

@@ -0,0 +1,37 @@
server {
server_name {{config.apxtribDNS}};
add_header X-Request-ID $request_id; # Return to client
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Expose-Headers Content-Length;
add_header Access-Control-Allow-Headers Range;
access_log /media/phil/usbfarm/apxtrib/tmp/nginx/apxtrib.crabdance.access.log main;
#location = /app {
# return 302 /app/;
#}
location /app/ {
rewrite /app/(.*$) /$1 break;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:3017;
proxy_redirect off;
}
location / {
root /media/phil/usbfarm/apxtrib/nationchains/;
index apxtrib.html;
}
error_page 404 /media/phil/usbfarm/apxtrib/nationchains/error/404.html;
error_page 500 502 503 504 /media/phil/usbfarm/apxtrib/nationchains/error/50x.html;
}

26
asupsetup/config.mustache Executable file
View File

@@ -0,0 +1,26 @@
const path = require( 'path' );
const config = {
loglevel:"{{consoleloglevel}}",
linuxuser:"{{linuxuser}}",
druidid:"{{druidid}}",
dnsapxtrib:"{{subdomain}}.{{domain}}",
mainDir: __dirname,
tmp: path.join( __dirname, '/tmp' ),
public: path.join( __dirname, 'data/tribe/{{druidid}}/www/cdn' ),
//(@TODO ASUP mettre /cdn de apxtrib) public allow to serve on /public file into folder /public with or without login
archivefolder: path.join( __dirname, '/archive' ),
domain: path.join( __dirname, '/data/tribe' ),
porthttp:{{porthttp}} ,
jwtSecret: '{{jwtsecret}}',
saltRounds: 10,
languagesAvailable: [ {{#language}}'{{.}}',{{/language}} ],
exposedHeaders:[ 'xauth', 'xpaganid', 'xlang', 'xtribe', 'xworkon', 'xapp' ],
bodyparse: {
urlencoded: {
limit: '50mb',
extended: true
},
json: { limit: '500mb' }
}
};
module.exports = config;

13
asupsetup/configsetup.json Executable file
View File

@@ -0,0 +1,13 @@
{
"linuxuser": "phil",
"mode": "dev",
"domain": "local.fr",
"subdomain": "dev",
"consoleloglevel": "quiet",
"porthttp": 3018,
"language": ["fr", "en"],
"jwtsecret": "longsentenceusedtoencryptionChangethisforproduction",
"druidid": "test",
"login": "testadmin",
"genericpsw": "@12ABab@"
}

View File

@@ -0,0 +1,9 @@
{
"hash": "",
"previousblockhash": "",
"nextblockhash": "",
"info": "txt:freedomandthefuturofinternet##",
"time": 1643763742,
"size": "",
"difficulty": ""
}

View File

@@ -0,0 +1,10 @@
{
"fixedIP":"",
"firsttimeping":0,
"lastimeping":0,
"positifping":0,
"negatifping":0,
"pubkeyadmin":"",
"tribeids":[],
"logins":[]
}

View File

@@ -0,0 +1,28 @@
{
"tribeid": "{{tribeid}}",
"genericpsw": "{{genericpsw}}",
"saltRounds": "tomakeitharderbetterstronger",
"website": {
"webapp": "{{subdomain}}.{{domain}}"
},
"allowedDOMs": ["{{domain}}"],
"customization": {
"claim": "Be a Producer, not a product.",
"name": "apxtrib",
"logo": "https://{{subdomain}}.{{domain}}/cdn/{{druidid}}/img/logo/apxtrib.png",
"favicon": "https://{{subdomain}}.{{domain}}/cdn/{{druidid}}/img/iconX74x74.png",
"colors": {
"primary": "#01717B",
"secondary": "#CA5F00",
"success": "",
"info": "",
"warning": "",
"danger": "",
"light": "#fff",
"dark": "#222"
}
},
"smtp": {},
"accepted-language": "fr,en",
"langueReferential": ["fr"]
}

View File

@@ -0,0 +1,106 @@
[{
"uuid": "ERRcritical",
"desc": {
"fr": "Erreur critique",
"en": "Critical Error"
}
},
{
"uuid": "failtoWritefs",
"desc": {
"fr": "Impossible d'enregistrer cette information",
"en": "Fail to write on system"
}
},
{
"uuid": "successfulCreate",
"desc": {
"fr": "Création réussie",
"en": "Creation is successful"
}
},
{
"uuid": "successfulUpdate",
"desc": {
"fr": "Mise à jour réussie",
"en": "Succesfull update"
}
},
{
"uuid": "successfulDelete",
"desc": {
"fr": "Suppression effectuée",
"en": "The user has been deleted"
}
},
{
"uuid": "forbiddenAccess",
"desc": {
"fr": "Accès non autorisé",
"en": "Forbidden access"
}
},
{
"uuid": "userNotAllowtoCreate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à créer un nouvel utilisateur",
"en": "Pagans is not allowed to create a new user account"
}
},
{
"uuid": "userNotAllowtoUpdate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à mettre à jour cet utilisateur",
"en": "Pagans is not allowed to update this user account"
}
},
{
"uuid": "userNotAllowtoDelete",
"desc": {
"fr": "L'utilisateur n'est pas authrisé à supprimer utilisateur",
"en": "Pagans is not allowed to delete a user account"
}
},
{
"uuid": "invalidData",
"desc": {
"fr": "Vérifiez vos données",
"en": "Check your data"
}
},
{
"uuid": "ERRemail",
"desc": {
"fr": "Vérifiez votre email",
"en": "Check your email"
}
},
{
"uuid": "Reservationplandoesnotexist",
"desc": {
"fr": "Il n'y a pas de Planning de réservation",
"en": "No reservation plan at this adress"
}
},
{
"uuid": "Reservationitemwelldone",
"desc": {
"fr": "Votre reservation a bien été enregistrée",
"en": "Registration well done."
}
},
{
"uuid": "Nomoreavailability",
"desc": {
"fr": "Désolé, la place n'est plus disponible",
"en": "Unavailable."
}
},
{
"uuid": "CatalogdoesnotExist",
"desc": {
"fr": "Désolé, ce catagoue n'existe pas",
"en": "This catalog does not exist."
}
}
]

View File

@@ -0,0 +1,170 @@
[
{
"uuid": "ERRcritical",
"desc": {
"fr": "Erreur critique",
"en": "Critical Error"
}
},
{
"uuid": "msgsentok",
"desc": {
"fr": "L'email a bien été envoyé",
"en": "email sent"
}
},
{
"uuid": "emailAlreadyExist",
"desc": {
"fr": "Cet email a déjà un compte",
"en": "Email already exists"
}
},
{
"uuid": "failtoWritefs",
"desc": {
"fr": "Impossible d'enregistrer cette information",
"en": "Fail to write on system"
}
},
{
"uuid": "successfulCreate",
"desc": {
"fr": "Création réussie",
"en": "Creation is successful"
}
},
{
"uuid": "successfulUpdate",
"desc": {
"fr": "Mise à jour réussie",
"en": "Succesfull update"
}
},
{
"uuid": "successfulDelete",
"desc": {
"fr": "Suppression effectuée",
"en": "The user has been deleted"
}
},
{
"uuid": "serverNeedAuthentification",
"desc": {
"fr": "Ce serveur nécessite une authentification",
"en": "This server needs authentification"
}
},
{
"uuid": "forbiddenAccess",
"desc": {
"fr": "Accès non autorisé",
"en": "Forbidden access"
}
},
{
"uuid": "userNotAllowtoCreate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à créer un nouvel utilisateur",
"en": "Pagans is not allowed to create a new user account"
}
},
{
"uuid": "userNotAllowtoUpdate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à mettre à jour cet utilisateur",
"en": "Pagans is not allowed to update this user account"
}
},
{
"uuid": "userNotAllowtoDelete",
"desc": {
"fr": "L'utilisateur n'est pas authrisé à supprimer utilisateur",
"en": "Pagans is not allowed to delete a user account"
}
},
{
"uuid": "useridNotfound",
"desc": {
"fr": "L'utilisateur {{uuid}} n'existe pas sur {{tribeid}}",
"en": "Pagans {{uuid}} not found for {{tribeid}}"
}
},
{
"uuid": "useremailNotfound",
"desc": {
"fr": "L'email n'existe pas",
"en": "Email not found"
}
},
{
"uuid": "loginDoesNotExist",
"desc": {
"fr": "Ce login n'existe pas",
"en": "This login doesn't exist"
}
},
{
"uuid": "checkCredentials",
"desc": {
"fr": "Vérifiez vos identifiants",
"en": "Check your credentials"
}
},
{
"uuid": "wrongPassword",
"desc": {
"fr": "Vérifiez votre mot de passe",
"en": "Check your password"
}
},
{
"uuid": "invalidData",
"desc": {
"fr": "Vérifiez vos données",
"en": "Check your data"
}
},
{
"uuid": "pswTooSimple",
"desc": {
"fr": "Le mot de passe doit faire au moins 8 caractéres comporter au moins un chiffre, une lettre minuscule, une lettre majuscule et un caractere spécial type @ !...",
"en": "Password too simple, need to contain at least 8 caracters lower and uppercase, number and @! ..."
}
},
{
"uuid": "ERRemail",
"desc": {
"fr": "Vérifiez votre email",
"en": "Check your email"
}
},
{
"uuid": "ERRnewnewbisdiff",
"desc": {
"fr": "Le mot de passe de confirmation ne correspond pas",
"en": "Check your confirmation password"
}
},
{
"uuid": "ERRnotemplate",
"desc": {
"fr": "il n'y a pas de template d'email dans la demande template.html est vide et pas de htmlfile présent.",
"en": "Check your email template conf tribeid no msg.template.htmlfile and msg.html==''"
}
},
{
"uuid": "wellPdfGenerated",
"desc": {
"fr": "Le pdf: {{filename}} a ete genere correctement.",
"en": "Pdf well generated"
}
},
{
"uuid": "pdfGenerationError",
"desc": {
"fr": "Erreur dans la generation de pdf, verifiez le json",
"en": "Error in pdf generation, check the json"
}
}
]

View File

@@ -0,0 +1,23 @@
[
{
"uuid": "fileUnknown",
"desc": {
"fr": "Fichier inconnu",
"en": "File unknown"
}
},
{
"uuid": "wellUpload",
"desc": {
"fr": "Fichier bien récupéré dans {{destination}}/{{filename}}",
"en": "File well uploaded in {{destination}}/{{filename}}"
}
},
{
"uuid": "savingError",
"desc": {
"fr": "Impossible de sauvegarder",
"en": "Saving file is impossible"
}
}
]

View File

@@ -0,0 +1,149 @@
[
{
"uuid": "ERRcritical",
"desc": {
"fr": "Erreur critique",
"en": "Critical Error"
}
},
{
"uuid": "loginAlreadyExist",
"desc": {
"fr": "Ce login est déjà attribué",
"en": "Login already exists"
}
},
{
"uuid": "emailAlreadyExist",
"desc": {
"fr": "Cet email a déjà un compte",
"en": "Email already exists"
}
},
{
"uuid": "failtoWritefs",
"desc": {
"fr": "Impossible d'enregistrer cette information",
"en": "Fail to write on system"
}
},
{
"uuid": "successfulCreate",
"desc": {
"fr": "Création réussie",
"en": "Creation is successful"
}
},
{
"uuid": "successfulUpdate",
"desc": {
"fr": "Mise à jour réussie",
"en": "Succesfull update"
}
},
{
"uuid": "successfulDelete",
"desc": {
"fr": "Suppression effectuée",
"en": "The user has been deleted"
}
},
{
"uuid": "serverNeedAuthentification",
"desc": {
"fr": "Ce serveur nécessite une authentification",
"en": "This server needs authentification"
}
},
{
"uuid": "forbiddenAccess",
"desc": {
"fr": "Accès non autorisé",
"en": "Forbidden access"
}
},
{
"uuid": "userNotAllowtoCreate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à créer un nouvel utilisateur",
"en": "Pagans is not allowed to create a new user account"
}
},
{
"uuid": "userNotAllowtoUpdate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à mettre à jour cet utilisateur",
"en": "Pagans is not allowed to update this user account"
}
},
{
"uuid": "userNotAllowtoDelete",
"desc": {
"fr": "L'utilisateur n'est pas authrisé à supprimer utilisateur",
"en": "Pagans is not allowed to delete a user account"
}
},
{
"uuid": "useridNotfound",
"desc": {
"fr": "L'utilisateur {{uuid}} n'existe pas sur {{tribeid}}",
"en": "Pagans {{uuid}} not found for {{tribeid}}"
}
},
{
"uuid": "useremailNotfound",
"desc": {
"fr": "L'email n'existe pas",
"en": "Email not found"
}
},
{
"uuid": "loginDoesNotExist",
"desc": {
"fr": "Ce login n'existe pas",
"en": "This login doesn't exist"
}
},
{
"uuid": "checkCredentials",
"desc": {
"fr": "Vérifiez vos identifiants",
"en": "Check your credentials"
}
},
{
"uuid": "wrongPassword",
"desc": {
"fr": "Vérifiez votre mot de passe",
"en": "Check your password"
}
},
{
"uuid": "invalidData",
"desc": {
"fr": "Vérifiez vos données",
"en": "Check your data"
}
},
{
"uuid": "pswTooSimple",
"desc": {
"fr": "Le mot de passe doit faire au moins 8 caractéres comporter au moins un chiffre, une lettre minuscule, une lettre majuscule et un caractere spécial type @ !...",
"en": "Password too simple, need to contain at least 8 caracters lower and uppercase, number and @! ..."
}
},
{
"uuid": "ERRemail",
"desc": {
"fr": "Vérifiez votre email",
"en": "Check your email"
}
},
{
"uuid": "ERRnewnewbisdiff",
"desc": {
"fr": "Le mot de passe de confirmation ne correspond pas",
"en": "Check your confirmation password"
}
}
]

View File

@@ -0,0 +1,12 @@
[
{
"uuid": 0,
"desclong": { "fr": "Monsieur", "en": "Mister" },
"desc": { "fr": "M.", "en": "M." }
},
{
"uuid": 1,
"desclong": { "fr": "Madame", "en": "Miss" },
"desc": { "fr": "Mme", "en": "Miss" }
}
]

View File

@@ -0,0 +1,314 @@
[
{
"uuid": "AMDTA",
"desc": { "fr": "Amiante (DTA)" },
"desclong": {
"fr": "correspond au champ diag_01 dans le modèle de rapport Ordre de mission"
},
"color": "#449245"
},
{
"uuid": "AMVENT",
"desc": { "fr": "Amiante (Vente)" },
"desclong": {
"fr": "correspond au champ diag_02 dans le modèle de rapport Ordre de mission"
},
"color": "#136202"
},
{
"uuid": "AMTRAV",
"desc": { "fr": "Amiante (Travaux)" },
"desclong": {
"fr": "correspond au champ diag_03 dans le modèle de rapport Ordre de mission"
},
"color": "#865694"
},
{
"uuid": "AMDEMOL",
"desc": { "fr": "Amiante (Démol)" },
"desclong": {
"fr": "correspond au champ diag_04 dans le modèle de rapport Ordre de mission"
},
"color": "#086446"
},
{
"uuid": "DIAGTERM",
"desc": { "fr": "Diag.Termites" },
"desclong": {
"fr": "correspond au champ diag_05 dans le modèle de rapport Ordre de mission"
},
"color": "#036034"
},
{
"uuid": "DIAGPARA",
"desc": { "fr": "Diag.Parasites" },
"desclong": {
"fr": "correspond au champ diag_06 dans le modèle de rapport Ordre de mission"
},
"color": "#116801"
},
{
"uuid": "CARREZ",
"desc": { "fr": "Mesurage (Carrez)" },
"desclong": {
"fr": "correspond au champ diag_07 dans le modèle de rapport Ordre de mission"
},
"color": "#341770"
},
{
"uuid": "CREP",
"desc": { "fr": "Constat des risques d'exposition au plomb (CREP)" },
"desclong": {
"fr": "correspond au champ diag_08 dans le modèle de rapport Ordre de mission"
},
"color": "#303924"
},
{
"uuid": "ASSAINISS",
"desc": { "fr": "Assainissement" },
"desclong": {
"fr": "correspond au champ diag_09 dans le modèle de rapport Ordre de mission"
},
"color": "#279573"
},
{
"uuid": "PISCINE",
"desc": { "fr": "Piscine" },
"desclong": {
"fr": "correspond au champ diag_10 dans le modèle de rapport Ordre de mission"
},
"color": "#450176"
},
{
"uuid": "GAZ",
"desc": { "fr": "Gaz" },
"desclong": {
"fr": "correspond au champ diag_11 dans le modèle de rapport Ordre de mission"
},
"color": "#763822"
},
{
"uuid": "ELEC",
"desc": { "fr": "Electricité" },
"desclong": {
"fr": "correspond au champ diag_12 dans le modèle de rapport Ordre de mission"
},
"color": "#873048"
},
{
"uuid": "SRU",
"desc": { "fr": "D.Technique SRU" },
"desclong": {
"fr": "correspond au champ diag_13 dans le modèle de rapport Ordre de mission"
},
"color": "#279870"
},
{
"uuid": "DPE",
"desc": { "fr": "DPE" },
"desclong": {
"fr": "correspond au champ diag_14 dans le modèle de rapport Ordre de mission"
},
"color": "#862302"
},
{
"uuid": "TX0",
"desc": { "fr": "Prêt à taux zéro" },
"desclong": {
"fr": "correspond au champ diag_15 dans le modèle de rapport Ordre de mission"
},
"color": "#279050"
},
{
"uuid": "ERNT",
"desc": { "fr": "ERNT" },
"desclong": {
"fr": "correspond au champ diag_16 dans le modèle de rapport Ordre de mission"
},
"color": "#870447"
},
{
"uuid": "ROBIEN",
"desc": { "fr": "Robien" },
"desclong": {
"fr": "correspond au champ diag_17 dans le modèle de rapport Ordre de mission"
},
"color": "#238577"
},
{
"uuid": "ETATLIEU",
"desc": { "fr": "Etat des lieux" },
"desclong": {
"fr": "correspond au champ diag_18 dans le modèle de rapport Ordre de mission"
},
"color": "#159488"
},
{
"uuid": "DIAGPBEAU",
"desc": { "fr": "Diag. plomb dans l'eau" },
"desclong": {
"fr": "correspond au champ diag_19 dans le modèle de rapport Ordre de mission"
},
"color": "#210836"
},
{
"uuid": "ASCENSE",
"desc": { "fr": "Ascenseur" },
"desclong": {
"fr": "correspond au champ diag_20 dans le modèle de rapport Ordre de mission"
},
"color": "#702852"
},
{
"uuid": "RADON",
"desc": { "fr": "Radon" },
"desclong": {
"fr": "correspond au champ diag_21 dans le modèle de rapport Ordre de mission"
},
"color": "#851497"
},
{
"uuid": "INCENDIE",
"desc": { "fr": "Incendie" },
"desclong": {
"fr": "correspond au champ diag_22 dans le modèle de rapport Ordre de mission"
},
"color": "#329688"
},
{
"uuid": "HANDICAP",
"desc": { "fr": "Handicapé" },
"desclong": {
"fr": "correspond au champ diag_23 dans le modèle de rapport Ordre de mission"
},
"color": "#752339"
},
{
"uuid": "BOUTIN",
"desc": { "fr": "Mesurage (Boutin)" },
"desclong": {
"fr": "correspond au champ diag_24 dans le modèle de rapport Ordre de mission"
},
"color": "#645556"
},
{
"uuid": "AMDAPP",
"desc": { "fr": "Amiante DAPP" },
"desclong": {
"fr": "correspond au champ diag_25 dans le modèle de rapport Ordre de mission"
},
"color": "#594067"
},
{
"uuid": "DRIPP",
"desc": { "fr": "DRIPP" },
"desclong": {
"fr": "correspond au champ diag_26 dans le modèle de rapport Ordre de mission"
},
"color": "#135691"
},
{
"uuid": "DPN",
"desc": { "fr": "DPN Performance numérique" },
"desclong": {
"fr": "correspond au champ diag_27 dans le modèle de rapport Ordre de mission"
},
"color": "#659536"
},
{
"uuid": "INFILTRO",
"desc": { "fr": "Infiltrométrie" },
"desclong": {
"fr": "correspond au champ diag_28 dans le modèle de rapport Ordre de mission"
},
"color": "#931708"
},
{
"uuid": "AMAPTVX",
"desc": { "fr": "Amiante Examun Visuel APTVX" },
"desclong": {
"fr": "correspond au champ diag_29 dans le modèle de rapport Ordre de mission"
},
"color": "#165812"
},
{
"uuid": "DECHET",
"desc": { "fr": "Dechet" },
"desclong": {
"fr": "correspond au champ diag_30 dans le modèle de rapport Ordre de mission"
},
"color": "#682853"
},
{
"uuid": "PBAPTVX",
"desc": { "fr": "Plomb APTVX" },
"desclong": {
"fr": "correspond au champ diag_31 dans le modèle de rapport Ordre de mission"
},
"color": "#051199"
},
{
"uuid": "AMCTRLPERIO",
"desc": { "fr": "Amiante Contrôl périodique" },
"desclong": {
"fr": "correspond au champ diag_32 dans le modèle de rapport Ordre de mission"
},
"color": "#726364"
},
{
"uuid": "AMEMPOUSS",
"desc": { "fr": "Amiante Empoussièrement" },
"desclong": {
"fr": "correspond au champ diag_33 dans le modèle de rapport Ordre de mission"
},
"color": "#053460"
},
{
"uuid": "DEVINTERNE",
"desc": { "fr": "Module developpement Interne" },
"desclong": {
"fr": "correspond au champ diag_34 dans le modèle de rapport Ordre de mission"
},
"color": "#687134"
},
{
"uuid": "HOMEINSPECT",
"desc": { "fr": "Home Inspection" },
"desclong": {
"fr": "correspond au champ diag_35 dans le modèle de rapport Ordre de mission"
},
"color": "#574776"
},
{
"uuid": "4PTINSPECTION",
"desc": { "fr": "4PT Inspection" },
"desclong": {
"fr": "correspond au champ diag_36 dans le modèle de rapport Ordre de mission"
},
"color": "#729909"
},
{
"uuid": "WINDMITIG",
"desc": { "fr": "Wind Mitigation Inspection" },
"desclong": {
"fr": "correspond au champ diag_37 dans le modèle de rapport Ordre de mission"
},
"color": "#646189"
},
{
"uuid": "PBAVTVX",
"desc": { "fr": "Plomb Av Tvx" },
"desclong": {
"fr": "correspond au champ diag_37 DIAG_38 dans le modèle de rapport Ordre de mission"
},
"color": "#971402"
},
{
"uuid": "HAP",
"desc": { "fr": "HAP" },
"desclong": {
"fr": "correspond au champ DIAG_39 dans le modèle de rapport Ordre de mission"
},
"color": "#577844"
}
]

View File

@@ -0,0 +1,46 @@
[
{
"uuid": "USER",
"desc": {
"fr": "USER - Utilisateur interne",
"en": "USER - User profil"
},
"desclong": {
"fr": "Utilisateur interne à l'organisation",
"en": "User profil"
}
},
{
"uuid": "CLIENT",
"desc": {
"fr": "CLIENT - Utilisateur externe",
"en": "CLIENT - User profil"
},
"desclong": {
"fr": "Utilisateur externe type client",
"en": "User profil"
}
},
{
"uuid": "MANAGER",
"desc": {
"fr": "MANAGER - Gestionnaire de données",
"en": "MANAGER - Data Manager"
},
"desclong": {
"fr": "Gestionnaire de données avec des accès sensible",
"en": "Data Manager with sensible data access"
}
},
{
"uuid": "ADMIN",
"desc": {
"fr": "ADMIN - Admininstrateur",
"en": "ADMIN - Admin profil"
},
"desclong": {
"fr": "Admininstrateur avec accès complet aux comptes",
"en": "Admin profil with full access"
}
}
]

View File

@@ -0,0 +1,17 @@
[
{
"uuid": "OTHER",
"desclong": { "fr": "Autre", "en": "Other" },
"desc": { "fr": "Aut.", "en": "Oth." }
},
{
"uuid": "DIAG",
"desclong": { "fr": "Diagnostiqueur", "en": "Diag" },
"desc": { "fr": "Diag", "en": "DIAG" }
},
{
"uuid": "GEST",
"desclong": { "fr": "Gestion", "en": "Management" },
"desc": { "fr": "Gest", "en": "Manag" }
}
]

View File

@@ -0,0 +1,129 @@
{
"commentaire": "Parametrage permettant de mettre à jour le cataloguecatBoutiquefixe.json",
"nomcatalogue": "",
"objet": "companies",
"optioncatalogcsv": {
"retln": "\n",
"sep": ";",
"seplevel": "__",
"replacespecialcarCsv2Json": "[[/Semicolon/g,';']]",
"replacespecialcarJson2Csv": "[[/;/g, 'Semicolon']]"
},
"removeforPublic": "",
"templates": {
"templateidintro": "",
"menu": "",
"header": "",
"section": "",
"filtre": "bdcfiltercateg",
"card": "bdccard"
},
"vendeur": {
"nom": "Commune dIgny",
"siret": "333 951 978 00050",
"responsable": "Marie Faujas (Directrice de la Communication, Culture et Evènementiel)",
"adressesociale": "23, avenue de la division Leclerc",
"CP": "91430",
"ville": "IGNY",
"logo": "logo.png",
"email": "mairie@igny.fr",
"tel": "01 69 33 11 19",
"telinter": "33169331119",
"cartegmap": "",
"reseauxociaux": [],
"horaire": "",
"horairedata": "",
"IBAN": "",
"tribunalRCS": "",
"ScanKbis": "",
"RIBSociete": "",
"ScanIdcardResp1": "",
"JustifDomicileResp1": "",
"ScanIdcardResp2": "",
"JustifDomicileResp2": ""
},
"hebergeur": {
"nom": "SAS Need-Data",
"adresse": "6 Avenue des Andes",
"CP": "91940",
"Ville": "Les Ulis",
"site": "https://need-data.com"
},
"page": {
"objettype": "page",
"nom": "",
"urlsite": "https://shop.igny.fr",
"couleurprimaire": "",
"couleursecondaire": "",
"description": "",
"metaauthor": "",
"keywords": "",
"ogtitle": "",
"ogvignette": "",
"title": "",
"ordre": []
},
"header": {
"objettype": "html",
"photopleinepage": "",
"TITREpage": "",
"SSTITREpage": "",
"inscriptionemailbouton": "S'inscrire",
"TEXTpage": ""
},
"edito": {
"objettype": "html",
"tmpl": "sectionmaildigit",
"contenthtml": "static/data/staticContentwebsite/edito.html"
},
"referencer": {
"objettype": "html",
"tmpl": "sectionmaildigit",
"contenthtml": "static/data/staticContentwebsite/referencer.html"
},
"footer": {
"objettype": "html",
"tmpl": "sectionmaildigit",
"classsection": "footer text-center bg-primary text-white",
"contenthtml": "static/data/staticContentwebsite/footer.html"
},
"catalogue": {
"objettype": "catalogueCards",
"tmpl": "sectionmaildigit",
"tpl": "",
"titre": "",
"sstitre": "",
"text": "",
"youtube": "",
"youtubesmallscreen": "",
"youtubebtn": "",
"filtrecatalog": {
"tpl": "bdcfiltercateg",
"btnlist": true,
"seleclist": false,
"classbtn": "btn-outline-primary",
"modalbutton": "video pour passer commande",
"modalcontent": "",
"modalcontentsmallscreen": "",
"classcolfiltre": "text-left",
"titrefiltre": "",
"message": " <p><b>La livraison est offerte à partir de <s>6 cartons</s> 2 cartons (pendant la période de confinement)</b></p>",
"categorie": "Nos Gammes",
"btncategorie": "btn-outline-primary",
"item": []
},
"cards": {
"tpl": "bdccard",
"devise": "€",
"buttonajoutpanier": true,
"showAvailibility": false,
"masqueitemifnomoreavailable": true,
"buttonBookwithemail": false,
"buttonBookaddForm": true,
"cardClass": "col-12",
"btndesc": "Ajouter cette réservation",
"cardbtn": "btn-primary",
"objet": {}
}
}
}

View File

@@ -0,0 +1,18 @@
{
"commentaire": "Spécification permettant d'importer et d'exporter en csv des companies, pour documentation voir gitlab",
"object": "companies",
"cardscsv": "compagniesIGNY.csv",
"ASUPPcatalogPerso": "catalogueCommerceIGNY.csv",
"optioncardscsv": {
"retln": "\n",
"sep": ";",
"champs": ["UUID", "TYPE", "STATUT", "CATEGORIE", "TITRE", "SSTITRE", "KEYWORD", "MOTHTMLDUMAGASIN", "MOTTXTDUMAGASIN", "ADRESSE_PRO", "CP_PRO", "VILLE_PRO", "PHONE_PRO", "HORAIRESDESC", "HORAIREDATA", "URL", "FACEBOOK", "INSTA", "YOUTUBE", "LINKEDIN", "EMAIL_PRO", "IMG_VIGNETTE", "DATEOUVERTURE", "DATEFERMETURE", "HTML", "COMMENTPOSTTRT", "DATE_CREATE", "DATE_UPDATE"],
"array": ["CATEGORIE", "PHONE_PRO", "EMAIL_PRO"],
"arraysplitsep": ",",
"numericfield": [],
"search": "(fiche.UUID && fiche.UUID == data.UUID) || (fiche.EMAIL_PRO && utils.testinarray(fiche.EMAIL_PRO,data.EMAIL_PRO)) || (fiche.PHONE_PRO && utils.testinarray(fiche.PHONE_PRO,data.PHONE_PRO))",
"merge": [],
"replacespecialcarCsv2Json": "[[/Semicolon/g,';']]",
"replacespecialcarJson2Csv": "[[/;/g, 'Semicolon']]"
}
}

View File

@@ -0,0 +1,134 @@
{
"fr": {
"menuleft": {
"sbbrandlink": "app.html",
"sbtitle": "YES",
"sbgroupmenu": [{
"groupheader": "Suivi",
"sbssgroupmenu": [{
"name": "Reporting",
"icon": "sliders",
"actionclick": "pwa.reporting.init()"
}, {
"name": "Test sous niveau",
"icon": "sliders",
"actionclick": "pwa.reporting.init()",
"iditemmenus": "suivitest",
"itemmenus": [{
"name": "sousmenu",
"actionclick": "pwa.reporting.init()"
}]
}]
}, {
"groupheader": "Admin",
"sbssgroupmenu": [{
"name": "Pagans",
"icon": "sliders",
"actionclick": "pwa.users.init()"
}, {
"name": "Referentiels",
"icon": "sliders",
"iditemmenus": "adminreferentiel",
"itemmenus": [{
"name": "Offre",
"actionclick": "pwa.referential.setting('offre')"
}, {
"name": "return action",
"actionclick": "pwa.referential.setting('returnaction')"
}, {
"name": "data",
"actionclick": "pwa.referential.setting('data')"
}, {
"name": "json",
"actionclick": "pwa.referential.setting('json')"
}, {
"name": "Objects",
"actionclick": "pwa.referential.setting('object')"
}]
}]
}, {
"groupheader": "Teacher",
"sbssgroupmenu": [{
"name": "Evaluation",
"icon": "sliders",
"actionclick": "pwa.evaluation.init()"
}, {
"name": "ScheduleOnce",
"icon": "sliders",
"actionclick": "pwa.scheduleonce.init()"
}]
}, {
"groupheader": "Operation",
"sbssgroupmenu": [{
"name": "Action Learner",
"icon": "sliders",
"actionclick": "pwa.actionlearner.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Marketing",
"sbssgroupmenu": [{
"name": "Gestion l'offre",
"icon": "sliders",
"actionclick": "pwa.offers.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Learner",
"sbssgroupmenu": [{
"name": "Mes evaluations",
"icon": "sliders",
"actionclick": "pwa.learner.init()"
},
{
"name": "Actions learner",
"icon": "sliders",
"actionclick": "pwa.learner.action()"
}
]
}]
},
"menutop": {
"withsearch": true,
"searchtxt": "Recherche...",
"withnotification": true,
"notificationheader": "Vos notifications",
"notificationfooter": "Voir toutes les notifications",
"href": "?action=notification.view",
"withmessage": false,
"messageheader": "Vos messages non lus",
"messagefooter": "Voir tous les messages",
"avatarimg": "",
"name": "",
"menuprofil": [{
"icon": "user",
"desc": "Profile",
"href": "?action=user.settings",
"menubreaker": false
},
{
"icon": "pie-chart",
"desc": "Activity",
"href": "?action=user.activity",
"menubreaker": true
},
{
"icon": "settings",
"desc": "Log out",
"href": "?action=userauth.logout",
"menubreaker": false
}
]
}
},
"en": {
"menuleft": {},
"menutop": {}
}
}

View File

@@ -0,0 +1,134 @@
{
"fr": {
"menuleft": {
"sbbrandlink": "app.html",
"sbtitle": "YES",
"sbgroupmenu": [{
"groupheader": "Suivi",
"sbssgroupmenu": [{
"name": "Reporting",
"icon": "sliders",
"actionclick": "pwa.reporting.init()"
}, {
"name": "Test sous niveau",
"icon": "sliders",
"actionclick": "pwa.reporting.init()",
"iditemmenus": "suivitest",
"itemmenus": [{
"name": "sousmenu",
"actionclick": "pwa.reporting.init()"
}]
}]
}, {
"groupheader": "Admin",
"sbssgroupmenu": [{
"name": "Pagans",
"icon": "sliders",
"actionclick": "pwa.users.init()"
}, {
"name": "Referentiels",
"icon": "sliders",
"iditemmenus": "adminreferentiel",
"itemmenus": [{
"name": "Offre",
"actionclick": "pwa.referential.setting('offre')"
}, {
"name": "return action",
"actionclick": "pwa.referential.setting('returnaction')"
}, {
"name": "data",
"actionclick": "pwa.referential.setting('data')"
}, {
"name": "json",
"actionclick": "pwa.referential.setting('json')"
}, {
"name": "Objects",
"actionclick": "pwa.referential.setting('object')"
}]
}]
}, {
"groupheader": "Teacher",
"sbssgroupmenu": [{
"name": "Evaluation",
"icon": "sliders",
"actionclick": "pwa.evaluation.init()"
}, {
"name": "ScheduleOnce",
"icon": "sliders",
"actionclick": "pwa.scheduleonce.init()"
}]
}, {
"groupheader": "Operation",
"sbssgroupmenu": [{
"name": "Action Learner",
"icon": "sliders",
"actionclick": "pwa.actionlearner.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Marketing",
"sbssgroupmenu": [{
"name": "Gestion l'offre",
"icon": "sliders",
"actionclick": "pwa.offers.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Learner",
"sbssgroupmenu": [{
"name": "Mes evaluations",
"icon": "sliders",
"actionclick": "pwa.learner.init()"
},
{
"name": "Actions learner",
"icon": "sliders",
"actionclick": "pwa.learner.action()"
}
]
}]
},
"menutop": {
"withsearch": true,
"searchtxt": "Recherche...",
"withnotification": true,
"notificationheader": "Vos notifications",
"notificationfooter": "Voir toutes les notifications",
"href": "?action=notification.view",
"withmessage": false,
"messageheader": "Vos messages non lus",
"messagefooter": "Voir tous les messages",
"avatarimg": "",
"name": "",
"menuprofil": [{
"icon": "user",
"desc": "Profile",
"href": "?action=user.settings",
"menubreaker": false
},
{
"icon": "pie-chart",
"desc": "Activity",
"href": "?action=user.activity",
"menubreaker": true
},
{
"icon": "settings",
"desc": "Log out",
"href": "?action=userauth.logout",
"menubreaker": false
}
]
}
},
"en": {
"menuleft": {},
"menutop": {}
}
}

View File

@@ -0,0 +1,70 @@
{
"fr": {
"titre": "Gestion des référentiels Administrateur",
"submenutitre": "Liste",
"btnsave": "Sauvegarder",
"btndelete": "Supprimer",
"btncopy": "Dupliquer",
"copyplaceholder": "avec le nom de referentiel",
"submenuitems": [{
"active": "active",
"groupinfo": "menuAdmin",
"id": "referentialmenuadmin",
"optionjsoneditor": {
"theme": "tailwind"
},
"onclick": "pwa.referential.save(event,'referentialmenuadmin')"
}, {
"active": "",
"id": "referentialmenuteacher",
"optionjsoneditor": {},
"groupinfo": "menuTeacher",
"onclick": "pwa.referential.save(event,'referentialmenuteacher')"
},
{
"active": "",
"id": "referentialusersetting",
"groupinfo": "usersetting",
"optionjsoneditor": {},
"btnsave": "Sauvegarder",
"onclick": "pwa.referential.save(event,'referentialmenuteacher')"
},
{
"active": "",
"id": "referentialreferentialsettting",
"optionjsoneditor": {},
"groupinfo": "referentialsetting",
"btnsave": "Sauvegarder",
"onclick": "pwa.referential.save(event,'referentialmenuteacher')"
}
]
},
"en": {
"titre": "Gestion des référentiels",
"submenutitre": "Liste",
"submenuitems": [{
"active": "active",
"groupinfo": "menuAdmin.json",
"id": "referentialmenuadmin",
"btnsave": "Sauvegarder",
"onclick": "pwa.form.submit(event,'referentialmenuadmin',pwa.referentials.save)"
}, {
"active": "",
"id": "referentialmenuTeacher",
"groupinfo": "menuTeacher.json",
"btnsave": "Sauvegarder",
"onclick": "pwa.form.submit(event,'referentialmenuteacher',pwa.referentials.save)"
},
{
"active": "",
"id": "referentialusersetting",
"groupinfo": "usersetting.json"
},
{
"active": "",
"id": "referentialreferentialsettting",
"groupinfo": "referentialsetting.json"
}
]
}
}

View File

@@ -0,0 +1 @@
{"fr":{"niv1":1,"niv2":12222222},"en":{"niv1":1,"niv2":2}}

View File

@@ -0,0 +1,60 @@
{
"fr": {
"titre": "Information",
"submenutitre": "Vos paramétres",
"submenuitems": [{
"active": "active",
"id": "usersettingaccount",
"groupinfo": "Compte",
"publicinfo": "Informations publiques",
"pseudoplaceholder": "Votre pseudo",
"pseudodesc": "Votre pseudo",
"biographyplaceholder": "Quelque chose qui vous décrit",
"biographydesc": "Biographie",
"imguserupload": "Charger votre avatar",
"infoimgavatar": "Pour un bon résultat, utiliser une image carrée de 128px au format .jpg",
"btnsave": "Sauvegarder",
"onclick": "pwa.form.submit(event,'userpublicinfo',pwa.user.save)"
}, {
"active": "",
"id": "usersettingpassword",
"groupinfo": "Mot de passe"
},
{
"active": "",
"id": "usersettingprivacy",
"groupinfo": "Protection des données"
},
{
"active": "",
"id": "usersettingdelete",
"groupinfo": "Supprimer son compte"
}
]
},
"en": {
"titre": "Information",
"submenutitre": "Vos paramétres",
"submenuitems": [{
"active": "active",
"id": "usersettingaccount",
"groupinfo": "Compte"
}, {
"active": "",
"id": "usersettingpassword",
"groupinfo": "Mot de passe"
},
{
"active": "",
"id": "usersettingprivacy",
"groupinfo": "Protection des données"
},
{
"active": "",
"id": "usersettingdelete",
"groupinfo": "Supprimer son compte"
}
]
}
}

View File

@@ -0,0 +1,21 @@
[
{
"idfield": "uuid",
"desc": { "fr": "identifiant de l'action", "en": "action id" },
"multilangue": false,
"required": true,
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "desc",
"required": true,
"multilangue": true,
"desc": {
"fr": "Description courte de cette modalité d'action",
"en": "Short Modality Description"
},
"type": "text",
"tpl": "questionInputVertical"
}
]

View File

@@ -0,0 +1,40 @@
[
{
"idfield": "uuid",
"desc": { "fr": "identifiant de l'objet", "en": "object id" },
"multilangue": false,
"required": true,
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "desc",
"required": true,
"multilangue": true,
"desc": {
"fr": "Description courte de cette modalité d'objet",
"en": "Short Modality Description"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "desclong",
"multilangue": true,
"desc": {
"fr": "Description de cette modalité d'objet",
"en": "Modality Description"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "deschtml",
"desc": {
"fr": "Description en bloc html de cette modalité d'objet",
"en": "Html description of this object"
},
"type": "text",
"tpl": "questionTextarea"
}
]

View File

@@ -0,0 +1,41 @@
[
{
"idfield": "uuid",
"desc": { "fr": "identifiant de l'objet", "en": "object id" },
"multilangue": false,
"required": true,
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "desc",
"required": true,
"multilangue": false,
"desc": {
"fr": "Description courte de cette modalité d'objet",
"en": "Short Modality Description"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "desclong",
"multilangue": true,
"desc": {
"fr": "Description de cette modalité d'objet",
"en": "Modality Description"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "deschtml",
"multilangue": true,
"desc": {
"fr": "Description en bloc html de cette modalité d'objet",
"en": "Html description of this object"
},
"type": "text",
"tpl": "questionTextarea"
}
]

View File

@@ -0,0 +1,96 @@
[{
"idfield": "UUID",
"desc": {
"fr": "identifiant utilisateur",
"en": "user id"
},
"nouservisible": true,
"check": ["required", "unique"],
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "LOGIN",
"check": ["required", "unique"],
"desc": {
"fr": "login",
"en": "login"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "EMAIL",
"desc": {
"fr": "email",
"en": "email"
},
"type": "email",
"check": ["email", "unique"],
"placeholder": "@",
"tpl": "questionInputVertical"
},
{
"idfield": "NAME",
"desc": {
"fr": "Nom",
"en": "Name"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "NICKNAME",
"desc": {
"fr": "Prénom",
"en": "Nickname"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "PSEUDO",
"desc": {
"fr": "pseudo",
"en": "pseudo"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_CREATE",
"desc": {
"fr": "Date de création",
"en": "Create Date"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_UPDATE",
"desc": {
"fr": "Date mise à jour",
"en": "Update date"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_LASTLOGIN",
"desc": {
"fr": "Date de derniére connexion",
"en": "Last date login"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
}
]

View File

@@ -0,0 +1,54 @@
[{
"idfield": "UUID",
"desc": {
"fr": "identifiant utilisateur",
"en": "user id"
},
"nouservisible": true,
"check": ["required"],
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "LOGIN",
"desc": {
"fr": "Login",
"en": "Login"
},
"multilangue": false,
"required": true,
"nouserupdate": true,
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "PASSWORD",
"required": true,
"desc": {
"fr": "Mot de passe actuel",
"en": "Current password"
},
"type": "password",
"tpl": "questionInputVertical"
},
{
"idfield": "PSNEW",
"required": true,
"check": ["password"],
"desc": {
"fr": "Nouveau mot de passe",
"en": "New password"
},
"type": "password",
"tpl": "questionInputVertical"
},
{
"idfield": "PSWNEWBIS",
"desc": {
"fr": "Confirmation de mot de passe",
"en": "Password confirmation"
},
"type": "password",
"tpl": "questionInputVertical"
}
]

View File

@@ -0,0 +1,96 @@
[{
"idfield": "UUID",
"desc": {
"fr": "identifiant utilisateur",
"en": "user id"
},
"nouservisible": true,
"check": ["required", "unique"],
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "LOGIN",
"check": ["required", "unique"],
"desc": {
"fr": "login",
"en": "login"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "EMAIL",
"desc": {
"fr": "email",
"en": "email"
},
"type": "email",
"check": ["email", "unique"],
"placeholder": "@",
"tpl": "questionInputVertical"
},
{
"idfield": "NAME",
"desc": {
"fr": "Nom",
"en": "Name"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "NICKNAME",
"desc": {
"fr": "Prénom",
"en": "Nickname"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "PSEUDO",
"desc": {
"fr": "pseudo",
"en": "pseudo"
},
"type": "text",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_CREATE",
"desc": {
"fr": "Date de création",
"en": "Create Date"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_UPDATE",
"desc": {
"fr": "Date mise à jour",
"en": "Update date"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_LASTLOGIN",
"desc": {
"fr": "Date de derniére connexion",
"en": "Last date login"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
}
]

View File

@@ -0,0 +1,108 @@
[{
"idfield": "UUID",
"desc": {
"fr": "identifiant de l'items",
"en": "item id"
},
"check": ["required", "unique"],
"type": "text",
"questioncollecte": "questioninput"
},
{
"idfield": "ETAT",
"check": ["required"],
"desc": {
"fr": "Etat",
"en": "Stat"
},
"type": "text",
"questioncollect": "questionselect",
"options": [{
"uuid": "available",
"desc": {
"fr": "Disponible",
"en": "Available"
}
},
{
"uuid": "unavailable",
"desc": {
"fr": "Indisponible",
"en": "Unavailable"
}
}
]
},
{
"idfield": "DESC",
"desc": {
"fr": "Description courte",
"en": "Short description"
},
"questioncollecte": "questioninput"
},
{
"idfield": "DESCLONG",
"desc": {
"fr": "Descrition longue",
"en": "Long description"
},
"questioncollecte": "questioninput"
},
{
"idfield": "URL",
"desc": {
"fr": "URL",
"en": "URL"
},
"desclong": {
"fr": "Lien vers une page de détail de l'items",
"en": "Link to a web page that describe item"
},
"questioncollecte": "questioninput"
},
{
"idfield": "PRICETTC",
"desc": {
"fr": "Prix TTC en cents",
"en": "VAT Price in cents"
},
"check": ["isInt"],
"desclong": {
"fr": "Prix TTC exprimé en cents Prix x 100",
"en": "Price including Tax x 100"
}
},
{
"idfield": "CARACTERES",
"desc": {
"fr": "Objet contennat des caractéres pour mettre une mise en forme",
"en": " Contain list of characteristics to style "
},
"desclong": {
"fr": "Stockage d'information pour décricre des formulaires",
"en": ""
}
},
{
"idfield": "DATE_CREATE",
"desc": {
"fr": "Date de création",
"en": "Create Date"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"questioncollecte": "questioninput"
}, {
"idfield": "DATE_UPDATE",
"desc": {
"fr": "Date mise à jour",
"en": "Update date"
},
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"questioncollecte": "questioninput"
}
]

View File

@@ -0,0 +1,48 @@
[{
"idfield": "UUID",
"desc": {
"fr": "identifiant utilisateur",
"en": "user id"
},
"nouservisible": true,
"check": ["required", "unique"],
"type": "text",
"tpl": "questionInputVertical"
},
{
"nouserupdate": "!(['ADMIN','MANAGER'].includes(contexte.profil))",
"check": ["required"],
"idfield": "appsdiagimmoprofil",
"desc": {
"fr": "Droits"
},
"desclong": {
"fr": "USER ne peut voir que ses informations, MANAGER voit tout le monde et peut modifier les informations, ADMIN accede à tout et peut modifier "
},
"default": "USER",
"values": "profile",
"type": "text",
"tpl": "questionSelect"
},
{
"idfield": "role",
"desc": {
"fr": "Rôle",
"en": "Role"
},
"default": "Autre",
"values": "role",
"tpl": "questionSelect"
},
{
"idfield": "manypseudo",
"desc": {
"fr": "Liste des noms touvés dans liciel séparé par , "
},
"desclong": {
"fr": "Permet de regrouper plusieurs noms sous ce login"
},
"type": "text",
"tpl": "questionInputVertical"
}
]

View File

@@ -0,0 +1,231 @@
[{
"idfield": "UUID",
"nouserupdate": true,
"nouservisible": true,
"desc": {
"fr": "identifiant utilisateur",
"en": "user id"
},
"desclong": {
"fr": "Identifiant unique généré via UUID v4",
"en": "unique Id from a UUID v4"
},
"info": {
"fr": "<p> L'usage d'UUID v4 permet de générer un code unique sans centralisation, car il est basé sur un timestamp et une clé crypto ce qui donne un code du type 7d8291c0-e137-11e8-9f7b-1dc8e57bed33 </p>",
"en": "<p> UUID v4 allow a client to generate a unique code without centralisation, base on a timestamp and a salt it looks like 7d8291c0-e137-11e8-9f7b-1dc8e57bed33</p>"
},
"check": ["required", "unique"],
"type": "text",
"tpl": "input"
},
{
"idfield": "LOGIN",
"nouserupdate": true,
"check": ["required", "unique"],
"desc": {
"fr": "login",
"en": "login"
},
"type": "text",
"tpl": "input",
"info": {
"fr": "<p>Le login doit être unique sur une instance d'apxtrib.</p><p> Pour échanger en dehors d'une instance apxtrib on utilise la clé public du user ou pour un humain login@apxtrib.domain.xx avec le nom du domaine qui heberge l'instance</p><p> Ou encore login@domain.xx tout domain.xx utilisé pour heberger un espace web client /tribeid/www/</p>",
"en": "<p>Login have to be unique into an apxtrib instance</p><p> To exchange outside of an apxtrib instance, we use PublicKey or login@apxtrib.domain.xx or login@domainclient.xx where domain.xx is a apxtrib name server on internet and domain.xx is a tribeid name where a /tribeid/www is available on the net.</p>"
}
},
{
"idfield": "BIOGRAPHY",
"desc": {
"fr": "Vous en quelques mots",
"en": "Few words"
},
"placeholder": {
"fr": "",
"en": ""
},
"rows": 2,
"tpl": "textarea"
},
{
"nouserupdate": true,
"idfield": "PUBLICKEY",
"desc": {
"fr": "Votre clé public pour ce compte",
"en": "Your public key for this uuid"
},
"info": {
"fr": "<p>Cette clé est générée par votre navigateur, garder précisuesement votre clé privée que seule vous connaissez. En cas de perte de cette clé tous vos actifs seront perdus.</p><p>Cette méthode nous permet de vous garantir un contrôle total décentralisé.</p>",
"en": "<p>This key was generated by your browser, keep the private key related to this public key.</p><p>We garanty your total control by this way</p>."
},
"tpl": "textarea"
},
{
"idfield": "IMGAVATAR",
"tpl": "inputimg",
"altimg": "image avatar",
"classimg": "rounded-circle img-responsive mt-2",
"width": 128,
"height:"
128,
"classdivupload": "mt-2",
"classbtn": "btn-primary",
"desc": {
"fr": "changer votre avatar",
"en": "upload an avatar"
},
"info": {
"fr": "Pour un meilleur rendu, une mage carré de 128pc en foat jpg",
"en": "For best results, use an image at least 128px by 128px in .jpg format"
},
},
{
"idfield": "EMAIL",
"desc": {
"fr": "email",
"en": "email"
},
"tpl": "input",
"type": "email",
"check": ["emailadress", "unique"],
"placeholder": {
"fr": "#@",
"en": "@"
}
},
{
"idfield": "NAME",
"desc": {
"fr": "Nom",
"en": "Name"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "NICKNAME",
"desc": {
"fr": "Prénom",
"en": "Nickname"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "PSEUDO",
"desc": {
"fr": "pseudo",
"en": "pseudo"
},
"info": {
"fr": "<p>Nom avec lequel vous souhaitez qu'on vous reconnaisse sur l'instance de l'apxtrib </p><p>Attention ce nom n'est unique que sur une instance d'apxtrib. Un même speudo peut-être utilisé sur un autre serveur pour garantir l'identité vérifié pseudo@ domaine de rattachement.</p>",
"en": "<p>Carrefull a pseudo is unique into an instance of apxtrib to be sure to contact the right person check pseudo@ domain</p>.<p> Pseudo can be changed that is not the case of login.</p>"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "ADDRESS1",
"desc": {
"fr": "Adresse",
"en": "Address"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "1 chemin du paradis",
"123 Main St"
}
},
{
"idfield": "ADDRESS2",
"desc": {
"fr": "Adresse 2",
"en": "Address 2"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "Appartement B",
"Apt B"
}
},
{
"idfield": "CITY",
"desc": {
"fr": "Ville ",
"en": "CITY"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "ZIP",
"desc": {
"fr": "Code Postal",
"en": "ZIP"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "COUNTRY",
"desc": {
"fr": "Pays",
"en": "Country"
},
"tpl": "input",
"type": "text"
},
{
"nouserupdate": true,
"idfield": "DATE_CREATE",
"desc": {
"fr": "Date de création",
"en": "Create Date"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
},
{
"nouserupdate": true,
"idfield": "DATE_UPDATE",
"desc": {
"fr": "Date mise à jour",
"en": "Update date"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
},
{
"nouserupdate": true,
"idfield": "DATE_LASTLOGIN",
"desc": {
"fr": "Date de derniére connexion",
"en": "Last date login"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
},
{
"idfield": "ACCESSRIGHTS",
"nouserupdate": true,
"desc": {
"fr": "Vos droits d'accès",
"en": "Your access rights"
},
"default": {
"app": {},
"data": {
"tribeidname": {
"users": "O"
}
}
},
"tpl": "jsoneditor"
}
]

View File

@@ -0,0 +1,231 @@
[{
"idfield": "UUID",
"nouserupdate": true,
"nouservisible": true,
"desc": {
"fr": "identifiant utilisateur",
"en": "user id"
},
"desclong": {
"fr": "Identifiant unique généré via UUID v4",
"en": "unique Id from a UUID v4"
},
"info": {
"fr": "<p> L'usage d'UUID v4 permet de générer un code unique sans centralisation, car il est basé sur un timestamp et une clé crypto ce qui donne un code du type 7d8291c0-e137-11e8-9f7b-1dc8e57bed33 </p>",
"en": "<p> UUID v4 allow a client to generate a unique code without centralisation, base on a timestamp and a salt it looks like 7d8291c0-e137-11e8-9f7b-1dc8e57bed33</p>"
},
"check": ["required", "unique"],
"type": "text",
"tpl": "input"
},
{
"idfield": "LOGIN",
"nouserupdate": true,
"check": ["required", "unique"],
"desc": {
"fr": "login",
"en": "login"
},
"type": "text",
"tpl": "input",
"info": {
"fr": "<p>Le login doit être unique sur une instance d'apxtrib.</p><p> Pour échanger en dehors d'une instance apxtrib on utilise la clé public du user ou pour un humain login@apxtrib.domain.xx avec le nom du domaine qui heberge l'instance</p><p> Ou encore login@domain.xx tout domain.xx utilisé pour heberger un espace web client /tribeid/www/</p>",
"en": "<p>Login have to be unique into an apxtrib instance</p><p> To exchange outside of an apxtrib instance, we use PublicKey or login@apxtrib.domain.xx or login@domainclient.xx where domain.xx is a apxtrib name server on internet and domain.xx is a tribeid name where a /tribeid/www is available on the net.</p>"
}
},
{
"idfield": "BIOGRAPHY",
"desc": {
"fr": "Vous en quelques mots",
"en": "Few words"
},
"placeholder": {
"fr": "",
"en": ""
},
"rows": 2,
"tpl": "textarea"
},
{
"nouserupdate": true,
"idfield": "PUBLICKEY",
"desc": {
"fr": "Votre clé public pour ce compte",
"en": "Your public key for this uuid"
},
"info": {
"fr": "<p>Cette clé est générée par votre navigateur, garder précisuesement votre clé privée que seule vous connaissez. En cas de perte de cette clé tous vos actifs seront perdus.</p><p>Cette méthode nous permet de vous garantir un contrôle total décentralisé.</p>",
"en": "<p>This key was generated by your browser, keep the private key related to this public key.</p><p>We garanty your total control by this way</p>."
},
"tpl": "textarea"
},
{
"idfield": "IMGAVATAR",
"tpl": "inputimg",
"altimg": "image avatar",
"classimg": "rounded-circle img-responsive mt-2",
"width": 128,
"height:"
128,
"classdivupload": "mt-2",
"classbtn": "btn-primary",
"desc": {
"fr": "changer votre avatar",
"en": "upload an avatar"
},
"info": {
"fr": "Pour un meilleur rendu, une mage carré de 128pc en foat jpg",
"en": "For best results, use an image at least 128px by 128px in .jpg format"
},
},
{
"idfield": "EMAIL",
"desc": {
"fr": "email",
"en": "email"
},
"tpl": "input",
"type": "email",
"check": ["emailadress", "unique"],
"placeholder": {
"fr": "#@",
"en": "@"
}
},
{
"idfield": "NAME",
"desc": {
"fr": "Nom",
"en": "Name"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "NICKNAME",
"desc": {
"fr": "Prénom",
"en": "Nickname"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "PSEUDO",
"desc": {
"fr": "pseudo",
"en": "pseudo"
},
"info": {
"fr": "<p>Nom avec lequel vous souhaitez qu'on vous reconnaisse sur l'instance de l'apxtrib </p><p>Attention ce nom n'est unique que sur une instance d'apxtrib. Un même speudo peut-être utilisé sur un autre serveur pour garantir l'identité vérifié pseudo@ domaine de rattachement.</p>",
"en": "<p>Carrefull a pseudo is unique into an instance of apxtrib to be sure to contact the right person check pseudo@ domain</p>.<p> Pseudo can be changed that is not the case of login.</p>"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "ADDRESS1",
"desc": {
"fr": "Adresse",
"en": "Address"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "1 chemin du paradis",
"123 Main St"
}
},
{
"idfield": "ADDRESS2",
"desc": {
"fr": "Adresse 2",
"en": "Address 2"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "Appartement B",
"Apt B"
}
},
{
"idfield": "CITY",
"desc": {
"fr": "Ville ",
"en": "CITY"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "ZIP",
"desc": {
"fr": "Code Postal",
"en": "ZIP"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "COUNTRY",
"desc": {
"fr": "Pays",
"en": "Country"
},
"tpl": "input",
"type": "text"
},
{
"nouserupdate": true,
"idfield": "DATE_CREATE",
"desc": {
"fr": "Date de création",
"en": "Create Date"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
},
{
"nouserupdate": true,
"idfield": "DATE_UPDATE",
"desc": {
"fr": "Date mise à jour",
"en": "Update date"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
},
{
"nouserupdate": true,
"idfield": "DATE_LASTLOGIN",
"desc": {
"fr": "Date de derniére connexion",
"en": "Last date login"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
},
{
"idfield": "ACCESSRIGHTS",
"nouserupdate": true,
"desc": {
"fr": "Vos droits d'accès",
"en": "Your access rights"
},
"default": {
"app": {},
"data": {
"tribeidname": {
"users": "O"
}
}
},
"tpl": "jsoneditor"
}
]

View File

@@ -0,0 +1,93 @@
[{
"idfield": "UUID",
"desc": "identifiant utilisateur",
"nouservisible": true,
"check": [
"required",
"unique"
],
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "LOGIN",
"check": [
"required",
"unique"
],
"desc": "login",
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "EMAIL",
"desc": "email",
"type": "email",
"check": [
"emailadress",
"unique"
],
"placeholder": "@",
"tpl": "questionInputVertical"
},
{
"idfield": "NAME",
"desc": "Nom",
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "NICKNAME",
"desc": "Prénom",
"type": "text",
"tpl": "questionInputVertical"
},
{
"idfield": "PSEUDO",
"desc": "pseudo",
"type": "text",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_CREATE",
"desc": "Date de création",
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_UPDATE",
"desc": "Date mise à jour",
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
},
{
"nouserupdate": true,
"idfield": "DATE_LASTLOGIN",
"desc": "Date de derniére connexion",
"type": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')",
"tpl": "questionInputVertical"
},
{
"idfield": "accessrights",
"type": "json",
"nouserupdate": true,
"desc": "Vos droits d'accès",
"default": {
"app": {},
"data": {
"tribeidname": {
"users": "O"
}
}
},
"tpl": "jsoneditor"
}
]

View File

@@ -0,0 +1,3 @@
{
"phc@ndda.fr": "admin"
}

View File

@@ -0,0 +1,3 @@
{
"admin": "admin"
}

View File

@@ -0,0 +1,19 @@
{
"admin": [
"admin",
"phc@ndda.fr",
"$2b$10$hFV/33UixB3Cn.XzLIhmTeRYU2XThnxYuwCVIifwQ7v/yCtRLIsuq",
{
"app": {
"apxtrib:webapp": "admin"
},
"data": {
"apxtrib": {
"users": "CRUDO",
"referentials": "CRUDO"
}
}
},
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHBpcmF0aW9uIjoiMjAyMS0wOS0wMlQxMjo0ODowMS41OTNaIiwiVVVJRCI6IjEyM2FiYyJ9.4daX42iXqoblGL0c1Ga-hs5tGIoJEuWBSKqCayA-qNk"
]
}

View File

@@ -0,0 +1,48 @@
<html data-tribeid="apxtrib" data-website="webapp" data-nametpl="app" data-pagename="index_fr" data-pageneedauthentification="false" data-pageredirectforauthentification="fullscreen_auth" data-urlbackoffice="apxtrib.ndda.fr" lang="fr" data-env="prod" data-version="1640518264045">
<head><!--head -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="author" content="">
<meta name="email" content="">
<meta name="keywords" content="">
<title>Need-Data manager></title>
<link rel="icon" type="image/png" href="static&#x2F;img&#x2F;icons&#x2F;iconX74x74.png">
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" type="text/css" href="static&#x2F;fonts&#x2F;icofont&#x2F;icofont.min.css">
<link rel="stylesheet" type="text/css" href="css&#x2F;app&#x2F;styles.css">
<!-- /head-->
</head>
<body>
<div class="wrapper"> <!-- div class="wrapper" -->
<nav id="sidebar" class="sidebar"></nav>
<div class="main">
<nav id ="navbar" class="navbar navbar-expand navbar-light navbar-bg"></nav>
<main class="content">
<div class="container-fluid p-0" id="maincontent"> <!-- div id="maincontent" class="container-fluid p-0" -->
<p>Content when js is desactivated</p>
<!--/div-->
</div>
</main>
<footer class="footer">
<div class="container-fluid">
<div class="row text-muted">
<div class="col-6 text-start">
<p class="mb-0">
<a href="" class="text-muted"><strong></strong></a> &copy;
</p>
</div>
<div class="col-6 text-end">
<ul class="list-inline">
</ul>
</div>
</div>
</div>
</footer>
</div>
<!-- /div -->
</div>
<script src='js/simplebar.min.js'></script><script src='js/feather.min.js'></script><script src='js/bootstrap.js'></script><script src='js/axios.min.js'></script><script src='js/mustache.min.js'></script><script src='js/Checkjson.js'></script><script src='js/auth.js'></script><script src='js/state.js'></script><script src='js/main.js'></script><script src='js/notification.js'></script></body></html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
[data-simplebar]{position:relative;flex-direction:column;flex-wrap:wrap;justify-content:flex-start;align-content:flex-start;align-items:flex-start}.simplebar-wrapper{overflow:hidden;width:inherit;height:inherit;max-width:inherit;max-height:inherit}.simplebar-mask{direction:inherit;position:absolute;overflow:hidden;padding:0;margin:0;left:0;top:0;bottom:0;right:0;width:auto!important;height:auto!important;z-index:0}.simplebar-offset{direction:inherit!important;box-sizing:inherit!important;resize:none!important;position:absolute;top:0;left:0;bottom:0;right:0;padding:0;margin:0;-webkit-overflow-scrolling:touch}.simplebar-content-wrapper{direction:inherit;box-sizing:border-box!important;position:relative;display:block;height:100%;width:auto;max-width:100%;max-height:100%;scrollbar-width:none;-ms-overflow-style:none}.simplebar-content-wrapper::-webkit-scrollbar,.simplebar-hide-scrollbar::-webkit-scrollbar{width:0;height:0}.simplebar-content:after,.simplebar-content:before{content:' ';display:table}.simplebar-placeholder{max-height:100%;max-width:100%;width:100%;pointer-events:none}.simplebar-height-auto-observer-wrapper{box-sizing:inherit!important;height:100%;width:100%;max-width:1px;position:relative;float:left;max-height:1px;overflow:hidden;z-index:-1;padding:0;margin:0;pointer-events:none;flex-grow:inherit;flex-shrink:0;flex-basis:0}.simplebar-height-auto-observer{box-sizing:inherit;display:block;opacity:0;position:absolute;top:0;left:0;height:1000%;width:1000%;min-height:1px;min-width:1px;overflow:hidden;pointer-events:none;z-index:-1}.simplebar-track{z-index:1;position:absolute;right:0;bottom:0;pointer-events:none;overflow:hidden}[data-simplebar].simplebar-dragging .simplebar-content{pointer-events:none;user-select:none;-webkit-user-select:none}[data-simplebar].simplebar-dragging .simplebar-track{pointer-events:all}.simplebar-scrollbar{position:absolute;left:0;right:0;min-height:10px}.simplebar-scrollbar:before{position:absolute;content:'';background:#000;border-radius:7px;left:2px;right:2px;opacity:0;transition:opacity .2s linear}.simplebar-scrollbar.simplebar-visible:before{opacity:.5;transition:opacity 0s linear}.simplebar-track.simplebar-vertical{top:0;width:11px}.simplebar-track.simplebar-vertical .simplebar-scrollbar:before{top:2px;bottom:2px}.simplebar-track.simplebar-horizontal{left:0;height:11px}.simplebar-track.simplebar-horizontal .simplebar-scrollbar:before{height:100%;left:2px;right:2px}.simplebar-track.simplebar-horizontal .simplebar-scrollbar{right:auto;left:0;top:2px;height:7px;min-height:0;min-width:10px;width:auto}[data-simplebar-direction=rtl] .simplebar-track.simplebar-vertical{right:auto;left:0}.hs-dummy-scrollbar-size{direction:rtl;position:fixed;opacity:0;visibility:hidden;height:500px;width:500px;overflow-y:hidden;overflow-x:scroll}.simplebar-hide-scrollbar{position:fixed;left:0;visibility:hidden;overflow-y:scroll;scrollbar-width:none;-ms-overflow-style:none}

View File

@@ -0,0 +1,169 @@
<html data-tribeid="apxtrib" data-website="webapp" data-nametpl="fullscreen" data-pagename="auth_fr" data-pageneedauthentification="false" data-pageredirectforauthentification="fullscreen_auth" data-urlbackoffice="apxtrib.ndda.fr" lang="fr" data-env="prod" data-version="1640518263426">
<head><!--head -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="author" content="">
<meta name="email" content="">
<meta name="keywords" content="">
<title>apxtrib></title>
<link rel="stylesheet" type="text/css" href="static&#x2F;fonts&#x2F;icofont&#x2F;icofont.min.css">
<link rel="stylesheet" type="text/css" href="css&#x2F;fullscreen&#x2F;styles.css">
<!-- /head-->
</head>
<body data-theme="dark" data-layout="fluid" data-sidebar-position="left" data-sidebar-layout="default">
<main class="d-flex w-100 h-100"><!-- main class="d-flex w-100 h-100" -->
<div class="container d-flex flex-column">
<div class="row vh-100">
<div class="col-sm-10 col-md-8 col-lg-6 mx-auto d-table h-100">
<div class="d-table-cell align-middle"><!--div class="d-table-cell align-middle"-->
<div id="signin">
<div class="text-center mt-4">
<h1 class="h2">Need-Data</h1>
<p class="lead">
Votre hébergement apxtrib en toute confidentialité.
</p>
</div>
<div class="card">
<div class="card-body">
<div class="m-sm-4">
<div class="text-center">
<img src="static/img/logo/ndda.png" alt="logo apxtrib" class="img-fluid" width="132" height="132" />
</div>
<form>
<div class="mb-3">
<label class="form-label">Identifiant</label>
<input class="form-control form-control-lg" type="text" name="login" value="adminapxtrib" placeholder="Votre identifiant (login ou clé public)" />
</div>
<div class="mb-3">
<label class="form-label">Mot de passe</label>
<input class="form-control form-control-lg" type="password" name="password"
value="Trze3aze!" placeholder="Mot de passe ou hash sur clé public" />
<small>
<a onclick="pwa.auth.route('#resetpsw');return false;">Mot de passe oublié?</a>
</small>
</div>
<div>
<label class="form-check">
<input class="form-check-input" type="checkbox" value="rememberme" name="rememberme" checked>
<span class="form-check-label">
Se souvenir de moi sur ce navigateur
</span>
</label>
</div>
<div class="text-center mt-3">
<button
class="btn btn-lg btn-primary"
onclick="pwa.auth.login();return false;"
data-msgok="Bienvenu dans votre espace."
data-msgko="Désolé, vos identifiants ne sont pas valides! Au bout de 3 echecs, vous devrez attendre 1 minute entre chaque tentative."
data-routeto="app_index_fr.html"
>S'authentifier
</button>
<p class="msginfo"></p>
</div>
</form>
<p class="text-center">
<small>
<a onclick="pwa.auth.route(&#39;#register&#39;);return false;" >Rejoindre un espace</a>
</small>
</p>
</div>
</div>
</div>
</div>
<div id="resetpsw" class="d-none">
<div class="text-center mt-4">
<h1 class="h2">Mot de passe oublié</h1>
<p class="lead">
Indiquez votre email. Si vous n'avez pas indiqué d'email, vos données sont definitivement perdues, c'est le prix de votre anonymat.
</p>
</div>
<div class="card">
<div class="card-body">
<div class="m-sm-4">
<form>
<div class="mb-3">
<label class="form-label">Email</label>
<input class="form-control form-control-lg" type="email" name="email" placeholder="indiquez votre email">
</div>
<div class="text-center mt-3">
<button
onclick="pwa.auth.forget();return false;"
data-msgok="Une email vous a été envoyé"
data-msgko="Lien impossible à envoyer "
class="btn btn-lg btn-primary">
Recevoir un lien temporaire
</button>
<p class="msginfo"></p>
</div>
</form>
</div>
</div>
</div>
</div>
<div id="register" class="d-none">
<div class="d-table-cell align-middle">
<div class="text-center mt-4">
<h1 class="h2">S'inscrire à votre communauté</h1>
<p class="lead">
Vous devez disposez du nom de la communauté qui vous invite à ouvrir un compte chez elle.<br> Pour créer une communauté, sur cet hébergement: <a href='mailto:contact@need-data.com'>contact@need-data.com</a>.<br> En savoir plus sur <a href='https://apxtrib.org/anonymat_avec_apixppress.html'>Anomymat & apxtrib</a>.
</p>
</div>
<div class="card">
<div class="card-body">
<div class="m-sm-4">
<form>
<div class="mb-3">
<label class="form-label">Nom de communauté</label>
<input class="form-control form-control-lg" type="text" name="company" placeholder="Nom communiqué par la personne qui vous invite.">
</div>
<div class="text-center mt-3">
<button
onclick="pwa.auth.autologin();return false;"
class="btn btn-lg btn-primary">
Génerer une paire<br> de clé public/privée
</button>
</div>
<div class="mb-3">
<label class="form-label">Votre identifiant</label>
<input class="form-control form-control-lg" type="text" name="login" placeholder="login ou clé public">
</div>
<div class="mb-3">
<label class="form-label">Mot de passe</label>
<input class="form-control form-control-lg" type="password" name="password" placeholder="Mot de passe ou hash sur clé public">
</div>
<div class="mb-3">
<label class="form-label">Email (optionel)</label>
<input class="form-control form-control-lg" type="email" name="email" placeholder="Sans email impossible de ré-initialiser son compte">
</div>
<div class="text-center mt-3">
<button
onclick="pwa.auth.register();return false;"
data-msgok="Votre compte a bien été créé"
data-msgko="Désolé impossible de créer le compte"
class="btn btn-lg btn-primary">
S'enregistrer
</button>
<p class="msginfo"></p>
<p class="text-center">
<small>
<a onclick="pwa.auth.route(&#39;#signin&#39;);return false;" >S'authentifier</a>
</small>
</p>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- /div-->
</div>
</div>
</div>
</div>
<!-- /main -->
</main>
<script src='js/simplebar.min.js'></script><script src='js/feather.min.js'></script><script src='js/bootstrap.js'></script><script src='js/axios.min.js'></script><script src='js/mustache.min.js'></script><script src='js/Checkjson.js'></script><script src='js/auth.js'></script><script src='js/state.js'></script><script src='js/main.js'></script><script src='js/notification.js'></script><script src='js/auth.js'></script></body></html>

View File

@@ -0,0 +1,216 @@
"use strict";
var pwa = pwa || {};
/*
Manage user authentification and registration
________________________
pwa.auth.route()
manage from state.json route if authenticated or not
redirect public page or app page
________________________
pwa.auth.screenlogin()
show login modal
________________________
pwa.auth.getlinkwithoutpsw()
special get with token and uuid workeable for 24h this link is une onetime
_________________________
pwa.auth.isAuthenticate()
test if token is still ok or not return false/true
_________________________
pwa.auth.authentification({LOGIN,PASSWORD})
if ok => load pwa.state.data.app .headers .userlogin
_________________________
pwa.auth.login()
Manage login modal to get login psw value and submit it to pwa.auth.authentification()
_________________________
pwa.auth.logout()
Remove localstorage and reload
_________________________
pwa.auth.register()
@TODO
__________________________
pwa.auth.forgetpsw()
Request to send an email with a unique get link to access from this link to the app
*/
/*MODULEJS*/
//--##
pwa.auth = {};
// Refresh browser state if exist else get pwa.state defaults
//pwa.state.ready( pwa.auth.check );
pwa.auth.Checkjson = () => {
if( pwa.state.data.login.isAuthenticated ) {
if( !pwa.auth.isAuthenticate() ) {
// Then reinit local storage and refresh page
pwa.state.data.login.isAuthenticated = false;
pwa.state.save();
//alert( 'reload page cause no more auth' )
window.location.reload();
};
}
};
pwa.auth.route = ( destination ) => {
console.log( 'auth.route to', destination );
//if check Authenticated && exist #signin button[data-routeto] then redirect browser to button[data-routeto]
//else manage component action auth
if( pwa.state && pwa.state.data && pwa.state.data.login && pwa.state.data.login.isAuthenticated ) {
if( destination )
window.location.pathname = `${pwa.state.data.ctx.urlbase}/${destination}`;
} else {
[ "#signin", "#resetpsw", "#register" ].forEach( e => {
if( e == destination ) {
document.querySelector( e )
.classList.remove( 'd-none' );
} else {
document.querySelector( e )
.classList.add( 'd-none' );
}
} )
}
}
pwa.auth.isAuthenticate = async function () {
// in any request, if middleware isAuthenticated return false
// then headers Xuuid is set to 1
// then try pwa.auth.isAuthenticate if rememberMe auto reconnect
// if jwt is ok then return true in other case => false
// this is the first test then depending of action see ACCESSRIGHTS of user
console.log( 'lance isauth', {
headers: pwa.state.data.headers.xpaganid
} )
//alert( 'uuid ' + pwa.state.data.headers.xpaganid )
console.log( `https://${pwa.state.data.ctx.urlbackoffice}/users/isauth`, {
headers: pwa.state.data.headers
} )
try {
const repisauth = await axios.get( `https://${pwa.state.data.ctx.urlbackoffice}/users/isauth`, {
headers: pwa.state.data.headers
} )
console.log( repisauth )
console.log( 'isAauthenticate: yes' )
return true;
} catch ( err ) {
if( err.response ) { console.log( "response err ", err.response.data ) }
if( err.request ) { console.log( "request err", err.request ) }
console.log( 'isAuthenticate: no' )
pwa.state.data.headers.xpaganid = "1";
if( pwa.state.data.login.rememberMe.login ) {
if( await pwa.auth.authentification( pwa.state.data.login.rememberMe ) ) {
return await pwa.auth.isAuthenticate();
};
}
return false;
}
};
pwa.auth.authentification = async function ( data ) {
// Core client function to chech auth from login & psw
// In case of 403 error lauch pwa.authentification(pwa.app.rememberMe)
// in case of sucess update paw.state.data.login
console.groupCollapsed( "Post Authentification for standard on : https://" + pwa.state.data.ctx.urlbackoffice + "/users/login param data", data )
console.log( 'header de login', pwa.state.data.headers )
let auth;
try {
auth = await axios.post( `https://${pwa.state.data.ctx.urlbackoffice }/users/login`, data, {
headers: pwa.state.data.headers
} );
console.log( "retour de login successfull ", auth );
//Maj variable globale authentifié
pwa.state.data.headers.xpaganid = auth.data.payload.data.UUID;
pwa.state.data.headers.xauth = auth.data.payload.data.TOKEN;
pwa.state.data.headers.xtribe = auth.data.payload.data.tribeid;
pwa.state.data.headers.xworkon = auth.data.payload.data.tribeid;
// Save local authentification uuid/token info user
pwa.state.data.login.user = auth.data.payload.data;
//request a refresh after a login
pwa.state.data.ctx.refreshstorage = true;
pwa.state.save();
//alert( 'pwa.state.save() fait avec uuid' + pwa.state.data.headers.xpaganid )
console.groupEnd();
return true;
} catch ( err ) {
if( err.response ) { console.log( "resp", err.response.data ) }
if( err.request ) { console.log( "req", err.request.data ) }
console.log( 'erreur de login reinit de rememberMe', err )
pwa.state.data.login.rememberMe = {};
document.querySelector( "#signin p.msginfo" )
.innerHTML = document.querySelector( "#signin [data-msgko]" )
.getAttribute( 'data-msgko' );
console.groupEnd();
return false;
}
};
pwa.auth.logout = function () {
console.log( "remove ", pwa.state.data.ctx.website );
localStorage.removeItem( pwa.state.data.ctx.website );
window.location.href = "/";
}
pwa.auth.login = async function () {
/*
Check login/psw
see auth.mustache & data_auth_lg.json for parameters
Context info used:
#signin p.msginfo contain message interaction with user
#signin data-msgok data-msgko
#signin button[data-routeto] is a redirection if authentification is successful
*/
document.querySelector( '#signin p.msginfo' )
.innerHTML = "";
const data = {
LOGIN: document.querySelector( "#signin input[name='login']" )
.value,
PASSWORD: document.querySelector( "#signin input[name='password']" )
.value
}
console.log( 'check password', Checkjson.test.password( "", data.PASSWORD ) )
if( data.LOGIN.length < 4 || !Checkjson.test.password( "", data.PASSWORD ) ) {
/*$("#loginpart p.msginfo")
.html("")
.fadeOut(2000)*/
document.querySelector( '#signin p.msginfo' )
.innerHTML = document.querySelector( '#signin [data-msgko]' )
.getAttribute( 'data-msgko' );
} else {
if( document.querySelector( "[name='rememberme']" )
.checked ) {
pwa.state.data.login.rememberMe = data;
}
if( await pwa.auth.authentification( data ) ) {
console.log( 'Authentification VALIDE' )
document.querySelector( '#signin p.msginfo' )
.innerHTML = document.querySelector( "#signin [data-msgok]" )
.getAttribute( 'data-msgok' );
//state l'état isAuthenticated et check la route
pwa.state.data.login.isAuthenticated = true;
pwa.state.save();
console.log( pwa.state.data.login )
console.log( 'Auth ok route to ', document.querySelector( '#signin button[data-routeto]' )
.getAttribute( 'data-routeto' ) );
pwa.auth.route( document.querySelector( '#signin button[data-routeto]' )
.getAttribute( 'data-routeto' ) );
}
}
};
pwa.auth.register = async function ( event ) {
event.preventDefault();
// gérer la cration du user
}
pwa.auth.forgetpsw = async function ( event ) {
event.preventDefault();
const tribeid = $( ".loginregister" )
.getAttribute( "data-tribeid" );
const email = $( '.forgetpsw .email' )
.val();
console.log( `Reinit email: ${email} for tribeid: ${tribeid}` )
try {
console.log( `https://${pwa.state.data.ctx.urlbackoffice }/users/getlinkwithoutpsw/${email}` )
const reinit = await axios.get( `https://${pwa.state.data.ctx.urlbackoffice }/users/getlinkwithoutpsw/${email}`, {
headers: pwa.state.data.headers
} )
$( "#forgetpswpart p.msginfo" )
.html( "Regardez votre boite email" );
return true;
} catch ( er ) {
console.log( "Pb d'accès au back check apiamaildigit" )
return false;
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
/*
This module have to be independant of any external package
it is shared between back and front and is usefull
to apply common check in front before sending it in back
can be include in project with
<script src="https://apiback.maildigit.fr/js/Checkjson.js"></script>
or with const Checkjson = require('../public/js/Checkjson.js')
*/
// --##
const Checkjson = {};
// each Checkjson.test. return true or false
Checkjson.test = {};
Checkjson.test.emailadress = ( ctx, email ) => {
const regExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regExp.test( email );
};
/*
* @emaillist = "email1,email2, email3"
* it check if each eamil separate by , are correct
*/
Checkjson.test.emailadresslist = ( ctx, emaillist ) => {
//console.log(emaillist.split(','))
if( emaillist.length > 0 ) {
const emails = emaillist.split( ',' );
for( var i in emails ) {
//console.log(emails[i])
if( !Checkjson.test.emailadress( "", emails[ i ].trim() ) ) {
return false
}
}
};
return true;
};
Checkjson.test.password = ( ctx, pwd ) => {
const regExp = new RegExp(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/
);
return regExp.test( pwd );
};
Checkjson.test.required = ( ctx, val ) =>
val != null && val != 'undefined' && val.length > 0;
Checkjson.test.isNumber = ( ctx, n ) => typeof n === 'number';
Checkjson.test.isInt = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) == n;
Checkjson.test.isFloat = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) != n;
Checkjson.test.unique = ( ctx, val ) => {
if( ctx.list[ ctx.currentfield ] ) {
return !ctx.list[ ctx.currentfield ].includes( val );
} else {
console.log( 'ERR no list for field:' + ctx.currentfield );
return false;
}
};
Checkjson.test.isDateDay = ( ctx, dateDay ) => true;
/* Checkjson.test.filterInvalidInArray = (array, validate) =>
array ? array.filter(el => !validate(el)) : true;
// return true when every elements is valid
*/
Checkjson.test.postalCode = ( ctx, postalCode ) => {
if( postalCode.length == 0 ) return true;
const regExp = new RegExp( /(^\d{5}$)|(^\d{5}-\d{4}$)/ );
return regExp.test( postalCode );
};
/**
* PHONE
*/
Checkjson.test.phoneNumber = ( ctx, phoneNumber ) => {
if( phoneNumber.length == 0 ) return true;
phoneNumber = phoneNumber.trim()
.replace( /[- .]/g, '' )
//french number
const regExpfr = new RegExp( /^0[1-9][0-9]{9}$/ );
const regExpInternational = new RegExp( /^\+*(\d{3})*[0-9,\-]{8,}/ );
return regExpfr.test( phoneNumber ) || regExpInternational.test( phoneNumber );
};
/*
* @phonelist = "phone1,phone2,phone3"
* it check if each phone separate by , are correct
*/
Checkjson.test.phoneNumberlist = ( ctx, phonelist ) => {
//console.log(emaillist.split(','))
if( phonelist.length > 0 ) {
const phones = phonelist.split( ',' );
for( var i in phones ) {
//console.log(emails[i])
if( !Checkjson.test.phoneNumber( "", phones[ i ].trim() ) ) {
return false
}
}
};
return true;
};
// Checkjson.normalize take a correct data then reformat it to harmonise it
Checkjson.normalize = {};
Checkjson.normalize.phoneNumber = ( ctx, phone ) => {
phone = phone.trim()
.replace( /[- .]/g, '' );
if( Checkjson.test.phoneNumber( '', phone ) && phone.length == 10 && phone[ 0 ] == "0" ) {
phone = '+33 ' + phone.substring( 1 );
}
return phone;
}
Checkjson.normalize.upperCase = ( ctx, txt ) => txt.toUpperCase();
Checkjson.normalize.lowerCase = ( ctx, txt ) => txt.toLowerCase();
// fixe 10 position et complete par des 0 devant
Checkjson.normalize.zfill10 = ( ctx, num ) => {
let s = num + '';
while( s.length < 10 ) s = '0' + s;
return s;
};
/*let tt = "+33 1 02.03 04 05";
console.log(Checkjson.test.phoneNumber('', tt))
console.log(Checkjson.normalize.phoneNumber('', tt))
*/
Checkjson.evaluate = ( contexte, referential, data ) => {
/*
* contexte object {} with full info for evaluation
* file referential path to get object to apply
* data related to object
- return {validefor =[keyword of error] if empty no error,
clean data eventually reformated
updateDatabase}
*/
console.log( 'contexte', contexte );
console.log( 'referentiel', referential );
console.log( 'data', data );
const invalidefor = [];
const objectdef = {};
const listfield = referential.map( ch => {
objectdef[ ch.idfield ] = ch;
return ch.idfield;
} );
Object.keys( data )
.forEach( field => {
if( !listfield.includes( field ) ) {
// some data can be inside an object with no control at all
// they are used for process only
// i leave it in case it will become a non sens
// invalidefor.push('ERRFIELD unknown of referentials ' + field);
} else {
if( objectdef[ field ].check ) {
// check data with rule list in check
objectdef[ field ].Checkjson.forEach( ctrl => {
console.log( 'ctrl', ctrl );
contexte.currentfield = field;
if( !Checkjson.test[ ctrl ] ) {
invalidefor.push( 'ERR check function does not exist :' + ctrl + '___' + field )
} else {
if( !Checkjson.test[ ctrl ]( contexte, data[ field ] ) )
invalidefor.push( 'ERR' + ctrl + '___' + field );
}
} );
}
if( objectdef[ field ].nouserupdate ) {
// check if user can modify this information
console.log(
'evaluation :' + field + ' -- ' + objectdef[ field ].nouserupdate,
eval( objectdef[ field ].nouserupdate )
);
const evalright = eval( objectdef[ field ].nouserupdate );
objectdef[ field ].nouserupdate = evalright;
}
}
} );
console.log( {
invalidefor,
data
} );
return {
invalidefor,
data
};
};
if( typeof module !== 'undefined' ) module.exports = Checkjson;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,150 @@
/*
After state.js / auth.js and all external js lib
Load
*/
var pwa = pwa || {};
pwa.main = pwa.main || {};
pwa.main.tpldata = pwa.main.tpldata || {};
pwa.main.tpl = pwa.main.tpl || {};
pwa.main.tpldata = pwa.main.tpldata || {};
pwa.main.ref = pwa.main.ref || {};
pwa.main.tpl.appsidebarmenu = 'static/components/appmesa/appsidebarmenu.mustache';
pwa.main.tpl.apptopbarmenu = 'static/components/appmesa/apptopbarmenu.mustache';
pwa.main.tpldata.sidebar = `static/components/appmesa/data_sidebar`;
pwa.main.tpldata.topbar = `static/components/appmesa/data_topbar`;
pwa.main.tpldata.topbarLogin = `static/components/appmesa/data_topbarLogin`;
pwa.main.init = () => {
const isempty = ( obj ) => {
return obj && Object.keys( obj )
.length === 0 && obj.constructor === Object
}
// Load public env tpl & tpldata
//Manage action depending of html file currently show
const currenthtml = location.pathname.split( '/' )
.at( -1 );
console.groupCollapsed( `pwa.main.init for ${currenthtml} html page` );
if( currenthtml.includes( 'app_' ) ) {
pwa.main.loadmenu()
}
// To manage if authenticated or not in a simple way
/*
if( pwa.state.data.login.isAuthenticated ) {
//authenticated
// identity inside pwa.state.login.user
}else{
//anonymous
// can check if the url is relevant with isAuthenticated
//route to app if exist data-routo into a signin
//pwa.auth.route( document.querySelector( '#signin button[data-routeto]' ).getAttribute( 'data-routeto' ) );
// add and load dynamicaly the gui.js plugin if user that request it has access
}
*/
console.groupEnd();
};
pwa.main.loadmenu = async () => {
console.log( 'pwa.main.loadmenu running' );
console.log( 'Status of pwa.state.data.login.isAuthenticated =', pwa.state.data.login.isAuthenticated );
let datasidebar, datatopbar;
/* Build datasidebar and datatopbar depending of list of module allowed by user in his ACCESSRIGHTS profil.
app[`${pwa.state.data.ctx.tribeid}:${pwa.state.data.ctx.website}`].js;
*/
//console.log( 'List of tpldata', pwa.main.tpldata )
//console.log( 'List of tpl', pwa.main.tpl )
console.log( `run pwa.state.loadfile with pwa.state.data.ctx.refreshstorage = ${pwa.state.data.ctx.refreshstorage} if true=> refresh anyway, if false refresh only if dest.name does not exist` );
await pwa.state.loadfile( pwa.main.tpl, 'tpl' );
await pwa.state.loadfile( pwa.main.tpldata, 'tpldata' );
datasidebar = pwa.state.data.tpldata.sidebar;
//any tpldata containing sidebar in pwa.state.data.tpldata is add to sbgroupmenu to be available
Object.keys( pwa.state.data.tpldata )
.filter( m => ( m != 'sidebar' && m.includes( 'sidebar' ) ) )
.some( k => {
datasidebar.sbgroupmenu.push( pwa.state.data.tpldata[ k ] )
} );
//merge les menu topbar
datatopbar = pwa.state.data.tpldata.topbar;
if( pwa.state.data.login.isAuthenticated ) {
// update user information if needed
datatopbar.name = pwa.state.data.login.user.LOGIN;
datatopbar.avatarimg = pwa.state.data.login.user.AVATARIMG;
delete pwa.state.data.tpldata.topbarLogin;
pwa.state.save();
}
datatopbar.menuprofil = [];
Object.keys( pwa.state.data.tpldata )
.filter( m => ( m != 'topbar' && m.includes( 'topbar' ) ) )
.some( k => {
datatopbar.menuprofil.push( pwa.state.data.tpldata[ k ] )
} );
if( pwa.state.data.tpl.appsidebarmenu ) {
document.querySelector( "#sidebar" )
.innerHTML = Mustache.render( pwa.state.data.tpl.appsidebarmenu, datasidebar )
document.querySelector( "#navbar" )
.innerHTML = Mustache.render( pwa.state.data.tpl.apptopbarmenu, datatopbar )
//active les icones svg de feather
feather.replace();
//active scroll presentation + sidebar animation
pwa.main.simplebar();
pwa.main.clickactive();
};
};
//////////////////////////////////////////////////////
// simplebar
//////////////////////////////////////////////////////
pwa.main.simplebar = () => {
const simpleBarElement = document.getElementsByClassName( "js-simplebar" )[ 0 ];
if( simpleBarElement ) {
/* Initialize simplebar */
new SimpleBar( document.getElementsByClassName( "js-simplebar" )[ 0 ] )
const sidebarElement = document.getElementsByClassName( "sidebar" )[ 0 ];
const sidebarToggleElement = document.getElementsByClassName( "sidebar-toggle" )[ 0 ];
sidebarToggleElement.addEventListener( "click", () => {
sidebarElement.classList.toggle( "collapsed" );
sidebarElement.addEventListener( "transitionend", () => {
window.dispatchEvent( new Event( "resize" ) );
} );
} );
}
}
/////////////////////////////////////////////////////////
// manage click effect
////////////////////////////////////////////////////////
pwa.main.clickactive = () => {
const cleanactive = () => {
const el = document.querySelectorAll( '.sidebar-item' )
for( var i = 0; i < el.length; i++ ) {
//console.log( 'clean', el[ i ].classList )
el[ i ].classList.remove( 'active' );
}
}
document.addEventListener( "click", ( e ) => {
console.log( 'click', e );
if( e.target.classList.contains( 'sidebar-link' ) ) {
cleanactive();
e.target.closest( '.sidebar-item' )
.classList.add( 'active' );
// remonte au menu au dessus si existe
e.target.closest( '.sidebar-item' )
.closest( '.sidebar-item' )
.classList.add( 'active' );
}
} );
// If enter run the research
document.getElementById( 'globalsearch' )
.addEventListener( 'keypress', ( e ) => {
if( e.keyCode == 13 ) {
pwa.search.req( 'globalsearch' );
e.preventDefault();
}
} )
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,28 @@
"use strict";
var pwa = pwa || {};
/*
Manage notification
Get notification after tomenu was load
from /components/notification
____________________
*/
//--##
pwa.notification = {};
pwa.notification.update = () => {
console.log( 'get notification update for a user' );
axios.get( `https://${pwa.state.data.ctx.urlbackoffice}/notifications/user`, { headers: pwa.state.data.headers } )
.then( rep => {
console.log( "list des notifs", rep.data.payload.data )
rep.data.payload.data.number = rep.data.payload.data.notifs.length;
document.getElementById( "topbarmenuright" )
.innerHTML = Mustache.render( pwa.state.data.tpl.notiflist, rep.data.payload.data ) + document.getElementById( "topbarmenuright" )
.innerHTML;
} )
.catch( err => {
console.log( `Err pwa.notification.update data for user into header ${pwa.state.data.headers}`, err );
} );
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,261 @@
"use strict";
var pwa = pwa || {};
/*
FROM ndda/plugins/maildigitcreator/appjs/state.js
Manage state of the webapp
author: Phil Colzy - yzlocp@gmail.com
____________________________
pwa.state.save(): save in localstorage pwa.state.data avec le Nom du projet
____________________________
pwa.state.update(): update localstorage data from referential version
____________________________
pwa.state.ready(callback): await DOM loaded before running the callback
____________________________
pwa.state.refresh(): check if newversion of webapp to refresh page for dev purpose (do not use to refresh data)
____________________________
pwa.state.route(); check url and reroute it to function(urlparameter) or hide show part into a onepage website.
____________________________
pwa.state.loadfile({key:url},'dest') store in pwa.state.data.dest.key= file from url await all key to store result
___________________________
pwa.state.confdev() if ENV is dev then change urlbase for axios to looking for relevant file and refresh at a frequency pwa.state.refresh()
*/
//--##
pwa.state = {};
pwa.state.refresh = () => {
//console.warn(`Lance le refresh de${pwa.state.env.page}`)
//console.warn( 'ggg', pwa.state.data.ctx.urlbase )
const currenthtml = location.pathname.split( '/' )
.at( -1 )
.replace( '.html', '.json' );
//console.log( currenthtml )
axios.get( ` ${pwa.state.data.ctx.urlbase}/static/lastchange/${currenthtml}` )
.then(
data => {
//console.log( data.data.time, pwa.state.data.ctx.version )
if( data.data.time > pwa.state.data.ctx.version ) {
//console.log( "reload la page pour cause de lastchange detecté" );
pwa.state.data.ctx.version = data.data.time;
pwa.state.data.ctx.refreshstorage = true;
pwa.state.save();
location.reload();
} else {
//console.log( 'nothing change' )
}
},
error => {
console.log( error );
}
);
};
pwa.state.ready = callback => {
// Equivalent of jquery Document.ready()
// in case the document is already rendered
if( document.readyState != "loading" ) callback();
// modern browsers
else if( document.addEventListener )
document.addEventListener( "DOMContentLoaded", callback );
// IE <= 8
else
document.attachEvent( "onreadystatechange", function () {
if( document.readyState == "complete" ) callback();
} );
};
pwa.state.route = async () => {
/* Allow to create dynamic content
?action=pwa object&id=&hh& => pwa.action("?action=pwa object&id=&hh&")
ex: ?action=test.toto&id=1&t=123
Each function that can be called have to start with
if (e[1]=="?"){
const urlpar = new URLSearchParams( loc.search );
Then set param with urlpar.get('variable name')
}
OR ?pagemd=name used into .html (div)
Then it hide all <div class="pagemd" and show the one with <div id="page"+name
*/
console.groupCollapsed( `pwa.state.route with window.location` );
console.log( 'List of pwa available ', Object.keys( pwa ) );
if( !pwa.auth ) {
console.log( 'Warning, no auth.js, not a pb if no authentification need, if not check js order to be sur auth.js load before state.js' )
} else {
// check if still authenticated
if( pwa.state.data.login.isAuthenticated ) {
pwa.state.data.login.isAuthenticated = await pwa.auth.isAuthenticate();
}
//check if need authentification to show this page
if( pwa.state.data.ctx.pageneedauthentification && !pwa.state.data.login.isAuthenticated ) {
console.log( 'reload page cause not auth and page require an auth' )
window.location = `${pwa.state.data.ctx.pageredirectforauthentification}_${pwa.state.data.ctx.lang}.html`;
}
}
const loc = window.location;
if( loc.search ) {
console.log( Object.keys( pwa ) )
const urlpar = new URLSearchParams( loc.search );
if( urlpar.get( 'action' ) ) {
const act = 'pwa.' + urlpar.get( 'action' ) + '("' + loc.search + '")';
try {
eval( act );
console.log( 'Specific action request to pwa.' + act )
} catch ( err ) {
console.log( err )
console.error( `You request ${act}, this action does not exist ` );
alert( `Sorry but you have no access to ${act}, ask your admin` );
}
}
let pgid = "pageindex"
if( urlpar.get( 'pagemd' ) ) {
pgid = "page" + urlpar.get( 'pagemd' )
}
//route to page content
Array.from( document.getElementsByClassName( "pagemd" ) )
.forEach( e => {
console.log( "detect pagemd", e.getAttribute( 'data-url' ) );
e.classList.add( "d-none" );
} );
if( document.getElementById( pgid ) ) {
document.getElementById( pgid )
.classList.remove( 'd-none' );
}
}
console.groupEnd();
// If pwa.main exist then start pwa.main.init();
if( pwa.main ) pwa.main.init();
}
pwa.state.loadfile = async ( list, dest ) => {
// load external file if flag pwa.state.data.ctx.refreshstorage is true from pwa.state.refresh();
//from list = {name:url} request are done with ctx.urlbase/url and store in localstorage pwa.state.data[dest][name]=data
// if dest=='js' then it eval the js and store origin in pwa.state.data.js={name:url}
//For at true refreshstorage if destination pwa.state.dest does not exist
//console.log( 'list', list )
//console.log( 'pwa.state.data.ctx.refreshstorage', pwa.state.data.ctx.refreshstorage )
if( pwa.state.data.ctx.refreshstorage || !pwa.state.data[ dest ] || Object.keys( pwa.state.data[ dest ] )
.length == 0 ) {
if( !pwa.state.data[ dest ] ) pwa.state.data[ dest ] = {};
try {
let reqname = [];
let reqload = [];
for( const [ k, v ] of Object.entries( list ) ) {
if( !pwa.state.data[ dest ][ k ] || pwa.state.data.ctx.refreshstorage ) {
// if still not exist or refresstorage is set to true then add to load
//@TODO check it works well on production
reqname.push( k );
reqload.push( v );
}
};
//console.log( pwa.state.data.ctx.urlbase, reqload )
let resload = await Promise.all( reqload.map( r => {
if( dest == 'tpldata' ) r = `${r}_${pwa.state.data.ctx.lang}.json`;
return axios.get( `${pwa.state.data.ctx.urlbase}/${r}`, { headers: pwa.state.data.headers } )
} ) );
resload.forEach( ( d, i ) => {
pwa.state.data[ dest ][ reqname[ i ] ] = d.data;
pwa.state.save();
} )
} catch ( err ) {
console.error( 'FATAL ERROR check that list exist remove if not', list, err.message )
}
}
};
pwa.state.save = function () {
localStorage.setItem( pwa.state.data.ctx.website, JSON.stringify( pwa.state.data ) );
};
pwa.state.update = async function () {
const domhtml = document.querySelector( "html" );
const ctx = {
tribeid: domhtml.getAttribute( 'data-tribeid' ),
website: domhtml.getAttribute( "data-website" ),
nametpl: domhtml.getAttribute( "data-nametpl" ),
pagename: domhtml.getAttribute( "data-pagename" ),
urlbackoffice: domhtml.getAttribute( "data-urlbackoffice" ),
pageneedauthentification: true,
pageredirectforauthentification: "",
lang: domhtml.getAttribute( "lang" ),
env: domhtml.getAttribute( "data-env" ),
version: domhtml.getAttribute( "data-version" ),
refreshstorage: false
}
if( !domhtml.getAttribute( 'data-pageneedauthentification' ) || domhtml.getAttribute( 'data-pageneedauthentification' ) == 'false' ) {
ctx.pageneedauthentification = false;
}
if( domhtml.getAttribute( 'data-pageforauthentification' ) ) {
ctx.pageforauthentification = domhtml.getAttribute( 'data-pageforauthentification' );
}
console.groupCollapsed( `update pwa.state with html attribut or from localstorage into ${ctx.website}` );
console.log( 'html context:', ctx );
if( localStorage.getItem( ctx.website ) ) {
pwa.state.data = JSON.parse( localStorage.getItem( ctx.website ) );
//alert( 'recupere pwa.state.data xpaganid:' + pwa.state.data.headers.xpaganid )
}
if( !( pwa.state.data && pwa.state.data.ctx.tribeid == ctx.tribeid && pwa.state.data.ctx.website == ctx.website ) ) {
console.log( " reinitialise localstorage cause work on a different project or first access" );
delete pwa.state.data;
localStorage.removeItem( ctx.website )
}
/*
if( pwa.state.data && statejson.data.app.version && ( parseInt( pwa.state.data.app.version ) < parseInt( statejson.data.app.version ) ) ) {
// le numero de version en mémoire est < au numero disponible sur le serveur
// force un logout pour reinitialiser les menus
delete pwa.state.data;
localStorage.removeItem( pwa.PROJET )
}
*/
if( !pwa.state.data ) {
// No localstorage available et one by default
pwa.state.data = {
ctx,
login: {
isAuthenticated: false,
user: {},
rememberMe: {}
},
headers: {
'xauth': '1',
'xpaganid': '1',
'xtribe': ctx.tribeid,
'xlang': ctx.lang,
'xworkon': ctx.tribeid,
'xapp': `${ctx.tribeid}:${ctx.website}`
}
}
console.log( 'load new state.data', pwa.state.data )
}
// Check if external component need to be load
const app = `${pwa.state.data.ctx.tribeid}:${pwa.state.data.ctx.website}`;
if( pwa.state.data.login.isAuthenticated &&
pwa.state.data.login.user.ACCESSRIGHTS.app[ app ] &&
pwa.state.data.login.user.ACCESSRIGHTS.app[ app ].js
) {
console.log( 'tttt', pwa.state.data.login.isAuthenticated, pwa.state.data.login.user.ACCESSRIGHTS.app[ app ].js )
pwa.state.data.login.user.ACCESSRIGHTS.app[ app ].js.some( ( u ) => {
console.log( `load from user ACCESSRIGHTS app[${pwa.state.data.ctx.tribeid}:${pwa.state.data.ctx.website}].js : ${u}` )
const script = document.createElement( 'script' );
script.src = u;
script.async = false;
document.body.appendChild( script );
} );
}
//Check dev to set parameter to simplify dev app
//check in confdev version and set pwa.state.data.ctx.refreshstorage at true if new version
pwa.state.confdev();
console.groupEnd();
pwa.state.route();
pwa.state.save();
};
pwa.state.confdev = () => {
if( pwa.state.data.ctx.env == 'dev' ) {
pwa.state.data.ctx.urlbase = `/space/${pwa.state.data.ctx.tribeid}/${pwa.state.data.ctx.website}/dist`;
// check each 3 sec if new version to reload
setInterval( "pwa.state.refresh()", 3000 );
} else {
//pwa.state.axios = axios.create();
pwa.state.data.ctx.urlbase = "/";
// check and refresh if new version only one time
pwa.state.refresh();
}
}
// Refresh browser state if exist else get pwa.state defaults
pwa.state.ready( pwa.state.update );

View File

@@ -0,0 +1 @@
{"name":"apxtrib Need-Data","short_name":"apxtrib","start_url":"app_index_fr.html","background_color":"purple","description":"Webapp to manage an apxtrib server.","display":"fullscreen","icons":[{"src":"static/img/icons/iconX74x74.png","sizes":"74x74","type":"image/png"}]}

View File

@@ -0,0 +1,2 @@
/*Automaticaly generated do not change*/
@import "../../src/scss/app.scss";

View File

@@ -0,0 +1,34 @@
pwa = pwa || {};
pwa.main = pwa.main || {};
pwa.main.tpldata = pwa.main.tpldata || {};
pwa.main.tpl = pwa.main.tpl || {};
//menu
pwa.main.tpldata.sidebarAdminapxtrib = `static/components/adminapxtrib/sidebaradminapxtrib`;
// Custom data in user lang
//tremplate to store
pwa.main.tpl.adminapxtrib = 'static/components/adminapxtrib/adminapxtrib.mustache';
pwa.main.tpl.adminapxtribsysinfo = 'static/components/adminapxtrib/adminapxtribsysinfo.mustache';
pwa.main.tpl.adminapxtribactivity = 'static/components/adminapxtrib/adminapxtribactivity.mustache';
pwa.adminapxtrib = {};
pwa.adminapxtrib.init = () => {
// Run back stuff to update data
}
pwa.adminapxtrib.sysinfoview = () => {
const datasysinfo = {}; //request to get info and push them in template
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( pwa.state.data.tpl.adminapxtribsysinfo, datasysinfo );
}
pwa.adminapxtrib.activityview = () => {
const dataactivity = {}; //request to get info and push them in template
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( pwa.state.data.tpl.adminapxtribactivity, dataactivity );
}
pwa.adminapxtrib.tribeidview = () => {
const datatribeid = {}; //request to get info and push them in template
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( pwa.state.data.tpl.adminapxtrib, datatribeid );
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,234 @@
<div class="mb-3">
<h1 class="h3 d-inline align-middle">tribeids</h1><a class="badge bg-primary ms-2" href="" target="_blank">120 <i class="fas fa-fw fa-external-link-alt"></i></a>
</div>
<div class="row">
<div class="col-xl-8">
<div class="card">
<div class="card-header pb-0">
<div class="card-actions float-end">
<div class="dropdown position-relative">
<a href="#" data-bs-toggle="dropdown" data-bs-display="static" aria-expanded="false" class="">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal align-middle"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</div>
</div>
<h5 class="card-title mb-0">Tribes</h5>
</div>
<div class="card-body">
<table class="table table-striped" style="width:100%">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Company</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="img/avatars/avatar.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Garrett Winters</td>
<td>Good Guys</td>
<td>garrett@winters.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Ashton Cox</td>
<td>Levitz Furniture</td>
<td>ashton@cox.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Sonya Frost</td>
<td>Child World</td>
<td>sonya@frost.com</td>
<td><span class="badge bg-danger">Deleted</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Jena Gaines</td>
<td>Helping Hand</td>
<td>jena@gaines.com</td>
<td><span class="badge bg-warning">Inactive</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-2.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Charde Marshall</td>
<td>Price Savers</td>
<td>charde@marshall.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-2.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Haley Kennedy</td>
<td>Helping Hand</td>
<td>haley@kennedy.com</td>
<td><span class="badge bg-danger">Deleted</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-2.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Tatyana Fitzpatrick</td>
<td>Good Guys</td>
<td>tatyana@fitzpatrick.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-3.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Michael Silva</td>
<td>Red Robin Stores</td>
<td>michael@silva.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-3.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Angelica Ramos</td>
<td>The Wiz</td>
<td>angelica@ramos.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-4.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Jennifer Chang</td>
<td>Helping Hand</td>
<td>jennifer@chang.com</td>
<td><span class="badge bg-warning">Inactive</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-4.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Brenden Wagner</td>
<td>The Wiz</td>
<td>brenden@wagner.com</td>
<td><span class="badge bg-warning">Inactive</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-4.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Fiona Green</td>
<td>The Sample</td>
<td>fiona@green.com</td>
<td><span class="badge bg-warning">Inactive</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-5.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Prescott Bartlett</td>
<td>The Sample</td>
<td>prescott@bartlett.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-5.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Gavin Cortez</td>
<td>Red Robin Stores</td>
<td>gavin@cortez.com</td>
<td><span class="badge bg-success">Active</span></td>
</tr>
<tr>
<td><img src="img/avatars/avatar-5.jpg" width="32" height="32" class="rounded-circle my-n1" alt="Avatar"></td>
<td>Howard Hatfield</td>
<td>Price Savers</td>
<td>howard@hatfield.com</td>
<td><span class="badge bg-warning">Inactive</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-xl-4">
<div class="card">
<div class="card-header">
<div class="card-actions float-end">
<div class="dropdown position-relative">
<a href="#" data-bs-toggle="dropdown" data-bs-display="static">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal align-middle"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
</a>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</div>
</div>
<h5 class="card-title mb-0">Angelica Ramos</h5>
</div>
<div class="card-body">
<div class="row g-0">
<div class="col-sm-3 col-xl-12 col-xxl-3 text-center">
<img src="img/avatars/avatar-3.jpg" width="64" height="64" class="rounded-circle mt-2" alt="Angelica Ramos">
</div>
<div class="col-sm-9 col-xl-12 col-xxl-9">
<strong>About me</strong>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.</p>
</div>
</div>
<table class="table table-sm mt-2 mb-4">
<tbody>
<tr>
<th>Name</th>
<td>Angelica Ramos</td>
</tr>
<tr>
<th>Company</th>
<td>The Wiz</td>
</tr>
<tr>
<th>Email</th>
<td>angelica@ramos.com</td>
</tr>
<tr>
<th>Phone</th>
<td>+1234123123123</td>
</tr>
<tr>
<th>Status</th>
<td><span class="badge bg-success">Active</span></td>
</tr>
</tbody>
</table>
<strong>Activity</strong>
<ul class="timeline mt-2 mb-0">
<li class="timeline-item">
<strong>Signed out</strong>
<span class="float-end text-muted text-sm">30m ago</span>
<p>Nam pretium turpis et arcu. Duis arcu tortor, suscipit...</p>
</li>
<li class="timeline-item">
<strong>Created invoice #1204</strong>
<span class="float-end text-muted text-sm">2h ago</span>
<p>Sed aliquam ultrices mauris. Integer ante arcu...</p>
</li>
<li class="timeline-item">
<strong>Discarded invoice #1147</strong>
<span class="float-end text-muted text-sm">3h ago</span>
<p>Nam pretium turpis et arcu. Duis arcu tortor, suscipit...</p>
</li>
<li class="timeline-item">
<strong>Signed in</strong>
<span class="float-end text-muted text-sm">3h ago</span>
<p>Curabitur ligula sapien, tincidunt non, euismod vitae...</p>
</li>
<li class="timeline-item">
<strong>Signed up</strong>
<span class="float-end text-muted text-sm">2d ago</span>
<p>Sed aliquam ultrices mauris. Integer ante arcu...</p>
</li>
</ul>
</div>
</div>
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
{
"groupheader": "Admin apxtrib",
"sbssgroupmenu": [{
"name": "Activité",
"icon": "sliders",
"actionclick": "pwa.adminapxtrib.init()",
"iditemmenus": "adminxpress",
"itemmenus": [{
"name": "Info activités",
"actionclick": "pwa.adminapxtrib.activityview()"
},
{
"name": "Admin des tribeid",
"actionclick": "pwa.adminapxtrib.tribeidview()"
}
]
}, {
"name": "System Info",
"icon": "sliders",
"actionclick": "pwa.adminapxtrib.sysinfoview()"
}]
}

View File

@@ -0,0 +1,22 @@
{
"groupheader": "Admin apxtrib",
"sbssgroupmenu": [{
"name": "Tribes",
"icon": "sliders",
"actionclick": "pwa.tribeids.init()",
"iditemmenus": "admintribeid",
"itemmenus": [{
"name": "Suivi des tribeid",
"actionclick": "pwa.tribeids.tribeidactivity()"
},
{
"name": "Admin des tribeid",
"actionclick": "pwa.tribeids.settings()"
}
]
}, {
"name": "Suivi technique",
"icon": "sliders",
"actionclick": "pwa.tribeids.suivi()"
}]
}

View File

@@ -0,0 +1,145 @@
// app management sidebar and navbar
/*
Load into pwa.state.data.tpl[name] templates content
appsidebarmenu.mustache side menu sbgroupmenu:[list of submenu]
apptopbarmenu.mustache top menu menuprofil:[ list of submenu]
apptopbarnotification.mustache show a dropdown list if withnotification is true
apptopbarmessage.mustache show a dropdown list if withmessage is true
Load list of public data into pwa.state.data.menu
sidebar
topbar
Then add specific menu in based on login list into
pwa.state.data.login.user.ACCESSRIGHTS.app[tribeid:website]
each name starting by sidebar is added into sidebar.subgroupmenu
each name starting by topbar is added into topbar.menuprofil
*/
var pwa = pwa || {};
pwa.app = {};
pwa.app.init = async () => {
//Load template in pwa.state.data.tpl
console.log( 'app.init()' );
const tpllist = {};
[ 'appsidebarmenu', 'apptopbarmenu', 'apptopbarnotification', 'apptopbarmessage' ].forEach( c => {
tpllist[ c ] = `static/components/appmesa/${c}.mustache`
} );
// add other generic public template, carefull name have to be unique
tpllist[ 'notiflist' ] = `static/components/notification/notiflist.mustache`;
// tpllist[ 'msglist' ] = `static/components/messagerie/msglist.mustache`;
tpllist[ 'verticaltab' ] = `static/components/verticaltab/verticaltab.mustache`;
await pwa.state.loadfile( tpllist, "tpl" );
await pwa.app.getcontent();
//get menu depending of user profile: tribeid:app
};
pwa.app.getcontent = async () => {
const menubase = {};
let menu = [ 'sidebar', 'topbar' ];
//check if authentify and add specific menu
if( pwa.state.data.login && pwa.state.data.login.isAuthenticated ) {
//Get personnal menu user.ACCESSRIGHTS={app:{ "tribeid:website":{sidebar,top}
const appname = `${pwa.state.data.ctx.tribeid}:${pwa.state.data.ctx.website}`
//console.log( 'with ', appname )
menu = menu.concat( pwa.state.data.login.user.ACCESSRIGHTS.app[ appname ] )
}
//console.log( 'update pwa.state.data.menu with ', menu )
menu.forEach( c => { menubase[ c ] = `static/data/${c}_${pwa.state.data.ctx.lang}.json` } );
await pwa.state.loadfile( menubase, 'menu' );
pwa.app.loadsidebarmenu( menu.filter( m => m.includes( 'sidebar' ) ) )
pwa.app.loadtopmenu( menu.filter( m => m.includes( 'topbar' ) ) );
//active les icones svg de feather
feather.replace();
//active scroll presentation + sidebar animation
pwa.app.simplebar();
pwa.app.clickactive();
}
pwa.app.loadsidebarmenu = ( list ) => {
//console.log( 'list de menu sidebar', list )
const data = pwa.state.data.menu.sidebar
for( let m of list ) {
if( m != 'sidebar' ) {
console.log( m )
data.sbgroupmenu = data.sbgroupmenu.concat( pwa.state.data.menu[ m ] )
}
}
//console.log( data )
document.querySelector( "#sidebar" )
.innerHTML = Mustache.render( pwa.state.data.tpl.appsidebarmenu, data )
}
pwa.app.loadtopmenu = ( list ) => {
const data = pwa.state.data.menu.topbar
for( let m of list ) {
if( m != 'topbar' ) {
data.menuprofil = data.menuprofil.concat( pwa.state.data.menu[ m ] )
}
}
//update date from login if authenticated
if( pwa.state.data.login.isAuthenticated ) {
//remove the login item from menuprofil
data.menuprofil.shift();
data.name = pwa.state.data.login.user.NAME;
data.avatarimg = pwa.state.data.login.user.AVATARIMG;
// get notification / message if exist
//const notifsrc= `${pwa.urlbackauth}/Tribes/logs/`;
if( pwa.state.data.menu.topbar.withnotification ) {
pwa.notification.update();
}
if( pwa.state.data.menu.topbar.withmessage ) {
//pwa.message.update( );
}
}
//console.log( 'topbar data', data.menuprofil );
document.querySelector( "#navbar" )
.innerHTML = Mustache.render( pwa.state.data.tpl.apptopbarmenu, data )
}
pwa.app.simplebar = () => {
const simpleBarElement = document.getElementsByClassName( "js-simplebar" )[ 0 ];
if( simpleBarElement ) {
/* Initialize simplebar */
new SimpleBar( document.getElementsByClassName( "js-simplebar" )[ 0 ] )
const sidebarElement = document.getElementsByClassName( "sidebar" )[ 0 ];
const sidebarToggleElement = document.getElementsByClassName( "sidebar-toggle" )[ 0 ];
sidebarToggleElement.addEventListener( "click", () => {
sidebarElement.classList.toggle( "collapsed" );
sidebarElement.addEventListener( "transitionend", () => {
window.dispatchEvent( new Event( "resize" ) );
} );
} );
}
}
pwa.app.clickactive = () => {
const cleanactive = () => {
const el = document.querySelectorAll( '.sidebar-item' )
for( var i = 0; i < el.length; i++ ) {
//console.log( 'clean', el[ i ].classList )
el[ i ].classList.remove( 'active' );
}
}
document.addEventListener( "click", ( e ) => {
console.log( 'click', e );
if( e.target.classList.contains( 'sidebar-link' ) ) {
cleanactive();
e.target.closest( '.sidebar-item' )
.classList.add( 'active' );
// remonte au menu au dessus si existe
e.target.closest( '.sidebar-item' )
.closest( '.sidebar-item' )
.classList.add( 'active' );
}
} );
// If enter run the research
document.getElementById( 'globalsearch' )
.addEventListener( 'keypress', ( e ) => {
if( e.keyCode == 13 ) {
pwa.search.req( 'globalsearch' );
e.preventDefault();
}
} )
};

View File

@@ -0,0 +1,5 @@
<!-- div id="maincontent" class="container-fluid p-0" -->
<p>Content when js is desactivated</p>
<!--/div-->

View File

@@ -0,0 +1,29 @@
<!-- div class="wrapper" -->
<nav id="sidebar" class="sidebar"></nav>
<div class="main">
<nav id ="navbar" class="navbar {{navbarclass}}"></nav>
<main class="content">
<importhtml></importhtml>
</main>
<footer class="footer">
<div class="container-fluid">
<div class="row text-muted">
<div class="col-6 text-start">
<p class="mb-0">
<a href="{{{urlapxtrib}}}" class="text-muted"><strong>{{claim}}</strong></a> &copy;
</p>
</div>
<div class="col-6 text-end">
<ul class="list-inline">
{{#links}}
<li class="list-inline-item">
<a class="text-muted" href="{{{url}}}">{{{desc}}}</a>
</li>
{{/links}}
</ul>
</div>
</div>
</div>
</footer>
</div>
<!-- /div -->

View File

@@ -0,0 +1,33 @@
<div class="sidebar-content js-simplebar">
<a class="sidebar-brand" href="{{sbbrandlink}}">
<span class="align-middle">{{{sbtitle}}}</span>
</a>
{{#sbgroupmenu}}
<ul class="sidebar-nav">
<li class="sidebar-header">
{{{groupheader}}}
</li>
{{#sbssgroupmenu}}
<li class="sidebar-item ">
{{^iditemmenus}}
<a class="sidebar-link" onclick="{{actionclick}}">
<i class="align-middle" data-feather="{{icon}}"></i>
<span class="align-middle">{{name}}</span>
</a>
{{/iditemmenus}}
{{#iditemmenus}}
<a data-bs-target="#{{iditemmenus}}" data-bs-toggle="collapse" class="sidebar-link collapsed">
<i class="align-middle" data-feather="{{icon}}"></i>
<span class="align-middle">{{name}}</span>
</a>
<ul id="{{iditemmenus}}" class="sidebar-dropdown list-unstyled collapse " data-bs-parent="#sidebar">
{{#itemmenus}}
<li class="sidebar-item"><a class="sidebar-link" onclick="{{actionclick}}">{{name}}</a></li>
{{/itemmenus}}
</ul>
{{/iditemmenus}}
</li>
{{/sbssgroupmenu}}
</ul>
{{/sbgroupmenu}}
</div>

View File

@@ -0,0 +1,61 @@
<a class="sidebar-toggle d-flex">
<i class="hamburger align-self-center"></i>
</a>
{{#withsearch}}
<form class="d-none d-sm-inline-block">
<div class="input-group input-group-navbar">
<input type="text" id="globalsearch" class="form-control" placeholder="{{{searchtxt}}}" aria-label="Search">
<button class="btn" type="button" onclick="pwa.search.req('globalsearch');return false;" cancelhref="?action=search.req&idsearchtxt=globalsearch">
<i class="align-middle" data-feather="search"></i>
</button>
</div>
</form>
{{/withsearch}}
<div class="navbar-collapse collapse">
<ul id="topbarmenuright" class="navbar-nav navbar-align">
{{#withnotif}}
<li id="notiflist" class="nav-item dropdown">
</li>
{{/withnotif}}
{{#withmessage}}
<li class="nav-item dropdown">
<a class="nav-icon dropdown-toggle" href="#" id="messagesDropdown" data-bs-toggle="dropdown">
<div class="position-relative">
<i class="align-middle" data-feather="message-square"></i>
<span class="indicator">0</span>
</div>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end py-0" aria-labelledby="messagesDropdown">
<div class="dropdown-menu-header">
<div class="position-relative">
{{{messageheader}}}
</div>
</div>
<div id="topmenumessages" class="list-group">
</div>
<div class="dropdown-menu-footer">
<a href="#" class="text-muted">{{{messagefooter}}}</a>
</div>
</div>
</li>
{{/withmessage}}
<li class="nav-item dropdown">
<a class="nav-icon dropdown-toggle d-inline-block d-sm-none" href="#" data-bs-toggle="dropdown">
<i class="align-middle" data-feather="settings"></i>
</a>
<a class="nav-link dropdown-toggle d-none d-sm-inline-block" href="#" data-bs-toggle="dropdown">
<img src="{{avatarimg}}" class="avatar img-fluid rounded me-1" alt="avatar" /> <span class="text-dark">{{name}}</span>
</a>
<div class="dropdown-menu dropdown-menu-end">
{{#menuprofil}}
<a class="dropdown-item" href="{{{href}}}">
<i class="align-middle me-1" data-feather="{{{icon}}}"></i> {{{desc}}}
</a>
{{#menubreaker}}
<div class="dropdown-divider"></div>
{{/menubreaker}}
{{/menuprofil}}
</div>
</li>
</ul>
</div>

View File

@@ -0,0 +1,16 @@
<div class="list-group">
{{#messages}}
<a href="#" class="list-group-item">
<div class="row g-0 align-items-center">
<div class="col-2">
<img src="{{{imgsrc}}}" class="avatar img-fluid rounded-circle" alt="Vanessa Tucker">
</div>
<div class="col-10">
<div class="text-dark">{{{name}}}</div>
<div class="text-muted small mt-1">{{{subject}}}.</div>
<div class="text-muted small mt-1">{{{elapse}}}</div>
</div>
</div>
</a>
{{/messages}}
</div>

View File

@@ -0,0 +1,16 @@
<div class="list-group">
{{#notifications}}
<a href="#" class="list-group-item">
<div class="row g-0 align-items-center">
<div class="col-2">
<i class="{{{classicon}}}" data-feather="{{{icon}}}"></i>
</div>
<div class="col-10">
<div class="text-dark">{{{titre}}}</div>
<div class="text-muted small mt-1">{{{description}}}.</div>
<div class="text-muted small mt-1">{{{elapse}}}</div>
</div>
</div>
</a>
{{/notifications}}
</div>

View File

@@ -0,0 +1,14 @@
{
"navbarclass": "navbar-expand navbar-light navbar-bg",
"footer": {
"urlapxtrib": "https://apxtrib.org",
"claim": "apxtrib made with love for freedom",
"links": [{
"url": "#",
"desc": "Support"
}, {
"url": "#",
"desc": "Privacy"
}]
}
}

View File

@@ -0,0 +1,47 @@
{
"groupheader": "Mon espace client",
"sbssgroupmenu": [{
"name": "Espace web",
"icon": "sliders",
"actionclick": "pwa.spaceweb.init()",
"iditemmenus": "clentidspace",
"itemmenus": [{
"name": "Mon espace web",
"actionclick": "pwa.spaceweb.myfile()"
},
{
"name": "Mes utilisateurs",
"actionclick": "pwa.spaceweb.users()"
},
{
"name": "Mes contenus",
"actionclick": "pwa.spaceweb.content()"
},
{
"name": "Mes plugins",
"actionclick": "pwa.spaceweb.plugins()"
}
]
}, {
"name": "Referentiels",
"icon": "sliders",
"iditemmenus": "adminreferentiel",
"itemmenus": [{
"name": "Force update",
"actionclick": "pwa.referential.forceupdate()"
},
{
"name": "data",
"actionclick": "pwa.referential.setting('data')"
},
{
"name": "json",
"actionclick": "pwa.referential.setting('json')"
},
{
"name": "Objects",
"actionclick": "pwa.referential.setting('object')"
}
]
}]
}

View File

@@ -0,0 +1,23 @@
{
"sbbrandlink": "app_index_fr.html",
"sbtitle": "Need-Data",
"sbgroupmenu": [{
"groupheader": "apxtrib",
"sbssgroupmenu": [{
"name": "Les news",
"icon": "sliders",
"actionclick": "pwa.news.init()"
},
{
"name": "Reporting",
"icon": "sliders",
"actionclick": "pwa.reporting.init()",
"iditemmenus": "reportingapxtrib",
"itemmenus": [{
"name": "",
"actionclick": "pwa.reporting.init()"
}]
}
]
}]
}

View File

@@ -0,0 +1,14 @@
{
"withsearch": true,
"searchtxt": "Recherche...",
"withnotification": true,
"notificationheader": "Vos notifications",
"notificationfooter": "Voir toutes les notifications",
"href": "?action=notification.view",
"withmessage": true,
"messageheader": "Vos messages non lus",
"messagefooter": "Voir tous les messages",
"avatarimg": "static/img/avataranonymous.png",
"name": "Login",
"menuprofil": []
}

View File

@@ -0,0 +1,28 @@
"use strict";
var pwa = pwa || {};
/*
Manage message
____________________
*/
//--##
pwa.message = {};
pwa.message.update = ( urlmsg ) => {
console.log( 'get message update' );
axios.get( urlmsg, { headers: pwa.state.data.headers } )
.then( rep => {
const tpl = document.getElementById( "app_menutopmessage" );
document.getElementById( "topmenumessages" )
.innerHTML = Mustache.render( tpl.innerHTML, { messagess: rep.data } );
} )
.catch( err => {
console.log( `Err pwa.notification.update data from ${urlmsg}`, err );
} );
};
/*pwa
.state
.ready( pwa.notification.update( 'static/data/staticContentwebsite/notification.json' ) );
*/

View File

@@ -0,0 +1,271 @@
"use strict";
var pwa = pwa || {};
/*
Manage referentials for a tribeid
*/
//--##
pwa.referential = {};
pwa.referential.content = `
<div class="row gap-20 masonry pos-r">
<div class="blocA masonry-item col-md-6">
<div class="bgc-white p-20 bd">
<h6 class="c-grey-900">Affiche json</h6>
<div id="jsoneditor" class="jsoneditor">
</div>
</div>
</div>
<div class="blocB masonry-item col-md-6">
<div class="bgc-white p-20 bd">
<h6 class="c-grey-900">Changer son mot de passe</h6>
<div class="formdata">
</div>
</div>
</div>
</div>
`;
pwa.referential.set = async ( sourceref ) => {
/*
@sourceref app (recupere le referentiel de l'application)
workon (recupere le referentiel du client id sur lequel on travail state.data.headers[X-workOn])
On alimente le referentiel ds localStorage
state.app.referentials = {menu:}
*/
console.groupCollapsed( 'Load pwa.referential.set with ', sourceref )
//let reftoload;
let savexworkon;
if( sourceref == "app" ) {
//reftoload = pwa.state.data.app.referentials
// affecte le menu en fonction du profil
console.assert( pwa.state.data.app.profil && pwa.state.data.app.profil[ pwa.state.data.userlogin.ACCESSRIGHTS.app[ `${pwa.tribeid}:${pwa.PROJET}` ] ], `profil de l app ${pwa.tribeid}:${pwa.PROJET} n'existe pas pour le user dans userlogin.ACCESSRIGHTS.app , verifier qu'on a bien pour cette app un menu correspondant` );
const menuapp = pwa.state.data.app.profil[ pwa.state.data.userlogin.ACCESSRIGHTS.app[ `${pwa.tribeid}:${pwa.PROJET}` ] ].menu;
if( !pwa.state.data.app.referentials.json[ menuapp ] ) {
//on ajoute ce menu avec une version à O pour forcer sa mise à jour
pwa.state.data.app.referentials.json[ menuapp ] = { version: 0 }
}
//set xworkon on tribeid where app is stored
savexworkon = pwa.state.data.headers[ 'X-WorkOn' ];
pwa.state.data.headers[ 'X-WorkOn' ] = pwa.tribeid
}
/* else if(sourceref == "xworkon") {
// on recupere le referentiel utile pour clientcon.json du xworkon pour le projet
reftoload = pwa.state.data.xworkon.referentials
}*/
// recupere les versions du clientconf.json
const clientconfref = await axios.get( `${pwa.urlbackauth}/referentials/clientconf/referentials`, { headers: pwa.state.data.headers } );
console.debug( 'clientconfref', clientconfref );
for( const o of [ 'object', 'json', 'data' ] ) {
console.debug( o )
//update by adding or upgraded version if in dev or in prod and pwa.state.data[sourceref].referentials[type=object|json|data][nomref].version is upper than the local one
// Take care app is load first with previous referential then need to refresh app to use the new version.
if( !clientconfref.data.referentials[ o ] ) {
clientconfref.data.referentials[ o ] = {};
}
//Warning do not use foreach in async cause foreach is not sync
for( const r of Object.keys( clientconfref.data.referentials[ o ] ) ) {
console.debug( r )
if( !pwa.state.data.app.referentials[ o ][ r ] ) {
// New referential added need to reload app to refresh app
pwa.state.data.app.referentials[ o ][ r ] = { version: -1 };
}
if( pwa.MODE_ENV == "dev" || !( pwa.state.data.app.referentials[ o ][ r ].version == clientconfref[ o ][ r ].version ) ) {
// alors nouvelle version à mettre à jour en dev on le fait systematiquement
console.log( `${pwa.urlbackauth}/referentials/content/${o}/${r}`, pwa.state.data.headers );
const dataref = await axios.get( `${pwa.urlbackauth}/referentials/content/${o}/${r}`, { headers: pwa.state.data.headers } )
if( dataref.status == 200 ) {
console.log( `${o} - ${r}`, dataref.data )
pwa.state.data[ sourceref ].referentials[ o ][ r ] = dataref.data;
pwa.state.save()
} else {
console.log( `ERREUR de recuperation de referentiel ${o} ${r}` )
}
}
}
// we remove from local if nomref disapear of app clientconfref.data.referential
Object.keys( pwa.state.data[ sourceref ].referentials[ o ] )
.forEach( r => {
if( !clientconfref.data.referentials[ o ] ) {
delete pwa.state.data[ sourceref ].referentials[ o ];
pwa.state.save();
}
} )
};
if( sourceref == "app" ) {
// on remet le xworkon en cours
pwa.state.data.headers[ 'X-WorkOn' ] = savexworkon;
}
console.groupEnd();
};
pwa.referential.forceupdate = () => {
axios.get( `${pwa.urlbackauth}/referentials/updatefull`, {
headers: pwa.state.data.headers
} )
.then( data => {
alert( data.status )
} )
}
// GUI to manage referential
pwa.referential.setting = async ( objecttype ) => {
// Convert location.search to get objecttype
const urlpar = new URLSearchParams( objecttype );
objecttype = ( urlpar.get( 'objecttype' ) ) ? urlpar.get( 'objecttype' ) : objecttype;
const tpleditor = `
<div class="input-group mb-3" data-idref="{{id}}">
<button class="btn btn-outline-primary actionobject save" >
{{{btnsave}}}
</button>
<button class="btn btn-outline-primary actionobject delete" >
{{{btndelete}}}
</button>
<button class=" btn btn-outline-primary actionobject copy" >
{{{btncopy}}}
</button>
<input type="text" class="form-control nameas" placeholder="{{{copyplaceholder}}}" >
</div>
</div>
<div class="jsoneditor mb-3"></div>
`;
console.groupCollapsed( `load referentials setting for ${objecttype}` );
//reinit submenuitems
pwa.state.data.app.referentials.json.referentialsetting.submenuitems = [];
//Requested directly the back warranty to get always the last updated referential
const data = await axios.get( `${pwa.urlbackauth}/referentials/contentlist/${objecttype}`, {
headers: pwa.state.data.headers
} );
console.log( "Liste des referentiels ", data.data )
let reqref = []
let ref = []
//init a temporary (opposite od state that is save) data to work on referential (each time referentials/object is load this variable is refresh)
pwa.tmp = {};
data.data.forEach( o => {
reqref.push( axios.get( `${pwa.urlbackauth}/referentials/contentfull/${objecttype}/${o}`, {
headers: pwa.state.data.headers
} ) )
ref.push( o );
} );
axios.all( reqref )
.then( axios.spread( ( ...rep ) => {
console.log( rep )
rep.forEach( ( d, i ) => {
pwa.tmp[ ref[ i ] ] = d.data
const submenuit = {
active: "",
groupinfo: ref[ i ],
id: `referential${ref[i]}`,
optionjsoneditor: {},
onclick: `pwa.referential.save(event,'referential${ref[i]}')`,
btnsave: pwa.state.data.app.referentials.json.referentialsetting.btnsave,
btndelete: pwa.state.data.app.referentials.json.referentialsetting.btndelete,
btncopy: pwa.state.data.app.referentials.json.referentialsetting.btncopy,
copyplaceholder: pwa.state.data.app.referentials.json.referentialsetting.copyplaceholder,
objecttype: objecttype
};
pwa.state.data.app.referentials.json.referentialsetting.submenuitems.push( submenuit )
} );
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( document.getElementById( 'referential' )
.innerHTML, pwa.state.data.app.referentials.json.referentialsetting );
pwa.state.data.app.referentials.json.referentialsetting.submenuitems.forEach( tab => {
document.getElementById( tab.id )
.innerHTML = Mustache.render( tpleditor, tab );
//console.log( tab.id, tab )
// Convert each div with formfieldtype to a form field set with value if exist and listen button to run callback
pwa.referential.init( tab );
} );
} ) )
.catch( err => {
console.log( "eeeee", err );
const submenuit = {
active: "",
groupinfo: objecttype,
id: `referential${objecttype}`,
optionjsoneditor: {},
onclick: `pwa.referential.save(event,'referential${objecttype}')`,
data: { erreur: err }
};
pwa.state.data.app.referentials.json.referentialsetting.submenuitems.push( submenuit )
} );
console.groupEnd();
}
pwa.referential.init = ( tab ) => {
const doctab = document.querySelector( `#${tab.id}` );
const editor = new JSONEditor( doctab.querySelector( `.jsoneditor` ), tab.optionjsoneditor );
console.table( tab )
console.log( tab.objecttype, tab.groupinfo )
editor.set( pwa.tmp[ tab.groupinfo ] );
editor.expandAll();
// ajoute un listener sur btn pour faire axios post with editor.get()
Array.from( doctab.querySelectorAll( '.save, .delete, .copy' ) )
.forEach( act => {
act.addEventListener( 'click', e => {
e.preventDefault();
console.log( 'cliiiiiiiiiiiiiiiiiiiick', tab.id )
if( e.target.classList.contains( 'save' ) ) {
/*
axios.put( `${pwa.urlbackauth}/referentials/content/${tab.objecttype}/${tab.groupinfo}`, editor.get(), {
headers: pwa.state.data.headers
} )
.then( data => {
console.log( "affiche message done" );
} )
.catch( err => {
console.log( "affiche message err ", err )
} );
*/
console.log( 'editor', editor.get() );
}
if( e.target.classList.contains( 'delete' ) ) {
//axios.get( @tTODO la mise à jour du nouveau referentiel avec la version)
}
if( e.target.classList.contains( 'copy' ) ) {
//@TODO create a new referential file localy from an existing one
}
/*console.log( e.target.closest( '[data-idref]' )
.getAttribute( 'data-idref' ) )
console.log( editor.get() );
envoyer à axios et modifier pwa.state. en cours
*/
} );
} );
}
///////////////////////////////////////////////////////
pwa.referential.initold = async ( object ) => {
$( '#mainContent' )
.html( pwa.referential.content );
// charge ul#menuleft
// create the editor
const container = document.getElementById( "jsoneditor" )
const options = {}
const editor = new JSONEditor( container, options )
axios.get( `${pwa.urlbackauth}/referentials/content/object/${object}`, {
headers: pwa.state.data.headers
} )
.then(
( ref ) => { editor.set( ref.data ) },
( error ) => {
window.location.pathname == "/"
} )
// set json
/*const initialJson = {
"Array": [1, 2, 3],
"Boolean": true,
"Null": null,
"Number": 123,
"Object": { "a": "b", "c": "d" },
"String": "Hello World"
}
editor.set(initialJson)
*/
// get json
const updatedJson = editor.get()
console.log( 'updatedJson', updatedJson )
}

View File

@@ -0,0 +1,48 @@
<h1 class="h3 mb-3">{{titre}}</h1>
<div class="row">
<div class="col-12">
<div class='card'>
<div class='card-body'>
<p>La gestion d'un referentiel est multilangue, la sauvegarde remplace l'ancien et génére chaque referentiel par langue.</p>
</div>
</div>
</div>
</div>
<div class="row">
<div class='col-sm-12 col-xl-4'>
<div class='card h-100'>
<div class='card-body'>{{{datadef}}}</div>
</div>
</div>
<div class='col-sm-12 col-xl-4'>
<div class='card h-100'>
<div class='card-body'>{{{jsondef}}}</div>
</div>
</div>
<div class='col-sm-12 col-xl-4'>
<div class='card h-100'>
<div class='card-body'>{{{objectdef}}}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xl-2">
<div class="card">
<div class="card-header bg-grey">
<h5 class="card-title mb-0">{{{submenutitre}}}</h5>
</div>
<div class="list-group list-group-flush" role="tablist">
{{#submenuitems}}
<a class="list-group-item list-group-item-action {{active}}" data-bs-toggle="list" href="#{{id}}" role="tab">{{{groupinfo}}}
</a>
{{/submenuitems}}
</div>
</div>
</div>
<div class="col-md-9 col-xl-10">
<div class="tab-content">
<div class="tab-pane fade show active" role="tabpanel">
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,12 @@
"use strict";
var pwa = pwa || {};
/*
Reporting js managing sceen
*/
//--##
pwa.reporting = pwa.reporting || {};
pwa.reporting.init = () => {
console.log('charge reporting');
}

View File

@@ -0,0 +1,68 @@
// app management for search request
/*
@todo create a search/index content for an apxtrib instance
//store request to analyse kind of looking for
*/
var pwa = pwa || {};
pwa.search = {};
pwa.search.tpl = `
<div class="container-fluid p-0">
<h1 class="h3 mb-3">Recherche pour: {{searchtxt}}</h1>
<div class="row">
{{#results}}
<div class="col-12">
<div class="card">
<div class="card-header">
<p><a href="{{url}}" target="_blank">{{url}}</a> - {{format}}</p>
<h5 class="card-title mb-0">{{{title}}}</h5>
</div>
<div class="card-body">
{{{desc}}}
</div>
<div class="card-footer">
{{#relatedtag}}
<button type="button" class="btn btn-dark" >{{.}}</button>
{{/relatedtag}}
</div>
</div>
</div>
{{/results}}
</div>
</div>
`;
pwa.search.dataexample = {
searchtxt: "search string to looking for",
results: [ {
url: "https://apxtrib.org",
format: "pdf",
title: "Search finding title of the page",
desc: "sentence contexte of the searchtxt",
relatedtag: [ "keyword1", "keyword2" ]
} ]
}
pwa.search.req = ( info ) => {
if( info[ 1 ] == "?" ) {
const urlparse = new URLSearchParams( info );
//Then set param with urlpar.get('variable name')
info = {
idsearch: urlparse.get( 'idsearchtxt' )
}
}
const searchtxt = document.getElementById( info )
.value;
console.log( info, searchtxt );
const req = { searchtxt };
req.results = [ {
url: "",
format: "",
title: "Nous n'avons rien à vous proposer sur ce sujet",
desc: `<p>To find more click on <a href='https://google.com/search?q=${searchtxt.replace(/ /g,'+')}' target="_blanck"> google search </a></p> `,
relatedtag: [ 'tag1', "tag2", "tag3" ]
} ];
document.getElementsByTagName( 'main' )[ 0 ]
.innerHTML = Mustache.render( pwa.search.tpl, req );
}

View File

@@ -0,0 +1,94 @@
"use strict";
var pwa = pwa || {};
// Affiche pour tous les tuso et manage des tutos en fonction des droits
// ces tuto sont stocké dans items avec les tag TUTO MAILDIGIT
// Un searchindex/TUTOMAILDIGIT.json list les id
//--##
pwa.tuto = {};
$(document)
.ready(function() {
//simule un ensemble de click pour faciliter le dev sans avoir à recliquer
// à commenter avant de passer en production
//pwa.tuto.init();
})
pwa.tuto.masonryview = () => {
const blocmasonry = {
classnamebloc: "searchtuto",
classmasonryitem: "col-md-12",
titlemasonry: "Liste des tutos",
masonrycontent: "",
actionmasonry: [{
classbtn: "btn-primary",
actionclick: "pwa.tuto.createTuto();",
title: "Ajouter un tuto",
icone: "fas fa-plus"
}]
}
//on recupere la data
const data = {}
// recupere liste des tag et comptage pour afficher les tag présent
//
// axios.get( items/tuto, company, card (objet)/tuto retourne un json avec liste de cards )
//
/*blocmasonry.masonrycontent = Mustache.render($('#sectionFilterlist')
.html(), data)*/
return blocmasonry
}
pwa.tuto.gestion = () => {
alert('gere')
}
pwa.tuto.menutop = () => {
//liste des options possibles pour le contexte
$('#tplmenutop > .nav-left li')
.removeClass('d-none')
.addClass('d-none');
const listmenutop = ['burger']; //['burger', 'filemanager', 'statistique', 'manageobjet'];
listmenutop.forEach(m => {
$('#tplmenutop > .nav-left li.' + m)
.removeClass('d-none');
});
//Ajout eventuel d'icone d'action on enleve celels d'avant
$('li.objectspecifique')
.remove();
const addmenutop = {
listmenu: [{
"liclass": "manageobjet",
"title": "Gestion des objets",
"actionclick": "pwa.tuto.gestion();",
"icone": "fas fa-cubes"
}]
}
const tplmenutop = `
{{#listmenu}}
<li class="{{liclass}} objectspecifique">
<a class='menutop' title="{{title}}" onclick="{{actionclick}}">
<i class="{{icone}}"></i>
</a>
</li>
{{/listmenu}}`;
$('#tplmenutop ul.nav-left')
.append(Mustache.render(tplmenutop, addmenutop))
}
pwa.tuto.init = async (object) => {
console.log('Lance tuto')
pwa.tuto.menutop();
// Reinit et ajoute des bloc
$('#mainContent > .row')
.html('<div class="masonry-sizer col-md-12"></div>');
$('#mainContent > .row')
.append(Mustache.render($('#app_masonryitemaction')
.html(), pwa.tuto.masonryview()));
new Masonry(".masonry", {
itemSelector: ".masonry-item",
columnWidth: ".masonry-sizer",
percentPosition: true
});
};

View File

@@ -0,0 +1,74 @@
"use strict";
var pwa = pwa || {};
//--##
pwa.userManager = pwa.userManager || {};
pwa.userManager.init = function() {
$("#espaceMasonry")
.html("");
pwa.userProfile.ficheUser("0", "ADMIN");
};
pwa.userManager.userList = async function() {
//check ADMIN
if(pwa.MODE_ENV) {
return [
{ uuid: "pcolzy", desc: "pcolzy" },
{ uuid: "user2", desc: "User2" }
];
} else {
const datauser = await axios.get(pwa.urlbackauth + "/login", {
headers: pwa.state.data.headers
});
const lst = datauser.data.items.map(u => {
return { uuid: u.uuid, desc: u.name };
});
return lst;
}
};
pwa.userManager.ficheUser = function(iduser) {
// Génere un form de user pre-rempli avec iduser
let userForm = {
includein: "#espaceMasonry",
template: "#mainContentMasonry",
idmasonry: "UserForm",
destinationclass: "formUser",
header: "Information du compte",
formclass: "formUserProfil",
nombreColonne: 6,
commentaire: "",
footer: ""
};
let datauser = pwa.state.data.user;
if(iduser) {
//On recupere les informations iduser
// faire un get (iduser) datauser =
userForm.button = [{
action: "update",
label: "Sauvegarder",
msgok: "Votre sauvegarde a bien été effectuée en local",
msgko: "Votre sauvegarde n'a pas pu être réalisée ",
actionclick: "pwa.userManager.update()",
idObject: pwa.state.data.user.uuid,
objectSelected: "user"
}];
} else {
userForm.button = [{
action: "create",
label: "Créer",
msgok: "Utilisateur créer en local",
msgko: "La création d'un utilisateur n'est pas possible ",
actionclick: "pwa.userManager.create()",
idObject: "0",
objectSelected: "user"
}];
}
userForm.main = pwa.forms.genereForm(pwa.state.app.ref.users, datauser);
console.log("button envoye", userForm.button);
userForm.buttonup = Mustache.render($("#actionButton")
.html(), {
button: userForm.button
});
pwa.masonry.append(userForm, pwa.forms.actionForm);
};

View File

@@ -0,0 +1,88 @@
<h1 class="h3 mb-3">Chart.js</h1>
<div class="row">
<div class="col-12 col-lg-6">
<div class="card flex-fill w-100">
<div class="card-header">
<h5 class="card-title">Line Chart</h5>
<h6 class="card-subtitle text-muted">A line chart is a way of plotting data points on a line.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-line"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Bar Chart</h5>
<h6 class="card-subtitle text-muted">A bar chart provides a way of showing data values represented as vertical bars.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-bar"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Doughnut Chart</h5>
<h6 class="card-subtitle text-muted">Doughnut charts are excellent at showing the relational proportions between data.</h6>
</div>
<div class="card-body">
<div class="chart chart-sm">
<canvas id="chartjs-doughnut"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Pie Chart</h5>
<h6 class="card-subtitle text-muted">Pie charts are excellent at showing the relational proportions between data.</h6>
</div>
<div class="card-body">
<div class="chart chart-sm">
<canvas id="chartjs-pie"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Radar Chart</h5>
<h6 class="card-subtitle text-muted">A radar chart is a way of showing multiple data points and the variation between them.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-radar"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Polar Area Chart</h5>
<h6 class="card-subtitle text-muted">Polar area charts are similar to pie charts, but each segment has the same angle.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-polar-area"></canvas>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,170 @@
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{publicinfo}}}</h5>
</div>
<div class="card-body">
<form id="userpublicinfo" somethingchange="false">
<div class="row">
<div class="col-md-8">
<div class="mb-3" {{{meta.users.PSEUDO.tag}}}
newvalue="">
{{{meta.users.PSEUDO.html}}}
</div>
<div class="mb-3" {{{meta.users.BIOGRAPHY.tag}}}
newvalue="">
{{{meta.users.BIOGRAPHY.html}}}
</div>
<div class="mb-3" {{{meta.users.PUBLICKEY.tag}}}
newvalue="">
{{{meta.users.PUBLICKEY.html}}}
</div>
</div>
<div class="col-md-4">
<div class="text-center" {{{meta.users.IMGAVATAR.tag}}}
newvalue="">
{{{meta.users.IMGAVATAR.html}}}
</div>
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickpub}}}">
{{{btnsavepub}}}
</button>
<div class="msgsubmit"></div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{privateinfo}}}</h5>
</div>
<div class="card-body">
<form id="userprivateinfo" somethingchange="false" >
<div class="row">
{{{infoprivate}}}
</div>
<div class="row">
<div class="mb-3 col-md-6" {{{meta.users.NICKNAME.tag}}}>
{{{meta.users.NICKNAME.html}}}
</div>
<div class="mb-3 col-md-6" {{{meta.users.NAME.tag}}}>
{{{meta.users.NAME.html}}}
</div>
</div>
<div class="mb-3" {{{meta.users.EMAIL.tag}}}>
{{{meta.users.EMAIL.html}}}
</div>
<div class="mb-3" {{{meta.users.ADDRESS1.tag}}}>
{{{meta.users.ADDRESS1.html}}}
</div>
<div class="mb-3" {{{meta.users.ADDRESS2.tag}}}>
{{{meta.users.ADDRESS2.html}}}
</div>
<div class="row">
<div class="mb-3 col-md-6" {{{meta.users.CITY.tag}}}>
{{{meta.users.CITY.html}}}
</div>
<div class="mb-3 col-md-4" {{{meta.users.ZIP.tag}}}>
{{{meta.users.ZIP.html}}}
</div>
<div class="mb-3 col-md-2" {{{meta.users.COUNTRY.tag}}}>
{{{meta.users.COUNTRY.html}}}
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickprivate}}}">
{{{btnsaveprivate}}}
</button>
</form>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{publicinfo}}}</h5>
</div>
<div class="card-body">
<form id="userpublicinfo" somethingchange="false">
<div class="row">
<div class="col-md-8">
<div class="mb-3" {{{meta.users.PSEUDO.tag}}} tpl="{{{users.PSEUDO.type}}}" object="users" fieldname="PSEUDO" placeholder="{{{users.PSEUDO.placeholder}}}" desc="{{{users.PSEUDO.desc}}}"
Checkjson="{{{users.PSEUDO.check}}}"
newvalue="">
{{{meta.users.PSEUDO.html}}}
</div>
<div class="mb-3" formfieldtype="{{users.BIOGRAPHY.type}}" object="users" fieldname="BIOGRAPHY" placeholder="{{users.BIOGRAPHY.placeholder}}" rows="{{{users.BIOGRAPHY.rows}}}" desc="{{{users.BIOGRAPHY.desc}}}" Checkjson="{{{users.BIOGRAPHY.check}}}" info="{{{users.BIOGRAPHY.info}}}"
newvalue="">
</div>
<div class="mb-3" formfieldtype="{{users.PUBLICKEY.type}}" object="users" fieldname="PUBLICKEY" placeholder="{{}}" rows="2" desc="{{{users.PUBLICKEY.desc}}}" info="{{{users.PUBLICKEY.info}}}"
newvalue="">
</div>
</div>
<div class="col-md-4">
<div class="text-center" formfieldtype="{{users.IMGAVATAR.type}}" object="users" fieldname="IMGAVATAR" altimg="{{users.PSEUDO.desc}}" classimg="rounded-circle img-responsive mt-2" width="{{users.IMGAVATAR.width}}" height="{{users.IMGAVATAR.height}}" classdivupload="mt-2" classbtn="btn btn-primary" desc="{{{users.IMGAVATAR.desc}}}"
info="{{{users.IMGAVATAR.info}}}"
newvalue="">
</div>
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickpub}}}">
{{{btnsavepub}}}
</button>
<div class="msgsubmit"></div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{privateinfo}}}</h5>
</div>
<div class="card-body">
<form id="userprivateinfo" somethingchange="false" >
<div class="row">
{{{infoprivate}}}
</div>
<div class="row">
<div class="mb-3 col-md-6" formfieldtype="{{{users.NICKNAME.type}}}" object="users" fieldname="NICKNAME" placeholder="{{{users.NICKNAME.placeholder}}}" desc="{{{users.NICKNAME.desc}}}"
Checkjson="{{{users.NICKNAME.check}}}"
newvalue="">
<div class="mb-3 col-md-6" formfieldtype="{{{users.NAME.type}}}" object="users" fieldname="NAME" placeholder="{{{users.NAME.placeholder}}}" desc="{{{users.NAME.desc}}}"
Checkjson="{{{users.NAME.check}}}"
newvalue="">
</div>
</div>
<div class="mb-3" formfieldtype="{{{users.EMAIL.type}}}" object="users" fieldname="EMAIL" placeholder="{{{users.EMAIL.placeholder}}}" desc="{{{users.EMAIL.desc}}}"
Checkjson="{{{users.EMAIL.check}}}"
newvalue="">
</div>
<div class="mb-3" formfieldtype="{{{users.ADDRESS1.type}}}" object="users" fieldname="ADDRESS1" placeholder="{{{users.ADDRESS1.placeholder}}}" desc="{{{users.ADDRESS1.desc}}}"
Checkjson="{{{users.ADDRESS1.check}}}"
newvalue="">
</div>
<div class="mb-3" formfieldtype="{{{users.ADDRESS2.type}}}" object="users" fieldname="ADDRESS2" placeholder="{{{users.ADDRESS2.placeholder}}}" desc="{{{users.ADDRESS2.desc}}}"
Checkjson="{{{users.ADDRESS2.check}}}"
newvalue="">
</div>
<div class="row">
<div class="mb-3 col-md-6" formfieldtype="{{{users.CITY.type}}}" object="users" fieldname="CITY" placeholder="{{{users.CITY.placeholder}}}" desc="{{{users.CITY.desc}}}"
Checkjson="{{{users.CITY.check}}}"
newvalue="">
</div>
<div class="mb-3 col-md-4" formfieldtype="{{{users.ZIP.type}}}" object="users" fieldname="ZIP" placeholder="{{{users.ZIP.placeholder}}}" desc="{{{users.ZIP.desc}}}"
Checkjson="{{{users.ZIP.check}}}"
newvalue="">
</div>
<div class="mb-3 col-md-2" formfieldtype="{{{users.COUNTRY.type}}}" object="users" fieldname="COUNTRY" placeholder="{{{users.COUNTRY.placeholder}}}" desc="{{{users.COUNTRY.desc}}}"
Checkjson="{{{users.COUNTRY.check}}}"
newvalue="">
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickprivate}}}">
{{{btnsaveprivate}}}
</button>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,23 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">Password</h5>
<form>
<div class="mb-3">
<label class="form-label" for="inputPasswordCurrent">Current password</label>
<input type="password" class="form-control" id="inputPasswordCurrent">
<small><a href="#">Forgot your password?</a></small>
</div>
<div class="mb-3">
<label class="form-label" for="inputPasswordNew">New password</label>
<input type="password" class="form-control" id="inputPasswordNew">
</div>
<div class="mb-3">
<label class="form-label" for="inputPasswordNew2">Verify password</label>
<input type="password" class="form-control" id="inputPasswordNew2">
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
</div>
</div>

View File

@@ -0,0 +1,19 @@
{
"title": "Profile",
"desc": "<p>Vos informations</p>",
"submenutitle": "Blabla",
"submenuitems": [{
"submenudesc": "group1",
"active": "active",
"id": "gr1",
"content": "blabla group1"
}, {
"submenudesc": "group2",
"id": "gr2",
"content": "blabla group2"
}, {
"submenudesc": "group3",
"id": "gr3",
"content": "blabla group 3"
}]
}

View File

@@ -0,0 +1,20 @@
$( 'a.contenu' )
.on( 'click', function () {
// $( this )
// .preventDefault();
// hide all
$( '.face.back >div' )
.addClass( 'd-none' );
const dest = $( this )
.attr( 'data-dest' )
$( '.face.back >.' + dest )
.removeClass( 'd-none' );
$( '.flip >.card' )
.toggleClass( 'flipped' );
} )
$( '.face.back >> a.retour' )
.on( 'click', function () {
$( '.flip >.card' )
.toggleClass( 'flipped' );
} )

View File

@@ -0,0 +1,37 @@
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="flip">
<div class="card">
<div class="face front">
<div class="inner">
<h3>Blabla</h3>
<p><a class="contenu" data-dest="contenuA">ContenuA</a></p>
<p><a class="contenu" data-dest="contenuB">ContenuB</a></p>
<p><a class="contenu" data-dest="contenuC">ContenuC</a></p>
</div>
</div>
<div class="face back">
<div class="inner text-center contenuA">
<a class="retour"> back </a>
<h3>A Improved efficiency through automation</h3>
</div>
<div class="inner text-center contenuB">
<a class="retour"> back </a>
<h3>B Improved efficiency through automation</h3>
</div>
<div class="inner text-center contenuC">
<a class="retour"> back </a>
<h3>C Improved efficiency through automation</h3>
</div>
<button type="button" class="btn btn-default">Know More</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,53 @@
.flipbox {
.flip {
-webkit-perspective: 800;
perspective: 800;
position: relative;
text-align: center;
}
.flip .card.flipped {
-webkit-transform: rotatey(-180deg);
transform: rotatey(-180deg);
}
.flip .card {
width: 270px;
height: 178px;
-webkit-transform-style: preserve-3d;
-webkit-transition: 0.5s;
transform-style: preserve-3d;
transition: 0.5s;
background-color: #fff;
}
.flip .card .face {
-webkit-backface-visibility: hidden ;
backface-visibility: hidden ;
z-index: 2;
}
.flip .card .front {
position: absolute;
width: 270px;
z-index: 1;
}
.flip .card .front img{
width: 100%;
height: 100%;
}
.flip .card .img {
position: relaitve;
width: 270px;
height: 178px;
z-index: 1;
border: 2px solid #000;
}
.flip .card .back {
padding-top: 10%;
-webkit-transform: rotatey(-180deg);
transform: rotatey(-180deg);
position: absolute;
}
.inner{
margin:0px !important;
width: 270px;
}
}

View File

@@ -0,0 +1,9 @@
<!-- main class="d-flex w-100 h-100" -->
<div class="container d-flex flex-column">
<div class="row vh-100">
<div class="col-sm-10 col-md-8 col-lg-6 mx-auto d-table h-100">
<importhtml></importhtml>
</div>
</div>
</div>
<!-- /main -->

View File

@@ -0,0 +1,33 @@
{
"iconnotif": "bell",
"number": 0,
"notifheader": "Vos notifications",
"notiffooter": "Voir toutes les notifications",
"actionnotifmanager": "",
"href": "?action=notification.view",
"notifs": [{
"urldetail": "#",
"classicon": "text-danger",
"icon": "alert",
"title": "Intusion de 192.168.1.3",
"desc": "Check your log",
"elapse": "il y a 13mn"
},
{
"urldetail": "#",
"classicon": "text-success",
"icon": "star",
"title": "Successfull backup",
"desc": "",
"elapse": "il y a 2 jours"
},
{
"urldetail": "#",
"classicon": "text-warning",
"icon": "bell",
"title": "Commande en attente",
"desc": "Vous avez des commandes en attente de traitement",
"elapse": "il y a 6 heures"
}
]
}

View File

@@ -0,0 +1,28 @@
"use strict";
var pwa = pwa || {};
/*
Manage notification
Get notification after tomenu was load
from /components/notification
____________________
*/
//--##
pwa.notification = {};
pwa.notification.update = () => {
console.log( 'get notification update for a user' );
axios.get( `https://${pwa.state.data.ctx.urlbackoffice}/notifications/user`, { headers: pwa.state.data.headers } )
.then( rep => {
console.log( "list des notifs", rep.data.payload.data )
rep.data.payload.data.number = rep.data.payload.data.notifs.length;
document.getElementById( "topbarmenuright" )
.innerHTML = Mustache.render( pwa.state.data.tpl.notiflist, rep.data.payload.data ) + document.getElementById( "topbarmenuright" )
.innerHTML;
} )
.catch( err => {
console.log( `Err pwa.notification.update data for user into header ${pwa.state.data.headers}`, err );
} );
};

View File

@@ -0,0 +1,32 @@
<li class="nav-item dropdown">
<a class="nav-icon dropdown-toggle" href="#" id="alertsDropdown" data-bs-toggle="dropdown">
<div class="position-relative">
<i class="align-middle" data-feather="{{iconnotif}}"></i>
<span class="indicator">{{number}}</span>
</div>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end py-0" aria-labelledby="alertsDropdown">
<div class="dropdown-menu-header">
{{{notificationheader}}}
</div>
<div class="list-group">
{{#notifs}}
<a href="{{urldetail}}" class="list-group-item">
<div class="row g-0 align-items-center">
<div class="col-2">
<i class="{{{classicon}}}" data-feather="{{{icon}}}"></i>
</div>
<div class="col-10">
<div class="text-dark">{{{title}}}</div>
<div class="text-muted small mt-1">{{{desc}}}</div>
<div class="text-muted small mt-1">{{{elapse}}}</div>
</div>
</div>
</a>
{{/notifs}}
</div>
<div class="dropdown-menu-footer">
<a href="#" class="text-muted">{{{notificationfooter}}}</a>
</div>
</div>
</li>

View File

@@ -0,0 +1,6 @@
{
"icon": "settings",
"desc": "Log out",
"href": "?action=auth.logout",
"menubreaker": false
}

View File

@@ -0,0 +1,6 @@
{
"icon": "user",
"desc": "Mes Activités",
"href": "?action=userprofile.settings",
"menubreaker": false
}

View File

@@ -0,0 +1,6 @@
{
"icon": "pie-chart",
"desc": "Mon profile",
"href": "?action=userprofile.settings",
"menubreaker": false
}

View File

@@ -0,0 +1,88 @@
<h1 class="h3 mb-3">Chart.js</h1>
<div class="row">
<div class="col-12 col-lg-6">
<div class="card flex-fill w-100">
<div class="card-header">
<h5 class="card-title">Line Chart</h5>
<h6 class="card-subtitle text-muted">A line chart is a way of plotting data points on a line.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-line"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Bar Chart</h5>
<h6 class="card-subtitle text-muted">A bar chart provides a way of showing data values represented as vertical bars.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-bar"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Doughnut Chart</h5>
<h6 class="card-subtitle text-muted">Doughnut charts are excellent at showing the relational proportions between data.</h6>
</div>
<div class="card-body">
<div class="chart chart-sm">
<canvas id="chartjs-doughnut"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Pie Chart</h5>
<h6 class="card-subtitle text-muted">Pie charts are excellent at showing the relational proportions between data.</h6>
</div>
<div class="card-body">
<div class="chart chart-sm">
<canvas id="chartjs-pie"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Radar Chart</h5>
<h6 class="card-subtitle text-muted">A radar chart is a way of showing multiple data points and the variation between them.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-radar"></canvas>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<h5 class="card-title">Polar Area Chart</h5>
<h6 class="card-subtitle text-muted">Polar area charts are similar to pie charts, but each segment has the same angle.</h6>
</div>
<div class="card-body">
<div class="chart">
<canvas id="chartjs-polar-area"></canvas>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,66 @@
var pwa = pwa || {};
/*
Manage user data for current login user
From user data = pwa.state.data.userlogin
*/
//--##
pwa.main = pwa.main || {};
pwa.main.tpldata = pwa.main.tpldata || {};
pwa.main.tpl = pwa.main.tpl || {};
pwa.main.ref = pwa.main.ref || {};
//Add template here to make it available after authentification
// in pwa.state.data.tpl[name]
// pwa.state.data.tpldata[name] in the user's language by adding at the end of value _{pwa.state.data.ctx.lang}.json
pwa.main.tpl.usersettingaccount = `static/components/userprofile/usersettingaccount.mustache`;
pwa.main.tpldata.topbaruserprofile = `static/components/userprofile/topbaruserprofile`;
pwa.main.tpldata.topbaruseractivity = `static/components/userprofile/topbaruseractivity`;
pwa.main.tpldata.topbaruserLogout = `static/components/userprofile/topbaruserLogout`;
pwa.main.referential.users = 'url ... obeject/users.json';
//header check if user is allow to get this
pwa.userprofile = pwa.userprofile || {};
pwa.userprofile.settings = ( e ) => {
console.groupCollapsed( 'load user settings' );
console.log( Object.keys( pwa ) )
//data form init from pwa.state or axios.get( data from user )
const data = pwa.state.data.userlogin;
// add meta data object to create forms
const meta = { "users": {} };
pwa.state.data.app.referentials.object.users.forEach( f => {
// genere tag
const test = Object.keys( f )
f.tag = " object='users' " + test.reduce( ( ac, k ) => {
return ac + k + '="' + f[ k ] + '" ';
} );
// genere html field care do html after tag if not
f.html = pwa.form.tpl[ f.tpl ].html( f )
meta.users[ f.idfield ] = f
//console.log( f.idfield )
//console.log( f )
} );
pwa.state.data.app.referentials.json.usersetting.submenuitems[ 0 ].meta = meta;
console.log( "meta", pwa.state.data.app.referentials.json.usersetting )
// tpl in #usersetting data in referentials json usersetting
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( document.getElementById( 'setting' )
.innerHTML, pwa.state.data.app.referentials.json.usersetting );
// load each content of tab then init form with data merged with tab info
pwa.state.data.app.referentials.json.usersetting.submenuitems.forEach( tab => {
document.getElementById( tab.id )
.innerHTML = Mustache.render( document.getElementById( `${tab.id}tpl` )
.innerHTML, tab );
console.log( tab.id, tab )
// Convert each div with formfieldtype to a form field set with value if exist and listen button to run callback
pwa.form.init( tab.id, { ...tab, ...data }, pwa.userprofile.save )
} )
console.groupEnd();
}
pwa.userprofile.save = ( data ) => {
console.log( "data to save", data )
}
pwa.userprofile.activities = () => {
console.group( 'load user activity' );
}

View File

@@ -0,0 +1,170 @@
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{publicinfo}}}</h5>
</div>
<div class="card-body">
<form id="userpublicinfo" somethingchange="false">
<div class="row">
<div class="col-md-8">
<div class="mb-3" {{{meta.users.PSEUDO.tag}}}
newvalue="">
{{{meta.users.PSEUDO.html}}}
</div>
<div class="mb-3" {{{meta.users.BIOGRAPHY.tag}}}
newvalue="">
{{{meta.users.BIOGRAPHY.html}}}
</div>
<div class="mb-3" {{{meta.users.PUBLICKEY.tag}}}
newvalue="">
{{{meta.users.PUBLICKEY.html}}}
</div>
</div>
<div class="col-md-4">
<div class="text-center" {{{meta.users.IMGAVATAR.tag}}}
newvalue="">
{{{meta.users.IMGAVATAR.html}}}
</div>
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickpub}}}">
{{{btnsavepub}}}
</button>
<div class="msgsubmit"></div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{privateinfo}}}</h5>
</div>
<div class="card-body">
<form id="userprivateinfo" somethingchange="false" >
<div class="row">
{{{infoprivate}}}
</div>
<div class="row">
<div class="mb-3 col-md-6" {{{meta.users.NICKNAME.tag}}}>
{{{meta.users.NICKNAME.html}}}
</div>
<div class="mb-3 col-md-6" {{{meta.users.NAME.tag}}}>
{{{meta.users.NAME.html}}}
</div>
</div>
<div class="mb-3" {{{meta.users.EMAIL.tag}}}>
{{{meta.users.EMAIL.html}}}
</div>
<div class="mb-3" {{{meta.users.ADDRESS1.tag}}}>
{{{meta.users.ADDRESS1.html}}}
</div>
<div class="mb-3" {{{meta.users.ADDRESS2.tag}}}>
{{{meta.users.ADDRESS2.html}}}
</div>
<div class="row">
<div class="mb-3 col-md-6" {{{meta.users.CITY.tag}}}>
{{{meta.users.CITY.html}}}
</div>
<div class="mb-3 col-md-4" {{{meta.users.ZIP.tag}}}>
{{{meta.users.ZIP.html}}}
</div>
<div class="mb-3 col-md-2" {{{meta.users.COUNTRY.tag}}}>
{{{meta.users.COUNTRY.html}}}
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickprivate}}}">
{{{btnsaveprivate}}}
</button>
</form>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{publicinfo}}}</h5>
</div>
<div class="card-body">
<form id="userpublicinfo" somethingchange="false">
<div class="row">
<div class="col-md-8">
<div class="mb-3" {{{meta.users.PSEUDO.tag}}} tpl="{{{users.PSEUDO.type}}}" object="users" fieldname="PSEUDO" placeholder="{{{users.PSEUDO.placeholder}}}" desc="{{{users.PSEUDO.desc}}}"
Checkjson="{{{users.PSEUDO.check}}}"
newvalue="">
{{{meta.users.PSEUDO.html}}}
</div>
<div class="mb-3" formfieldtype="{{users.BIOGRAPHY.type}}" object="users" fieldname="BIOGRAPHY" placeholder="{{users.BIOGRAPHY.placeholder}}" rows="{{{users.BIOGRAPHY.rows}}}" desc="{{{users.BIOGRAPHY.desc}}}" Checkjson="{{{users.BIOGRAPHY.check}}}" info="{{{users.BIOGRAPHY.info}}}"
newvalue="">
</div>
<div class="mb-3" formfieldtype="{{users.PUBLICKEY.type}}" object="users" fieldname="PUBLICKEY" placeholder="{{}}" rows="2" desc="{{{users.PUBLICKEY.desc}}}" info="{{{users.PUBLICKEY.info}}}"
newvalue="">
</div>
</div>
<div class="col-md-4">
<div class="text-center" formfieldtype="{{users.IMGAVATAR.type}}" object="users" fieldname="IMGAVATAR" altimg="{{users.PSEUDO.desc}}" classimg="rounded-circle img-responsive mt-2" width="{{users.IMGAVATAR.width}}" height="{{users.IMGAVATAR.height}}" classdivupload="mt-2" classbtn="btn btn-primary" desc="{{{users.IMGAVATAR.desc}}}"
info="{{{users.IMGAVATAR.info}}}"
newvalue="">
</div>
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickpub}}}">
{{{btnsavepub}}}
</button>
<div class="msgsubmit"></div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{privateinfo}}}</h5>
</div>
<div class="card-body">
<form id="userprivateinfo" somethingchange="false" >
<div class="row">
{{{infoprivate}}}
</div>
<div class="row">
<div class="mb-3 col-md-6" formfieldtype="{{{users.NICKNAME.type}}}" object="users" fieldname="NICKNAME" placeholder="{{{users.NICKNAME.placeholder}}}" desc="{{{users.NICKNAME.desc}}}"
Checkjson="{{{users.NICKNAME.check}}}"
newvalue="">
<div class="mb-3 col-md-6" formfieldtype="{{{users.NAME.type}}}" object="users" fieldname="NAME" placeholder="{{{users.NAME.placeholder}}}" desc="{{{users.NAME.desc}}}"
Checkjson="{{{users.NAME.check}}}"
newvalue="">
</div>
</div>
<div class="mb-3" formfieldtype="{{{users.EMAIL.type}}}" object="users" fieldname="EMAIL" placeholder="{{{users.EMAIL.placeholder}}}" desc="{{{users.EMAIL.desc}}}"
Checkjson="{{{users.EMAIL.check}}}"
newvalue="">
</div>
<div class="mb-3" formfieldtype="{{{users.ADDRESS1.type}}}" object="users" fieldname="ADDRESS1" placeholder="{{{users.ADDRESS1.placeholder}}}" desc="{{{users.ADDRESS1.desc}}}"
Checkjson="{{{users.ADDRESS1.check}}}"
newvalue="">
</div>
<div class="mb-3" formfieldtype="{{{users.ADDRESS2.type}}}" object="users" fieldname="ADDRESS2" placeholder="{{{users.ADDRESS2.placeholder}}}" desc="{{{users.ADDRESS2.desc}}}"
Checkjson="{{{users.ADDRESS2.check}}}"
newvalue="">
</div>
<div class="row">
<div class="mb-3 col-md-6" formfieldtype="{{{users.CITY.type}}}" object="users" fieldname="CITY" placeholder="{{{users.CITY.placeholder}}}" desc="{{{users.CITY.desc}}}"
Checkjson="{{{users.CITY.check}}}"
newvalue="">
</div>
<div class="mb-3 col-md-4" formfieldtype="{{{users.ZIP.type}}}" object="users" fieldname="ZIP" placeholder="{{{users.ZIP.placeholder}}}" desc="{{{users.ZIP.desc}}}"
Checkjson="{{{users.ZIP.check}}}"
newvalue="">
</div>
<div class="mb-3 col-md-2" formfieldtype="{{{users.COUNTRY.type}}}" object="users" fieldname="COUNTRY" placeholder="{{{users.COUNTRY.placeholder}}}" desc="{{{users.COUNTRY.desc}}}"
Checkjson="{{{users.COUNTRY.check}}}"
newvalue="">
</div>
</div>
<button type="submit" class="btn btn-primary" onclick="{{{onclickprivate}}}">
{{{btnsaveprivate}}}
</button>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,22 @@
<div class="card">
<div class="card-body">
<h5 class="card-title">Password</h5>
<form>
<div class="mb-3">
<label class="form-label" for="inputPasswordCurrent">Current password</label>
<input type="password" class="form-control" id="inputPasswordCurrent">
<small><a href="#">Forgot your password?</a></small>
</div>
<div class="mb-3">
<label class="form-label" for="inputPasswordNew">New password</label>
<input type="password" class="form-control" id="inputPasswordNew">
</div>
<div class="mb-3">
<label class="form-label" for="inputPasswordNew2">Verify password</label>
<input type="password" class="form-control" id="inputPasswordNew2">
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
</form>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More