Compare commits

..

29 Commits

Author SHA1 Message Date
a8a64031bb Removing post
All checks were successful
Build and Deploy docker container / build (push) Successful in 5m16s
2025-04-15 22:17:11 -05:00
58b12534bb New blog post
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m19s
2025-02-07 00:15:58 -06:00
e1a3d59894 Adjusted umami host url
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m34s
2025-01-30 12:22:37 -06:00
6ba9b29daf changed the csp
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m6s
2025-01-28 18:21:52 -06:00
a4195f1ee5 updated linting issue
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m5s
2025-01-28 17:58:32 -06:00
ee886d4d13 updated umami
Some checks failed
Build and Deploy docker container / build (push) Has been cancelled
2025-01-28 17:57:51 -06:00
bbc1567ea4 updated headers to capitilize the letters
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m0s
2025-01-28 14:34:28 -06:00
418de925c4 added resume to navlinks
Some checks failed
Build and Deploy docker container / build (push) Has been cancelled
2025-01-28 14:32:15 -06:00
676b0c4ac1 hide nav link
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m9s
2025-01-28 04:47:12 -06:00
4bf423525a updated header and tagline
All checks were successful
Build and Deploy docker container / build (push) Successful in 5m4s
2025-01-28 04:41:15 -06:00
6dcfad7f0d qbitmaid
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m54s
2025-01-28 00:22:40 -06:00
78ac5b0423 publish qbitmaid 2025-01-28 00:22:19 -06:00
79b1b2db40 updated build.yml
All checks were successful
Build and Deploy docker container / build (push) Successful in 2m52s
2025-01-25 16:16:05 -06:00
30fdb4a3b9 test
All checks were successful
Build and Deploy docker container / build (push) Successful in 4m31s
2025-01-25 12:16:06 -06:00
292439fe68 test act runner 2025-01-25 12:09:18 -06:00
fafc6ed859 test build.yml 2025-01-25 12:04:25 -06:00
a79eac496a Merge pull request 'updated build.yml' (#2) from update-readme into main
Reviewed-on: #2
2025-01-25 12:01:04 -06:00
e06089e6a6 updated build.yml 2025-01-25 12:00:29 -06:00
1debc3e50c Changed twitter card 2025-01-25 11:38:28 -06:00
34d1ec5006 Added react-doc-viewer 2025-01-25 11:28:33 -06:00
a1a3e21266 merging latest changes 2025-01-25 10:57:53 -06:00
6bfe06f65c Updated site icons and added resume to about 2025-01-25 10:57:16 -06:00
30e26313b0 Finished cygnus blog
Some checks failed
Build and Deploy docker container / build (push) Failing after 5m38s
2024-11-26 01:02:24 -06:00
b806e8af0e updated cache
All checks were successful
Build and Deploy docker container / build (push) Successful in 3m38s
2024-11-20 23:20:04 -06:00
58798646df updated build
All checks were successful
Build and Deploy docker container / build (push) Successful in 2m10s
2024-11-19 14:57:11 -06:00
43e4aed2db test new build
Some checks failed
Build and Deploy docker container / build (push) Failing after 1m50s
2024-11-19 14:50:18 -06:00
1230e8d9f3 test of standalone build
All checks were successful
Build and Deploy docker container / build (push) Successful in 3m41s
2024-11-19 14:21:02 -06:00
99611462a3 Merge branch 'main' of https://git.jonb.io/jblu/jonbio
All checks were successful
Build and Deploy docker container / build (push) Successful in 2m14s
2024-11-15 22:49:04 -06:00
15426ed4a5 yarn cache update 2024-11-15 22:48:01 -06:00
51 changed files with 19220 additions and 1991 deletions

View File

@ -1,2 +0,0 @@
node_modules
.eslintrc.js

View File

@ -1,42 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
env: {
browser: true,
amd: true,
node: true,
es6: true,
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:jsx-a11y/recommended',
'plugin:prettier/recommended',
'next',
'next/core-web-vitals',
],
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
rules: {
'prettier/prettier': 'error',
'react/react-in-jsx-scope': 'off',
'jsx-a11y/anchor-is-valid': [
'error',
{
components: ['Link'],
specialLink: ['hrefLeft', 'hrefRight'],
aspects: ['invalidHref', 'preferButton'],
},
],
'react/prop-types': 0,
'@typescript-eslint/no-unused-vars': 0,
'react/no-unescaped-entities': 0,
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
},
}

View File

@ -1,21 +1,14 @@
name: Build and Deploy docker container
'on':
on:
push:
branches: main
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Send message job is starting
uses: cstuder/apprise-ga@master
with:
title: 'jonb.io site deployment'
message: >-
Started build for:
Commit by {{ head_commit.author.name }}: {{ head_commit.message |
truncate(128) }} ({{ head_commit.id[0:7] }})
env:
APPRISE_URL: '${{ secrets.APPRISE_URL }}'
- name: Checkout main
uses: actions/checkout@v4
- name: Install Yarn
@ -34,12 +27,14 @@ jobs:
with:
path: |
${{ steps.yarn-cache-dir-path.outputs.dir }}
${{ github.workspace }}/.next/cache
${{ github.workspace }}/.next/
node_modules/
${{ github.workspace }}/out/
key: '${{ runner.os }}-yarn-${{ hashFiles(''**/yarn.lock'') }}'
restore-keys: |
${{ runner.os }}-yarn-
- name: Update node modules
run: yarn
run: yarn install --immutable
- name: Lint
run: yarn lint
- name: Build app and export to ./out
@ -61,20 +56,10 @@ jobs:
cache-from: 'type=registry,ref=git.jonb.io/jblu/jonbio:buildcache'
cache-to: >-
type=registry,image-manifest=true,oci-mediatypes=true,ref=git.jonb.io/jblu/jonbio:buildcache,mode=max
- name: Deploy qpp
- name: Deploy app
uses: fjogeleit/http-request-action@v1
with:
url: 'http://192.168.4.11:7777/v1/update'
url: 'http://10.5.0.11:7777/v1/update'
method: GET
bearerToken: '${{ secrets.DEPLOYTOKEN }}'
timeout: 60000
- name: Send message app has deployed
uses: cstuder/apprise-ga@master
with:
title: 'jonb.io site deployment'
message: >-
Deployed {{head_commit.message}}
Commit by {{ head_commit.author.name }}: {{ head_commit.message |
truncate(128) }} ({{ head_commit.id[0:7] }})
env:
APPRISE_URL: '${{ secrets.APPRISE_URL }}'
timeout: 60000

3
.vscode/ltex.dictionary.en-US.txt vendored Normal file
View File

@ -0,0 +1,3 @@
Webroot
Xeon
Lar

1
.vscode/ltex.disabledRules.en-US.txt vendored Normal file
View File

@ -0,0 +1 @@
PRP_RB_NO_VB

View File

@ -0,0 +1,2 @@
{"rule":"MORFOLOGIK_RULE_EN_US","sentence":"^\\Q<TOCInline toc={props.toc} asDisclosure toHeading={3} />\\E$"}
{"rule":"MORFOLOGIK_RULE_EN_US","sentence":"^\\QRestart after restart: Nada.\\E$"}

View File

@ -11,4 +11,4 @@ ENV HOSTNAME="0.0.0.0"
EXPOSE 3000
CMD ["npx", "serve", "out"]
CMD ["npx", "-y", "serve", "out"]

View File

@ -1,6 +1,8 @@
![tailwind-nextjs-banner](/public/static/images/twitter-card.png)
# Tailwind Nextjs Starter Blog
# Joyful Fixations
> This template has been adapted for my needs. Please view the upstream if you wish to create a similar site.
[![GitHub Repo stars](https://img.shields.io/github/stars/timlrx/tailwind-nextjs-starter-blog?style=social)](https://GitHub.com/timlrx/tailwind-nextjs-starter-blog/stargazers/)
[![GitHub forks](https://img.shields.io/github/forks/timlrx/tailwind-nextjs-starter-blog?style=social)](https://GitHub.com/timlrx/tailwind-nextjs-starter-blog/network/)

View File

@ -67,29 +67,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
className={`${space_grotesk.variable} scroll-smooth`}
suppressHydrationWarning
>
<link
rel="apple-touch-icon"
sizes="76x76"
href={`${basePath}/static/favicons/apple-touch-icon.png`}
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href={`${basePath}/static/favicons/favicon-32x32.png`}
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href={`${basePath}/static/favicons/favicon-16x16.png`}
/>
<link rel="manifest" href={`${basePath}/static/favicons/site.webmanifest`} />
<link
rel="mask-icon"
href={`${basePath}/static/favicons/safari-pinned-tab.svg`}
color="#5bbad5"
/>
<link rel="icon" type="image/png" href="/static/favicons/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/static/favicons/favicon.svg" />
<link rel="shortcut icon" href="/static/favicons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png" />
<link rel="manifest" href="/static/favicons/site.webmanifest" />
<meta name="msapplication-TileColor" content="#000000" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000" />

View File

@ -1 +1 @@
{"art":1,"store":1,"melis-sweetsimple":1,"visual-studio-code":1,"guide":3,"tailwind-nextjs-starter-blog":1,"github":1,"python":1,"projects":1,"code":1,"cygnus":1,"self-hosted":1,"server":1,"next-js":1}
{"tag1":1,"tag2":1,"tag3":1,"art":1,"store":1,"melis-sweetsimple":1,"visual-studio-code":1,"guide":4,"tailwind-nextjs-starter-blog":1,"disney":1,"github":1,"python":1,"projects":1,"code":1,"docker":1,"unraid":1,"cygnus":1,"self-hosted":1,"server":1,"next-js":1}

View File

@ -17,6 +17,7 @@ export default function Footer() {
<SocialIcon kind="instagram" href={siteMetadata.instagram} size={6} />
<SocialIcon kind="threads" href={siteMetadata.threads} size={6} />
<SocialIcon kind="medium" href={siteMetadata.medium} size={6} />
<SocialIcon kind="resume" href={siteMetadata.resume} size={6} />
</div>
<div className="mb-2 flex space-x-2 text-sm text-gray-500 dark:text-gray-400">
<div>{siteMetadata.author}</div>
@ -26,7 +27,7 @@ export default function Footer() {
<Link href="/">{siteMetadata.title}</Link>
</div>
<div className="mb-8 text-sm text-gray-500 dark:text-gray-400">
<Link href="https://github.com/timlrx/tailwind-nextjs-starter-blog">Theme</Link>
<Link href="https://git.jonb.io/jblu/jonbio">Theme</Link>
</div>
</div>
</footer>

View File

@ -29,7 +29,7 @@ const Header = () => {
</div>
</Link>
<div className="flex items-center space-x-4 leading-5 sm:space-x-6">
<div className="no-scrollbar hidden max-w-40 items-center space-x-4 overflow-x-auto sm:flex sm:space-x-6 md:max-w-72 lg:max-w-96">
<div className="no-scrollbar hidden max-w-40 items-center space-x-4 overflow-x-scroll sm:flex sm:space-x-3 md:max-w-72 lg:max-w-96">
{headerNavLinks
.filter((link) => link.href !== '/')
.map((link) => (

View File

@ -102,3 +102,12 @@ export function Medium(svgProps: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function Resume(svgProps: SVGProps<SVGSVGElement>) {
return (
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...svgProps}>
<title>Resume</title>
<path d="M14.727 6.727H14V0H4.91c-.905 0-1.637.732-1.637 1.636v20.728c0 .904.732 1.636 1.636 1.636h14.182c.904 0 1.636-.732 1.636-1.636V6.727h-6zm-.545 10.455H7.09v-1.364h7.09v1.364zm2.727-3.273H7.091v-1.364h9.818v1.364zm0-3.273H7.091V9.273h9.818v1.363zM14.727 6h6l-6-6v6z" />
</svg>
)
}

View File

@ -10,6 +10,7 @@ import {
Threads,
Instagram,
Medium,
Resume,
} from './icons'
const components = {
@ -24,6 +25,7 @@ const components = {
threads: Threads,
instagram: Instagram,
medium: Medium,
resume: Resume,
}
type SocialIconProps = {

View File

@ -140,6 +140,7 @@ export const Authors = defineDocumentType(() => ({
linkedin: { type: 'string' },
github: { type: 'string' },
layout: { type: 'string' },
resume: { type: 'string' },
},
computedFields,
}))

View File

@ -2,15 +2,16 @@
name: Jonathan Branan
avatar: https://s3.jonb.io/cdn/author/2.JPG
occupation: Software Engineer
company: Fortra
email: jonathan.branan@fortra.com
# company:
email: jonbranan@gmail.com
linkedin: https://www.linkedin.com/in/jonathanbranan/
github: https://github.com/jonbranan
resume: https://s3.jonb.io/cdn/author/Resume.pdf
---
Jonathan Branan is a Software Engineer at Fortra. He is mostly self-taught however he did attend a vocational class at Rackspace Open Cloud Academy.
Jonathan Branan is a Software Engineer. He is mostly self-taught however he did attend a vocational class at Rackspace Open Cloud Academy.
He has worked for Geek Squad repairing computers, GlobalScape as a Lead of the Client Services department and as a Product Owner of MFT applications at Fortra.
He has worked for Geek Squad repairing computers, GlobalScape as a Lead of the Client Services department, a Product Owner of MFT applications and as a Software Engineer at Fortra.
He currently lives in San Antonio, Texas with his wife and two dogs. He enjoys Basketball, Video games, cooking, camping(backpacking, glamping), watching movies,
making mixed drinks and building Lego's. Jonathan and his wife like to frequently travel.

10
data/blog/blogideas.mdx Normal file
View File

@ -0,0 +1,10 @@
---
title: 'idea'
date: '2025-1-1'
#lastmod: '202year-month-day'
draft: true
---
- split dns jonb.io vs int.jonb.io
- entertainment stack
<video controls autoPlay playsInline src="https://youtu.be/M1DJSXgJrDc?si=Rasg9u8muFno_si8"/>

View File

@ -1,16 +1,15 @@
---
title: 'Using Visual Studio Code to quickly write frontmatter'
date: '2024-11-12'
#lastmod: '202year-day-month'
tags: ['Visual Studio Code', 'guide', 'Tailwind Nextjs Starter Blog']
draft: true
date: '2024-2-7'
tags: ['Visual Studio Code', 'guide', 'code']
draft: false
summary: 'summary'
#images: ['/static/images/image.jpg',]
#authors: ['default',]
#layout: PostLayout
#canonicalUrl: https://jonb.io/blog/the-url-here
---
1. Paste the following into your md or mdx snippet file.In Visual Studio Code, go to File --> Preferences --> Configure Snippets.
2. To use a snippet, type the prefix and you will get snippet suggestions.
```json
{
"Blog Frontmatter": {

View File

@ -1,23 +1,25 @@
---
title: How I Built Cygnus
date: '2024-10-14'
lastmod: '2024-11-26'
tags: ['cygnus', 'self-hosted', 'server']
draft: false
summary: A story of how I started self-hosting.
---
<TOCInline toc={props.toc} asDisclosure toHeading={3} />
> #### Now that I think about it, they kind of took advantage of me
### Side work for Aunt Laurie
![Saw Blade](https://www.kakaindustrial.com/cdn/shop/articles/KakaIndustrialLLC-278201-Different-Types-Saws-Blogbanner1.jpg?v=1710260765&width=1000)
The first piece of hardware I ever owned was given to me by my dear Aunt. At the time she had worked for a company that sold industrial saw blades and she was frequently fixing their IT issues despite being an accountant. To this day it still infuriates me how much they took advantage of her. Now that I think about it, they kind of took advantage of me too. You see I had worked for GeekSquad at the time and looking back at it, I realize how little I knew then. While I know there is always more to learn in the industry, you learn pretty quickly is that your time is valuable, and that not everyone views IT work as "easy" and "no big deal". If they had invited an IT consultant to do the 3 hours of work I did, the bill easily would have been hundreds of dollars. These days, I don't charge by the hour, I charge by the job. This way, I can work quickly and not get penalized for it.
The first piece of hardware I ever owned was given to me by my dear Aunt. At the time she had worked for a company that sold industrial saw blades, and she was frequently fixing their IT issues despite being an accountant. To this day it still infuriates me how much they took advantage of her. Now that I think about it, they kind of took advantage of me too. You see I had worked for Geek Squad at the time and looking back at it, I realize how little I knew then. While I know there is always more to learn in the industry, you learn pretty quickly is that your time is valuable, and that not everyone views IT work as "easy" and "no big deal". If they had invited an IT consultant to do the 3 hours of work I did, the bill easily would have been hundreds of dollars. These days, I don't charge by the hour, I charge by the job. This way, I can work quickly and not get penalized for it.
She asked me in to help take a look at a virus infecting some of the computers at the office. This virus was mean; one of those that would keep creating itself until you found the source process. I don't really think it was a self-replicating [worm-type](https://en.wikipedia.org/wiki/Computer_worm) virus because it didn't spread to all of the computers in the network, just a few. It was more like one of those click-a-link-in-an-email-that-you-shouldn't-have types. No, Harbor freights doesn't have a free gift card for you. What is free, is the headache it takes to find the dang source. I was able to find it only because I suggested an antivirus I liked: Webroot. It was one of the most performant and reliable anti-viruses I have ever seen. After convincing my aunt to buy it, I whipped up a batch file to install the application and register it all in one go. Now that I think about it, that's probably when I really started to enjoy writing code/scripting. After using the script, Webroot immediately found the source process and resource usage went back to normal.
She asked me in to help take a look at a virus infecting some computers at the office. This virus was mean; one of those that would keep creating itself until you found the source process. I don't really think it was a self-replicating [worm-type](https://en.wikipedia.org/wiki/Computer_worm) virus because it didn't spread to all the computers in the network, just a few. It was more like one of those click-a-link-in-an-email-that-you-shouldn't-have types. No, Harbor freights doesn't have a gift card for you. What is free, is the headache it takes to find the dang source. I was able to find it only because I suggested an antivirus I liked: Webroot. It was one of the most performant and reliable anti-viruses I have ever seen. After convincing my aunt to buy it, I whipped up a batch file to install the application and register it all in one go. Now that I think about it, that's probably when I really started to enjoy writing code/scripting. After using the script, Webroot immediately found the source process and resource usage went back to normal.
"Would you like a server?"
"A what?" I respond confused (I was expecting money). She starts walking to the networking closet where I see an old dell tower sitting on the floor. At first I think it's just a desktop, but after looking at the label closely, I realize it's actually a server in desktop form. She was offering me a [Dell T110](https://i.dell.com/sites/csdocuments/Shared-Content_data-Sheets_Documents/en/T110-SpecSheet.pdf) equipped with a quad core Xeon processor(Intel Server CPU) and 4 bays for hard drives. Hard drives included.
"A what?" I respond confused (I was expecting money). She starts walking to the networking closet where I see an old dell tower sitting on the floor. At first, I think it's just a desktop, but after looking at the label closely, I realize it's actually a server in desktop form. She was offering me a [Dell T110](https://i.dell.com/sites/csdocuments/Shared-Content_data-Sheets_Documents/en/T110-SpecSheet.pdf) equipped with a quad-core Xeon processor(Intel Server CPU) and 4 bays for hard drives. Hard drives included.
"Oh Sweet! Thanks Auntie Lar!"
@ -30,30 +32,35 @@ Despite not being paid like a professional, this was sufficient compensation. Wh
_Dell T110 Server_
The computer sits in my room for months. Every glance in its direction, I ask my self: "What should I ask it to do?". A month or two later, I am lounging in the living room with my parents watching OTA tv. My father just did a scan of channels and while we are flipping through the channels we come across The Fresh Prince of Bel-Air reruns. We couldn't get enough. I was so happy. You see, you couldn't find those reruns on OTA in San Antonio; And we just happened to be getting a channel from Austin! This goes on for a couple of days then one day, the floor just gets pull from underneath our feet. We were no longer getting signal! I have my answer to the question now. I am going to download The Fresh Prince of Bel-Air and watch it off my server. After installing Windows Sever 2012 Edition, I install plex. I happily download The Fresh Prince of Bel-Air and ironically, to this day I haven't watched all the episodes. I still have all of the files though.
The computer sits in my room for months. Every glance in its direction, I ask my self: "What should I ask it to do?". A month or two later, I am lounging in the living room with my parents watching OTA TV. My father just did a scan of channels and while we are flipping through the channels we come across The Fresh Prince of Bel Air reruns. We couldn't get enough. I was so happy. You see, you couldn't find those reruns on OTA in San Antonio; And we just happened to be getting a channel from Austin! This goes on for a couple of days then one day, the floor just gets pull from underneath our feet. We were no longer getting signal! I have my answer to the question now. I am going to download The Fresh Prince of Bel Air and watch it off my server. After installing Windows Sever 2012 Edition, I install Plex. I happily download The Fresh Prince of Bel Air and ironically, to this day I haven't watched all the episodes. I still have all the files though.
### Learning virtualization
"So what do you like to do with your free time?"
"I love watching movies! I have a plex server. I also like to play Video Games like League of legends and Overwatch." I smile as I respond to my future Manager.
"I love watching movies! I have a Plex server. I also like to play Video Games like League of Legends and Overwatch." I smile as I respond to my future Manager.
"That's cool, I have one too. Well alright, we are going to give you a technical interview now. Here's the packet. You have an hour."
[GlobalSCAPE office](https://www.google.com/maps/uv?pb=!1s0x865c66b4a81e869b%3A0x43fa6342ae196013!3m1!7e115!4s%2Fmaps%2Fplace%2Fglobalscape%2Boffice%2Bsan%2Bantonio%2F%4029.5870434%2C-98.5723876%2C3a%2C75y%2C272.84h%2C90t%2Fdata%3D*213m4*211e1*213m2*211s_-NCMpFUBxQjzfAExNE2dA*212e0*214m2*213m1*211s0x865c66b4a81e869b%3A0x43fa6342ae196013%3Fsa%3DX%26ved%3D2ahUKEwj0lbWns_mJAxWIJNAFHf4eLhsQpx96BAhOEAA!5sglobalscape%20office%20san%20antonio%20-%20Google%20Search!15sCgIgAQ&imagekey=!1e2!2s_-NCMpFUBxQjzfAExNE2dA&cr=le_a7&hl=en&ved=1t%3A206134&ictx=111)
I would say the interview went well. After all, I got the job. This was my first "Corporate Job". The exposure really required me to teach my self on the side. Using [VMWare ESXi](https://www.vmware.com/products/cloud-infrastructure/esxi-and-esx) was very easy. Their bare metal hypervisor came with networking, storage management etc. and was reasonably intuitive.
I would say the interview went well. After all, I got the job. This was my first "Corporate Job". The exposure really required me to teach my self on the side. Using [VMWare ESXI](https://www.vmware.com/products/cloud-infrastructure/esxi-and-esx) was very easy. Their bare metal hypervisor came with networking, storage management etc and was reasonably intuitive.
Downloading and setting up linux virtual machines with each application was challenging yet rewarding. Setting up servers manually and then installing the applications was an involved process; One that likely set the ground work for understanding the use case of containers. Being RHEL certified did afford me a large degree of appreciation for linux. I would combine these later to make managing my home lab much, much easier. But, hey, you live and you learn. In this case I learned that I was demanding too much of my little Dell T100 server. I would need to build it bigger. After all, I was already at home.
Downloading and setting up Linux virtual machines with each application was challenging yet rewarding. Setting up servers manually and then installing the applications was an involved process; One that likely set the groundwork for understanding the use case of containers. Being RHEL certified did afford me a large degree of appreciation for Linux. I would combine these later to make managing my home lab much, much easier. But, hey, you live and you learn. In this case I learned that I was demanding too much of my little Dell T100 server. I would need to build it bigger. After all, I was already at home.
### Building from scratch
Ebay, is a great website. You can find anything from car parts to an ice tea maker. They also sell things from China. Did you know you could buy server parts from Ebay? I built a dual processor server. Guess how much each processor was? $5. And the RAM(Memory)? $7. Just to put that in perspective, *new* processors were *hundreds* of dollars, sometimes even thousands. Ebay enabled me build an affordable home lab server. It was great while it lasted.....
eBay, is a great website. You can find anything from car parts to an ice tea maker. They also sell things from China. Did you know you could buy server parts from eBay? I built a dual processor server. Guess how much each processor was? 5 bucks. And the RAM(Memory)? 7. Just to put that in perspective, *new* processors are *hundreds* of dollars, sometimes even thousands. eBay enabled me build an affordable home lab server. It was great while it lasted.....
### 15 amp circuit breaker
Did you know, in America, there are two typical types of circuits run in residential rooms? Me either! For example, your bedroom likely has several outlets however in most cases they all are sharing the same power connection or circuit. These circuits have a maximum amount of power they can safely pull before the safety switch or circuit breaker will "trip". I learned this the hard way. You see, I lived my grandparents at the time and my grandfather needed the house warmer to stay healthy and in a good mood. So my grandmother and I never raised the temperature. But since we live in Texas, I needed something to cool down my room. I was in the middle of an Overwatch gaming session and all of the sudden the pc shuts off. I new instantly the circuit breaker tripped because the AC shutoff too. I ran over to the circuit panel, slapped the breaker on and ran right back to my game. Booted the PC and get back in game as quickly as possible. Trips again. Turn it back on. I repeat this business another time before I give up. A gaming PC, a couple of servers and an air conditioner draw a lot of power. When all three are running on the same circuit, it can cause it to trip. I find out later the circuit in my room is a 15 amp breaker, instead of a 20! At least I know now. I go to watch a movie on Plex and I notice plex isn't loading. I walk over to the servers and power them on. The Dell T100 works just fine. My custom server, won't turn on! Panic sets in.
### 15 amp circuit breakers
![breaker](https://preview.redd.it/mncg0t1iu3iz.jpg?width=1080&crop=smart&auto=webp&s=093eb8b8ee58e94042b7590f12d15d719163ad9d)
Did you know, in America, there are two typical types of circuits run in residential rooms? Me either! For example, your bedroom likely has several outlets however in most cases they all are sharing the same power connection or circuit. These circuits have a maximum amount of power they can safely pull before the safety switch or circuit breaker will "trip". I learned this the hard way. You see, I lived my grandparents at the time and my grandfather needed the house warmer to stay healthy and in a good mood. So my grandmother and I never raised the temperature. But since we live in Texas, I needed something to cool down my room. I was in the middle of an Overwatch gaming session and all of a sudden the pc shuts off. I knew instantly the circuit breaker tripped because the AC shutoff too. Therefore, I ran over to the circuit panel, slapped the breaker on and ran right back to my game. Booted the PC and get back in game as quickly as possible. Trips again. Turn it back on. I repeat this business another time before I give up. A gaming PC, a couple of servers and an air conditioner draw a lot of power. When all three are running on the same circuit, it can cause it to trip. I find out later the circuit in my room is a 15 amp breaker, instead of a 20! At least I know now. I go to watch a movie on Plex and I notice Plex isn't loading. I walk over to the servers and power them on. The Dell T100 works just fine. My custom server, won't turn on! Panic sets in.
### Troubleshooting insanity
What on God's green earth is going on? The server's fans are spinning, but I am not getting any video output. I open the case
What on God's green earth is going on? The server's fans are spinning, but I am not getting any video output. I open the case and start checking the CPU fans. They're spinning... but the monitor doesn't have any video output. Restart after restart: Nada. Order more parts from China and wait. Discover the motherboard is bad, order another. Still can't figure it out. My best guess is my server was so messed up, I kept shorting parts when mixing good and bad. Things kept breaking as I was troubleshooting. Sometimes, the only solution is to start new.
{/*
### The Need to Concede
![I quit](https://i.giphy.com/qKKhLmjFRMfxWBnRli.webp)
This is taking too much time.
Time to order newer, stronger parts off the internet! This is how Cygnus was born. Cygnus means swan in [Latinized Greek](https://en.wikipedia.org/wiki/Cygnus_(constellation)). In some ways, this server, which is serving this website, is my swan song. It enables me to be a part of the internet and share my voice among a [billion](https://www.digitalsilk.com/digital-trends/how-many-websites-are-there/#:~:text=Although%20the%20internet%20has%20already,infected%20with%20malware%20in%202022.) of other websites.
### Light at the end of the tunnel
*/}
Fast forward to today. My current server has a 64 core processor with 128 GB of RAM. I mostly run docker containers with a few VMs. I have two servers. The little dell server is still running strong providing network and backup type services. Some of the pictures you see on this site are served from it. My home lab has enabled me to learn so much without additional schooling. If I didn't do this, I would have never pushed my make my own python applications in docker containers. If I hadn't done that I would have never been able to move into a developer role.

View File

@ -7,12 +7,12 @@ draft: false
summary: 'How to take a lightsaber on an airplane.'
#images: ['/static/images/', '']
#authors: ['default']
# layout: PostLayout # PostLayout, PostSimple and PostBanner
#layout: PostLayout # PostLayout, PostSimple and PostBanner
#canonicalUrl: https://jonb.io/blog/the-url-here
---
![GalaxysEdge](https://cdn1.parksmedia.wdprapps.disney.com/resize/mwImage/1/900/360/90/media/disneyparks_v0100/1/media/star-wars-galaxys-edge/courtyard-wide-galaxys-edge-5x2-1.jpg)
In short, yes you can but you have to break it down. I recently went to Disneyland and enjoyed the experience at [Savi's workshop](https://disneyland.disney.go.com/shops/disneyland/savis-workshop-handbuilt-lightsabers/). On my way out they handed me a long slender case for my lightsaber. Fast forward a few days and my wife and I are packing for the return trip home. I google it and find [others](https://www.reddit.com/r/GalaxysEdge/comments/u3vqxz/how_do_you_guys_get_your_lightsabers_onto_planes/) were able to take their lightsabers on the plane just fine as a personal item. When I get to the LAX airport I am quickly told that I would have to check in my lightsaber since their policy doesn't allow them to be stowed overhead anymore. I wait to see if I will be told a second time I would have to check it in and sure enough, someone spots my carrying case and mentions to the gentleman that is helping my I would have to check it in. I mention to the gentleman I can break it down and put it in my backpack.
In short, yes you can but you have to break it down. I recently went to Disneyland and enjoyed the experience at [Savi's workshop](https://disneyland.disney.go.com/shops/disneyland/savis-workshop-handbuilt-lightsabers/). On my way out they handed me a long slender case for my lightsaber. Fast forward a few days and my wife and I are packing for the return trip home. I google it and find [others](https://www.reddit.com/r/GalaxysEdge/comments/u3vqxz/how_do_you_guys_get_your_lightsabers_onto_planes/) were able to take their lightsabers on the plane just fine as a personal item. When I get to the LAX airport I am quickly told that I would have to check in my lightsaber since their policy doesn't allow them to be stowed overhead anymore. I wait to see if I will be told a second time. Sure enough, someone spots my carrying case and points it out to the gentleman that is helping me. He states I would have to check it in. I mention to the gentleman I can break it down and put it in my backpack.
"You can do that?" He says surprised.

View File

@ -1,30 +0,0 @@
---
title: 'Introducing Melis Sweet&Simple'
date: '2024-11-14'
#lastmod: '202year-month-day'
tags: ['Art', 'store', 'Melis Sweet&Simple']
draft: false
summary: 'Elevate your Space with Bespoke Handcrafted Art from our Store that is Super Sweet and Simple.'
images: ['','','']
authors: ['default','meli']
layout: PostLayout # PostLayout, PostSimple and PostBanner
#canonicalUrl: https://jonb.io/blog/the-url-here
---
## Meli's Sweet&Simple
> Elevate your Space with Bespoke Handcrafted Art from our Store that is Super Sweet and Simple.
[Check it out!](https://melisweetsimple.jonb.io/)
<div className="-mx-2 flex flex-wrap overflow-hidden xl:-mx-2">
<div className="my-1 w-full overflow-hidden px-2 xl:my-1 xl:w-1/2 xl:px-2">
![walle](https://melisweetsimple.jonb.io/cdn/shop/files/FullSizeRender.heic?v=1730088140&width=2200)
</div>
<div className="my-1 w-full overflow-hidden px-2 xl:my-1 xl:w-1/2 xl:px-2">
![walleclose](https://melisweetsimple.jonb.io/cdn/shop/files/FullSizeRender_4297464f-1fe2-4809-9694-72058873bed8.heic?v=1730088140&width=2200)
</div>
<div className="my-1 w-full overflow-hidden px-2 xl:my-1 xl:w-1/2 xl:px-2">
![bird](https://melisweetsimple.jonb.io/cdn/shop/files/A_Whirlwind_6713f0cd-3ed1-4e75-a825-504ad1c88e66.png?v=1729026172&width=2048)
</div>
</div>

View File

@ -1,22 +1,24 @@
---
title: Qbitmaid
date: '2024-11-3'
lastmod: '2024-11-14'
tags: ['python', 'projects', 'code']
draft: true
lastmod: '2025-1-28'
tags: ['python', 'projects', 'code', 'docker', 'unraid']
draft: false
summary: How I used python to keep my torrents in check
layout: PostBanner
images: ['https://s3.jonb.io/cdn/projects/qbitmaid.jpg']
---
### qbit-maid
Development [^1] of qbitmaid was over the course of several months. At first, the project was called qbit-clean and didn't have all the features the project has now. The issue was mainly with my download cache in unraid being filled with torrents I no longer needed to seed[^2]. When I would get a notification from the server that the download cache was 95% full I would have to manually go to [qbittorrent](https://www.qbittorrent.org/), sort the torrents by age and remove the ones older than two weeks avoiding torrents I wanted to keep.
![qbittorrent](https://www.qbittorrent.org/img/qb_banner.webp)
[![qbittorrent](https://www.qbittorrent.org/img/qb_banner.webp)](https://www.qbittorrent.org/)
This was tedious. Very tedious. So I went off to do more work just to avoid a little.
This was tedious. Very tedious. So I went off to do more work just to avoid a little.`qbitmaid.py` is the main file that glues the project together. This was my first project where I heavily abstracted the design. More on this later. First, we'll connect to the API. I used an existing client [library](https://pypi.org/project/qbittorrent-api/2022.5.32/) that makes this process simpler than writing your own client.
`qbitmaid.py`[^3]
```python
...
class Qbt:
@ -31,38 +33,157 @@ class Qbt:
except qbittorrentapi.APIError as e:
self.tl.exception(e)
self.po.send_message(e, title="qbit-maid API ERROR")
# Pulling all torrent data
self.torrent_list = self.qbt_client.torrents_info()
#Main process block
if self.use_log:
list_qbit_api_info(self)
list_first_tor(self)
debug_torrent_list(self)
build_tor_list(self)
process_counts(self)
if self.use_log:
torrent_count(self)
tor_processor(self)
if self.use_log:
print_processor(self)
if self.delete_torrents:
tor_delete(self)
self.et = datetime.datetime.now()
get_script_runtime(self)
if self.use_pushover:
tor_notify_summary(self)
if self.use_apprise:
tor_notify_apprise(self, r, apprise_notify)
if self.use_healthcheck:
send_ping(self, r, self.healthcheck_url)
# Run
if __name__== "__main__":
Qbt()
...
```
This is the main file that glues the project together. This was my first project where I heavily
Then we use the api to make an list of the torrents:
```python
# Pulling all torrent data
self.torrent_list = self.qbt_client.torrents_info()
```
Next, we "sift" out torrents to be deleted. This was created with a positive sieve meaning we specify positive scenarios. In other words, I know which torrents I want to keep as opposed to the torrents I don't want. Theres pros and cons to both scenarios however in the long term a positive sieve is less work.
> `qlist.py` has functions at the bottom of the file that are referenced in the conditions. This very method of programming made it easy to write unit tests as I went.
```python
if is_preme(torrent['seeding_time'], self.min_age):
continue
```
```python
def is_preme(seeding_time, minage):
if seeding_time <= minage:
return True
```
When it comes across an item that meets certain criteria it will skip it. For instance, the example above checks to see if it's too soon to remove a torrent. This is because some [trackers][private tracker] require a minimum seed time. If you were to remove a torrent sooner than they require, it could lead to getting kicked.
`qlist.py` Has a couple jobs:
- Tag torrents according to how they should be treated.
- Sort
`qlist.py`
```python
...
def build_tor_list(self):
while self.torrent_list:
...
if is_tracker_blank(torrent['tracker']):
...
continue
elif is_cat_ignored(torrent['category'], self.cat_whitelist.values()):
...
continue
elif is_ignored_tag(self.ignored_tags.values(),torrent['tags']):
...
continue
if is_tag_blank(torrent['tags']):
...
if is_protected_tracker(torrent['tracker'], self.tracker_whitelist.values()):
self.qbt_client.torrents_add_tags(self.tracker_protected_tag,torrent['hash'])
elif is_not_protected_tracker(torrent['tracker'], self.tracker_whitelist.values()):
self.qbt_client.torrents_add_tags(self.tracker_non_protected_tag,torrent['hash'])
if is_preme(torrent['seeding_time'], self.min_age):
continue
elif is_protected_tracker(torrent['tracker'], self.tracker_whitelist.values()):
if is_tag_blank(torrent['tags']):
self.qbt_client.torrents_add_tags(self.tracker_protected_tag,torrent['hash'])
...
self.tracker_list.append(torrent)
elif is_not_protected_tracker(torrent['tracker'], self.tracker_whitelist.values()):
if is_tag_blank(torrent['tags']):
self.qbt_client.torrents_add_tags(self.tracker_non_protected_tag,torrent['hash'])
...
self.tracker_list.append(torrent)
```
In this case the items I want to keep stays. `qlist.py` passes the data over to `qprocess.py`. This is done through about 2 layers of abstraction. Unfortunatley, this type of programming makes it difficult to follow.
`qprocess.py` has four jobs:
- Collect telemetry
- Delete torrents if needed
- Enable debugging if needed
`qprocess.py`
```python
def tor_processor(self):
"""Main logic to sort through both self.tracker_nonprotected_list and self.tracker_protected_list
If torrent meets criteria for deletion, its infohash_v1 will be appended to self.torrent_hash_delete_list
"""
for canidate in self.tracker_list:
if self.enable_telemetry:
header = ['state','ratio','tags','added','hash','name','tracker']
row = [canidate['state'],canidate['ratio'],canidate["tags"],canidate['added_on'],canidate['infohash_v1'],canidate["name"][0:20],canidate['tracker']]
write_csv(self.cv,self.telemetry_outfile,header,row)
...
elif is_protected_over_ratio(canidate['ratio'], 1.05, self.tracker_protected_tag, canidate["tags"]):
if self.use_log:
self.tl.debug(f'["{canidate["name"][0:20]}..."] is above a 1.05 ratio({canidate["ratio"]}).')
self.torrent_hash_delete_list.append(canidate['infohash_v1'])
...
elif is_not_protected_tor(self.tracker_non_protected_tag, canidate["tags"]):
self.torrent_hash_delete_list.append(canidate['infohash_v1'])
...
else:
if self.enable_dragnet:
header = ['state','ratio','tags','added','thash','tname','trname']
row = [canidate['state'],canidate['ratio'],canidate["tags"],canidate['added_on'],canidate['infohash_v1'],canidate["name"][0:20],canidate['tracker']]
write_csv(self.cv,self.dragnet_outfile,header,row)
continue
```
I package this in a docker file:
```Dockerfile
FROM python:alpine3.18
WORKDIR /
COPY . opt
RUN apk add --no-cache supercronic
RUN pip install requests
RUN pip install qbittorrent-api
RUN chmod +x /opt/entrypoint.sh
CMD ["/opt/entrypoint.sh"]
```
Then use [Drone][drone] to package this into a container. This pushes the container to an [OCI repo][oci] in gitea. The application is configured through a toml file:
```toml
[qbittorrent]
host = "192.168.x.x"
port = 8080
username = "user"
password = "pass"
...
[healthcheck]
use_healthcheck = true
healthcheck_url = "https://example.com/ping/<uuid>>"
```
Using unraid has honestly been a delight. I had some performance issues but that was due to how I was using the storage pool.
![unraid](https://s3.jonb.io/cdn/blog/qbitmaid/unraid.png)
Finally, this same container will run the test cases in `test_qbitmaid.py`. This is handled by drone. So eachtime I push new code to a development branch on gitea, it creates a container to test and tests the code. Once I see that it has passed, I can merge the code to the main branch.
## Final Notes
I have been using this for over 2 years. It was a huge learning experience and my coding practices have evolved over my newer projects. While I did make this for my use mainly, feel free to try it out! If you have any questions, you can open an issue [here](https://git.jonb.io/jblu/qbit-maid/issues/new).
[^1]: The Source Code can be found [here][source-code].
[^2]: Private trackers require you to seed a torrent for a period of time. In my case, I have to seed for about 2 weeks or to a ratio of 1.
[^3]: Code has been removed for examples in this article.
[source-code]: https://git.jonb.io/jblu/qbit-maid
[source-code]: https://git.jonb.io/jblu/qbit-maid
[private tracker]: https://en.wikipedia.org/wiki/BitTorrent_tracker#Private_trackers
[drone]: https://www.drone.io/
[oci]: https://git.jonb.io/jblu/-/packages/container/qbit-maid/latest

View File

@ -1,10 +1,10 @@
const headerNavLinks = [
{ href: '/', title: 'Home' },
{ href: '/blog', title: 'Blog' },
// { href: 'https://git.jonb.io/', title: 'Code' },
{ href: '/likes', title: 'Likes' },
{ href: '/tags', title: 'Tags' },
{ href: '/projects', title: 'Projects' },
{ href: 'https://s3.jonb.io/cdn/author/Resume.pdf', title: 'Resume' },
{ href: '/about', title: 'About' },
]

3
data/logo copy.svg Normal file
View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="344.5639097744361 330.27819548872174 111.73684210526318 91.21804511278197" width="53.87" height="43.61"><defs><path d="M453.3 331.28L453.3 359.85L388.64 418.5L388.64 388.42L453.3 331.28Z" id="aFZf6T5ED"></path><linearGradient id="gradientb2ThqnP5Op" gradientUnits="userSpaceOnUse" x1="420.97" y1="331.28" x2="420.97" y2="418.5"><stop style="stop-color: #06b6d4;stop-opacity: 1" offset="0%"></stop><stop style="stop-color: #67e8f9;stop-opacity: 1" offset="100%"></stop></linearGradient><path d="M410.23 331.28L410.23 359.85L345.56 418.5L345.56 388.42L410.23 331.28Z" id="a9fehgwfM"></path><linearGradient id="gradientk1wNV9Ostb" gradientUnits="userSpaceOnUse" x1="377.89" y1="331.28" x2="377.89" y2="418.5"><stop style="stop-color: #06b6d4;stop-opacity: 1" offset="0%"></stop><stop style="stop-color: #67e8f9;stop-opacity: 1" offset="100%"></stop></linearGradient></defs><g><g><use xlink:href="#aFZf6T5ED" opacity="1" fill="url(#gradientb2ThqnP5Op)"></use></g><g><use xlink:href="#a9fehgwfM" opacity="1" fill="url(#gradientk1wNV9Ostb)"></use></g></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -3,7 +3,7 @@ const siteMetadata = {
title: 'Joyful Fixations',
author: 'Jonathan Branan',
headerTitle: 'Joyful Fixations',
description: "Thought's from someone with an internet connection.",
description: 'Things that bring me joy and intense focus.',
language: 'en-us',
theme: 'system', // system, dark or light
siteUrl: 'https://jonb.io',
@ -21,6 +21,7 @@ const siteMetadata = {
// threads: 'https://www.threads.net',
// instagram: 'https://www.instagram.com',
// medium: 'https://medium.com',
resume: 'https://s3.jonb.io/cdn/author/Resume.pdf',
locale: 'en-US',
// set to true if you want a navbar fixed to the top
stickyNav: true,
@ -32,7 +33,8 @@ const siteMetadata = {
// We use an env variable for this site to avoid other users cloning our analytics ID
umamiWebsiteId: process.env.NEXT_UMAMI_ID, // e.g. 123e4567-e89b-12d3-a456-426614174000
// You may also need to overwrite the script if you're storing data in the US - ex:
// src: 'https://us.umami.is/script.js'
src: 'https://umami.jonb.io/script.js',
umamiHostUrl: 'https://umami.jonb.io',
// Remember to add 'us.umami.is' in `next.config.js` as a permitted domain for the CSP
},
// plausibleAnalytics: {

70
eslint.config.mjs Normal file
View File

@ -0,0 +1,70 @@
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import globals from 'globals'
import tsParser from '@typescript-eslint/parser'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import js from '@eslint/js'
import { FlatCompat } from '@eslint/eslintrc'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
})
export default [
{
ignores: [],
},
js.configs.recommended,
...compat.extends(
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:jsx-a11y/recommended',
'plugin:prettier/recommended',
'next',
'next/core-web-vitals'
),
{
plugins: {
'@typescript-eslint': typescriptEslint,
},
languageOptions: {
globals: {
...globals.browser,
...globals.amd,
...globals.node,
},
parser: tsParser,
ecmaVersion: 5,
sourceType: 'commonjs',
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
},
rules: {
'prettier/prettier': 'error',
'react/react-in-jsx-scope': 'off',
'jsx-a11y/anchor-is-valid': [
'error',
{
components: ['Link'],
specialLink: ['hrefLeft', 'hrefRight'],
aspects: ['invalidHref', 'preferButton'],
},
],
'react/prop-types': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'react/no-unescaped-entities': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
},
},
]

View File

@ -9,7 +9,7 @@ interface Props {
}
export default function AuthorLayout({ children, content }: Props) {
const { name, avatar, occupation, company, email, twitter, linkedin, github } = content
const { name, avatar, occupation, company, email, twitter, linkedin, github, resume } = content
return (
<>
@ -38,6 +38,7 @@ export default function AuthorLayout({ children, content }: Props) {
<SocialIcon kind="github" href={github} />
<SocialIcon kind="linkedin" href={linkedin} />
<SocialIcon kind="x" href={twitter} />
<SocialIcon kind="resume" href={resume} />
</div>
</div>
<div className="prose max-w-none pb-8 pt-8 dark:prose-invert xl:col-span-2">

View File

@ -1,4 +1,3 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
'use client'
import { usePathname } from 'next/navigation'

2
next-env.d.ts vendored
View File

@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@ -7,7 +7,7 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
// You might need to insert additional domains in script-src if you are using external services
const ContentSecurityPolicy = `
default-src 'self';
script-src 'self' 'unsafe-eval' 'unsafe-inline' giscus.app analytics.umami.is;
script-src 'self' 'unsafe-eval' 'unsafe-inline' giscus.app umami.jonb.io;
style-src 'self' 'unsafe-inline';
img-src * blob: data:;
media-src s3.jonb.io* melisweetsimple.jonb.io*;

16561
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
},
"dependencies": {
"@headlessui/react": "2.2.0",
"@next/bundle-analyzer": "15.0.2",
"@next/bundle-analyzer": "15.1.4",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"autoprefixer": "^10.4.13",
@ -24,12 +24,13 @@
"gray-matter": "^4.0.2",
"hast-util-from-html-isomorphic": "^2.0.0",
"image-size": "1.0.0",
"next": "15.0.2",
"next": "15.1.4",
"next-contentlayer2": "0.5.3",
"next-themes": "^0.3.0",
"pliny": "0.4.0",
"postcss": "^8.4.24",
"react": "rc",
"react-doc-viewer": "^0.1.14",
"react-dom": "rc",
"reading-time": "1.5.0",
"rehype-autolink-headings": "^7.1.0",
@ -47,6 +48,8 @@
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.16.0",
"@svgr/webpack": "^8.0.1",
"@types/mdx": "^2.0.12",
"@types/react": "^18.2.73",
@ -54,9 +57,10 @@
"@typescript-eslint/parser": "^8.12.0",
"cross-env": "^7.0.3",
"eslint": "^9.14.0",
"eslint-config-next": "15.0.2",
"eslint-config-next": "15.1.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.0",
"globals": "^15.12.0",
"husky": "^9.0.0",
"lint-staged": "^13.0.0",
"prettier": "^3.0.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#000000</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,21 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="107.000000pt" height="107.000000pt" viewBox="0 0 107.000000 107.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,107.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M609 942 c-17 -18 -95 -88 -289 -258 -117 -102 -177 -155 -204 -179
-6 -5 -31 -28 -56 -50 l-45 -39 -1 -144 -1 -143 21 18 c11 10 25 23 31 28 7 6
46 41 88 79 86 77 86 77 216 196 52 47 103 93 115 103 11 10 50 45 86 77 l65
59 -1 131 c0 71 -3 132 -5 135 -3 2 -12 -3 -20 -13z"/>
<path d="M929 857 c-64 -56 -145 -128 -180 -159 -35 -31 -75 -66 -89 -78 -55
-47 -68 -59 -150 -131 l-85 -76 2 -129 c1 -71 2 -135 2 -143 1 -11 5 -10 19 5
10 10 54 51 98 89 43 39 92 83 109 99 26 24 63 58 189 171 12 11 47 43 77 70
30 28 70 63 89 79 l35 29 1 138 c1 77 1 139 0 139 0 -1 -53 -47 -117 -103z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,14 +1,21 @@
{
"name": "",
"short_name": "",
"name": "Joyful Fixations",
"short_name": "Joyful Fixations",
"icons": [
{
"src": "/android-chrome-96x96.png",
"sizes": "96x96",
"type": "image/png"
"src": "/static/favicons/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/favicons/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#000000",
"background_color": "#000000",
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

4041
yarn.lock

File diff suppressed because it is too large Load Diff