0
1
Fork 0

Compare commits

..

66 Commits

Author SHA1 Message Date
Juan Ferrer f7d58c8546 gitignore 2020-01-17 13:09:16 +01:00
Juan Ferrer Toribio 85bd397b83 Merge 2018-02-19 18:24:49 +01:00
Juan Ferrer Toribio 5d26b6cc3f Merge 2018-02-19 18:24:40 +01:00
Juan Ferrer Toribio 2ca8a09acd CSS class inherithed in components, bugs solved 2018-01-16 09:10:44 +01:00
Juan Ferrer Toribio 6666304332 Merge 2018-01-15 09:38:13 +01:00
Juan Ferrer Toribio 479ad20fa9 Webpack updated 2018-01-04 10:23:08 +01:00
Juan Ferrer Toribio 17d3a4457e Merge with master 2018-01-03 16:52:45 +01:00
Juan Ferrer Toribio e458987f67 Merge with master 2018-01-03 16:51:59 +01:00
Juan Ferrer Toribio cae37d5d6a Merge 2018-01-02 16:54:47 +01:00
Juan Ferrer Toribio a341f17ee5 Merge 2018-01-02 16:54:40 +01:00
Juan Ferrer Toribio 6829bb5a83 Merge with master 2017-11-29 11:29:08 +01:00
Juan Ferrer Toribio 2b9c28bc76 CSS bug 2017-11-27 16:51:40 +01:00
Juan Ferrer Toribio a5225fbb5c Merge with master 2017-11-27 16:41:08 +01:00
Juan Ferrer Toribio 42b5662b23 Merge with master 2017-11-24 12:38:07 +01:00
Juan Ferrer Toribio b9c14883c4 Backup 2017-11-23 17:06:28 +01:00
Juan Ferrer Toribio 7b54145da6 Refactor, Component improved 2017-11-22 13:25:19 +01:00
Juan Ferrer Toribio 23715816ce Tags Alpha v2 2017-11-21 12:50:55 +01:00
Juan Ferrer Toribio 857e46ce4d Tags alpha, bugs combo 2017-11-20 19:01:14 +01:00
Juan Ferrer Toribio ce167066f4 Refactor 2017-11-20 13:15:01 +01:00
Juan Ferrer Toribio 7658a5680d Tags, errores solucionados 2017-11-16 15:53:20 +01:00
Juan Ferrer Toribio f9c002f08a backup 2017-11-13 17:36:30 +01:00
Juan Ferrer Toribio 1cec1b4cd6 Backup 2017-11-06 16:00:14 +01:00
Juan Ferrer Toribio c3c7c77b65 Push test 2017-11-02 10:45:04 +01:00
Juan Ferrer Toribio 3aaf1298ee Various fixes 2017-11-02 09:23:55 +01:00
Juan Ferrer Toribio 37236b6bb0 Various fixes 2017-10-28 17:13:00 +02:00
Juan Ferrer Toribio fe68eb5ce0 Vn.Model v1 2017-10-25 09:47:32 +02:00
Juan Ferrer Toribio 4d8bd4ad95 Interpolate v2 2017-10-22 02:25:57 +02:00
Juan Ferrer Toribio f0dcd47baf Bugs solved 2017-10-20 19:45:11 +02:00
Juan Ferrer Toribio c1cf88e44d Bugs solved, refactor 2017-10-20 19:22:24 +02:00
Juan Ferrer Toribio 60a1d7cf9e interpolate v1, camelCase htmlId 2017-10-20 19:09:06 +02:00
Juan Ferrer Toribio 63158d335e New mark for translatable TextNode, bugs in builder solved, 4 spaces replaced by tabs 2017-10-20 16:24:49 +02:00
Juan Ferrer Toribio 2e9ea7ac33 Refactorización Vn.Builder 2017-10-20 14:06:16 +02:00
Juan Ferrer Toribio f6603ccc13 Backup 2017-10-18 18:01:21 +02:00
Juan Ferrer Toribio e178c74f25 Backup 2017-10-16 09:58:12 +02:00
Juan Ferrer Toribio 125686307f Backup 2017-10-10 13:58:25 +02:00
Juan Ferrer Toribio a8b6dd4c1a Backup 2017-09-12 13:31:15 +02:00
Juan Ferrer Toribio b8c83df08f Backup 2017-08-21 12:20:36 +02:00
Juan Ferrer Toribio fab5253406 Merge 2017-07-14 07:37:10 +02:00
Juan Ferrer Toribio c1175122d0 Backup 2017-07-05 11:50:42 +02:00
Juan Ferrer Toribio 942772ec03 Errores merge solucionados 2017-07-04 09:13:06 +02:00
Juan Ferrer Toribio 13e45afc77 Merge with master 2017-07-04 08:51:41 +02:00
Juan Ferrer Toribio d327be4780 Backup 2017-05-22 09:08:21 +02:00
Juan Ferrer Toribio ff0b899fa2 Errores solucionados 2017-05-11 17:38:31 +02:00
Juan Ferrer Toribio 06bd47bf91 Merge with master 2017-05-10 08:38:26 +02:00
Juan Ferrer Toribio 45f5cc3deb Merge with master 2017-05-09 15:33:02 +02:00
Juan Ferrer Toribio 25e9b37b75 Merge con master 2017-05-02 15:48:47 +02:00
Juan Ferrer Toribio b7bdef4bf9 Merge with master fixes 2017-04-27 10:39:37 +02:00
Juan Ferrer Toribio 28b2302d78 Backup 2017-04-25 19:56:39 +02:00
Juan Ferrer Toribio 1e43aead46 backup 2017-04-24 09:47:56 +02:00
Juan Ferrer Toribio adb651fe69 Backup 2017-04-21 12:53:15 +02:00
Juan Ferrer Toribio 234566b6af Backup 2017-04-19 08:16:37 +02:00
Juan Ferrer Toribio 73b2fed910 Backup 2017-04-10 18:23:40 +02:00
Juan Ferrer Toribio 22ee7e5020 Backup 2017-04-10 17:17:56 +02:00
Juan Ferrer Toribio 403845bf2b Backup 2017-04-08 13:42:27 +02:00
Juan Ferrer Toribio 0be33631ca Backup 2017-04-07 13:00:33 +02:00
Juan Ferrer Toribio 944a955068 Backup 2017-04-05 16:06:07 +02:00
Juan Ferrer Toribio efdde01e19 Backup 2017-03-30 13:44:53 +02:00
Juan Ferrer Toribio a5854290e9 Backup 2017-03-23 17:20:51 +01:00
Juan Ferrer Toribio 5640951c80 Backup 2017-03-22 17:57:21 +01:00
Juan Ferrer Toribio 86a19925d8 Backup 2017-03-17 13:42:10 +01:00
Juan Ferrer Toribio 043c11a4ed backup 2017-03-09 13:30:39 +01:00
Juan Ferrer Toribio b7ec06ffdc Webpack caching 2017-03-02 11:01:29 +01:00
Juan Ferrer Toribio 1a5bcf2bd2 Deteccion idioma del navedor mejorada 2017-01-18 13:11:23 +01:00
Juan Ferrer Toribio 969f58981e Estilo beta 2017-01-18 12:50:07 +01:00
Juan Ferrer Toribio 2c6c51865f Estilos CSS depurados 2016-12-23 09:57:49 +01:00
Juan Ferrer Toribio 63d75a7ff6 Js doc asteriscos 2016-12-20 10:32:17 +01:00
734 changed files with 21322 additions and 52964 deletions

View File

@ -1,2 +0,0 @@
debian
node_modules

View File

@ -1,9 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -1,8 +0,0 @@
/dist
/src-bex/www
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.js
babel.config.js

View File

@ -1,82 +0,0 @@
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
parserOptions: {
parser: '@babel/eslint-parser',
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module' // Allows for the use of imports
},
env: {
browser: true,
'vue/setup-compiler-macros': true
},
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
'standard'
],
plugins: ['vue', 'prettier'],
globals: {
ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly'
},
// add your custom rules here
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow paren-less arrow functions
'arrow-parens': 'off',
'one-var': 'off',
'no-void': 'off',
'multiline-ternary': 'off',
'import/first': 'off',
'import/named': 'error',
'import/namespace': 'error',
'import/default': 'error',
'import/export': 'error',
'import/extensions': 'off',
'import/no-unresolved': 'off',
'import/no-extraneous-dependencies': 'off',
'prefer-promise-reject-errors': 'off',
semi: 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
overrides: [
{
extends: ['plugin:vue/vue3-essential'],
files: ['src/**/*.{js,vue,scss}'], // Aplica ESLint solo a archivos .js, .vue y .scss dentro de src (Proyecto de quasar)
rules: {
semi: 'off',
indent: ['error', 4, { SwitchCase: 1 }],
'space-before-function-paren': 'off'
}
}
]
};

11
.eslintrc.yml Normal file
View File

@ -0,0 +1,11 @@
extends: eslint:recommended
parserOptions:
ecmaVersion: 2017
sourceType: module
rules:
no-undef: 0
no-redeclare: 0
no-mixed-spaces-and-tabs: 0
no-console: 0
no-cond-assign: 0
no-unexpected-multiline: 0

36
.gitignore vendored
View File

@ -1,36 +1,4 @@
node_modules
build/
config.my.php
.DS_Store
.thumbs.db
node_modules
# Quasar core related directories
.quasar
/dist
# Cordova related directories and files
/src-cordova/node_modules
/src-cordova/platforms
/src-cordova/plugins
/src-cordova/www
# Capacitor related directories and files
/src-capacitor/www
/src-capacitor/node_modules
# BEX related directories and files
/src-bex/www
/src-bex/js/core
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
.vscode/

3
.npmrc
View File

@ -1,3 +0,0 @@
# pnpm-related options
shamefully-hoist=true
strict-peer-dependencies=false

View File

@ -1,9 +0,0 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// to edit target browsers: use "browserslist" field in package.json
require('autoprefixer')
]
}

View File

@ -1,9 +0,0 @@
module.exports = {
printWidth: 80,
tabWidth: 4,
useTabs: false,
singleQuote: true,
bracketSpacing: true,
arrowParens: 'avoid',
trailingComma: 'none'
};

View File

@ -1,14 +0,0 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"vue.volar",
"wayou.vscode-todo-highlight"
],
"unwantedRecommendations": [
"octref.vetur",
"hookyqr.beautify",
"dbaeumer.jshint",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

22
.vscode/launch.json vendored
View File

@ -1,22 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for XDebug",
"type": "php",
"request": "launch",
"port": 9000
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 9000
}
]
}

10
.vscode/settings.json vendored
View File

@ -1,10 +0,0 @@
{
"files.eol": "\n",
"eslint.autoFixOnSave": true,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"]
}

View File

@ -1,52 +0,0 @@
# Not using buster because of bug: https://bugs.php.net/bug.php?id=78870
FROM debian:stretch-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_14.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/ stretch main" \
> /etc/apt/sources.list.d/vn.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
php-image-text \
php-text-captcha \
php-apcu \
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"]

102
Jenkinsfile vendored
View File

@ -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()
}
}
}

View File

@ -1,35 +1,3 @@
# Hedera
Hedera is the main web page for Verdnatura.
## Getting Started
Required dependencies.
- PHP >= 7.0
- Node.js >= 8.0
Launch application for development.
```
$ npm run dev
```
Launch project backend.
```
$ php -S 127.0.0.1:3002 -t . index.php
Run server side method from command line.
```
$ php hedera-web.php -m method_path
```
## Built with
* [Webpack](https://webpack.js.org/)
* [MooTools](https://mootools.net/)
* [TinyMCE](https://www.tinymce.com/)
```
Hedera is the main page for Verdnatura.

84
app.js
View File

@ -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]));
}

View File

@ -1,14 +0,0 @@
/* eslint-disable */
module.exports = api => {
return {
presets: [
[
'@quasar/babel-preset-app',
api.caller(caller => caller && caller.target === 'node')
? { targets: { node: 'current' } }
: {}
]
]
}
}

View File

@ -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>

View File

@ -14,15 +14,16 @@
* - http://test.mydomain.org -> config.test.php
*/
return [
/**
* Database parameters.
*/
'db' => [
'host' => 'localhost'
,'port' => 3306
,'schema' => 'hedera'
,'user' => 'hedera-web'
,'pass' => ''
,'tz' => 'Europe/madrid'
]
/**
* Database parameters.
*/
'db' => [
'host' => 'localhost'
,'port' => 3306
,'schema' => 'hedera-web'
,'user' => 'hedera-web'
,'pass' => ''
]
];

2
debian/changelog vendored
View File

