initial upload
This commit is contained in:
parent
987c99d00b
commit
bb6c0147db
44 changed files with 1884 additions and 131 deletions
133
.gitignore
vendored
133
.gitignore
vendored
|
|
@ -1,132 +1,3 @@
|
||||||
# ---> Node
|
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
logs/
|
||||||
|
package-lock.json
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
|
||||||
web_modules/
|
|
||||||
|
|
||||||
# TypeScript cache
|
|
||||||
*.tsbuildinfo
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Gatsby files
|
|
||||||
.cache/
|
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
|
||||||
.vuepress/dist
|
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
6
assets/css/bootstrap.min.css
vendored
Normal file
6
assets/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
assets/css/bootstrap.min.css.map
Normal file
1
assets/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
4
assets/css/tabler-icons.min.css
vendored
Normal file
4
assets/css/tabler-icons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/fonts/tabler-icons.eot
Normal file
BIN
assets/fonts/tabler-icons.eot
Normal file
Binary file not shown.
BIN
assets/fonts/tabler-icons.ttf
Normal file
BIN
assets/fonts/tabler-icons.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/tabler-icons.woff
Normal file
BIN
assets/fonts/tabler-icons.woff
Normal file
Binary file not shown.
BIN
assets/fonts/tabler-icons.woff2
Normal file
BIN
assets/fonts/tabler-icons.woff2
Normal file
Binary file not shown.
1
assets/js/vue.global.prod.js
Normal file
1
assets/js/vue.global.prod.js
Normal file
File diff suppressed because one or more lines are too long
1
assets/js/vue.runtime.global.prod.js
Normal file
1
assets/js/vue.runtime.global.prod.js
Normal file
File diff suppressed because one or more lines are too long
65
assets/start.js
Normal file
65
assets/start.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
const { loadModule } = window['vue3-sfc-loader'];
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
moduleCache: {
|
||||||
|
vue: Vue,
|
||||||
|
},
|
||||||
|
getFile(url) {
|
||||||
|
return fetch(url).then((resp) =>
|
||||||
|
resp.ok ? resp.text() : Promise.reject(resp)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
addStyle(styleStr) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = styleStr;
|
||||||
|
const ref = document.head.getElementsByTagName('style')[0] || null;
|
||||||
|
document.head.insertBefore(style, ref);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var vueApp = Vue.createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modeEdit: false,
|
||||||
|
elements: [{
|
||||||
|
id: '234bi23b4ikj',
|
||||||
|
type: 'jumbotron',
|
||||||
|
title: 'my super awesome title',
|
||||||
|
text: 'there is nothing better on this planet'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '234bi23b4ikj',
|
||||||
|
type: 'grid',
|
||||||
|
title: 'Group 1',
|
||||||
|
columns: 2,
|
||||||
|
elements: [{
|
||||||
|
id: '234bi23b4ikj',
|
||||||
|
type: 'link',
|
||||||
|
link: 'https://google.com/',
|
||||||
|
title: 'Tisoware',
|
||||||
|
description: 'Elektronische Arbeitszeiterfassung'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '234bi23b4ikj',
|
||||||
|
type: 'link',
|
||||||
|
link: 'https://google.com/',
|
||||||
|
title: 'IT Helpdesk',
|
||||||
|
description: 'Helpdesk der operativen IT für Supportanfragen'
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleEditor()
|
||||||
|
{
|
||||||
|
this.modeEdit = !this.modeEdit;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
DynamicRoot: Vue.defineAsyncComponent(() =>
|
||||||
|
loadModule('/vue/root.vue', options)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}).mount('#app');
|
||||||
|
|
||||||
|
|
||||||
33
assets/vue/grid.vue
Normal file
33
assets/vue/grid.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div :class="[
|
||||||
|
'grid',
|
||||||
|
'grid-cols-1',
|
||||||
|
'md:grid-cols-2',
|
||||||
|
'lg:grid-cols-' + columns,
|
||||||
|
'gap-6',
|
||||||
|
'relative'
|
||||||
|
]">
|
||||||
|
<div v-if="title" class="col-span-full">
|
||||||
|
<h5 class="font-medium text-gray-700 dark:text-gray-300 text-2xl">{{ title }}</h5>
|
||||||
|
</div>
|
||||||
|
<button v-if="this.$root.modeEdit" type="button" title="Edit Element"
|
||||||
|
class="text-white bg-blue-700 hover:bg-blue-800 outline-none rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 absolute top-2 right-2">
|
||||||
|
<i class="ti ti-edit mx-3 my-2 block"></i>
|
||||||
|
</button>
|
||||||
|
<slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
columns: Number,
|
||||||
|
title: String
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
28
assets/vue/jumbotron.vue
Normal file
28
assets/vue/jumbotron.vue
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<template>
|
||||||
|
<section class="w-full relative">
|
||||||
|
<button v-if="this.$root.modeEdit" type="button" title="Edit Element" class="text-white bg-blue-700 hover:bg-blue-800 outline-none rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 absolute top-2 right-2">
|
||||||
|
<i class="ti ti-edit mx-3 my-2 block"></i>
|
||||||
|
</button>
|
||||||
|
<div class="py-4 px-4 mx-auto max-w-screen-xl text-center">
|
||||||
|
<h1 class="my-4 font-extrabold tracking-tight leading-none text-4xl md:text-5xl lg:text-6xl">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p class="my-4 text-lg font-normal text-gray-500 lg:text-xl sm:px-16 lg:px-48">
|
||||||
|
<slot>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
title: String
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
25
assets/vue/link.vue
Normal file
25
assets/vue/link.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<a :href="link" target="_blank" class="p-8 border rounded-md bg-gray-50 dark:bg-gray-700 border-gray-200 dark:border-gray-600 hover:border-blue-400 ">
|
||||||
|
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-gray-100">
|
||||||
|
{{ title }}
|
||||||
|
</h5>
|
||||||
|
<p class="font-normal text-gray-500 dark:text-gray-400 text-md">
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
link: String,
|
||||||
|
title: String,
|
||||||
|
description: String
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
45
assets/vue/root.vue
Normal file
45
assets/vue/root.vue
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
<template>
|
||||||
|
<template v-for="(element, elementIndex) in elements">
|
||||||
|
<div class="container flex justify-end">
|
||||||
|
<button v-if="this.$root.modeEdit" type="button" class="text-white bg-blue-700 hover:bg-blue-800 outline-none rounded-lg text-center dark:bg-blue-600 dark:hover:bg-blue-700 absolute top-2 right-2" title="Edit Element">
|
||||||
|
<i class="ti ti-edit mx-3 my-2 block"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<tailwind-jumbotron v-if="element.type == 'jumbotron'" :title="element.title">
|
||||||
|
{{ element.text }}
|
||||||
|
</tailwind-jumbotron>
|
||||||
|
<tailwind-grid v-if="element.type == 'grid'" :columns="element.columns" :title="element.title">
|
||||||
|
<dynamic-root :elements="element.elements"></dynamic-root>
|
||||||
|
</tailwind-grid>
|
||||||
|
<tailwind-link v-if="element.type == 'link'" :link="element.link" :title="element.title" :description="element.description">
|
||||||
|
|
||||||
|
</tailwind-link>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
elements: Array
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
TailwindJumbotron: Vue.defineAsyncComponent(() =>
|
||||||
|
loadModule('/vue/jumbotron.vue', options)
|
||||||
|
),
|
||||||
|
TailwindGrid: Vue.defineAsyncComponent(() =>
|
||||||
|
loadModule('/vue/grid.vue', options)
|
||||||
|
),
|
||||||
|
TailwindLink: Vue.defineAsyncComponent(() =>
|
||||||
|
loadModule('/vue/link.vue', options)
|
||||||
|
),
|
||||||
|
DynamicRoot: Vue.defineAsyncComponent(() =>
|
||||||
|
loadModule('/vue/root.vue', options)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
11
assets/widgets.jumbotron.js
Normal file
11
assets/widgets.jumbotron.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
let jumbotron = Vue.defineComponent('tailwind-jumbotron',
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
text: String
|
||||||
|
},
|
||||||
|
template: '#template-jumbotron'
|
||||||
|
});
|
||||||
|
|
||||||
|
// customElements.define('tailwind-jumbotron', jumbotron)
|
||||||
4
config/settings.yaml
Normal file
4
config/settings.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
serverPort: 3000
|
||||||
|
serverAddr: 0.0.0.0
|
||||||
|
trustProxy:
|
||||||
|
- loopback
|
||||||
32
datastore/certificates/1a278ace.crt
Normal file
32
datastore/certificates/1a278ace.crt
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
||||||
1
datastore/database/authorityDB.json
Normal file
1
datastore/database/authorityDB.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
6
datastore/database/certificates.json
Normal file
6
datastore/database/certificates.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"1a278ace": {
|
||||||
|
"displayName": "waggeling.net rootCA",
|
||||||
|
"certificateType": "rootCA"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
datastore/keys/1a278ace.key
Normal file
51
datastore/keys/1a278ace.key
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
58
functions/db.authorities.mjs
Normal file
58
functions/db.authorities.mjs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
JsonDB,
|
||||||
|
Config
|
||||||
|
} from 'node-json-db';
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as ValidateSchema
|
||||||
|
} from 'validate'
|
||||||
|
|
||||||
|
import {
|
||||||
|
randomBytes
|
||||||
|
} from "crypto";
|
||||||
|
|
||||||
|
|
||||||
|
var authorityDB = new JsonDB(new Config("datastore/database/authorityDB.json", true, true, '/'));
|
||||||
|
|
||||||
|
const authoritySchema = new ValidateSchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[a-zA-Z0-9\ \-\_]+$/,
|
||||||
|
length: { min: 3, max: 32 },
|
||||||
|
message: 'invalid name'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function saveAuthority(authorityID, authorityConfig) {
|
||||||
|
let configErrorList = authoritySchema.validate(authorityConfig);
|
||||||
|
|
||||||
|
configErrorList = configErrorList.map((configError) => {
|
||||||
|
return configError.message;
|
||||||
|
})
|
||||||
|
|
||||||
|
if (configErrorList.length > 0) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
errors: configErrorList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await authorityDB.push(`/${authorityID}`, authorityConfig);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAuthority(authorityID) {
|
||||||
|
let AuthorityData = await authorityDB.getData(`/${authorityID}`);
|
||||||
|
return AuthorityData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAuthority(authorityID) {
|
||||||
|
await authorityDB.delete(`/${authorityID}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAuthority(randomBytes(4).toString("hex"), {names: "{test}"})
|
||||||
91
functions/db.certificates.mjs
Normal file
91
functions/db.certificates.mjs
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
JsonDB,
|
||||||
|
Config
|
||||||
|
} from 'node-json-db';
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as ValidateSchema
|
||||||
|
} from 'validate'
|
||||||
|
|
||||||
|
|
||||||
|
var certificateDB = new JsonDB(new Config("datastore/database/certificates.json", true, true, '/'));
|
||||||
|
|
||||||
|
const certificateSchema = new ValidateSchema({
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[a-zA-Z0-9\ \-\_\.]+$/,
|
||||||
|
message: {
|
||||||
|
type: '[displayName] must be a string.',
|
||||||
|
required: '[displayName] is required.',
|
||||||
|
match: '[displayName] is invalid.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
certificateType: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
enum: [
|
||||||
|
'rootCA',
|
||||||
|
'intermediateCA',
|
||||||
|
'identity'
|
||||||
|
],
|
||||||
|
message: {
|
||||||
|
type: '[certificateType] must be a string.',
|
||||||
|
required: '[certificateType] is required.',
|
||||||
|
enum: '[certificateType] must be one of [rootCA|intermediateCA|identity]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export async function saveCertificate(sCertificateID, oCertificateData) {
|
||||||
|
console.log("-----------------------------------------");
|
||||||
|
let configErrorList = certificateSchema.validate(oCertificateData);
|
||||||
|
|
||||||
|
configErrorList = configErrorList.map((configError) => {
|
||||||
|
return configError.message;
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(configErrorList);
|
||||||
|
|
||||||
|
if (configErrorList.length > 0) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
errors: configErrorList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await certificateDB.push(`/${sCertificateID}`, oCertificateData);
|
||||||
|
|
||||||
|
console.log(oCertificateData);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCertificateByID(sCertificateID) {
|
||||||
|
let oCertificateData = await certificateDB.getData(`/${sCertificateID}`);
|
||||||
|
return oCertificateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllCertificates() {
|
||||||
|
let aCertificateList = await certificateDB.getData(`/`);
|
||||||
|
return aCertificateList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCertificatesOfType(sCertificateType) {
|
||||||
|
let aCertificateList = await certificateDB.getData(`/`);
|
||||||
|
aCertificateList = aCertificateList.filter((oCertificateData) => {
|
||||||
|
return oCertificateData.certificateType == sCertificateType;
|
||||||
|
})
|
||||||
|
return aCertificateList;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteCertificate(sCertificateID) {
|
||||||
|
await certificateDB.delete(`/${sCertificateID}`);
|
||||||
|
}
|
||||||
134
functions/db.users.mjs
Normal file
134
functions/db.users.mjs
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
JsonDB,
|
||||||
|
Config
|
||||||
|
} from 'node-json-db';
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as ValidateSchema
|
||||||
|
} from 'validate'
|
||||||
|
|
||||||
|
|
||||||
|
var userDB = new JsonDB(new Config("datastore/database/userDB.json", true, true, '/'));
|
||||||
|
|
||||||
|
const userSchema = new ValidateSchema({
|
||||||
|
username: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[a-zA-Z0-9\ \-\_]+$/,
|
||||||
|
length: { min: 3, max: 32 },
|
||||||
|
message: {
|
||||||
|
type: '[username] must be of type String.',
|
||||||
|
required: '[username] is required.',
|
||||||
|
match: '[username] is invalid. Allowed characters: (a-z|A-Z|0-9| |-|_)',
|
||||||
|
length: '[username] must consist of 3 to 32 characters.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
firstName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[a-zA-Z0-9\ \-\_]+$/,
|
||||||
|
length: { min: 1, max: 32 },
|
||||||
|
message: {
|
||||||
|
type: '[firstName] must be of type String.',
|
||||||
|
required: '[firstName] is required.',
|
||||||
|
match: '[firstName] is invalid. Allowed characters: (a-z|A-Z|0-9| |-|_)',
|
||||||
|
length: '[firstName] must consist of 1 to 32 characters.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[a-zA-Z0-9\ \-\_]+$/,
|
||||||
|
length: { min: 1, max: 32 },
|
||||||
|
message: {
|
||||||
|
type: '[lastName] must be of type String.',
|
||||||
|
required: '[lastName] is required.',
|
||||||
|
match: '[lastName] is invalid. Allowed characters: (a-z|A-Z|0-9| |-|_)',
|
||||||
|
length: '[lastName] must consist of 1 to 32 characters.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emailAddress: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
|
||||||
|
message: {
|
||||||
|
type: '[emailAddress] must be of type String.',
|
||||||
|
required: '[emailAddress] is required.',
|
||||||
|
match: '[emailAddress] is invalid (e.g. admin@example.com).'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
enum: [
|
||||||
|
'true',
|
||||||
|
'false'
|
||||||
|
],
|
||||||
|
message: {
|
||||||
|
type: '[enabled] must be of type String.',
|
||||||
|
required: '[enabled] is required.',
|
||||||
|
enum: '[enabled] must be one of [true|false].'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
elements: [{
|
||||||
|
type: String
|
||||||
|
}],
|
||||||
|
message: {
|
||||||
|
type: '[permissions] must be of type Array.',
|
||||||
|
required: '[permissions] is required.',
|
||||||
|
elements: '[permissions] elements must be of type String.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export async function saveUser(sUserID, oUserData) {
|
||||||
|
let dataErrorList = userSchema.validate(oUserData);
|
||||||
|
|
||||||
|
dataErrorList = dataErrorList.map((configError) => {
|
||||||
|
return configError.message;
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dataErrorList.length > 0) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
errors: dataErrorList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await userDB.push(`/${sUserID}`, oUserData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "ok"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUser(userID) {
|
||||||
|
let requestData = await userDB.getData(`/${userID}`);
|
||||||
|
return {
|
||||||
|
username: requestData.username,
|
||||||
|
firstName: requestData.firstName,
|
||||||
|
lastName: requestData.lastName,
|
||||||
|
emailAddress: requestData.emailAddress
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllUsers() {
|
||||||
|
let userList = await userDB.getData(`/`);
|
||||||
|
return userList.map((userData) => {
|
||||||
|
return {
|
||||||
|
username: userData.username,
|
||||||
|
firstName: userData.firstName,
|
||||||
|
lastName: userData.lastName,
|
||||||
|
emailAddress: userData.emailAddress
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUser(userID) {
|
||||||
|
await userDB.delete(`/${userID}`);
|
||||||
|
}
|
||||||
84
functions/pki.createCA.mjs
Normal file
84
functions/pki.createCA.mjs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as forge
|
||||||
|
} from "node-forge";
|
||||||
|
|
||||||
|
import {
|
||||||
|
writeFileSync,
|
||||||
|
mkdirSync
|
||||||
|
} from "fs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
randomBytes
|
||||||
|
} from "crypto";
|
||||||
|
|
||||||
|
// Funktion zur Generierung und Speicherung eines Root-CA-Zertifikats
|
||||||
|
export function generateRootCA(Params) {
|
||||||
|
const uid = randomBytes(4).toString("hex");
|
||||||
|
// Generiere ein neues Schlüsselpaar
|
||||||
|
const keys = forge.pki.rsa.generateKeyPair(4096);
|
||||||
|
|
||||||
|
// Erstelle ein neues Zertifikat
|
||||||
|
const cert = forge.pki.createCertificate();
|
||||||
|
cert.publicKey = keys.publicKey;
|
||||||
|
// cert.serialNumber = '01';
|
||||||
|
cert.validity.notBefore = new Date();
|
||||||
|
cert.validity.notAfter = new Date();
|
||||||
|
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
|
||||||
|
|
||||||
|
const attrs = [{
|
||||||
|
name: 'commonName',
|
||||||
|
value: 'My Root CA'
|
||||||
|
}, {
|
||||||
|
name: 'countryName',
|
||||||
|
value: 'US'
|
||||||
|
}, {
|
||||||
|
shortName: 'ST',
|
||||||
|
value: 'California'
|
||||||
|
}, {
|
||||||
|
name: 'localityName',
|
||||||
|
value: 'San Francisco'
|
||||||
|
}, {
|
||||||
|
name: 'organizationName',
|
||||||
|
value: 'My Organization'
|
||||||
|
}, {
|
||||||
|
shortName: 'OU',
|
||||||
|
value: 'My Organizational Unit'
|
||||||
|
}];
|
||||||
|
|
||||||
|
cert.setSubject(attrs);
|
||||||
|
cert.setIssuer(attrs);
|
||||||
|
|
||||||
|
// Erweiterungen hinzufügen
|
||||||
|
cert.setExtensions([{
|
||||||
|
name: 'basicConstraints',
|
||||||
|
cA: true
|
||||||
|
}, {
|
||||||
|
name: 'keyUsage',
|
||||||
|
keyCertSign: true,
|
||||||
|
cRLSign: true
|
||||||
|
}, {
|
||||||
|
name: 'subjectKeyIdentifier'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Zertifikat mit dem privaten Schlüssel signieren
|
||||||
|
cert.sign(keys.privateKey, forge.md.sha256.create());
|
||||||
|
|
||||||
|
// Zertifikat und Schlüssel als PEM kodieren
|
||||||
|
const pemCert = forge.pki.certificateToPem(cert);
|
||||||
|
const pemPrivateKey = forge.pki.privateKeyToPem(keys.privateKey);
|
||||||
|
const pemPublicKey = forge.pki.publicKeyToPem(keys.publicKey);
|
||||||
|
|
||||||
|
// Erstelle das RootCA Verzeichnis
|
||||||
|
mkdirSync(`datastore/certificates/${uid}`)
|
||||||
|
|
||||||
|
// Zertifikat und Schlüssel in Dateien speichern
|
||||||
|
writeFileSync(`datastore/certificates/${uid}/rootCA.crt`, pemCert);
|
||||||
|
writeFileSync(`datastore/certificates/${uid}/rootCA.key`, pemPrivateKey);
|
||||||
|
writeFileSync(`datastore/certificates/${uid}/rootCA.pub`, pemPublicKey);
|
||||||
|
|
||||||
|
console.log('Root CA-Zertifikat und Schlüssel wurden generiert und gespeichert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funktion aufrufen, um das Root-CA-Zertifikat zu generieren
|
||||||
|
// generateRootCA();
|
||||||
77
functions/pki.createCSR.mjs
Normal file
77
functions/pki.createCSR.mjs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as forge
|
||||||
|
} from "node-forge";
|
||||||
|
|
||||||
|
import {
|
||||||
|
writeFileSync,
|
||||||
|
mkdirSync
|
||||||
|
} from "fs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
randomBytes
|
||||||
|
} from "crypto";
|
||||||
|
|
||||||
|
// Funktion zur Generierung und Speicherung eines Root-CA-Zertifikats
|
||||||
|
export function generateRootCA(Params) {
|
||||||
|
const uid = randomBytes(4).toString("hex");
|
||||||
|
|
||||||
|
// Generiere ein neues Schlüsselpaar
|
||||||
|
const keys = forge.pki.rsa.generateKeyPair(4096);
|
||||||
|
|
||||||
|
// Erstelle einen neuen CSR
|
||||||
|
const csr = forge.pki.createCertificationRequest();
|
||||||
|
|
||||||
|
// Setze den öffentlichen Schlüssel
|
||||||
|
csr.publicKey = keys.publicKey;
|
||||||
|
|
||||||
|
// Setze die CSR Attribute
|
||||||
|
csr.setSubject([{
|
||||||
|
name: 'commonName',
|
||||||
|
value: 'example.com'
|
||||||
|
}, {
|
||||||
|
name: 'countryName',
|
||||||
|
value: 'US'
|
||||||
|
}, {
|
||||||
|
shortName: 'ST',
|
||||||
|
value: 'California'
|
||||||
|
}, {
|
||||||
|
name: 'localityName',
|
||||||
|
value: 'San Francisco'
|
||||||
|
}, {
|
||||||
|
name: 'organizationName',
|
||||||
|
value: 'Example, Inc.'
|
||||||
|
}, {
|
||||||
|
shortName: 'OU',
|
||||||
|
value: 'IT'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Signiere die CSR mit dem privaten Schlüssel
|
||||||
|
csr.sign(keys.privateKey, forge.md.sha256.create());
|
||||||
|
|
||||||
|
// Überprüfe die CSR
|
||||||
|
const verified = csr.verify();
|
||||||
|
if (verified) {
|
||||||
|
console.log('CSR verification successful');
|
||||||
|
} else {
|
||||||
|
console.error('CSR verification failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSR und Schlüssel als PEM kodieren
|
||||||
|
const pemCsr = forge.pki.certificationRequestToPem(csr);
|
||||||
|
const pemPrivateKey = forge.pki.privateKeyToPem(keys.privateKey);
|
||||||
|
|
||||||
|
// Erstelle das RootCA Verzeichnis
|
||||||
|
mkdirSync(`datastore/certificates/${uid}`)
|
||||||
|
|
||||||
|
// Zertifikat und Schlüssel in Dateien speichern
|
||||||
|
writeFileSync(`datastore/certificates/${uid}/rootCA.crt`, pemCert);
|
||||||
|
writeFileSync(`datastore/certificates/${uid}/rootCA.key`, pemPrivateKey);
|
||||||
|
writeFileSync(`datastore/certificates/${uid}/rootCA.pub`, pemPublicKey);
|
||||||
|
|
||||||
|
console.log('Root CA-Zertifikat und Schlüssel wurden generiert und gespeichert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funktion aufrufen, um das Root-CA-Zertifikat zu generieren
|
||||||
|
// generateRootCA();
|
||||||
298
functions/pki.createCert.mjs
Normal file
298
functions/pki.createCert.mjs
Normal file
|
|
@ -0,0 +1,298 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as forge
|
||||||
|
} from "node-forge";
|
||||||
|
|
||||||
|
import {
|
||||||
|
loadCertificate,
|
||||||
|
loadPrivateKey,
|
||||||
|
saveCertificate,
|
||||||
|
savePrivateKey
|
||||||
|
} from "./pki.utils.mjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
saveCertificate as saveCertificateToDatabase
|
||||||
|
} from "./db.certificates.mjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as ValidateSchema
|
||||||
|
} from 'validate'
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as parseDuration
|
||||||
|
} from "parse-duration";
|
||||||
|
|
||||||
|
import {
|
||||||
|
randomBytes
|
||||||
|
} from "crypto";
|
||||||
|
import { error } from "console";
|
||||||
|
|
||||||
|
|
||||||
|
const CertificateSchema = new ValidateSchema({
|
||||||
|
certType: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
enum: [
|
||||||
|
'rootCA',
|
||||||
|
'intermediateCA',
|
||||||
|
'identity'
|
||||||
|
],
|
||||||
|
message: {
|
||||||
|
type: '[certType] must be a string.',
|
||||||
|
required: '[certType] is required.',
|
||||||
|
enum: '[certType] must be one of [authority, identity]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
message: {
|
||||||
|
type: '[parent] must be a string.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
message: {
|
||||||
|
type: '[duration] must be a string.',
|
||||||
|
required: '[duration] is required.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
commonName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[a-zA-Z0-9\ \-\_\.]+$/,
|
||||||
|
length: { min: 3, max: 32 },
|
||||||
|
message: {
|
||||||
|
type: '[commonName] must be a string.',
|
||||||
|
required: '[commonName] is required.',
|
||||||
|
match: '[commonName] is invalid.',
|
||||||
|
length: '[commonName] is invalid.'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emailAddress: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
|
||||||
|
message: {
|
||||||
|
type: '[emailAddress] must be a string.',
|
||||||
|
required: '[emailAddress] is required.',
|
||||||
|
match: '[emailAddress] is invalid (e.g. admin@example.com).'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
countryName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[A-Z]{2}$/,
|
||||||
|
message: {
|
||||||
|
type: '[countryName] must be a string.',
|
||||||
|
required: '[countryName] is required.',
|
||||||
|
match: '[countryName] needs to be 2 letter code (e.g. US).'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stateOrProvinceName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[A-Za-z ]{3,64}$/,
|
||||||
|
message: {
|
||||||
|
type: '[stateOrProvinceName] must be a string.',
|
||||||
|
required: '[stateOrProvinceName] is required.',
|
||||||
|
match: '[stateOrProvinceName] is invalid: (A-Z|a-z| ), 3-64 characters'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
localityName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[A-Za-z ]{3,64}$/,
|
||||||
|
message: {
|
||||||
|
type: '[localityName] must be a string.',
|
||||||
|
required: '[localityName] is required.',
|
||||||
|
match: '[localityName] is invalid: (A-Z,a-z, ), 3-64 characters'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
organizationName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[A-Za-z\ \-\_\.]{3,64}$/,
|
||||||
|
message: {
|
||||||
|
type: '[organizationName] must be a string.',
|
||||||
|
required: '[organizationName] is required.',
|
||||||
|
match: '[organizationName] is invalid: (A-Z,a-z, ), 3-64 characters'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
organizationalUnitName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
match: /^[A-Za-z\ \-\_\.]{3,64}$/,
|
||||||
|
message: {
|
||||||
|
type: '[organizationalUnitName] must be a string.',
|
||||||
|
required: '[organizationalUnitName] is required.',
|
||||||
|
match: '[organizationalUnitName] is invalid: (A-Z,a-z, ,.,-,_), 3-64 characters'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function generateCertificate(Params) {
|
||||||
|
let configErrorList = CertificateSchema.validate(Params);
|
||||||
|
|
||||||
|
configErrorList = configErrorList.map((configError) => {
|
||||||
|
return configError.message;
|
||||||
|
})
|
||||||
|
|
||||||
|
if (configErrorList.length > 0) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
errors: configErrorList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate certificate id
|
||||||
|
const certificateId = randomBytes(4).toString("hex");
|
||||||
|
|
||||||
|
// generate new keypair
|
||||||
|
const {
|
||||||
|
privateKey,
|
||||||
|
publicKey
|
||||||
|
} = forge.pki.rsa.generateKeyPair(4096);
|
||||||
|
|
||||||
|
// initialize new certificate
|
||||||
|
const certificate = forge.pki.createCertificate();
|
||||||
|
certificate.publicKey = publicKey;
|
||||||
|
// cert.serialNumber = '01';
|
||||||
|
certificate.validity.notBefore = new Date(Date.now());
|
||||||
|
certificate.validity.notAfter = new Date(Date.now() + parseDuration(Params.duration));
|
||||||
|
|
||||||
|
const attrs = [{
|
||||||
|
name: 'commonName',
|
||||||
|
value: Params.commonName
|
||||||
|
}, {
|
||||||
|
name: 'countryName',
|
||||||
|
value: Params.countryName
|
||||||
|
}, {
|
||||||
|
name: 'stateOrProvinceName',
|
||||||
|
value: Params.stateOrProvinceName
|
||||||
|
}, {
|
||||||
|
name: 'localityName',
|
||||||
|
value: Params.localityName
|
||||||
|
}, {
|
||||||
|
name: 'organizationName',
|
||||||
|
value: Params.organizationName
|
||||||
|
}, {
|
||||||
|
name: 'organizationalUnitName',
|
||||||
|
value: Params.organizationalUnitName
|
||||||
|
}];
|
||||||
|
|
||||||
|
certificate.setSubject(attrs);
|
||||||
|
certificate.setIssuer(attrs);
|
||||||
|
|
||||||
|
// Erweiterungen hinzufügen
|
||||||
|
certificate.setExtensions([{
|
||||||
|
name: 'basicConstraints',
|
||||||
|
cA: true
|
||||||
|
}, {
|
||||||
|
name: 'keyUsage',
|
||||||
|
keyCertSign: true,
|
||||||
|
cRLSign: true
|
||||||
|
}, {
|
||||||
|
name: 'subjectKeyIdentifier'
|
||||||
|
}]);
|
||||||
|
|
||||||
|
console.log(`new certificate with uid ${certificateId} generated`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "ok",
|
||||||
|
certificateId,
|
||||||
|
certificate,
|
||||||
|
privateKey,
|
||||||
|
publicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createRootCertificate(certificateParameters) {
|
||||||
|
let oGeneratedData = generateCertificate({
|
||||||
|
certType: 'rootCA',
|
||||||
|
parent: null,
|
||||||
|
duration: certificateParameters.duration,
|
||||||
|
emailAddress: certificateParameters.emailAddress,
|
||||||
|
commonName: certificateParameters.commonName,
|
||||||
|
countryName: certificateParameters.countryName,
|
||||||
|
stateOrProvinceName: certificateParameters.stateOrProvinceName,
|
||||||
|
localityName: certificateParameters.localityName,
|
||||||
|
organizationName: certificateParameters.organizationName,
|
||||||
|
organizationalUnitName: certificateParameters.organizationalUnitName
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oGeneratedData.status != "ok") {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
errors: oGeneratedData.errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign the new certificate
|
||||||
|
oGeneratedData.certificate.sign(oGeneratedData.privateKey, forge.md.sha256.create());
|
||||||
|
|
||||||
|
// save certificate and keys
|
||||||
|
saveCertificate(oGeneratedData.certificate, oGeneratedData.certificateId, 'rootCA');
|
||||||
|
savePrivateKey(oGeneratedData.privateKey, oGeneratedData.certificateId, 'rootCA');
|
||||||
|
saveCertificateToDatabase(oGeneratedData.certificateId, {
|
||||||
|
displayName: certificateParameters.commonName,
|
||||||
|
certificateType: 'rootCA'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`new rootCA certificate with uid ${oGeneratedData.certificateId} saved`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "ok",
|
||||||
|
certificateId: oGeneratedData.certificateId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createIntermediateCertificate(oCertificateParameters, sRootCertificateId) {
|
||||||
|
let {
|
||||||
|
certificateId: newCertificateId,
|
||||||
|
certificate: newCertificate,
|
||||||
|
privateKey: newPrivateKey
|
||||||
|
} = generateCertificate(oCertificateParameters);
|
||||||
|
|
||||||
|
// load certificate authority certificate and privateKey
|
||||||
|
const rootCACertificate = loadCertificate(sRootCertificateId);
|
||||||
|
const rootCAPrivateKey = loadPrivateKey(sRootCertificateId);
|
||||||
|
|
||||||
|
// set rootCA as issuer
|
||||||
|
newCertificate.setIssuer(rootCACertificate.subject.attributes);
|
||||||
|
// sign certificate with root certificate
|
||||||
|
newCertificate.sign(rootCAPrivateKey, forge.md.sha256.create());
|
||||||
|
|
||||||
|
// save certificate and keys
|
||||||
|
saveCertificate(newCertificate, newCertificateId, 'intermediateCA');
|
||||||
|
savePrivateKey(newPrivateKey, newCertificateId, 'intermediateCA');
|
||||||
|
saveCertificateToDatabase(newCertificateId, {
|
||||||
|
displayName: Params.commonName,
|
||||||
|
certificateType: 'intermediateCA'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`new intermediateCA certificate with uid ${newCertificateId} saved`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createIdentityCertificate(Params) {
|
||||||
|
let {
|
||||||
|
certificateId,
|
||||||
|
certificate,
|
||||||
|
privateKey,
|
||||||
|
publicKey
|
||||||
|
} = generateCertificate(Params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funktion aufrufen, um das Root-CA-Zertifikat zu generieren
|
||||||
|
console.log((await createRootCertificate({
|
||||||
|
duration: '10y',
|
||||||
|
emailAddress: 'kai@waggeling.net',
|
||||||
|
commonName: 'waggeling.net rootCA',
|
||||||
|
countryName: 'DE',
|
||||||
|
stateOrProvinceName: 'Sachsen',
|
||||||
|
localityName: 'Leipzig',
|
||||||
|
organizationName: 'WCloud',
|
||||||
|
organizationalUnitName: 'TEst'
|
||||||
|
})));
|
||||||
51
functions/pki.utils.mjs
Normal file
51
functions/pki.utils.mjs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
default as forge
|
||||||
|
} from "node-forge";
|
||||||
|
|
||||||
|
import {
|
||||||
|
readFileSync,
|
||||||
|
writeFileSync,
|
||||||
|
mkdirSync
|
||||||
|
} from "fs";
|
||||||
|
|
||||||
|
|
||||||
|
export function loadCertificate(sCertID) {
|
||||||
|
return forge.pki.certificateFromPem(
|
||||||
|
readFileSync(`datastore/certificates/${sCertID}/certificate.pem`, 'utf8')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadPrivateKey(sCertID) {
|
||||||
|
return forge.pki.privateKeyFromPem(
|
||||||
|
readFileSync(`datastore/certificates/${sCertID}/privateKey.pem`, 'utf8')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function saveCertificate(iCertificate, sCertID, sCertType) {
|
||||||
|
// encode certificates as PEM
|
||||||
|
const certificate = forge.pki.certificateToPem(iCertificate);
|
||||||
|
|
||||||
|
// create certificate directory
|
||||||
|
mkdirSync(`datastore/certificates/`, { recursive: true });
|
||||||
|
|
||||||
|
// write certificate to file
|
||||||
|
writeFileSync(`datastore/certificates/${sCertID}.crt`, certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function savePrivateKey(iPrivateKey, sCertID) {
|
||||||
|
// encode keys as PEM
|
||||||
|
const privateKey = forge.pki.privateKeyToPem(iPrivateKey);
|
||||||
|
|
||||||
|
// create key directory
|
||||||
|
mkdirSync(`datastore/keys/`, { recursive: true });
|
||||||
|
|
||||||
|
// write keys to file
|
||||||
|
writeFileSync(`datastore/keys/${sCertID}.key`, privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function generatePublicKeyFromPrivateKey(iPrivateKey) {
|
||||||
|
return forge.pki.rsa.setPublicKey(iPrivateKey.n, iPrivateKey.e);
|
||||||
|
}
|
||||||
92
functions/structure.mjs
Normal file
92
functions/structure.mjs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
Op
|
||||||
|
} from "sequelize";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ElementTable,
|
||||||
|
AttributeTable
|
||||||
|
} from "../database/models/structure.mjs";
|
||||||
|
|
||||||
|
|
||||||
|
export async function CreateDirectory(parentId, name)
|
||||||
|
{
|
||||||
|
let ParentDirectory = await ElementTable.findByPk(parentId);
|
||||||
|
|
||||||
|
if (ParentDirectory == null) {
|
||||||
|
var NewDirectory = await ElementTable.create({
|
||||||
|
type: 'directory'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var NewDirectory = await ParentDirectory.createChild({
|
||||||
|
type: 'directory'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await NewDirectory.createAttribute({
|
||||||
|
version: NewDirectory.latestVersion,
|
||||||
|
contentType: 'name',
|
||||||
|
contentValue: name
|
||||||
|
})
|
||||||
|
|
||||||
|
return NewDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetDirectory(directoryId, version = null)
|
||||||
|
{
|
||||||
|
let Directory = await ElementTable.findByPk(directoryId);
|
||||||
|
|
||||||
|
if (Directory == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == null) {
|
||||||
|
version = Directory.latestVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
let Attributes = await Directory.getAttribute()
|
||||||
|
|
||||||
|
var Result = {
|
||||||
|
id: Directory.id,
|
||||||
|
version
|
||||||
|
}
|
||||||
|
|
||||||
|
Attributes.forEach((Attribute) => {
|
||||||
|
Result[Attribute.contentType] = Attribute.contentValue
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetDirectories(parentId = null)
|
||||||
|
{
|
||||||
|
let Directories = await ElementTable.findAll({
|
||||||
|
where: {
|
||||||
|
[Op.and]: {
|
||||||
|
type: 'directory',
|
||||||
|
parentId: parentId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Directories = Directories.map(async (Directory) => {
|
||||||
|
let Attributes = await Directory.getAttribute()
|
||||||
|
|
||||||
|
var Result = {
|
||||||
|
id: Directory.id,
|
||||||
|
version: Directory.latestVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(Attributes);
|
||||||
|
|
||||||
|
Attributes.forEach((Attribute) => {
|
||||||
|
Result[Attribute.contentType] = Attribute.contentValue
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(Directories);
|
||||||
|
|
||||||
|
return Directories;
|
||||||
|
}
|
||||||
43
master.mjs
Normal file
43
master.mjs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
|
||||||
|
// Load WebServer
|
||||||
|
import {
|
||||||
|
default as express
|
||||||
|
} from "express";
|
||||||
|
|
||||||
|
import {
|
||||||
|
router
|
||||||
|
} from "express-file-routing"
|
||||||
|
|
||||||
|
// Load Templating Engine
|
||||||
|
import nunjucks from "nunjucks";
|
||||||
|
|
||||||
|
// Initialize WebServer
|
||||||
|
const expressApp = express();
|
||||||
|
const expressPort = 3000;
|
||||||
|
|
||||||
|
// Middleware, um CORS zu aktivieren
|
||||||
|
expressApp.use((req, res, next) => {
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
|
||||||
|
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure Templating Engine
|
||||||
|
nunjucks.configure('./', {
|
||||||
|
autoescape: false,
|
||||||
|
express: expressApp,
|
||||||
|
noCache: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mount Middlewares to WebServer
|
||||||
|
expressApp.use(express.json());
|
||||||
|
expressApp.use(express.urlencoded());
|
||||||
|
expressApp.use(express.static('./assets/'))
|
||||||
|
// Mount Routes to WebServer
|
||||||
|
expressApp.use("/", await router())
|
||||||
|
|
||||||
|
// Server starten
|
||||||
|
expressApp.listen(expressPort, () => {
|
||||||
|
console.log(`Server is listening on port ${expressPort}`);
|
||||||
|
});
|
||||||
20
package.json
Normal file
20
package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "script-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "master.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"start": "npm i && node ./master.mjs"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Kai Uwe Waggeling",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^5.0.1",
|
||||||
|
"express-file-routing": "^3.0.3",
|
||||||
|
"node-json-db": "^2.3.0",
|
||||||
|
"nunjucks": "^3.2.4",
|
||||||
|
"validate": "^5.2.0",
|
||||||
|
"yaml": "^2.7.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
84
routes/api.queue.mjs
Normal file
84
routes/api.queue.mjs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
Router
|
||||||
|
} from "express";
|
||||||
|
|
||||||
|
|
||||||
|
export var Routes = Router();
|
||||||
|
|
||||||
|
// Routes.get('/queues', async function (Request, Response)
|
||||||
|
// {
|
||||||
|
// let QueueList = await QueueTable.findAll();
|
||||||
|
|
||||||
|
// QueueList.sort(function (a, b) {
|
||||||
|
// if (a.name < b.name) return -1;
|
||||||
|
// if (a.name > b.name) return 1;
|
||||||
|
// return 0;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Response.status(200);
|
||||||
|
// Response.send(QueueList);
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// Routes.get('/queue/:QueueID', async function (Request, Response)
|
||||||
|
// {
|
||||||
|
// let Queue = await QueueTable.findByPk(Request.params.QueueID);
|
||||||
|
|
||||||
|
// Response.status(200);
|
||||||
|
// Response.send(Queue);
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// Routes.post('/queue', async function (Request, Response)
|
||||||
|
// {
|
||||||
|
// let Queue = await QueueTable.create({
|
||||||
|
// name: Request.body.name
|
||||||
|
// });
|
||||||
|
|
||||||
|
// await Queue.setPrinter(Request.body.printerId)
|
||||||
|
|
||||||
|
// Response.status(200);
|
||||||
|
// Response.send(Queue);
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// Routes.put('/queue', async function (Request, Response)
|
||||||
|
// {
|
||||||
|
// let Queue = await QueueTable.findByPk(Request.body.id);
|
||||||
|
|
||||||
|
// await Queue.update({
|
||||||
|
// name: Request.body.name
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (Request.body.printerId == "null") {
|
||||||
|
// await Queue.setPrinter(null);
|
||||||
|
// } else {
|
||||||
|
// await Queue.setPrinter(Request.body.printerId)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Response.status(200);
|
||||||
|
// Response.send(Queue);
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// Routes.delete('/queue/:QueueID', async function (Request, Response)
|
||||||
|
// {
|
||||||
|
// await (await QueueTable.findByPk(Request.params.QueueID)).destroy();
|
||||||
|
|
||||||
|
// Response.status(200);
|
||||||
|
// Response.end();
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// Routes.put('/queue/printer', async function (Request, Response)
|
||||||
|
// {
|
||||||
|
// let Queue = await QueueTable.findByPk(Request.params.queueId);
|
||||||
|
// let Printer = await PrinterTable.findByPk(Request.params.printerId);
|
||||||
|
|
||||||
|
// await Queue.setPrinter(Printer);
|
||||||
|
|
||||||
|
// Response.status(200);
|
||||||
|
// Response.end();
|
||||||
|
// })
|
||||||
|
|
||||||
6
routes/master.mjs
Normal file
6
routes/master.mjs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
export const get = async (request, response) => {
|
||||||
|
if (request.method !== "GET") return response.status(405)
|
||||||
|
|
||||||
|
response.render(`ui/master.njk`);
|
||||||
|
}
|
||||||
153
ui/components/editor.njk
Normal file
153
ui/components/editor.njk
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
<!-- drawer component -->
|
||||||
|
<div id="drawer-form"
|
||||||
|
class="fixed top-0 left-0 z-40 h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white w-80 dark:bg-gray-800"
|
||||||
|
tabindex="-1" aria-labelledby="drawer-form-label">
|
||||||
|
<h5 id="drawer-label"
|
||||||
|
class="inline-flex items-center mb-6 text-base font-semibold text-gray-500 uppercase dark:text-gray-400"><svg
|
||||||
|
class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
|
||||||
|
viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
d="M0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm14-7.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm0 4a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm-5-4a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm0 4a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm-5-4a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1Zm0 4a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1ZM20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4Z" />
|
||||||
|
</svg>New event</h5>
|
||||||
|
<button type="button" data-drawer-hide="drawer-form" aria-controls="drawer-form"
|
||||||
|
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 absolute top-2.5 end-2.5 inline-flex items-center justify-center dark:hover:bg-gray-600 dark:hover:text-white">
|
||||||
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Close menu</span>
|
||||||
|
</button>
|
||||||
|
<form class="mb-6">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="title" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Title</label>
|
||||||
|
<input type="text" id="title"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Apple Keynote" required />
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="description"
|
||||||
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Description</label>
|
||||||
|
<textarea id="description" rows="4"
|
||||||
|
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Write event description..."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="relative mb-6">
|
||||||
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input datepicker="" datepicker-autohide datepicker-buttons="" type="text"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500 datepicker-input"
|
||||||
|
placeholder="Select date">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="guests" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Invite
|
||||||
|
guests</label>
|
||||||
|
<div class="relative">
|
||||||
|
<input type="search" id="guests"
|
||||||
|
class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
|
placeholder="Add guest email" required />
|
||||||
|
<button type="button"
|
||||||
|
class="absolute inline-flex items-center px-3 py-1 text-sm font-medium text-white bg-blue-700 rounded-lg end-2 bottom-2 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"><svg
|
||||||
|
class="w-3 h-3 me-1.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
|
||||||
|
viewBox="0 0 20 18">
|
||||||
|
<path
|
||||||
|
d="M6.5 9a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9ZM8 10H5a5.006 5.006 0 0 0-5 5v2a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-2a5.006 5.006 0 0 0-5-5Zm11-3h-2V5a1 1 0 0 0-2 0v2h-2a1 1 0 1 0 0 2h2v2a1 1 0 0 0 2 0V9h2a1 1 0 1 0 0-2Z" />
|
||||||
|
</svg>Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex mb-4 -space-x-4 rtl:space-x-reverse">
|
||||||
|
<img class="w-8 h-8 border-2 border-white rounded-full dark:border-gray-800"
|
||||||
|
src="/docs/images/people/profile-picture-5.jpg" alt="">
|
||||||
|
<img class="w-8 h-8 border-2 border-white rounded-full dark:border-gray-800"
|
||||||
|
src="/docs/images/people/profile-picture-2.jpg" alt="">
|
||||||
|
<img class="w-8 h-8 border-2 border-white rounded-full dark:border-gray-800"
|
||||||
|
src="/docs/images/people/profile-picture-3.jpg" alt="">
|
||||||
|
<img class="w-8 h-8 border-2 border-white rounded-full dark:border-gray-800"
|
||||||
|
src="/docs/images/people/profile-picture-4.jpg" alt="">
|
||||||
|
</div>
|
||||||
|
<button type="submit"
|
||||||
|
class="text-white justify-center flex items-center bg-blue-700 hover:bg-blue-800 w-full focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"><svg
|
||||||
|
class="w-3.5 h-3.5 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
|
||||||
|
viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
d="M18 2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2ZM2 18V7h6.7l.4-.409A4.309 4.309 0 0 1 15.753 7H18v11H2Z" />
|
||||||
|
<path
|
||||||
|
d="M8.139 10.411 5.289 13.3A1 1 0 0 0 5 14v2a1 1 0 0 0 1 1h2a1 1 0 0 0 .7-.288l2.886-2.851-3.447-3.45ZM14 8a2.463 2.463 0 0 0-3.484 0l-.971.983 3.468 3.468.987-.971A2.463 2.463 0 0 0 14 8Z" />
|
||||||
|
</svg> Create event</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- speed dial -->
|
||||||
|
<div data-dial-init class="fixed end-6 bottom-6 group">
|
||||||
|
<div id="editor-actions" class="flex flex-col items-center hidden mb-4 space-y-2">
|
||||||
|
<button type="button" data-tooltip-target="tooltip-share" data-tooltip-placement="left"
|
||||||
|
class="flex justify-center items-center w-[52px] h-[52px] text-gray-500 hover:text-gray-900 bg-white rounded-lg border border-gray-200 dark:border-gray-600 shadow-sm dark:hover:text-white dark:text-gray-400 hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600 focus:ring-4 focus:ring-gray-300 focus:outline-none dark:focus:ring-gray-400">
|
||||||
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
|
||||||
|
viewBox="0 0 18 18">
|
||||||
|
<path
|
||||||
|
d="M14.419 10.581a3.564 3.564 0 0 0-2.574 1.1l-4.756-2.49a3.54 3.54 0 0 0 .072-.71 3.55 3.55 0 0 0-.043-.428L11.67 6.1a3.56 3.56 0 1 0-.831-2.265c.006.143.02.286.043.428L6.33 6.218a3.573 3.573 0 1 0-.175 4.743l4.756 2.491a3.58 3.58 0 1 0 3.508-2.871Z" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Share</span>
|
||||||
|
</button>
|
||||||
|
<div id="tooltip-share" role="tooltip"
|
||||||
|
class="absolute z-10 invisible inline-block w-auto px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
||||||
|
Share
|
||||||
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
|
</div>
|
||||||
|
<button type="button" data-tooltip-target="tooltip-print" data-tooltip-placement="left"
|
||||||
|
class="flex justify-center items-center w-[52px] h-[52px] text-gray-500 hover:text-gray-900 bg-white rounded-lg border border-gray-200 dark:border-gray-600 shadow-sm dark:hover:text-white dark:text-gray-400 hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600 focus:ring-4 focus:ring-gray-300 focus:outline-none dark:focus:ring-gray-400">
|
||||||
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
|
||||||
|
viewBox="0 0 20 20">
|
||||||
|
<path d="M5 20h10a1 1 0 0 0 1-1v-5H4v5a1 1 0 0 0 1 1Z" />
|
||||||
|
<path
|
||||||
|
d="M18 7H2a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2v-3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2Zm-1-2V2a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v3h14Z" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Print</span>
|
||||||
|
</button>
|
||||||
|
<div id="tooltip-print" role="tooltip"
|
||||||
|
class="absolute z-10 invisible inline-block w-auto px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
||||||
|
Print
|
||||||
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
|
</div>
|
||||||
|
<button type="button" data-tooltip-target="tooltip-download" data-tooltip-placement="left"
|
||||||
|
class="flex justify-center items-center w-[52px] h-[52px] text-gray-500 hover:text-gray-900 bg-white rounded-lg border border-gray-200 dark:border-gray-600 shadow-sm dark:hover:text-white dark:text-gray-400 hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600 focus:ring-4 focus:ring-gray-300 focus:outline-none dark:focus:ring-gray-400">
|
||||||
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
|
||||||
|
viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
d="M14.707 7.793a1 1 0 0 0-1.414 0L11 10.086V1.5a1 1 0 0 0-2 0v8.586L6.707 7.793a1 1 0 1 0-1.414 1.414l4 4a1 1 0 0 0 1.416 0l4-4a1 1 0 0 0-.002-1.414Z" />
|
||||||
|
<path
|
||||||
|
d="M18 12h-2.55l-2.975 2.975a3.5 3.5 0 0 1-4.95 0L4.55 12H2a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2Zm-3 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Download</span>
|
||||||
|
</button>
|
||||||
|
<div id="tooltip-download" role="tooltip"
|
||||||
|
class="absolute z-10 invisible inline-block w-auto px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
||||||
|
Download
|
||||||
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
|
</div>
|
||||||
|
<button type="button" data-tooltip-target="tooltip-copy" data-tooltip-placement="left"
|
||||||
|
class="flex justify-center items-center w-[52px] h-[52px] text-gray-500 hover:text-gray-900 bg-white rounded-lg border border-gray-200 dark:border-gray-600 dark:hover:text-white shadow-sm dark:text-gray-400 hover:bg-gray-50 dark:bg-gray-700 dark:hover:bg-gray-600 focus:ring-4 focus:ring-gray-300 focus:outline-none dark:focus:ring-gray-400">
|
||||||
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor"
|
||||||
|
viewBox="0 0 18 20">
|
||||||
|
<path
|
||||||
|
d="M5 9V4.13a2.96 2.96 0 0 0-1.293.749L.879 7.707A2.96 2.96 0 0 0 .13 9H5Zm11.066-9H9.829a2.98 2.98 0 0 0-2.122.879L7 1.584A.987.987 0 0 0 6.766 2h4.3A3.972 3.972 0 0 1 15 6v10h1.066A1.97 1.97 0 0 0 18 14V2a1.97 1.97 0 0 0-1.934-2Z" />
|
||||||
|
<path
|
||||||
|
d="M11.066 4H7v5a2 2 0 0 1-2 2H0v7a1.969 1.969 0 0 0 1.933 2h9.133A1.97 1.97 0 0 0 13 18V6a1.97 1.97 0 0 0-1.934-2Z" />
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Copy</span>
|
||||||
|
</button>
|
||||||
|
<div id="tooltip-copy" role="tooltip"
|
||||||
|
class="absolute z-10 invisible inline-block w-auto px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
||||||
|
Copy
|
||||||
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" data-dial-toggle="editor-actions" aria-controls="editor-actions" aria-expanded="false"
|
||||||
|
class="flex items-center justify-center text-white bg-blue-700 rounded-lg w-14 h-14 hover:bg-blue-800 dark:bg-blue-600 dark:hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 focus:outline-none dark:focus:ring-blue-800">
|
||||||
|
<i class="ti ti-plus text-3xl transition-transform group-hover:rotate-45"></i>
|
||||||
|
<span class="sr-only">Open actions menu</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
24
ui/components/meta.njk
Normal file
24
ui/components/meta.njk
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<link rel="icon" type="image/x-icon" href="/img/AppLogo.svg">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/flowbite@2.5.1/dist/flowbite.min.css" rel="stylesheet" />
|
||||||
|
<!-- <link rel="stylesheet" href="/css/bootstrap.min.css"> -->
|
||||||
|
<link rel="stylesheet" href="/css/tabler-icons.min.css">
|
||||||
|
<!-- <script src="/js/jquery-3.6.4.min.js"></script>
|
||||||
|
<script src="/js/bootstrap.bundle.min.js"></script>-->
|
||||||
|
<!-- <script src="/js/vue.global.prod.js"></script> -->
|
||||||
|
<script src="https://unpkg.com/vue@latest"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader"></script>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
/* width: 100svw; */
|
||||||
|
min-height: 100svh;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
background: var(--bs-body-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
50
ui/components/modals.njk
Normal file
50
ui/components/modals.njk
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
|
||||||
|
{% macro insertCreateDirectoryModal() %}
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="createDirectoryModal" tabindex="-1" data-bs-backdrop="static">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="exampleModalLabel">Create New Directory</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="createDirectoryForm">
|
||||||
|
<div class="flex-fill">
|
||||||
|
<label class="form-label">Directory Name:</label>
|
||||||
|
<input type="text" class="form-control" name="name" placeholder="New Directory">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="buttonCancelCreateDirectory" class="btn btn-sm btn-outline-warning px-2" title="save directory" data-bs-toggle="modal" data-bs-target="#createDirectoryModal">
|
||||||
|
<i class="ti ti-x me-2"></i>Cancel
|
||||||
|
</button>
|
||||||
|
<button id="buttonSaveCreateDirectory" class="btn btn-sm btn-outline-primary px-2" title="save directory">
|
||||||
|
<i class="ti ti-device-floppy me-2"></i>Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$('#buttonSaveCreateDirectory').on('click', async () => {
|
||||||
|
console.log("click");
|
||||||
|
var Response = await fetch(window.location.pathname,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(
|
||||||
|
Object.fromEntries(
|
||||||
|
new FormData(document.querySelector('#createDirectoryForm'))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}).then(() => {
|
||||||
|
$('#createDirectoryModal').modal('hide');
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(Response);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endmacro %}
|
||||||
110
ui/components/navbar.njk
Normal file
110
ui/components/navbar.njk
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
<header class="bg-white dark:bg-gray-900 shadow-md sticky top-0 z-50 w-full divide-y divide-gray-300 dark:divide-gray-700 flex flex-col">
|
||||||
|
<nav class="container flex justify-between items-center py-4 mx-auto relative">
|
||||||
|
<!-- Application Logo -->
|
||||||
|
<a href="https://flowbite.com/" class="flex items-center space-x-3 rtl:space-x-reverse z-10">
|
||||||
|
<img src="https://flowbite.com/docs/images/logo.svg" class="h-6" alt="Flowbite Logo" />
|
||||||
|
<span class="self-center text-xl font-semibold whitespace-nowrap">Bookmark Manager</span>
|
||||||
|
</a>
|
||||||
|
<!-- Quick Search -->
|
||||||
|
<div class="flex absolute w-full z-0">
|
||||||
|
<form class="flex items-stretch w-1/4 max-w-sm mx-auto gap-2">
|
||||||
|
<input type="text" id="simple-search"
|
||||||
|
class="bg-gray-50 border border-gray-300 text-sm rounded-md focus:border-blue-500 block w-full px-4 py-2 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400"
|
||||||
|
placeholder="search bookmarks..." required />
|
||||||
|
<button type="submit" class="py-1 px-4 text-white bg-blue-700 rounded-md border border-blue-700 hover:bg-blue-800 outline-none dark:bg-blue-600 dark:hover:bg-blue-700">
|
||||||
|
<i class="ti ti-search"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- User Menu -->
|
||||||
|
<div class="flex flex-row gap-2 z-10">
|
||||||
|
<a href="#" class="flex items-center px-4 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800" @click="toggleEditor()">
|
||||||
|
<i :class="['ti','ti-edit',modeEdit ? 'text-blue-700 dark:text-blue-600' : '']"></i>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center relative md:order-2">
|
||||||
|
<button type="button" data-dropdown-toggle="user-dropdown"
|
||||||
|
class="font-medium text-sm px-4 py-2 cursor-pointer rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white">
|
||||||
|
Kai Waggeling
|
||||||
|
</button>
|
||||||
|
<div id="user-dropdown" class="w-48 hidden my-4 text-base bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600 overflow-hidden absolute right-0">
|
||||||
|
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">
|
||||||
|
Sign out
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<nav class="container flex justify-between items-center py-4 mx-auto relative">
|
||||||
|
<div class="flex flex-row align-stretch gap-8">
|
||||||
|
<a href="#" class="block py-1 text-blue-700 dark:text-blue-500">
|
||||||
|
Start
|
||||||
|
</a>
|
||||||
|
<a href="#" class="block py-1 text-gray-900 dark:text-white hover:text-blue-700 dark:hover:text-blue-500">
|
||||||
|
Bookmarks
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<!-- <nav class="bg-white border-gray-200 dark:bg-gray-900">
|
||||||
|
<div class="container flex flex-wrap items-center justify-between mx-auto p-4">
|
||||||
|
<a href="https://flowbite.com/" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||||
|
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" />
|
||||||
|
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Bookmark Manager</span>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center md:order-2 space-x-1 md:space-x-0 rtl:space-x-reverse">
|
||||||
|
<button type="button" data-dropdown-toggle="user-dropdown"
|
||||||
|
class="inline-flex items-center font-medium justify-center px-4 py-2 text-sm text-gray-900 dark:text-white rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 dark:hover:text-white">
|
||||||
|
Kai Waggeling
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600"
|
||||||
|
id="user-dropdown">
|
||||||
|
<ul class="py-2" aria-labelledby="user-menu-button">
|
||||||
|
<li>
|
||||||
|
<a href="#"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#"
|
||||||
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">
|
||||||
|
Sign out
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<button data-collapse-toggle="navbar-language" type="button"
|
||||||
|
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
|
||||||
|
aria-controls="navbar-language" aria-expanded="false">
|
||||||
|
<span class="sr-only">Open main menu</span>
|
||||||
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
|
viewBox="0 0 17 14">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M1 1h15M1 7h15M1 13h15" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-language">
|
||||||
|
<ul
|
||||||
|
class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
||||||
|
<li>
|
||||||
|
<a href="#"
|
||||||
|
class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500"
|
||||||
|
aria-current="page">
|
||||||
|
Start
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#"
|
||||||
|
class="block py-2 px-3 md:p-0 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700">
|
||||||
|
Bookmarks
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav> -->
|
||||||
37
ui/components/widgets copy.njk
Normal file
37
ui/components/widgets copy.njk
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
{% macro jumbotron(id='', title='', text='') %}
|
||||||
|
<section class="w-full">
|
||||||
|
<div class="py-4 px-4 mx-auto max-w-screen-xl text-center">
|
||||||
|
<h1 class="my-4 font-extrabold tracking-tight leading-none text-gray-900 text-4xl md:text-5xl lg:text-6xl">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p class="my-4 text-lg font-normal text-gray-500 lg:text-xl sm:px-16 lg:px-48">
|
||||||
|
{{ text }}
|
||||||
|
</p>
|
||||||
|
{{ caller() }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro container(id='', width='w-full', flow='row', vAlign='center', hAlign='center') %}
|
||||||
|
<div class="container mx-auto {{width}} pt-24 pb-32 flex flex-{{flow}} justify-{{hAlign}} items-{{vAlign}}">
|
||||||
|
{{ caller() }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro grid(id='', title='', columns=3) %}
|
||||||
|
<div class="my-4 grid grid-cols-1 md:grid-cols-{{2 if columns > 2 else columns}} lg:grid-cols-{{columns}} gap-6">
|
||||||
|
{{ caller() }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro link(id='', link='#', title='New Link', description='Description') %}
|
||||||
|
<a href="{{ link }}" target="_blank" class="p-8 bg-gray-50 rounded-md border border-gray-200 hover:border-purple-400">
|
||||||
|
<h4 class="font-medium text-gray-700 text-lg mb-4">
|
||||||
|
{{ title }}
|
||||||
|
</h4>
|
||||||
|
<p class="font-normal text-gray-500 text-md">
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
{% endmacro %}
|
||||||
14
ui/components/widgets.njk
Normal file
14
ui/components/widgets.njk
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{% raw %}
|
||||||
|
<template id="template-jumbotron">
|
||||||
|
<section class="w-full">
|
||||||
|
<div class="py-4 px-4 mx-auto max-w-screen-xl text-center">
|
||||||
|
<h1 class="my-4 font-extrabold tracking-tight leading-none text-gray-900 text-4xl md:text-5xl lg:text-6xl">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p class="my-4 text-lg font-normal text-gray-500 lg:text-xl sm:px-16 lg:px-48">
|
||||||
|
{{ text }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
{% endraw %}
|
||||||
17
ui/error.njk
Normal file
17
ui/error.njk
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "./master.njk" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex align-items-center justify-content-center py-5">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="display-1 fw-bold">{{ Error.Code }}</h1>
|
||||||
|
<p class="fs-3"> <span class="text-danger">Opps!</span> {{ Error.Title }}</p>
|
||||||
|
<p class="lead">
|
||||||
|
{{ Error.Message }}
|
||||||
|
</p>
|
||||||
|
<a href="{{ Error.Link }}" class="btn btn-primary">Go Back</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
35
ui/master copy.njk
Normal file
35
ui/master copy.njk
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Shell Script Hub</title>
|
||||||
|
{% include "./components/meta.njk" %}
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||||
|
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-100 dark:bg-gray-800 dark:text-white">
|
||||||
|
<div id="app" class="w-full">
|
||||||
|
{% include "./components/navbar.njk" %}
|
||||||
|
{% include "./components/editor.njk" %}
|
||||||
|
<div class="container relative mx-auto py-12 flex flex-col justify-center items-center gap-16">
|
||||||
|
<dynamic-root :elements="elements"></dynamic-root>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/flowbite@2.5.1/dist/flowbite.min.js"></script>
|
||||||
|
<!-- <script src="/widgets.jumbotron.js"></script> -->
|
||||||
|
<script src="/start.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
43
ui/master.njk
Normal file
43
ui/master.njk
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{% import "./widgets/jumbotron.njk" as jumbotron %}
|
||||||
|
{% import "./widgets/title.njk" as title %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Shell Script Hub</title>
|
||||||
|
{% include "./components/meta.njk" %}
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
|
||||||
|
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-100 dark:bg-gray-800 dark:text-white">
|
||||||
|
<div id="app" class="w-full flex flex-col gap-12">
|
||||||
|
{% include "./components/navbar.njk" %}
|
||||||
|
<div class="container mx-auto relative grid grid-cols-5 gap-4">
|
||||||
|
<div class="col-span-full row-span-1 border border-gray-500 rounded">{{ jumbotron.createWidget("MTitle", "Message", { center: false }) }}</div>
|
||||||
|
<div class="col-span-1 row-span-2 border border-gray-500 rounded">2</div>
|
||||||
|
<div class="col-span-full row-span-1 border border-gray-500 rounded">{{ title.alignLeft("MTitle") }}</div>
|
||||||
|
<div class="col-span-1 row-span-1 border border-gray-500 rounded">4</div>
|
||||||
|
<div class="col-span-1 row-span-1 border border-gray-500 rounded">5</div>
|
||||||
|
<div class="col-span-1 row-span-2 border border-gray-500 rounded">6</div>
|
||||||
|
<div class="col-span-1 row-span-1 border border-gray-500 rounded">7</div>
|
||||||
|
<div class="col-span-1 row-span-1 border border-gray-500 rounded">8</div>
|
||||||
|
<div class="col-span-1 row-span-1 border border-gray-500 rounded">9</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/flowbite@2.5.1/dist/flowbite.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
31
ui/widgets/jumbotron.njk
Normal file
31
ui/widgets/jumbotron.njk
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
{% macro createWidget(title, text, settings) %}
|
||||||
|
<section class="flex flex-row py-6 px-8">
|
||||||
|
{% if settings.center == true %}
|
||||||
|
<div class="w-full flex flex-col gap-4">
|
||||||
|
<h1 class="font-extrabold tracking-tight leading-none text-gray-900 text-4xl md:text-5xl lg:text-6xl text-center">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p class="w-full text-lg font-normal text-gray-500 lg:text-xl mx-2 text-center">
|
||||||
|
{{ text }}
|
||||||
|
</p>
|
||||||
|
{% if ctaSettings and ctaSettings.url %}
|
||||||
|
<div class="inline-flex flex-row">
|
||||||
|
<a href="{{ ctaSettings.url }}" class="py-3 px-5 font-medium text-white rounded-lg bg-blue-700 hover:bg-blue-800">
|
||||||
|
{{ ctaSettings.text }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="w-full flex flex-col gap-4">
|
||||||
|
<h1 class="font-extrabold tracking-tight leading-none text-gray-900 text-4xl md:text-5xl lg:text-6xl">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg font-normal text-gray-500 lg:text-xl mx-2">
|
||||||
|
{{ text }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
{% endmacro %}
|
||||||
16
ui/widgets/title.njk
Normal file
16
ui/widgets/title.njk
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
{% macro alignLeft(title) %}
|
||||||
|
<div class="w-full flex flex-col p-4">
|
||||||
|
<h1 class="font-extrabold text-gray-900 text-4xl md:text-5xl lg:text-6xl text-left pb-4 border-b border-gray-500">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro largeLeft(title, settings) %}
|
||||||
|
<div class="w-full flex flex-col py-4">
|
||||||
|
<h1 class="font-extrabold text-gray-900 text-7xl md:text-8xl lg:text-9xl text-center">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue