Compare commits
141 Commits
snyk-upgra
...
15d927febd
Author | SHA1 | Date | |
---|---|---|---|
15d927febd | |||
|
e90d95b1e2 | ||
c1592d1d5a | |||
0e6d3de292 | |||
|
3c2ea09726 | ||
|
23d87d13b3 | ||
f34aa1a43a | |||
|
43bdfef332 | ||
ddd9d5b69e | |||
03ed5dc411 | |||
9db4f4a2d1 | |||
|
fd88a753de | ||
|
a28c4fed8c | ||
|
99bab4b66b | ||
1d326acff7 | |||
|
a27ef97e48 | ||
567851895d | |||
|
6e84cdc667 | ||
e6527d6e4d | |||
|
2844a07d3e | ||
e8ffeefaf8 | |||
c333789cdb | |||
|
859bc53ade | ||
|
8221062b49 | ||
6506e1d91e | |||
1c25f5ef05 | |||
d2488c8c1b | |||
9493a7b977 | |||
|
d905036905 | ||
|
abab9ed263 | ||
afeec5c499 | |||
1da2b0fcf7 | |||
|
aa6c0449a3 | ||
|
38ab029ae2 | ||
bdc4f5637f | |||
909be554cc | |||
|
1b698ff528 | ||
|
aad305d005 | ||
9ae4290fe3 | |||
|
ecb106c4c3 | ||
1b299da29a | |||
070c5097d1 | |||
912ab1e261 | |||
ebe2caa94e | |||
|
1612836a1f | ||
|
c9a3153643 | ||
|
5b4e802e6c | ||
636614930d | |||
1e76f0bbdd | |||
d1d747b011 | |||
cbe81a5dc9 | |||
c73ec86056 | |||
2822cd8718 | |||
e52ec71782 | |||
6adc3fd57d | |||
|
f0a3d10a1d | ||
|
42bc5be209 | ||
|
d68b3e51c4 | ||
|
4984dddc6b | ||
|
f1fd7f1c82 | ||
|
96f224619c | ||
|
e3caf67f09 | ||
|
65b81509f9 | ||
d94d664f2f | |||
aee00a1d89 | |||
04b1f0c1bb | |||
|
58d37e4941 | ||
|
2740ec31e8 | ||
|
709b0f21cf | ||
c73fa4773e | |||
e8fb994aaf | |||
51859f5fdb | |||
0117b60f24 | |||
|
fec5843cff | ||
|
d723b29c4f | ||
|
f0fbd541ea | ||
|
917ab7d901 | ||
a56058027e | |||
|
f128512bfd | ||
64ce161623 | |||
|
4f39d58d3b | ||
abdfc5c100 | |||
|
6f3a81e694 | ||
779c26cc9f | |||
|
037bfc816d | ||
28591fe51c | |||
05a571b8b2 | |||
|
b5b805e8eb | ||
|
8b88c801cd | ||
a79549758b | |||
|
c63a9e0743 | ||
7459a71ea1 | |||
|
d48c4c078e | ||
08eef25094 | |||
|
ce84bbdd51 | ||
4ab8ccd715 | |||
72acf4ab9e | |||
|
dc871724cd | ||
|
2047bc4424 | ||
1b08ff5443 | |||
d650f7e643 | |||
3dd9b4c13b | |||
|
cc470236e0 | ||
|
8976100b00 | ||
|
5679236b48 | ||
87979429dc | |||
|
b9721ffdc6 | ||
7c2233700f | |||
7981e30496 | |||
848aaf890b | |||
c37b3fa824 | |||
|
b76ac17824 | ||
|
7b5df78a2f | ||
|
682bf161a9 | ||
31597d25a6 | |||
a7710a4c32 | |||
1cb3ea2532 | |||
0ad80becd6 | |||
02e4c01bd0 | |||
a83befd5b2 | |||
7c93cd76d9 | |||
c7ef1146b3 | |||
|
d07d7880bf | ||
06784424ab | |||
98249f0ddb | |||
c326bfa87b | |||
47578d2411 | |||
|
9098e27cae | ||
b9d1b1ae13 | |||
|
0ce8af8cc7 | ||
|
6a9b948601 | ||
6eee5ac150 | |||
7e897d7aa7 | |||
2e65d236d0 | |||
9b34c6226b | |||
af3497f3d8 | |||
029e59c029 | |||
|
07b36b5b06 | ||
|
084a9cdfc2 | ||
9f05d8a715 | |||
283c25681b |
@@ -4,16 +4,16 @@ name: default
|
||||
|
||||
steps:
|
||||
- name: install
|
||||
image: node:19.4.0
|
||||
image: node:19.9.0
|
||||
commands:
|
||||
- npm install
|
||||
|
||||
- name: test
|
||||
image: node:19.4.0
|
||||
image: node:19.9.0
|
||||
commands:
|
||||
- npm run test:coverage
|
||||
|
||||
- name: build
|
||||
image: node:19.4.0
|
||||
image: node:19.9.0
|
||||
commands:
|
||||
- npm run build
|
||||
|
@@ -1,4 +0,0 @@
|
||||
*.*
|
||||
!*.ts
|
||||
!*.tsx
|
||||
/public/
|
53
.eslintrc
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
|
||||
"extends": [
|
||||
"plugin:import/typescript",
|
||||
"plugin:import/errors",
|
||||
"airbnb-typescript",
|
||||
"@mastermindzh/eslint-config",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"jest",
|
||||
"@typescript-eslint",
|
||||
"simple-import-sort",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": [
|
||||
"**/*.test.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/internal/**/*.ts"
|
||||
]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": ["off"],
|
||||
"@typescript-eslint/no-use-before-define": ["off"],
|
||||
"@typescript-eslint/quotes": ["error", "double"],
|
||||
"@typescript-eslint/naming-convention": ["error", {
|
||||
"format": ["camelCase", "UPPER_CASE", "snake_case", "PascalCase"],
|
||||
"leadingUnderscore": "allow",
|
||||
"selector": "parameter"
|
||||
}],
|
||||
"react/static-property-placement": ["off"],
|
||||
"react/prop-types": ["off"],
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"typescript": {}
|
||||
}
|
||||
}
|
||||
}
|
10
.github/workflows/release.yml
vendored
@@ -5,6 +5,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -19,15 +22,10 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.14.0
|
||||
node-version: 22.12.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
|
||||
# - name: Release
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# run: npm run semantic-release
|
||||
|
@@ -1,6 +1,3 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit "$1"
|
||||
npm run lint:staged
|
||||
npm run test
|
@@ -1,3 +1,4 @@
|
||||
module.exports = {
|
||||
...require("@mastermindzh/prettier-config")
|
||||
...require("@mastermindzh/prettier-config"),
|
||||
trailingComma: "all",
|
||||
};
|
||||
|
22
.prettierrc.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"requirePragma": false,
|
||||
"insertPragma": false,
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"endOfLine": "lf",
|
||||
"semi": true,
|
||||
"singleAttributePerLine": false,
|
||||
"embeddedLanguageFormatting": "auto",
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"experimentalTernaries": false
|
||||
}
|
@@ -9,13 +9,16 @@
|
||||
"font-weight-notation": "named-where-possible",
|
||||
"function-url-no-scheme-relative": true,
|
||||
"function-url-quotes": "always",
|
||||
"max-empty-lines": 1,
|
||||
"no-descending-specificity": true,
|
||||
"no-duplicate-selectors": true,
|
||||
"order/order": ["custom-properties", "declarations"],
|
||||
"order/order": [
|
||||
[
|
||||
"custom-properties",
|
||||
"declarations"
|
||||
]
|
||||
],
|
||||
"order/properties-alphabetical-order": true,
|
||||
"property-no-unknown": [true, { "ignoreProperties": ["/^lost-/"] }],
|
||||
"string-quotes": "double",
|
||||
"value-keyword-case": "lower"
|
||||
}
|
||||
}
|
||||
|
4
.vscode/settings.json
vendored
@@ -5,12 +5,16 @@
|
||||
"fontawesome",
|
||||
"fortawesome",
|
||||
"frontmatter",
|
||||
"gitops",
|
||||
"homelab",
|
||||
"Jellyfin",
|
||||
"Lieshoutt",
|
||||
"Rickvan",
|
||||
"rickvanlieshoutcom",
|
||||
"slsw",
|
||||
"soundcloud",
|
||||
"todos",
|
||||
"unraid",
|
||||
"weibo"
|
||||
],
|
||||
"grammarly.selectors": [
|
||||
|
954
CHANGELOG.md
@@ -12,3 +12,4 @@ A list of categories used in the blog
|
||||
| exploits | blogs about exploits and bugs in open-source software |
|
||||
| lego | blogs about Lego |
|
||||
| house | Content about the place I live |
|
||||
| life | Posts about life |
|
||||
|
@@ -66,4 +66,4 @@
|
||||
"phone": "+31614436562"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
content/pages/resume/DDD-vijfhart.pdf
Normal file
@@ -18,18 +18,26 @@ template: "page"
|
||||
|
||||
**Open-source aficionado**<br />
|
||||
_Always_<br />
|
||||
A fair share of my private work is done through open-source media. I don't have a portfolio but I have a [Github](https://github.com/mastermindzh) where I share most things.
|
||||
A fair share of my private work is done through open-source media. I don't have a portfolio but I have a [Github](https://github.com/mastermindzh) where I share most things. If you're interested in what else I do, or why, let me know and I'll share a detailed description.
|
||||
|
||||
**CTO at Frontliners (previously INFORIT)**<br />
|
||||
_2018 - Now_<br />
|
||||
At Frontliners I am responsible for the entire technical architecture of the new TFX stack that I launched shortly after 2018. This involves setting up and maintaining a modern event-based microservice architecture on a Kubernetes-based SaaS cloud solution and migrating to modern programming languages such as dotnetcore and React.
|
||||
At Frontliners I am responsible for the entire technical architecture of the new TFX stack that I launched shortly after 2018. This involves setting up and maintaining a modern event-based microservice architecture on a Kubernetes-based SaaS cloud solution and migrating to modern programming languages such as dotnetcore, Supabase and React. We've delivered a few smaller pieces of software but mainly focus on building a next-gen Transport Management System called TFX.
|
||||
|
||||
The other main responsibility I have is creating and maintaining a coherent team of lead developers by sharing knowledge, making decisions, and empowering leads to explore technologies outside of their comfort zone.
|
||||
The other main responsibility I have is creating and maintaining, and hiring, a coherent team of (lead) engineers by sharing knowledge, making decisions, and empowering them to explore technologies outside of their comfort zone.
|
||||
This includes everything from platform to e2e engineering.
|
||||
During my time at Frontliners, the company has grown from 5-7 in-house developers on a single product to 35-40 technical engineers on multiple projects.
|
||||
|
||||
At Frontliners I also help with the recruitment of new employees, setting up and maintaining agile strategies including proper test management, and general software development.
|
||||
During my time at Frontliners, we've grown from 5-7 in-house developers to 10, full-sized, SCRUM teams.
|
||||
The teams I manage will be listed below. Some of the core concepts they work with will also be listed.
|
||||
Apart from the last one I also play an active role in them:
|
||||
|
||||
Most relevant technologies used: dotnetcore, React, Angular, Kafka, RabbitMQ, Mongo, PostgreSQL, Growthbook, Docker & Kubernetes
|
||||
- 4 Full-fledged (6-8 people) Scrum development teams working on the latest product.
|
||||
- day-to-day management of people done by "Chapter leads"
|
||||
- React, Docker, .NET 9+ (core), Mongo, Kafka, Debezium, DDD, CQRS
|
||||
- Business Integration team (Supabase, Kysely, PostgreSQL)
|
||||
- Platform team (Kubernetes, Terraform, ArgoCD, CI/CD pipelines, Vault, Keycloak, etc...)
|
||||
- Legacy team (ASP.NET, MSSQL, MongoDB, Elasticsearch)
|
||||
- Legacy team (Clarion, SQL-Sybase)
|
||||
|
||||
**Hanflex employee**<br />
|
||||
_September 2015 - July 2018_<br />
|
||||
@@ -64,6 +72,7 @@ Building an interactive and fully customizable dashboard on top of VAA's existin
|
||||
I'll list some of the training courses I've had whilst working.
|
||||
Some of these have (official) certificates, some don't, but all have brought value and knowledge.
|
||||
|
||||
- Domain Driven Design [Vijfhart](./DDD-vijfhart.pdf)
|
||||
- Young Executive Program (YEP) - [De Baak](https://debaak.nl/trainingen/young-executives-program)
|
||||
- Hiring & interview training ([icm.nl](https://www.icm.nl/opleidingen-en-trainingen/hrm/selectiegesprekken-voeren/))
|
||||
- Conversation techniques & de-escalation ([TIJDwinst.com](https://gesprekstechnieken.com/cursus-gesprekstechnieken/)](<https://gesprekstechnieken.com/cursus-gesprekstechnieken/>)).
|
||||
|
@@ -102,7 +102,7 @@ First, let's add a script to the `package.json` that will do our commit for us:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"commit": "git-cz",
|
||||
"commit": "git-cz"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -138,14 +138,7 @@ module.exports = {
|
||||
"subject-full-stop": [2, "never", "."],
|
||||
"type-case": [2, "always", "lower-case"],
|
||||
"type-empty": [2, "never"],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"first type",
|
||||
"second type",
|
||||
],
|
||||
],
|
||||
"type-enum": [2, "always", ["first type", "second type"]],
|
||||
},
|
||||
prompt: {
|
||||
questions: {
|
||||
@@ -155,7 +148,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
As you can see we have configured a whole bunch of things, but there are 2 things that matter for now:
|
||||
@@ -234,8 +226,7 @@ module.exports = {
|
||||
emoji: "💎",
|
||||
},
|
||||
refactor: {
|
||||
description:
|
||||
"A code change that neither fixes a bug nor adds a feature",
|
||||
description: "A code change that neither fixes a bug nor adds a feature",
|
||||
title: "Code Refactoring",
|
||||
emoji: "📦",
|
||||
},
|
||||
@@ -274,12 +265,10 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
description:
|
||||
"What is the scope of this change (e.g. component or file name)",
|
||||
description: "What is the scope of this change (e.g. component or file name)",
|
||||
},
|
||||
subject: {
|
||||
description:
|
||||
"Write a short, imperative tense description of the change",
|
||||
description: "Write a short, imperative tense description of the change",
|
||||
},
|
||||
body: {
|
||||
description: "Provide a longer description of the change",
|
||||
@@ -340,7 +329,7 @@ Now, let's add a Husky git hook to combat future commits that are unconventional
|
||||
npx husky-init && npm install
|
||||
```
|
||||
|
||||
This will automatically create the `.husky/pre-commit` file. Let's edit it and add commitlint:
|
||||
This will automatically create the `.husky/` directory. Let's create a file in it called `commit-msg` and add commitlint:
|
||||
|
||||
```sh
|
||||
#!/usr/bin/env sh
|
||||
@@ -377,7 +366,7 @@ First, let's add some npm scripts again:
|
||||
"release": "standard-version",
|
||||
"release:minor": "standard-version --release-as minor",
|
||||
"release:patch": "standard-version --release-as patch",
|
||||
"release:major": "standard-version --release-as major",
|
||||
"release:major": "standard-version --release-as major"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@@ -95,11 +95,11 @@ Below you'll find a list of some of the biggest things I've changed.
|
||||
- I added a Dark mode with a little [react-toggle](https://github.com/aaronshaf/react-toggle) to switch between light/dark.
|
||||
- Blog posts now show a header that includes the "back to articles" button and my name.
|
||||
- Added code block theming for both light and dark mode
|
||||

|
||||

|
||||
- I added support for tables with a bit of styling around them (slight indent and row styling)
|
||||

|
||||

|
||||
- I added the ability to quote others in a beautiful way
|
||||

|
||||

|
||||
- Medium like image zooming (click any of the images above)
|
||||
|
||||
#### The posts query bug
|
||||
|
@@ -14,7 +14,6 @@ description: "Flashing the LSI-9211 used to be way more difficult, luckily the E
|
||||
socialImage: ./media/flash-result.jpg
|
||||
---
|
||||
|
||||
|
||||
I've been building a new storage-oriented server for a while now and have yet again decided to go with (3 +1 backup) LSI 9211 raid controllers.
|
||||
|
||||
The reason I keep going for these specific raid controllers is quite simple. The card can theoretically support (8x500MB) 4GB/s in throughput divided over 8 drives (2x SFF-8087) which is near the maximum for consumer hard drives.
|
||||
|
@@ -0,0 +1,356 @@
|
||||
---
|
||||
title: "A Hidden Life of Pain, Sorrow, Misery and Rampant Emotions"
|
||||
description: "A deeply personal story about grief, memory, and the long road through the shadows. For anyone who’s lost someone they can never get back."
|
||||
date: 2025-05-04
|
||||
template: "post"
|
||||
category: "life"
|
||||
tags:
|
||||
- life
|
||||
- grief
|
||||
- memory
|
||||
- loss
|
||||
- trauma
|
||||
- healing
|
||||
- personal
|
||||
- poetry
|
||||
coverImage: ./media/cover.png
|
||||
---
|
||||
|
||||

|
||||
|
||||
<center>⚠️ <strong>Content Warning</strong><br/>
|
||||
This blog contains themes of death, grief, mental health, and emotional trauma.<br />
|
||||
It is raw, honest, and may be deeply triggering for some.<br />
|
||||
Please read with care and stop if it becomes too heavy. You're not alone.</center>
|
||||
|
||||
## A Hidden Life of Pain, Sorrow, Misery and Rampant Emotions
|
||||
|
||||
Some kinds of missing are temporary. A friend you haven’t seen in a while. A conversation left unfinished. A plan postponed.
|
||||
|
||||
But then there's another kind.
|
||||
|
||||
The kind that settles into your bones. The kind that haunts you during the quiet hours. The kind that never finds a conclusion.
|
||||
|
||||
That kind is not absence. It's **_gone_**. Irrevocable, final, cruel. A door slammed shut that you still find yourself knocking on, long after you’ve forgotten why you started.
|
||||
|
||||
It never opens. And still, you try the handle every now and then.
|
||||
|
||||
## In the background of every moment
|
||||
|
||||
Losing a loved one doesn’t stay in one room of your house though, it isn't one door that is locked. It leaks under doors and through cracks. You find it in the most unexpected places.
|
||||
|
||||
You're sipping coffee with someone, and they mention a trip they took. Suddenly, you're remembering the trip _you_ never took. The one you'd planned. The one that cancer took from you.
|
||||
|
||||
They talk about a shared playlist. You remember the song you danced to in the rain, soaking wet but laughing anyway. The same song you now skip every time it comes on because it hurts too much
|
||||
|
||||
Someone laughs about chipped mugs. You think of the one she cracked and called "vintage," and how it's still at the back of your cabinet. Untouched. Sacred.
|
||||
|
||||
It’s like walking through a museum where only _you_ know what’s behind the glass.
|
||||
|
||||
> They talk, they laugh, the world goes on,<br />
|
||||
> Yet I sit with shadows, from dusk to dawn. <br />
|
||||
> A smile here hides a silent scream,<br />
|
||||
> A haunted heart, a broken dream.
|
||||
|
||||
<figure class="float-right" style="width: 240px">
|
||||
<img src="/media/the-flashback-no-one-sees.png" alt="A foggy bathroom mirror with a red lipstick kiss at the center, surrounded by a shadowy, intimate atmosphere.">
|
||||
</figure>
|
||||
|
||||
These are what I call _mind pops_. Short for Involuntary Autobiographical Memory Chains. I wrote a journal-like book once, and later a smaller one. I wanted to share them. I tried. But I was never brave enough to.
|
||||
|
||||
Mind pops come uninvited. And they come _hard_.
|
||||
Here are some examples from my book:
|
||||
|
||||
- The eyeliner she never quite got even.
|
||||
- The way she tucked me in, thinking I was asleep.
|
||||
- The fake wedding we planned just to see what colors we'd choose.
|
||||
- Your love for Twilight. And the moment we realized, quietly, you'd never get to read the next book—if there ever was one.
|
||||
- The mole you've had removed and I pretended not to notice. I did miss it so.
|
||||
- The mixture of guilt, remorse and happiness in your eyes as conscious life ebbed away.
|
||||
|
||||
They’re not just memories. They’re grenades. Quiet ones. You never know when they'll detonate. Nor whether they will ruin your entire day.
|
||||
|
||||
> A flicker in time, unbidden and wild,<br />
|
||||
> A whisper of laughter, a memory smiled.<br />
|
||||
> Then tears arrive with no request,<br />
|
||||
> The heart’s old wounds laid bare, unblessed.
|
||||
|
||||
## Unfair doesn’t even begin to cover it
|
||||
|
||||
We grow up believing that the world has some kind of order to it. That bad things happen, sure—but not _this_ bad. Not _this_ unfair.
|
||||
You lose someone who made life feel infinite. You watch them disappear into the haze of hospital lights and soft-spoken specialists.
|
||||
|
||||
Cancer doesn’t care if you’re young. Or kind.<br />
|
||||
Or if you had a hundred plans left.
|
||||
It just takes.<br />
|
||||
And keeps taking.
|
||||
|
||||
And you're left with jewelry in a drawer. Her necklace still smells like her.<br />
|
||||
Sometimes I take it out. I hold it. Smell it. Let it transport me. I don’t even know if I should still be doing that. But it’s a thread. One of the last ones.
|
||||
|
||||
> This necklace, cold, rests in my hand,<br />
|
||||
> The last soft echo of a silent land.<br />
|
||||
> I breathe her in, though she's not near,<br />
|
||||
> A ghost in scent, in touch, in tear.
|
||||
|
||||
## The masks we wear
|
||||
|
||||
For a long time, no one knew. Most don't, still.
|
||||
|
||||
People see the version of you they expect: Smiling. Joking. Coping. Existing.<br />
|
||||
But behind the eyes, you’re unraveling. Constantly. Because grief doesn’t pause.<br />
|
||||
Not **_really_**.
|
||||
|
||||
Sharing this is hard. Not because I don’t want to, but because it makes you so vulnerable. Because as you're writing or speaking, memories pop up uninvited. They derail your words. They choke your sentences. They just pop in... bastards...
|
||||
|
||||
And even when you manage to share, others often don’t understand. They compare. They say things like “I lost someone too,” but it’s different. This kind of grief? This kind of loss? It’s heavier. It cuts deeper. And hearing it treated like it’s the same as all the rest—_that_ makes you angry.
|
||||
|
||||
Here’s something I once wrote, when I thought no one would ever read it:
|
||||
|
||||
> I'm surrounded by many figures, but still alone...<br />
|
||||
> No one sees me, nor the pain behind my mask.<br />
|
||||
> They see the smiling, happy guy I show them.<br />
|
||||
> You can't blame them though, how can I let them see the darkness in my heart? <br />
|
||||
> It would scare them, for it has been torn apart.
|
||||
|
||||
I didn’t want to scare people. Or burden them. So I said nothing.<br />
|
||||
For years.
|
||||
|
||||
## Strange rituals of survival
|
||||
|
||||
<figure class="float-right" style="width: 240px">
|
||||
<img
|
||||
src="/media/strange-rituals-of-survival.png"
|
||||
alt="A softly lit restaurant table for two. One plate is untouched, the other half-eaten. A single candle flickers between them, hinting at absence and memory.">
|
||||
</figure>
|
||||
|
||||
Grief makes you do strange things too..<br />
|
||||
One of my telltale behaviors is that I start eating, lots... but there are weirder ones.<br />
|
||||
More unexpected ones...
|
||||
|
||||
I’ve spoken out loud to pictures. Imagined new conversations. Tried to conjure her in dreams. I’ve spent hours crafting memories that never happened, just to feel like I got one more day.
|
||||
I name items, characters, and other things in games after her.
|
||||
If I eat alone, at a restaurant, I order what she would’ve. (who do I kid, I sometimes do it with company too...)
|
||||
|
||||
And I wrote a song. It’s unfinished though—like she was.
|
||||
|
||||
**🎵 (Refrain)**<br />
|
||||
_A strand of blonde hair, as delicate as sun,_<br />
|
||||
_A mystery found, in silence it's spun._<br />
|
||||
_But memory, like a fleeting breeze,_<br />
|
||||
_Fades away as a haze, in the time we seize._<br /><br />
|
||||
|
||||
**🎶(Verse)**<br />
|
||||
_Days pass like rustling leaves,_<br />
|
||||
_Stories fade, like quiet thieves._<br />
|
||||
_How swiftly memory slips away,_<br />
|
||||
_Like an old melody, lost in the fray._<br />
|
||||
|
||||
_A mysterious gate, in the labyrinth of the mind,_<br />
|
||||
_Locked and hidden, what secrets behind?_
|
||||
|
||||
As I'm writing this blog however, I have a sudden urge to add sound to the lyrics, it'll still be unfinished, but a little farther along.
|
||||
I used both a piano and a viola to give it a more interesting sound than just the piano.
|
||||
|
||||
<center>
|
||||
<audio controls>
|
||||
<source src="./media/a-strand-of-blonde-hair-as-delicate-as-sun.mp3" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
|
||||
<br />
|
||||
<a href="./media/a-strand-of-blonde-hair-as-delicate-as-sun.pdf" target="_blank">score</a> |
|
||||
<a href="./media/a-strand-of-blonde-hair-as-delicate-as-sun.mid" target="_blank">midi</a> |
|
||||
<a href="./media/a-strand-of-blonde-hair-as-delicate-as-sun.flac" target="_blank">flac</a>
|
||||
</center>
|
||||
|
||||
## The final moments and the words that stayed
|
||||
|
||||
There are memories too hard to sit with for long. But they come back, as clear as yesterday.
|
||||
|
||||
The quiet nights filled with endless conversation. Plans made, even though we both knew we'd never see them through. Fantasies of places we'd never travel, children we’d never have, shows we’d never finish.
|
||||
|
||||
She once told me something that still echoes today:
|
||||
|
||||
> "You have to grieve me, for a while... and then you move on. <br />
|
||||
> You find someone else to love. Someone who makes you feel something again. <br /> You deserve that."
|
||||
|
||||
I shook my head. _"I can’t. I **won’t**. Not because I don’t want to feel again. But because abstinence, to me, feels like remembering you as you are. Eternal."_ <br />
|
||||
We both cried after that. For different reasons, I think.
|
||||
|
||||
### My words weren't true though
|
||||
|
||||
But life went on. And I did try. I did have relations after her.
|
||||
And every single one felt... off. Wrong. Like stepping into a life that wasn’t meant to fit me anymore.
|
||||
|
||||
They weren’t bad people. In fact, I think some of them really tried to care. But the weight of comparison was impossible to escape. The shadow she left was too wide.
|
||||
|
||||
Sometimes I’m mad at myself for trying. Other times, I’m mad at myself for failing to let them in.
|
||||
And always,...always,.. I feel like I betrayed her just a little.
|
||||
|
||||
I know grief isn’t rational. It doesn’t follow clean lines.<br />
|
||||
And maybe that’s what hurts most of all—knowing I tried, and still came back to the same place. <br />
|
||||
Alone, but full of someone who isn’t here.
|
||||
|
||||
One day... _maybe_.
|
||||
|
||||
## Why I’m finally speaking
|
||||
|
||||
It’s taken me over a decade to find the strength to say any of this.
|
||||
And maybe the only reason I can now… is her.
|
||||
|
||||
But also—my mother.<br />
|
||||
She’s terminally ill. And while it’s “natural” for a parent to go before their child, it’s still excruciating. <br />
|
||||
We don't know how long she's got left, but I know we'll make the best of it. For both of us.
|
||||
|
||||
I do find myself ashamed to admit that it hasn’t hit me quite the same way. But it’s not lesser. It’s just different. A different wound on the same, torn, body.<br />
|
||||
And this time, I’m watching it happen while knowing exactly what’s coming. And I'm scared. <br />
|
||||
For **everyone**.
|
||||
|
||||
I think often of my brothers. For them, this may be their first real loss. Their first brush with the permanence of death.<br />
|
||||
And it breaks me in new ways to imagine them feeling what I felt, without ever seeing it coming. I'll try to be there for them, as you do.
|
||||
|
||||
But it's at least part of why I’m writing now. That’s why I’m finally saying all this.<br />
|
||||
Because pain grows in the dark.
|
||||
|
||||
In a moment of foolish bravery, I once planned to publish my journaled thoughts. That book. That attempt to show what this kind of loss looks like.<br />
|
||||
I didn’t. I **couldn’t**.
|
||||
|
||||
But maybe I can share this blog.
|
||||
|
||||
## On those who want(ed) to leave
|
||||
|
||||
I need to say something with a very serious tone now, and I do hope you read it gently and thoroughly:
|
||||
|
||||
I’ve met people over the years who’ve carried this same grief. And some of them have found themselves teetering.
|
||||
|
||||
On the edge.
|
||||
|
||||
Wondering if there’s peace on the other side of absence.<br />
|
||||
Let me say this clearly: I have never thought of taking that road for myself. If anything, I would _ask_ for eternal life. Even with the grief.<br />
|
||||
But if you are someone who has stood on that edge:
|
||||
|
||||
**Don’t. Please.**
|
||||
|
||||
The pain is real. The grief is heavy. But life—_even broken life_—has light in it still. And sometimes, all you need is one person willing to sit with you in the dark.
|
||||
|
||||
Let me be that person for a moment. Just long enough to remind you: the door might be closed, but the room isn’t empty.
|
||||
|
||||
If you are on the edge. Seek help. <sup>([dutch](https://www.113.nl) | [english](https://www.113.nl/english))</sup>
|
||||
Whatever you do, don't pass on the pain.
|
||||
|
||||
## What help looks like
|
||||
|
||||
Over the past two years, I’ve slowly—_achingly_—learned to live with it.
|
||||
And honestly, I'm happier now than I've ever been since.
|
||||
|
||||
I’ve had friends. Real ones. Some with the training to guide me through the murk. Others who simply sat there while I unraveled, trying their best.
|
||||
|
||||
They didn’t try to fix me. They didn’t rush me. They just showed up.
|
||||
They had always been there, I just didn't allow them to be there for me before.
|
||||
|
||||
I’ve learned that grief isn’t a wall to climb or a puzzle to solve. It’s a landscape.
|
||||
|
||||
You don’t conquer it.<br />
|
||||
You _walk it_. One aching step at a time.<br />
|
||||
And if you're lucky... you don’t walk it alone.
|
||||
|
||||
### To those who held me together
|
||||
|
||||
There’s a quiet kind of heroism in being the one who stays.
|
||||
Not the one who fixes. Not the one with the right words.
|
||||
Just… the one who stays.
|
||||
|
||||
To those of you who sat with me in silence,
|
||||
who didn’t flinch at the weight of my words,
|
||||
who let me cry without needing to understand why.
|
||||
thank you.
|
||||
|
||||
To those who asked how I was and meant it.
|
||||
Who asked again when I lied the first time.
|
||||
Who sent messages I never responded to, but always read.
|
||||
Thank you.
|
||||
|
||||
To those who reminded me that it was okay to laugh.
|
||||
To love again.
|
||||
To mess up.
|
||||
To heal slow.
|
||||
Thank you.
|
||||
|
||||
And to the ones who didn’t even know they helped—
|
||||
you did.
|
||||
|
||||
You all held the thread when I couldn’t anymore.
|
||||
And maybe that’s the reason I’m still happy...
|
||||
Still holding her memory, but not drowning in it.
|
||||
|
||||
## Before I finally sign off
|
||||
|
||||
This entire blog was something I never thought I could write.
|
||||
And what comes next,this next part, is something I swore I _never_ would share.
|
||||
|
||||
But here we are.
|
||||
|
||||
What follows is the foreword and some pages from the book I wrote years ago. A raw collection of thoughts, pain, patterns, and memory. I’ve always wanted to share it, and I’ve always been too scared. Too protective. Too convinced no one would understand.
|
||||
|
||||
My grief has changed, not disappeared, never that. But shifted.
|
||||
And time has a way of loosening things you once held too tight.
|
||||
Of showing you that even pain deserves to breathe.
|
||||
|
||||
So this is me, still afraid—but doing it anyway.<br />
|
||||
This is me, defiant in the face of silence.<br />
|
||||
This is me, opening the door just a little wider.
|
||||
|
||||
What comes next is the truth as I once wrote it, in a different voice, at a different time (2021-2022)—but still very much me.
|
||||
|
||||
### A Peek into the Pain and Misery
|
||||
|
||||
**The foreword to a book I once wrote titled "A Peek into the Pain and Misery"**
|
||||
|
||||
We, humans, perceive the vast majority of other humans we encounter as bystanders, observers and otherwise side characters in our own lives. Yet, each and every one of them has a life of comparable complexity to our own. Realizing that for the first time is an emotion called "sonder".
|
||||
|
||||
This book will give you a small glimpse into my life. By the end, you might find that I'm more complex than you've thought till now. That there's another side to the happy, carefree guy I show the world for the majority of my time.
|
||||
|
||||
Truth is, that life hasn't always been easy. In fact, I'd say that after the turning point my life has never felt easy anymore. In fact, for at least 10 years now I've been living with prodigious amounts of a single emotion: bereavement.
|
||||
|
||||
And though I've tried not to let the bereavement take over my life, or in fact make it known to others. it's always been there. I've effectively come to think of it as a zit. Constantly there, constantly annoying and once it's gone away it's only a matter of time before it shows up again somewhere else on your body.
|
||||
|
||||
Ten years is truly a long time. In it, I've tried countless things to forget my sorrow. I've written songs and poetry. I've indulged in life's greatest things. I've thrown myself into the pursuit of knowledge and I've even tried to seclude myself, thinking that I'd become a burden to the world.
|
||||
However, In trying to cope with it I've also made many mistakes. I've experienced more loss, extirpated meaningful connections and, to my deepest regret, hurt others.
|
||||
And even now, 10 full years later I'm still burdened by the same emotions, though now compounded by the feelings and mistakes of the past 10 years.
|
||||
|
||||
Furthermore, During these 10 years, on nearly every day I've been haunted by something I've come to know as Involuntary Autobiographical Memory Chains, or as I now like to call them: mind pops.
|
||||
Mind pops are sudden memory flashes, seemingly triggered at random, about things I've experienced prior. These mind pops never seem to go away and can alter my mood for the rest of the day with the emotions they invoke.
|
||||
|
||||
I've actually been able to capture the entire story of the past 10 years in a book, of sorts, titled "a hidden life of pain, sorrow, misery and rampant emotions". And though I've always wanted to share the entire story, I've never been strong enough to do so. Not 10 years ago, not now, and maybe not ever.
|
||||
|
||||
But... The urge to share... something... is still there.
|
||||
So on the 1st of January 2021, I set out on a challenge. I wanted to, at least, share a part of the story. Not one from a distant past though, one of constant remembrance. One of pain and pleasure. One I've lived for every day of the entire year. One I'll live for every day for many years to come.
|
||||
|
||||
Every page henceforth will contain one "mind pop" that I've had on the date listed. So turn over this page and join me on my struggle from January 1st through December 31st of 2021.
|
||||
|
||||
<figure style="width: 240px">
|
||||
<img
|
||||
src="/media/pages/sept-12.png"
|
||||
alt="page of september 12th, a teardrop at the bottom to indicate a sad memory, and the text: my colleagues and I went skiing. They didn't know it was something we had planned to do after our studies which made it infinitely more difficult for me. I didn't really like it, probably because you weren't there to share in my happiness. On the way there and back my colleague drove straight past a place where we had lunch, that hit home a just little too hard.">
|
||||
</figure>
|
||||
|
||||
<figure style="width: 240px;">
|
||||
<img
|
||||
src="/media/pages/march-19.png"
|
||||
alt="page of march 19th, a flower at the bottom to indicate a happy memory, and the text: The warmth you brought into a room.
|
||||
A happy one">
|
||||
</figure>
|
||||
|
||||
<figure style="width: 240px;">
|
||||
<img
|
||||
src="/media/pages/jan-11.png"
|
||||
alt="page of january 11th, a teardrop at the bottom to indicate a sad memory, and the text: Your scared helplessness which slowly transitioned into anger, to violence and back to anxiousness again.">
|
||||
</figure>
|
||||
|
||||
## A postscript
|
||||
|
||||
If you’ve read this far—thank you.
|
||||
That alone means more than you know.
|
||||
|
||||
_- Rick, mastermindzh, lycan, xxxroosjexxx, and other alias' you might know me from._
|
After Width: | Height: | Size: 2.4 MiB |
After Width: | Height: | Size: 2.2 MiB |
After Width: | Height: | Size: 2.3 MiB |
After Width: | Height: | Size: 1.8 MiB |
After Width: | Height: | Size: 2.3 MiB |
After Width: | Height: | Size: 1.9 MiB |
50
content/posts/2025/iac-part-2-my-homelab/index.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Infrastructure as Code - Simplifying My Homelab Setup with Portainer GitOps
|
||||
date: "2025-05-03"
|
||||
template: "post"
|
||||
category: "homelab"
|
||||
tags:
|
||||
- "homelab"
|
||||
- "portainer"
|
||||
- "gitops"
|
||||
- "iac"
|
||||
- "docker"
|
||||
- "unraid"
|
||||
- "kubernetes"
|
||||
description: "How I've simplified my homelab by combining the ease of Unraid storage management with Portainer's GitOps feature to implement Infrastructure as Code."
|
||||
socialImage: ./media/portainer_gitops.png
|
||||
---
|
||||
|
||||
After writing my last blog post about using Infrastructure as Code for uptime monitoring with UptimeKuma, I felt inspired to dive deeper into my broader homelab story.
|
||||
|
||||
## From Complexity to Clarity
|
||||
|
||||
When I started building my homelab, it was all about experimenting and having fun. Kubernetes, Ansible, HA clusters spread across multiple nodes... I dove into everything headfirst. The concept of Infrastructure as Code (IaC), something I fell deeply in love with thanks to Kubernetes, quickly became the backbone of my ideal setup.
|
||||
|
||||
Yet, as much as I enjoyed the complexity and elegance Kubernetes brought to my experiments, I soon realized something pretty obvious: when it comes to crucial services like Jellyfin for movies or Home Assistant for managing my smart home, the last thing I needed was an over-engineered, highly available Kubernetes cluster requiring constant babysitting.
|
||||
|
||||
## The Before Times
|
||||
|
||||
It reminded me of simpler times, the "before times", before Docker and Kubernetes even existed. Back then, my homelab consisted of multiple Ubuntu servers, each handling different parts of my setup, along with a single, reliable main server named "Erebus," using Greyhole for redundancy. Even when Docker came along (and later Kubernetes), I adopted them immediately for non-critical, experimental projects, but crucial services always remained comfortably on one stable server. After all, Kubernetes-level scalability was never really necessary for my home media or automation and I could still use all the benefits of Docker.
|
||||
|
||||
## Hades, the Storage Behemoth
|
||||
|
||||
Today, this philosophy still holds true. My critical services run smoothly on a single Unraid server, my trusted main storage server named "Hades." On Hades, it’s a pleasantly chaotic mix of Unraid's Docker plugin, community apps, and various Docker Compose files. Each container has decentralized configuration variables, with updates effortlessly handled by Unraid's auto-update plugin.
|
||||
|
||||
Here's the ironic twist: despite having Kubernetes nodes running non-critical, playful stuff, all my serious storage still ends up on Hades. Honestly, nothing else compares. Hades dwarfs every other node in my network storage-wise, think around 200TB of free space at any given time. Even Kubernetes relies heavily on Hades for its back-end storage.
|
||||
|
||||
## Simplifying with Portainer GitOps
|
||||
|
||||

|
||||
|
||||
Still, this fragmented setup felt manual and messy. I missed the simplicity and clarity of Infrastructure as Code. Thankfully, Portainer's Ops feature came to my rescue, allowing me to blend the massive storage and ease of use from Unraid with structured IaC management. To fully leverage this, I registered for 3 free nodes of Portainer Business Edition, unlocking their powerful GitOps feature.
|
||||
|
||||
Portainer's GitOps polls my private Git repository for updates to Docker Compose files at regular intervals, automatically deploying any changes. It seamlessly integrates with Docker Hub, pulling the latest container images and ensuring my stacks remain updated without manual intervention. This automation keeps my services fresh and secure with minimal effort.
|
||||
|
||||
The UptimeKuma example from my last blog is one such stack I recently migrated to this new, cleaner workflow.
|
||||
|
||||
## The Best of Both Worlds
|
||||
|
||||
Now, I have the best of both worlds—a robust and neatly structured setup embracing Infrastructure as Code without sacrificing ease of use or storage headaches. In other words, I could keep the things I liked best about my Kubernetes (the predictability and IaC parts) and got rid of its worst (multiple nodes, manual updates).  
|
||||
|
||||
On to the next adventure!
|
After Width: | Height: | Size: 1.9 MiB |
BIN
content/posts/2025/iac-part-2-my-homelab/media/sync.png
Normal file
After Width: | Height: | Size: 2.3 MiB |
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Infrastructure as code - Service uptime monitoring
|
||||
date: "2025-01-18"
|
||||
template: "post"
|
||||
category: "homelab"
|
||||
tags:
|
||||
- "uptime"
|
||||
- "kuma"
|
||||
- "iac"
|
||||
- "infrastructure"
|
||||
- "code"
|
||||
- "servers"
|
||||
description: "In this post I demonstrate how I've moved from an old and simple UptimeKuma setup to a setup with AutoKuma so I can register my monitors with 'infrastructure as code'"
|
||||
socialImage: ./media/uptimekuma.png
|
||||
---
|
||||
|
||||
If there’s one thing I’ve always embraced, it’s the philosophy of working smarter, not harder. Infrastructure as Code (IaC) has been one of the cornerstones of my career. a perfect blend of laziness and the pursuit of predictability. From my earliest days experimenting with Docker back in 2014 to leading a platform team at my current company, IaC has proven invaluable for simplifying complexity, ensuring reproducibility, and enabling automation.
|
||||
|
||||
Below you'll find my journey and why I think every engineer should embrace it, eventually walking you through one of the latest automations I set up at home: Automatically creating uptime monitors in UptimeKuma based on Docker labels.
|
||||
|
||||
## The Early Days: Discovering Docker
|
||||
|
||||
Back in 2014, I stumbled upon Docker, and it was a game-changer. At the time, I was frustrated by the manual and error-prone process of setting up environments for my code (especially on other people's machines....). Docker offered a way to reliably recreate these environments with simple, declarative configuration files. Suddenly, I could spin up a development environment in minutes and be confident it would work exactly the same on another machine.
|
||||
|
||||
In 2017, I taught several university classes on Docker, emphasizing the importance of reproducibility. My students learned how to containerize applications and ensure their work could be shared and run anywhere, regardless of underlying infrastructure. Reproducibility wasn’t just a technical advantage; it became a mindset I encouraged in every project.
|
||||
|
||||
And yes.. for those that know me well, I did go overboard in some of the details and even ended up explaining the entire Copy-On-Write (COW) nature of the Docker filesystem in those classes... But ah well, people learned a lot :P.
|
||||
|
||||
## Home Servers: The Personal Lab
|
||||
|
||||
My passion for IaC extended to my home servers. By 2017, nearly everything I ran at home was Docker-based. I created dozens of bash scripts and system services to orchestrate my personal infrastructure. Whether it was media servers, backup systems, or development environments, everything was automated and version-controlled. Even my own computers mostly became IaC based as I figured out that part of the Linux community was saving their setups (and install instructions, usually) in so called "dotfiles". To this day you can still find my setup (and its changes when I switch machine) in my [Dotfiles on Github](https://github.com/Mastermindzh/dotfiles/).
|
||||
|
||||
All in all, my home setup became a sandbox for testing new ideas and tools, many of which eventually found their way into my professional work as well.
|
||||
|
||||
## Scaling IaC in the Workplace
|
||||
|
||||
At my current company, I introduced Docker about eight years ago. It was a gradual process, but within a year, we had our first Kubernetes cluster running. This transition wasn’t just about adopting new tools; it was about embedding the principles of IaC into the organization’s culture.
|
||||
|
||||
Over time, I spearheaded the creation of a dedicated platform team. With an architect/Product Owner and four DevOps engineers, this team took IaC to the next level. They implemented robust CI/CD pipelines, infrastructure monitoring, and scalable deployment patterns. Some of these practices mirrored what I had done at home, while others were tailored to the unique needs of the business. The result was a resilient and predictable infrastructure that supports rapid development and deployment.
|
||||
|
||||
## Setting Up Uptime Monitoring with UptimeKuma
|
||||
|
||||
One of the most satisfying aspects of IaC is the ability to automate even the smallest tasks. Take uptime monitoring, for example. At home and at work, ensuring that services are available is critical. Recently, I’ve been using my old UptimeKuma instance, a self-hosted monitoring tool that’s as powerful as it is user-friendly, a lot more after introducing it to some friends (who started homelabbing) and at work.
|
||||
|
||||
Setting up UptimeKuma is straightforward, you can simply use our old friend Docker:
|
||||
|
||||
`docker run -d --name uptime-kuma -v ./data/uptimekuma:/app/data -p 3001:3001 louislam/uptime-kuma`
|
||||
|
||||
And access the Dashboard by navigating to <http://localhost:3001> to configure your monitors.
|
||||
But that isn't automatic enough for me, I like to put my things in compose files for home usage.
|
||||
|
||||
## Automating uptime monitors with AutoKuma
|
||||
|
||||
[AutoKuma](https://github.com/BigBoot/AutoKuma) allows us to set labels on our Docker containers that will then automatically generate monitors in UptimeKuma.
|
||||
|
||||
One of my containers (UptimeKuma actually) has the following labels attached:
|
||||
|
||||
```yml
|
||||
labels:
|
||||
kuma.monitoring.group.name: "Monitoring"
|
||||
kuma.uptime_kuma.http.parent_name: "monitoring"
|
||||
kuma.uptime_kuma.http.name: "Kuma status monitoring"
|
||||
kuma.uptime_kuma.http.url: "http://${HOST_IP}:3001"
|
||||
```
|
||||
|
||||
This actually does 2 things:
|
||||
|
||||
- creates a group with the _key/id_ `monitoring` and the name `Monitoring`
|
||||
- Adds a monitor with the _key/id_ `uptime_kuma` to UptimeKuma with the type `http`, name `Kuma status monitoring`, and url `http://${HOST_IP}:3001`
|
||||
|
||||
Adding these labels, whilst AutoKuma is running and configured to pick up labels starting with `kuma` is enough for monitors to show up (after restarting the containers).
|
||||
All in all, my `docker-compose.yml` file for both UptimeKuma and AutoKuma now looks like this:
|
||||
|
||||
```yml
|
||||
services:
|
||||
autokuma:
|
||||
image: ghcr.io/bigboot/autokuma:master
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
AUTOKUMA__KUMA__URL: http://${HOST_IP}:3001
|
||||
AUTOKUMA__KUMA__USERNAME: ${KUMA_USERNAME}
|
||||
AUTOKUMA__KUMA__PASSWORD: ${KUMA_PASSWORD}
|
||||
AUTOKUMA__TAG_NAME: AutoKuma
|
||||
AUTOKUMA__TAG_COLOR: "#42C0FB"
|
||||
AUTOKUMA__DEFAULT_SETTINGS: |-
|
||||
docker.docker_container: {{container_name}}
|
||||
http.max_redirects: 10
|
||||
*.max_retries: 3
|
||||
AUTOKUMA__DOCKER__LABEL_PREFIX: kuma
|
||||
AUTOKUMA__MIGRATE: true
|
||||
volumes:
|
||||
- ${APP_DATA}/autokuma:/data
|
||||
- ${DOCKER_SOCKET}:/var/run/docker.sock
|
||||
depends_on:
|
||||
- kuma
|
||||
|
||||
kuma:
|
||||
image: louislam/uptime-kuma:1
|
||||
volumes:
|
||||
- ${APP_DATA}/uptimekuma:/app/data
|
||||
- ${DOCKER_SOCKET}:/var/run/docker.sock
|
||||
ports:
|
||||
- 3001:3001
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
kuma.monitoring.group.name: "Monitoring"
|
||||
kuma.uptime_kuma.http.parent_name: "monitoring"
|
||||
kuma.uptime_kuma.http.name: "Kuma status monitoring"
|
||||
kuma.uptime_kuma.http.url: "http://${HOST_IP}:3001"
|
||||
```
|
||||
|
||||
## Problems... the compose file doesn't work :O
|
||||
|
||||
If you'd try to run the compose file in the previous chapter, even after replacing all the variables, things likely still won't work.
|
||||
This is because AutoKuma relies on the credentials for UptimeKuma which we setup during the initial launch of UptimeKuma.
|
||||
Unfortunately, that account is the only account we can currently set up in UptimeKuma since it doesn't have user management.
|
||||
And yes, that means we **have** to use these same credentials to get AutoKuma to work, which also prohibits us from enabling MFA.
|
||||
|
||||
Anyway, after setting up the kuma account, simply adjust both the `KUMA_USERNAME` and `KUMA_PASSWORD` variables and restart the containers.
|
||||
After doing so we should see the monitor appear in UptimeKuma:
|
||||
|
||||

|
||||
|
||||
## The Future of IaC
|
||||
|
||||
Infrastructure as Code isn’t just a technical approach; it’s a philosophy that prioritizes automation, reproducibility, and simplicity. Whether you’re managing a home lab or a global platform, IaC provides the tools and practices to build resilient systems with minimal effort.
|
||||
|
||||
For me, IaC has been a journey of continuous learning and experimentation. From Docker to Kubernetes, from bash scripts to dedicated platform teams, the principles remain the same: automate everything, document everything, and embrace the predictability that code brings to infrastructure.
|
||||
|
||||
If you haven’t already, give UptimeKuma and AutoKuma a try. Their combination is an excellent example of how IaC can simplify even the most mundane tasks, leaving you more time to focus on what really matters... or just to be a little lazier.
|
After Width: | Height: | Size: 91 KiB |
65
eslint.config.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const mastermindzhConfig = require("@mastermindzh/eslint-config");
|
||||
const typescriptEslint = require("@typescript-eslint/eslint-plugin");
|
||||
const typescriptParser = require("@typescript-eslint/parser");
|
||||
const importPlugin = require("eslint-plugin-import");
|
||||
const reactPlugin = require("eslint-plugin-react");
|
||||
|
||||
module.exports = [
|
||||
// Global ignores (replaces .eslintignore)
|
||||
{
|
||||
ignores: [
|
||||
"dist/**",
|
||||
"build/**",
|
||||
"public/**",
|
||||
".cache/**",
|
||||
"node_modules/**",
|
||||
"*.config.js",
|
||||
"*.config.ts",
|
||||
],
|
||||
},
|
||||
...mastermindzhConfig,
|
||||
{
|
||||
// Only lint TypeScript files (matching your .eslintignore pattern)
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: typescriptParser,
|
||||
parserOptions: {
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
import: importPlugin,
|
||||
react: reactPlugin,
|
||||
},
|
||||
// Your project-specific rules
|
||||
rules: {
|
||||
// Import rules
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
devDependencies: ["**/*.test.ts", "**/*.test.tsx", "**/internal/**/*.ts"],
|
||||
},
|
||||
],
|
||||
// TypeScript rules
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
// Use the correct rule name for quotes
|
||||
quotes: ["error", "double"],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
format: ["camelCase", "UPPER_CASE", "snake_case", "PascalCase"],
|
||||
leadingUnderscore: "allow",
|
||||
selector: "parameter",
|
||||
},
|
||||
],
|
||||
// React rules
|
||||
"react/static-property-placement": "off",
|
||||
"react/prop-types": "off",
|
||||
// Shadow rules
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/no-shadow": "error",
|
||||
},
|
||||
},
|
||||
];
|
@@ -1,18 +1,10 @@
|
||||
import path from "path";
|
||||
|
||||
const templates = Object.freeze({
|
||||
indexTemplate: path.resolve(
|
||||
"./src/templates/IndexTemplate/IndexTemplate.tsx",
|
||||
),
|
||||
notFoundTemplate: path.resolve(
|
||||
"./src/templates/NotFoundTemplate/NotFoundTemplate.tsx",
|
||||
),
|
||||
categoryTemplate: path.resolve(
|
||||
"./src/templates/CategoryTemplate/CategoryTemplate.tsx",
|
||||
),
|
||||
categoriesTemplate: path.resolve(
|
||||
"./src/templates/CategoriesTemplate/CategoriesTemplate.tsx",
|
||||
),
|
||||
indexTemplate: path.resolve("./src/templates/IndexTemplate/IndexTemplate.tsx"),
|
||||
notFoundTemplate: path.resolve("./src/templates/NotFoundTemplate/NotFoundTemplate.tsx"),
|
||||
categoryTemplate: path.resolve("./src/templates/CategoryTemplate/CategoryTemplate.tsx"),
|
||||
categoriesTemplate: path.resolve("./src/templates/CategoriesTemplate/CategoriesTemplate.tsx"),
|
||||
tagTemplate: path.resolve("./src/templates/TagTemplate/TagTemplate.tsx"),
|
||||
tagsTemplate: path.resolve("./src/templates/TagsTemplate/TagsTemplate.tsx"),
|
||||
pageTemplate: path.resolve("./src/templates/PageTemplate/PageTemplate.tsx"),
|
||||
|
@@ -99,7 +99,7 @@ const createPages: GatsbyNode["createPages"] = async ({ graphql, actions }) => {
|
||||
const path = utils.concat(
|
||||
constants.routes.categoryRoute,
|
||||
"/",
|
||||
utils.toKebabCase(category.fieldValue)
|
||||
utils.toKebabCase(category.fieldValue),
|
||||
);
|
||||
|
||||
for (let page = 0; page < total; page += 1) {
|
||||
|
@@ -28,7 +28,7 @@ const onCreateNode: GatsbyNode["onCreateNode"] = ({ node, actions, getNode }) =>
|
||||
|
||||
if (tags) {
|
||||
const value = tags.map((tag) =>
|
||||
utils.concat(constants.routes.tagRoute, "/", utils.toKebabCase(tag), "/")
|
||||
utils.concat(constants.routes.tagRoute, "/", utils.toKebabCase(tag), "/"),
|
||||
);
|
||||
|
||||
createNodeField({ node, name: "tagSlugs", value });
|
||||
@@ -39,7 +39,7 @@ const onCreateNode: GatsbyNode["onCreateNode"] = ({ node, actions, getNode }) =>
|
||||
constants.routes.categoryRoute,
|
||||
"/",
|
||||
utils.toKebabCase(category),
|
||||
"/"
|
||||
"/",
|
||||
);
|
||||
|
||||
createNodeField({ node, name: "categorySlug", value });
|
||||
|
@@ -5,24 +5,12 @@ const gatsby = jest.requireActual("gatsby");
|
||||
export default {
|
||||
...gatsby,
|
||||
graphql: jest.fn(),
|
||||
Link: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
({
|
||||
activeClassName,
|
||||
activeStyle,
|
||||
getProps,
|
||||
innerRef,
|
||||
ref,
|
||||
replace,
|
||||
to,
|
||||
...rest
|
||||
}) =>
|
||||
React.createElement("a", {
|
||||
...rest,
|
||||
href: to,
|
||||
}),
|
||||
),
|
||||
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
|
||||
React.createElement("a", {
|
||||
...rest,
|
||||
href: to,
|
||||
}),
|
||||
),
|
||||
StaticQuery: jest.fn(),
|
||||
useStaticQuery: jest.fn(),
|
||||
};
|
||||
|
@@ -22,10 +22,7 @@ const jestConfig: Config.InitialOptions = {
|
||||
"identity-obj-proxy",
|
||||
"^gatsby-page-utils/(.*)$": "gatsby-page-utils/$1",
|
||||
"^gatsby-core-utils/(.*)$": "gatsby-core-utils/dist/$1",
|
||||
"^gatsby-plugin-utils/(.*)$": [
|
||||
"gatsby-plugin-utils/dist/$1",
|
||||
"gatsby-plugin-utils/$1",
|
||||
],
|
||||
"^gatsby-plugin-utils/(.*)$": ["gatsby-plugin-utils/dist/$1", "gatsby-plugin-utils/$1"],
|
||||
},
|
||||
transform: { "^.+\\.[jt]sx?$": ["@swc/jest", swc] },
|
||||
setupFiles: ["<rootDir>/internal/testing/jest-setup.ts"],
|
||||
|
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"MD033": false
|
||||
"MD033": false
|
||||
}
|
||||
|
29950
package-lock.json
generated
187
package.json
@@ -18,20 +18,21 @@
|
||||
"author": "Rick van Lieshout <info@rickvanlieshout.com>",
|
||||
"scripts": {
|
||||
"build": "npm run clean && gatsby build",
|
||||
"commit": "git-cz",
|
||||
"clean": "rimraf .cache public",
|
||||
"commit": "git-cz",
|
||||
"format": "npm run format:ts && npm run format:scss",
|
||||
"format:scss": "stylelint \"src/**/*.scss\" --fix",
|
||||
"format:ts": "eslint \"src\" --ext .tsx,.ts --fix && prettier --write .",
|
||||
"format:ts": "eslint \"src/**/*.{ts,tsx}\" --fix && prettier --write .",
|
||||
"install:legacy": "npm install --legacy-peer-deps",
|
||||
"lint": "npm run lint:ts && npm run lint:scss",
|
||||
"lint:scss": "stylelint \"src/**/*.scss\"",
|
||||
"lint:staged": "lint-staged",
|
||||
"lint:ts": "eslint \"src\" --ext .tsx,.ts && prettier --check .",
|
||||
"lint:ts": "eslint \"src/**/*.{ts,tsx}\" && prettier --check .",
|
||||
"prepare": "husky install",
|
||||
"release": "standard-version",
|
||||
"release:major": "standard-version --release-as major",
|
||||
"release:minor": "standard-version --release-as minor",
|
||||
"release:patch": "standard-version --release-as patch",
|
||||
"release:major": "standard-version --release-as major",
|
||||
"reset-snapshots": "find -type f -name '*.snap*' -delete && npm run test",
|
||||
"semantic-release": "semantic-release",
|
||||
"serve": "gatsby serve",
|
||||
@@ -48,114 +49,112 @@
|
||||
"npm run format:scss"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "@commitlint/cz-commitlint"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.0.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.0.0",
|
||||
"@fortawesome/react-fontawesome": "^3.0.0",
|
||||
"classnames": "^2.5.1",
|
||||
"disqus-react": "^1.1.5",
|
||||
"gatsby": "^5.14.0",
|
||||
"gatsby-link": "^5.12.1",
|
||||
"gatsby-plugin-catch-links": "^5.13.1",
|
||||
"gatsby-plugin-feed": "^5.13.1",
|
||||
"gatsby-plugin-google-gtag": "^5.13.1",
|
||||
"gatsby-plugin-image": "^3.13.0",
|
||||
"gatsby-plugin-manifest": "^5.13.1",
|
||||
"disqus-react": "^1.1.7",
|
||||
"gatsby": "^5.14.5",
|
||||
"gatsby-link": "^5.14.1",
|
||||
"gatsby-plugin-catch-links": "^5.14.0",
|
||||
"gatsby-plugin-feed": "^5.14.0",
|
||||
"gatsby-plugin-google-gtag": "^5.14.0",
|
||||
"gatsby-plugin-image": "^3.14.0",
|
||||
"gatsby-plugin-manifest": "^5.14.0",
|
||||
"gatsby-plugin-optimize-svgs": "^1.0.5",
|
||||
"gatsby-plugin-react-helmet": "^6.13.1",
|
||||
"gatsby-plugin-react-helmet": "^6.14.0",
|
||||
"gatsby-plugin-remove-serviceworker": "^1.0.0",
|
||||
"gatsby-plugin-robots-txt": "^1.8.0",
|
||||
"gatsby-plugin-sass": "^6.13.1",
|
||||
"gatsby-plugin-sharp": "^5.13.1",
|
||||
"gatsby-plugin-sitemap": "^6.13.1",
|
||||
"gatsby-remark-autolink-headers": "^6.13.1",
|
||||
"gatsby-remark-copy-linked-files": "^6.13.2",
|
||||
"gatsby-plugin-sass": "^6.14.0",
|
||||
"gatsby-plugin-sharp": "^5.14.0",
|
||||
"gatsby-plugin-sitemap": "^6.14.0",
|
||||
"gatsby-remark-autolink-headers": "^6.14.0",
|
||||
"gatsby-remark-copy-linked-files": "^6.14.0",
|
||||
"gatsby-remark-external-links": "0.0.4",
|
||||
"gatsby-remark-images": "^7.13.2",
|
||||
"gatsby-remark-images": "^7.14.0",
|
||||
"gatsby-remark-images-medium-zoom": "^1.7.0",
|
||||
"gatsby-remark-prismjs": "^7.13.2",
|
||||
"gatsby-remark-responsive-iframe": "^6.13.2",
|
||||
"gatsby-remark-smartypants": "^6.13.1",
|
||||
"gatsby-source-filesystem": "^5.13.1",
|
||||
"gatsby-transformer-remark": "^6.13.1",
|
||||
"gatsby-transformer-sharp": "^5.13.1",
|
||||
"prismjs": "^1.29.0",
|
||||
"gatsby-remark-prismjs": "^7.14.0",
|
||||
"gatsby-remark-responsive-iframe": "^6.14.0",
|
||||
"gatsby-remark-smartypants": "^6.14.0",
|
||||
"gatsby-source-filesystem": "^5.14.0",
|
||||
"gatsby-transformer-remark": "^6.14.0",
|
||||
"gatsby-transformer-sharp": "^5.14.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "^18.3.1",
|
||||
"react-cookie-consent": "^8.0.1",
|
||||
"react-cookie-consent": "^9.0.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"reading-time": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/config-conventional": "^17.6.6",
|
||||
"@commitlint/cz-commitlint": "^17.5.0",
|
||||
"@jest/globals": "^29.5.0",
|
||||
"@mastermindzh/eslint-config": "^1.0.2",
|
||||
"@mastermindzh/prettier-config": "^1.0.0",
|
||||
"@semantic-release/exec": "6.0.3",
|
||||
"@semantic-release/git": "10.0.1",
|
||||
"@swc/core": "^1.3.67",
|
||||
"@swc/jest": "^0.2.26",
|
||||
"@types/gatsby-transformer-remark": "^2.9.1",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"@types/react-toggle": "^4.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"browserslist": "^4.21.9",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@commitlint/cz-commitlint": "^19.8.1",
|
||||
"@eslint/compat": "^1.3.1",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.31.0",
|
||||
"@jest/globals": "^30.0.4",
|
||||
"@mastermindzh/eslint-config": "^3.1.0",
|
||||
"@mastermindzh/prettier-config": "^1.1.0",
|
||||
"@swc/core": "^1.12.14",
|
||||
"@swc/jest": "^0.2.39",
|
||||
"@types/gatsby-transformer-remark": "^2.9.4",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-test-renderer": "^18.3.1",
|
||||
"@types/react-toggle": "^4.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.37.0",
|
||||
"@typescript-eslint/parser": "^8.37.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"browserslist": "^4.25.1",
|
||||
"codecov": "^3.8.3",
|
||||
"commitizen": "^4.3.0",
|
||||
"commitlint": "^17.6.6",
|
||||
"concurrently": "^8.2.0",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"eslint-plugin-flowtype": "^8.0.3",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jest": "^27.2.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"husky": "^8.0.3",
|
||||
"commitizen": "^4.3.1",
|
||||
"commitlint": "^19.8.1",
|
||||
"concurrently": "^9.2.0",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jest": "^29.0.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-prettier": "^5.5.1",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"globals": "^16.3.0",
|
||||
"husky": "^9.0.0",
|
||||
"identity-obj-proxy": "3.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"jest-cli": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jest": "^30.0.4",
|
||||
"jest-cli": "^30.0.4",
|
||||
"jest-environment-jsdom": "^30.0.4",
|
||||
"jest-svg-transformer": "^1.0.0",
|
||||
"lint-staged": "^13.2.3",
|
||||
"lost": "9.0.1",
|
||||
"markdownlint": "^0.29.0",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-packagejson": "^2.4.3",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"rimraf": "^4.4.1",
|
||||
"sass": "^1.63.6",
|
||||
"lint-staged": "^16.1.2",
|
||||
"lost": "9.0.2",
|
||||
"markdownlint": "^0.38.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-packagejson": "^2.5.18",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.89.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"standard-version": "^9.5.0",
|
||||
"stylelint": "^15.9.0",
|
||||
"stylelint-config-recommended-scss": "^9.0.1",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"stylelint-scss": "^4.6.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6",
|
||||
"unist-util-find": "1.0.2"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "@commitlint/cz-commitlint"
|
||||
}
|
||||
"stylelint": "^16.21.1",
|
||||
"stylelint-config-recommended-scss": "^16.0.0",
|
||||
"stylelint-order": "^7.0.0",
|
||||
"stylelint-scss": "^6.12.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"unist-util-find": "3.0.0"
|
||||
}
|
||||
}
|
||||
|
11
renovate.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"ignoreDeps": [
|
||||
"react",
|
||||
"react-dom",
|
||||
"@types/react",
|
||||
"@types/react-dom",
|
||||
"@types/react-test-renderer",
|
||||
"react-test-renderer"
|
||||
]
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@import "base/generic";
|
||||
@use "base/generic";
|
||||
|
||||
.showInPrintView {
|
||||
display: none;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@import "mixins/breakpoints";
|
||||
@import "mixins/line-height";
|
||||
@import "mixins/padding";
|
||||
@import "mixins/margin";
|
||||
@forward "mixins/breakpoints";
|
||||
@forward "mixins/line-height";
|
||||
@forward "mixins/padding";
|
||||
@forward "mixins/margin";
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../variables";
|
||||
@import "../mixins";
|
||||
@use "../variables" as *;
|
||||
@use "../mixins" as *;
|
||||
|
||||
html {
|
||||
font-size: $typographic-root-font-size;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@import "variables";
|
||||
@import "mixins";
|
||||
@import "base";
|
||||
@use "variables";
|
||||
@use "mixins";
|
||||
@use "base";
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin breakpoint-xs {
|
||||
@content;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin line-height($number) {
|
||||
line-height: #{$number * $typographic-leading + "px"};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin margin-auto($number: 0) {
|
||||
margin: #{$number * $typographic-leading + "px"} auto;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@import "../variables";
|
||||
@use "../variables" as *;
|
||||
|
||||
@mixin padding-left($number) {
|
||||
padding-left: #{$number * $typographic-leading + "px"};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
|
||||
$shadow: 0px -1px 5px rgba(50, 50, 50, 0.75);
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.feed {
|
||||
.item {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Feed renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -3,7 +3,6 @@ import React from "react";
|
||||
import * as styles from "./Icon.module.scss";
|
||||
import { ICONS } from "@/constants";
|
||||
|
||||
|
||||
interface Props {
|
||||
name: keyof typeof ICONS;
|
||||
icon: {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Icon renders correctly 1`] = `
|
||||
<svg
|
||||
|
@@ -1,11 +1,7 @@
|
||||
import React, { FC } from "react";
|
||||
|
||||
import { graphql, StaticQuery } from "gatsby";
|
||||
import {
|
||||
GatsbyImage,
|
||||
GatsbyImageProps,
|
||||
IGatsbyImageData,
|
||||
} from "gatsby-plugin-image";
|
||||
import { GatsbyImage, GatsbyImageProps, IGatsbyImageData } from "gatsby-plugin-image";
|
||||
import { FileSystemNode } from "gatsby-source-filesystem";
|
||||
|
||||
interface Props extends Omit<GatsbyImageProps, "image"> {
|
||||
@@ -28,9 +24,7 @@ const Image: FC<Props> = ({ path, ...rest }: Props) => (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query {
|
||||
images: allFile(
|
||||
filter: { ext: { regex: "/png|jpg|jpeg|webp|tif|tiff/" } }
|
||||
) {
|
||||
images: allFile(filter: { ext: { regex: "/png|jpg|jpeg|webp|tif|tiff/" } }) {
|
||||
edges {
|
||||
node {
|
||||
absolutePath
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.layout {
|
||||
lost-center: $layout-width;
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import Helmet from "react-helmet";
|
||||
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
import { CookieBar } from "../Cookiebar/CookieBar";
|
||||
import * as styles from "./Layout.module.scss";
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@@ -20,7 +20,6 @@ const Layout: React.FC<Props> = ({
|
||||
description,
|
||||
socialImage = "",
|
||||
noIndex = false,
|
||||
slug,
|
||||
}: Props) => {
|
||||
const { author, url } = useSiteMetadata();
|
||||
const metaImage = socialImage || author.photo;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Layout renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.page {
|
||||
@include margin-bottom(2);
|
||||
|
@@ -11,9 +11,7 @@ describe("Page", () => {
|
||||
title: mocks.markdownRemark.frontmatter.title,
|
||||
};
|
||||
|
||||
const tree = renderer
|
||||
.create(<Page {...props}>{props.children}</Page>)
|
||||
.toJSON();
|
||||
const tree = renderer.create(<Page {...props}>{props.children}</Page>).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
|
||||
import type { Nullable } from "@/types";
|
||||
import { Helmet } from "react-helmet";
|
||||
import * as styles from "./Page.module.scss";
|
||||
import type { Nullable } from "@/types";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Page renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
@use "sass:color";
|
||||
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { PAGINATION } from "@/constants";
|
||||
import classNames from "classnames";
|
||||
import { Link } from "gatsby";
|
||||
import React from "react";
|
||||
import * as styles from "./Pagination.module.scss";
|
||||
import { PAGINATION } from "@/constants";
|
||||
|
||||
type Props = {
|
||||
prevPagePath: string;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Pagination renders correctly 1`] = `
|
||||
<div
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.author {
|
||||
border-top: 1px solid $color-gray-border;
|
||||
|
@@ -11,9 +11,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("Author", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
import React from "react";
|
||||
import * as styles from "./Author.module.scss";
|
||||
import { useSiteMetadata } from "@/hooks";
|
||||
|
||||
const Author = () => {
|
||||
const { author } = useSiteMetadata();
|
||||
@@ -13,6 +13,7 @@ const Author = () => {
|
||||
<strong>{author.name}</strong>
|
||||
</a>
|
||||
{typeof window !== "undefined" ? (
|
||||
// eslint-disable-next-line no-undef
|
||||
<span className="showInPrintView"> {window ? `@ ${window.location}` : ""}</span>
|
||||
) : (
|
||||
""
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Author renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -11,9 +11,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("Comments", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
@@ -1,3 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Comments renders correctly 1`] = `null`;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.content {
|
||||
@include margin-auto();
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Content } from "@/components/Post/Content";
|
||||
import * as mocks from "@/mocks";
|
||||
import { StaticQuery, useStaticQuery } from "gatsby";
|
||||
import React from "react";
|
||||
import renderer from "react-test-renderer";
|
||||
import * as mocks from "@/mocks";
|
||||
import { Content } from "@/components/Post/Content";
|
||||
|
||||
const mockedStaticQuery = StaticQuery as jest.Mock;
|
||||
const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Content renders correctly 1`] = `
|
||||
[
|
||||
@@ -69,19 +69,15 @@ exports[`Content renders correctly 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-moon "
|
||||
className="svg-inline--fa fa-moon"
|
||||
data-icon="moon"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={{}}
|
||||
viewBox="0 0 384 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
||||
d="M256 0C114.6 0 0 114.6 0 256S114.6 512 256 512c68.8 0 131.3-27.2 177.3-71.4 7.3-7 9.4-17.9 5.3-27.1s-13.7-14.9-23.8-14.1c-4.9 .4-9.8 .6-14.8 .6-101.6 0-184-82.4-184-184 0-72.1 41.5-134.6 102.1-164.8 9.1-4.5 14.3-14.3 13.1-24.4S322.6 8.5 312.7 6.3C294.4 2.2 275.4 0 256 0z"
|
||||
fill="currentColor"
|
||||
style={{}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -102,19 +98,15 @@ exports[`Content renders correctly 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-sun "
|
||||
className="svg-inline--fa fa-sun"
|
||||
data-icon="sun"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={{}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 576 512"
|
||||
>
|
||||
<path
|
||||
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
||||
d="M178.2-10.1c7.4-3.1 15.8-2.2 22.5 2.2l87.8 58.2 87.8-58.2c6.7-4.4 15.1-5.2 22.5-2.2S411.4-.5 413 7.3l20.9 103.2 103.2 20.9c7.8 1.6 14.4 7 17.4 14.3s2.2 15.8-2.2 22.5l-58.2 87.8 58.2 87.8c4.4 6.7 5.2 15.1 2.2 22.5s-9.6 12.8-17.4 14.3L433.8 401.4 413 504.7c-1.6 7.8-7 14.4-14.3 17.4s-15.8 2.2-22.5-2.2l-87.8-58.2-87.8 58.2c-6.7 4.4-15.1 5.2-22.5 2.2s-12.8-9.6-14.3-17.4L143 401.4 39.7 380.5c-7.8-1.6-14.4-7-17.4-14.3s-2.2-15.8 2.2-22.5L82.7 256 24.5 168.2c-4.4-6.7-5.2-15.1-2.2-22.5s9.6-12.8 17.4-14.3L143 110.6 163.9 7.3c1.6-7.8 7-14.4 14.3-17.4zM207.6 256a80.4 80.4 0 1 1 160.8 0 80.4 80.4 0 1 1 -160.8 0zm208.8 0a128.4 128.4 0 1 0 -256.8 0 128.4 128.4 0 1 0 256.8 0z"
|
||||
fill="currentColor"
|
||||
style={{}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.meta {
|
||||
.date {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Meta renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.post {
|
||||
|
||||
|
@@ -11,9 +11,7 @@ const mockedUseStaticQuery = useStaticQuery as jest.Mock;
|
||||
|
||||
describe("Post", () => {
|
||||
beforeEach(() => {
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) =>
|
||||
render(mocks.siteMetadata),
|
||||
);
|
||||
mockedStaticQuery.mockImplementationOnce(({ render }) => render(mocks.siteMetadata));
|
||||
mockedUseStaticQuery.mockReturnValue(mocks.siteMetadata);
|
||||
});
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import type { Node } from "@/types";
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useSiteMetadata } from "../../hooks";
|
||||
@@ -8,6 +7,7 @@ import { Content } from "./Content";
|
||||
import { Meta } from "./Meta";
|
||||
import * as styles from "./Post.module.scss";
|
||||
import { Tags } from "./Tags";
|
||||
import type { Node } from "@/types";
|
||||
|
||||
interface Props {
|
||||
post: Node;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.tags {
|
||||
@include margin-bottom(0.5);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Tags renders correctly 1`] = `
|
||||
<div
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Post renders correctly 1`] = `
|
||||
<div>
|
||||
@@ -70,19 +70,15 @@ exports[`Post renders correctly 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-moon "
|
||||
className="svg-inline--fa fa-moon"
|
||||
data-icon="moon"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={{}}
|
||||
viewBox="0 0 384 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
>
|
||||
<path
|
||||
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
||||
d="M256 0C114.6 0 0 114.6 0 256S114.6 512 256 512c68.8 0 131.3-27.2 177.3-71.4 7.3-7 9.4-17.9 5.3-27.1s-13.7-14.9-23.8-14.1c-4.9 .4-9.8 .6-14.8 .6-101.6 0-184-82.4-184-184 0-72.1 41.5-134.6 102.1-164.8 9.1-4.5 14.3-14.3 13.1-24.4S322.6 8.5 312.7 6.3C294.4 2.2 275.4 0 256 0z"
|
||||
fill="currentColor"
|
||||
style={{}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -103,19 +99,15 @@ exports[`Post renders correctly 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-sun "
|
||||
className="svg-inline--fa fa-sun"
|
||||
data-icon="sun"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={{}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 576 512"
|
||||
>
|
||||
<path
|
||||
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
||||
d="M178.2-10.1c7.4-3.1 15.8-2.2 22.5 2.2l87.8 58.2 87.8-58.2c6.7-4.4 15.1-5.2 22.5-2.2S411.4-.5 413 7.3l20.9 103.2 103.2 20.9c7.8 1.6 14.4 7 17.4 14.3s2.2 15.8-2.2 22.5l-58.2 87.8 58.2 87.8c4.4 6.7 5.2 15.1 2.2 22.5s-9.6 12.8-17.4 14.3L433.8 401.4 413 504.7c-1.6 7.8-7 14.4-14.3 17.4s-15.8 2.2-22.5-2.2l-87.8-58.2-87.8 58.2c-6.7 4.4-15.1 5.2-22.5 2.2s-12.8-9.6-14.3-17.4L143 401.4 39.7 380.5c-7.8-1.6-14.4-7-17.4-14.3s-2.2-15.8 2.2-22.5L82.7 256 24.5 168.2c-4.4-6.7-5.2-15.1-2.2-22.5s9.6-12.8 17.4-14.3L143 110.6 163.9 7.3c1.6-7.8 7-14.4 14.3-17.4zM207.6 256a80.4 80.4 0 1 1 160.8 0 80.4 80.4 0 1 1 -160.8 0zm208.8 0a128.4 128.4 0 1 0 -256.8 0 128.4 128.4 0 1 0 256.8 0z"
|
||||
fill="currentColor"
|
||||
style={{}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.header {
|
||||
align-items: baseline;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { Image } from "@/components/Image";
|
||||
import { Link, navigate } from "gatsby";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { ThemeSwitcher } from "../ThemeSwitcher/ThemeSwitcher";
|
||||
import * as styles from "./PostHeader.module.scss";
|
||||
import { Image } from "@/components/Image";
|
||||
|
||||
type Props = { author: { name: string; photo: string } };
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.author {
|
||||
.photo {
|
||||
|
@@ -5,7 +5,6 @@ import { Link } from "gatsby";
|
||||
import * as styles from "./Author.module.scss";
|
||||
import { Image } from "@/components/Image";
|
||||
|
||||
|
||||
type Props = {
|
||||
author: {
|
||||
name: string;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Author renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.contacts {
|
||||
@include margin-bottom(1);
|
||||
|
@@ -25,7 +25,7 @@ const Contacts: React.FC<Props> = ({ contacts }: Props) => (
|
||||
<Icon name={name} icon={getIcon(name)} />
|
||||
</a>
|
||||
</li>
|
||||
) : null
|
||||
) : null,
|
||||
)}
|
||||
<li className={styles.item} key="keys">
|
||||
<a className={styles.link} href="/keys.json" rel="noopener noreferrer" target="_blank">
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Contacts renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
@use "sass:color";
|
||||
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.copyright {
|
||||
color: $color-gray;
|
||||
|
@@ -6,8 +6,6 @@ type Props = {
|
||||
copyright: string;
|
||||
};
|
||||
|
||||
const Copyright = ({ copyright }: Props) => (
|
||||
<div className={styles.copyright}>{copyright}</div>
|
||||
);
|
||||
const Copyright = ({ copyright }: Props) => <div className={styles.copyright}>{copyright}</div>;
|
||||
|
||||
export default Copyright;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Copyright renders correctly 1`] = `
|
||||
<div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../../assets/scss/variables";
|
||||
@import "../../../assets/scss/mixins";
|
||||
@use "../../../assets/scss/variables" as *;
|
||||
@use "../../../assets/scss/mixins" as *;
|
||||
|
||||
.menu {
|
||||
.list {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`Menu renders correctly 1`] = `
|
||||
<nav
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import "../../assets/scss/variables";
|
||||
@import "../../assets/scss/mixins";
|
||||
@use "../../assets/scss/variables" as *;
|
||||
@use "../../assets/scss/mixins" as *;
|
||||
|
||||
.sidebar {
|
||||
width: 100%;
|
||||
|