@ -1,4 +1,4 @@
hedera-web (22.48.2) stable; urgency=low
hedera-web (2.0.4) stable; urgency=low
* Initial Release.

2
debian/compat vendored
View File

@ -1 +1 @@
10
9

6
debian/control vendored
View File

@ -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-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

2
debian/copyright vendored
View File

@ -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>

15
debian/cron.d vendored
View File

@ -1,10 +1,7 @@
MAILTO=webmaster
*/1 * * * * root hedera-web.php -m misc/mail
*/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
0 5 * * * root hedera-web.php -m misc/exchange-rate
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

7
debian/install vendored
View File

@ -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

3
debian/links vendored
View File

@ -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/7.0/apache2/conf.d/99-hedera-web.ini
usr/share/hedera-web/hedera-web.php usr/bin/hedera-web.php

9
debian/postinst vendored
View File

@ -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

4
debian/postrm vendored
View File

@ -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

6
debian/rules vendored
View File

@ -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)

View File

@ -1 +0,0 @@
hedera-web: package-contains-npm-ignore-file

View File

@ -1,41 +0,0 @@
version: '3.7'
services:
main:
image: registry.verdnatura.es/hedera-web:${BRANCH_NAME:?}
build:
context: .
dockerfile: Dockerfile
args:
- VERSION=${VERSION:?}
ports:
- 80
configs:
- source: config
target: /etc/hedera-web/config.my.php
volumes:
- /mnt/appdata:/mnt/storage
- /mnt/appdata/image:/var/lib/hedera-web/image-db
- /mnt/appdata/vn-access:/var/lib/hedera-web/vn-access
deploy:
replicas: ${MAIN_REPLICAS:?}
placement:
constraints:
- node.role == worker
cron:
image: registry.verdnatura.es/hedera-web:${BRANCH_NAME:?}
command: 'cron -f'
configs:
- source: config
target: /etc/hedera-web/config.my.php
volumes:
- /mnt/appdata:/mnt/storage
- /mnt/appdata/image:/var/lib/hedera-web/image-db
deploy:
replicas: ${CRON_REPLICAS:?}
placement:
constraints:
- node.role == worker
configs:
config:
external: true
name: ${PROJECT_NAME:?}-${BRANCH_NAME:?}

View File

@ -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';

View File

@ -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 ();
}
}
});

View File

@ -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
});
}
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@

View File

@ -1,6 +0,0 @@
hedera-address-list {
.htk-list .side {
padding-right: 16px;
}
}

View File

@ -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&amp;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>

View File

@ -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();
}
});

View File

@ -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()
}
});

View File

@ -0,0 +1 @@

View File

@ -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, 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>

View File

@ -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'});
}
});

View File

@ -1,76 +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');
if (this.hash.$.verificationToken)
this.onPassChangeClick();
}
,onPassChangeClick() {
this.$.oldPassword.value = '';
this.$.newPassword.value = '';
this.$.repeatPassword.value = '';
var verificationToken = this.hash.$.verificationToken;
this.$.oldPassword.style.display = verificationToken ? 'none' : 'block';
this.$.changePassword.show();
if (verificationToken)
this.$.newPassword.focus();
else
this.$.oldPassword.focus();
}
,async onPassModifyClick() {
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 verificationToken = this.hash.$.verificationToken;
var params = {newPassword};
let err;
try {
if (verificationToken) {
params.verificationToken = verificationToken;
await this.conn.send('user/restore-password', params);
} else {
let userId = this.gui.user.id;
params.oldPassword = oldPassword;
await this.conn.patch(
`Accounts/${userId}/changePassword`, params);
}
} catch(e) {
err = e;
Htk.Toast.showError(err.message);
if (this.hash.$.verificationToken)
this.$.newPassword.select();
else
this.$.oldPassword.select();
return;
}
this.$.changePassword.hide();
this.hash.unset('verificationToken');
Htk.Toast.showMessage(_('Password changed!'));
this.$.userForm.refresh();
}
,onPassInfoClick() {
this.$.passwordInfo.show();
}
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -1,4 +0,0 @@
.pass-info ul {
list-style-type: none;
}

View File

