Compare commits
66 Commits
dev
...
0000-impro
Author | SHA1 | Date |
---|---|---|
Juan Ferrer | f7d58c8546 | |
Juan Ferrer Toribio | 85bd397b83 | |
Juan Ferrer Toribio | 5d26b6cc3f | |
Juan Ferrer Toribio | 2ca8a09acd | |
Juan Ferrer Toribio | 6666304332 | |
Juan Ferrer Toribio | 479ad20fa9 | |
Juan Ferrer Toribio | 17d3a4457e | |
Juan Ferrer Toribio | e458987f67 | |
Juan Ferrer Toribio | cae37d5d6a | |
Juan Ferrer Toribio | a341f17ee5 | |
Juan Ferrer Toribio | 6829bb5a83 | |
Juan Ferrer Toribio | 2b9c28bc76 | |
Juan Ferrer Toribio | a5225fbb5c | |
Juan Ferrer Toribio | 42b5662b23 | |
Juan Ferrer Toribio | b9c14883c4 | |
Juan Ferrer Toribio | 7b54145da6 | |
Juan Ferrer Toribio | 23715816ce | |
Juan Ferrer Toribio | 857e46ce4d | |
Juan Ferrer Toribio | ce167066f4 | |
Juan Ferrer Toribio | 7658a5680d | |
Juan Ferrer Toribio | f9c002f08a | |
Juan Ferrer Toribio | 1cec1b4cd6 | |
Juan Ferrer Toribio | c3c7c77b65 | |
Juan Ferrer Toribio | 3aaf1298ee | |
Juan Ferrer Toribio | 37236b6bb0 | |
Juan Ferrer Toribio | fe68eb5ce0 | |
Juan Ferrer Toribio | 4d8bd4ad95 | |
Juan Ferrer Toribio | f0dcd47baf | |
Juan Ferrer Toribio | c1cf88e44d | |
Juan Ferrer Toribio | 60a1d7cf9e | |
Juan Ferrer Toribio | 63158d335e | |
Juan Ferrer Toribio | 2e9ea7ac33 | |
Juan Ferrer Toribio | f6603ccc13 | |
Juan Ferrer Toribio | e178c74f25 | |
Juan Ferrer Toribio | 125686307f | |
Juan Ferrer Toribio | a8b6dd4c1a | |
Juan Ferrer Toribio | b8c83df08f | |
Juan Ferrer Toribio | fab5253406 | |
Juan Ferrer Toribio | c1175122d0 | |
Juan Ferrer Toribio | 942772ec03 | |
Juan Ferrer Toribio | 13e45afc77 | |
Juan Ferrer Toribio | d327be4780 | |
Juan Ferrer Toribio | ff0b899fa2 | |
Juan Ferrer Toribio | 06bd47bf91 | |
Juan Ferrer Toribio | 45f5cc3deb | |
Juan Ferrer Toribio | 25e9b37b75 | |
Juan Ferrer Toribio | b7bdef4bf9 | |
Juan Ferrer Toribio | 28b2302d78 | |
Juan Ferrer Toribio | 1e43aead46 | |
Juan Ferrer Toribio | adb651fe69 | |
Juan Ferrer Toribio | 234566b6af | |
Juan Ferrer Toribio | 73b2fed910 | |
Juan Ferrer Toribio | 22ee7e5020 | |
Juan Ferrer Toribio | 403845bf2b | |
Juan Ferrer Toribio | 0be33631ca | |
Juan Ferrer Toribio | 944a955068 | |
Juan Ferrer Toribio | efdde01e19 | |
Juan Ferrer Toribio | a5854290e9 | |
Juan Ferrer Toribio | 5640951c80 | |
Juan Ferrer Toribio | 86a19925d8 | |
Juan Ferrer Toribio | 043c11a4ed | |
Juan Ferrer Toribio | b7ec06ffdc | |
Juan Ferrer Toribio | 1a5bcf2bd2 | |
Juan Ferrer Toribio | 969f58981e | |
Juan Ferrer Toribio | 2c6c51865f | |
Juan Ferrer Toribio | 63d75a7ff6 |
|
@ -1,2 +0,0 @@
|
|||
debian
|
||||
node_modules
|
|
@ -8,8 +8,4 @@ rules:
|
|||
no-mixed-spaces-and-tabs: 0
|
||||
no-console: 0
|
||||
no-cond-assign: 0
|
||||
no-unexpected-multiline: 0
|
||||
brace-style: [error, 1tbs]
|
||||
space-before-function-paren: [error, never]
|
||||
padded-blocks: [error, never]
|
||||
func-call-spacing: [error, never]
|
||||
no-unexpected-multiline: 0
|
|
@ -2,4 +2,3 @@ node_modules
|
|||
build/
|
||||
config.my.php
|
||||
.vscode/
|
||||
.quasar
|
||||
|
|
52
Dockerfile
52
Dockerfile
|
@ -1,52 +0,0 @@
|
|||
# Not using buster because of bug: https://bugs.php.net/bug.php?id=78870
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
gnupg2
|
||||
|
||||
# Apache
|
||||
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
apache2 \
|
||||
libapache2-mod-php \
|
||||
&& . /etc/apache2/envvars \
|
||||
&& ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log" \
|
||||
&& ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log" \
|
||||
&& ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"
|
||||
|
||||
RUN a2dissite 000-default
|
||||
|
||||
# NodeJs
|
||||
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs
|
||||
|
||||
# Hedera
|
||||
|
||||
RUN curl -sL https://apt.verdnatura.es/conf/verdnatura.gpg | apt-key add - \
|
||||
&& echo "deb http://apt.verdnatura.es/ bookworm main" \
|
||||
> /etc/apt/sources.list.d/vn.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
php-apcu \
|
||||
php-image-text \
|
||||
php-text-captcha \
|
||||
php-zip \
|
||||
hedera-web \
|
||||
cron
|
||||
|
||||
ARG BUILD_ID=unknown
|
||||
ARG VERSION
|
||||
ENV VERSION $VERSION
|
||||
RUN echo $VERSION
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y php-vn-lib hedera-web=$VERSION \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
CMD ["apachectl", "-D", "FOREGROUND"]
|
|
@ -1,102 +0,0 @@
|
|||
#!/usr/bin/env groovy
|
||||
|
||||
def BRANCH_ENV = [
|
||||
test: 'test',
|
||||
master: 'production'
|
||||
]
|
||||
def remote = [:]
|
||||
|
||||
node {
|
||||
stage('Setup') {
|
||||
env.NODE_ENV = BRANCH_ENV[env.BRANCH_NAME] ?: 'dev'
|
||||
|
||||
echo "NODE_NAME: ${env.NODE_NAME}"
|
||||
echo "WORKSPACE: ${env.WORKSPACE}"
|
||||
}
|
||||
}
|
||||
pipeline {
|
||||
agent any
|
||||
environment {
|
||||
PROJECT_NAME = 'hedera-web'
|
||||
}
|
||||
stages {
|
||||
stage('Debuild') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch 'test'
|
||||
}
|
||||
}
|
||||
agent {
|
||||
docker {
|
||||
image 'registry.verdnatura.es/verdnatura/debuild:2.23.4-vn7'
|
||||
registryUrl 'https://registry.verdnatura.es/'
|
||||
registryCredentialsId 'docker-registry'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh 'debuild -us -uc -b'
|
||||
sh 'mkdir -p debuild'
|
||||
sh 'mv ../hedera-web_* debuild'
|
||||
|
||||
script {
|
||||
def files = findFiles(glob: 'debuild/*.changes')
|
||||
files.each { file -> env.CHANGES_FILE = file.name }
|
||||
}
|
||||
|
||||
configFileProvider([
|
||||
configFile(fileId: "dput.cf", variable: 'DPUT_CONFIG')
|
||||
]) {
|
||||
sshagent(credentials: ['jenkins-agent']) {
|
||||
sh 'dput --config "$DPUT_CONFIG" verdnatura "debuild/$CHANGES_FILE"'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Deploy') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch 'test'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
CREDS = credentials('docker-registry')
|
||||
IMAGE = "$REGISTRY/verdnatura/hedera-web"
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
def packageJson = readJSON file: 'package.json'
|
||||
env.VERSION = "${packageJson.version}"
|
||||
env.TAG = "${packageJson.version}-build${env.BUILD_ID}"
|
||||
}
|
||||
|
||||
sh 'docker-compose build --build-arg BUILD_ID=$BUILD_ID --parallel'
|
||||
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
|
||||
sh 'docker push $IMAGE:$TAG'
|
||||
|
||||
script {
|
||||
if (env.BRANCH_NAME == 'master') {
|
||||
sh 'docker tag $IMAGE:$TAG $IMAGE:latest'
|
||||
sh 'docker push $IMAGE:latest'
|
||||
}
|
||||
}
|
||||
|
||||
withKubeConfig([
|
||||
serverUrl: "$KUBERNETES_API",
|
||||
credentialsId: 'kubernetes',
|
||||
namespace: 'salix'
|
||||
]) {
|
||||
sh 'kubectl set image deployment/hedera-web-$BRANCH_NAME hedera-web-$BRANCH_NAME=$IMAGE:$TAG'
|
||||
sh 'kubectl set image deployment/hedera-web-cron-$BRANCH_NAME hedera-web-cron-$BRANCH_NAME=$IMAGE:$TAG'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
unsuccessful {
|
||||
setEnv()
|
||||
sendEmail()
|
||||
}
|
||||
}
|
||||
}
|
57
README.md
57
README.md
|
@ -1,58 +1,3 @@
|
|||
# Hedera
|
||||
|
||||
Hedera is the main web shop page for Verdnatura.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Required applications.
|
||||
* PHP >= 8.4
|
||||
* Node.js >= 20.0
|
||||
|
||||
Take a look to *debian/control* file to see additional dependencies.
|
||||
|
||||
Copy config.php to *config.my.php* and place your DB config there.
|
||||
|
||||
### Installing dependencies and launching
|
||||
|
||||
Pull from repository.
|
||||
|
||||
Run this commands on project root directory to install Node dependencies.
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Install project dependences (debian/control).
|
||||
|
||||
Pull from repository [php-vn-lib](https://gitea.verdnatura.es/verdnatura/php-vn-lib) and install [dependences](https://gitea.verdnatura.es/verdnatura/php-vn-lib/src/branch/master/debian/control) of this project.
|
||||
|
||||
Configure config.php file.
|
||||
|
||||
Launch project fronted.
|
||||
```
|
||||
$ npm run front
|
||||
```
|
||||
|
||||
Launch salix backend.
|
||||
```
|
||||
$ npm run db
|
||||
$ npm run back
|
||||
```
|
||||
Launch project backend.
|
||||
```
|
||||
$ php -S 127.0.0.1:3001 -t . index.php
|
||||
```
|
||||
|
||||
### Command line
|
||||
|
||||
Run server side method from command line.
|
||||
```
|
||||
$ php hedera-web.php -m method_path
|
||||
```
|
||||
|
||||
## Built with
|
||||
|
||||
* [nodejs](https://nodejs.org/)
|
||||
* [php](https://www.php.net/)
|
||||
* [Webpack](https://webpack.js.org/)
|
||||
* [MooTools](https://mootools.net/)
|
||||
* [TinyMCE](https://www.tinymce.com/)
|
||||
Hedera is the main page for Verdnatura.
|
||||
|
|
84
app.js
84
app.js
|
@ -1,41 +1,71 @@
|
|||
__webpack_public_path__ = _PUBLIC_PATH;
|
||||
|
||||
import 'promise-polyfill/src/polyfill';
|
||||
import 'hedera/hedera';
|
||||
const locales = require('./import').locales;
|
||||
const packageJson = require('./package.json');
|
||||
var assetsPath;
|
||||
|
||||
window.onload = function() {
|
||||
loadLocale(main);
|
||||
if (_DEV_MODE)
|
||||
{
|
||||
var host = window.location.host.split(':')[0];
|
||||
assetsPath = 'http://'+ host +':'+ _DEV_SERVER_PORT +'/'+ _PUBLIC_PATH;
|
||||
}
|
||||
else
|
||||
assetsPath = _PUBLIC_PATH;
|
||||
|
||||
__webpack_public_path__ = assetsPath;
|
||||
|
||||
require ('hedera/hedera');
|
||||
|
||||
window.onload = function ()
|
||||
{
|
||||
loadLocale (main);
|
||||
}
|
||||
|
||||
function main() {
|
||||
Vn.setVersion(packageJson.version);
|
||||
const hederaWeb = new Hedera.App();
|
||||
window.hederaWeb = hederaWeb;
|
||||
hederaWeb.run();
|
||||
function main (req)
|
||||
{
|
||||
if (req)
|
||||
onLocaleLoad (req);
|
||||
|
||||
hederaWeb = new Hedera.App ();
|
||||
hederaWeb.run ();
|
||||
}
|
||||
|
||||
function loadLocale(callback) {
|
||||
Vn.Locale.init();
|
||||
function loadLocale (cb)
|
||||
{
|
||||
Vn.Locale.init ();
|
||||
var lang = Vn.Locale.language;
|
||||
|
||||
var req = require.context('js', true, /locale\/en.yml$/);
|
||||
onLocaleLoad(Vn.Locale.fallbackLang, req);
|
||||
var req = require.context ('js', true, /locale\/en.yml$/);
|
||||
onLocaleLoad (req);
|
||||
|
||||
const loadFn = locales[lang];
|
||||
if (loadFn)
|
||||
loadFn(function(req) {
|
||||
onLocaleLoad(lang, req);
|
||||
callback();
|
||||
});
|
||||
else
|
||||
callback();
|
||||
switch (lang)
|
||||
{
|
||||
case 'ca':
|
||||
require ([], function () {
|
||||
cb (require.context ('js', true, /locale\/ca.yml$/)); });
|
||||
break;
|
||||
case 'es':
|
||||
require ([], function () {
|
||||
cb (require.context ('js', true, /locale\/es.yml$/)); });
|
||||
break;
|
||||
case 'fr':
|
||||
require ([], function () {
|
||||
cb (require.context ('js', true, /locale\/fr.yml$/)); });
|
||||
break;
|
||||
case 'mn':
|
||||
require ([], function () {
|
||||
cb (require.context ('js', true, /locale\/mn.yml$/)); });
|
||||
break;
|
||||
case 'pt':
|
||||
require ([], function () {
|
||||
cb (require.context ('js', true, /locale\/pt.yml$/)); });
|
||||
break;
|
||||
default:
|
||||
cb ();
|
||||
}
|
||||
}
|
||||
|
||||
function onLocaleLoad(lang, req) {
|
||||
var keys = req.keys();
|
||||
function onLocaleLoad (req)
|
||||
{
|
||||
var keys = req.keys ();
|
||||
|
||||
for (var i = 0; i < keys.length; i++)
|
||||
Vn.Locale.add(req(keys[i]), lang);
|
||||
Vn.Locale.add (req (keys[i]));
|
||||
}
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
# Alias /hedera-web /usr/share/hedera-web/
|
||||
# Alias /image-db /var/lib/hedera-web/image-db/
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
AddType text/x-yaml .yml
|
||||
</IfModule>
|
||||
|
||||
<IfModule mpm_prefork_module>
|
||||
ServerLimit 30
|
||||
MaxRequestWorkers 30
|
||||
MaxRequestsPerChild 1000
|
||||
</IfModule>
|
||||
|
||||
<VirtualHost *:80>
|
||||
DocumentRoot /usr/share/hedera-web/
|
||||
</VirtualHost>
|
||||
|
||||
<Directory /usr/share/hedera-web/>
|
||||
Options -Indexes -FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
|
||||
|
||||
<FilesMatch "\.(css|js|json|yml|php|xml|html|svg)$">
|
||||
SetOutputFilter DEFLATE
|
||||
|
@ -24,7 +16,13 @@
|
|||
|
||||
<FilesMatch "\.(ttf|otf|eot|woff)$">
|
||||
<IfModule mod_headers.c>
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
</Directory>
|
||||
|
||||
<Directory /var/lib/hedera-web/image-db/>
|
||||
Options Indexes FollowSymLinks MultiViews
|
||||
AllowOverride FileInfo Options
|
||||
Require all granted
|
||||
</Directory>
|
|
@ -12,19 +12,18 @@
|
|||
*
|
||||
* - http://www.mydomain.org -> config.www.php
|
||||
* - http://test.mydomain.org -> config.test.php
|
||||
*
|
||||
* Put the password in base64.
|
||||
*/
|
||||
return [
|
||||
/**
|
||||
* Database parameters.
|
||||
*/
|
||||
'db' => [
|
||||
'host' => 'localhost'
|
||||
,'port' => 3306
|
||||
,'schema' => 'hedera'
|
||||
,'user' => 'hedera-web'
|
||||
,'pass' => '' // base64 encoded
|
||||
,'tz' => 'Europe/madrid'
|
||||
]
|
||||
|
||||
/**
|
||||
* Database parameters.
|
||||
*/
|
||||
'db' => [
|
||||
'host' => 'localhost'
|
||||
,'port' => 3306
|
||||
,'schema' => 'hedera-web'
|
||||
,'user' => 'hedera-web'
|
||||
,'pass' => ''
|
||||
]
|
||||
|
||||
];
|
|
@ -1,4 +1,4 @@
|
|||
hedera-web (24.14.11) stable; urgency=low
|
||||
hedera-web (2.0.4) stable; urgency=low
|
||||
|
||||
* Initial Release.
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
10
|
||||
9
|
|
@ -5,12 +5,12 @@ Build-Depends: build-essential, debhelper, nodejs
|
|||
Standards-Version: 3.9.3
|
||||
Section: misc
|
||||
Homepage: https://verdnatura.es
|
||||
Vcs-Git: https://gitea.verdnatura.es/verdnatura/hedera-web
|
||||
Vcs-Git: https://git.verdnatura.es/hedera-web
|
||||
|
||||
Package: hedera-web
|
||||
Architecture: all
|
||||
Depends: apache2 | httpd, nodejs, php-cli, php-vn-lib, php-apcu, php-imap, php-soap, libphp-phpmailer, php-gd, php-pear
|
||||
Suggests: php-image-text, php-text-captcha, php-zip, cron
|
||||
Depends: apache2 | httpd, nodejs, php5-cli, php5-mysql, php5-mcrypt, php5-ldap, php5-ssh2, php5-apcu, php-vn-lib
|
||||
Suggests: php-text-captcha, php5-imap
|
||||
Section: misc
|
||||
Priority: optional
|
||||
Description: Verdnatura's web page
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Format: http://dep.debian.net/deps/dep5
|
||||
Name: hedera-web
|
||||
Source: https://gitea.verdnatura.es/verdnatura/hedera-web
|
||||
Source: git://www.verdnatura.es/var/git/hedera-web
|
||||
|
||||
Files: *
|
||||
Copyright: 2011-2015 Juan Ferrer Toribio <juan@verdnatura.es>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
MAILTO=webmaster
|
||||
*/1 * * * * root hedera-web.php -m misc/mail
|
||||
*/2 * * * * root hedera-web.php -m edi/load
|
||||
0 23 * * * root hedera-web.php -m edi/clean
|
||||
0 5 * * * root hedera-web.php -m edi/update
|
||||
0 0 * * * root hedera-web.php -m image/sync
|
||||
0 1 * * * root /usr/share/hedera-web/utils/image-clean.sh > /dev/null
|
||||
0 */1 * * * root /usr/share/hedera-web/utils/update-browscap.sh > /dev/null
|
||||
*/4 * * * * root hedera-web.php -m tpv/confirm-mail
|
||||
*/2 * * * * root hedera-web.php -m edi/load
|
||||
0 23 * * * root hedera-web.php -m edi/clean
|
||||
0 5 * * * root hedera-web.php -m edi/update
|
||||
*/1 * * * * root hedera-web.php -m misc/mail
|
||||
0 5 * * * root hedera-web.php -m misc/exchange-rate
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
apache.conf etc/hedera-web
|
||||
config.php etc/hedera-web
|
||||
php.ini etc/hedera-web
|
||||
conf/* etc/hedera-web
|
||||
web usr/share/php/vn
|
||||
doc/* usr/share/doc/hedera-web
|
||||
hedera-web.php usr/share/hedera-web
|
||||
|
@ -10,10 +8,9 @@ js usr/share/hedera-web
|
|||
pages usr/share/hedera-web
|
||||
reports usr/share/hedera-web
|
||||
rest usr/share/hedera-web
|
||||
utils usr/share/hedera-web
|
||||
index.php usr/share/hedera-web
|
||||
package.json usr/share/hedera-web
|
||||
build usr/share/hedera-web
|
||||
README.md usr/share/hedera-web
|
||||
webpack.config.json usr/share/hedera-web
|
||||
build-deps/node_modules usr/share/hedera-web
|
||||
build usr/share/hedera-web
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
usr/share/hedera-web/hedera-web.php usr/bin/hedera-web.php
|
||||
etc/hedera-web/apache.conf etc/apache2/conf-available/hedera-web.conf
|
||||
etc/hedera-web/php.ini etc/php/8.2/apache2/conf.d/99-hedera-web.ini
|
||||
usr/share/hedera-web/hedera-web.php usr/bin/hedera-web.php
|
|
@ -2,15 +2,14 @@
|
|||
|
||||
set -e
|
||||
|
||||
/usr/share/hedera-web/utils/update-browscap.sh > /dev/null
|
||||
(cd /usr/share/hedera-web && npm install --production)
|
||||
|
||||
if [ -e /usr/share/apache2/apache2-maintscript-helper ]
|
||||
then
|
||||
. /usr/share/apache2/apache2-maintscript-helper
|
||||
apache2_invoke enmod rewrite
|
||||
apache2_invoke enmod headers
|
||||
apache2_invoke enconf hedera-web.conf
|
||||
fi
|
||||
|
||||
#service php7.3-fpm restart
|
||||
#service cron restart
|
||||
service php5-fpm restart
|
||||
service cron restart
|
||||
|
||||
|
|
|
@ -8,5 +8,5 @@ then
|
|||
apache2_invoke disconf hedera-web.conf
|
||||
fi
|
||||
|
||||
#rm -rf /usr/share/hedera-web/node_modules
|
||||
#service cron restart
|
||||
rm -rf /usr/share/hedera-web/node_modules
|
||||
service cron restart
|
||||
|
|
|
@ -10,9 +10,5 @@ clean:
|
|||
dh_clean $@
|
||||
|
||||
build:
|
||||
npm install --no-audit --prefer-offline
|
||||
npm --omit=dev run build
|
||||
npm --production run build
|
||||
|
||||
mkdir -p build-deps
|
||||
cp package.json package-lock.json build-deps
|
||||
(cd build-deps && npm install --omit=dev --no-audit --prefer-offline)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
hedera-web: package-contains-npm-ignore-file
|
|
@ -1,9 +0,0 @@
|
|||
version: '3.7'
|
||||
services:
|
||||
main:
|
||||
image: registry.verdnatura.es/verdnatura/hedera-web:${TAG:?}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- VERSION=${VERSION:?}
|
3
env.php
3
env.php
|
@ -2,11 +2,12 @@
|
|||
|
||||
require_once __DIR__.'/../php-vn-lib/env.php';
|
||||
|
||||
set_include_path(__DIR__.PATH_SEPARATOR.get_include_path());
|
||||
set_include_path (__DIR__.PATH_SEPARATOR.get_include_path ());
|
||||
|
||||
$vnAutoloadMap['vn/web'] = __DIR__.'/web';
|
||||
|
||||
const _ENABLE_DEBUG = TRUE;
|
||||
const _DEV_MODE = TRUE;
|
||||
const _CONFIG_DIR = __DIR__.'/../../.config';
|
||||
const _LOG_DIR = '/tmp';
|
||||
const _DATA_DIR = '/tmp';
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
Hedera.AddressList = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,activate: function ()
|
||||
{
|
||||
this.$.userModel.setInfo ('c', 'myClient', 'hedera');
|
||||
this.$.addresses.setInfo ('a', 'myAddress', 'hedera');
|
||||
}
|
||||
|
||||
,onAddAddressClick: function ()
|
||||
{
|
||||
this.hash.setAll ({
|
||||
form: 'account/address',
|
||||
address: 0
|
||||
});
|
||||
}
|
||||
|
||||
,onReturnClick: function ()
|
||||
{
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
,onRemoveAddressClick: function (button)
|
||||
{
|
||||
if (confirm (_('AreYouSureDeleteAddress')))
|
||||
{
|
||||
button.lot.set ('isActive', false);
|
||||
button.lot.refresh ();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
|
||||
,activate() {
|
||||
this.$.userModel.setInfo('c', 'myClient', 'hedera');
|
||||
this.$.addresses.setInfo('a', 'myAddress', 'hedera');
|
||||
}
|
||||
|
||||
,onAddAddressClick() {
|
||||
this.hash.setAll({
|
||||
form: 'account/address',
|
||||
address: 0
|
||||
});
|
||||
}
|
||||
|
||||
,onReturnClick() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
,onSetDefaultClick(event, addressId) {
|
||||
if (event.defaultPrevented) return;
|
||||
this.$.defaultAddress.value = addressId;
|
||||
Htk.Toast.showMessage(_('DefaultAddressModified'));
|
||||
}
|
||||
|
||||
,async onRemoveAddressClick(form) {
|
||||
if (confirm(_('AreYouSureDeleteAddress'))) {
|
||||
await form.set('isActive', false);
|
||||
await form.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
,onEditAddressClick(address) {
|
||||
this.hash.setAll({
|
||||
form: 'account/address',
|
||||
address
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
Addresses: Adreces
|
||||
Addresses: Direccions
|
||||
Return: Tornar
|
||||
AddAddress: Afegir adreça
|
||||
SetAsDefault: Establir com per defecte
|
||||
RemoveAddress: Esborrar direcció
|
||||
EditAddress: Modificar direcció
|
||||
AreYouSureDeleteAddress: Estàs segur de que vols eliminar la direcció?
|
||||
DefaultAddressModified: Adreça per defecte modificada
|
||||
|
|
|
@ -5,4 +5,3 @@ SetAsDefault: Set as default
|
|||
RemoveAddress: Remove address
|
||||
EditAddress: Edit address
|
||||
AreYouSureDeleteAddress: Are you sure you want to delete the address?
|
||||
DefaultAddressModified: Default address modified
|
||||
|
|
|
@ -5,4 +5,3 @@ SetAsDefault: Establecer como predeterminada
|
|||
RemoveAddress: Borrar dirección
|
||||
EditAddress: Modificar dirección
|
||||
AreYouSureDeleteAddress: ¿Estás seguro de que quieres borrar la dirección?
|
||||
DefaultAddressModified: Dirección por defecto modificada
|
||||
|
|
|
@ -5,4 +5,3 @@ SetAsDefault: Définir par défaut
|
|||
RemoveAddress: Supprimer l'adresse
|
||||
EditAddress: Changement d'adresse
|
||||
AreYouSureDeleteAddress: Souhaitez-vous vraiment supprier l'adresse?
|
||||
DefaultAddressModified: Adresse par défaut modifiée
|
||||
|
|
|
@ -5,4 +5,3 @@ SetAsDefault: Selecionar como pre-determinado
|
|||
RemoveAddress: Eliminar Morada
|
||||
EditAddress: Modificar Morada
|
||||
AreYouSureDeleteAddress: Tens certeza que queres eliminar esta morada?
|
||||
DefaultAddressModified: Endereço padrão modificado
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
hedera-address-list {
|
||||
.htk-list .side {
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
|
@ -3,66 +3,62 @@
|
|||
<db-form id="user-form">
|
||||
<db-model property="model" id="user-model" updatable="true">
|
||||
SELECT id, defaultAddressFk
|
||||
FROM myClient c
|
||||
FROM myClient
|
||||
</db-model>
|
||||
</db-form>
|
||||
<db-model id="addresses" updatable="true">
|
||||
SELECT a.id, a.nickname, p.name province, a.postalCode,
|
||||
a.city, a.street, a.isActive
|
||||
a.city, a.street, a.isActive, c.country
|
||||
FROM myAddress a
|
||||
LEFT JOIN vn.province p ON p.id = a.provinceFk
|
||||
JOIN vn.country c ON c.id = p.countryFk
|
||||
WHERE a.isActive
|
||||
</db-model>
|
||||
</vn-group>
|
||||
<div id="title">
|
||||
<h1><t>Addresses</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_Addresses
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-bar-button
|
||||
icon="add"
|
||||
tip="_AddAddress"
|
||||
on-click="this.onAddAddressClick()"/>
|
||||
on-click="onAddAddressClick"/>
|
||||
</div>
|
||||
<div id="form" class="hedera-address-list">
|
||||
<div class="box vn-w-sm">
|
||||
<div id="main" class="address-list">
|
||||
<div class="card list">
|
||||
<htk-radio-group
|
||||
id="default-address"
|
||||
column="defaultAddressFk"
|
||||
form="user-form"/>
|
||||
<htk-repeater model="addresses" form-id="address" class="htk-list">
|
||||
lot="user-form"
|
||||
name="defaultAddressFk"/>
|
||||
<htk-repeater model="addresses">
|
||||
<custom>
|
||||
<div class="item clickable" on-click="this.onSetDefaultClick($event, address.id)">
|
||||
<div class="side">
|
||||
<a
|
||||
class="list-row"
|
||||
href="#!form=account/address&address={{id}}"
|
||||
title="_EditAddress">
|
||||
<div class="actions">
|
||||
<htk-radio
|
||||
lot="iter"
|
||||
name="id"
|
||||
radio-group="default-address"
|
||||
val="{{address.id}}"
|
||||
tip="_SetAsDefault"
|
||||
name="test"/>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="important">
|
||||
{{address.nickname}}
|
||||
</p>
|
||||
<p>
|
||||
{{address.street}}
|
||||
</p>
|
||||
<p>
|
||||
{{address.postalCode}}, {{address.city}}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="actions"
|
||||
on-click="$event.preventDefault()">
|
||||
tip="_SetAsDefault"/>
|
||||
<htk-button
|
||||
icon="delete"
|
||||
lot="iter"
|
||||
name="id"
|
||||
tip="_RemoveAddress"
|
||||
on-click="this.onRemoveAddressClick($iter)"/>
|
||||
<htk-button
|
||||
icon="edit"
|
||||
tip="_EditAddress"
|
||||
on-click="this.onEditAddressClick(address.id)"/>
|
||||
icon="delete"
|
||||
on-click="onRemoveAddressClick"/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="important">
|
||||
{{nickname}}
|
||||
</p>
|
||||
<p>
|
||||
{{street}}
|
||||
</p>
|
||||
<p>
|
||||
{{postalCode}}, {{city}}
|
||||
</p>
|
||||
</a>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
Hedera.Address = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,activate: function ()
|
||||
{
|
||||
this.$.model.setInfo ('a', 'myAddress', 'hedera', ['id'], 'id');
|
||||
this.$.model.setDefault ('clientFk', 'a',
|
||||
new Sql.Function ({schema: 'account', name: 'userGetId'}));
|
||||
}
|
||||
|
||||
,onStatusChange: function (form)
|
||||
{
|
||||
if (form.ready && this.$.params.$.address == 0)
|
||||
form.insertRow ();
|
||||
}
|
||||
|
||||
,onOperationsDone: function ()
|
||||
{
|
||||
Htk.Toast.showMessage (_('AddressChangedSuccessfully'));
|
||||
this.onReturnClick ();
|
||||
}
|
||||
|
||||
,onAcceptClick: function ()
|
||||
{
|
||||
this.$.iter.performOperations ();
|
||||
}
|
||||
|
||||
,onReturnClick: function ()
|
||||
{
|
||||
window.history.back();
|
||||
}
|
||||
});
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml'),
|
||||
|
||||
activate() {
|
||||
this.$.model.setInfo('a', 'myAddress', 'hedera', ['id'], 'id');
|
||||
this.$.model.setDefault('clientFk', 'a',
|
||||
new Sql.Function({schema: 'account', name: 'myUser_getId'}));
|
||||
},
|
||||
|
||||
onStatusChange() {
|
||||
if (this.$.iter.ready && this.hash.$.address == 0)
|
||||
this.$.iter.insertRow();
|
||||
},
|
||||
|
||||
onOperationsDone() {
|
||||
Htk.Toast.showMessage(_('AddressChangedSuccessfully'));
|
||||
window.history.back()
|
||||
}
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -3,81 +3,75 @@
|
|||
<vn-lot-query id="params">
|
||||
<vn-spec name="address" type="Number"/>
|
||||
</vn-lot-query>
|
||||
<db-form id="iter" on-status-changed="this.onStatusChange()">
|
||||
<db-form id="iter" on-status-changed="onStatusChange">
|
||||
<db-model
|
||||
id="model"
|
||||
property="model"
|
||||
updatable="true"
|
||||
mode="ON_DEMAND"
|
||||
lot="params"
|
||||
on-operations-done="this.onOperationsDone()">
|
||||
on-operations-done="onOperationsDone">
|
||||
SELECT a.id, a.street, a.nickname, a.city,
|
||||
a.postalCode, a.provinceFk, p.countryFk
|
||||
a.postalCode, a.provinceFk, c.id countryFk
|
||||
FROM myAddress a
|
||||
LEFT JOIN vn.province p ON p.id = a.provinceFk
|
||||
JOIN vn.country c ON c.id = p.countryFk
|
||||
WHERE a.id = #address
|
||||
</db-model>
|
||||
</db-form>
|
||||
</vn-group>
|
||||
<div id="title">
|
||||
<h1><t>Configuration</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_AddEditAddress
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-bar-button
|
||||
icon="ok"
|
||||
tip="_Accept"
|
||||
on-click="onAcceptClick"/>
|
||||
<htk-bar-button
|
||||
icon="close"
|
||||
tip="_Return"
|
||||
on-click="window.history.back()"/>
|
||||
<htk-bar-button
|
||||
icon="check"
|
||||
tip="_Accept"
|
||||
on-click="iter.performOperations()"/>
|
||||
on-click="onReturnClick"/>
|
||||
</div>
|
||||
<div id="form" class="hedera-address">
|
||||
<div class="form box vn-w-sm vn-pa-lg">
|
||||
<h5 class="vn-mb-md">
|
||||
<t>AddEditAddress</t>
|
||||
</h5>
|
||||
<div class="form-group">
|
||||
<htk-entry
|
||||
placeholder="_Name"
|
||||
form="iter" column="nickname"/>
|
||||
<div id="main" class="address">
|
||||
<div class="card form">
|
||||
<div>
|
||||
<label>_Name</label>
|
||||
<htk-entry lot="iter" name="nickname"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-entry
|
||||
placeholder="_Address"
|
||||
form="iter" column="street"/>
|
||||
<div>
|
||||
<label>_Address</label>
|
||||
<htk-entry lot="iter" name="street"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-entry
|
||||
placeholder="_City"
|
||||
form="iter" column="city"/>
|
||||
<div>
|
||||
<label>_City</label>
|
||||
<htk-entry lot="iter" name="city"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-entry
|
||||
placeholder="_ZipCode"
|
||||
form="iter" column="postalCode"/>
|
||||
<div>
|
||||
<label>_ZipCode</label>
|
||||
<htk-entry lot="iter" name="postalCode"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-combo
|
||||
placeholder="_Country"
|
||||
form="iter" column="countryFk"
|
||||
id="country"
|
||||
one-way="true"
|
||||
one-time="true">
|
||||
<div>
|
||||
<label>_Country</label>
|
||||
<htk-combo>
|
||||
<vn-param
|
||||
id="country"
|
||||
property="param"
|
||||
lot="iter"
|
||||
name="country"
|
||||
one-way="true"/>
|
||||
<db-model property="model">
|
||||
SELECT id, name FROM vn.country
|
||||
ORDER BY name
|
||||
SELECT id, country FROM vn.country
|
||||
ORDER BY country
|
||||
</db-model>
|
||||
</htk-combo>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-combo
|
||||
placeholder="_Province"
|
||||
column="provinceFk"
|
||||
form="iter">
|
||||
<db-model property="model" lot="country">
|
||||
<div>
|
||||
<label>_Province</label>
|
||||
<htk-combo lot="iter" name="province_id">
|
||||
<db-model property="model" lot="iter">
|
||||
SELECT id, name FROM vn.province
|
||||
WHERE countryFk = #id
|
||||
WHERE countryFk = #country
|
||||
ORDER BY name
|
||||
</db-model>
|
||||
</htk-combo>
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
|
||||
Hedera.Conf = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,activate: function ()
|
||||
{
|
||||
this.$.userModel.setInfo ('c', 'myClient', 'hedera');
|
||||
|
||||
if (this.hash.$.changePass)
|
||||
this.onPassChangeClick ();
|
||||
}
|
||||
|
||||
,onChangePassOpen: function ()
|
||||
{
|
||||
this.hash.assign ({changePass: true});
|
||||
}
|
||||
|
||||
,onChangePassClose: function ()
|
||||
{
|
||||
this.hash.assign ({changePass: undefined});
|
||||
}
|
||||
|
||||
,onPassChangeClick: function ()
|
||||
{
|
||||
this.$.oldPassword.value = '';
|
||||
this.$.newPassword.value = '';
|
||||
this.$.repeatPassword.value = '';
|
||||
|
||||
var recoverPass = this.$.user.get ('recoverPass');
|
||||
this.$.oldPassword.style.display = recoverPass ? 'none' : 'block';
|
||||
this.$.changePassword.show ();
|
||||
|
||||
var focusInput;
|
||||
|
||||
if (recoverPass)
|
||||
focusInput = this.$.newPassword;
|
||||
else
|
||||
focusInput = this.$.oldPassword;
|
||||
|
||||
focusInput.focus ();
|
||||
focusInput.select ();
|
||||
}
|
||||
|
||||
,onPassModifyClick: function ()
|
||||
{
|
||||
try {
|
||||
var oldPassword = this.$.oldPassword.value;
|
||||
var newPassword = this.$.newPassword.value;
|
||||
var repeatedPassword = this.$.repeatPassword.value;
|
||||
|
||||
if (newPassword == '' && repeatedPassword == '')
|
||||
throw new Error (_('Passwords empty'));
|
||||
if (newPassword !== repeatedPassword)
|
||||
throw new Error (_('Passwords doesn\'t match'));
|
||||
|
||||
var params = {
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPassword
|
||||
};
|
||||
this.conn.send ('core/change-password', params,
|
||||
this._onPassChange.bind (this));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
Htk.Toast.showError (e.message);
|
||||
}
|
||||
}
|
||||
|
||||
,_onPassChange: function (json, error)
|
||||
{
|
||||
if (json)
|
||||
{
|
||||
this.$.changePassword.hide ();
|
||||
Htk.Toast.showMessage (_('Password changed!'));
|
||||
this.$.user.refresh ();
|
||||
}
|
||||
else
|
||||
{
|
||||
Htk.Toast.showError (error.message);
|
||||
this.$.oldPassword.select ();
|
||||
}
|
||||
}
|
||||
|
||||
,onPassInfoClick: function ()
|
||||
{
|
||||
this.$.passwordInfo.show ();
|
||||
}
|
||||
|
||||
,onAddressesClick: function ()
|
||||
{
|
||||
this.hash.setAll ({form: 'account/address-list'});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml'),
|
||||
|
||||
activate() {
|
||||
this.$.userModel.setInfo('c', 'myClient', 'hedera');
|
||||
this.$.userModel.setInfo('u', 'myUser', 'account');
|
||||
this.$.changePassword.conn = this.conn
|
||||
this.$.changePassword.user = this.gui.user
|
||||
}
|
||||
});
|
|
@ -1,5 +1,4 @@
|
|||
Configuration: Configuració
|
||||
Personal information: Dades personals
|
||||
Username: Nom d'usuari
|
||||
Password: Contrasenya
|
||||
Email: Correu electrònic
|
||||
|
@ -10,7 +9,7 @@ Receive invoices by email: Rebre factures per correu electrònic
|
|||
Old password: Contrasenya antiga
|
||||
New password: Nova contrasenya
|
||||
Repeat password: Repetir contrasenya
|
||||
Requirements: Requisits
|
||||
Info: Info
|
||||
Modify: Modificar
|
||||
Password requirements: Requisits de contrasenya
|
||||
characters long: caràcters de longitud
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
Configuration: Configuration
|
||||
Personal information: Personal information
|
||||
Username: Username
|
||||
Password: Password
|
||||
Email: Email
|
||||
|
@ -10,7 +9,7 @@ Receive invoices by email: Receive invoices by email
|
|||
Old password: Old password
|
||||
New password: New password
|
||||
Repeat password: Repeat password
|
||||
Requirements: Requirements
|
||||
Info: Info
|
||||
Modify: Modify
|
||||
Password requirements: Password requirements
|
||||
characters long: characters long
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
Configuration: Configuración
|
||||
Personal information: Datos personales
|
||||
Username: Nombre de usuario
|
||||
Password: Contraseña
|
||||
Email: Correo electrónico
|
||||
|
@ -10,7 +9,7 @@ Receive invoices by email: Recibir facturas por correo electrónico
|
|||
Old password: Contaseña antigua
|
||||
New password: Nueva contraseña
|
||||
Repeat password: Repetir contraseña
|
||||
Requirements: Requisitos
|
||||
Info: Info
|
||||
Modify: Modificar
|
||||
Password requirements: Requisitos de constraseña
|
||||
characters long: carácteres de longitud
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
Configuration: Configuration
|
||||
Personal information: Informations personnelles
|
||||
Username: Utilisateur
|
||||
Password: Mot de passe
|
||||
Email: Courriel
|
||||
|
@ -10,7 +9,7 @@ Receive invoices by email: Recevoir des factures par e-mail
|
|||
Old password: Ancien mot de passe
|
||||
New password: Nouveau mot de passe
|
||||
Repeat password: Répéter le mot de passe
|
||||
Requirements: Exigences
|
||||
Info: Info
|
||||
Modify: Modifier
|
||||
Password requirements: Mot de passe exigences
|
||||
characters long: Longs caractères
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
Configuration: Configuração
|
||||
Personal information: Dados pessoais
|
||||
Username: Nome de usuario
|
||||
Password: Palavra-passe
|
||||
Password: Palavra-Passe
|
||||
Email: E-Mail
|
||||
Display name: Nome para mostrar
|
||||
Language: Idioma
|
||||
Billing: Facturação
|
||||
Receive invoices by email: Receber facturas por e-mail
|
||||
Old password: Palavra-passe antiga
|
||||
New password: Nova Palavra-passe
|
||||
Repeat password: Repetir Palavra-passe
|
||||
Requirements: Requisitos
|
||||
Old password: Palavra-Passe antiga
|
||||
New password: Nova Palavra-Passe
|
||||
Repeat password: Repetir Palavra-Passe
|
||||
Info: Info
|
||||
Modify: Modificar
|
||||
Password requirements: Requisitos de Palavra-passe
|
||||
Password requirements: Requisitos de Palavra-Passe
|
||||
characters long: caracteres
|
||||
alphabetic characters: caracteres alfabéticos
|
||||
capital letters: letras maiúsculas
|
||||
digits: dígitos
|
||||
symbols: 'símbolos. Ej: $%&.'
|
||||
Password changed!: Palavra-passe Modificada!
|
||||
Password doesn't meet the requirements: Palavra-passe não atende aos requisitos
|
||||
Password changed!: Palavra-Passe Modificada!
|
||||
Password doesn't meet the requirements: Palavra-Passe não atende aos requisitos
|
||||
Passwords doesn't match: As Palavras-Passe não coincidem!
|
||||
Passwords empty: Palavra-passe vazia
|
||||
Passwords empty: Palavra-Passe vazia
|
||||
Addresses: Moradas
|
||||
Change password: Mudar Palavra-passe
|
||||
Change password: Mudar Palavra-Passe
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
.conf .form-group input[type=password]
|
||||
{
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.pass-change
|
||||
{
|
||||
max-width: 15em;
|
||||
}
|
||||
|
||||
.pass-info
|
||||
{
|
||||
max-width: 17em;
|
||||
}
|
||||
.pass-info ul
|
||||
{
|
||||
list-style-type: none;
|
||||
padding-left: 1.5em;
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
.pass-info ul {
|
||||
list-style-type: none;
|
||||
}
|
|
@ -1,72 +1,118 @@
|
|||
<vn>
|
||||
<vn-group>
|
||||
<db-form id="user-form">
|
||||
<db-lot id="password-form">
|
||||
SELECT length, nAlpha, nUpper, nDigits, nPunct
|
||||
FROM account.userPassword
|
||||
</db-lot>
|
||||
<db-form id="user">
|
||||
<db-model property="model" id="user-model" updatable="true">
|
||||
SELECT u.id, u.name, u.email, u.nickname,
|
||||
u.lang, c.isToBeMailed, c.id clientFk
|
||||
FROM account.myUser u
|
||||
SELECT u.id, u.name, u.email, u.recoverPass,
|
||||
u.nickname, u.lang, c.isToBeMailed, c.id clientFk
|
||||
FROM account.userView u
|
||||
LEFT JOIN myClient c
|
||||
ON u.id = c.id
|
||||
</db-model>
|
||||
</db-form>
|
||||
</vn-group>
|
||||
<div id="title">
|
||||
<h1><t>Configuration</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_Configuration
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-bar-button
|
||||
icon="place"
|
||||
tip="_Addresses"
|
||||
on-click="hash.setAll({form: 'account/address-list'})"/>
|
||||
on-click="onAddressesClick"/>
|
||||
<htk-bar-button
|
||||
icon="lock_reset"
|
||||
icon="preferences"
|
||||
tip="_Change password"
|
||||
on-click="this.$.changePassword.open()"/>
|
||||
on-click="onPassChangeClick"/>
|
||||
</div>
|
||||
<div id="form" class="conf">
|
||||
<div class="form box vn-w-sm vn-pa-lg">
|
||||
<h5 class="vn-mb-md">
|
||||
<t>Personal information</t>
|
||||
</h5>
|
||||
<div class="form-group">
|
||||
<htk-entry
|
||||
placeholder="_Username"
|
||||
disabled="true"
|
||||
form="user-form"
|
||||
column="name"/>
|
||||
<div id="main" class="conf">
|
||||
<div class="card form">
|
||||
<div>
|
||||
<label for="user-name">_Username</label>
|
||||
<htk-text lot="user" name="name"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-entry
|
||||
placeholder="_Email"
|
||||
form="user-form"
|
||||
column="email">
|
||||
</htk-entry>
|
||||
<div>
|
||||
<label for="email">_Email</label>
|
||||
<htk-entry lot="user" name="email"></htk-entry>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-entry
|
||||
placeholder="_Display name"
|
||||
form="user-form"
|
||||
column="nickname"/>
|
||||
<div>
|
||||
<label for="nickname">_Display name</label>
|
||||
<htk-entry lot="user" name="nickname"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<htk-combo
|
||||
placeholder="_Language"
|
||||
form="user-form"
|
||||
column="lang">
|
||||
<div>
|
||||
<label for="lang">_Language</label>
|
||||
<htk-combo lot="user" name="lang"
|
||||
value-field="code">
|
||||
<db-model property="model">
|
||||
<custom>
|
||||
SELECT code, name FROM language WHERE isActive
|
||||
</custom>
|
||||
SELECT code, name FROM language WHERE active
|
||||
</db-model>
|
||||
</htk-combo>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<htk-check form="user-form" column="isToBeMailed"/>
|
||||
<t>Receive invoices by email</t>
|
||||
</label>
|
||||
<div>
|
||||
<span>
|
||||
<htk-check id="mail" lot="user" name="isToBeMailed"/>
|
||||
<label for="mail">_Receive invoices by email</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<htk-change-password id="change-password"/>
|
||||
<htk-popup
|
||||
id="change-password"
|
||||
on-open="onChangePassOpen"
|
||||
on-closed="onChangePassClose"
|
||||
modal="true">
|
||||
<div property="child-node" class="htk-dialog pass-change">
|
||||
<div>
|
||||
<input
|
||||
id="old-password"
|
||||
type="password"
|
||||
placeholder="_Old password"/>
|
||||
<input
|
||||
id="new-password"
|
||||
type="password"
|
||||
placeholder="_New password"/>
|
||||
<input
|
||||
id="repeat-password"
|
||||
type="password"
|
||||
placeholder="_Repeat password"/>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<button class="thin" on-click="onPassModifyClick">
|
||||
_Modify
|
||||
</button>
|
||||
<button class="thin" on-click="onPassInfoClick">
|
||||
_Info
|
||||
</button>
|
||||
<div class="clear"/>
|
||||
</div>
|
||||
</div>
|
||||
</htk-popup>
|
||||
<htk-popup
|
||||
id="password-info"
|
||||
modal="true">
|
||||
<div property="child-node" class="htk-dialog pass-info">
|
||||
<h3>
|
||||
_Password requirements
|
||||
</h3>
|
||||
<ul>
|
||||
<li>
|
||||
{{passwordForm.length}} <span>_characters long</span>
|
||||
</li>
|
||||
<li>
|
||||
{{passwordForm.nAlpha}} <span>_alphabetic characters</span>
|
||||
</li>
|
||||
<li>
|
||||
{{passwordForm.nUpper}} <span>_capital letters</span>
|
||||
</li>
|
||||
<li>
|
||||
{{passwordForm.nDigits}} <span>_digits</span>
|
||||
</li>
|
||||
<li>
|
||||
{{passwordForm.nPunct}} <span>_symbols</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</htk-popup>
|
||||
</vn>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Hedera.AccessLog = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
AccessLog: Registro de acessos
|
||||
'UserNumber:': 'Nº utilizador:'
|
||||
'User:': 'Utilizador:'
|
||||
'UserNumber:': 'Nº usuario:'
|
||||
'User:': 'Usuario:'
|
||||
'Phone:': 'Telefone:'
|
||||
'Mobile:': 'Telemóvel:'
|
||||
Access: Acceso
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
.access-log .card
|
||||
{
|
||||
max-width: 28em;
|
||||
}
|
||||
|
||||
.access-log .form > p
|
||||
{
|
||||
font-size: 1.2em;
|
||||
margin: .1em 0;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
.access-log .form > p {
|
||||
font-size: 1.2rem;
|
||||
margin: .1em 0;
|
||||
}
|
||||
|
||||
/* List */
|
||||
|
||||
.access-log .htk-list {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
|
@ -1,50 +1,54 @@
|
|||
<vn>
|
||||
<vn-group>
|
||||
<db-form v-model="user">
|
||||
<db-form id="client">
|
||||
<db-model property="model" lot="hash">
|
||||
SELECT u.id, u.name user, u.nickname, u.email, c.phone, r.name role
|
||||
FROM account.user u
|
||||
JOIN account.role r ON r.id = u.role
|
||||
LEFT JOIN vn.client c ON c.id = u.id
|
||||
WHERE u.id = #user
|
||||
SELECT Id_Cliente, Cliente, Telefono, movil
|
||||
FROM vn2008.Clientes WHERE Id_Cliente = #user
|
||||
</db-model>
|
||||
</db-form>
|
||||
</vn-group>
|
||||
<div id="title">
|
||||
<h1><t>AccessLog</t></h1>
|
||||
</div>
|
||||
<div id="form" class="access-log">
|
||||
<div class="box vn-w-xs vn-pa-lg">
|
||||
<div class="form">
|
||||
<h4>{{user.nickname}}</h4>
|
||||
<p>#{{user.id}} - {{user.user}}</p>
|
||||
<p>{{user.role}}</p>
|
||||
<p>{{user.email}}</p>
|
||||
<p>{{user.phone}}</p>
|
||||
<h1 id="title">
|
||||
_AccessLog
|
||||
</h1>
|
||||
<div id="main" class="access-log">
|
||||
<div class="card form">
|
||||
<p>
|
||||
{{client.Id_Cliente}}
|
||||
</p>
|
||||
<p>
|
||||
{{client.Cliente}}
|
||||
</p>
|
||||
<p>
|
||||
{{client.Telefono}}
|
||||
</p>
|
||||
<p>
|
||||
{{client.movil}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div>
|
||||
<htk-repeater>
|
||||
<db-model property="model" lot="hash">
|
||||
SELECT u.stamp, a.platform, a.browser, a.version, a.javascript, a.cookies
|
||||
FROM visitUser u
|
||||
JOIN visitAccess c ON c.id = u.access
|
||||
JOIN visitAgent a ON a.id = c.agent
|
||||
WHERE u.user = #user
|
||||
ORDER BY u.stamp DESC
|
||||
LIMIT 8
|
||||
</db-model>
|
||||
<custom>
|
||||
<div class="list-row">
|
||||
<p>
|
||||
<htk-text lot="iter" name="stamp" format="_%a, %e %b %Y at %T"/>
|
||||
</p>
|
||||
<p>
|
||||
{{platform}} - {{browser}} {{version}}
|
||||
</p>
|
||||
</div>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</div>
|
||||
</div>
|
||||
<htk-repeater form-id="iter" class="box vn-w-xs htk-list vn-mt-md">
|
||||
<db-model property="model" lot="hash">
|
||||
SELECT u.stamp, a.platform, a.browser, a.version, a.javascript, a.cookies
|
||||
FROM visitUser u
|
||||
JOIN visitAccess c ON c.id = u.accessFk
|
||||
JOIN visitAgent a ON a.id = c.agentFk
|
||||
WHERE u.userFk = #user
|
||||
ORDER BY u.stamp DESC
|
||||
LIMIT 8
|
||||
</db-model>
|
||||
<custom>
|
||||
<div class="item">
|
||||
<div class="content">
|
||||
<p>
|
||||
{{Vn.Value.format(iter.stamp, _('%a, %e %b %Y at %T'))}}
|
||||
</p>
|
||||
<p>
|
||||
{{iter.platform}} - {{iter.browser}} {{iter.version}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</div>
|
||||
</vn>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
Hedera.Connections = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,_timeoutId: null
|
||||
|
||||
,onModelStatusChange: function (model)
|
||||
{
|
||||
if (!model.ready)
|
||||
return;
|
||||
|
||||
if (this._timeoutId)
|
||||
clearTimeout (this._timeoutId);
|
||||
|
||||
this._timeoutId = setTimeout (this.onRefreshClick.bind (this), 60000);
|
||||
}
|
||||
|
||||
,deactivate: function ()
|
||||
{
|
||||
clearTimeout (this._timeoutId);
|
||||
}
|
||||
|
||||
,onRefreshClick: function ()
|
||||
{
|
||||
this.$.sessions.refresh ();
|
||||
}
|
||||
|
||||
,onChangeUserClick: function (button, form)
|
||||
{
|
||||
this.gui.supplantUser (form.get ('user'),
|
||||
this._onUserSupplant.bind (this));
|
||||
}
|
||||
|
||||
,_onUserSupplant: function ()
|
||||
{
|
||||
this.hash.setAll ({form: 'ecomerce/orders'});
|
||||
}
|
||||
|
||||
,sessionsFunc: function ()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
|
||||
,_timeoutId: null
|
||||
|
||||
,onModelStatusChange() {
|
||||
if (!this.$.sessions.ready)
|
||||
return;
|
||||
|
||||
if (this._timeoutId)
|
||||
clearTimeout(this._timeoutId);
|
||||
|
||||
this._timeoutId = setTimeout(
|
||||
() => this.$.sessions.refresh(), 60000);
|
||||
}
|
||||
|
||||
,deactivate() {
|
||||
clearTimeout(this._timeoutId);
|
||||
}
|
||||
|
||||
,async onChangeUserClick(userName) {
|
||||
await this.gui.supplantUser(userName);
|
||||
this.hash.setAll({form: 'ecomerce/orders'});
|
||||
}
|
||||
|
||||
,sessionsFunc() {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
.action-bar .connections-sum
|
||||
{
|
||||
padding: .4em;
|
||||
margin-top: .9em;
|
||||
margin-right: .5em;
|
||||
background-color: #1e88e5;
|
||||
border-radius: 0.1em;
|
||||
box-shadow: 0 0 0.4em #666;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
.action-bar .connections-sum {
|
||||
padding: .4em;
|
||||
background-color: #1e88e5;
|
||||
border-radius: .1em;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
<vn>
|
||||
<div id="title">
|
||||
<h1><t>Connections</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_Connections
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-bar-button
|
||||
icon="refresh"
|
||||
tip="_Refresh"
|
||||
on-click="sessions.refresh()"/>
|
||||
on-click="onRefreshClick"/>
|
||||
<div class="connections-sum">
|
||||
<htk-text>
|
||||
<db-calc-sum
|
||||
|
@ -14,50 +14,48 @@
|
|||
model="sessions"
|
||||
func="sessionsFunc"/>
|
||||
</htk-text>
|
||||
<t>connections</t>
|
||||
_connections
|
||||
</div>
|
||||
</div>
|
||||
<div id="form" class="connections">
|
||||
<htk-repeater form-id="iter" class="box htk-list vn-w-xs">
|
||||
<db-model
|
||||
property="model"
|
||||
id="sessions"
|
||||
on-status-changed="this.onModelStatusChange()">
|
||||
SELECT vu.userFk userId, vu.stamp, u.nickname, s.lastUpdate,
|
||||
a.platform, a.browser, a.version, u.name user
|
||||
FROM userSession s
|
||||
JOIN visitUser vu ON vu.id = s.userVisitFk
|
||||
JOIN visitAccess ac ON ac.id = vu.accessFk
|
||||
JOIN visitAgent a ON a.id = ac.agentFk
|
||||
JOIN visit v ON v.id = a.visitFk
|
||||
JOIN account.user u ON u.id = vu.userFk
|
||||
ORDER BY lastUpdate DESC
|
||||
</db-model>
|
||||
<custom>
|
||||
<a class="item"
|
||||
href="{{`#!form=admin/access-log&user=${iter.userId}`}}"
|
||||
title="_Access log">
|
||||
<div class="content">
|
||||
<div id="main" class="connections">
|
||||
<div class="card list">
|
||||
<htk-repeater>
|
||||
<db-model property="model" id="sessions" on-status-changed="onModelStatusChange">
|
||||
SELECT vu.user userId, vu.stamp, u.nickname, s.lastUpdate,
|
||||
a.platform, a.browser, a.version, u.name user
|
||||
FROM userSession s
|
||||
JOIN visitUser vu ON vu.id = s.userVisit
|
||||
JOIN visitAccess ac ON ac.id = vu.access
|
||||
JOIN visitAgent a ON a.id = ac.agent
|
||||
JOIN visit v ON v.id = a.visit
|
||||
JOIN account.user u ON u.id = vu.user
|
||||
ORDER BY lastUpdate DESC
|
||||
</db-model>
|
||||
<custom>
|
||||
<a href="#!form=admin/access-log&user={{userId}}"
|
||||
class="list-row"
|
||||
title="_Access log">
|
||||
<div class="actions">
|
||||
<htk-button
|
||||
lot="iter"
|
||||
name="id"
|
||||
tip="_Supplant user"
|
||||
icon="incognito"
|
||||
on-click="onChangeUserClick"/>
|
||||
</div>
|
||||
<p class="important">
|
||||
{{iter.nickname}}
|
||||
{{nickname}}
|
||||
</p>
|
||||
<p>
|
||||
{{Vn.Value.format(iter.stamp, '%a, %T')}} -
|
||||
{{Vn.Value.format(iter.lastUpdate, '%T')}}
|
||||
<htk-text lot="iter" name="stamp" format="%a, %T"/> -
|
||||
<htk-text lot="iter" name="lastUpdate" format="%T"/>
|
||||
</p>
|
||||
<p>
|
||||
{{iter.platform}} - {{iter.browser}} {{iter.version}}
|
||||
{{platform}} - {{browser}} {{version}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="actions"
|
||||
on-click="$event.preventDefault()">
|
||||
<htk-button
|
||||
tip="_Supplant user"
|
||||
icon="supervisor_account"
|
||||
on-click="this.onChangeUserClick(iter.user)"/>
|
||||
</div>
|
||||
</a>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</a>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</div>
|
||||
</div>
|
||||
</vn>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml'),
|
||||
|
||||
activate() {
|
||||
this.$.items.setInfo('i', 'item', 'vn', ['id']);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Hedera.Items = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
/* Row */
|
||||
|
||||
.items .list-row > .photo
|
||||
{
|
||||
margin-right: 1em;
|
||||
float: left;
|
||||
border-radius: 50%;
|
||||
height: 3.2em;
|
||||
width: 3.2em;
|
||||
}
|
||||
.items .list-row > p
|
||||
{
|
||||
margin: .1em 0;
|
||||
margin-left: 5em;
|
||||
}
|
||||
|
||||
/* Topbar */
|
||||
|
||||
.action-bar .htk-search-entry
|
||||
{
|
||||
margin: .8em .6em;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
.items .item .photo {
|
||||
border-radius: 10px;
|
||||
height: 80px;
|
||||
width: 80px;
|
||||
}
|
|
@ -1,57 +1,44 @@
|
|||
<vn>
|
||||
<div id="title">
|
||||
<h1><t>Items</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_Items
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-search-entry form="hash" column="search"/>
|
||||
<htk-search-entry lot="hash" name="search"/>
|
||||
</div>
|
||||
<div id="form" class="items">
|
||||
<htk-repeater
|
||||
class="htk-list rows box vn-w-xs"
|
||||
form-id="iter"
|
||||
empty-message="_Enter a search term">
|
||||
<db-model property="model" id="items" lot="hash">
|
||||
SELECT i.id, i.longName, i.size, i.category,
|
||||
i.value5, i.value6, i.value7,
|
||||
i.image, im.updated
|
||||
FROM vn.item i
|
||||
LEFT JOIN image im
|
||||
ON im.collectionFk = 'catalog'
|
||||
AND im.name = i.image
|
||||
WHERE i.longName LIKE CONCAT('%', #search, '%')
|
||||
OR i.id = #search
|
||||
ORDER BY i.longName LIMIT 50
|
||||
</db-model>
|
||||
<custom>
|
||||
<div class="item">
|
||||
<div class="side vn-mr-md">
|
||||
<div id="main" class="items">
|
||||
<div class="card list">
|
||||
<htk-repeater empty-message="_Enter a search term">
|
||||
<db-model property="model" lot="hash">
|
||||
SELECT Id_Article, Article, Medida, Categoria, Foto
|
||||
FROM vn2008.Articles
|
||||
WHERE Article LIKE CONCAT('%', #search, '%')
|
||||
OR Id_Article = #search
|
||||
ORDER BY Article LIMIT 50
|
||||
</db-model>
|
||||
<custom>
|
||||
<div class="list-row">
|
||||
<htk-image
|
||||
form="$iter"
|
||||
column="image"
|
||||
stamp-column="updated"
|
||||
lot="iter"
|
||||
name="Foto"
|
||||
class="photo"
|
||||
directory="catalog"
|
||||
subdir="200x200"
|
||||
full-dir="1600x900"
|
||||
full-dir="900x900"
|
||||
editable="true"
|
||||
conn="conn"/>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p class="important">
|
||||
{{iter.longName}}
|
||||
</p>
|
||||
<p class="tags">
|
||||
{{iter.value5}} {{iter.value6}} {{iter.value7}}
|
||||
<p class="concept">
|
||||
{{Article}} {{Medida}} {{Categoria}}
|
||||
</p>
|
||||
<p>
|
||||
{{iter.id}}
|
||||
@{{Id_Article}}
|
||||
</p>
|
||||
<p>
|
||||
{{iter.image}}
|
||||
{{Foto}}
|
||||
</p>
|
||||
<div class="clear"/>
|
||||
</div>
|
||||
</div>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</div>
|
||||
</div>
|
||||
</vn>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
});
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
Hedera.Links = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
.cpanel .list-row:hover
|
||||
{
|
||||
background-color: rgba(1, 1, 1, 0.05);
|
||||
}
|
||||
.cpanel .list-row > .htk-image
|
||||
{
|
||||
margin: 0;
|
||||
margin-right: 1em;
|
||||
float: left;
|
||||
height: 2.8em;
|
||||
width: 2.8em;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
|
||||
.cpanel .items > div {
|
||||
max-width: 900px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.cpanel .item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
width: 140px;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
transition: background-color 250ms ease-out;
|
||||
}
|
||||
.cpanel .item:hover {
|
||||
background-color: rgba(1, 1, 1, 0.05);
|
||||
}
|
||||
.cpanel .item > .htk-image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
float: left;
|
||||
height: 80px;
|
||||
}
|
||||
.cpanel .item > .htk-image > img {
|
||||
max-height: 60px;
|
||||
max-width: 60px;
|
||||
padding: 0;
|
||||
}
|
||||
.cpanel .item > h6 {
|
||||
flex: none;
|
||||
margin: .1em 0;
|
||||
font-size: .9rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cpanel .item > .text-secondary {
|
||||
flex: none;
|
||||
margin: 0;
|
||||
font-size: .8rem;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
}
|
|
@ -1,31 +1,29 @@
|
|||
<vn>
|
||||
<div id="title">
|
||||
<h1><t>ControlPanel</t></h1>
|
||||
</div>
|
||||
<div id="form" class="cpanel">
|
||||
<htk-repeater form-id="iter" class="items">
|
||||
<db-model property="model">
|
||||
<custom>
|
||||
<h1 id="title">
|
||||
_ControlPanel
|
||||
</h1>
|
||||
<div id="main" class="cpanel">
|
||||
<div class="card list">
|
||||
<htk-repeater>
|
||||
<db-model property="model">
|
||||
SELECT image, name, description, link FROM link
|
||||
ORDER BY name
|
||||
</db-model>
|
||||
<custom>
|
||||
<a class="list-row" href="{{link}}" target="_blank">
|
||||
<htk-image
|
||||
value="{{image}}"
|
||||
directory="link"
|
||||
subdir="full"/>
|
||||
<p class="important">
|
||||
{{name}}
|
||||
</p>
|
||||
<p>
|
||||
{{description}}
|
||||
</p>
|
||||
</a>
|
||||
</custom>
|
||||
</db-model>
|
||||
<custom>
|
||||
<a class="item box"
|
||||
href="{{iter.link}}"
|
||||
target="_blank">
|
||||
<htk-image
|
||||
value="{{iter.image}}"
|
||||
directory="link"
|
||||
subdir="full"/>
|
||||
<h6>
|
||||
{{iter.name}}
|
||||
</h6>
|
||||
<p class="text-secondary">
|
||||
{{iter.description}}
|
||||
</p>
|
||||
</a>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</htk-repeater>
|
||||
</div>
|
||||
</div>
|
||||
</vn>
|
||||
|
|
|
@ -1,201 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
var Status = {
|
||||
NONE : 0
|
||||
,WAITING : 1
|
||||
,UPLOADING : 2
|
||||
,UPLOADED : 3
|
||||
};
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
|
||||
,filesData: []
|
||||
,uploadCount: 0
|
||||
,isUploading: false
|
||||
|
||||
,activate() {
|
||||
this.$.schema.value = 'catalog';
|
||||
}
|
||||
|
||||
,addFiles(files) {
|
||||
if (!files)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < files.length; i++)
|
||||
this.addFile(files[i]);
|
||||
}
|
||||
|
||||
,addFile(file) {
|
||||
var doc = document;
|
||||
var li = doc.createElement('div');
|
||||
|
||||
var div = doc.createElement('div');
|
||||
div.className = 'thumb';
|
||||
li.appendChild(div);
|
||||
|
||||
var thumb = doc.createElement('img');
|
||||
thumb.file = file;
|
||||
div.appendChild(thumb);
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
thumb.src = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
var name = doc.createElement('input');
|
||||
name.type = 'text';
|
||||
name.value = getFileName(file.name);
|
||||
li.appendChild(name);
|
||||
|
||||
var statusNode = doc.createElement('div');
|
||||
statusNode.className = 'status';
|
||||
li.appendChild(statusNode);
|
||||
|
||||
var fileData = {
|
||||
li: li,
|
||||
file: file,
|
||||
name: name,
|
||||
statusNode: statusNode
|
||||
};
|
||||
|
||||
var button = new Htk.Button({
|
||||
tip: 'Remove',
|
||||
icon: 'delete'
|
||||
});
|
||||
button.node.addEventListener('click',
|
||||
() => this.onFileRemove(fileData));
|
||||
li.appendChild(button.node);
|
||||
|
||||
this.filesData.push(fileData);
|
||||
|
||||
this.$.fileList.appendChild(li);
|
||||
this.setImageStatus(fileData, Status.NONE, 'add', _('Pending upload'));
|
||||
}
|
||||
|
||||
,async onUploadClick() {
|
||||
if (this.isUploading) return;
|
||||
|
||||
const uploadQueue = [];
|
||||
let hasFiles = false;
|
||||
|
||||
for (const fileData of this.filesData) {
|
||||
if (fileData.status !== Status.NONE) continue;
|
||||
this.setImageStatus(
|
||||
fileData, Status.WAITING, 'cloud_upload', _('Waiting for upload'));
|
||||
fileData.name.disabled = true;
|
||||
uploadQueue.push(fileData);
|
||||
hasFiles = true;
|
||||
}
|
||||
|
||||
if (!hasFiles) {
|
||||
Htk.Toast.showWarning(_('There are no files to upload'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.isUploading = true;
|
||||
let hasErrors = false;
|
||||
|
||||
for (const fileData of uploadQueue) {
|
||||
this.setImageStatus(
|
||||
fileData, Status.UPLOADING, 'upload', _('Uploading file'));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('updateMatching', this.$.updateMatching.value);
|
||||
formData.append('image', fileData.file);
|
||||
formData.append('name', fileData.name.value);
|
||||
formData.append('schema', this.$.schema.value);
|
||||
formData.append('srv', 'json:image/upload');
|
||||
|
||||
try {
|
||||
await this.conn.sendFormData(formData);
|
||||
this.setImageStatus(
|
||||
fileData, Status.UPLOADED, 'cloud_done', _('Image uploaded'));
|
||||
} catch(err) {
|
||||
this.setImageStatus(
|
||||
fileData, Status.NONE, 'error', err.message);
|
||||
fileData.name.disabled = false;
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.isUploading = false;
|
||||
|
||||
if (hasErrors)
|
||||
Htk.Toast.showError(_('Some errors happened on upload'));
|
||||
else
|
||||
Htk.Toast.showMessage(_('Upload finished successfully'));
|
||||
}
|
||||
|
||||
,setImageStatus(fileData, status, icon, title) {
|
||||
fileData.status = status;
|
||||
|
||||
var statusNode = fileData.statusNode;
|
||||
Vn.Node.removeChilds(statusNode);
|
||||
|
||||
var iconNode = new Htk.Icon({name: icon});
|
||||
statusNode.appendChild(iconNode.node);
|
||||
statusNode.title = title ? title : '';
|
||||
}
|
||||
|
||||
,onFileRemove(fileData) {
|
||||
this.$.fileList.removeChild(fileData.li);
|
||||
|
||||
for (var i = 0; i < this.filesData.length; i++)
|
||||
if (this.filesData[i] === fileData) {
|
||||
this.filesData.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
,onClearClick() {
|
||||
this.filesData = [];
|
||||
Vn.Node.removeChilds(this.$.fileList);
|
||||
}
|
||||
|
||||
,onDropzoneClick() {
|
||||
this.$.file.click();
|
||||
}
|
||||
|
||||
,onFileChange() {
|
||||
this.addFiles(this.$.file.files);
|
||||
}
|
||||
|
||||
,onDragEnter() {
|
||||
this.$.dropzone.classList.add('dragover');
|
||||
}
|
||||
|
||||
,onDragLeave() {
|
||||
this.$.dropzone.classList.remove('dragover');
|
||||
}
|
||||
|
||||
,onDragOver(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
,onDragEnd(event) {
|
||||
this.$.dropzone.classList.remove('dragover');
|
||||
event.dataTransfer.clearData();
|
||||
}
|
||||
|
||||
,onDrop(event) {
|
||||
event.preventDefault();
|
||||
this.addFiles(event.dataTransfer.files);
|
||||
}
|
||||
});
|
||||
|
||||
function getFileName(path) {
|
||||
var barIndex = path.lastIndexOf('/');
|
||||
if (barIndex === -1)
|
||||
barIndex = path.lastIndexOf('\\');
|
||||
if (barIndex === -1)
|
||||
barIndex = 0;
|
||||
|
||||
var dotIndex = path.lastIndexOf('.');
|
||||
if (dotIndex === -1)
|
||||
dotIndex = 0;
|
||||
|
||||
return path.substr(barIndex, dotIndex);
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
Images: Imatges
|
||||
Collection: Col·lecció
|
||||
Click or drop files here: Prem o deixa anar els arxius aquí
|
||||
Pending upload: Pujada pendent
|
||||
Update items with matching id: Actualitzar els elements amb id coincident
|
||||
Clear all: Netejar tot
|
||||
Upload files: Pujar arxius
|
||||
Waiting for upload: Esperant per pujar
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
Images: Images
|
||||
Collection: Collection
|
||||
Click or drop files here: Click or drop files here
|
||||
Pending upload: Pending upload
|
||||
Update items with matching id: Update items with matching id
|
||||
Clear all: Clear all
|
||||
Upload files: Upload files
|
||||
Waiting for upload: Waiting for upload
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
Images: Imágenes
|
||||
Collection: Colección
|
||||
Click or drop files here: Pulsa o suelta los archivos aquí
|
||||
Pending upload: Subida pendiente
|
||||
Update items with matching id: Actualizar ítems con id coincidente
|
||||
Clear all: Limpiar todo
|
||||
Upload files: Subir archivos
|
||||
Waiting for upload: Esperando para subir
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
Images: Images
|
||||
Collection: Collection
|
||||
Click or drop files here: Cliquez ici ou déposer des fichiers
|
||||
Pending upload: Hausse en attente
|
||||
Update items with matching id: Mettre à jour les éléments avec l'identifiant correspondant
|
||||
Clear all: Tout effacer
|
||||
Upload files: Upload Files
|
||||
Waiting for upload: En attente de télécharger
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
Images: Imagens
|
||||
Collection: Coleção
|
||||
Click or drop files here: Clique ou solte arquivos aqui
|
||||
Pending upload: Ascensão pendente
|
||||
Update items with matching id: Atualizar itens com id correspondente
|
||||
Clear all: Limpar tudo
|
||||
Upload files: Fazer upload de arquivos
|
||||
Waiting for upload: Esperando para enviar
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
(function() {
|
||||
|
||||
var Status = {
|
||||
NONE : 0
|
||||
,WAITING : 1
|
||||
,UPLOADING : 2
|
||||
,UPLOADED : 3
|
||||
};
|
||||
|
||||
Hedera.Photos = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
,filesData: []
|
||||
,uploadCount: 0
|
||||
,errors: false
|
||||
,uploadQueue: []
|
||||
,isUploading: false
|
||||
|
||||
,activate: function ()
|
||||
{
|
||||
this.$.schema.value = 'catalog';
|
||||
}
|
||||
|
||||
,addFiles: function (files)
|
||||
{
|
||||
if (!files)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < files.length; i++)
|
||||
this.addFile (files[i]);
|
||||
}
|
||||
|
||||
,addFile: function (file)
|
||||
{
|
||||
var doc = document;
|
||||
var div = doc.createElement ('div');
|
||||
|
||||
var button = new Htk.Button ({
|
||||
tip: 'Remove',
|
||||
icon: 'delete'
|
||||
});
|
||||
button.on ('click', this.onFileRemove, this);
|
||||
div.appendChild (button.node);
|
||||
|
||||
var thumb = doc.createElement ('img');
|
||||
thumb.file = file;
|
||||
thumb.className = 'thumb';
|
||||
div.appendChild (thumb);
|
||||
|
||||
var reader = new FileReader ();
|
||||
reader.onload = function (e) { thumb.src = e.target.result; };
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
var name = doc.createElement ('input');
|
||||
name.type = 'text';
|
||||
name.value = getFileName (file.name);
|
||||
div.appendChild (name);
|
||||
|
||||
var statusNode = doc.createElement ('span');
|
||||
statusNode.className = 'status';
|
||||
div.appendChild (statusNode);
|
||||
|
||||
var fileData = {
|
||||
div: div,
|
||||
file: file,
|
||||
name: name,
|
||||
statusNode: statusNode,
|
||||
status: Status.NONE
|
||||
};
|
||||
this.filesData.push (fileData);
|
||||
button.value = fileData;
|
||||
|
||||
this.$.fileList.appendChild (div);
|
||||
}
|
||||
|
||||
,onUploadClick: function ()
|
||||
{
|
||||
var filesData = this.filesData;
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < filesData.length; i++)
|
||||
{
|
||||
var fileData = filesData[i];
|
||||
|
||||
if (fileData.status === Status.NONE)
|
||||
{
|
||||
this.setImageStatus (
|
||||
fileData, Status.WAITING, 'cloud-upload', _('Waiting for upload'));
|
||||
fileData.name.disabled = true;
|
||||
this.uploadQueue.push (fileData);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count === 0)
|
||||
Htk.Toast.showWarning (_('There are no files to upload'));
|
||||
else
|
||||
this.uploadNextFile ();
|
||||
}
|
||||
|
||||
,uploadNextFile: function ()
|
||||
{
|
||||
if (this.isUploading)
|
||||
return;
|
||||
|
||||
this.isUploading = true;
|
||||
|
||||
var fileData = this.uploadQueue.shift ();
|
||||
this.setImageStatus (
|
||||
fileData, Status.UPLOADING, 'upload', _('Uploading file'));
|
||||
|
||||
var formData = new FormData();
|
||||
formData.append ('image', fileData.file);
|
||||
formData.append ('name', fileData.name.value);
|
||||
formData.append ('schema', this.$.schema.value);
|
||||
formData.append ('srv', 'json:image/upload');
|
||||
this.conn.sendFormData (formData,
|
||||
this.onFileUpload.bind (this, fileData));
|
||||
}
|
||||
|
||||
,onFileUpload: function (fileData, data, error)
|
||||
{
|
||||
this.isUploading = false;
|
||||
|
||||
if (data)
|
||||
{
|
||||
this.setImageStatus (
|
||||
fileData, Status.UPLOADED, 'ok', _('Image uploaded'));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setImageStatus (
|
||||
fileData, Status.NONE, 'error', error.message);
|
||||
fileData.name.disabled = false;
|
||||
this.errors = true;
|
||||
}
|
||||
|
||||
if (this.uploadQueue.length === 0)
|
||||
{
|
||||
if (this.errors)
|
||||
Htk.Toast.showError (_('Some errors happened on upload'));
|
||||
else
|
||||
Htk.Toast.showMessage (_('Upload finished successfully'));
|
||||
|
||||
this.errors = false;
|
||||
}
|
||||
else
|
||||
this.uploadNextFile ();
|
||||
}
|
||||
|
||||
,setImageStatus: function (fileData, status, icon, title)
|
||||
{
|
||||
fileData.status = status;
|
||||
|
||||
var statusNode = fileData.statusNode;
|
||||
Vn.Node.removeChilds (statusNode);
|
||||
|
||||
var iconNode = new Htk.Icon ({icon: icon});
|
||||
statusNode.appendChild (iconNode.node);
|
||||
statusNode.title = title ? title : '';
|
||||
}
|
||||
|
||||
,onFileRemove: function (button)
|
||||
{
|
||||
var fileData = button.value;
|
||||
this.$.fileList.removeChild (fileData.div);
|
||||
|
||||
for (var i = 0; i < this.filesData.length; i++)
|
||||
if (this.filesData[i] === fileData)
|
||||
{
|
||||
this.filesData.splice (i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
,onClearClick: function ()
|
||||
{
|
||||
this.filesData = [];
|
||||
Vn.Node.removeChilds (this.$.fileList);
|
||||
}
|
||||
|
||||
,onDropzoneClick: function ()
|
||||
{
|
||||
this.$.file.click ();
|
||||
}
|
||||
|
||||
,onFileChange: function ()
|
||||
{
|
||||
this.addFiles (this.$.file.files);
|
||||
}
|
||||
|
||||
,onDragEnter: function ()
|
||||
{
|
||||
Vn.Node.addClass (this.$.dropzone, 'dragover');
|
||||
}
|
||||
|
||||
,onDragLeave: function ()
|
||||
{
|
||||
Vn.Node.removeClass (this.$.dropzone, 'dragover');
|
||||
}
|
||||
|
||||
,onDragOver: function (event)
|
||||
{
|
||||
event.preventDefault ();
|
||||
}
|
||||
|
||||
,onDragEnd: function (event)
|
||||
{
|
||||
Vn.Node.removeClass (this.$.dropzone, 'dragover');
|
||||
event.dataTransfer.clearData ();
|
||||
}
|
||||
|
||||
,onDrop: function (event)
|
||||
{
|
||||
event.preventDefault ();
|
||||
this.addFiles (event.dataTransfer.files);
|
||||
}
|
||||
});
|
||||
|
||||
function getFileName (path)
|
||||
{
|
||||
var barIndex = path.lastIndexOf ('/');
|
||||
if (barIndex === -1)
|
||||
barIndex = path.lastIndexOf ('\\');
|
||||
if (barIndex === -1)
|
||||
barIndex = 0;
|
||||
|
||||
var dotIndex = path.lastIndexOf ('.');
|
||||
if (dotIndex === -1)
|
||||
dotIndex = 0;
|
||||
|
||||
return path.substr (barIndex, dotIndex);
|
||||
}
|
||||
|
||||
})();
|
|
@ -0,0 +1,63 @@
|
|||
|
||||
/* Dropzone */
|
||||
|
||||
.photos .dropzone
|
||||
{
|
||||
background-color: white;
|
||||
border-style: dashed;
|
||||
border-radius: .4em;
|
||||
border-color: #2196F3;
|
||||
padding: 2em 1em;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
.photos .dropzone.dragover
|
||||
{
|
||||
color: #CCC;
|
||||
border-style: solid;
|
||||
}
|
||||
.photos input[type=file]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* File list */
|
||||
|
||||
.photos .file-list
|
||||
{
|
||||
margin-top: 1em;
|
||||
}
|
||||
.photos .file-list > div
|
||||
{
|
||||
height: 2.5em;
|
||||
}
|
||||
.photos .file-list .thumb
|
||||
{
|
||||
max-height: 2em;
|
||||
max-width: 2em;
|
||||
vertical-align: middle;
|
||||
margin: 0 1em;
|
||||
}
|
||||
.photos .file-list input
|
||||
{
|
||||
max-width: 10em;
|
||||
}
|
||||
.photos .file-list .status
|
||||
{
|
||||
margin-left: .5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
.photos .footer
|
||||
{
|
||||
margin-top: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
.photos .footer > button
|
||||
{
|
||||
font-size: 1.2em;
|
||||
margin-left: 1em;
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
|
||||
/* Dropzone */
|
||||
|
||||
.photos .dropzone {
|
||||
background-color: white;
|
||||
border-style: dashed;
|
||||
border-radius: .4em;
|
||||
border-color: #2196F3;
|
||||
padding: 2em 1em;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
.photos .dropzone.dragover {
|
||||
color: #CCC;
|
||||
border-style: solid;
|
||||
}
|
||||
.photos input[type=file] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* File list */
|
||||
|
||||
.photos .file-list {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.photos .file-list > div {
|
||||
height: 2.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.photos .file-list > div > * {
|
||||
overflow: hidden;
|
||||
}
|
||||
.photos .file-list .thumb {
|
||||
width: 2em;
|
||||
padding-right: .5em;
|
||||
text-align: center;
|
||||
}
|
||||
.photos .file-list .thumb > img {
|
||||
max-height: 2em;
|
||||
max-width: 2em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.photos .file-list input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.photos .file-list .status {
|
||||
cursor: pointer;
|
||||
width: 1.2em;
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
}
|
||||
.photos .file-list .status > .htk-icon {
|
||||
display: block;
|
||||
}
|
||||
.photos .file-list .htk-button {
|
||||
opacity: .2;
|
||||
}
|
||||
.photos .file-list .htk-button:hover {
|
||||
background-color: transparent;
|
||||
opacity: 1;
|
||||
}
|
||||
.photos .file-list .htk-button > img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
.photos .update-matching {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
.photos .footer {
|
||||
margin-top: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
.photos .footer > button {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 1em;
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
<vn>
|
||||
<div id="title">
|
||||
<h1><t>Images</t></h1>
|
||||
</div>
|
||||
<div id="form" class="photos">
|
||||
<div class="box form vn-w-sm vn-pa-lg">
|
||||
<h1 id="title">
|
||||
_Images
|
||||
</h1>
|
||||
<div id="main" class="photos">
|
||||
<div class="card form">
|
||||
<div class="form-group">
|
||||
<label><t>Collection</t></label>
|
||||
<label>_Collection</label>
|
||||
<htk-combo id="schema">
|
||||
<db-model property="model">
|
||||
<custom>
|
||||
SELECT name, `desc` FROM imageCollection ORDER BY `desc`
|
||||
</custom>
|
||||
SELECT name, `desc` FROM imageCollection ORDER BY `desc`
|
||||
</db-model>
|
||||
</htk-combo>
|
||||
</div>
|
||||
|
@ -22,7 +20,7 @@
|
|||
on-drop="onDrop"
|
||||
on-dragend="onDragEnd"
|
||||
on-click="onDropzoneClick">
|
||||
<t>Click or drop files here</t>
|
||||
_Click or drop files here
|
||||
</div>
|
||||
<input
|
||||
id="file"
|
||||
|
@ -31,18 +29,12 @@
|
|||
name="image"
|
||||
on-change="onFileChange"/>
|
||||
<div id="file-list" class="file-list"/>
|
||||
<div class="update-matching">
|
||||
<label>
|
||||
<htk-check id="update-matching" value="true"/>
|
||||
<t>Update items with matching id</t>
|
||||
</label>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button class="thin" on-click="onClearClick">
|
||||
<t>Clear all</t>
|
||||
_Clear all
|
||||
</button>
|
||||
<button class="thin" on-click="onUploadClick">
|
||||
<t>Upload files</t>
|
||||
_Upload files
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
|
||||
,activate() {
|
||||
this.$.resultIndex.value = 0;
|
||||
}
|
||||
|
||||
,clean() {
|
||||
if (this._grid) {
|
||||
this.$.gridHolder.removeChild(this._grid.node);
|
||||
this._grid.unref();
|
||||
this._grid = null;
|
||||
}
|
||||
}
|
||||
|
||||
,onExecuteClick() {
|
||||
this.clean();
|
||||
|
||||
var model = new Db.Model({
|
||||
conn: this.conn,
|
||||
query: this.$.sql.value,
|
||||
resultIndex: this.$.resultIndex.value,
|
||||
updatable: this.$.updatable.value
|
||||
});
|
||||
model.on('status-changed', this.onModelChange, this);
|
||||
}
|
||||
|
||||
,onCleanClick() {
|
||||
this.clean();
|
||||
}
|
||||
|
||||
,onModelChange(model, status) {
|
||||
if (status !== Db.Model.Status.LOADING) {
|
||||
model.disconnect('status-changed', this.onModelChange, this);
|
||||
model.unref();
|
||||
}
|
||||
|
||||
if (status !== Db.Model.Status.READY)
|
||||
return;
|
||||
|
||||
Htk.Toast.showMessage(_('Query executed!'));
|
||||
|
||||
var gridHolder = this.$.gridHolder;
|
||||
|
||||
if (gridHolder.firstChild)
|
||||
gridHolder.removeChilds(gridHolder.firstChild);
|
||||
|
||||
var grid = new Htk.Grid();
|
||||
|
||||
var columns = model.columns;
|
||||
|
||||
for (var i = 0; i < columns.length; i++) {
|
||||
var c = columns[i];
|
||||
|
||||
switch (c.type) {
|
||||
case Db.Conn.Type.BOOLEAN:
|
||||
var column = new Htk.ColumnCheck();
|
||||
break;
|
||||
case Db.Conn.Type.INTEGER:
|
||||
var column = new Htk.ColumnSpin();
|
||||
break;
|
||||
case Db.Conn.Type.DOUBLE:
|
||||
var column = new Htk.ColumnSpin({digits: 2});
|
||||
break;
|
||||
case Db.Conn.Type.DATE:
|
||||
var column = new Htk.ColumnDate({format: '%a, %e %b %Y'});
|
||||
break;
|
||||
case Db.Conn.Type.DATE_TIME:
|
||||
var column = new Htk.ColumnDate({format: '%a, %e %b %Y, %T'});
|
||||
break;
|
||||
case Db.Conn.Type.STRING:
|
||||
default:
|
||||
var column = new Htk.ColumnText();
|
||||
}
|
||||
|
||||
column.setProperties({
|
||||
title: c.name,
|
||||
editable: this.$.updatable.value,
|
||||
columnIndex: i
|
||||
});
|
||||
|
||||
grid.appendColumn(column);
|
||||
}
|
||||
|
||||
grid.model = model;
|
||||
gridHolder.appendChild(grid.node);
|
||||
this._grid = grid;
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
|
||||
Hedera.Queries = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,activate: function ()
|
||||
{
|
||||
this.$.resultIndex.value = 0;
|
||||
}
|
||||
|
||||
,clean: function ()
|
||||
{
|
||||
if (this._grid)
|
||||
{
|
||||
this.$.gridHolder.removeChild (this._grid.node);
|
||||
this._grid.unref ();
|
||||
this._grid = null;
|
||||
}
|
||||
}
|
||||
|
||||
,_onExecuteClick: function ()
|
||||
{
|
||||
this.clean ();
|
||||
|
||||
var model = new Db.Model ({
|
||||
conn: this.conn,
|
||||
query: this.$.sql.value,
|
||||
resultIndex: this.$.resultIndex.value,
|
||||
updatable: this.$.updatable.value
|
||||
});
|
||||
model.on ('status-changed', this._onModelChange, this);
|
||||
}
|
||||
|
||||
,_onCleanClick: function ()
|
||||
{
|
||||
this.clean ();
|
||||
}
|
||||
|
||||
,_onModelChange: function (model, status)
|
||||
{
|
||||
if (status !== Db.Model.Status.LOADING)
|
||||
{
|
||||
model.disconnect ('status-changed', this._onModelChange, this);
|
||||
model.unref ();
|
||||
}
|
||||
|
||||
if (status !== Db.Model.Status.READY)
|
||||
return;
|
||||
|
||||
Htk.Toast.showMessage (_('Query executed!'));
|
||||
|
||||
var gridHolder = this.$.gridHolder;
|
||||
|
||||
if (gridHolder.firstChild)
|
||||
gridHolder.removeChilds (gridHolder.firstChild);
|
||||
|
||||
var grid = new Htk.Grid ();
|
||||
|
||||
var columns = model.columns;
|
||||
|
||||
for (var i = 0; i < columns.length; i++)
|
||||
{
|
||||
var c = columns[i];
|
||||
|
||||
switch (c.type)
|
||||
{
|
||||
case Db.Conn.Type.BOOLEAN:
|
||||
var column = new Htk.ColumnCheck ();
|
||||
break;
|
||||
case Db.Conn.Type.INTEGER:
|
||||
var column = new Htk.ColumnSpin ();
|
||||
break;
|
||||
case Db.Conn.Type.DOUBLE:
|
||||
var column = new Htk.ColumnSpin ({digits: 2});
|
||||
break;
|
||||
case Db.Conn.Type.DATE:
|
||||
var column = new Htk.ColumnDate ({format: '%a, %e %b %Y'});
|
||||
break;
|
||||
case Db.Conn.Type.DATE_TIME:
|
||||
var column = new Htk.ColumnDate ({format: '%a, %e %b %Y, %T'});
|
||||
break;
|
||||
case Db.Conn.Type.STRING:
|
||||
default:
|
||||
var column = new Htk.ColumnText ();
|
||||
}
|
||||
|
||||
column.setProperties ({
|
||||
title: c.name,
|
||||
editable: this.$.updatable.value,
|
||||
columnIndex: i
|
||||
});
|
||||
|
||||
grid.appendColumn (column);
|
||||
}
|
||||
|
||||
grid.model = model;
|
||||
gridHolder.appendChild (grid.node);
|
||||
this._grid = grid;
|
||||
|
||||
}
|
||||
});
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
|
||||
.queries textarea {
|
||||
.queries .card
|
||||
{
|
||||
max-width: 40em;
|
||||
}
|
||||
.queries textarea
|
||||
{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 8em;
|
||||
}
|
||||
.queries .result {
|
||||
.queries .result
|
||||
{
|
||||
margin-top: 1em;
|
||||
overflow: auto;
|
||||
}
|
|
@ -1,37 +1,37 @@
|
|||
<vn>
|
||||
<div id="title">
|
||||
<h1><t>Queries</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_Queries
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-bar-button
|
||||
icon="ok"
|
||||
tip="_Execute"
|
||||
on-click="this.onExecuteClick()"/>
|
||||
on-click="_onExecuteClick"/>
|
||||
<htk-bar-button
|
||||
icon="delete"
|
||||
tip="_Clean"
|
||||
on-click="this.onCleanClick()"/>
|
||||
on-click="_onCleanClick"/>
|
||||
</div>
|
||||
<div id="form" class="queries">
|
||||
<div class="box form vn-w-sm vn-pa-lg">
|
||||
<div class="form-group">
|
||||
<label><t>SQL query</t></label>
|
||||
<div id="main" class="queries">
|
||||
<div class="card form">
|
||||
<div>
|
||||
<label>_SQL query</label>
|
||||
<textarea
|
||||
id="sql"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><t>Result index</t></label>
|
||||
<div>
|
||||
<label>_Result index</label>
|
||||
<htk-spin id="result-index"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><t>Updatable</t></label>
|
||||
<div>
|
||||
<label>_Updatable</label>
|
||||
<htk-check id="updatable"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box result">
|
||||
<div class="card result">
|
||||
<div id="grid-holder"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
|
||||
,rendererFunc(scope, form) {
|
||||
var isEnabled = form.$.active
|
||||
scope.$.disabled.style.display = isEnabled ?
|
||||
'none' : 'block';
|
||||
scope.$.impersonate.node.style.display = isEnabled ?
|
||||
'block' : 'none';
|
||||
}
|
||||
|
||||
,async onChangeUserClick(userName) {
|
||||
await this.gui.supplantUser(userName);
|
||||
this.hash.setAll({form: 'ecomerce/orders'});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
|
||||
.users-box.item > .actions {
|
||||
display: flex;
|
||||
}
|
||||
.users-box .disabled {
|
||||
.users ._disabled
|
||||
{
|
||||
color: white;
|
||||
background-color: #F66;
|
||||
border-radius: .2em;
|
||||
padding: .3em;
|
||||
font-size: .8em;
|
||||
}
|
||||
.action-bar .htk-search-entry
|
||||
{
|
||||
margin: .8em;
|
||||
}
|
||||
|
|
@ -1,48 +1,46 @@
|
|||
<vn>
|
||||
<div id="title">
|
||||
<h1><t>User management</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_User management
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-search-entry form="hash" column="user"/>
|
||||
<htk-search-entry lot="hash" name="user"/>
|
||||
</div>
|
||||
<div id="form" class="users">
|
||||
<htk-repeater
|
||||
form-id="iter"
|
||||
renderer="rendererFunc"
|
||||
class="htk-list box vn-w-xs">
|
||||
<db-model property="model" lot="hash">
|
||||
SELECT u.id, u.name, u.nickname, u.active
|
||||
FROM account.user u
|
||||
WHERE u.name LIKE CONCAT('%', #user, '%')
|
||||
OR u.nickname LIKE CONCAT('%', #user, '%')
|
||||
OR u.id = #user
|
||||
ORDER BY u.name LIMIT 200
|
||||
</db-model>
|
||||
<custom>
|
||||
<a class="users-box item"
|
||||
href="{{`#!form=admin/access-log&user=${iter.id}`}}"
|
||||
title="_Access log">
|
||||
<div class="content">
|
||||
<div id="main" class="users">
|
||||
<div class="card list">
|
||||
<htk-repeater renderer="rendererFunc">
|
||||
<db-model property="model" lot="hash">
|
||||
SELECT u.id, u.name, u.nickname, u.active
|
||||
FROM account.user u
|
||||
WHERE u.name LIKE CONCAT('%', #user, '%')
|
||||
OR u.nickname LIKE CONCAT('%', #user, '%')
|
||||
OR u.id = #user
|
||||
ORDER BY u.name LIMIT 200
|
||||
</db-model>
|
||||
<custom>
|
||||
<a
|
||||
class="list-row"
|
||||
href="#!form=admin/access-log&user={{id}}"
|
||||
title="_AccessLog">
|
||||
<div class="actions">
|
||||
<span id="disabled">
|
||||
_Disabled
|
||||
</span>
|
||||
<htk-button
|
||||
id="impersonate"
|
||||
value="{{id}}"
|
||||
tip="_AccessAsUser"
|
||||
icon="incognito"
|
||||
on-click="onChangeUserClick"/>
|
||||
</div>
|
||||
<p class="important">
|
||||
{{iter.nickname}}
|
||||
{{nickname}}
|
||||
</p>
|
||||
<p>
|
||||
#{{iter.id}} - {{iter.name}}
|
||||
@{{id}} - {{name}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="actions"
|
||||
on-click="$event.preventDefault()">
|
||||
<span id="disabled" class="disabled">
|
||||
<t>Disabled</t>
|
||||
</span>
|
||||
<htk-button
|
||||
id="impersonate"
|
||||
icon="supervisor_account"
|
||||
tip="_Impersonate user"
|
||||
on-click="this.onChangeUserClick(iter.name)"/>
|
||||
</div>
|
||||
</a>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</a>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
</div>
|
||||
</div>
|
||||
</vn>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
Hedera.Users = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,rendererFunc: function (scope, lot)
|
||||
{
|
||||
var isEnabled = lot.$.active;
|
||||
scope.$.disabled.style.display = isEnabled ?
|
||||
'none' : 'block';
|
||||
scope.$.impersonate.node.style.display = isEnabled ?
|
||||
'block' : 'none';
|
||||
}
|
||||
|
||||
,onChangeUserClick: function (button)
|
||||
{
|
||||
this.gui.supplantUser (button.value,
|
||||
this.onUserSupplant.bind (this));
|
||||
}
|
||||
|
||||
,onUserSupplant: function ()
|
||||
{
|
||||
this.hash.setAll ({form: 'ecomerce/orders'});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml')
|
||||
|
||||
,activate() {
|
||||
if (!this.hash.$.to)
|
||||
this.hash.assign({
|
||||
from: Date.vnNew(),
|
||||
to: Date.vnNew()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
.visits .card
|
||||
{
|
||||
max-width: 28em;
|
||||
}
|
||||
|
||||
.visits .summary p
|
||||
{
|
||||
margin: 0;
|
||||
font-size: 1.4em;
|
||||
text-align: right;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
.visits .box {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.visits .box:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.visits .summary p {
|
||||
font-size: 1.4rem;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
|
@ -3,85 +3,76 @@
|
|||
<vn-spec name="from" type="Date"/>
|
||||
<vn-spec name="to" type="Date"/>
|
||||
</vn-lot-query>
|
||||
<div id="title">
|
||||
<h1><t>Visits</t></h1>
|
||||
</div>
|
||||
<h1 id="title">
|
||||
_Visits
|
||||
</h1>
|
||||
<div id="actions">
|
||||
<htk-bar-button
|
||||
icon="refresh"
|
||||
tip="_Refresh"
|
||||
on-click="visits.refresh()"/>
|
||||
on-click="onRefreshClick"/>
|
||||
<htk-bar-button
|
||||
icon="visibility"
|
||||
icon="user-info"
|
||||
tip="_Connections"
|
||||
on-click="this.hash.setAll({form: 'admin/connections'})"/>
|
||||
on-click="onSessionsClick"/>
|
||||
</div>
|
||||
<div id="form" class="visits">
|
||||
<div class="vn-w-xs">
|
||||
<div class="form vn-pa-lg box">
|
||||
<div class="form-group">
|
||||
<label><t>From</t></label>
|
||||
<htk-date-chooser
|
||||
form="params"
|
||||
column="from"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><t>To</t></label>
|
||||
<htk-date-chooser
|
||||
form="params"
|
||||
column="to"/>
|
||||
</div>
|
||||
<div id="main" class="visits">
|
||||
<div class="card form">
|
||||
<div>
|
||||
<label>_From</label>
|
||||
<htk-date-chooser lot="params" name="from"/>
|
||||
</div>
|
||||
<div class="summary vn-pa-lg box">
|
||||
<p>
|
||||
<htk-text>
|
||||
<db-calc-sum
|
||||
property="param"
|
||||
model="visits"
|
||||
column-name="visits"/>
|
||||
</htk-text>
|
||||
<t>visits</t>,
|
||||
<htk-text>
|
||||
<db-calc-sum
|
||||
property="param"
|
||||
model="visits"
|
||||
column-name="newVisits"/>
|
||||
</htk-text>
|
||||
<t>news</t>
|
||||
</p>
|
||||
<div>
|
||||
<label>_To</label>
|
||||
<htk-date-chooser lot="params" name="to"/>
|
||||
</div>
|
||||
<htk-repeater
|
||||
class="box htk-list"
|
||||
form-id="iter"
|
||||
empty-message="_Select date interval">
|
||||
</div>
|
||||
<div class="summary card form">
|
||||
<p>
|
||||
<htk-text>
|
||||
<db-calc-sum
|
||||
property="param"
|
||||
model="visits"
|
||||
column-name="visits"/>
|
||||
</htk-text>
|
||||
<span>_visits</span>,
|
||||
<htk-text>
|
||||
<db-calc-sum
|
||||
property="param"
|
||||
model="visits"
|
||||
column-name="newVisits"/>
|
||||
</htk-text>
|
||||
<span>_news</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card list ">
|
||||
<htk-repeater empty-message="_Select date interval">
|
||||
<db-model property="model" id="visits" lot="params">
|
||||
SELECT browser,
|
||||
MIN(CAST(version AS DECIMAL(4,1))) minVersion,
|
||||
MAX(CAST(version AS DECIMAL(4,1))) maxVersion,
|
||||
MAX(c.stamp) lastVisit,
|
||||
COUNT(DISTINCT c.id) visits,
|
||||
SUM(a.firstAccessFk = c.id AND v.firstAgentFk = a.id) newVisits
|
||||
SELECT browser
|
||||
,MIN(CAST(version AS DECIMAL(4,1))) minVersion
|
||||
,MAX(CAST(version AS DECIMAL(4,1))) maxVersion
|
||||
,MAX(c.stamp) lastVisit
|
||||
,COUNT(DISTINCT c.id) visits
|
||||
,SUM(a.firstAccess = c.id AND v.firstAgent = a.id) newVisits
|
||||
FROM visitUser e
|
||||
JOIN visitAccess c ON c.id = e.accessFk
|
||||
JOIN visitAgent a ON a.id = c.agentFk
|
||||
JOIN visit v ON v.id = a.visitFk
|
||||
JOIN visitAccess c ON e.access = c.id
|
||||
JOIN visitAgent a ON c.agent = a.id
|
||||
JOIN visit v ON a.visit = v.id
|
||||
WHERE c.stamp BETWEEN TIMESTAMP(#from,'00:00:00') AND TIMESTAMP(#to,'23:59:59')
|
||||
GROUP BY browser ORDER BY visits DESC
|
||||
GROUP BY browser
|
||||
ORDER BY visits DESC
|
||||
</db-model>
|
||||
<custom>
|
||||
<div class="item">
|
||||
<div class="content">
|
||||
<p class="important">
|
||||
{{iter.browser}} {{iter.minVersion}} - {{iter.maxVersion}}
|
||||
</p>
|
||||
<p>
|
||||
{{iter.visits}} <t>visits</t>,
|
||||
{{iter.newVisits}} <t>news</t>
|
||||
</p>
|
||||
<p>
|
||||
{{Vn.Value.format(iter.lastVisit, _('%a, %e %b %Y at %T'))}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="list-row">
|
||||
<p class="important">
|
||||
{{browser}} {{minVersion}} - {{maxVersion}}
|
||||
</p>
|
||||
<p>
|
||||
{{visits}} <span>_visits</span>, {{newVisits}} <span>_news</span>
|
||||
</p>
|
||||
<p>
|
||||
<htk-text lot="iter" name="lastVisit" format="_%a, %e %b %Y at %T"/>
|
||||
</p>
|
||||
</div>
|
||||
</custom>
|
||||
</htk-repeater>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
Hedera.Visits = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,activate: function ()
|
||||
{
|
||||
if (!this.hash.$.to)
|
||||
this.hash.assign ({
|
||||
from: new Date (),
|
||||
to: new Date ()
|
||||
});
|
||||
}
|
||||
|
||||
,onRefreshClick: function ()
|
||||
{
|
||||
this.$.visits.refresh ();
|
||||
}
|
||||
|
||||
,onSessionsClick: function ()
|
||||
{
|
||||
this.hash.setAll ({form: 'admin/connections'});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import './style.scss';
|
||||
|
||||
export default new Class({
|
||||
Extends: Hedera.Form,
|
||||
Template: require('./ui.xml'),
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
ListByAgency: Paquets per agència
|
||||
ShowByProvince: Mostrar desglose per província
|
||||
Agency: Agència
|
||||
Exps: Exps.
|
||||
Bundles: Paquets
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
ListByAgency: Bundles by agency
|
||||
ShowByProvince: Show breakdown by province
|
||||
Agency: Agency
|
||||
Exps: Exps.
|
||||
Bundles: Bundles
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
ListByAgency: Bultos por agencia
|
||||
ShowByProvince: Mostrar desglose por provincia
|
||||
Agency: Agencia
|
||||
Exps: Exps.
|
||||
Bundles: Bultos
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
ListByAgency: Liste par agence
|
||||
ShowByProvince: Montrer par province
|
||||
Agency: Agence
|
||||
Exps: Expéditeur
|
||||
Bundles: Cartons
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
ListByAgency: Bultos por agencia
|
||||
ShowByProvince: Mostrar desglosse por Distrito
|
||||
Agency: Agencia
|
||||
Exps: Exps.
|
||||
Bundles: Bultos
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
Hedera.Packages = new Class
|
||||
({
|
||||
Extends: Hedera.Form
|
||||
|
||||
,onShowClick: function (column, agencyId)
|
||||
{
|
||||
this.hash.setAll ({
|
||||
form: 'agencies/provinces',
|
||||
agency: agencyId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.packages .card
|
||||
{
|
||||
max-width: 35em;
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
<vn>
|
||||
<div id="title">
|
||||
<h1><t>ListByAgency</t></h1>
|
||||
</div>
|
||||
<div id="form" class="packages">
|
||||
<div class="box vn-w-sm">
|
||||
<h1 id="title">
|
||||
_ListByAgency
|
||||
</h1>
|
||||
<div id="main" class="packages">
|
||||
<div class="card">
|
||||
<htk-grid>
|
||||
<db-model property="model">
|
||||
<custom>
|
||||
CALL vn.agencyVolume()
|
||||
</custom>
|
||||
CALL vn2008.agencia_volume ()
|
||||
</db-model>
|
||||
<htk-column-button
|
||||
column="agency_id"
|
||||
icon="show"
|
||||
tip="_ShowByProvince"
|
||||
on-clicked="onShowClick"/>
|
||||
<htk-column-text title="_Agency" column="Agencia"/>
|
||||
<htk-column-spin title="_Exps" column="expediciones"/>
|
||||
<htk-column-spin title="_Bundles" column="Bultos"/>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
ByProvince: Desglose per província
|
||||
Return: Tornar
|
||||
SelectAgency: Selecciona una agència al llistat de l'esquerra
|
||||
Province: Província
|
||||
Expeditions: Exps.
|
||||
Left: Falten
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue