Compare commits
137 commits
parcel-bun
...
master
Author | SHA1 | Date | |
---|---|---|---|
b1db76d7c5 | |||
7e56c51ca9 | |||
280e67693e | |||
ae50943802 | |||
c719db5657 | |||
e00442b9da | |||
b38c66a0f0 | |||
b5d6cbfacb | |||
43ccbd72f7 | |||
bab8fc435e | |||
|
71ce6ee81b | ||
|
5df5d7412e | ||
|
ff731ce18e | ||
|
3ca2112351 | ||
|
fabd83f1f4 | ||
|
2613032cd0 | ||
|
cb475c3746 | ||
|
2c3bc4b4e7 | ||
|
0348d52123 | ||
|
ae0dac4437 | ||
|
3f214a4cac | ||
50dbff7f2c | |||
19beccff30 | |||
3457314e87 | |||
50689d2b83 | |||
f0e92fe024 | |||
b2efb92a53 | |||
fc75c07473 | |||
0e0ca76afb | |||
ffc750c405 | |||
3ab7888a2a | |||
f140d1e271 | |||
ccb5af6c8e | |||
9abcba556c | |||
8c5b68037a | |||
d465f5ecb8 | |||
bfae5cc098 | |||
0f70b811f9 | |||
7b56fbbdcd | |||
a6a470f8aa | |||
aa670a0512 | |||
bbc1fb852f | |||
b8605da2b3 | |||
fa119a00cd | |||
8131709a88 | |||
9b6c5d0fb8 | |||
8c2252a0d4 | |||
65db25ae63 | |||
fa0d3baf0b | |||
ac00743cb8 | |||
b2dccc445c | |||
bae04c21cf | |||
8e990911c1 | |||
b18fe46788 | |||
04e198c6d4 | |||
031cccb475 | |||
e2524efc6a | |||
61bc809fa8 | |||
f155578daa | |||
c4685355b5 | |||
1781d611f3 | |||
68afb9607d | |||
49ef0459cd | |||
ffbbdae474 | |||
281a7c3ff4 | |||
9c236bba83 | |||
b4371f8db4 | |||
|
a150b0c744 | ||
b183bf90c1 | |||
|
15b36397ae | ||
|
b818a962d5 | ||
|
fa7109260e | ||
|
7fe912b172 | ||
|
97158b5f0c | ||
|
e305c030e6 | ||
|
92ce267baf | ||
|
abe8db6e8b | ||
|
9b12fe2c94 | ||
|
f132ba2c55 | ||
|
63c2fde0de | ||
|
356f839f9a | ||
|
49fa8a89b8 | ||
|
4fb15ec6d5 | ||
|
5c001a992b | ||
|
7e371dac06 | ||
|
bd9be3b0b5 | ||
|
d1cda0ba2f | ||
|
7509544b00 | ||
|
3eb89d763c | ||
|
0349c0533e | ||
947e64d94c | |||
b5a37776e8 | |||
4904526443 | |||
00740f441a | |||
ccfb317317 | |||
bc986d7547 | |||
093e63653c | |||
2ad5e46827 | |||
410dff42d4 | |||
bcf5f02f36 | |||
9e20e5e15a | |||
27dbbb1e8b | |||
2405d712fa | |||
9ddb43af2d | |||
dcb5262744 | |||
9bcc34ff43 | |||
8710d5e601 | |||
66312e4f93 | |||
bc4d5f1acb | |||
1e8ed63c24 | |||
ce30653d3b | |||
0eb8e4d22d | |||
5fbd725f6c | |||
fa5b9d9089 | |||
dbefc2ba8d | |||
187ddf9683 | |||
8deebbb1e1 | |||
abc8b4cacf | |||
8c9fa6f3b5 | |||
7cf19bbd06 | |||
40e6c049bf | |||
43dcd5871c | |||
c63cc3015b | |||
0af4202811 | |||
a108682032 | |||
fbf0932c88 | |||
f3dfeb2424 | |||
c0ee2ec89e | |||
e72b3acf0a | |||
23664a55bd | |||
37db2cc33d | |||
4c8fb1aae6 | |||
76fcf276f9 | |||
a5707e660f | |||
9e04efa8c7 | |||
d73e903044 | |||
9f215ebfb2 |
|
@ -1,7 +1,7 @@
|
|||
FROM alpine:edge
|
||||
RUN apk update && \
|
||||
apk upgrade && \
|
||||
apk add nodejs yarn php8 php8-mysqli php8-mbstring php8-curl php8-simplexml git nano composer openssh-client curl && \
|
||||
apk add nodejs yarn php8 php8-mysqli php8-mbstring php8-curl php8-simplexml git nano composer openssh-client curl gpg && \
|
||||
ln -s /usr/bin/php8 /usr/bin/php && \
|
||||
curl -L https://unpkg.com/@pnpm/self-installer | node
|
||||
|
||||
|
|
11
.gitignore
vendored
|
@ -1,5 +1,5 @@
|
|||
#config file
|
||||
src/API/lib/config.php
|
||||
public/API/lib/config.php
|
||||
|
||||
#phpstorm
|
||||
.idea/
|
||||
|
@ -7,14 +7,13 @@ src/API/lib/config.php
|
|||
# vscode
|
||||
.vscode/
|
||||
|
||||
public/css/
|
||||
public/js/
|
||||
.sass-cache/
|
||||
|
||||
#node cache
|
||||
node_modules/
|
||||
pnpm-lock.yaml
|
||||
|
||||
#composer
|
||||
src/API/vendor
|
||||
|
||||
# parcel
|
||||
dist/
|
||||
.parcel-cache/
|
||||
public/API/vendor
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
docker-build:
|
||||
# Use the official docker image.
|
||||
image: docker:latest
|
||||
image: gitlab.jonasled.de/jonasled/buildx-docker:latest
|
||||
stage: build
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker context create build
|
||||
- docker buildx create build --use
|
||||
- docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
# Default branch leaves tag empty (= latest tag)
|
||||
# All other branches are tagged with the escaped branch name (commit ref slug)
|
||||
script:
|
||||
- |
|
||||
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
|
||||
|
@ -17,10 +17,9 @@ docker-build:
|
|||
tag=":$CI_COMMIT_REF_SLUG"
|
||||
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
|
||||
fi
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
|
||||
- docker push "$CI_REGISTRY_IMAGE${tag}"
|
||||
- docker buildx build --platform linux/amd64,linux/arm,linux/arm64 --push --tag "$CI_REGISTRY_IMAGE${tag}" .
|
||||
# Run this job in a branch where a Dockerfile exists
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
exists:
|
||||
- Dockerfile
|
||||
- Dockerfile
|
||||
|
|
19
Dockerfile
|
@ -1,26 +1,33 @@
|
|||
# |--------------------------------------------------------------------------
|
||||
# | Build SCSS and JS
|
||||
# |--------------------------------------------------------------------------
|
||||
FROM node:lts-alpine AS buildJS
|
||||
FROM docker-proxy.jonasled.de/library/node:lts-alpine AS buildJS
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN yarn install
|
||||
RUN yarn compile
|
||||
RUN mkdir public/js
|
||||
RUN mkdir public/css
|
||||
RUN yarn install --network-timeout 1000000
|
||||
RUN yarn build
|
||||
|
||||
# |--------------------------------------------------------------------------
|
||||
# | Install PHP dependencies
|
||||
# |--------------------------------------------------------------------------
|
||||
FROM composer:2 AS composer
|
||||
FROM docker-proxy.jonasled.de/library/composer:2 AS composer
|
||||
WORKDIR /build
|
||||
COPY --from=buildJS /build/dist .
|
||||
COPY --from=buildJS /build .
|
||||
RUN composer install
|
||||
|
||||
# |--------------------------------------------------------------------------
|
||||
# | Install Webserver
|
||||
# |--------------------------------------------------------------------------
|
||||
FROM gitlab.jonasled.de/jonasled/nginx-php-minimal:8-latest
|
||||
FROM gitlab.jonasled.de/jonasled/nginx-php-minimal:8-latest as production
|
||||
ENV PHP_FILE_UPLOADS=Off \
|
||||
PHP_MAX_POST=1M
|
||||
|
||||
|
||||
RUN apk update && \
|
||||
apk add php8-mysqli php8-mbstring php8-curl php8-simplexml --no-cache && \
|
||||
rm /etc/nginx/http.d/default.conf
|
||||
|
||||
COPY --from=composer /build/public .
|
||||
COPY ./nginx.conf /etc/nginx/http.d/default.conf
|
||||
|
|
34
Readme.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Website
|
||||
This is the repo containing my personal website. It is based on simple HTML pages, with custom HTML componetes, for things like header and footer. The components are written in pure CS and compiled with webpackt to one big file. In this step also the dependencies are getting injected. Stylesheets are written in stylus and then compiled to CSS in the build process. For the backend there is a GraphQL endpoint at `/API/graphql.php` which holds most of the resources, for files (ebay images and S3) there are seperate endpoints in the API directory. Comments and the blog ist stored in a MySQL Database.
|
||||
|
||||
## Dev-Setup
|
||||
### Requirements
|
||||
* NodeJS
|
||||
* Yarn
|
||||
* PHP 8 with the following extensions:
|
||||
* mysqli
|
||||
* mbstring
|
||||
* curl
|
||||
* simplexml
|
||||
* Composer
|
||||
|
||||
### Setup
|
||||
1. Install the node dependencies: `yarn install`
|
||||
2. Install the PHP depdendencies: `composer install`
|
||||
3. Start the dev environment: `yarn watch`
|
||||
This will start the compile service for the JavaScript and the stylus files and also start a PHP dev server on port 1234
|
||||
|
||||
## Production-Setup
|
||||
For production there is a docker images with the following name available: `gitlab.jonasled.de/jonasled/website:latest`. The configuration is stored in the config PHP, a example file is available in this repo in the `/public/API/lib` folder. Later this file has to be mounted at `/var/www/html/API/lib/config.php` A example compose could look like this:
|
||||
```yaml
|
||||
version: '3.2'
|
||||
|
||||
services:
|
||||
website:
|
||||
image: gitlab.jonasled.de/jonasled/website:latest
|
||||
restart: always
|
||||
volumes:
|
||||
./config.php:/var/www/html/API/lib/config.php
|
||||
ports:
|
||||
- "80:80"
|
||||
```
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"require": {
|
||||
"aws/aws-sdk-php": "^3.181",
|
||||
"guzzlehttp/guzzle": "^7.0"
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"webonyx/graphql-php": "^14.11"
|
||||
},
|
||||
"config": {
|
||||
"vendor-dir": "public/API/vendor"
|
||||
|
|
70
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f47a9b8d286ca72493a7fe02f263976e",
|
||||
"content-hash": "31a3a0321659f9c8afff63a68a9fafb6",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
|
@ -669,6 +669,72 @@
|
|||
}
|
||||
],
|
||||
"time": "2021-05-27T12:26:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webonyx/graphql-php",
|
||||
"version": "v14.11.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webonyx/graphql-php.git",
|
||||
"reference": "ffa431c0821821839370a68dab3c2597c06bf7f0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webonyx/graphql-php/zipball/ffa431c0821821839370a68dab3c2597c06bf7f0",
|
||||
"reference": "ffa431c0821821839370a68dab3c2597c06bf7f0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8"
|
||||
},
|
||||
"require-dev": {
|
||||
"amphp/amp": "^2.3",
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"nyholm/psr7": "^1.2",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "0.12.82",
|
||||
"phpstan/phpstan-phpunit": "0.12.18",
|
||||
"phpstan/phpstan-strict-rules": "0.12.9",
|
||||
"phpunit/phpunit": "^7.2 || ^8.5",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/promise": "2.*",
|
||||
"simpod/php-coveralls-mirror": "^3.0",
|
||||
"squizlabs/php_codesniffer": "3.5.4"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/http-message": "To use standard GraphQL server",
|
||||
"react/promise": "To leverage async resolving on React PHP platform"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GraphQL\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A PHP port of GraphQL reference implementation",
|
||||
"homepage": "https://github.com/webonyx/graphql-php",
|
||||
"keywords": [
|
||||
"api",
|
||||
"graphql"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webonyx/graphql-php/issues",
|
||||
"source": "https://github.com/webonyx/graphql-php/tree/v14.11.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/webonyx-graphql-php",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2022-01-24T11:13:31+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
@ -679,5 +745,5 @@
|
|||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.1.0"
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
||||
|
|
67
js/customElements/404Buttons.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
class notFoundButtons extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.windowResized = false;
|
||||
|
||||
const redButton = document.createElement("div");
|
||||
redButton.classList.add("button");
|
||||
redButton.classList.add("red");
|
||||
this.appendChild(redButton);
|
||||
|
||||
const yellowButton = document.createElement("div");
|
||||
yellowButton.classList.add("button");
|
||||
yellowButton.classList.add("yellow");
|
||||
this.appendChild(yellowButton);
|
||||
|
||||
const greenButton = document.createElement("div");
|
||||
greenButton.classList.add("button");
|
||||
greenButton.classList.add("green");
|
||||
this.appendChild(greenButton);
|
||||
|
||||
greenButton.onclick
|
||||
|
||||
greenButton.onclick = () => {
|
||||
const terminal = document.querySelector(".terminal-window");
|
||||
if (!this.windowResized) {
|
||||
terminal.style.width = "95%";
|
||||
terminal.style.height = "95%";
|
||||
terminal.style.top = "2%";
|
||||
this.windowResized = true;
|
||||
} else {
|
||||
terminal.style.width = "37.5rem";
|
||||
terminal.style.height = "22.5rem";
|
||||
terminal.style.top = "10.5rem";
|
||||
this.windowResized = false;
|
||||
}
|
||||
}
|
||||
|
||||
redButton.onclick = () => {
|
||||
location.href = "https://jonasled.de";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return Math.max(
|
||||
document.body.scrollWidth,
|
||||
document.documentElement.scrollWidth,
|
||||
document.body.offsetWidth,
|
||||
document.documentElement.offsetWidth,
|
||||
document.documentElement.clientWidth
|
||||
);
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return Math.max(
|
||||
document.body.scrollHeight,
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.offsetHeight,
|
||||
document.documentElement.offsetHeight,
|
||||
document.documentElement.clientHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-404_buttons", notFoundButtons);
|
32
js/customElements/blogFooter.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
class blogFooter extends HTMLElement {
|
||||
connectedCallback(){
|
||||
this.getBlogEntries();
|
||||
}
|
||||
|
||||
async getBlogEntries() {
|
||||
let ul = document.createElement("ul");
|
||||
this.appendChild(ul);
|
||||
var graphql = JSON.stringify({
|
||||
query: 'query($count: Int!) { blogPosts(count: $count) { title id }}',
|
||||
variables: {
|
||||
"count": 5
|
||||
}
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
let posts = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.blogPosts;
|
||||
posts.forEach((element) => {
|
||||
let li = document.createElement("li");
|
||||
let a = document.createElement("a");
|
||||
a.href = "/post.html?id=" + element["id"];
|
||||
a.innerText = element["title"];
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-footer_blog", blogFooter);
|
50
js/customElements/blogIndex.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
class BlogIndex extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.getBlogPosts();
|
||||
}
|
||||
|
||||
async getBlogPosts() {
|
||||
var graphql = JSON.stringify({
|
||||
query: 'query($count: Int! $contentLength: Int!) { blogPosts(count: $count contentLength: $contentLength) { content title id }}',
|
||||
variables: {
|
||||
"count": 3,
|
||||
"contentLength": 300
|
||||
}
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
let posts = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.blogPosts;
|
||||
posts.forEach((element) => {
|
||||
const article = document.createElement("article");
|
||||
article.classList.add("breakWord");
|
||||
this.appendChild(article);
|
||||
|
||||
const h2 = document.createElement("h2");
|
||||
h2.innerText = element["title"];
|
||||
article.appendChild(h2);
|
||||
|
||||
const content = document.createElement("p");
|
||||
content.classList.add("breakWord");
|
||||
content.innerHTML = element["content"];
|
||||
article.appendChild(content);
|
||||
|
||||
const moreP = document.createElement("p");
|
||||
moreP.classList.add("center");
|
||||
article.appendChild(moreP);
|
||||
|
||||
const moreLink = document.createElement("a");
|
||||
moreLink.href = "/post.html?id=" + element["id"];
|
||||
moreP.appendChild(moreLink);
|
||||
|
||||
const moreButton = document.createElement("button");
|
||||
moreButton.innerText = "Mehr lesen";
|
||||
moreLink.appendChild(moreButton);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-blog_index", BlogIndex);
|
48
js/customElements/commentsDisplay.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
class commentsDisplay extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.getComments()
|
||||
}
|
||||
|
||||
async getComments() {
|
||||
var graphql = JSON.stringify({
|
||||
query: 'query($article: String!) { comments(article: $article) { name comment gravatarURL }}',
|
||||
variables: {
|
||||
"article": window.location.pathname
|
||||
}
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
let comments = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.comments;
|
||||
this.innerHTML = "";
|
||||
comments.forEach((element) => {
|
||||
const h3 = document.createElement("h3");
|
||||
h3.classList.add("commentTitle");
|
||||
h3.innerText = element["name"];
|
||||
this.appendChild(h3);
|
||||
|
||||
const commentDiv = document.createElement("div");
|
||||
commentDiv.classList.add("comment");
|
||||
this.appendChild(commentDiv);
|
||||
|
||||
const image = document.createElement("img");
|
||||
image.src = element["gravatarURL"];
|
||||
commentDiv.appendChild(image);
|
||||
|
||||
const article = document.createElement("article");
|
||||
article.classList.add("commentArticle");
|
||||
commentDiv.appendChild(article);
|
||||
|
||||
const commentText = document.createElement("p");
|
||||
commentText.classList.add("commentText");
|
||||
commentText.innerText = element["comment"];
|
||||
article.appendChild(commentText);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-comments_display", commentsDisplay);
|
|
@ -6,7 +6,16 @@ class contactMailButton extends HTMLElement {
|
|||
}
|
||||
|
||||
async addButton() {
|
||||
let sitekey = await (await fetch("/API/config.php?name=sitekey")).text();
|
||||
var graphql = JSON.stringify({
|
||||
query: "query {sitekey}"
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
|
||||
let sitekey = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.sitekey;
|
||||
console.log(sitekey);
|
||||
this.innerHTML = `E-Mail: <button id="emailButton" class="h-captcha" data-sitekey="${sitekey}" data-callback="onSubmit">laden</button><br>`;
|
||||
const script = document.createElement("script");
|
26
js/customElements/ebkBanner.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
class ebkBanner extends HTMLElement {
|
||||
constructor(){
|
||||
super();
|
||||
this.generateBanner();
|
||||
}
|
||||
|
||||
async generateBanner() {
|
||||
var graphql = JSON.stringify({
|
||||
query: 'query { ebayKleinanzeigen{ count }}',
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
let elementCount = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.ebayKleinanzeigen.count;
|
||||
if(elementCount > 0) {
|
||||
const h2 = document.createElement("h2");
|
||||
h2.classList.add("red");
|
||||
h2.innerHTML = "Ich biete aktuell verschiedene Artikel zum verkauf an, eine genaue Übersicht ist <a class=\"red\" href=\"/selling.html\">hier</a> zu sehen."
|
||||
this.appendChild(h2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-ebk-banner", ebkBanner);
|
96
js/customElements/footer.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
class Footer extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.socialButtons = [
|
||||
{
|
||||
"link": "//www.thingiverse.com/jonasled/designs/",
|
||||
"icon": "3d_model",
|
||||
},
|
||||
{
|
||||
"link": "//paypal.me/jonasled",
|
||||
"icon": "paypal",
|
||||
},
|
||||
{
|
||||
"link": "//matrix.to/#/@jonasled:jonasled.de",
|
||||
"icon": "matrix"
|
||||
},
|
||||
{
|
||||
"link": "//twitter.com/jonasled1",
|
||||
"icon": "twitter"
|
||||
}
|
||||
];
|
||||
this.links = [
|
||||
{
|
||||
"name": "Datenschutzerklärung",
|
||||
"link": "/datenschutzerklaerung.html"
|
||||
},
|
||||
{
|
||||
"name": "Bildquellen",
|
||||
"link": "/bildquellen.html"
|
||||
},
|
||||
{
|
||||
"name": "Impressum",
|
||||
"link": "/impressum.html"
|
||||
},
|
||||
{
|
||||
"name": "Quellcode",
|
||||
"link": "//gitlab.jonasled.de/jonasled/website"
|
||||
}
|
||||
]
|
||||
const footer = document.createElement("footer");
|
||||
this.appendChild(footer);
|
||||
|
||||
const blueBar = document.createElement("div");
|
||||
blueBar.id = "blueBar";
|
||||
footer.appendChild(blueBar);
|
||||
|
||||
const footerContent = document.createElement("div");
|
||||
footerContent.id = "footerContent";
|
||||
footer.appendChild(footerContent);
|
||||
|
||||
const footerLinks = document.createElement("div");
|
||||
footerContent.appendChild(footerLinks);
|
||||
|
||||
this.links.forEach(link => {
|
||||
const linkElement = document.createElement("a");
|
||||
linkElement.href = link["link"];
|
||||
linkElement.innerText = link["name"];
|
||||
footerLinks.appendChild(linkElement);
|
||||
|
||||
const linebreak = document.createElement("br");
|
||||
footerLinks.appendChild(linebreak);
|
||||
});
|
||||
|
||||
const footerPostDiv = document.createElement("div");
|
||||
footerPostDiv.id = "newestPost";
|
||||
footerContent.appendChild(footerPostDiv);
|
||||
|
||||
const postHeadline = document.createElement("h3");
|
||||
postHeadline.innerText = "Neueste Beiträge";
|
||||
footerPostDiv.appendChild(postHeadline);
|
||||
|
||||
const footerPost = document.createElement("jl-footer_blog");
|
||||
footerPostDiv.appendChild(footerPost);
|
||||
|
||||
const footerIconDiv = document.createElement("div");
|
||||
footerIconDiv.className = "center";
|
||||
footerContent.appendChild(footerIconDiv);
|
||||
|
||||
const socialButtonsContainer = document.createElement("p");
|
||||
socialButtonsContainer.className = "center";
|
||||
footerIconDiv.appendChild(socialButtonsContainer);
|
||||
|
||||
this.socialButtons.forEach(button => {
|
||||
const link = document.createElement("a");
|
||||
link.href = button["link"];
|
||||
socialButtonsContainer.appendChild(link)
|
||||
|
||||
const icon = document.createElement("jl-svg");
|
||||
icon.setAttribute("data-name", button["icon"]);
|
||||
link.appendChild(icon);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-footer", Footer);
|
44
js/customElements/header.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
class Header extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
let pageTitle = this.getAttribute("data-title");
|
||||
|
||||
const header = document.createElement("header");
|
||||
this.appendChild(header);
|
||||
|
||||
const headerWrapper = document.createElement("div");
|
||||
headerWrapper.classList.add("header-wrapper");
|
||||
header.appendChild(headerWrapper);
|
||||
|
||||
const headerHomepage = document.createElement("div");
|
||||
headerHomepage.classList.add("header-homepage");
|
||||
headerWrapper.appendChild(headerHomepage);
|
||||
|
||||
const alignHolder = document.createElement("div");
|
||||
alignHolder.classList.add("align-holder");
|
||||
headerHomepage.appendChild(alignHolder);
|
||||
|
||||
const h1 = document.createElement("h1");
|
||||
h1.innerText = pageTitle;
|
||||
h1.onclick = () => {
|
||||
document.getElementById("content").scrollIntoView();
|
||||
}
|
||||
alignHolder.appendChild(h1);
|
||||
|
||||
const headerSeparator = document.createElement("div");
|
||||
headerSeparator.classList.add("header-separator");
|
||||
headerSeparator.classList.add("header-separator-bottom");
|
||||
headerWrapper.appendChild(headerSeparator);
|
||||
|
||||
const svg = document.createElement("jl-svg");
|
||||
svg.setAttribute("data-name", "banner");
|
||||
headerSeparator.appendChild(svg);
|
||||
|
||||
const mainMenu = document.createElement("jl-main_menu");
|
||||
mainMenu.setAttribute("data-title", pageTitle);
|
||||
mainMenu.id = "mainmenu";
|
||||
header.appendChild(mainMenu);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-header", Header);
|
48
js/customElements/image.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import * as basicLightbox from 'basiclightbox'
|
||||
|
||||
class CustomImage extends HTMLElement {
|
||||
async connectedCallback(){
|
||||
const originalURL = new URL(this.getAttribute("src"), document.baseURI).href;
|
||||
|
||||
|
||||
var graphql = JSON.stringify({
|
||||
query: "query($url: String!) {imgproxy(url: $url)}",
|
||||
variables: {
|
||||
"url": originalURL
|
||||
}
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
|
||||
let imgproxy = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.imgproxy;
|
||||
|
||||
let image = document.createElement("img");
|
||||
image.src = imgproxy;
|
||||
if(this.getAttribute("alt") != null) image.setAttribute("alt", this.getAttribute("alt"));
|
||||
if(this.getAttribute("title") != null) image.setAttribute("title", this.getAttribute("title"));
|
||||
if(this.getAttribute("class") != null) image.setAttribute("class", this.getAttribute("class"));
|
||||
if(this.getAttribute("style") != null) image.setAttribute("style", this.getAttribute("style"));
|
||||
if(this.getAttribute("width") != null) image.setAttribute("width", this.getAttribute("width"));
|
||||
if(this.getAttribute("height") != null) image.setAttribute("height", this.getAttribute("height"));
|
||||
if(this.getAttribute("id") != null) image.setAttribute("id", this.getAttribute("id"));
|
||||
image.setAttribute("loading", "lazy");
|
||||
image.setAttribute("original-src", originalURL);
|
||||
this.appendChild(image);
|
||||
|
||||
this.setAttribute("id", "");
|
||||
|
||||
if(!(this.getAttribute("data-noPreview") === "true")) {
|
||||
image.onclick = () => {
|
||||
const instance = basicLightbox.create(`
|
||||
<img src="${originalURL}">
|
||||
`);
|
||||
instance.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-img", CustomImage)
|
15
js/customElements/inline-code.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
class InlineCode extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const codeElement = document.createElement("code");
|
||||
if (this.hasAttribute("language")) {
|
||||
codeElement.classList.add(this.getAttribute("language"));
|
||||
} else {
|
||||
codeElement.classList.add("language-text");
|
||||
}
|
||||
codeElement.innerHTML = this.innerHTML;
|
||||
this.appendChild(codeElement);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-code", InlineCode);
|
|
@ -10,10 +10,10 @@ class MainMenu extends HTMLElement {
|
|||
menuContainer.className = "mainmenuContainer";
|
||||
|
||||
let burgerMenu = document.createElement("div");
|
||||
burgerMenu.id = "burgerMenu";
|
||||
burgerMenu.id = "burgermenu";
|
||||
|
||||
burgerMenu.onclick = () => {
|
||||
document.getElementById("burgerMenu").classList.toggle("change");
|
||||
document.getElementById("burgermenu").classList.toggle("change");
|
||||
document.querySelector(".mainmenuContainer").classList.toggle("visible");
|
||||
|
||||
}
|
140
js/customElements/newComment.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
class newComment extends HTMLElement {
|
||||
connectedCallback() {
|
||||
const buttonElement = document.createElement("button");
|
||||
buttonElement.classList.add("bigButton");
|
||||
buttonElement.id = "showCommentButton";
|
||||
buttonElement.innerText = "Neues Kommentar verfassen";
|
||||
this.appendChild(buttonElement);
|
||||
buttonElement.onclick = this.setupForm;
|
||||
}
|
||||
|
||||
async setupForm() {
|
||||
var graphql = JSON.stringify({
|
||||
query: "query {sitekey}"
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
|
||||
let sitekey = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.sitekey;
|
||||
|
||||
let script = document.createElement('script');
|
||||
script.src = "https://hCaptcha.com/1/api.js";
|
||||
script.type = 'text/javascript';
|
||||
script.onload = () => {
|
||||
let pageName = window.location.pathname
|
||||
const parent = this.parentElement;
|
||||
parent.innerHTML = "";
|
||||
|
||||
const form = document.createElement("form");
|
||||
parent.appendChild(form);
|
||||
|
||||
const labelName = document.createElement("label")
|
||||
labelName.setAttribute("for", "name");
|
||||
labelName.innerText = "Name:";
|
||||
form.appendChild(labelName);
|
||||
|
||||
const nameInput = document.createElement("input");
|
||||
nameInput.type = "text";
|
||||
nameInput.name = "name";
|
||||
nameInput.id = "name";
|
||||
form.appendChild(nameInput);
|
||||
|
||||
let linebreak = document.createElement("br");
|
||||
form.appendChild(linebreak);
|
||||
|
||||
const labelMail = document.createElement("label")
|
||||
labelMail.setAttribute("for", "email");
|
||||
labelMail.innerText = "E-Mail: (wird nicht veröffentlicht)";
|
||||
form.appendChild(labelMail);
|
||||
|
||||
const mailInput = document.createElement("input");
|
||||
mailInput.type = "email";
|
||||
mailInput.name = "email";
|
||||
mailInput.id = "email";
|
||||
form.appendChild(mailInput);
|
||||
|
||||
linebreak = document.createElement("br");
|
||||
form.appendChild(linebreak);
|
||||
|
||||
const labelComment = document.createElement("label")
|
||||
labelComment.setAttribute("for", "comment");
|
||||
labelComment.innerText = "Kommentar:";
|
||||
form.appendChild(labelComment);
|
||||
|
||||
const commentInput = document.createElement("textarea");
|
||||
commentInput.name = "comment";
|
||||
commentInput.id = "comment";
|
||||
form.appendChild(commentInput);
|
||||
|
||||
|
||||
linebreak = document.createElement("br");
|
||||
form.appendChild(linebreak);
|
||||
|
||||
const hcaptcha = document.createElement("div");
|
||||
hcaptcha.classList.add("h-captcha");
|
||||
hcaptcha.setAttribute("data-theme", "dark");
|
||||
hcaptcha.setAttribute("data-sitekey", sitekey);
|
||||
form.appendChild(hcaptcha);
|
||||
|
||||
linebreak = document.createElement("br");
|
||||
form.appendChild(linebreak);
|
||||
|
||||
const submitButton = document.createElement("input");
|
||||
submitButton.value = "Kommentar veröffentlichen";
|
||||
submitButton.type = "submit";
|
||||
form.appendChild(submitButton);
|
||||
|
||||
const labelDatenschutz = document.createElement("p");
|
||||
labelDatenschutz.innerText = "Mit dem Klick auf den obigen Button erklären sie sich mit der ";
|
||||
form.appendChild(labelDatenschutz);
|
||||
|
||||
const datenschutzLink = document.createElement("a");
|
||||
datenschutzLink.innerText = "Datenschutzerklärung";
|
||||
datenschutzLink.href = "/datenschutzerklaerung.html";
|
||||
labelDatenschutz.appendChild(datenschutzLink);
|
||||
|
||||
const datenschutzTextNode = document.createTextNode(" einverstanden");
|
||||
labelDatenschutz.appendChild(datenschutzTextNode);
|
||||
|
||||
submitButton.onclick = async () => {
|
||||
if (nameInput.value == "" || commentInput.value == "") {
|
||||
alert("Name oder Kommentar nicht ausgefüllt.");
|
||||
return;
|
||||
}
|
||||
|
||||
var graphql = JSON.stringify({
|
||||
query: 'mutation($article: String!, $name: String!, $hCaptchaResponse: String!, $email: String!, $comment: String!) { comment(article: $article, name: $name, email: $email, comment: $comment, hCaptchaResponse: $hCaptchaResponse)}',
|
||||
variables: {
|
||||
"article": pageName,
|
||||
"name": nameInput.value,
|
||||
"email": mailInput.value,
|
||||
"comment": commentInput.value,
|
||||
"hCaptchaResponse": form.querySelector(".h-captcha iframe").getAttribute("data-hcaptcha-response")
|
||||
}
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
let data = (await (await fetch("/API/graphql.php", requestOptions)).json()).data;
|
||||
if (data.comment == "OK") {
|
||||
document.querySelector("jl-comments_display").getComments();
|
||||
parent.innerHTML = "<jl-new_comment></jl-new_comment>"
|
||||
} else {
|
||||
alert("Fehler: " + data.newComment);
|
||||
}
|
||||
}
|
||||
|
||||
form.onsubmit = () => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
document.body.append(script);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-new_comment", newComment);
|
|
@ -6,22 +6,37 @@ class NtpGraph extends HTMLElement {
|
|||
|
||||
let ip = this.getAttribute("data-server-ip");
|
||||
|
||||
this.innerHTML = `
|
||||
<span class="ntpBanner">${ip}</span>
|
||||
<span class="ntpContent">
|
||||
<a target="_blank" href="https://www.ntppool.org/scores/${ip}" class="linkToNtpPool">Server auf dem NTP Pool anzeigen</a>
|
||||
<canvas class="graphDelay"></canvas>
|
||||
<canvas class="graphScore"></canvas>
|
||||
</span>
|
||||
`;
|
||||
const ntpBanner = document.createElement("span");
|
||||
ntpBanner.classList.add("ntpBanner");
|
||||
ntpBanner.innerText = ip;
|
||||
this.appendChild(ntpBanner);
|
||||
|
||||
this.querySelector(".ntpBanner").onclick = () => {
|
||||
let contentElement = this.querySelector(".ntpContent");
|
||||
const ntpContent = document.createElement("span");
|
||||
ntpContent.classList.add("ntpContent");
|
||||
this.appendChild(ntpContent);
|
||||
|
||||
if (contentElement.classList.contains("visible")) {
|
||||
contentElement.classList.remove("visible");
|
||||
const ntpLink = document.createElement("a");
|
||||
ntpLink.target = "_blank";
|
||||
ntpLink.href = `https://www.ntppool.org/scores/${ip}`;
|
||||
ntpLink.classList.add("linkToNtpPool");
|
||||
ntpLink.innerText = "Server auf der Seite des NTP Pools anzeigen";
|
||||
ntpContent.appendChild(ntpLink);
|
||||
|
||||
|
||||
const ntpDelayCanvas = document.createElement("canvas");
|
||||
ntpDelayCanvas.classList.add("graphDelay");
|
||||
ntpContent.appendChild(ntpDelayCanvas);
|
||||
|
||||
const ntpScoreCanvas = document.createElement("canvas");
|
||||
ntpScoreCanvas.classList.add("graphScore");
|
||||
ntpContent.appendChild(ntpScoreCanvas);
|
||||
|
||||
ntpBanner.onclick = () => {
|
||||
|
||||
if (ntpContent.classList.contains("visible")) {
|
||||
ntpContent.classList.remove("visible");
|
||||
} else {
|
||||
contentElement.classList.add("visible");
|
||||
ntpContent.classList.add("visible");
|
||||
}
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
120
js/customElements/pwgen.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
class PasswordGenerator extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const outValue = document.createElement("input");
|
||||
outValue.type = "text";
|
||||
this.appendChild(outValue);
|
||||
|
||||
const lineBreak = document.createElement("br");
|
||||
this.appendChild(lineBreak);
|
||||
|
||||
const pwlen = document.createElement("input");
|
||||
pwlen.type = "range";
|
||||
pwlen.min = "8";
|
||||
pwlen.max = "128";
|
||||
pwlen.value = "32";
|
||||
this.appendChild(pwlen);
|
||||
|
||||
const pwlenSpan = document.createElement("span");
|
||||
pwlenSpan.innerText = "32";
|
||||
this.appendChild(pwlenSpan);
|
||||
|
||||
const lineBreak2 = document.createElement("br");
|
||||
this.appendChild(lineBreak2);
|
||||
|
||||
const includeNum = document.createElement("input");
|
||||
includeNum.type = "checkbox";
|
||||
includeNum.checked = true;
|
||||
this.appendChild(includeNum);
|
||||
|
||||
const includeNumText = document.createTextNode(" Zahlen");
|
||||
this.appendChild(includeNumText);
|
||||
|
||||
const lineBreak3 = document.createElement("br");
|
||||
this.appendChild(lineBreak3);
|
||||
|
||||
const includeBigChar = document.createElement("input");
|
||||
includeBigChar.type = "checkbox";
|
||||
includeBigChar.checked = true;
|
||||
this.appendChild(includeBigChar);
|
||||
|
||||
const bigCharText = document.createTextNode(" Großbuchstaben");
|
||||
this.appendChild(bigCharText);
|
||||
|
||||
const lineBreak4 = document.createElement("br");
|
||||
this.appendChild(lineBreak4);
|
||||
|
||||
const includeSmallChar = document.createElement("input");
|
||||
includeSmallChar.type = "checkbox";
|
||||
includeSmallChar.checked = true;
|
||||
this.appendChild(includeSmallChar);
|
||||
|
||||
const includeSmallCharText = document.createTextNode(" Kleinbuchstaben");
|
||||
this.appendChild(includeSmallCharText);
|
||||
|
||||
const lineBreak5 = document.createElement("br");
|
||||
this.appendChild(lineBreak5);
|
||||
|
||||
const includeSpecialChar = document.createElement("input");
|
||||
includeSpecialChar.type = "checkbox";
|
||||
includeSpecialChar.checked = true;
|
||||
this.appendChild(includeSpecialChar);
|
||||
|
||||
const inclideSpecialCharText = document.createTextNode(" Sonderzeichen");
|
||||
this.appendChild(inclideSpecialCharText);
|
||||
|
||||
const lineBreak6 = document.createElement("br");
|
||||
this.appendChild(lineBreak6);
|
||||
|
||||
const button = document.createElement("button");
|
||||
button.innerText = "Generieren";
|
||||
this.appendChild(button);
|
||||
|
||||
pwlen.oninput = () => {
|
||||
pwlenSpan.innerText = pwlen.value;
|
||||
button.click();
|
||||
}
|
||||
|
||||
includeNum.onchange = () => {
|
||||
button.click();
|
||||
}
|
||||
includeBigChar.onchange = () => {
|
||||
button.click();
|
||||
}
|
||||
includeSmallChar.onchange = () => {
|
||||
button.click();
|
||||
}
|
||||
includeSpecialChar.onchange = () => {
|
||||
button.click();
|
||||
}
|
||||
|
||||
button.onclick = () => {
|
||||
let possibleChar = "";
|
||||
let password = "";
|
||||
|
||||
if(includeNum.checked) {
|
||||
possibleChar += "1234567890";
|
||||
}
|
||||
if(includeBigChar.checked) {
|
||||
possibleChar += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
}
|
||||
if(includeSmallChar.checked) {
|
||||
possibleChar += "abcdefghijklmnopqrstuvwxyz";
|
||||
}
|
||||
if(includeSpecialChar.checked) {
|
||||
possibleChar += ",;.:-_#'+*?=)(/&%$§\"!°{[]}<>|~"
|
||||
}
|
||||
|
||||
for(let i = 0; i < pwlen.value; i++) {
|
||||
password += possibleChar[Math.floor(Math.random() * possibleChar.length)];
|
||||
}
|
||||
|
||||
outValue.value = password;
|
||||
}
|
||||
|
||||
button.click();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-pwgen", PasswordGenerator)
|
106
js/customElements/sellingTable.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
import * as basicLightbox from 'basiclightbox'
|
||||
|
||||
class sellingTable extends HTMLElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.config = [
|
||||
{
|
||||
"title": "Bild",
|
||||
"fieldName": "preview",
|
||||
"displayType": "image",
|
||||
"fullImage": "image",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"title": "Titel",
|
||||
"fieldName": "title",
|
||||
"displayType": "text"
|
||||
},
|
||||
{
|
||||
"title": "Preis",
|
||||
"fieldName": "price",
|
||||
"displayType": "text"
|
||||
},
|
||||
{
|
||||
"title": "Versand",
|
||||
"fieldName": "shipping",
|
||||
"displayType": "text"
|
||||
},
|
||||
{
|
||||
"title": "Link",
|
||||
"fieldName": "link",
|
||||
"displayType": "link",
|
||||
"linkText": "Anzeige ansehen",
|
||||
"target": "_blank"
|
||||
},
|
||||
];
|
||||
|
||||
this.generateTable();
|
||||
}
|
||||
|
||||
async generateTable() {
|
||||
const table = document.createElement("table");
|
||||
this.appendChild(table);
|
||||
|
||||
const tr = document.createElement("tr");
|
||||
table.appendChild(tr);
|
||||
|
||||
this.config.forEach(element => {
|
||||
const th = document.createElement("th");
|
||||
th.innerText = element["title"];
|
||||
tr.appendChild(th);
|
||||
});
|
||||
|
||||
var graphql = JSON.stringify({
|
||||
query: 'query { ebayKleinanzeigen(imageCount: 1) { elements { images { preview image } title price shipping link }}}',
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
let elements = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.ebayKleinanzeigen.elements;
|
||||
elements.forEach(ad => {
|
||||
const tr = document.createElement("tr");
|
||||
table.appendChild(tr);
|
||||
this.config.forEach(element => {
|
||||
const th = document.createElement("th");
|
||||
|
||||
switch (element["displayType"]) {
|
||||
case "text":
|
||||
th.innerText = ad[element["fieldName"]];
|
||||
break;
|
||||
case "link":
|
||||
const link = document.createElement("a");
|
||||
th.appendChild(link);
|
||||
link.href = ad[element["fieldName"]];
|
||||
link.innerText = element["linkText"];
|
||||
|
||||
if ("target" in element) {
|
||||
link.target = element["target"];
|
||||
}
|
||||
break;
|
||||
case "image":
|
||||
const img = document.createElement("img");
|
||||
th.appendChild(img);
|
||||
img.src = ad["images"][element["index"]][element["fieldName"]];
|
||||
img.onclick = () => {
|
||||
const instance = basicLightbox.create(`
|
||||
<img src="${ad["images"][element["index"]][element["fullImage"]]}">
|
||||
`);
|
||||
instance.show();
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
tr.appendChild(th);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-selling-table", sellingTable);
|
28
js/customElements/skills.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
class Skill extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.getSkills();
|
||||
}
|
||||
|
||||
async getSkills(){
|
||||
var graphql = JSON.stringify({
|
||||
query: "query {skills}"
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
|
||||
let skills = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.skills;
|
||||
skills.forEach(skill => {
|
||||
const image = document.createElement("img");
|
||||
image.classList.add("skills");
|
||||
image.src = skill;
|
||||
this.appendChild(image);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("jl-skills", Skill);
|
|
@ -1,6 +1,5 @@
|
|||
class svgLoad extends HTMLElement {
|
||||
constructor(){
|
||||
super();
|
||||
connectedCallback() {
|
||||
let svgName = this.getAttribute("data-name");
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = () => {
|
||||
|
@ -9,7 +8,7 @@ class svgLoad extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
xhr.open("GET", "../assets/svg/" + svgName + ".svg");
|
||||
xhr.open("GET", "/svg/" + svgName + ".svg");
|
||||
xhr.send();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
require("./browserCheck");
|
||||
|
||||
require("./error");
|
||||
require("./imgPreview");
|
||||
require("./viewPost");
|
||||
require("./externalLinkHandler");
|
||||
require("./prism");
|
||||
|
@ -19,4 +16,7 @@ require("./customElements/footer");
|
|||
require("./customElements/ebkBanner");
|
||||
require("./customElements/sellingTable");
|
||||
require("./customElements/skills");
|
||||
require("./customElements/pwgen");
|
||||
require("./customElements/pwgen");
|
||||
require("./customElements/inline-code");
|
||||
require("./customElements/404Buttons");
|
||||
require("./customElements/image");
|
45
js/viewPost.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
if(window.location['pathname'] == "/post.html"){
|
||||
loadPost();
|
||||
}
|
||||
|
||||
// return the value of the get parameter with the given name
|
||||
function getParameter(name) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
|
||||
var results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
async function loadPost() {
|
||||
let id = getParameter("id");
|
||||
|
||||
let header = document.createElement("jl-header");
|
||||
let footer = document.createElement("jl-footer");
|
||||
let content = document.createElement("div");
|
||||
|
||||
if(id == null) {
|
||||
content.innerHTML = "<h1>404 - Post not found</h1>";
|
||||
} else {
|
||||
var graphql = JSON.stringify({
|
||||
query: 'query($postID: String!) {blogPost(id: $postID) {content title}}',
|
||||
variables: {
|
||||
"postID": id
|
||||
}
|
||||
})
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
body: graphql,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
};
|
||||
let post = (await (await fetch("/API/graphql.php", requestOptions)).json()).data.blogPost;
|
||||
content.innerHTML = post["content"];
|
||||
document.title = post["title"] + " - Jonas Leder";
|
||||
|
||||
header.setAttribute("data-title", post["title"]);
|
||||
}
|
||||
content.id = "content";
|
||||
|
||||
document.body.appendChild(header);
|
||||
document.body.appendChild(content);
|
||||
document.body.appendChild(footer);
|
||||
}
|
14
package.json
|
@ -6,15 +6,19 @@
|
|||
"author": "jonasled <git@jonasled.de>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"watch": "parcel serve src/*.html src/*/*.html",
|
||||
"build": "parcel build src/*.html src/*/*.html"
|
||||
"build": "concurrently \"yarn css\" \"yarn js\"",
|
||||
"css": "stylus styl/ -o public/css/ ",
|
||||
"js": "webpack --config ./webpack.conf.js",
|
||||
"watch": "concurrently \"stylus -w styl/ -o public/css/\" \"cd public && php -S 0.0.0.0:1234\" \"webpack --config ./webpack.conf.js --mode development --watch\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/transformer-sass": "^2.2.1"
|
||||
"concurrently": "^6.0.0",
|
||||
"webpack": "^5.28.0",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"stylus": "^0.56.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"basiclightbox": "^5.0.4",
|
||||
"chart.js": "^2.9.4",
|
||||
"parcel": "^2.2.1"
|
||||
"chart.js": "^2.9.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,12 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>404 - Page not found</title>
|
||||
<link href="scss/error.scss" rel="stylesheet">
|
||||
<link href="/css/error.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<jl-matomo></jl-matomo>
|
||||
<div id="particles-js"></div>
|
||||
<body style="height: 100vh;">
|
||||
<div class="terminal-window">
|
||||
<header>
|
||||
<div class="button green"></div>
|
||||
<div class="button yellow"></div>
|
||||
<div class="button red"></div>
|
||||
<jl-404_buttons></jl-404_buttons>
|
||||
</header>
|
||||
<section class="terminal">
|
||||
<div class="history"></div>
|
||||
|
@ -43,6 +39,6 @@
|
|||
|
||||
];
|
||||
</script>
|
||||
<script src="js/script.js" type="module"></script>
|
||||
<script src="/js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
22
public/API/ebayimg.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
if (!array_key_exists("url", $_GET) || $_GET["url"] == "") {
|
||||
die("URL not set or empty");
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, "https://i.ebayimg.com/" . $_GET["url"]);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20);
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
$content = curl_exec($curl);
|
||||
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
if ($httpcode == 200) {
|
||||
$contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
|
||||
header('Content-Type: ' . $contentType);
|
||||
echo ($content);
|
||||
} else {
|
||||
die("Failed to fetch image, server responded with " . $httpcode);
|
||||
}
|
29
public/API/graphql.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
use GraphQL\Server\StandardServer;
|
||||
use GraphQL\Type\Schema;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
require "./lib/config.php";
|
||||
require "./lib/mysql.php";
|
||||
require "./queries/queries.php";
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => $queryType,
|
||||
'mutation' => $mutationType,
|
||||
]);
|
||||
|
||||
try {
|
||||
$serverConfig = [
|
||||
'schema' => $schema,
|
||||
'rootValue' => [
|
||||
'db' =>$conn,
|
||||
],
|
||||
'debugFlag' => DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE,
|
||||
];
|
||||
$server = new StandardServer($serverConfig);
|
||||
|
||||
$server->handleRequest();
|
||||
} catch (Throwable $error) {
|
||||
StandardServer::send500Error($error);
|
||||
}
|
|
@ -19,4 +19,10 @@ $S3SecretKey = "";
|
|||
$S3BucketName = "";
|
||||
|
||||
$ebayKleinanzeigenUserId = "";
|
||||
$ebayKleinanzeigenToken = ""; # To get this token you have to sniff the HTTPS traffic from the app or decompile the app and find it.
|
||||
$ebayKleinanzeigenToken = ""; # To get this token you have to sniff the HTTPS traffic from the app or decompile the app and find it.
|
||||
|
||||
$imgProxyUrl = "";
|
||||
$imgProxyKey = "";
|
||||
$imgProxySalt = "";
|
||||
$defaultImageWidth = 512; # width is in px
|
||||
$defaultSkillsWidth = 80; # width is in px
|
|
@ -97,6 +97,16 @@
|
|||
"name": "Mailcow E-Mail Server",
|
||||
"url": "/anleitungen/mailcow.html",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "Traefik 2 Reverse Proxy",
|
||||
"url": "/anleitungen/traefik.html",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "Vaultwarden Passwort Manager",
|
||||
"url": "/anleitungen/vaultwarden.html",
|
||||
"type": "link"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -114,11 +124,6 @@
|
|||
"url": "/systeme/laptop.html",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "NAS",
|
||||
"url": "/systeme/nas.html",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "Epyc Server",
|
||||
"url": "/systeme/epycServer.html",
|
||||
|
@ -128,11 +133,6 @@
|
|||
"name": "i7 Server",
|
||||
"url": "/systeme/i7Server.html",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "Backup Server",
|
||||
"url": "/systeme/backupServer.html",
|
||||
"type": "link"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -179,6 +179,11 @@
|
|||
"name": "Passwort Generator",
|
||||
"url": "/passwordgen.html",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "Speedtest Server",
|
||||
"url": "//jonasled.speedtestcustom.com",
|
||||
"type": "link"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -191,11 +196,6 @@
|
|||
"url": "//status.jonasled.de",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "Discord Bots",
|
||||
"url": "//discordstatus.jonasled.de",
|
||||
"type": "link"
|
||||
},
|
||||
{
|
||||
"name": "NTP Server",
|
||||
"url": "/ntpstatus.html",
|
66
public/API/queries/blogPost.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
|
||||
$blogPostFields = new ObjectType([
|
||||
"name" => "Blog",
|
||||
"fields" => [
|
||||
"title" => Type::string(),
|
||||
"content" => Type::string(),
|
||||
"date" => Type::string(),
|
||||
"id" => Type::string()
|
||||
],
|
||||
]);
|
||||
|
||||
function blogPost($id, $conn)
|
||||
{
|
||||
$id = $conn->real_escape_string($id);
|
||||
$result = $conn->query("SELECT * FROM posts WHERE id=$id");
|
||||
if ($result->num_rows > 0) {
|
||||
$row = $result->fetch_assoc();
|
||||
} else {
|
||||
return [
|
||||
"title" => "Nicht Gefunden",
|
||||
"content" => "Post wurde nicht gefunden",
|
||||
"date" => "2000-01-01 00:00:00",
|
||||
"id" => "-1"
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
"title" => $row["title"],
|
||||
"content" => $row["content"],
|
||||
"date" => $row["date"],
|
||||
"id" => $row["id"],
|
||||
];
|
||||
}
|
||||
|
||||
function blogPosts($count, $contentLength, $conn)
|
||||
{
|
||||
$response = [];
|
||||
$count = $conn->real_escape_string($count);
|
||||
$result = $conn->query("SELECT * FROM posts order by id desc limit $count");
|
||||
if ($result->num_rows > 0) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$content = $row["content"];
|
||||
if($contentLength != null && strlen($content) > $contentLength) {
|
||||
$contentNew = substr($content, 0, $contentLength);
|
||||
$contentRest = substr($content, $contentLength);
|
||||
|
||||
$content = $contentNew . explode(" ", $contentRest)[0] . " ...";
|
||||
|
||||
}
|
||||
$blogElement = [
|
||||
"title" => $row["title"],
|
||||
"content" => $content,
|
||||
"date" => $row["date"],
|
||||
"id" => $row["id"],
|
||||
];
|
||||
|
||||
array_push($response, $blogElement);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
70
public/API/queries/comments.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
include "lib/getGravatar.php";
|
||||
$commentField = new ObjectType([
|
||||
"name" => "Comment",
|
||||
"fields" => [
|
||||
"name" => Type::string(),
|
||||
"comment" => Type::string(),
|
||||
"gravatarURL" => Type::string(),
|
||||
"id" => Type::int()
|
||||
],
|
||||
]);
|
||||
|
||||
function comments($article, $conn)
|
||||
{
|
||||
$response = [];
|
||||
$article = $conn->real_escape_string($article);
|
||||
$result = $conn->query("SELECT * FROM comments WHERE article='$article'");
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$commentElement = [
|
||||
"name" => $row["name"],
|
||||
"comment" => $row["comment"],
|
||||
"gravatarURL" => get_gravatar($row["email"]),
|
||||
"id" => $row["id"]
|
||||
];
|
||||
|
||||
array_push($response, $commentElement);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
function newComment($conn, $article, $name, $email, $comment, $hCaptchaResponse)
|
||||
{
|
||||
require "./lib/config.php";
|
||||
$data = array(
|
||||
'secret' => $secretkey,
|
||||
'response' => $hCaptchaResponse
|
||||
);
|
||||
$client = new Client();
|
||||
|
||||
$response = $client->post("https://hcaptcha.com/siteverify", [
|
||||
"form_params" => $data
|
||||
]);
|
||||
|
||||
$responseData = json_decode($response->getBody());
|
||||
if (!$responseData->success) {
|
||||
return "Failed to verify Captcha";
|
||||
}
|
||||
|
||||
if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
return "Invalid email address.";
|
||||
}
|
||||
|
||||
$article = $conn->escape_string($article);
|
||||
$name = $conn->escape_string($name);
|
||||
$email = $conn->escape_string($email);
|
||||
$comment = $conn->escape_string($comment);
|
||||
|
||||
$sql = "INSERT INTO comments (name, email, comment, article) VALUES ('$name', '$email', '$comment', '$article')";
|
||||
|
||||
if ($conn->query($sql) === TRUE) {
|
||||
return "OK";
|
||||
} else {
|
||||
return "Error: " . $sql . "<br>" . $conn->error;
|
||||
}
|
||||
}
|
103
public/API/queries/ebayKleinanzeigen.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
$ebayKleinanzeigenImages = new ObjectType([
|
||||
"name" => "EBK Image",
|
||||
"fields" => [
|
||||
"preview" => Type::string(),
|
||||
"image" => Type::string()
|
||||
]
|
||||
]);
|
||||
|
||||
$ebayKleinanzeigenElements = new ObjectType([
|
||||
"name" => "EBK Elements",
|
||||
"fields" => [
|
||||
"title" => Type::string(),
|
||||
"price" => Type::string(),
|
||||
"shipping" => Type::string(),
|
||||
"link" => Type::string(),
|
||||
"images" => [
|
||||
"type" => Type::listOf($ebayKleinanzeigenImages),
|
||||
"args" => [
|
||||
"count" => Type::int()
|
||||
]
|
||||
],
|
||||
"id" => Type::string()
|
||||
]
|
||||
]);
|
||||
|
||||
$ebayKleinanzeigenFields = new ObjectType([
|
||||
"name" => "Ebay Kleinanzeigen",
|
||||
"fields" => [
|
||||
"count" => Type::int(),
|
||||
"elements" => Type::listOf($ebayKleinanzeigenElements)
|
||||
],
|
||||
]);
|
||||
|
||||
function ebayKleinanzeigen($imageCount) {
|
||||
require "./lib/config.php";
|
||||
$elements = [];
|
||||
|
||||
$client = new Client();
|
||||
$headers = [
|
||||
'authorization' => 'Basic ' . $ebayKleinanzeigenToken,
|
||||
'user-agent' => 'okhttp/4.9.1',
|
||||
'x-ebayk-app' => '4e10d7fd-6fef-4f87-afb0-b8ede2f494071636475109828',
|
||||
'Host' => 'api.ebay-kleinanzeigen.de',
|
||||
'Accept' => '*/*',
|
||||
'Accept-Encoding' => 'gzip, deflate, br'
|
||||
];
|
||||
$response = $client->request('GET', "https://api.ebay-kleinanzeigen.de/api/ads.json?_in=title,price,pictures,link,features-active,search-distance,negotiation-enabled,attributes,medias,medias.media,medias.media.title,medias.media.media-link,store-id,store-title&page=0&size=31&userIds=$ebayKleinanzeigenUserId&pictureRequired=false&includeTopAds=false&limitTotalResultCount=true", [
|
||||
'headers' => $headers ]);
|
||||
|
||||
$response = json_decode($response->getBody(), true);
|
||||
$ads = $response["{http://www.ebayclassifiedsgroup.com/schema/ad/v1}ads"]["value"]["ad"];
|
||||
|
||||
foreach($ads as $ad) {
|
||||
$element = [
|
||||
"title" => html_entity_decode($ad["title"]["value"]),
|
||||
"id" => $ad["id"],
|
||||
"price" => $ad["price"]["amount"]["value"] . " €",
|
||||
"shipping" => "nein"
|
||||
];
|
||||
|
||||
foreach($ad["attributes"]["attribute"] as $attribute) {
|
||||
if(str_contains($attribute["name"], "versand")) {
|
||||
$element["shipping"] = $attribute["value"][0]["value"];
|
||||
}
|
||||
}
|
||||
|
||||
foreach($ad["link"] as $link) {
|
||||
if($link["rel"] == "self-public-website") {
|
||||
$element["link"] = $link["href"];
|
||||
}
|
||||
}
|
||||
|
||||
$images = [];
|
||||
foreach(array_slice($ad["pictures"]["picture"], 0, $imageCount) as $picture) {
|
||||
$image = [];
|
||||
|
||||
foreach($picture["link"] as $pictureSize) {
|
||||
if($pictureSize["rel"] == "teaser") {
|
||||
$image["preview"] = str_replace("https://i.ebayimg.com", "/API/ebayimg.php?url=", $pictureSize["href"]);
|
||||
}
|
||||
if($pictureSize["rel"] == "XXL") {
|
||||
|
||||
$image["image"] = str_replace("https://i.ebayimg.com", "/API/ebayimg.php?url=", $pictureSize["href"]);
|
||||
}
|
||||
}
|
||||
array_push($images, $image);
|
||||
}
|
||||
$element["images"] = $images;
|
||||
|
||||
array_push($elements, $element);
|
||||
}
|
||||
|
||||
return [
|
||||
"count" => sizeof($elements),
|
||||
"elements" => $elements
|
||||
];
|
||||
}
|
10
public/API/queries/imgproxy.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
function imgproxy($imageURL) {
|
||||
require "./lib/config.php";
|
||||
$encodedUrl = rtrim(strtr(base64_encode($imageURL), '+/', '-_'), '=');
|
||||
$path = "/rs:fit:0:$defaultImageWidth:1/g:no/{$encodedUrl}.webp";
|
||||
|
||||
$signature = rtrim(strtr(base64_encode(hash_hmac('sha256', $imgProxySalt.$path, $imgProxyKey, true)), '+/', '-_'), '=');
|
||||
return $imgProxyUrl . "/" . $signature . $path;
|
||||
}
|
22
public/API/queries/mailAddress.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
function mailAddress($hCaptchaResponse) {
|
||||
require "./lib/config.php";
|
||||
$data = array(
|
||||
'secret' => $secretkey,
|
||||
'response' => $hCaptchaResponse
|
||||
);
|
||||
$client = new Client();
|
||||
|
||||
$response = $client->post("https://hcaptcha.com/siteverify", [
|
||||
"form_params" => $data
|
||||
]);
|
||||
|
||||
$responseData = json_decode($response->getBody());
|
||||
if($responseData->success) {
|
||||
return "$contactmail";
|
||||
} else {
|
||||
return "Fehler beim Validieren des Captchas.";
|
||||
}
|
||||
}
|
93
public/API/queries/queries.php
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
require "./queries/skills.php";
|
||||
require "./queries/blogPost.php";
|
||||
require "./queries/comments.php";
|
||||
require "./queries/mailAddress.php";
|
||||
require "./queries/ebayKleinanzeigen.php";
|
||||
require "./queries/imgproxy.php";
|
||||
|
||||
|
||||
$queryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => [
|
||||
'sitekey' => [
|
||||
'type' => Type::string(),
|
||||
'resolve' => fn ($rootValue, $args) => $sitekey,
|
||||
],
|
||||
'mailAddress' => [
|
||||
'type' => Type::string(),
|
||||
"args" => [
|
||||
"hCaptchaResponse" => Type::string()
|
||||
],
|
||||
'resolve' => fn ($rootValue, $args) => mailAddress($args["hCaptchaResponse"]),
|
||||
],
|
||||
'skills' => [
|
||||
'type' => Type::listOf(Type::string()),
|
||||
'resolve' => fn ($rootValue, $args) => getSkills(),
|
||||
],
|
||||
'blogPost' => [
|
||||
"type" => $blogPostFields,
|
||||
'args' => [
|
||||
'id' => Type::nonNull(Type::string()),
|
||||
],
|
||||
'resolve' => fn ($rootValue, $args) => blogPost($args["id"], $rootValue["db"]),
|
||||
],
|
||||
'blogPosts' => [
|
||||
"type" => Type::listOf($blogPostFields),
|
||||
"args" => [
|
||||
"count" => Type::nonNull(Type::int()),
|
||||
"contentLength" => [
|
||||
"type" => Type::int(),
|
||||
"defaultValue" => null
|
||||
]
|
||||
|
||||
],
|
||||
'resolve' => fn ($rootValue, $args) => blogPosts($args["count"], $args["contentLength"], $rootValue["db"]),
|
||||
],
|
||||
'comments' => [
|
||||
"type" => Type::listOf($commentField),
|
||||
"args" => [
|
||||
"article" => Type::nonNull(Type::string()),
|
||||
],
|
||||
'resolve' => fn ($rootValue, $args) => comments($args["article"], $rootValue["db"]),
|
||||
],
|
||||
'ebayKleinanzeigen' => [
|
||||
"type" => $ebayKleinanzeigenFields,
|
||||
"args" => [
|
||||
"imageCount" => [
|
||||
"type" => Type::int(),
|
||||
"defaultValue" => 0
|
||||
]
|
||||
],
|
||||
'resolve' => fn ($rootValue, $args) => ebayKleinanzeigen($args["imageCount"]),
|
||||
],
|
||||
'imgproxy' => [
|
||||
"type" => Type::string(),
|
||||
"args" => [
|
||||
"url" => Type::nonNull(Type::string()),
|
||||
],
|
||||
'resolve' => fn ($rootValue, $args) => imgproxy($args["url"]),
|
||||
]
|
||||
|
||||
]
|
||||
]);
|
||||
|
||||
$mutationType = new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'fields' => [
|
||||
"comment" => [
|
||||
"type" => Type::string(),
|
||||
"args" => [
|
||||
"article" => Type::string(),
|
||||
"name" => Type::string(),
|
||||
"email" => Type::string(),
|
||||
"comment" => Type::string(),
|
||||
"hCaptchaResponse" => Type::string()
|
||||
],
|
||||
'resolve' => fn ($rootValue, $args) => newComment($rootValue["db"], $args["article"], $args["name"], $args["email"], $args["comment"], $args["hCaptchaResponse"]),
|
||||
],
|
||||
]
|
||||
]);
|
40
public/API/queries/skills.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
function getSkills() {
|
||||
require "./lib/config.php";
|
||||
$s3Client = new Aws\S3\S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => 'us-east-1',
|
||||
'endpoint' => $S3Server,
|
||||
'use_path_style_endpoint' => true,
|
||||
'credentials' => [
|
||||
'key' => $S3AccessKey,
|
||||
'secret' => $S3SecretKey,
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $s3Client->ListObjects(['Bucket' => $S3BucketName, 'Delimiter'=>'/', 'Prefix' => 'skills/']);
|
||||
|
||||
$response = [];
|
||||
foreach ($result["Contents"] as $skill){
|
||||
// Get a command object from the client
|
||||
$command = $s3Client->getCommand('GetObject', [
|
||||
'Bucket' => $S3BucketName,
|
||||
'Key' => $skill["Key"]
|
||||
]);
|
||||
|
||||
// Create a pre-signed URL for a request with duration of 10 miniutes
|
||||
$presignedRequest = $s3Client->createPresignedRequest($command, '10 minutes');
|
||||
|
||||
// Get the actual presigned-url
|
||||
$downloadURL = (string) $presignedRequest->getUri();
|
||||
|
||||
// Generate the imgproxy URL
|
||||
$encodedUrl = rtrim(strtr(base64_encode($downloadURL), '+/', '-_'), '=');
|
||||
$path = "/rs:fit:0:$defaultSkillsWidth:1/g:no/{$encodedUrl}.webp";
|
||||
|
||||
$signature = rtrim(strtr(base64_encode(hash_hmac('sha256', $imgProxySalt.$path, $imgProxyKey, true)), '+/', '-_'), '=');
|
||||
array_push($response, $imgProxyUrl . "/" . $signature . $path);
|
||||
}
|
||||
return $response;
|
||||
}
|
45
public/about.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title></title>
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<jl-header data-title="Über mich"></jl-header>
|
||||
<div id="content">
|
||||
<p>Hallo, mein Name ist Jonas Leder. Ich bin aktuell Auszubildender bei der <a
|
||||
href="https://www.jobrouter.com/de/">JobRouter AG</a> in Fachrichtung Fachinformatiker für
|
||||
Anwendungsentwicklung. In meiner
|
||||
Freizeit beschäftige ich mich viel mit Computern und der Softwareprogrammierung. Dabei verwende ich häufig
|
||||
folgende Programmiersprachen:</p>
|
||||
<ul>
|
||||
<li>Python</li>
|
||||
<li>C / C#</li>
|
||||
<li>"HTML"</li>
|
||||
<li>JavaScript / TypeScript</li>
|
||||
<li>CSS / SCSS</li>
|
||||
<li>PHP</li>
|
||||
<li>Java</li>
|
||||
<li>Bash</li>
|
||||
</ul>
|
||||
<p>Die oben genannten Sprachen setzt ich auf PCs und Microcontrollern ein. Dabei verwende ich die Standard
|
||||
Arduino
|
||||
Boards (nano, mega, …) und spezielle, wie das ESP8266, welches WLAN unterstützt.</p>
|
||||
<p>Neben Microcontrollern beschäftige ich auch viel mit Servern und Computern. Auch für diese Systeme schreibe
|
||||
ich verschiedenste Programme und Scripts. Mein Hauptgebiet ist aktuell die Webentwicklung. Dierfür setze ich
|
||||
bei den meisten Seiten auf HTML, JS und SCSS für das Frontend. Im Backend nutze ich in den meisten Fällen
|
||||
PHP.</p>
|
||||
<p>
|
||||
Neben der Erfahrung im Programmieren kenne ich mich auch sowohl mit Windows als auch mit Linux aus. Wobei
|
||||
ich mittlerweile fast überall nurnoch Linux einsetze und Windows nur im Ausnahmefall verwendet wird.
|
||||
</p>
|
||||
</div>
|
||||
<jl-footer></jl-footer>
|
||||
<script src="/js/script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title></title>
|
||||
<link href="../scss/style.scss" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -18,16 +18,16 @@
|
|||
href="https://www.debian.org/download">hier</a> heruntergeladen werden. Hierfür wird ein System mit
|
||||
mindestens 2GB RAM, 2 Kernen und 10GB Storage empfohlen. Wenn die VM con der CD gebootet wird kommt als
|
||||
erstes ein Auswahlmenü, in diesem die Option Install wählen und danach enter drücken.</p>
|
||||
<img src="../assets/img/debian_grub.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/debian_grub.jpg"></jl-img>
|
||||
<p>Im nächsten Schritt wird die Sprache, die Region und das Tastaturlayout fetgelegt. Im nächsten Schritt
|
||||
konfiguriert Debian einige Einstellungen wie die Netzwerkkonfiguration. Wenn die automatische Konfiguration
|
||||
abgeschlossen ist, frag der Installer nach dem Hostname, dieser kann frei gewählt werden. Ich verwende
|
||||
hierbei gerne Namen, die zu dem System passen, wie zum Beispiel mailcow. Der Domain Name im darauffolgenden
|
||||
Schritt kann leer gelassen werden. Wenn dieser festgelegt wurde sollte das Passwort für den root Benutzer
|
||||
festgelegt werden. Hierbei sollte auf einen <a href="../passwordgen.html">Passwortgenerator</a>
|
||||
festgelegt werden. Hierbei sollte auf einen <a href="/passwordgen.html/">Passwortgenerator</a>
|
||||
gesetzt werden. Nachdem das Passwort für den root Benutzer festgelegt wurde fragt Debian noch nach
|
||||
benötigten Daten für einen nicht root Nutzer. Hierbei muss ein Anzeigenahme, ein Nutzername und ein <a
|
||||
href="../passwordgen.html">generiertes Passwort</a> festgelegt werden. Die Partitionierung
|
||||
href="/passwordgen.html/">generiertes Passwort</a> festgelegt werden. Die Partitionierung
|
||||
wird mit <code class="language-text">Guieded - use entire disk</code> bestätigt, danach die Festplatte
|
||||
ausgewählt. Als Partitionsschema wird
|
||||
<code class="language-text">All Files in one partition</code> gewählt. Wenn alle Optionen gesetzt wurden
|
||||
|
@ -37,8 +37,8 @@
|
|||
class="language-text">yes</code>
|
||||
bestätigt.
|
||||
</p>
|
||||
<img src="../assets/img/debian_partition_method.jpg">
|
||||
<img src="../assets/img/debian_partition_finish.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/debian_partition_method.jpg"></jl-img>
|
||||
<jl-img src="/img/anleitungen/mailcow/debian_partition_finish.jpg"></jl-img>
|
||||
<p>Nun wird Debian auf die Festplatte installiert. Jenachdem wie schnell das Bootlaufwerk und die Festplatte ist
|
||||
kann dieser Schritt einige Minuten dauern. Nachdem die ersten Dateien auf die Festplatte kopiert wurden,
|
||||
fragt Debian ob CDs mit Paketen eingelesen werden sollen. Dieser Schritt sollte mit <code
|
||||
|
@ -52,13 +52,13 @@
|
|||
weitergeben. Im nächsten Schritt sollte nur der SSH Server aktiviert werden. Die Optionen können deaktiviert
|
||||
oder aktiviert werden, indem mit den Pfeiltasten auf die entsprechende Option navigiert wird und dann die
|
||||
Leertaste gedrückt wird.</p>
|
||||
<img src="../assets/img/debian_scan_media.jpg">
|
||||
<img src="../assets/img/debian_survey.jpg">
|
||||
<img src="../assets/img/debian_software.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/debian_scan_media.jpg"></jl-img>
|
||||
<jl-img src="/img/anleitungen/mailcow/debian_survey.jpg"></jl-img>
|
||||
<jl-img src="/img/anleitungen/mailcow/debian_software.jpg"></jl-img>
|
||||
<p>Nachdem der SSH Server installiert wurde, muss der Bootloader installiert werden, dafür muss bei der Frage ob
|
||||
Grub installiert werden soll "Yes" gedrückt werdeb und in der nächsten Seite die Systemfestplatte ausgewählt
|
||||
werden.</p>
|
||||
<img src="../assets/img/debian_grub_install.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/debian_grub_install.jpg"></jl-img>
|
||||
<p>Nachdem das System installiert wurde wird Debian neugestartet. Nun kann sich entweder über die Oberfläche
|
||||
angemeldet werden oder via SSH über die IP Adresse. Nach der Authentifizierung am System mit dem zuvor
|
||||
erstellten Nutzer muss sich als root angemeldet werden und danach das System auf den neuesten Stand gebracht
|
||||
|
@ -127,7 +127,7 @@ cd mailcow-dockerized
|
|||
die IP Adresse der reverse DNS Eintrag auf den gleichen Domain wie im MX Eintrag geändert werden (also in meinem
|
||||
Fall auf <code class="language-text">mail.jonasled-test.xyz</code>)
|
||||
</p>
|
||||
<img src="../assets/img/mailcow_dns.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_dns.jpg"></jl-img>
|
||||
<p>
|
||||
Wenn nun alle DNS Einstellungen laufen kann Mailcow das erste mal mit dem nachfolgenden Befehl gestartet
|
||||
werden. Beim ersten mal werden alle Programme heruntergeladen, abhängig von der Internetgeschwindigkeit kann
|
||||
|
@ -139,14 +139,14 @@ cd mailcow-dockerized
|
|||
# nicht benötigt falls noch als root angemeldet aus dem vorherigen Schritt.
|
||||
docker-compose up</code>
|
||||
</pre>
|
||||
<img src="../assets/img/mailcow_login.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_login.jpg"></jl-img>
|
||||
<p>
|
||||
Nachdem das oben abgebildete Login Fenster angezeigt wird, ist Mailcow fertig gestartet. Der default
|
||||
Nutzername ist admin mit dem Passwort moohoo, dieses sollte umgehend nach dem ersten Login abgeändert
|
||||
werden. Dazu in der Benutzerübersicht beim Admin Benutzer auf <code class="language-text">edit</code>
|
||||
(blauer Button im Bild unten) klicken
|
||||
und ein neues Passwort mit
|
||||
einem <a href="../passwordgen.html">Passwortgenerator</a> erstellen und speichern. Als nächstes
|
||||
einem <a href="/passwordgen.html">Passwortgenerator</a> erstellen und speichern. Als nächstes
|
||||
empfehle ich dringend ein Zweifaktor Login festzulegen. Dazu kann entweder wenn ein passender <a
|
||||
href="https://www.amazon.de/dp/B07HBD71HL/">Hardwareschlüssel</a> vorhanden ist WebAuthn oder Yubico
|
||||
verwendet werden. Wenn kein Hardwareshlüssel vorhanden ist, können time based OTP Keys verwendet werden.
|
||||
|
@ -161,22 +161,22 @@ docker-compose up</code>
|
|||
class="language-text">Mailboxes</code> können nun
|
||||
Mailboxen angelegt werden.
|
||||
</p>
|
||||
<img src="../assets/img/mailcow_setup_mail.jpg">
|
||||
<img src="../assets/img/mailcow_domain_setup.jpg"><br>
|
||||
<img src="../assets/img/mailcow_domain_new_1.jpg">
|
||||
<img src="../assets/img/mailcow_domain_new_2.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_setup_mail.jpg"></jl-img>
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_domain_setup.jpg"></jl-img><br>
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_domain_new_1.jpg"></jl-img>
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_domain_new_2.jpg"></jl-img>
|
||||
<p>
|
||||
In der Mailbox Konfiguration kann nun mit dem Button <code class="language-text">Add mailbox</code> eine
|
||||
neue Mailbox angelegt werden. Hier
|
||||
muss der Teil der Mail vor dem <code class="language-text">@</code> angegeben werden. (Beispielswiese für
|
||||
die Mail <code class="language-text">info@jonasled-test.xyz</code>
|
||||
muss hier info angegeben werden) Danach sollte der volle Name des Nutzers und ein Passwort aus einem
|
||||
<a href="../passwordgen.html">Passwortgenerator</a> festgelegt werden. Wenn nun alle
|
||||
<a href="/passwordgen.html/">Passwortgenerator</a> festgelegt werden. Wenn nun alle
|
||||
Einstellungen passen, kann der Domain mit <code class="language-text">Add</code> angelegt werden. Nun kann
|
||||
sich der Nutzer ins SOGo anmelden
|
||||
um das Webmail zu nutzen oder mit einem Client wie Thunderbird anmelden.
|
||||
</p>
|
||||
<img src="../assets/img/mailcow_mailbox_new.jpg">
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_mailbox_new.jpg"></jl-img>
|
||||
<p>
|
||||
Nachdem wir nun die erste Mailbox erstellt haben, muss noch ein DNS Eintrag erstellt werden, damit andere
|
||||
Server validieren können, dass der sendende Server wirklich authorisiert dazu ist. Dazu im Mailcow Admin
|
||||
|
@ -184,6 +184,8 @@ docker-compose up</code>
|
|||
Configuration auf ARC/DKIM Keys gehen. Danach den beim Domain angegebenen Key kopieren und in der DNS
|
||||
Verwaltung als TXT Record mit der Bezeichnung <code class="language-text">dkim._domainkey</code> eintragen.
|
||||
</p>
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_dkim_webui.jpg"></jl-img>
|
||||
<jl-img src="/img/anleitungen/mailcow/mailcow_dkim_dns.jpg"></jl-img>
|
||||
<p>
|
||||
Nun ist unser Mail Server vollständig konfiguriert und kann auch eingesetzt haben. Um die Funktion zu testen
|
||||
sollte zuerst mit einem anderen Anbieter eine Mail an eine Adresse auf dem neuen Server gesendet werden.
|
||||
|
@ -191,11 +193,12 @@ docker-compose up</code>
|
|||
href="https://www.mail-tester.com/">mail-tester.com</a>. Auf dieser bekommt man eine Mail Adresse, an
|
||||
welche man eine Mail senden kann und danach alle fehler angezeigt bekommt.
|
||||
</p>
|
||||
<img src="../assets/img/mailcow_dkim_webui.jpg">
|
||||
<img src="../assets/img/mailcow_dkim_dns.jpg">
|
||||
<h2>Kommentare:</h2>
|
||||
<jl-comments_display></jl-comments_display>
|
||||
<jl-new_comment id="newComment"></jl-new_comment>
|
||||
</div>
|
||||
<jl-footer></jl-footer>
|
||||
<script src="../js/script.js" type="module"></script>
|
||||
<script src="/js/script.js"></script>
|
||||
<script>
|
||||
document.title = "Mailcow installieren - Jonas Leder";
|
||||
</script>
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title></title>
|
||||
<link href="../scss/style.scss" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -70,7 +70,7 @@ chown apache /var/www/localhost/htdocs/ -R</code>
|
|||
installiert. Nachdem dieser Schritt erfolgreich durchlaufen ist landen wir auf der Setup-Seite, dort müssen wir
|
||||
unsere Datenbank und unseren admin Nutzer wie unten im Bild zu sehen angeben. Falls gewünscht unten den Haken
|
||||
bei den empfohlenden Anwendungen entfernen.<br>
|
||||
<img src="../assets/img/nextcloud-setup.png"><br>
|
||||
<jl-img src="/img/anleitungen/nextcloud-setup.png"></jl-img><br>
|
||||
Nachdem die Datenbank angelegt und alle Apps installiert wurden, solltest du auf der Startseite der Nextcloud
|
||||
gelandet sein. Optional kann nun für mehr performance noch ein Memory-Caching konfiguriert werden. Wie dieses
|
||||
eingerichtet ist, kann dem <a
|
||||
|
@ -84,4 +84,4 @@ chown apache /var/www/localhost/htdocs/ -R</code>
|
|||
<jl-footer></jl-footer>
|
||||
|
||||
|
||||
<script src="../js/script.js" type="module"></script>
|
||||
<script src="/js/script.js"></script>
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title></title>
|
||||
<link href="../scss/style.scss" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<jl-header data-title="Installation von Snowboy"></jl-header>
|
||||
|
@ -31,7 +31,7 @@ build-essential git python3-setuptools python3-dev autotools-dev automake</code>
|
|||
<pre><code class="language-bash">wget http://downloads.sourceforge.net/swig/swig-3.0.12.tar.gz
|
||||
tar -xovzf swig-3.0.12.tar.gz
|
||||
cd swig-3.0.12
|
||||
wget https://sourceforge.net/projects/pcre../assets/files/pcre/8.42/pcre-8.42.tar.gz
|
||||
wget https://sourceforge.net/projects/pcre/8.42/pcre-8.42.tar.gz
|
||||
./Tools/pcre-build.sh
|
||||
./autogen
|
||||
./configure
|
||||
|
@ -57,8 +57,8 @@ make</code></pre>
|
|||
Rate". Das Problem der Soundkarte ist, dass sie nur 44kHz als Abtastrate unterstützt, Snowboy braucht aber eine
|
||||
Abtastrate von 16kHz wie ich daraufhin nachgelesen habe. Unten sind zwei Screenshots der Fehler angefügt.</p>
|
||||
<br>
|
||||
<img src="../assets/img/snowboy_no_mic.png">
|
||||
<img src="../assets/img/snowboy_wrong_sample_rate.png">
|
||||
<jl-img src="/img/anleitungen/snowboy/snowboy_no_mic.png"></jl-img>
|
||||
<jl-img src="/img/anleitungen/snowboy/snowboy_wrong_sample_rate.png"></jl-img>
|
||||
<h2>Kommentare:</h2>
|
||||
<jl-comments_display></jl-comments_display>
|
||||
<jl-new_comment id="newComment"></jl-new_comment>
|
||||
|
@ -66,4 +66,4 @@ make</code></pre>
|
|||
<jl-footer></jl-footer>
|
||||
|
||||
|
||||
<script src="../js/script.js" type="module"></script>
|
||||
<script src="/js/script.js"></script>
|
184
public/anleitungen/traefik.html
Normal file
|
@ -0,0 +1,184 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
<title></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<jl-header data-title="Traefik Reverse Proxy"></jl-header>
|
||||
<div id="content">
|
||||
<p>In der folgenden Anleitung werde ich darauf eingehen, wie man eine VM mit alpine Linux aufsetzt, darauf
|
||||
Docker installiert und dann als Container einen Traefik 2 reverse Proxy.</p>
|
||||
<p>Als erstes muss eine neue VM erstellt werden und als Boot Medium Alpine Linux eingelegt werden. Wenn die ISO
|
||||
gebootet ist kommt ein Login Fenster, hier einfach mit dem Benutzer <code class="language-text">root</code>
|
||||
anmelden. Danach sollte ein Terminal Prompt kommen. Um Alpine zu installieren muss der Befehl <code
|
||||
class="language-text">setup-alpine</code> eingegeben werden. Hierbei gilt es zu beachten, dass die ISO
|
||||
ein englisches Tastaturlayout benutzt. Der Bindestrich liegt hier auf dem ß. Als erstes muss nun im Setup
|
||||
das Tastaturlayout angegeben werden, um dieses auf Deutsch festzulegen zwie mal <code
|
||||
class="language-text">de</code> eingeben.
|
||||
Nachdem die Tastatur geändert wurde muss der Hostname festgelegt werden, meine VM heißt einfach <code
|
||||
class="language-text">docker</code>. In der darauffolgenden Netzwerkkonfiguration muss als erstes der
|
||||
Name des zu konfigurierendes Netzwerkinterfaces angegeben werden (meist <code
|
||||
class="language-text">eth0</code>)
|
||||
Im darauffolgenden Schritt wird die Methode der IP konfiguration angegeben. Im Folgenden setze ich hier auf
|
||||
DHCP. Nachdem das root Passwort (welches mit einem <a href="/passwordgen.html">Passwortgenerator</a>
|
||||
generiert wurde) festgelegt wurde muss die Zeitzone angegeben werden. Für Deutschland ist
|
||||
diese <code class="language-text">Europe/Berlin</code>. Einen Proxy brauchen wir nicht, genauso wie beim
|
||||
mirror können wir einfach mit enter bestätigen. Der SSH Server kann auch bei openssh belassen werden.
|
||||
Nachdem nun die Grundkonfiguration im Installer abgeschlossen ist, muss noch die Festplatte angegeben
|
||||
werden. Dafür wird eine Liste an erkannten Platten angezeigt. In meinem Fall war dies <code
|
||||
class="language-text">sda</code> und danach noch der Typ. Dies ist <code
|
||||
class="language-text">sys</code>,
|
||||
da das System auf die Platte installiert wird. Nach der Installation muss das System nochmal neugestartet
|
||||
werden.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Wenn das System nun von der Festplatte gebootet hat kann man sich mit dem Benutzer <code
|
||||
class="language-text">root</code> und dem zuvor gewählten Passwort anmelden. Danach muss als erstes das
|
||||
System auf den neuesten Stand gebracht werden und ein paar tool installiert werden. Dazu die beiden unten
|
||||
ausfgeführten Befehle eingeben.
|
||||
<pre>
|
||||
<code class="language-bash">apk update
|
||||
apk upgrade
|
||||
apk add nano htop git</code>
|
||||
</pre>
|
||||
Um nun Docker zu installieren muss als erstes die Community repo aktiviert werden. Dazu mit <code
|
||||
class="language-bash">nano</code> die Datei <code class="language-text">/etc/apk/repositories</code> öffnen
|
||||
und in der Zeile, welche mit <code class="language-text">community</code> endet das <code
|
||||
class="language-text">#</code> am Anfang entfernen. (Nicht in den Zeilen mit <code
|
||||
class="language-text">edge</code> im URL) Danach kann Docker installiert werden.
|
||||
</p>
|
||||
<pre>
|
||||
<code class="language-bash">nano /etc/apk/repositories
|
||||
apk update
|
||||
apk add docker docker-compose
|
||||
rc-update add docker
|
||||
/etc/init.d/docker start</code>
|
||||
</pre>
|
||||
<p>
|
||||
Nun ist Docker auf unserem System installiert und kann eingesetzt werden. Um Traefik 2 nun einzusetzen muss
|
||||
als erstes meine Vorlage von <a href="https://gitlab.jonasled.de/jonasled/traefik-config">hier</a>
|
||||
heruntergeladen werden. Danach muss in der Datei <code class="language-text">config/traefik.yml</code> unter
|
||||
letsencrypt => acme => email die E-Mail Adresse festgelegt werden, welche für letsencrypt verwendet werden
|
||||
soll. Danach noch die Berechtigungen von der Zertifikatsdatei einschränken. Bevor wir traefik starten könenn
|
||||
müssen wir noch ein Netzwerk namens <code class="language-text">web</code> angelegt werden. Nachdem nun
|
||||
alles vorbereit wurde kann dieser mit
|
||||
<code class="language-bash">docker-compose up</code> gestartet werden.
|
||||
</p>
|
||||
<pre>
|
||||
<code class="language-bash">git clone https://gitlab.jonasled.de/jonasled/traefik-config
|
||||
cd traefik-config
|
||||
nano config/traefik.yml
|
||||
chmod 600 letsencrypt/acme.json
|
||||
docker network create web
|
||||
docker-compose up
|
||||
# Wenn alles läuft strg und c drücken
|
||||
docker-compose up -d</code>
|
||||
</pre>
|
||||
<p>
|
||||
Traefik ist nun installiert und sollte von außen erreichbar sein. Als Antwort sollte bei nicht bekannten
|
||||
Domains immer ein 404 Fehler kommen. Zum testen setzen wir als nächstes den whoami Docker Container auf,
|
||||
dieser ist nur wenige kb groß und bietet einen minimalen Webserver. Dazu muss als erstes die unten
|
||||
angehängte docker-compose auf dem Host in einem neuen Ordner unter dem Namen <code
|
||||
class="language-text">docker-compose.yml</code> abspeichern und den Host anpassen. Danach kann der
|
||||
Container mit <code class="language-bash">docker-compose up</code> gestartet werden. Nun sollte nach 1-2
|
||||
Minuten auf dem zuvor angegebenen Domain die 404 Meldung durch eine Seite ersetzt werden. Falls dies nicht
|
||||
der Fall ist kann im Ordner, in dem der Traefik abgelegt wurde der Befehl <code
|
||||
class="language-text">docker-compose logs -f</code> ausgeführt werden um den Fehlerlog zu überprüfen.
|
||||
</p>
|
||||
<pre>
|
||||
<code class="language-yaml">version: "3.2"
|
||||
services:
|
||||
whoami:
|
||||
image: containous/whoami
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- web
|
||||
labels:
|
||||
- traefik.http.routers.whoami-https.rule=Host(`whoami.jonasled-test.xyz`)
|
||||
- traefik.http.routers.whoami-https.entrypoints=https
|
||||
- traefik.http.routers.whoami-https.tls=true
|
||||
- traefik.http.routers.whoami-https.tls.certresolver=letsencrypt
|
||||
- traefik.http.services.whoami.loadbalancer.server.port=80
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true</code>
|
||||
</pre>
|
||||
<p>
|
||||
Um die Konfigurationen für den Traefik Server zu erstellen verwende nutze ich ein kleines selber
|
||||
geschriebenes Tool, welches <a
|
||||
href="https://jonasled.pages.gitlab.jonasled.de/traefik-config-generator/">hier</a> erreichbar ist.
|
||||
</p>
|
||||
<h2>Verbesserungen</h2>
|
||||
<h3>HTTP auf HTTPS weiterleiten</h3>
|
||||
<p>Ich empfehle diesen Schritt für alle, da dadurch traefik den Nutzer automatisch von einer unverschlüsselten
|
||||
HTTP Verbindung auf eine verschlüsselte HTTPS verbindung weiterleitet. Dies verhindert das mitlesen der
|
||||
Daten durch dritte. Um die Weiterleitung einzurichten muss unter <code
|
||||
class="language-text">config/providers</code> eine neue Datei mit der Endung <code
|
||||
class="language-text">.yml</code> angelegt werden. (also z.B. <code
|
||||
class="language-bash">nano config/providers/http.yml</code>) Danach muss in die Datei der unten
|
||||
angeführte Snippet eingefügt werden und danach gescpeichert werden (strg + x und dann mit y und enter
|
||||
bestätigen)
|
||||
</p>
|
||||
<pre>
|
||||
<code class="language-yaml">http:
|
||||
routers:
|
||||
http-redirect:
|
||||
rule: HostRegexp(`{any:.+}`)
|
||||
middlewares: redirect
|
||||
service: dummy
|
||||
|
||||
middlewares:
|
||||
redirect:
|
||||
redirectscheme:
|
||||
scheme: https
|
||||
|
||||
services:
|
||||
dummy:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://0.0.0.0/"
|
||||
passHostHeader: true</code>
|
||||
</pre>
|
||||
<h2>Nicht Docker Services einbinden</h2>
|
||||
<p>
|
||||
Das Einbinden von Diensten, die nicht auf dem Docker Host laufen ist auch ziemlich einfach. Dafür muss nur
|
||||
eine Provider yaml Datei (Dateiendung .yml) im <code class="language-text">config/providers</code> Ordner
|
||||
mit dem nachfolgenden Inhalt angelegt werden. Um den Router zu nutzen muss <code
|
||||
class="language-text">servicename</code> und <code class="language-text">routername</code> durch Namen
|
||||
ersetzt werden. Zusätzlich muss im Router der Hostname und im Service der interne URL zu dem dienst, der von
|
||||
außen erreichbar sein soll gesetzt werden.
|
||||
</p>
|
||||
<pre>
|
||||
<code class="language-yaml">http:
|
||||
# Add the router
|
||||
routers:
|
||||
routername:
|
||||
service: servicename
|
||||
rule: Host(`whoami.jonasled-test.xyz`)
|
||||
tls:
|
||||
certresolver: letsencrypt
|
||||
|
||||
# Add the service
|
||||
services:
|
||||
servicename:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://10.0.0.1
|
||||
passHostHeader: true</code>
|
||||
</pre>
|
||||
<h2>Kommentare:</h2>
|
||||
<jl-comments_display></jl-comments_display>
|
||||
<jl-new_comment id="newComment"></jl-new_comment>
|
||||
</div>
|
||||
<jl-footer></jl-footer>
|
||||
<script src="/js/script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
79
public/anleitungen/vaultwarden.html
Normal file
|
@ -0,0 +1,79 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
<title></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<jl-header data-title="Vaultwarden Passwortmanager"></jl-header>
|
||||
<div id="content">
|
||||
<p>
|
||||
Vaultwarden ist ein Passwort Manager, welcher das Frontend und die Anwendungen von Bitwarden unterstützt,
|
||||
aber durch die Implementation in Rust schneller und Resourcensparender als der originale Server ist.
|
||||
Zusätzlich bietet Vaultwarden alle Premium Features kostenlos. Für die Installation setze ich Vorraus, dass
|
||||
auf dem System bereits ein Reverse Proxy wie <a href="traefik.html">Traefik 2</a> existiert und Docker mit
|
||||
Docker Compose installiert ist.
|
||||
</p>
|
||||
<p>
|
||||
Als erstes muss ein neuer Ordner auf dem Host erstellt werden. In diesem muss als erstes eine <jl-code>
|
||||
docker-compose.yml</jl-code> Datei mit dem Inhalt unten erstellt werden.
|
||||
<pre>
|
||||
<code class="language-yaml">version: "3.2"
|
||||
services:
|
||||
bitwarden:
|
||||
image: vaultwarden/server:latest
|
||||
restart: always
|
||||
ports:
|
||||
- 8080:80
|
||||
- 3012:3012
|
||||
environment:
|
||||
- ADMIN_TOKEN=
|
||||
- DOMAIN=https://bitwarden.jonasled-test.xyz
|
||||
- WEBSOCKET_ENABLED=true
|
||||
volumes:
|
||||
- ./data:/data</code>
|
||||
</pre>
|
||||
In der Datei muss noch der <jl-code>ADMIN_TOKEN</jl-code> auf ein sicheres Passwort aus einem <a
|
||||
href="/passwordgen.html">Passwortgenerator</a> ersetzt werden. Dieses Password wird benötigt um die Admin
|
||||
Oberfläsche zu erreichen. Da über die Oberfläche auch zum Beispiel Nutzerkonten gelöscht werden können sollte es
|
||||
wirklich sehr sicher sein. Zusätzlich muss auch der URL angegeben werden, über den der Passwortmanager später
|
||||
von außen erreichbar ist.
|
||||
</p>
|
||||
<p>
|
||||
Als nächstes muss der Reverse Proxy eingerichtet werden. Dazu muss Port 8080 als root Pfad (also /) und auf
|
||||
<jl-code>/notifications/hub</jl-code> port 3012 freigegeben werden. Traefik Nutzer können die Docker Compose
|
||||
welche <a href="https://gitlab.jonasled.de/-/snippets/8">hier</a> zu finden ist anstelle der obigen
|
||||
verwenden und dort die Hosts anpassen.
|
||||
</p>
|
||||
<p>
|
||||
Nachdem nun die Konfiguration abgeschlossen ist kann der Container mit dem Befehl <jl-code>docker-compose up
|
||||
</jl-code> das erste mal gestartet werden. Wenn der Container läuft und alles funktioniert kann der
|
||||
Container wieder mit strg und c gestoppt werden und danach mit <jl-code>docker-compose up -d</jl-code> im
|
||||
Hintergrund gestartet werden.
|
||||
</p>
|
||||
<p>
|
||||
Als erstes sollte das Admin Interface aufgerufen werden und ein SMTP Server für den E-Mail versand
|
||||
eingestellt werden. Um das Interface zu erreichen muss an den URL <jl-code>/admin</jl-code> angehängt und
|
||||
dass Passwort aus der <jl-code>docker-compose.yml</jl-code> angegeben werden.
|
||||
</p>
|
||||
<p>
|
||||
Nun sollte die Konfiguration vom Bitwarden / Vaultwarden abgeschlossen sein. Wenn man jetzt die URL aufruft
|
||||
kann man einen neuen Account anlegen. Im Client muss man zuerst in die Einstellungen gehen und dann dort den
|
||||
URL zum Server angeben. Ganz wichtig ist hierbei das vorgestellte HTTPS. Anschließend funktioniert der Login
|
||||
auf der Startseite mit dem gleichen Benutzer, wie auch im Webclient.
|
||||
</p>
|
||||
<jl-img src="/img/anleitungen/bitwarden/bitwarden_browser_1.jpg"></jl-img>
|
||||
<jl-img src="/img/anleitungen/bitwarden/bitwarden_browser_2.jpg"></jl-img>
|
||||
<h2>Kommentare:</h2>
|
||||
<jl-comments_display></jl-comments_display>
|
||||
<jl-new_comment id="newComment"></jl-new_comment>
|
||||
</div>
|
||||
<jl-footer></jl-footer>
|
||||
<script src="/js/script.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -4,30 +4,31 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title></title>
|
||||
<link href="scss/style.scss" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<jl-header data-title="Bildquellen"></jl-header>
|
||||
<div id="content">
|
||||
<ul>
|
||||
<li><span class="clickSpan" src='../assets/img/bannerHome.webp'>Bild oben</span>: Photo by <a
|
||||
<li><span class="clickSpan" src='/img/bannerHome.webp'>Bild oben</span>: Photo by <a
|
||||
href="https://unsplash.com/@hishahadat?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Shahadat
|
||||
Rahman</a> on <a
|
||||
href="https://unsplash.com/s/photos/programmer?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
|
||||
</li>
|
||||
<li><span class="clickSpan" src='../assets/img/bildHome.webp'>Bild Startseite</span>: Photo by <a
|
||||
<li><span class="clickSpan" src='/img/bildHome.webp'>Bild Startseite</span>: Photo by <a
|
||||
href="https://unsplash.com/@grohsfabian?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Fabian
|
||||
Grohs</a> on <a
|
||||
href="https://unsplash.com/s/photos/programmer?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
|
||||
</li>
|
||||
<li>
|
||||
<span class="clickSpan" src='../assets/img/laptop.jpg'>Bild Laptop</span>: <a href="https://sm.pcmag.com/t/pcmag_au/review/l/lenovo-thi/lenovo-thinkpad-l13-yoga_7bvb.1920.jpg">pcmag</a>
|
||||
<span class="clickSpan" src='/img/systeme/laptop.jpg'>Bild Laptop</span>: <a href="https://sm.pcmag.com/t/pcmag_au/review/l/lenovo-thi/lenovo-thinkpad-l13-yoga_7bvb.1920.jpg">pcmag</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<jl-footer></jl-footer>
|
||||
<script src="js/script.js" type="module"></script>
|
||||
<script src="/js/script.js"></script>
|
||||
<script>
|
||||
// Set document title to "Bildquellen - Jonas Leder"
|
||||
document.title = "Bildquellen - Jonas Leder";
|
||||
</script>
|
||||
</body>
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title></title>
|
||||
<link href="scss/style.scss" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -263,7 +263,7 @@
|
|||
mit Datenschutz-Generator.de von RA Dr. Thomas Schwenke</a></p>
|
||||
</div>
|
||||
<jl-footer></jl-footer>
|
||||
<script src="js/script.js" type="module"></script>
|
||||
<script src="/js/script.js"></script>
|
||||
<script>
|
||||
document.title = "Datenschutzerklärung - Jonas Leder";
|
||||
</script>
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 706 B |
65
public/gpg.txt
Normal file
|
@ -0,0 +1,65 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
0Kfe8lA=
|
||||
=cYJ8
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
BIN
public/img/anleitungen/bitwarden/bitwarden_browser_1.jpg
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
public/img/anleitungen/bitwarden/bitwarden_browser_2.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 612 KiB After Width: | Height: | Size: 612 KiB |
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 243 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 827 KiB After Width: | Height: | Size: 827 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 1,005 KiB After Width: | Height: | Size: 1,005 KiB |
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 735 KiB After Width: | Height: | Size: 735 KiB |
Before Width: | Height: | Size: 939 KiB After Width: | Height: | Size: 939 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 675 KiB After Width: | Height: | Size: 675 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 187 KiB |