@ -1,87 +1,70 @@
<vn>
<vn-group>
<db-form v-model="passwordForm">
<db-model property="model">
SELECT length, nAlpha, nUpper, nDigits, nPunct
FROM account.userPassword
</db-model>
</db-form>
<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.onPassChangeClick()"/>
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-popup
id="change-password"
on-open="onChangePassOpen"
on-closed="onChangePassClose"
modal="true">
<div property="child-node" class="htk-dialog vn-w-xs vn-pa-lg">
<div class="form">
<h5 class="vn-mb-md">
<t>Change password</t>
</h5>
<div property="child-node" class="htk-dialog pass-change">
<div>
<input
id="old-password"
type="password"
@ -96,11 +79,11 @@
placeholder="_Repeat password"/>
</div>
<div class="button-bar">
<button class="thin" on-click="this.onPassModifyClick()">
<t>Modify</t>
<button class="thin" on-click="onPassModifyClick">
_Modify
</button>
<button class="thin" on-click="this.onPassInfoClick()">
<t>Requirements</t>
<button class="thin" on-click="onPassInfoClick">
_Info
</button>
<div class="clear"/>
</div>
@ -109,25 +92,25 @@
<htk-popup
id="password-info"
modal="true">
<div property="child-node" class="htk-dialog pass-info vn-w-xs vn-pa-lg">
<h5 class="vn-mb-md">
<t>Password requirements</t>
</h5>
<div property="child-node" class="htk-dialog pass-info">
<h3>
_Password requirements
</h3>
<ul>
<li>
{{passwordForm.length}} <t>characters long</t>
{{passwordForm.length}} <span>_characters long</span>
</li>
<li>
{{passwordForm.nAlpha}} <t>alphabetic characters</t>
{{passwordForm.nAlpha}} <span>_alphabetic characters</span>
</li>
<li>
{{passwordForm.nUpper}} <t>capital letters</t>
{{passwordForm.nUpper}} <span>_capital letters</span>
</li>
<li>
{{passwordForm.nDigits}} <t>digits</t>
{{passwordForm.nDigits}} <span>_digits</span>
</li>
<li>
{{passwordForm.nPunct}} <t>symbols</t>
{{passwordForm.nPunct}} <span>_symbols</span>
</li>
</ul>
</div>

View File

@ -0,0 +1,6 @@
Hedera.AccessLog = new Class
({
Extends: Hedera.Form
});

View File

@ -1,7 +0,0 @@
import './style.scss';
export default new Class({
Extends: Hedera.Form,
Template: require('./ui.xml')
});

View File

@ -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

View File

@ -0,0 +1,12 @@
.access-log .card
{
max-width: 28em;
}
.access-log .form > p
{
font-size: 1.2em;
margin: .1em 0;
}

View File

@ -1,12 +0,0 @@
.access-log .form > p {
font-size: 1.2rem;
margin: .1em 0;
}
/* List */
.access-log .htk-list {
margin-top: 16px;
}

View File

@ -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>

View File

@ -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;
}
});

View File

@ -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;
}
});

View File

@ -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;
}

View File

@ -1,9 +0,0 @@
.action-bar .connections-sum {
padding: .4em;
background-color: #1e88e5;
border-radius: .1em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}

View File

@ -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&amp;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&amp;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>

View File

@ -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']);
}
});

View File

@ -0,0 +1,6 @@
Hedera.Items = new Class
({
Extends: Hedera.Form
});

View File

@ -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;
}

View File

@ -1,6 +0,0 @@
.items .item .photo {
border-radius: 10px;
height: 80px;
width: 80px;
}

View File

@ -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>

View File

@ -1,7 +0,0 @@
import './style.scss';
export default new Class({
Extends: Hedera.Form,
Template: require('./ui.xml')
});

View File

@ -0,0 +1,6 @@
Hedera.Links = new Class
({
Extends: Hedera.Form
});

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}
})();

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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;
}
});

View File

@ -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;
}
});

View File

@ -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;
}

View File

@ -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>

View File

@ -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'});
}
});

View File

@ -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;
}

View File

@ -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&amp;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&amp;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>

View File

@ -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'});
}
});

View File

@ -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: new Date(),
to: new Date()
});
}
});

View File

@ -0,0 +1,13 @@
.visits .card
{
max-width: 28em;
}
.visits .summary p
{
margin: 0;
font-size: 1.4em;
text-align: right;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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'});
}
});

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