first new commit

This commit is contained in:
2023-01-22 10:53:09 +01:00
commit 869f192031
280 changed files with 101529 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
<html data-tribeid="apixtribe" data-website="webapp" data-nametpl="app" data-pagename="index_fr" data-pageneedauthentification="false" data-pageredirectforauthentification="fullscreen_auth" data-urlbackoffice="apixtribe.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/checkdata.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="apixtribe" data-website="webapp" data-nametpl="fullscreen" data-pagename="auth_fr" data-pageneedauthentification="false" data-pageredirectforauthentification="fullscreen_auth" data-urlbackoffice="apixtribe.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>apixtribe></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 apixtribe 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 apixtribe" 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="adminapixtribe" 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://apixtribe.org/anonymat_avec_apixppress.html'>Anomymat & apixtribe</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/checkdata.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.check = () => {
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', checkdata.test.password( "", data.PASSWORD ) )
if( data.LOGIN.length < 4 || !checkdata.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/checkdata.js"></script>
or with const checkdata = require('../public/js/checkdata.js')
*/
// --##
const checkdata = {};
// each checkdata.test. return true or false
checkdata.test = {};
checkdata.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
*/
checkdata.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( !checkdata.test.emailadress( "", emails[ i ].trim() ) ) {
return false
}
}
};
return true;
};
checkdata.test.password = ( ctx, pwd ) => {
const regExp = new RegExp(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/
);
return regExp.test( pwd );
};
checkdata.test.required = ( ctx, val ) =>
val != null && val != 'undefined' && val.length > 0;
checkdata.test.isNumber = ( ctx, n ) => typeof n === 'number';
checkdata.test.isInt = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) == n;
checkdata.test.isFloat = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) != n;
checkdata.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;
}
};
checkdata.test.isDateDay = ( ctx, dateDay ) => true;
/* checkdata.test.filterInvalidInArray = (array, validate) =>
array ? array.filter(el => !validate(el)) : true;
// return true when every elements is valid
*/
checkdata.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
*/
checkdata.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
*/
checkdata.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( !checkdata.test.phoneNumber( "", phones[ i ].trim() ) ) {
return false
}
}
};
return true;
};
// checkdata.normalize take a correct data then reformat it to harmonise it
checkdata.normalize = {};
checkdata.normalize.phoneNumber = ( ctx, phone ) => {
phone = phone.trim()
.replace( /[- .]/g, '' );
if( checkdata.test.phoneNumber( '', phone ) && phone.length == 10 && phone[ 0 ] == "0" ) {
phone = '+33 ' + phone.substring( 1 );
}
return phone;
}
checkdata.normalize.upperCase = ( ctx, txt ) => txt.toUpperCase();
checkdata.normalize.lowerCase = ( ctx, txt ) => txt.toLowerCase();
// fixe 10 position et complete par des 0 devant
checkdata.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(checkdata.test.phoneNumber('', tt))
console.log(checkdata.normalize.phoneNumber('', tt))
*/
checkdata.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 ].check.forEach( ctrl => {
console.log( 'ctrl', ctrl );
contexte.currentfield = field;
if( !checkdata.test[ ctrl ] ) {
invalidefor.push( 'ERR check function does not exist :' + ctrl + '___' + field )
} else {
if( !checkdata.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 = checkdata;

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":"apixtribe Need-Data","short_name":"apixtribe","start_url":"app_index_fr.html","background_color":"purple","description":"Webapp to manage an apixtribe 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.sidebarAdminapixtribe = `static/components/adminapixtribe/sidebaradminapixtribe`;
// Custom data in user lang
//tremplate to store
pwa.main.tpl.adminapixtribe = 'static/components/adminapixtribe/adminapixtribe.mustache';
pwa.main.tpl.adminapixtribesysinfo = 'static/components/adminapixtribe/adminapixtribesysinfo.mustache';
pwa.main.tpl.adminapixtribeactivity = 'static/components/adminapixtribe/adminapixtribeactivity.mustache';
pwa.adminapixtribe = {};
pwa.adminapixtribe.init = () => {
// Run back stuff to update data
}
pwa.adminapixtribe.sysinfoview = () => {
const datasysinfo = {}; //request to get info and push them in template
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( pwa.state.data.tpl.adminapixtribesysinfo, datasysinfo );
}
pwa.adminapixtribe.activityview = () => {
const dataactivity = {}; //request to get info and push them in template
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( pwa.state.data.tpl.adminapixtribeactivity, dataactivity );
}
pwa.adminapixtribe.tribeidview = () => {
const datatribeid = {}; //request to get info and push them in template
document.getElementById( 'maincontent' )
.innerHTML = Mustache.render( pwa.state.data.tpl.adminapixtribe, 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 apixtribe",
"sbssgroupmenu": [{
"name": "Activité",
"icon": "sliders",
"actionclick": "pwa.adminapixtribe.init()",
"iditemmenus": "adminxpress",
"itemmenus": [{
"name": "Info activités",
"actionclick": "pwa.adminapixtribe.activityview()"
},
{
"name": "Admin des tribeid",
"actionclick": "pwa.adminapixtribe.tribeidview()"
}
]
}, {
"name": "System Info",
"icon": "sliders",
"actionclick": "pwa.adminapixtribe.sysinfoview()"
}]
}

View File

@@ -0,0 +1,22 @@
{
"groupheader": "Admin apixtribe",
"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="{{{urlapixtribe}}}" 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": {
"urlapixtribe": "https://apixtribe.org",
"claim": "apixtribe 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": "apixtribe",
"sbssgroupmenu": [{
"name": "Les news",
"icon": "sliders",
"actionclick": "pwa.news.init()"
},
{
"name": "Reporting",
"icon": "sliders",
"actionclick": "pwa.reporting.init()",
"iditemmenus": "reportingapixtribe",
"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 apixtribe 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://apixtribe.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}}}"
checkdata="{{{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}}}" checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}" checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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}}}"
checkdata="{{{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>

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,26 @@
<h1 class="h3 mb-3">{{title}}</h1>
{{{desc}}}
<div class="row">
<div class="col-md-3 col-xl-2">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">{{{submenutitle}}}</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">{{{submenudesc}}}
</a>
{{/submenuitems}}
</div>
</div>
</div>
<div class="col-md-9 col-xl-10">
<div class="tab-content">
{{#submenuitems}}
<div class="tab-pane fade show {{active}}" id="{{id}}" role="tabpanel">
{{{content}}}
</div>
{{/submenuitems}}
</div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,7 @@
{
"title": "Need-Data manager",
"js": [],
"icons": ["static/img/icons/iconX74x74.png"],
"manifest": "manifest.json",
"css": ["static/fonts/icofont/icofont.min.css", "css/app/styles.css"]
}

View File

@@ -0,0 +1,53 @@
{
"signin": {
"claimh1": "Need-Data",
"claimp": "Votre hébergement apixtribe en toute confidentialité.",
"logo": "static/img/logo/ndda.png",
"classlogo": "img-fluid",
"login": "Identifiant",
"loginph": "Votre identifiant (login ou clé public)",
"password": "Mot de passe",
"passwordph": "Mot de passe ou hash sur clé public",
"forget": "Mot de passe oublié?",
"forgetonclick": "pwa.auth.route('#resetpsw');return false;",
"rememberme": "Se souvenir de moi sur ce navigateur",
"submit": "S'authentifier",
"onclick": "pwa.auth.login();return false;",
"registerrequest": "Rejoindre un espace",
"registeronclick": "pwa.auth.route('#register');return false;",
"ifAuthenticatedRouteto": "app_index_fr.html",
"msgok": "Bienvenu dans votre espace.",
"msgko": "Désolé, vos identifiants ne sont pas valides! Au bout de 3 echecs, vous devrez attendre 1 minute entre chaque tentative."
},
"resetpsw": {
"claimh1": "Mot de passe oublié",
"claimp": "Indiquez votre email. Si vous n'avez pas indiqué d'email, vos données sont definitivement perdues, c'est le prix de votre anonymat.",
"emailph": "indiquez votre email",
"submit": "Recevoir un lien temporaire",
"onclick": "pwa.auth.forget();return false;",
"msgok": "Une email vous a été envoyé",
"msgko": "Lien impossible à envoyer "
},
"register": {
"claimh1": "S'inscrire à votre communauté",
"claimp": "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://apixtribe.org/anonymat_avec_apixppress.html'>Anomymat & apixtribe</a>.",
"logoapixtribe": "static/img/logo/apixtribe.png",
"logoowner": "static/img/logo/ndda.png",
"login": "Votre identifiant",
"loginph": "login ou clé public",
"community": "Nom de communauté",
"communityph": "Nom communiqué par la personne qui vous invite.",
"email": "Email (optionel)",
"emailph": "Sans email impossible de ré-initialiser son compte",
"password": "Mot de passe",
"passwordph": "Mot de passe ou hash sur clé public",
"autologin": "Génerer une paire<br> de clé public/privée",
"submit": "S'enregistrer",
"autologinonclick": "pwa.auth.autologin();return false;",
"onclick": "pwa.auth.register();return false;",
"msgok": "Votre compte a bien été créé",
"msgko": "Désolé impossible de créer le compte",
"signinonclick": "pwa.auth.route('#signin');return false;",
"signinrequest": "S'authentifier"
}
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,5 @@
{
"title": "apixtribe",
"js": [],
"css": ["static/fonts/icofont/icofont.min.css", "css/fullscreen/styles.css"]
}

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,22 @@
[{
"groupheader": "Admin apixtribe",
"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,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": "apixtribe",
"sbssgroupmenu": [{
"name": "Les news",
"icon": "sliders",
"actionclick": "pwa.news.init()"
},
{
"name": "Reporting",
"icon": "sliders",
"actionclick": "pwa.reporting.init()",
"iditemmenus": "reportingapixtribe",
"itemmenus": [{
"name": "",
"actionclick": "pwa.reporting.init()"
}]
}
]
}]
}

View File

@@ -0,0 +1,19 @@
[{
"icon": "user",
"desc": "Mon 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=auth.logout",
"menubreaker": false
}
]

View File

@@ -0,0 +1,16 @@
{
"withsearch": true,
"searchtxt": "Recherche...",
"withnotification": true,
"withmessage": true,
"messageheader": "Vos messages non lus",
"messagefooter": "Voir tous les messages",
"avatarimg": "static/img/avataranonymous2.png",
"name": "Anonymous",
"menuprofil": [{
"icon": "user",
"desc": "S'identifier",
"href": "fullscreen_auth_fr.html",
"menubreaker": false
}]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.3 MiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1 @@
{"time":1640518264618}

View File

@@ -0,0 +1 @@
{"time":1640518264044}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB