fix: add blog pages and fix project schema, remove internal TimeNet CLI
CI/CD Pipeline / Build & Deploy (push) Successful in 23s

This commit is contained in:
wh-leader
2026-05-11 14:02:08 +02:00
parent 3f7e54c72e
commit 54cad6684c
4 changed files with 130 additions and 55 deletions
@@ -1,9 +1,10 @@
---
title: "Python Project Template"
description: "Opinionated Python project template with modern tooling: uv, ruff, mypy, pytest, Docker, and CI/CD ready"
pubDate: 2026-05-01
url: "https://gitlab.impresion3d.pro/root/python-project-template"
status: "active"
stack: ["Python", "Docker", "Gitea Actions", "Portainer"]
tags: ["Python", "Docker", "CI/CD", "DevOps"]
startDate: 2026-05-01
featured: true
---
-53
View File
@@ -1,53 +0,0 @@
---
title: "TimeNet CLI"
description: "Command-line tool for managing TimeNet attendance tracking - clock in/out from terminal"
pubDate: 2026-04-15
status: "active"
stack: ["Python", "CLI", "Automation"]
featured: false
---
# TimeNet CLI
A command-line interface for managing TimeNet attendance tracking. Clock in, clock out, and check status without opening the web interface.
## What It Does
TimeNet CLI automates daily time tracking workflows:
- Clock in/out with a single command
- Check current status (in/out, hours worked today)
- View work history
- Export reports
## Why I Built This
My employer uses TimeNet for attendance tracking. Opening a browser, navigating to the site, logging in, and clicking buttons for a simple clock-in felt like unnecessary friction.
I wanted a faster way: `timenet in` in the morning, `timenet out` when leaving. That's it.
## Stack
- **Python** for CLI logic
- **Click** for command-line interface
- **Requests** for API interaction
- **Credential management** for secure token storage
## Usage
```bash
# Clock in
timenet in
# Clock out
timenet out
# Check status
timenet status
# View today's hours
timenet today
```
## Status
Active. I use it daily for work attendance tracking.
+65
View File
@@ -0,0 +1,65 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import Header from '../../components/layout/Header.astro';
import Footer from '../../components/layout/Footer.astro';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog', ({ data }) => !data.draft);
return blogEntries.map(entry => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<BaseLayout
title={entry.data.title}
description={entry.data.description}
>
<Header />
<main class="flex-1 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<article>
<header class="mb-8">
<time class="text-sm text-text-tertiary block mb-2">
{entry.data.publishDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
<h1 class="text-4xl font-bold mb-4">{entry.data.title}</h1>
<p class="text-xl text-text-secondary mb-4">
{entry.data.description}
</p>
<div class="flex flex-wrap gap-2">
{entry.data.tags.map((tag) => (
<span class="inline-block px-3 py-1 rounded-full text-sm font-medium border bg-text-tertiary/10 text-text-secondary border-text-tertiary/20">
{tag}
</span>
))}
</div>
</header>
<div class="prose prose-invert prose-lg max-w-none">
<Content />
</div>
<footer class="mt-12 pt-8 border-t border-text-tertiary/20">
<a
href="/blog"
class="text-primary hover:text-secondary transition-colors font-medium"
>
← Back to all posts
</a>
</footer>
</article>
</main>
<Footer />
</BaseLayout>
+62
View File
@@ -0,0 +1,62 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import Header from '../../components/layout/Header.astro';
import Footer from '../../components/layout/Footer.astro';
const allPosts = await getCollection('blog', ({ data }) => !data.draft);
const sortedPosts = allPosts.sort(
(a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);
---
<BaseLayout
title="Blog"
description="Thoughts on building products, technical learnings, and the indie builder journey"
>
<Header />
<main class="flex-1 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 class="text-4xl font-bold mb-8">Blog</h1>
<div class="space-y-8">
{sortedPosts.map((post) => (
<article class="bg-surface rounded-lg border border-text-tertiary/20 p-6 hover:border-primary/40 transition-colors">
<a href={`/blog/${post.slug}`} class="block">
<time class="text-sm text-text-tertiary block mb-2">
{post.data.publishDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
<h2 class="text-2xl font-semibold mb-3 text-text-primary hover:text-primary transition-colors">
{post.data.title}
</h2>
<p class="text-text-secondary mb-4">
{post.data.description}
</p>
<div class="flex flex-wrap gap-2 mb-4">
{post.data.tags.map((tag) => (
<span class="inline-block px-3 py-1 rounded-full text-sm font-medium border bg-text-tertiary/10 text-text-secondary border-text-tertiary/20">
{tag}
</span>
))}
</div>
<span class="text-primary font-medium">Read more →</span>
</a>
</article>
))}
</div>
{sortedPosts.length === 0 && (
<p class="text-text-secondary text-center py-12">
No posts yet. Check back soon!
</p>
)}
</main>
<Footer />
</BaseLayout>