Merge remote-tracking branch 'template/master' into develop
This commit is contained in:
commit
3c2d408428
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=WMS website
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=wms
|
||||
Group=wms
|
||||
WorkingDirectory=/home/wms/live
|
||||
Restart=on-success
|
||||
Environment=NODE_ENV=production
|
||||
ExecStart=/bin/node .
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,6 +1,8 @@
|
|||
import './external_links';
|
||||
import './message_icons';
|
||||
import './forms';
|
||||
import './copyable_text';
|
||||
import './main_menu';
|
||||
|
||||
import '../sass/app.scss';
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.copyable-text').forEach(el => {
|
||||
const contentEl = el.querySelector('.content');
|
||||
contentEl.addEventListener('click', () => {
|
||||
window.getSelection().selectAllChildren(contentEl);
|
||||
});
|
||||
el.querySelector('.copy-button').addEventListener('click', () => {
|
||||
window.getSelection().selectAllChildren(contentEl);
|
||||
document.execCommand('copy');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,4 +8,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
window.applyFormMessages = function (formElement, messages) {
|
||||
for (const fieldName of Object.keys(messages)) {
|
||||
const field = formElement.querySelector('#field-' + fieldName);
|
||||
let parent = field.parentElement;
|
||||
while (parent && !parent.classList.contains('form-field')) parent = parent.parentElement;
|
||||
|
||||
if (field) {
|
||||
let err = field.querySelector('.error');
|
||||
if (!err) {
|
||||
err = document.createElement('div');
|
||||
err.classList.add('error');
|
||||
parent.insertBefore(err, parent.querySelector('.hint') || parent);
|
||||
}
|
||||
err.innerHTML = `<i data-feather="x-circle"></i> ${messages[fieldName].message}`;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const menuButton = document.getElementById('menu-button');
|
||||
const mainMenu = document.getElementById('main-menu');
|
||||
|
||||
menuButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
mainMenu.classList.toggle('open');
|
||||
});
|
||||
|
||||
mainMenu.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
mainMenu.classList.remove('open');
|
||||
});
|
||||
});
|
|
@ -25,3 +25,6 @@ $warningColor: desaturate($warningText, 50%);
|
|||
$error: #ff0000;
|
||||
$errorText: darken($error, 30%);
|
||||
$errorColor: desaturate($errorText, 50%);
|
||||
|
||||
// Responsivity
|
||||
$menuLayoutSwitchTreshold: 700px;
|
||||
|
|
|
@ -51,32 +51,175 @@ header {
|
|||
}
|
||||
}
|
||||
|
||||
nav ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
nav {
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-size: 20px;
|
||||
font-size: 20px;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
li {
|
||||
list-style: none;
|
||||
|
||||
a, span {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
padding: 0 24px;
|
||||
a, button {
|
||||
position: relative;
|
||||
height: 64px;
|
||||
margin: 0;
|
||||
padding: 0 24px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&:hover, &:active {
|
||||
&:not(button) {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
}
|
||||
|
||||
.tip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity ease-out 100ms;
|
||||
transition-delay: 150ms;
|
||||
}
|
||||
}
|
||||
|
||||
.feather {
|
||||
--icon-size: 24px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.tip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity ease-out 100ms;
|
||||
transition-delay: 150ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 8px;
|
||||
padding: 24px;
|
||||
height: 32px;
|
||||
|
||||
.feather {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.tip {
|
||||
text-transform: initial;
|
||||
font-weight: initial;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#menu-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $menuLayoutSwitchTreshold) {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.logo {
|
||||
padding: 0 16px 0 8px;
|
||||
font-size: 24px;
|
||||
|
||||
img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
#menu-button {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
line-height: $headerHeight;
|
||||
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
|
||||
.feather {
|
||||
--icon-size: 24px;
|
||||
margin-right: 10px;
|
||||
--icon-size: 28px;
|
||||
margin: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
ul {
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
transform: translateX(-100%);
|
||||
transition: transform ease-out 150ms;
|
||||
|
||||
background-color: $headerBackground;
|
||||
|
||||
&.open {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
|
||||
li {
|
||||
a, button {
|
||||
.tip {
|
||||
display: block;
|
||||
margin-left: 8px;
|
||||
text-transform: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $menuLayoutSwitchTreshold) {
|
||||
nav ul li {
|
||||
a, button {
|
||||
.tip {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: max-content;
|
||||
height: 30px;
|
||||
padding: 4px 8px;
|
||||
line-height: 22px;
|
||||
top: calc(100% + 8px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
color: $defaultTextColor;
|
||||
opacity: 0;
|
||||
transition: opacity ease-out 100ms, visibility step-end 150ms;
|
||||
transition-delay: 0ms;
|
||||
background-color: #000;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
a, button {
|
||||
.tip {
|
||||
left: unset;
|
||||
right: 4px;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +337,7 @@ form {
|
|||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
|
||||
&:focus, &:not([value=""]) {
|
||||
&:focus, &:not([value=""]), &[type="file"] {
|
||||
~ label {
|
||||
top: 8px;
|
||||
font-size: 14px;
|
||||
|
@ -544,4 +687,68 @@ button, .button {
|
|||
margin: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copyable-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 8px;
|
||||
|
||||
background-color: darken($backgroundColor, 2%);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
|
||||
.feather {
|
||||
--icon-size: 20px;
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 8px;
|
||||
padding: 4px;
|
||||
background: #fff1;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: var(--progress);
|
||||
height: 100%;
|
||||
transition: width ease-out 150ms;
|
||||
|
||||
background: $secondary;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
export default Object.assign(require("wms-core/config/default").default, {
|
||||
app: {
|
||||
name: 'ALDAP',
|
||||
contact_email: 'contact@toot.party'
|
||||
},
|
||||
log_level: "DEV",
|
||||
db_log_level: "ERROR",
|
||||
public_url: "http://localhost:4899",
|
||||
public_websocket_url: "ws://localhost:4899",
|
||||
port: 4899,
|
||||
mysql: {
|
||||
connectionLimit: 10,
|
||||
|
@ -13,5 +21,25 @@ export default Object.assign(require("wms-core/config/default").default, {
|
|||
port: 6379,
|
||||
prefix: 'aldap'
|
||||
},
|
||||
session: {
|
||||
secret: "very_secret_not_known",
|
||||
cookie: {
|
||||
secure: false
|
||||
}
|
||||
},
|
||||
mail: {
|
||||
host: "127.0.0.1",
|
||||
port: "1025",
|
||||
secure: false,
|
||||
username: "",
|
||||
password: "",
|
||||
allow_invalid_tls: true,
|
||||
from: 'contact@example.net',
|
||||
from_name: 'Example App',
|
||||
},
|
||||
view: {
|
||||
cache: false
|
||||
},
|
||||
approval_mode: false,
|
||||
'prelaunch-password': '$argon2i$v=19$m=4096,t=3,p=1$V7njt+IBmIQ/epc7tuQcfA$ypJCNauYSPrjOhtb5UqTbRlqCHkEGikBApOrYmbdYC0',
|
||||
});
|
|
@ -1,3 +1,16 @@
|
|||
export default Object.assign(require("wms-core/config/production").default, {
|
||||
'prelaunch-password': 'CHANGE ME',
|
||||
log_level: "DEBUG",
|
||||
db_log_level: "ERROR",
|
||||
public_url: "https://watch-my.stream",
|
||||
public_websocket_url: "wss://watch-my.stream",
|
||||
session: {
|
||||
cookie: {
|
||||
secure: true
|
||||
}
|
||||
},
|
||||
mail: {
|
||||
secure: true,
|
||||
allow_invalid_tls: false
|
||||
}
|
||||
});
|
26
package.json
26
package.json
|
@ -10,7 +10,7 @@
|
|||
"test": "jest --verbose --runInBand",
|
||||
"dist-webpack": "webpack --mode production",
|
||||
"dist": "tsc && npm run dist-webpack",
|
||||
"dev": "concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon dist/main.js\" \"webpack --watch --mode development\" \"maildev\"",
|
||||
"dev": "concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon\" \"webpack --watch --mode development\" \"maildev\"",
|
||||
"start": "yarn dist && node dist/main.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -19,9 +19,15 @@
|
|||
"@types/argon2": "^0.15.0",
|
||||
"@types/config": "^0.0.36",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/express-session": "^1.17.0",
|
||||
"@types/formidable": "^1.0.31",
|
||||
"@types/jest": "^26.0.4",
|
||||
"@types/ldapjs": "^1.0.7",
|
||||
"@types/node": "^13.13.2",
|
||||
"@types/mysql": "^2.15.15",
|
||||
"@types/node": "^14.0.23",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
"@types/nunjucks": "^3.1.3",
|
||||
"@types/ws": "^7.2.6",
|
||||
"babel-loader": "^8.1.0",
|
||||
"concurrently": "^5.1.0",
|
||||
"css-loader": "^3.5.2",
|
||||
|
@ -29,16 +35,16 @@
|
|||
"file-loader": "^6.0.0",
|
||||
"imagemin": "^7.0.1",
|
||||
"imagemin-gifsicle": "^7.0.0",
|
||||
"imagemin-mozjpeg": "^8.0.0",
|
||||
"imagemin-pngquant": "^8.0.0",
|
||||
"imagemin-svgo": "^7.1.0",
|
||||
"imagemin-mozjpeg": "^9.0.0",
|
||||
"imagemin-pngquant": "^9.0.0",
|
||||
"imagemin-svgo": "^8.0.0",
|
||||
"img-loader": "^3.0.1",
|
||||
"jest": "^25.4.0",
|
||||
"jest": "^26.1.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.14.0",
|
||||
"nodemon": "^2.0.3",
|
||||
"sass-loader": "^8.0.2",
|
||||
"ts-jest": "^25.4.0",
|
||||
"sass-loader": "^9.0.2",
|
||||
"ts-jest": "^26.1.1",
|
||||
"typescript": "^3.8.3",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"webpack": "^4.43.0",
|
||||
|
@ -49,6 +55,6 @@
|
|||
"argon2": "^0.26.2",
|
||||
"config": "^3.3.1",
|
||||
"express": "^4.17.1",
|
||||
"ldapjs": "^1.0.2"
|
||||
"ldapjs": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import Application from "wms-core/Application";
|
||||
import {Type} from "wms-core/Utils";
|
||||
import Migration from "wms-core/db/Migration";
|
||||
import CreateMigrationsTable from "wms-core/migrations/CreateMigrationsTable";
|
||||
import CreateLogsTable from "wms-core/migrations/CreateLogsTable";
|
||||
import ExpressAppComponent from "wms-core/components/ExpressAppComponent";
|
||||
import NunjucksComponent from "wms-core/components/NunjucksComponent";
|
||||
import MysqlComponent from "wms-core/components/MysqlComponent";
|
||||
import LogRequestsComponent from "wms-core/components/LogRequestsComponent";
|
||||
import RedisComponent from "wms-core/components/RedisComponent";
|
||||
import ServeStaticDirectoryComponent from "wms-core/components/ServeStaticDirectoryComponent";
|
||||
import MaintenanceComponent from "wms-core/components/MaintenanceComponent";
|
||||
import MailComponent from "wms-core/components/MailComponent";
|
||||
import SessionComponent from "wms-core/components/SessionComponent";
|
||||
import RedirectBackComponent from "wms-core/components/RedirectBackComponent";
|
||||
import FormHelperComponent from "wms-core/components/FormHelperComponent";
|
||||
import CsrfProtectionComponent from "wms-core/components/CsrfProtectionComponent";
|
||||
import WebSocketServerComponent from "wms-core/components/WebSocketServerComponent";
|
||||
import HomeController from "./controllers/HomeController";
|
||||
import AutoUpdateComponent from "wms-core/components/AutoUpdateComponent";
|
||||
|
||||
export default class App extends Application {
|
||||
private readonly port: number;
|
||||
|
||||
constructor(port: number) {
|
||||
super(require('../package.json').version);
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
protected getMigrations(): Type<Migration>[] {
|
||||
return [
|
||||
CreateMigrationsTable,
|
||||
CreateLogsTable,
|
||||
];
|
||||
}
|
||||
|
||||
protected async init(): Promise<void> {
|
||||
this.registerComponents();
|
||||
this.registerWebSocketListeners();
|
||||
this.registerControllers();
|
||||
}
|
||||
|
||||
private registerComponents() {
|
||||
const redisComponent = new RedisComponent();
|
||||
const mysqlComponent = new MysqlComponent();
|
||||
|
||||
const expressAppComponent = new ExpressAppComponent(this.port);
|
||||
this.use(expressAppComponent);
|
||||
this.use(new NunjucksComponent());
|
||||
this.use(new LogRequestsComponent());
|
||||
|
||||
// Static files
|
||||
this.use(new ServeStaticDirectoryComponent('public'));
|
||||
this.use(new ServeStaticDirectoryComponent('node_modules/feather-icons/dist', '/icons'));
|
||||
|
||||
// Maintenance
|
||||
this.use(new MaintenanceComponent(this, () => {
|
||||
return redisComponent.canServe() && mysqlComponent.canServe();
|
||||
}));
|
||||
this.use(new AutoUpdateComponent());
|
||||
|
||||
// Services
|
||||
this.use(mysqlComponent);
|
||||
this.use(new MailComponent());
|
||||
|
||||
// Session
|
||||
this.use(redisComponent);
|
||||
this.use(new SessionComponent(redisComponent));
|
||||
|
||||
// Utils
|
||||
this.use(new RedirectBackComponent());
|
||||
this.use(new FormHelperComponent());
|
||||
|
||||
// Middlewares
|
||||
this.use(new CsrfProtectionComponent());
|
||||
|
||||
// WebSocket server
|
||||
this.use(new WebSocketServerComponent(this, expressAppComponent, redisComponent));
|
||||
}
|
||||
|
||||
private registerWebSocketListeners() {
|
||||
}
|
||||
|
||||
private registerControllers() {
|
||||
this.use(new HomeController());
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import Logger from "wms-core/Logger";
|
||||
import Aldap from "./Aldap";
|
||||
import App from "./App";
|
||||
import config from "config";
|
||||
|
||||
(async () => {
|
||||
const app = new Aldap(config.get<number>('port'));
|
||||
const app = new App(config.get<number>('port'));
|
||||
await app.start();
|
||||
})().catch(err => {
|
||||
Logger.error(err);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% extends 'layouts/base.njk' %}
|
||||
|
||||
{% set title = 'Example App - About us' %}
|
||||
{% set title = app.name + ' - About us' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>Very interesting</h1>
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -1,2 +0,0 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -1,2 +0,0 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -1,2 +0,0 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -1,2 +0,0 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -1,2 +0,0 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -1,2 +0,0 @@
|
|||
{% extends './error.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
|
@ -1,36 +0,0 @@
|
|||
{% extends '../layouts/barebone.njk' %}
|
||||
|
||||
{% set title = error_code + ' - ' + error_message %}
|
||||
|
||||
{% block _stylesheets %}
|
||||
<link rel="stylesheet" href="/css/error.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block _body %}
|
||||
<div class="logo"><a href="/">Example app</a></div>
|
||||
|
||||
<main class="{% block class %}{% endblock %}">
|
||||
{% if flash %}
|
||||
{{ macros.messages(flash) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="error-code">{{ error_code }}</div>
|
||||
<div class="error-message">{{ error_message }}</div>
|
||||
<div class="error-instructions">{{ error_instructions|safe }}</div>
|
||||
|
||||
<nav>
|
||||
{% if session.previousUrl and session.previousUrl != '/' and session.previousUrl != url %}
|
||||
<a href="{{ session.previousUrl }}" class="button"><i data-feather="arrow-left"></i> Go back</a>
|
||||
{% endif %}
|
||||
|
||||
<a href="/" class="button"><i data-feather="home"></i> Go to homepage</a>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<div class="contact">
|
||||
Error ID: {{ error_id }}
|
||||
<br>
|
||||
If you think this isn't right, please contact us with the above error ID at
|
||||
<a href="mailto:contact@example.net">contact@example.net</a>.
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,6 +1,6 @@
|
|||
{% extends 'layouts/base.njk' %}
|
||||
|
||||
{% set title = 'ALDAP - Welcome to the toot.party auth center!' %}
|
||||
{% set title = app.name + ' - Welcome to the toot.party auth center!' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{{ title }}</h1>
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="/img/logox1024.png">
|
||||
<link rel="shortcut icon" type="image/png" href="/img/logox128.png">
|
||||
<link rel="shortcut icon" type="image/svg" href="/img/logo.svg">
|
||||
|
||||
{% if description %}
|
||||
<meta name="description" content="{{ description }}">
|
||||
{% endif %}
|
||||
|
||||
{% if refresh_after %}
|
||||
<meta http-equiv="refresh" content="{{ refresh_after }}">
|
||||
{% endif %}
|
||||
|
||||
{% block _stylesheets %}{% endblock %}
|
||||
{% block _scripts %}
|
||||
<script src="/js/app.js" defer></script>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
</header>
|
||||
|
||||
{% block _body %}{% endblock %}
|
||||
|
||||
<footer>{% block footer %}{% endblock %}</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,4 +1,4 @@
|
|||
{% extends './barebone.njk' %}
|
||||
{% extends 'layouts/barebone.njk' %}
|
||||
{% import 'macros.njk' as macros %}
|
||||
|
||||
{% block _stylesheets %}
|
||||
|
@ -12,9 +12,10 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<a href="{{ route('home') }}" class="logo"><img src="/img/logo.svg" alt="Logo"> ALDAP</a>
|
||||
<a href="/" class="logo"><img src="/img/logo.svg" alt="Logo"> {{ app.name }}</a>
|
||||
<nav>
|
||||
<ul>
|
||||
<button id="menu-button"><i data-feather="menu"></i></button>
|
||||
<ul id="main-menu">
|
||||
<li><a href="{{ route('about') }}"><i data-feather="info"></i> About</a></li>
|
||||
{% if user %}
|
||||
<li><a href="{{ route('home') }}"><i data-feather="user"></i> {{ user.name }}</a></li>
|
||||
|
@ -23,6 +24,7 @@
|
|||
<li><a href="{{ route('login') }}"><i data-feather="log-in"></i> Login</a></li>
|
||||
<li><a href="{{ route('register') }}"><i data-feather="user-plus"></i> Register</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ route('about') }}"><i data-feather="info"></i> <span class="tip">About</span></a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
@ -44,4 +46,4 @@
|
|||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}ALDAP v{{ app_version }} - all rights reserved.{% endblock %}
|
||||
{% block footer %}{{ app.name }} v{{ app_version }} - all rights reserved.{% endblock %}
|
142
views/macros.njk
142
views/macros.njk
|
@ -1,142 +0,0 @@
|
|||
{% macro message(type, content, raw=false, discreet=false) %}
|
||||
<div class="message{{ ' message-discreet' if discreet }}" data-type="{{ type }}">
|
||||
<i class="icon"></i>
|
||||
<span class="content">
|
||||
{{ content|safe if raw else content }}
|
||||
</span>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro messages(flash) %}
|
||||
{% set flashed = flash() %}
|
||||
{% set display = 0 %}
|
||||
|
||||
{% for type, bag in flashed %}
|
||||
{% if bag|length %}
|
||||
{% set display = 1 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if display %}
|
||||
<div class="messages">
|
||||
{% for type, bag in flashed %}
|
||||
{% for content in bag %}
|
||||
{{ message(type, content) }}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro csrf(getCSRFToken) %}
|
||||
<input type="hidden" name="csrf" value="{{ getCSRFToken() }}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro field(_locals, type, name, value, placeholder, hint, validation_attributes='', extraData='') %}
|
||||
{% set validation = _locals.validation() %}
|
||||
{% set validation = validation[name] if validation[name] or null %}
|
||||
{% set previousFormData = _locals.previousFormData() %}
|
||||
{% set value = previousFormData[name] or value or validation.value or '' %}
|
||||
|
||||
{% if type == 'hidden' %}
|
||||
{% if validation %}
|
||||
{{ message('error', validation.message) }}
|
||||
{% endif %}
|
||||
<input type="hidden" name="{{ name }}" value="{{ value }}">
|
||||
{% else %}
|
||||
<div class="form-field{{ ' inline' if type == 'checkbox' }}">
|
||||
{% if type == 'duration' %}
|
||||
<div class="input-group">
|
||||
{% for f in extraData %}
|
||||
<div class="time-input">
|
||||
{% if previousFormData[name] %}
|
||||
{% set v = value[f] %}
|
||||
{% else %}
|
||||
{% set v = (value % 60) if f == 's' else (((value - value % 60) / 60 % 60) if f == 'm' else ((value - value % 3600) / 3600 if f == 'h')) %}
|
||||
{% endif %}
|
||||
<input type="number" name="{{ name }}[{{ f }}]" id="field-{{ name }}-{{ f }}"
|
||||
value="{{ v }}"
|
||||
min="0" {{ 'max=60' if (f == 's' or f == 'm') }}
|
||||
{{ validation_attributes }}>
|
||||
<label for="field-{{ name }}-{{ f }}">{{ f }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elseif type == 'select' %}
|
||||
<select name="{{ name }}" id="field-{{ name }}" {{ validation_attributes|safe }}>
|
||||
{% for option in extraData %}
|
||||
<option value="{{ option }}" {{ 'selected' if value == option }}>{{ option }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<i data-feather="chevron-down"></i>
|
||||
{% else %}
|
||||
<input type="{{ type }}" name="{{ name }}" id="field-{{ name }}"
|
||||
{% if type != 'checkbox' %} value="{{ value }}" {% endif %}
|
||||
{{ 'checked' if (type == 'checkbox' and value == 'on') }}
|
||||
{{ validation_attributes|safe }}>
|
||||
{% endif %}
|
||||
|
||||
<label for="field-{{ name }}{{ '-' + extraData[0] if type == 'duration' }}">{{ placeholder }}</label>
|
||||
{{ fieldError(_locals, name) }}
|
||||
{% if hint %}
|
||||
<div class="hint"><i data-feather="info"></i> {{ hint }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro fieldError(_locals, name) %}
|
||||
{% set validation = _locals.validation() %}
|
||||
{% set validation = validation[name] if validation[name] or null %}
|
||||
{% if validation %}
|
||||
<div class="error"><i data-feather="x-circle"></i> {{ validation.message }}</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro websocket(websocketUrl, listener, reconnectOnClose = 1, checkFunction = 0) %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
{% if checkFunction %}
|
||||
if (!{{ checkFunction }}()) return;
|
||||
{% endif %}
|
||||
|
||||
const run = () => {
|
||||
const websocket = new WebSocket('{{ websocketUrl }}');
|
||||
websocket.onopen = (e) => {
|
||||
console.debug('Websocket connected');
|
||||
};
|
||||
websocket.onmessage = (e) => {
|
||||
{{ listener }}(websocket, e);
|
||||
};
|
||||
websocket.onerror = (e) => {
|
||||
console.error('Websocket error', e);
|
||||
};
|
||||
websocket.onclose = (e) => {
|
||||
console.debug('Websocket closed', e.code, e.reason);
|
||||
|
||||
{% if reconnectOnClose %}
|
||||
setTimeout(run, 1000);
|
||||
{% endif %}
|
||||
};
|
||||
};
|
||||
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paginate(pagination, routeName) %}
|
||||
{% if pagination.hasPrevious() or pagination.hasNext() %}
|
||||
<div class="pagination">
|
||||
{% if pagination.hasPrevious() %}
|
||||
<a href="{{ route(routeName, {page: pagination.page - 1}) }}"><i data-feather="chevron-left"></i></a>
|
||||
{% endif %}
|
||||
|
||||
<span>{{ pagination.page }}</span>
|
||||
|
||||
{% if pagination.hasNext() %}
|
||||
<a href="{{ route(routeName, {page: pagination.page + 1}) }}"><i data-feather="chevron-right"></i></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
Loading…
Reference in New Issue