Compare commits
6 Commits
5a583a94ff
...
master
Author | SHA1 | Date | |
---|---|---|---|
41615303db | |||
06767268de | |||
3c03cc133c | |||
8bbadf36a8 | |||
634f05bc27 | |||
64d317115c |
29346
docs/index.html
29346
docs/index.html
File diff suppressed because one or more lines are too long
@ -1,98 +0,0 @@
|
|||||||
//// ------------------------------------------------------
|
|
||||||
//// THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
|
||||||
//// ------------------------------------------------------
|
|
||||||
|
|
||||||
Project "ATAS" {
|
|
||||||
database_type: ''
|
|
||||||
Note: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
Table alerts {
|
|
||||||
id Int [pk, increment]
|
|
||||||
type alertType [not null]
|
|
||||||
state alertState [not null]
|
|
||||||
description String
|
|
||||||
date DateTime [not null]
|
|
||||||
actionplan actionPlan
|
|
||||||
actionplanId Int
|
|
||||||
acknowledged_by alertContacts [not null]
|
|
||||||
acknowledged_at DateTime
|
|
||||||
}
|
|
||||||
|
|
||||||
Table alertContacts {
|
|
||||||
id Int [pk, increment]
|
|
||||||
name String [not null]
|
|
||||||
phone String [unique, not null]
|
|
||||||
comment String
|
|
||||||
prios priorities [not null]
|
|
||||||
alerts alerts [not null]
|
|
||||||
}
|
|
||||||
|
|
||||||
Table actionPlan {
|
|
||||||
id Int [pk, increment]
|
|
||||||
name String [unique, not null]
|
|
||||||
comment String
|
|
||||||
alerthook String [unique, not null]
|
|
||||||
prio priorities [not null]
|
|
||||||
content content [not null]
|
|
||||||
alerts alerts [not null]
|
|
||||||
}
|
|
||||||
|
|
||||||
Table priorities {
|
|
||||||
id Int [pk, increment]
|
|
||||||
Contact alertContacts [not null]
|
|
||||||
contactId Int [not null]
|
|
||||||
priority Int [not null]
|
|
||||||
actionplan actionPlan [not null]
|
|
||||||
actionplanId Int [not null]
|
|
||||||
|
|
||||||
indexes {
|
|
||||||
(priority, actionplanId) [unique]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Table content {
|
|
||||||
id Int [pk, increment]
|
|
||||||
type contentType [not null]
|
|
||||||
name String [not null]
|
|
||||||
filename String [not null]
|
|
||||||
actionplan actionPlan [not null]
|
|
||||||
}
|
|
||||||
|
|
||||||
Table alertContactsToalerts {
|
|
||||||
acknowledged_byId Int [ref: > alertContacts.id]
|
|
||||||
alertsId Int [ref: > alerts.id]
|
|
||||||
}
|
|
||||||
|
|
||||||
Table actionPlanTocontent {
|
|
||||||
contentId Int [ref: > content.id]
|
|
||||||
actionplanId Int [ref: > actionPlan.id]
|
|
||||||
}
|
|
||||||
|
|
||||||
Enum contentType {
|
|
||||||
voice_alarm
|
|
||||||
voice_explainer
|
|
||||||
voice_acknowledgement
|
|
||||||
voice_ending
|
|
||||||
}
|
|
||||||
|
|
||||||
Enum alertType {
|
|
||||||
generic
|
|
||||||
fire
|
|
||||||
fault
|
|
||||||
intrusion
|
|
||||||
clear
|
|
||||||
}
|
|
||||||
|
|
||||||
Enum alertState {
|
|
||||||
incoming
|
|
||||||
running
|
|
||||||
failed
|
|
||||||
acknowledged
|
|
||||||
}
|
|
||||||
|
|
||||||
Ref: alerts.actionplanId > actionPlan.id
|
|
||||||
|
|
||||||
Ref: priorities.contactId > alertContacts.id
|
|
||||||
|
|
||||||
Ref: priorities.actionplanId > actionPlan.id
|
|
File diff suppressed because one or more lines are too long
3708
package-lock.json
generated
3708
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -33,8 +33,6 @@
|
|||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"prisma": "^6.2.1",
|
"prisma": "^6.2.1",
|
||||||
"prisma-dbml-generator": "^0.12.0",
|
|
||||||
"prisma-docs-generator": "^0.8.0",
|
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
},
|
},
|
||||||
@ -46,9 +44,11 @@
|
|||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"express-fileupload": "^1.5.1",
|
"express-fileupload": "^1.5.1",
|
||||||
"express-session": "^1.18.1",
|
"express-session": "^1.18.1",
|
||||||
|
"helmet": "^8.0.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"minio": "^8.0.4",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"signale": "^1.4.0",
|
"signale": "^1.4.0",
|
||||||
|
@ -13,23 +13,6 @@ datasource db {
|
|||||||
url = env("DATABASE_URL")
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// https://github.com/pantharshit00/prisma-docs-generator
|
|
||||||
generator docs {
|
|
||||||
provider = "node node_modules/prisma-docs-generator"
|
|
||||||
output = "../docs"
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/notiz-dev/prisma-dbml-generator
|
|
||||||
// Viewer: https://dbdiagram.io/d
|
|
||||||
generator dbml {
|
|
||||||
provider = "prisma-dbml-generator"
|
|
||||||
output = "../docs"
|
|
||||||
outputName = "schema.dbml"
|
|
||||||
projectName = "ATAS"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
enum contentType {
|
enum contentType {
|
||||||
voice_alarm
|
voice_alarm
|
||||||
voice_explainer
|
voice_explainer
|
||||||
@ -84,7 +67,7 @@ model actionPlan {
|
|||||||
id Int @id @unique @default(autoincrement())
|
id Int @id @unique @default(autoincrement())
|
||||||
name String @unique
|
name String @unique
|
||||||
comment String?
|
comment String?
|
||||||
alerthook String @unique
|
alerthook String @unique @default(ulid())
|
||||||
prio priorities[]
|
prio priorities[]
|
||||||
content content[] // aka. all voice files
|
content content[] // aka. all voice files
|
||||||
|
|
||||||
@ -105,13 +88,13 @@ model priorities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model content {
|
model content {
|
||||||
id Int @id @unique @default(autoincrement())
|
//id Int @id @unique @default(autoincrement())
|
||||||
|
s3_key String @id @unique
|
||||||
|
name String @unique
|
||||||
type contentType
|
type contentType
|
||||||
name String
|
|
||||||
filename String
|
|
||||||
actionplan actionPlan[]
|
actionplan actionPlan[]
|
||||||
|
|
||||||
@@fulltext([name, filename])
|
@@fulltext([name])
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://spacecdn.de/file/bma_stoe_v1.mp3
|
// https://spacecdn.de/file/bma_stoe_v1.mp3
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import ConfigManager from '../libs/configManager.js';
|
import ConfigManager from '../libs/configManager.js';
|
||||||
import __path from "./path.js";
|
import __path from './path.js';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import log from './log.js';
|
||||||
|
|
||||||
// Create a new config instance.
|
// Create a new config instance.
|
||||||
const config = new ConfigManager(__path + '/config.json', true, {
|
const config = new ConfigManager(__path + '/config.json', true, {
|
||||||
db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
db_connection_string: 'mysql://USER:PASSWORD@HOST:3306/DATABASE',
|
||||||
http_listen_address: '0.0.0.0',
|
http_listen_address: '0.0.0.0',
|
||||||
http_port: 3000,
|
http_port: 3000,
|
||||||
debug: true,
|
http_domain: 'example.org',
|
||||||
|
http_enable_hsts: false,
|
||||||
|
devmode: true,
|
||||||
|
s3: {
|
||||||
|
endpoint: 'minio.example.org',
|
||||||
|
port: 443,
|
||||||
|
use_ssl: true,
|
||||||
|
access_key: '',
|
||||||
|
secret_key: ''
|
||||||
|
},
|
||||||
auth: {
|
auth: {
|
||||||
cookie_secret: 'gen',
|
cookie_secret: 'gen',
|
||||||
cookie_secure: true,
|
cookie_secure: true,
|
||||||
@ -25,9 +34,11 @@ const config = new ConfigManager(__path + '/config.json', true, {
|
|||||||
// If no local User exists, create the default with a generated password
|
// If no local User exists, create the default with a generated password
|
||||||
if (_.isEqual(config.global.auth.local.users, {})) {
|
if (_.isEqual(config.global.auth.local.users, {})) {
|
||||||
config.global.auth.local.users = {
|
config.global.auth.local.users = {
|
||||||
'administrator': 'gen',
|
administrator: 'gen'
|
||||||
};
|
};
|
||||||
config.save_config();
|
config.save_config();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
!config.global.devmode && log.core.error('devmode active! Do NOT use this in prod!');
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@ -14,6 +14,7 @@ type log = {
|
|||||||
core: Logger<unknown>
|
core: Logger<unknown>
|
||||||
db: Logger<unknown>
|
db: Logger<unknown>
|
||||||
web: Logger<unknown>
|
web: Logger<unknown>
|
||||||
|
S3: Logger<unknown>
|
||||||
auth: Logger<unknown>
|
auth: Logger<unknown>
|
||||||
api?: Logger<unknown>
|
api?: Logger<unknown>
|
||||||
frontend?: Logger<unknown>
|
frontend?: Logger<unknown>
|
||||||
@ -24,6 +25,7 @@ let log: log = {
|
|||||||
core: new Logger(loggerConfig("Core")),
|
core: new Logger(loggerConfig("Core")),
|
||||||
db: new Logger(loggerConfig("DB")),
|
db: new Logger(loggerConfig("DB")),
|
||||||
web: new Logger(loggerConfig("Web")),
|
web: new Logger(loggerConfig("Web")),
|
||||||
|
S3: new Logger(loggerConfig("S3")),
|
||||||
auth: new Logger(loggerConfig("Auth")),
|
auth: new Logger(loggerConfig("Auth")),
|
||||||
// helper: new Logger(loggerConfig("HELPER")),
|
// helper: new Logger(loggerConfig("HELPER")),
|
||||||
};
|
};
|
||||||
|
19
src/handlers/s3.ts
Normal file
19
src/handlers/s3.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as Minio from 'minio';
|
||||||
|
import log from './log.js';
|
||||||
|
import config from './config.js';
|
||||||
|
|
||||||
|
const minioClient = new Minio.Client({
|
||||||
|
endPoint: config.global.s3.endpoint,
|
||||||
|
port: config.global.s3.port,
|
||||||
|
useSSL: config.global.s3.use_ssl,
|
||||||
|
accessKey: config.global.s3.access_key,
|
||||||
|
secretKey: config.global.s3.secret_key
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function test() {
|
||||||
|
log.S3.debug('GET', await minioClient.presignedGetObject('atas-dev', 'test', 500));
|
||||||
|
|
||||||
|
log.S3.debug('PUT', await minioClient.presignedPutObject('atas-dev', 'test', 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default minioClient;
|
61
src/index.ts
61
src/index.ts
@ -1,17 +1,18 @@
|
|||||||
// MARK: Imports
|
// MARK: Imports
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import __path from "./handlers/path.js";
|
import __path from './handlers/path.js';
|
||||||
import log from "./handlers/log.js";
|
import log from './handlers/log.js';
|
||||||
import db from "./handlers/db.js";
|
import db from './handlers/db.js';
|
||||||
import config from './handlers/config.js';
|
import config from './handlers/config.js';
|
||||||
|
|
||||||
// Express & more
|
// Express & more
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import cors from 'cors'
|
import cors from 'cors';
|
||||||
|
import helmet from 'helmet';
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import fileUpload from 'express-fileupload';
|
import fileUpload from 'express-fileupload';
|
||||||
import bodyParser, { Options } from 'body-parser';
|
import bodyParser, { Options } from 'body-parser';
|
||||||
import { Eta } from "eta";
|
import { Eta } from 'eta';
|
||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
|
|
||||||
import ChildProcess from 'child_process';
|
import ChildProcess from 'child_process';
|
||||||
@ -20,28 +21,26 @@ import routes from './routes/index.js';
|
|||||||
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
|
||||||
log.core.trace("Running from path: " + __path);
|
log.core.trace('Running from path: ' + __path);
|
||||||
|
|
||||||
|
|
||||||
// MARK: Express
|
// MARK: Express
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// Versioning
|
// Versioning
|
||||||
try {
|
try {
|
||||||
const rawPkg = fs.readFileSync("package.json", 'utf8');
|
const rawPkg = fs.readFileSync('package.json', 'utf8');
|
||||||
const pkgJson = JSON.parse(rawPkg);
|
const pkgJson = JSON.parse(rawPkg);
|
||||||
app.locals.version = pkgJson.version;
|
app.locals.version = pkgJson.version;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.core.error("Failed to get version from package.json.");
|
log.core.error('Failed to get version from package.json.');
|
||||||
app.locals.version = "0.0.0";
|
app.locals.version = '0.0.0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
try {
|
|
||||||
app.locals.versionRevLong = ChildProcess.execSync('git rev-parse HEAD').toString().trim();
|
app.locals.versionRevLong = ChildProcess.execSync('git rev-parse HEAD').toString().trim();
|
||||||
app.locals.versionRev = app.locals.versionRevLong.substring(0, 7);
|
app.locals.versionRev = app.locals.versionRevLong.substring(0, 7);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.core.error("Failed to get git revision hash.");
|
log.core.error('Failed to get git revision hash.');
|
||||||
app.locals.versionRev = '0';
|
app.locals.versionRev = '0';
|
||||||
app.locals.versionRevLong = '0';
|
app.locals.versionRevLong = '0';
|
||||||
}
|
}
|
||||||
@ -49,7 +48,7 @@ try {
|
|||||||
try {
|
try {
|
||||||
app.locals.versionRevLatest = ChildProcess.execSync('git ls-remote --refs -q').toString().trim().split('\t')[0];
|
app.locals.versionRevLatest = ChildProcess.execSync('git ls-remote --refs -q').toString().trim().split('\t')[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.core.error("Failed to get latest git revision hash.");
|
log.core.error('Failed to get latest git revision hash.');
|
||||||
app.locals.versionRevLatest = '0';
|
app.locals.versionRevLatest = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,19 +60,31 @@ if (app.locals.versionRevLong === app.locals.versionRevLatest) {
|
|||||||
app.locals.versionUpdateAvailable = true;
|
app.locals.versionUpdateAvailable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ETA Init
|
// ETA Init
|
||||||
const eta = new Eta({ views: path.join(__path, "views") })
|
const eta = new Eta({ views: path.join(__path, 'views') });
|
||||||
app.engine("eta", buildEtaEngine())
|
app.engine('eta', buildEtaEngine());
|
||||||
app.set("view engine", "eta")
|
app.set('view engine', 'eta');
|
||||||
|
|
||||||
|
|
||||||
// MARK: Express Middleware & Config
|
// MARK: Express Middleware & Config
|
||||||
app.set('x-powered-by', false);
|
app.set('x-powered-by', false); // helmet does this too. But not in devmode
|
||||||
|
if (!config.global.devmode) {
|
||||||
|
app.use(
|
||||||
|
helmet({
|
||||||
|
strictTransportSecurity: config.global.http_enable_hsts,
|
||||||
|
contentSecurityPolicy: {
|
||||||
|
useDefaults: false,
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'", config.global.http_domain],
|
||||||
|
objectSrc: ["'none'"],
|
||||||
|
upgradeInsecureRequests: config.global.devmode ? null : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
); // Add headers
|
||||||
|
}
|
||||||
|
|
||||||
app.use(fileUpload());
|
app.use(fileUpload());
|
||||||
//app.use(cors());
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
@ -90,16 +101,13 @@ app.use(
|
|||||||
app.use(passport.authenticate('session'));
|
app.use(passport.authenticate('session'));
|
||||||
app.use(routes);
|
app.use(routes);
|
||||||
|
|
||||||
|
|
||||||
app.listen(config.global.http_port, config.global.http_listen_address, () => {
|
app.listen(config.global.http_port, config.global.http_listen_address, () => {
|
||||||
log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`);
|
log.web.info(`Listening at http://${config.global.http_listen_address}:${config.global.http_port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Helper Functions
|
// MARK: Helper Functions
|
||||||
function buildEtaEngine() {
|
function buildEtaEngine() {
|
||||||
return (path:string, opts:Options, callback: CallableFunction) => {
|
return (path: string, opts: Options, callback: CallableFunction) => {
|
||||||
try {
|
try {
|
||||||
const fileContent = eta.readFile(path);
|
const fileContent = eta.readFile(path);
|
||||||
const renderedTemplate = eta.renderString(fileContent, opts);
|
const renderedTemplate = eta.renderString(fileContent, opts);
|
||||||
@ -109,4 +117,3 @@ function buildEtaEngine() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import validator from 'joi'; // DOCS: https://joi.dev/api
|
import validator from 'joi'; // DOCS: https://joi.dev/api
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
// MARK: GET alertContact
|
// MARK: GET alertContact
|
||||||
const schema_get = validator.object({
|
const schema_get = validator.object({
|
||||||
sort: validator.string().valid('id', 'name', 'phone', 'comment').default('id'),
|
//sort: validator.string().valid('id', 'name', 'phone', 'comment').default('id'),
|
||||||
|
sort: validator.string().valid(...Object.keys(Prisma.AlertContactsScalarFieldEnum)).default('id'),
|
||||||
order: validator.string().valid('asc', 'desc').default('asc'),
|
order: validator.string().valid('asc', 'desc').default('asc'),
|
||||||
take: validator.number().min(1).max(512),
|
take: validator.number().min(1).max(512),
|
||||||
skip: validator.number().min(0),
|
skip: validator.number().min(0),
|
||||||
|
7
src/routes/frontend/contact.ts
Normal file
7
src/routes/frontend/contact.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
|
||||||
|
function get(req: Request, res: Response) {
|
||||||
|
res.render("contacts")
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { get };
|
@ -1,11 +1,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
// Route imports
|
// Route imports
|
||||||
// import skuRoute from './:id.js';
|
|
||||||
// import skuRouteDash from './itemInfo.js'
|
|
||||||
// import testRoute from './test.js';
|
|
||||||
import dashboardRoute from './dashboard.js';
|
import dashboardRoute from './dashboard.js';
|
||||||
import testRoute from './test.js';
|
import testRoute from './test.js';
|
||||||
|
import contactRoute from './contact.js';
|
||||||
// import itemsRoute from './items.js';
|
// import itemsRoute from './items.js';
|
||||||
// import manage_routes from './manage/index.js';
|
// import manage_routes from './manage/index.js';
|
||||||
|
|
||||||
@ -22,5 +20,6 @@ const Router = express.Router({ strict: false });
|
|||||||
|
|
||||||
Router.route('/').get(dashboardRoute.get);
|
Router.route('/').get(dashboardRoute.get);
|
||||||
Router.route('/dbTest').get(testRoute.get);
|
Router.route('/dbTest').get(testRoute.get);
|
||||||
|
Router.route('/contact').get(contactRoute.get);
|
||||||
|
|
||||||
export default Router;
|
export default Router;
|
||||||
|
@ -96,7 +96,7 @@
|
|||||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
119
views/contacts.eta
Normal file
119
views/contacts.eta
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<%~ include("partials/base_head.eta", {"title": "Kontakte"}) %>
|
||||||
|
<%~ include("partials/nav.eta") %>
|
||||||
|
|
||||||
|
<section class="hero is-primary" id="heroStatus">
|
||||||
|
<div class="hero-body">
|
||||||
|
<p class="title" data-tK="start-hero-header-welcome">Kontaktverwaltung</p>
|
||||||
|
<p class="subtitle" data-tK="start-hero-header-subtitle-default" id="heroExplainer">Erklärungstext</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="section">
|
||||||
|
<nav class="level">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading">Kontakte</p>
|
||||||
|
<p class="title"><span data-dataSource="AlertContacts" data-dataAction="COUNT" class="is-skeleton">Load.</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<p class="heading"><button class="js-modal-trigger button" data-target="modal-js-example">
|
||||||
|
Neuen Konakt anlegen
|
||||||
|
</button></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- TODO: Mark required fields as required; add handling for validation -->
|
||||||
|
<div id="modal-js-example" class="modal">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="box entryPhase is-hidden">
|
||||||
|
<h2 class="title">Neuer Kontakt</h1>
|
||||||
|
|
||||||
|
<i class="bi bi-arrow-clockwise title"></i>
|
||||||
|
</div>
|
||||||
|
<div class="box entryPhase">
|
||||||
|
|
||||||
|
<form data-targetTable="AlertContacts">
|
||||||
|
<h2 class="title">Neuer Kontakt</h1>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Name</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="text" placeholder="John Doe" value="" name="name">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="bi bi-file-earmark-person-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Telefonummer</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="text" placeholder="+49 1234 5678912" value="" name="phone">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="bi bi-telephone-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Anmerkung</label>
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<input class="input" type="text" placeholder="" value="" name="comment">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="bi bi-chat-fill"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<input type="submit" class="button is-link" value="Save" data-actionBtn="save">
|
||||||
|
</div>
|
||||||
|
<!--<div class="control">
|
||||||
|
<button type="button" class="button is-link is-light" data-actionBtn="cancel">Cancel</button>
|
||||||
|
</div>-->
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="modal-close is-large" aria-label="close"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<h1 class="title" data-tK="start-recent-header">Kontaktübersicht</h1>
|
||||||
|
<input class="input" type="text" data-searchTargetId="contactTable" placeholder="Search..." />
|
||||||
|
<table class="table is-striped is-fullwidth is-hoverable" data-dataSource="AlertContacts" id="contactTable" data-pageSize="5">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-dataCol = "id"><abbr title="Position">Pos</abbr></th>
|
||||||
|
<th data-dataCol = "name">Name</th>
|
||||||
|
<th data-dataCol = "phone">Telefonnummer</th>
|
||||||
|
<th data-dataCol = "comment">Kommentar</th>
|
||||||
|
<th data-fnc="actions" data-actions="edit,delete">Aktionen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<nav class="pagination is-hidden" role="navigation" aria-label="pagination" data-targetTable="contactTable">
|
||||||
|
<ul class="pagination-list">
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<%~ include("partials/footer.eta") %>
|
||||||
|
<%~ include("partials/base_foot.eta") %>
|
@ -20,6 +20,7 @@
|
|||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="/">Home</a>
|
<a class="navbar-item" href="/">Home</a>
|
||||||
<a class="navbar-item" href="/dbTest">API Integration <span class="tag is-info">Dev</span></a>
|
<a class="navbar-item" href="/dbTest">API Integration <span class="tag is-info">Dev</span></a>
|
||||||
|
<a class="navbar-item" href="/contact">Kontakte <span class="tag is-primary">Neu!</span></a>
|
||||||
<!--<div class="navbar-item has-dropdown is-hoverable">
|
<!--<div class="navbar-item has-dropdown is-hoverable">
|
||||||
<a class="navbar-link">More</a>
|
<a class="navbar-link">More</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
@ -32,7 +33,7 @@
|
|||||||
</div>-->
|
</div>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="button is-primary">
|
<a class="button is-primary">
|
||||||
@ -41,6 +42,6 @@
|
|||||||
<a class="button is-light">Log in</a>
|
<a class="button is-light">Log in</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>-->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -32,6 +32,34 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<h1 class="title" data-tK="start-sysinfo-header">File test</h1>
|
||||||
|
<form method="put" action="/api/upload" enctype="multipart/form-data" id="uploadForm">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">File</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="file" name="file" id="file">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- URL field -->
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">URL</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" name="url" id="url">
|
||||||
|
</div>
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control">
|
||||||
|
<input type="submit" class="button is-link" value="Upload" data-actionBtn="upload">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById("url").addEventListener("input", function() {
|
||||||
|
document.getElementById("uploadForm").action = document.getElementById("url").value;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- TODO: Mark required fields as required; add handling for validation -->
|
<!-- TODO: Mark required fields as required; add handling for validation -->
|
||||||
<div id="modal-js-example" class="modal">
|
<div id="modal-js-example" class="modal">
|
||||||
<div class="modal-background"></div>
|
<div class="modal-background"></div>
|
||||||
|
Reference in New Issue
Block a user