Compare commits
1 Commits
master
...
fix/replac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77ab7ba702 |
32
.github/dependabot.yml
vendored
32
.github/dependabot.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
day: "monday"
|
|
||||||
time: "06:00"
|
|
||||||
open-pull-requests-limit: 5
|
|
||||||
reviewers:
|
|
||||||
- "tuan-dat-tran"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
commit-message:
|
|
||||||
prefix: "deps"
|
|
||||||
include: "scope"
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
day: "monday"
|
|
||||||
time: "06:00"
|
|
||||||
open-pull-requests-limit: 3
|
|
||||||
reviewers:
|
|
||||||
- "tuan-dat-tran"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "github-actions"
|
|
||||||
commit-message:
|
|
||||||
prefix: "ci"
|
|
||||||
include: "scope"
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
FROM node:25-alpine as build
|
FROM node:20-alpine as build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install --legacy-peer-deps --ignore-scripts
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:25-slim
|
FROM node:20-slim
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -24,192 +24,21 @@ export async function initDB() {
|
|||||||
await db('cv_data').insert({
|
await db('cv_data').insert({
|
||||||
id: 1,
|
id: 1,
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
"personal": {
|
personal: {
|
||||||
"name": "Tuan-Dat Tran",
|
name: "Tuan-Dat Tran",
|
||||||
"title": "Junior DevOps Engineer",
|
title: "Junior DevOps Engineer",
|
||||||
"intro": "DevOps Engineer and Homelab Enthusiast based in Essen, Germany. Primary owner of production SaaS platforms on Azure AKS, with a strong background in Linux, automation, and infrastructure engineering.",
|
intro: "Passionierter DevOps Engineer mit Fokus auf Cloud-Infrastruktur.",
|
||||||
"email": "tuan-dat.tran@dextradata.com",
|
email: "tuan-dat.tran@example.com",
|
||||||
"github": "https://github.com/TuDatTr",
|
github: "https://github.com/tuan-dat-tran",
|
||||||
"linkedin": "https://www.linkedin.com/in/tudattr/",
|
linkedin: "https://linkedin.com/in/tuan-dat-tran",
|
||||||
"location": "Essen, Germany"
|
location: "Deutschland"
|
||||||
},
|
},
|
||||||
"experience": [
|
experience: [],
|
||||||
{
|
skills: {},
|
||||||
"id": 1,
|
education: [],
|
||||||
"role": "Junior DevOps Engineer",
|
projects: []
|
||||||
"company": "DextraData GRC Technologies GmbH",
|
})
|
||||||
"period": "2025 – present",
|
});
|
||||||
"type": "Full-time",
|
|
||||||
"description": "Primary owner of the GRASP GRC platform and Logipad aviation SaaS platform, running on Azure AKS and Rancher-managed Kubernetes clusters.",
|
|
||||||
"highlights": [
|
|
||||||
"Drives GitOps adoption with ArgoCD across production Kubernetes clusters",
|
|
||||||
"Manages CI/CD pipelines with Bitbucket Pipelines",
|
|
||||||
"Leads incident response and on-call for production outages",
|
|
||||||
"Reduced GRASP frontend pipeline from 30 to 16 minutes",
|
|
||||||
"Eliminated ~$2k/month Azure logging costs via workspace restructuring",
|
|
||||||
"Built internal AI tooling with LiteLLM and Ollama",
|
|
||||||
"Implemented Azure Container Registry mirroring to resolve Docker Hub rate limits"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"role": "Research Assistant",
|
|
||||||
"company": "NCS Group, Universität Duisburg-Essen",
|
|
||||||
"period": "2021 – 2024",
|
|
||||||
"type": "Part-time",
|
|
||||||
"description": "University research group role covering infrastructure management, containerized systems, and networking experiments.",
|
|
||||||
"highlights": [
|
|
||||||
"Managed lab infrastructure: Ansible-automated servers, AWS accounts, and inventory systems",
|
|
||||||
"Built and operated Dockerized and Kubernetes-based experiment environments",
|
|
||||||
"Co-authored a peer-reviewed publication at IEEE LCN 2023"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"role": "Student Mentor",
|
|
||||||
"company": "Universität Duisburg-Essen",
|
|
||||||
"period": "2021 – 2022",
|
|
||||||
"type": "Volunteer",
|
|
||||||
"description": "Introduced groups of ~20 freshmen to their new academic environment each semester, providing organizational and technical guidance throughout their first year.",
|
|
||||||
"highlights": [
|
|
||||||
"Guided new students through academic orientation each semester",
|
|
||||||
"Provided technical and organizational support throughout the first year"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"role": "Software Engineer",
|
|
||||||
"company": "gefeba Engineering GmbH",
|
|
||||||
"period": "2018 – 2020",
|
|
||||||
"type": "Part-time",
|
|
||||||
"description": "Worked on the company's main product — a frame-based data exchange system for monitoring industrial machinery — and developed a real-time log visualization application.",
|
|
||||||
"highlights": [
|
|
||||||
"Developed features for a frame-based industrial data exchange system",
|
|
||||||
"Built a real-time log visualization application for industrial machinery",
|
|
||||||
"Stack: C#, Angular, Bootstrap, Entity Framework"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"role": "Student Council Member",
|
|
||||||
"company": "Universität Duisburg-Essen",
|
|
||||||
"period": "2016 – 2019",
|
|
||||||
"type": "Volunteer",
|
|
||||||
"description": "Participated in faculty committees and organized social events. Managed the faculty's IT infrastructure and provided support to fellow students.",
|
|
||||||
"highlights": [
|
|
||||||
"Managed faculty IT infrastructure (Linux, Networking, LaTeX)",
|
|
||||||
"Organized social events and participated in faculty committees",
|
|
||||||
"Provided subject-specific and organizational support to fellow students"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"role": "Software Engineer",
|
|
||||||
"company": "gefeba Engineering GmbH",
|
|
||||||
"period": "2013 – 2015",
|
|
||||||
"type": "Part-time",
|
|
||||||
"description": "Joined after a school internship. Worked on internal ERP projects, designed a mail traffic management tool, and contributed to master data management tooling.",
|
|
||||||
"highlights": [
|
|
||||||
"Developed internal ERP project features",
|
|
||||||
"Designed a tool for managing project-related mail traffic",
|
|
||||||
"Contributed to internal master data management tooling",
|
|
||||||
"Stack: C#, HTML, CSS, JavaScript"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"skills": {
|
|
||||||
"Container & Orchestration": [
|
|
||||||
"Kubernetes",
|
|
||||||
"Docker",
|
|
||||||
"Helm",
|
|
||||||
"ArgoCD"
|
|
||||||
],
|
|
||||||
"CI/CD": [
|
|
||||||
"Bitbucket Pipelines",
|
|
||||||
"Git"
|
|
||||||
],
|
|
||||||
"Cloud": [
|
|
||||||
"Azure",
|
|
||||||
"Azure AKS",
|
|
||||||
"Azure App Gateway",
|
|
||||||
"Azure Entra ID"
|
|
||||||
],
|
|
||||||
"Infrastructure as Code": [
|
|
||||||
"Ansible"
|
|
||||||
],
|
|
||||||
"Observability": [
|
|
||||||
"ELK Stack",
|
|
||||||
"Checkmk",
|
|
||||||
"UptimeRobot"
|
|
||||||
],
|
|
||||||
"Databases": [
|
|
||||||
"PostgreSQL",
|
|
||||||
"MongoDB",
|
|
||||||
"CouchDB"
|
|
||||||
],
|
|
||||||
"Programming": [
|
|
||||||
"Rust",
|
|
||||||
"Python",
|
|
||||||
"Bash"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"education": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"degree": "B.Sc. Systems Engineering",
|
|
||||||
"institution": "Universität Duisburg-Essen",
|
|
||||||
"period": "2015 – present",
|
|
||||||
"status": "Bachelor thesis ongoing"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"projects": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Ethereum Smart Contract Fuzzer",
|
|
||||||
"description": "A fuzzer for Ethereum smart contracts built as part of the bachelor thesis project.",
|
|
||||||
"url": "#",
|
|
||||||
"tech": [
|
|
||||||
"Rust"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Homelab",
|
|
||||||
"description": "Ansible-managed homelab running on Debian Linux with Raspberry Pi, Mikrotik networking, Pihole, and self-hosted services.",
|
|
||||||
"url": "https://git.tudattr.dev/tudattr/ansible",
|
|
||||||
"tech": [
|
|
||||||
"Ansible",
|
|
||||||
"Kubernetes",
|
|
||||||
"Debian Linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"name": ".dotfiles",
|
|
||||||
"description": "Configuration files for daily-use tools providing a reproducible baseline for any personal ArchLinux system, including NeoVim (LazyVim), Zellij, and k9s.",
|
|
||||||
"url": "https://github.com/TuDatTr/dotfiles",
|
|
||||||
"tech": [
|
|
||||||
"Zsh",
|
|
||||||
"NeoVim",
|
|
||||||
"Zellij",
|
|
||||||
"ArchLinux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"name": "tudattr.dev",
|
|
||||||
"description": "Full-stack WebAssembly personal website built with the Rust-based Dioxus framework and Tailwind CSS.",
|
|
||||||
"url": "https://www.tudattr.dev",
|
|
||||||
"tech": [
|
|
||||||
"Rust",
|
|
||||||
"Dioxus",
|
|
||||||
"Tailwind CSS",
|
|
||||||
"WebAssembly"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
});
|
|
||||||
console.log('Initialized database with default CV data');
|
console.log('Initialized database with default CV data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
"test:watch": "vitest"
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^12.0.0",
|
"better-sqlite3": "^11.7.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^5.0.0",
|
"express": "^4.21.2",
|
||||||
"knex": "^3.1.0",
|
"knex": "^3.1.0",
|
||||||
"swagger-jsdoc": "^6.2.8",
|
"swagger-jsdoc": "^6.2.8",
|
||||||
"swagger-ui-express": "^5.0.1"
|
"swagger-ui-express": "^5.0.1"
|
||||||
|
|||||||
15
package.json
15
package.json
@@ -4,7 +4,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"sync-profile": "node scripts/sync-profile.mjs",
|
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"framer-motion": "^12.34.1",
|
"framer-motion": "^12.34.1",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^0.574.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^7.13.0"
|
"react-router-dom": "^7.13.0"
|
||||||
@@ -41,7 +40,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^20.4.2",
|
"@commitlint/cli": "^20.4.2",
|
||||||
"@commitlint/config-conventional": "^20.4.2",
|
"@commitlint/config-conventional": "^20.4.2",
|
||||||
"@eslint/js": "^10.0.0",
|
"@eslint/js": "^9.39.1",
|
||||||
"@lhci/cli": "^0.15.1",
|
"@lhci/cli": "^0.15.1",
|
||||||
"@playwright/test": "^1.58.2",
|
"@playwright/test": "^1.58.2",
|
||||||
"@semantic-release/changelog": "^6.0.3",
|
"@semantic-release/changelog": "^6.0.3",
|
||||||
@@ -52,24 +51,24 @@
|
|||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.0",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"@vitest/coverage-v8": "^4.0.18",
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
"autoprefixer": "^10.4.24",
|
"autoprefixer": "^10.4.24",
|
||||||
"better-sqlite3": "^12.6.2",
|
"better-sqlite3": "^12.6.2",
|
||||||
"bundlesize": "^0.18.2",
|
"bundlesize": "^0.18.2",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"eslint": "^10.0.0",
|
"eslint": "^9.39.1",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.0",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"globals": "^17.0.0",
|
"globals": "^16.5.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jsdom": "^28.1.0",
|
"jsdom": "^28.1.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"semantic-release": "^25.0.3",
|
"semantic-release": "^25.0.3",
|
||||||
"supertest": "^7.2.2",
|
"supertest": "^7.2.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vite": "^8.0.0",
|
"vite": "^7.3.1",
|
||||||
"vitest": "^4.0.18"
|
"vitest": "^4.0.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,37 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
],
|
||||||
|
"timezone": "Europe/Berlin",
|
||||||
|
"schedule": ["on monday before 10:00am"],
|
||||||
|
"prConcurrentLimit": 5,
|
||||||
|
"prHourlyLimit": 2,
|
||||||
|
"labels": ["dependencies"],
|
||||||
|
"commitMessagePrefix": "deps",
|
||||||
|
"commitMessageAction": "update",
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchManagers": ["npm"],
|
||||||
|
"rangeStrategy": "bump",
|
||||||
|
"semanticCommitType": "deps",
|
||||||
|
"semanticCommitScope": "{{packageFileDir}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["github-actions"],
|
||||||
|
"semanticCommitType": "ci",
|
||||||
|
"semanticCommitScope": "actions",
|
||||||
|
"groupName": "github-actions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchDepTypes": ["devDependencies"],
|
||||||
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
|
"automerge": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lockFileMaintenance": {
|
||||||
|
"enabled": true,
|
||||||
|
"schedule": ["on the first day of the month"]
|
||||||
|
},
|
||||||
|
"postUpdateOptions": ["npmDedupe"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Reads cv-data.json from the persons profile directory and writes it into:
|
|
||||||
* - src/lib/cv.json (frontend default / compile-time fallback)
|
|
||||||
* - backend/db/init.js (backend DB seed on first boot)
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node scripts/sync-profile.mjs [path/to/cv-data.json]
|
|
||||||
*
|
|
||||||
* Default source path: ~/workspace/persons/tuan-dat.tran/cv-data.json
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { readFileSync, writeFileSync } from 'fs';
|
|
||||||
import { resolve } from 'path';
|
|
||||||
import { homedir } from 'os';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import { dirname } from 'path';
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
const root = resolve(__dirname, '..');
|
|
||||||
|
|
||||||
const sourcePath = process.argv[2]
|
|
||||||
?? resolve(homedir(), 'workspace/persons/tuan-dat.tran/cv-data.json');
|
|
||||||
|
|
||||||
let data;
|
|
||||||
try {
|
|
||||||
data = JSON.parse(readFileSync(sourcePath, 'utf8'));
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Failed to read ${sourcePath}: ${e.message}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Write src/lib/cv.json
|
|
||||||
const cvJsonPath = resolve(root, 'src/lib/cv.json');
|
|
||||||
writeFileSync(cvJsonPath, JSON.stringify(data, null, 2) + '\n');
|
|
||||||
console.log(`wrote ${cvJsonPath}`);
|
|
||||||
|
|
||||||
// 2. Rewrite the seed block in backend/db/init.js
|
|
||||||
const initPath = resolve(root, 'backend/db/init.js');
|
|
||||||
const initSrc = readFileSync(initPath, 'utf8');
|
|
||||||
|
|
||||||
const seedJson = JSON.stringify(data, null, 8)
|
|
||||||
.split('\n')
|
|
||||||
.map((line, i) => i === 0 ? line : ' ' + line)
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
const updated = initSrc.replace(
|
|
||||||
/data: JSON\.stringify\([\s\S]*?\)\s*\}\);/,
|
|
||||||
`data: JSON.stringify(${seedJson})\n });`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updated === initSrc) {
|
|
||||||
console.error('Could not find seed block in backend/db/init.js — pattern mismatch');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(initPath, updated);
|
|
||||||
console.log(`wrote ${initPath}`);
|
|
||||||
|
|
||||||
console.log('done — rebuild Docker images and restart with a fresh volume to apply');
|
|
||||||
@@ -7,20 +7,25 @@ export default function Education() {
|
|||||||
const { education } = data;
|
const { education } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="education" className="py-12 px-6">
|
<section id="education" className="py-20 px-6">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<motion.h2
|
<motion.h2
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
whileInView={{ opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
||||||
>
|
>
|
||||||
Education
|
Ausbildung
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
{education.map((edu, index) => (
|
{education.map((edu, index) => (
|
||||||
<div
|
<motion.div
|
||||||
key={edu.id}
|
key={edu.id}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: index * 0.2 }}
|
||||||
className="bg-white rounded-xl p-6 shadow-sm border border-slate-100"
|
className="bg-white rounded-xl p-6 shadow-sm border border-slate-100"
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
@@ -39,7 +44,7 @@ export default function Education() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,20 +7,25 @@ export default function Experience() {
|
|||||||
const { experience } = data;
|
const { experience } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="experience" className="py-12 px-6">
|
<section id="experience" className="py-20 px-6">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<motion.h2
|
<motion.h2
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
whileInView={{ opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
||||||
>
|
>
|
||||||
Experience
|
Berufserfahrung
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{experience.map((exp, index) => (
|
{experience.map((exp, index) => (
|
||||||
<div
|
<motion.div
|
||||||
key={exp.id}
|
key={exp.id}
|
||||||
|
initial={{ opacity: 0, x: -20 }}
|
||||||
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: index * 0.2 }}
|
||||||
className="relative pl-8 pb-12 last:pb-0"
|
className="relative pl-8 pb-12 last:pb-0"
|
||||||
>
|
>
|
||||||
<div className="absolute left-0 top-2 w-4 h-4 rounded-full bg-primary-500 border-4 border-primary-100" />
|
<div className="absolute left-0 top-2 w-4 h-4 rounded-full bg-primary-500 border-4 border-primary-100" />
|
||||||
@@ -58,7 +63,7 @@ export default function Experience() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default function Hero() {
|
|||||||
const { personal } = data;
|
const { personal } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex items-center justify-center px-6 py-16">
|
<section className="min-h-screen flex items-center justify-center px-6 py-20">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
|||||||
@@ -7,14 +7,15 @@ export default function Projects() {
|
|||||||
const { projects } = data;
|
const { projects } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="projects" className="py-12 px-6 bg-white">
|
<section id="projects" className="py-20 px-6 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<motion.h2
|
<motion.h2
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
whileInView={{ opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
||||||
>
|
>
|
||||||
Projects
|
Projekte
|
||||||
</motion.h2>
|
</motion.h2>
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-6">
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
@@ -24,6 +25,10 @@ export default function Projects() {
|
|||||||
href={project.url}
|
href={project.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: index * 0.2 }}
|
||||||
whileHover={{ y: -4 }}
|
whileHover={{ y: -4 }}
|
||||||
className="group bg-slate-50 rounded-xl p-6 border border-slate-100 hover:border-primary-200 hover:shadow-md transition-all"
|
className="group bg-slate-50 rounded-xl p-6 border border-slate-100 hover:border-primary-200 hover:shadow-md transition-all"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,22 +5,17 @@ const skillIcons = {
|
|||||||
"Docker": "🐳",
|
"Docker": "🐳",
|
||||||
"Kubernetes": "☸️",
|
"Kubernetes": "☸️",
|
||||||
"Helm": "⛵",
|
"Helm": "⛵",
|
||||||
"ArgoCD": "🔄",
|
|
||||||
"Jenkins": "🔧",
|
"Jenkins": "🔧",
|
||||||
"Bitbucket Pipelines": "📘",
|
"Bitbucket": "📘",
|
||||||
|
"ArgoCD": "🔄",
|
||||||
"Git": "📝",
|
"Git": "📝",
|
||||||
"Azure": "☁️",
|
"Azure": "☁️",
|
||||||
"Azure AKS": "☁️",
|
"Azure Resource Manager": "🏗️",
|
||||||
"Ansible": "⚙️",
|
"Release Engineering": "🚀",
|
||||||
"ELK Stack": "📊",
|
"Linux Administration": "🐧",
|
||||||
"PostgreSQL": "🐘",
|
|
||||||
"MongoDB": "🍃",
|
|
||||||
"Rust": "🦀",
|
|
||||||
"Python": "🐍",
|
"Python": "🐍",
|
||||||
"Bash": "💻",
|
"Bash": "💻",
|
||||||
"P4": "🔬",
|
"Rust": "🦀"
|
||||||
"Intel Tofino": "🔬",
|
|
||||||
"Linux Administration": "🐧",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Skills() {
|
export default function Skills() {
|
||||||
@@ -28,11 +23,12 @@ export default function Skills() {
|
|||||||
const { skills } = data;
|
const { skills } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="skills" className="py-12 px-6 bg-white">
|
<section id="skills" className="py-20 px-6 bg-white">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<motion.h2
|
<motion.h2
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
whileInView={{ opacity: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
className="text-3xl font-bold text-slate-900 mb-12 text-center"
|
||||||
>
|
>
|
||||||
Skills
|
Skills
|
||||||
@@ -40,8 +36,12 @@ export default function Skills() {
|
|||||||
|
|
||||||
<div className="grid md:grid-cols-2 gap-8">
|
<div className="grid md:grid-cols-2 gap-8">
|
||||||
{Object.entries(skills).map(([category, items], catIndex) => (
|
{Object.entries(skills).map(([category, items], catIndex) => (
|
||||||
<div
|
<motion.div
|
||||||
key={category}
|
key={category}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: catIndex * 0.1 }}
|
||||||
className="bg-slate-50 rounded-xl p-6"
|
className="bg-slate-50 rounded-xl p-6"
|
||||||
>
|
>
|
||||||
<h3 className="text-lg font-semibold text-primary-600 mb-4">
|
<h3 className="text-lg font-semibold text-primary-600 mb-4">
|
||||||
@@ -51,6 +51,10 @@ export default function Skills() {
|
|||||||
{items.map((skill, i) => (
|
{items.map((skill, i) => (
|
||||||
<motion.span
|
<motion.span
|
||||||
key={skill}
|
key={skill}
|
||||||
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
|
whileInView={{ opacity: 1, scale: 1 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ delay: i * 0.05 }}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
className="px-3 py-1.5 bg-white rounded-lg text-sm text-slate-700 shadow-sm border border-slate-100 flex items-center gap-1.5 cursor-default"
|
className="px-3 py-1.5 bg-white rounded-lg text-sm text-slate-700 shadow-sm border border-slate-100 flex items-center gap-1.5 cursor-default"
|
||||||
>
|
>
|
||||||
@@ -59,7 +63,7 @@ export default function Skills() {
|
|||||||
</motion.span>
|
</motion.span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
189
src/lib/cv.json
189
src/lib/cv.json
@@ -2,185 +2,72 @@
|
|||||||
"personal": {
|
"personal": {
|
||||||
"name": "Tuan-Dat Tran",
|
"name": "Tuan-Dat Tran",
|
||||||
"title": "Junior DevOps Engineer",
|
"title": "Junior DevOps Engineer",
|
||||||
"intro": "DevOps Engineer and Homelab Enthusiast based in Essen, Germany. Primary owner of production SaaS platforms on Azure AKS, with a strong background in Linux, automation, and infrastructure engineering.",
|
"intro": "Passionierter DevOps Engineer mit Fokus auf Cloud-Infrastruktur, Container-Orchestrierung und automatisierte Deployment-Pipelines.",
|
||||||
"email": "tuan-dat.tran@dextradata.com",
|
"email": "tuan-dat.tran@example.com",
|
||||||
"github": "https://github.com/TuDatTr",
|
"github": "https://github.com/tuan-dat-tran",
|
||||||
"linkedin": "https://www.linkedin.com/in/tudattr/",
|
"linkedin": "https://linkedin.com/in/tuan-dat-tran",
|
||||||
"location": "Essen, Germany"
|
"location": "Deutschland"
|
||||||
},
|
},
|
||||||
"experience": [
|
"experience": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"role": "Junior DevOps Engineer",
|
"role": "Junior DevOps Engineer",
|
||||||
"company": "DextraData GRC Technologies GmbH",
|
"company": "Unternehmen",
|
||||||
"period": "2025 – present",
|
"period": "Seit 2025",
|
||||||
"type": "Full-time",
|
"type": "Vollzeit",
|
||||||
"description": "Primary owner of the GRASP GRC platform and Logipad aviation SaaS platform, running on Azure AKS and Rancher-managed Kubernetes clusters.",
|
"description": "Verantwortlich für CI/CD Pipelines, Container-Orchestrierung und Cloud-Infrastruktur auf Azure.",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Drives GitOps adoption with ArgoCD across production Kubernetes clusters",
|
"Automatisierte Deployments mit Jenkins und Bitbucket Pipelines",
|
||||||
"Manages CI/CD pipelines with Bitbucket Pipelines",
|
"Kubernetes Cluster Management mit Helm und ArgoCD",
|
||||||
"Leads incident response and on-call for production outages",
|
"Infrastructure as Code mit Azure Resource Manager"
|
||||||
"Reduced GRASP frontend pipeline from 30 to 16 minutes",
|
|
||||||
"Eliminated ~$2k/month Azure logging costs via workspace restructuring",
|
|
||||||
"Built internal AI tooling with LiteLLM and Ollama",
|
|
||||||
"Implemented Azure Container Registry mirroring to resolve Docker Hub rate limits"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"role": "Research Assistant",
|
"role": "Software Entwickler",
|
||||||
"company": "NCS Group, Universität Duisburg-Essen",
|
"company": "Verschiedene Unternehmen",
|
||||||
"period": "2021 – 2024",
|
"period": "2021 - 2025",
|
||||||
"type": "Part-time",
|
"type": "Teilzeit / Werkstudent / SHK",
|
||||||
"description": "University research group role covering infrastructure management, containerized systems, and networking experiments.",
|
"description": "Entwicklung und Maintenance von Softwarelösungen in verschiedenen Rollen.",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"Managed lab infrastructure: Ansible-automated servers, AWS accounts, and inventory systems",
|
"Backend-Entwicklung mit Python",
|
||||||
"Built and operated Dockerized and Kubernetes-based experiment environments",
|
"Linux System Administration",
|
||||||
"Co-authored a peer-reviewed publication at IEEE LCN 2023"
|
"Git Version Control und Release Engineering"
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"role": "Student Mentor",
|
|
||||||
"company": "Universität Duisburg-Essen",
|
|
||||||
"period": "2021 – 2022",
|
|
||||||
"type": "Volunteer",
|
|
||||||
"description": "Introduced groups of ~20 freshmen to their new academic environment each semester, providing organizational and technical guidance throughout their first year.",
|
|
||||||
"highlights": [
|
|
||||||
"Guided new students through academic orientation each semester",
|
|
||||||
"Provided technical and organizational support throughout the first year"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"role": "Software Engineer",
|
|
||||||
"company": "gefeba Engineering GmbH",
|
|
||||||
"period": "2018 – 2020",
|
|
||||||
"type": "Part-time",
|
|
||||||
"description": "Worked on the company's main product — a frame-based data exchange system for monitoring industrial machinery — and developed a real-time log visualization application.",
|
|
||||||
"highlights": [
|
|
||||||
"Developed features for a frame-based industrial data exchange system",
|
|
||||||
"Built a real-time log visualization application for industrial machinery",
|
|
||||||
"Stack: C#, Angular, Bootstrap, Entity Framework"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"role": "Student Council Member",
|
|
||||||
"company": "Universität Duisburg-Essen",
|
|
||||||
"period": "2016 – 2019",
|
|
||||||
"type": "Volunteer",
|
|
||||||
"description": "Participated in faculty committees and organized social events. Managed the faculty's IT infrastructure and provided support to fellow students.",
|
|
||||||
"highlights": [
|
|
||||||
"Managed faculty IT infrastructure (Linux, Networking, LaTeX)",
|
|
||||||
"Organized social events and participated in faculty committees",
|
|
||||||
"Provided subject-specific and organizational support to fellow students"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"role": "Software Engineer",
|
|
||||||
"company": "gefeba Engineering GmbH",
|
|
||||||
"period": "2013 – 2015",
|
|
||||||
"type": "Part-time",
|
|
||||||
"description": "Joined after a school internship. Worked on internal ERP projects, designed a mail traffic management tool, and contributed to master data management tooling.",
|
|
||||||
"highlights": [
|
|
||||||
"Developed internal ERP project features",
|
|
||||||
"Designed a tool for managing project-related mail traffic",
|
|
||||||
"Contributed to internal master data management tooling",
|
|
||||||
"Stack: C#, HTML, CSS, JavaScript"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"skills": {
|
"skills": {
|
||||||
"Container & Orchestration": [
|
"Container & Orchestration": ["Docker", "Kubernetes", "Helm"],
|
||||||
"Kubernetes",
|
"CI/CD & GitOps": ["Jenkins", "Bitbucket", "ArgoCD", "Git"],
|
||||||
"Docker",
|
"Cloud": ["Azure"],
|
||||||
"Helm",
|
"Infrastructure as Code": ["Azure Resource Manager"],
|
||||||
"ArgoCD"
|
"Release Engineering": ["Release Engineering"],
|
||||||
],
|
"Operating Systems": ["Linux Administration"],
|
||||||
"CI/CD": [
|
"Programming": ["Python", "Bash", "Rust"]
|
||||||
"Bitbucket Pipelines",
|
|
||||||
"Git"
|
|
||||||
],
|
|
||||||
"Cloud": [
|
|
||||||
"Azure",
|
|
||||||
"Azure AKS",
|
|
||||||
"Azure App Gateway",
|
|
||||||
"Azure Entra ID"
|
|
||||||
],
|
|
||||||
"Infrastructure as Code": [
|
|
||||||
"Ansible"
|
|
||||||
],
|
|
||||||
"Observability": [
|
|
||||||
"ELK Stack",
|
|
||||||
"Checkmk",
|
|
||||||
"UptimeRobot"
|
|
||||||
],
|
|
||||||
"Databases": [
|
|
||||||
"PostgreSQL",
|
|
||||||
"MongoDB",
|
|
||||||
"CouchDB"
|
|
||||||
],
|
|
||||||
"Programming": [
|
|
||||||
"Rust",
|
|
||||||
"Python",
|
|
||||||
"Bash"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"education": [
|
"education": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"degree": "B.Sc. Systems Engineering",
|
"degree": "Bachelor Informatik",
|
||||||
"institution": "Universität Duisburg-Essen",
|
"institution": "Universität",
|
||||||
"period": "2015 – present",
|
"period": "Laufend",
|
||||||
"status": "Bachelor thesis ongoing"
|
"status": "Bachelorarbeit offen"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"name": "Ethereum Smart Contract Fuzzer",
|
"name": "Projekt 1",
|
||||||
"description": "A fuzzer for Ethereum smart contracts built as part of the bachelor thesis project.",
|
"description": "Beschreibung folgt",
|
||||||
"url": "#",
|
"url": "#",
|
||||||
"tech": [
|
"tech": ["Docker", "Kubernetes", "ArgoCD"]
|
||||||
"Rust"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"name": "Homelab",
|
"name": "Projekt 2",
|
||||||
"description": "Ansible-managed homelab running on Debian Linux with Raspberry Pi, Mikrotik networking, Pihole, and self-hosted services.",
|
"description": "Beschreibung folgt",
|
||||||
"url": "https://git.tudattr.dev/tudattr/ansible",
|
"url": "#",
|
||||||
"tech": [
|
"tech": ["Azure", "ARM", "Jenkins"]
|
||||||
"Ansible",
|
|
||||||
"Kubernetes",
|
|
||||||
"Debian Linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"name": ".dotfiles",
|
|
||||||
"description": "Configuration files for daily-use tools providing a reproducible baseline for any personal ArchLinux system, including NeoVim (LazyVim), Zellij, and k9s.",
|
|
||||||
"url": "https://github.com/TuDatTr/dotfiles",
|
|
||||||
"tech": [
|
|
||||||
"Zsh",
|
|
||||||
"NeoVim",
|
|
||||||
"Zellij",
|
|
||||||
"ArchLinux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"name": "tudattr.dev",
|
|
||||||
"description": "Full-stack WebAssembly personal website built with the Rust-based Dioxus framework and Tailwind CSS.",
|
|
||||||
"url": "https://www.tudattr.dev",
|
|
||||||
"tech": [
|
|
||||||
"Rust",
|
|
||||||
"Dioxus",
|
|
||||||
"Tailwind CSS",
|
|
||||||
"WebAssembly"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user