5 Commits
master ... dev

Author SHA1 Message Date
Tuan-Dat Tran
fef5d771ba feat(dioxus): bump
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2025-11-04 01:00:50 +01:00
Tuan-Dat Tran
1284dd9dd6 chore: recompile
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2025-11-03 23:09:31 +01:00
Tuan-Dat Tran
538c99166f chore(deps): update rust dependencies
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2025-08-15 08:18:54 +02:00
Tuan-Dat Tran
6e3db0294f Add favorite tools
Some checks failed
Build Docker Image / Build (push) Has been cancelled
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2025-04-06 18:23:26 +02:00
Tuan-Dat Tran
a1fd3ea358 feat(badge): Streamlined badging
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2025-04-06 17:11:44 +02:00
20 changed files with 2347 additions and 1853 deletions

View File

@@ -1,18 +0,0 @@
name: Build Docker Image
on:
push:
branches:
- main
- dev
- cicd
jobs:
build:
name: Build
runs-on: [ubuntu-latest, aya01]
steps:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
tags: tudattr/athome:latest

3579
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "athome"
version = "0.4.1"
version = "0.2.0"
authors = ["Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>"]
edition = "2021"
@@ -8,27 +8,17 @@ edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
dioxus = { version = "0.6", features = ["fullstack", "router"] }
dioxus = { version = "0.7.0", features = ["fullstack", "router"] }
# Debug
tracing = "0.1.40"
dioxus-logger = "0.6.0"
dioxus-i18n = "0.3.0"
dioxus-logger = "0.7.0"
dioxus-i18n = { git = "https://github.com/Kannen/dioxus-i18n/", branch = "main"}
tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] }
[features]
default = []
default = ["web"]
web = ["dioxus/web"]
desktop = ["dioxus/desktop"]
mobile = ["dioxus/mobile"]
server = ["dioxus/server"]
[profile]
[profile.wasm-dev]
inherits = "dev"
opt-level = 1
[profile.server-dev]
inherits = "dev"
[profile.android-dev]
inherits = "dev"

View File

@@ -24,4 +24,4 @@ title = "Tuan-Dat Tran"
reload_html = true
# which files or dirs will be watcher monitoring
watch_path = ["src", "assets"]
watch_path = ["src", "assets", "languages"]

View File

@@ -19,7 +19,10 @@ ENV PATH="/.cargo/bin:$PATH"
# Create the final bundle folder. Bundle always executes in release mode with optimizations enabled
RUN dx bundle --platform web
FROM chef AS runtime
FROM debian:bookworm-slim AS runtime
# Install ca-certificates for HTTPS requests if the server makes any outgoing calls
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/dx/athome/release/web/ /usr/local/app
# set our port and make sure to listen for all connections

View File

@@ -723,18 +723,10 @@ video {
max-width: 36rem;
}
.flex-auto {
flex: 1 1 auto;
}
.flex-grow {
flex-grow: 1;
}
.grow {
flex-grow: 1;
}
.cursor-pointer {
cursor: pointer;
}
@@ -747,14 +739,6 @@ video {
flex-wrap: wrap;
}
.content-center {
align-content: center;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
@@ -771,10 +755,6 @@ video {
justify-content: space-between;
}
.justify-stretch {
justify-content: stretch;
}
.gap-2 {
gap: 0.5rem;
}
@@ -801,10 +781,6 @@ video {
margin-bottom: calc(2rem * var(--tw-space-y-reverse));
}
.self-stretch {
align-self: stretch;
}
.rounded {
border-radius: 0.25rem;
}
@@ -1071,10 +1047,6 @@ video {
text-transform: uppercase;
}
.italic {
font-style: italic;
}
.leading-none {
line-height: 1;
}

View File

@@ -1,7 +1,6 @@
headers_home = Home
headers_cv = Lebenslauf
headers_publications_projects = Artikel/Projekte
headers_consulting = Consulting
headers_about = Impressum
headers_language_buttons_english = 🇬🇧 Englisch
headers_language_buttons_german = 🇩🇪 Deutsch
@@ -12,13 +11,15 @@ home_card_text =
Willkommen auf meiner kleinen Webseite im World Wide Web.
Mein Name ist Tuan und ich bin Linux-Bastler, IT-Security Enthusiast und IT-Automatisierer aus Leidenschaft.
home_card_contact_button = Get in touch.
cv_introduction_title = DevOps Engineer | Homelab Enthusiast
cv_introduction_0 =
DevOps-Engineer und Softwareentwickler mit starkem akademischen Hintergrund
DevOps Engineer und Softwareentwickler mit starkem akademischen Hintergrund
in Netzwerktechnologien und Softwareentwicklung. Spezialisiert auf Kubernetes,
Ansible, Azure und moderne Cloud-Technologien. Praxisnahe
Ansible, Azure und modernen Cloud-Technologien. Praxisnahe
Forschungserfahrung in Software-Defined Networking, 5G und Maschinellem Lernen.
Leidenschaft für effiziente IT-Infrastrukturen, Automatisierung und
innovative Softwarelösungen.
cv_introduction_tools = Lieblingstools:
cv_workexperience_title = Berufserfahrung
cv_workexperience_se1_gefeba_title = Software Entwickler @ gefeba Engineering GmbH
cv_workexperience_se1_gefeba_time = 2013 - 2015
@@ -60,21 +61,19 @@ cv_workexperience_ra_ude_description = Während meiner Tätigkeit bei der
aufgebaut und verwaltet.
cv_workexperience_dd_devops_title = DevOps Engineer @ DextraData
cv_workexperience_dd_devops_time = 2025 - Jetzt
cv_workexperience_dd_devops_description = Als DevOps Engineer bei DextraData
bin ich Teil eines horizontal strukturierten DevOps-Teams und verantwortlich
für die Verwaltung und Optimierung sowohl cloudbasierter als auch On-Premise-
Infrastrukturen, die unsere Single-Tenant-SaaS-Lösungen hosten. Meine Aufgabe
besteht nicht nur darin, diese Umgebungen zu warten und zu verbessern, sondern
auch Automatisierung und Effizienz durch moderne DevOps-Praktiken
voranzutreiben. Ich war maßgeblich an der Umstellung unserer Kubernetes-
basierten Deployments von manuellen Prozessen auf einen stärker
automatisierten und skalierbaren Ansatz beteiligt, indem ich Infrastructure as
Code mit Azure Resource Manager und Ansible eingesetzt habe.
cv_workexperience_dd_devops_description = Als DevOps Engineer war ich
verantwortlich für das Design, die Implementierung und die Wartung
skalierbarer Infrastrukturlösungen in verschiedenen SaaS-Produktumgebungen.
Dies umfasste die Standardisierung und Optimierung von CI/CD-Pipelines sowie
den Aufbau robuster Monitoring-Frameworks zur Gewährleistung hoher
Verfügbarkeit und Performance. Meine Arbeit konzentrierte sich auf einen
"Shift-Left"-Ansatz, der Entwicklungsteams durch Self-Service-Funktionen und
optimierte operative Workflows befähigte, sowie auf die Verbesserung der
Systemzuverlässigkeit und -sicherheit.
cv_socials_title = Profile
cv_education_title = Bildungsweg
cv_education_bachelor_title = BSc Angewandte Informatik - Systems Engineering
cv_education_bachelor_time = 2015 - jetzt
cv_education_bachelor_description = ""
cv_skills_title = Fähigkeiten
cv_skills_devops_title = DevOps
cv_skills_devops_ansible = Ansible
@@ -110,7 +109,6 @@ publications_projects_publications_rpm_description = In diesem Artikel stellen
publications_projects_publications_iot_fuzzers_title = Overview of IoT Fuzzing Techniques
publications_projects_publications_iot_fuzzers_authors = Tuan-Dat Tran
publications_projects_publications_iot_fuzzers_conference = Seminar
publications_projects_publications_iot_fuzzers_url = https://git.tudattr.dev/AISE/seminar/src/branch/main/paper.pdf
publications_projects_publications_iot_fuzzers_description = In dieser Arbeit
vergleichen wir Methoden, die speziell von IoT Fuzzern genutzt werden um die
von IoT Geräten stammenden Herausforderungen und Einschränkungen zu umgehen.
@@ -118,13 +116,11 @@ publications_projects_projects_title = Projekte
publications_projects_projects_bpba_title = Unbenannter Ethereum Smart Contract Fuzzer
publications_projects_projects_bpba_authors = Tuan-Dat Tran
publications_projects_projects_bpba_kind = Bachelorprojekt/Bachelorarbeit
publications_projects_projects_bpba_url = https://git.ude-syssec.de/uni-due-syssec/students/2022_tuan-dat_tran_libAFLEVMFuzzer/ethfuzz/
publications_projects_projects_bpba_description = In diesem aktuell laufendem
Projekt entwickle ich einen Ethereum Smart Contract Fuzzer. Mehr Infos folgen...
publications_projects_projects_dotfiles_title = .dotfiles
publications_projects_projects_dotfiles_authors = Tuan-Dat Tran
publications_projects_projects_dotfiles_kind = Personal
publications_projects_projects_dotfiles_url = https://git.tudattr.dev/tudattr/dotfiles
publications_projects_projects_dotfiles_description = dotfiles ist ein
umgangssprachlicher Begriff, der normalerweise für Konfigurationsdateien in
Linux-basierten Systemen verwendet wird. Meine Dotfiles enthalten
@@ -136,7 +132,6 @@ publications_projects_projects_dotfiles_description = dotfiles ist ein
publications_projects_projects_homelab_title = Homelab
publications_projects_projects_homelab_authors = Tuan-Dat Tran
publications_projects_projects_homelab_kind = Personal
publications_projects_projects_homelab_url = https://git.tudattr.dev/tudattr/ansible
publications_projects_projects_homelab_description = Ansible ist ein
Automatisierungs-Werkzeug, die eine automatische Maschinenbereitstellung,
Konfigurationsverwaltung und Anwendungsbereitstellung ermöglicht. Ich
@@ -145,15 +140,14 @@ publications_projects_projects_homelab_description = Ansible ist ein
publications_projects_projects_athome_title = Diese Website
publications_projects_projects_athome_authors = Tuan-Dat Tran
publications_projects_projects_athome_kind = Personal
publications_projects_projects_athome_url = /#
publications_projects_projects_athome_description = Diese Website ist eine mit
dem auf Rust basiertem Dioxus Framwork und TailwindCSS gebaute Full Stack
WASM Website, die sowohl zum Auffrischen von Web Themen, sowie als Rust
Hobbyprojekt dient.
impressum_off = Impressum anzeigen
impressum_on = Impressum
component_under_construction = Diese Seite befindet sich gerade im Aufbau
footer_year = © 2025
footer_name = Tuan-Dat Tran
footer_name = Tuan-Dat Tran
footer_rights = . All Rights Reserved.
footer_contact = Kontakt
link_opens_new_tab = (öffnet in neuem Tab)

View File

@@ -1,7 +1,6 @@
headers_home = Home
headers_cv = Résumé
headers_publications_projects = Publications/Projects
headers_consulting = Consulting
headers_about = About
headers_language_buttons_english = 🇬🇧 English
headers_language_buttons_german = 🇩🇪 German
@@ -12,13 +11,16 @@ home_card_text =
Welcome to my little place on the internet
My name is Tuan and I'm passionate about Linux, system security, automation, performance tweaking and all things tech.
home_card_contact_button = Get in touch.
cv_introduction_title = DevOps Engineer | Homelab Enthusiast
cv_introduction_0 =
A results-driven DevOps Engineer with a career in technology spanning over 8 years. My journey began with foundational part-time roles in software development (C#, Python) and academic research during my university studies. Now, I apply this deep technical understanding to my full-time DevOps role, where I specialize in building and maintaining scalable, high-availability SaaS infrastructure using Kubernetes, Azure, ArgoCD, and Ansible.
DevOps Engineer and Software Developer with a strong academic background in
networking technologies and software development. Specialized in Kubernetes,
Ansible, Azure, and modern cloud technologies. Hands-on research experience
in Software-Defined Networking, 5G, and Machine Learning. Passionate about
efficient IT infrastructures, automation, and innovative software
solutions.
cv_introduction_tools = Favorite Tools:
cv_workexperience_title = Work Experience
cv_workexperience_se1_gefeba_title = Software Engineer @ gefeba Engineering GmbH
cv_workexperience_se1_gefeba_time = 2013 - 2015
@@ -56,20 +58,18 @@ cv_workexperience_ra_ude_description = While working at the Network
infractructure, inventory system and online presence.
cv_workexperience_dd_devops_title = DevOps Engineer @ DextraData
cv_workexperience_dd_devops_time = 2025 - now
cv_workexperience_dd_devops_description = As a DevOps Engineer at DextraData,
I am part of a horizontally structured DevOps team, responsible for managing
and optimizing both cloud and on-premise infrastructure that hosts our
single-tenant SaaS solutions. My role involves not only maintaining and
improving these environments but also driving automation and efficiency
through modern DevOps practices. I have been actively involved in
transitioning our Kubernetes-based deployments from manual processes to a
more automated and scalable approach by leveraging Infrastructure as Code
with Azure Resource Manager and Ansible.
cv_workexperience_dd_devops_description = As a DevOps Engineer, I was
responsible for designing, implementing, and maintaining scalable
infrastructure solutions across multiple SaaS product environments. This
involved standardizing and optimizing CI/CD pipelines and establishing
robust monitoring frameworks to ensure high availability and performance.
My work focused on a "shift-left" approach, empowering development teams
with self-service capabilities and streamlined operational workflows as
well as enhancement of system reliability and security.
cv_socials_title = Socials
cv_education_title = Education
cv_education_bachelor_title = BSc Systems Engineering
cv_education_bachelor_time = 2015 - now
cv_education_bachelor_description = ""
cv_skills_title = Skills
cv_skills_devops_title = DevOps
cv_skills_devops_ansible = Ansible
@@ -105,7 +105,6 @@ publications_projects_publications_rpm_description = In this paper, we present
publications_projects_publications_iot_fuzzers_title = Overview of IoT Fuzzing Techniques
publications_projects_publications_iot_fuzzers_authors = Tuan-Dat Tran
publications_projects_publications_iot_fuzzers_conference = Seminar
publications_projects_publications_iot_fuzzers_url = https://git.tudattr.dev/AISE/seminar/src/branch/main/paper.pdf
publications_projects_publications_iot_fuzzers_description = In this paper, we
are comparing techniques used by IoT fuzzers to circumvent the challenges
presented by IoT devices and the constraints of the solutions proposed by the
@@ -114,12 +113,10 @@ publications_projects_projects_title = Projects
publications_projects_projects_bpba_title = Undisclosed Ethereum Smart Contract Fuzzer
publications_projects_projects_bpba_authors = Tuan-Dat Tran
publications_projects_projects_bpba_kind = Bachelor Project/Bachelor Thesis
publications_projects_projects_bpba_url = https://git.ude-syssec.de/uni-due-syssec/students/2022_tuan-dat_tran_libAFLEVMFuzzer/ethfuzz/
publications_projects_projects_bpba_description = In this ongoing project I am building an Ethereum Smart Contract Fuzzer. More info will follow.
publications_projects_projects_dotfiles_title = .dotfiles
publications_projects_projects_dotfiles_authors = Tuan-Dat Tran
publications_projects_projects_dotfiles_kind = Personal
publications_projects_projects_dotfiles_url = https://git.tudattr.dev/tudattr/dotfiles
publications_projects_projects_dotfiles_description = dotfiles is a slang term
usually used for configuration files in Linux based systems. My dotfiles
contain configurations for tools I frequently use as well as a documentation
@@ -129,7 +126,6 @@ publications_projects_projects_dotfiles_description = dotfiles is a slang term
publications_projects_projects_homelab_title = Homelab
publications_projects_projects_homelab_authors = Tuan-Dat Tran
publications_projects_projects_homelab_kind = Personal
publications_projects_projects_homelab_url = https://git.tudattr.dev/tudattr/ansible
publications_projects_projects_homelab_description = Ansible is a automation
tool which allows for automatic provisioning, configuration management and
application deployment. I use ansible to set up my homelab, which serves as a
@@ -137,15 +133,14 @@ publications_projects_projects_homelab_description = Ansible is a automation
publications_projects_projects_athome_title = This Website
publications_projects_projects_athome_authors = Tuan-Dat Tran
publications_projects_projects_athome_kind = Personal
publications_projects_projects_athome_url = /#
publications_projects_projects_athome_description = This website is a
full-stack WASM site built using the Rust-based Dioxus framework and
TailwindCSS. It serves both as a way to refresh web development topics and as
a Rust hobby project.
impressum_off = Show Impressum
impressum_on = Impressum
component_under_construction = This page is currently under construction
footer_year = © 2025
footer_name = Tuan-Dat Tran
footer_name = Tuan-Dat Tran
footer_rights = . All Rights Reserved.
footer_contact = Contact
link_opens_new_tab = (opens in a new tab)

24
package-lock.json generated
View File

@@ -4,6 +4,9 @@
"requires": true,
"packages": {
"": {
"dependencies": {
"caniuse-lite": "^1.0.30001753"
},
"devDependencies": {
"tailwindcss": "^3.4.3"
}
@@ -227,6 +230,26 @@
"node": ">= 6"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001753",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz",
"integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "CC-BY-4.0"
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -782,6 +805,7 @@
"url": "https://github.com/sponsors/ai"
}
],
"peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",

View File

@@ -1,5 +1,8 @@
{
"devDependencies": {
"tailwindcss": "^3.4.3"
},
"dependencies": {
"caniuse-lite": "^1.0.30001753"
}
}

View File

@@ -1,3 +1,3 @@
[toolchain]
channel = "1.85.0"
channel = "1.91.0"
components = ["rustfmt", "clippy", "rust-analyzer"]

61
scripts/check_i18n.py Normal file
View File

@@ -0,0 +1,61 @@
import os
import re
def check_i18n_keys(project_root):
src_dir = os.path.join(project_root, "src")
languages_dir = os.path.join(project_root, "languages")
rust_key_regex = re.compile(r't!\("([a-zA-Z0-9_.]+)"\)')
ftl_key_regex = re.compile(r"^([a-zA-Z0-9_.-]+)\s*=")
used_keys = set()
defined_keys = set()
# Extract keys from Rust files
for root, _, files in os.walk(src_dir):
for file in files:
if file.endswith(".rs"):
file_path = os.path.join(root, file)
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
for match in rust_key_regex.finditer(content):
used_keys.add(match.group(1))
# Extract keys from FTL files
for root, _, files in os.walk(languages_dir):
for file in files:
if file.endswith(".ftl"):
file_path = os.path.join(root, file)
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
match = ftl_key_regex.match(line)
if match:
defined_keys.add(match.group(1))
print("--- i18n Key Check Report ---")
missing_keys = used_keys - defined_keys
if not missing_keys:
print("✅ No missing translation keys found in FTL files.")
else:
print(
"❌ Missing translation keys (used in code but not defined in FTL files):"
)
for key in sorted(list(missing_keys)):
print(f" - {key}")
unused_keys = defined_keys - used_keys
if not unused_keys:
print("✅ No unused translation keys found in FTL files.")
else:
print("⚠️ Unused translation keys (defined in FTL files but not used in code):")
for key in sorted(list(unused_keys)):
print(f" - {key}")
print("-----------------------------")
if __name__ == "__main__":
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
check_i18n_keys(project_root)

View File

@@ -242,3 +242,73 @@ pub fn Urling(prop: UrlingProp) -> Element {
}
}
}
#[component]
pub fn BadgeList(list: Vec<String>) -> Element {
rsx!(
ul {
class: "flex flex-wrap gap-2",
for (index, value) in list.iter().enumerate() {
li { key: "{index}", RandomBadge { text: "{value}"} }
}
}
)
}
#[component]
fn RandomBadge(text: String) -> Element {
let badge_color = random_badge_color(text.len());
rsx! {
span {
class:"text-xs font-medium me-2 px-2.5 py-0.5 rounded {badge_color}",
"{text}"
}
}
}
fn random_badge_color(seed: usize) -> String {
let colors = [
"bg-blue-900 text-blue-300",
"bg-gray-700 text-gray-300",
"bg-red-900 text-red-300",
"bg-green-900 text-green-300",
"bg-yellow-900 text-yellow-300",
"bg-indigo-900 text-indigo-300",
"bg-purple-900 text-purple-300",
"bg-pink-900 text-pink-300",
];
colors[seed % colors.len()].to_string()
}
#[derive(PartialEq, Props, Clone)]
pub struct AccessibleLinkProps {
to: String,
#[props(default = "".to_string())]
class: String,
#[props(default = false)]
new_tab: bool,
children: Element,
}
#[component]
pub fn AccessibleLink(props: AccessibleLinkProps) -> Element {
let mut aria_label = String::new();
let mut rel = String::new();
if props.new_tab {
aria_label = t!("link_opens_new_tab").to_string();
rel = "noopener noreferrer".to_string();
}
rsx! {
Link {
to: "{props.to}",
class: "{props.class}",
new_tab: props.new_tab,
rel: "{rel}",
aria_label: "{aria_label}",
{props.children}
}
}
}

View File

@@ -1,7 +1,7 @@
use dioxus::prelude::*;
use dioxus_i18n::t;
use crate::components::{H4, HR};
use crate::components::*;
#[component]
pub fn CV() -> Element {
@@ -13,7 +13,7 @@ pub fn CV() -> Element {
img {
class: "rounded-full w-24 h-24",
alt: "headshot",
src: asset!("./assets/pictures/headshot.webp")
src: asset!("/assets/pictures/headshot.webp")
}
Introduction {},
Socials {}
@@ -33,8 +33,18 @@ pub fn CV() -> Element {
fn Introduction() -> Element {
rsx! {
div {
class: "flex",
class: "flex-col",
h6 { class: "text-lg font-semibold text-white", { t!("cv_introduction_title") } },
P { { t!("cv_introduction_0") } },
P {
{ t!("cv_introduction_tools") },
" ",
AccessibleLink { new_tab: true, to: "https://www.lazyvim.org/", "NeoVim" },
", ",
AccessibleLink { new_tab: true, to: "https://zellij.dev/", "Zellij" },
", ",
AccessibleLink { new_tab: true, to: "https://k9scli.io/", "k9s" }
},
},
}
}
@@ -47,7 +57,7 @@ fn WorkExperience() -> Element {
ol {
class:"relative border-s border-gray-700",
CVEntry {time: t!("cv_workexperience_dd_devops_time"), title: t!("cv_workexperience_dd_devops_title"),
technologies: vec!["Kubenertes".to_string(), "ArgoCD".to_string(), "Ansible".to_string(), "Azure".to_string(), "Docker".to_string(), "ELK".to_string()],
technologies: vec!["Kubernetes".to_string(), "ArgoCD".to_string(), "Ansible".to_string(), "Azure".to_string(), "Elastic Stack".to_string(), "Helm".to_string()],
description: t!("cv_workexperience_dd_devops_description")
},
CVEntry {time: t!("cv_workexperience_ra_ude_time"), title: t!("cv_workexperience_ra_ude_title"),
@@ -186,44 +196,13 @@ fn CVEntry(props: CVEntryProps) -> Element {
div { class:"absolute w-3 h-3 rounded-full mt-1.5 -start-1.5 border border-gray-900 bg-gray-700"},
time { class:"mb-1 text-sm font-normal leading-none text-gray-500", "{props.time}"},
h6 { class: "text-lg font-semibold text-white", "{props.title}"}
ul {
class: "flex flex-wrap gap-2",
for (index, value) in props.technologies.iter().enumerate() {
li { key: "{index}", RandomBadge { text: "{value}"} }
}
}
BadgeList{ list: props.technologies }
p { class:"text-base font-normal text-gray-400", "{props.description}"},
{props.children}
}
}
}
#[component]
fn RandomBadge(text: String) -> Element {
let badge_color = random_badge_color(text.len());
rsx! {
span {
class:"text-xs font-medium me-2 px-2.5 py-0.5 rounded {badge_color}",
"{text}"
}
}
}
fn random_badge_color(seed: usize) -> String {
let colors = [
"bg-blue-900 text-blue-300",
"bg-gray-700 text-gray-300",
"bg-red-900 text-red-300",
"bg-green-900 text-green-300",
"bg-yellow-900 text-yellow-300",
"bg-indigo-900 text-indigo-300",
"bg-purple-900 text-purple-300",
"bg-pink-900 text-pink-300",
];
colors[seed % colors.len()].to_string()
}
fn Socials() -> Element {
rsx! {
div {
@@ -231,7 +210,7 @@ fn Socials() -> Element {
H4 { { t!("cv_socials_title") } },
div {
class: "flex justify-center items-center space-x-4",
P { Link { to:"https://www.linkedin.com/in/tudattr/", class:"hover:underline", new_tab: true, img { class: "h-8", src:asset!("./assets/pictures/LI-Bug.svg.original.svg"), alt:"LinkedIn Logo" } }},
P { AccessibleLink { to:"https://www.linkedin.com/in/tudattr/", class:"hover:underline", new_tab: true, img { class: "h-8", src:asset!("/assets/pictures/LI-Bug.svg.original.svg"), alt:"LinkedIn Logo" } }},
}
}
}

View File

@@ -1,4 +1,4 @@
use crate::components::{Card, P};
use crate::components::{AccessibleLink, Card, P};
use dioxus::prelude::*;
use dioxus_i18n::t;
@@ -10,7 +10,7 @@ pub fn Home() -> Element {
Card {
name: t!("home_card_name"),
gender: t!("home_card_gender"),
picture: asset!("./assets/pictures/headshot.webp"),
picture: asset!("/assets/pictures/headshot.webp"),
div {
class: "py-4",
div {
@@ -20,9 +20,10 @@ pub fn Home() -> Element {
}
},
},
Link {
AccessibleLink {
to: "mailto:tuan-dat.tran@tudattr.dev",
class: "text-gray-900 bg-gradient-to-br from-green-400 to-blue-600 group-hover:from-green-400 group-hover:to-blue-600 hover:text-white rounded-full shadow-lg py-4 px-4",
new_tab: true,
{ t!("home_card_contact_button") }
}
},

View File

@@ -1,59 +1,30 @@
use dioxus::prelude::*;
use dioxus_i18n::t;
use crate::components::{H1, HR, P};
#[component]
pub fn Impressum() -> Element {
let mut impressum = use_signal(Vec::<String>::new);
let mut contact = use_signal(Vec::<String>::new);
rsx! {
div {
div {
class: "flex flex-col items-center",
button {
onclick: move |_| async move {
if let Ok(data) = get_impressum().await {
impressum.set(data.clone());
}
if let Ok(data) = get_contact().await {
contact.set(data.clone());
}
},
H1 { { t!("impressum_on") } },
},
H1 { "Impressum" },
},
HR{},
div {
class: "flex flex-col items-center",
for line in impressum() {
P { {line} }
}
P { {"Tuan-Dat Tran"} },
P { {"c/o AutorenServices.de"} },
P { {"Birkenallee 24"} },
P { {"36037 Fulda"} },
}
if !impressum.read().is_empty() { HR{} },
HR{},
div {
class: "flex flex-col items-center",
for line in contact() {
P { {line} }
}
P { {"tuan-dat.tran(at)tudattr(dot)dev"} },
P { {"+49 17(six) 83(four)683(eight)8"} },
}
}
}
}
#[server(GetServerData)]
async fn get_impressum() -> Result<Vec<String>, ServerFnError> {
Ok(vec![
"Tuan-Dat Tran".to_string(),
"c/o AutorenServices.de".to_string(),
"Birkenallee 24".to_string(),
"36037 Fulda".to_string(),
])
}
async fn get_contact() -> Result<Vec<String>, ServerFnError> {
Ok(vec![
"tuan-dat.tran(at)tudattr(dot)dev".to_string(),
"+49 17(six) 83(four)683(eight)8".to_string(),
])
}

View File

@@ -12,7 +12,9 @@ pub fn Footer() -> Element {
span {
class:"text-sm sm:text-center text-gray-400",
{ t!("footer_year") },
" ",
a { href: "#", class: "hover:underline", { t!("footer_name") }},
" ",
{ t!("footer_rights") }
}
ul {

View File

@@ -1,5 +1,5 @@
use dioxus::prelude::*;
use dioxus_i18n::{prelude::i18n, t, unic_langid::langid};
use dioxus_i18n::{prelude::*, t, unic_langid::langid};
use crate::Route;
@@ -15,7 +15,7 @@ pub fn Header() -> Element {
Link {
to: Route::Home {},
class: "rounded-md shadow-sm",
img { src:asset!("./assets/pictures/ClackCat_t.webp"), class:"rounded-full h-8", alt:"TuDatTr Logo" },
img { src:asset!("/assets/pictures/ClackCat_t.webp"), class:"rounded-full h-8", alt:"TuDatTr Logo" },
},
},
li { HeaderLink { url: Route::Home {}, text: t!("headers_home")} },

View File

@@ -3,13 +3,11 @@
use components::H1;
use dioxus::prelude::*;
use dioxus_i18n::prelude::use_init_i18n;
use dioxus_i18n::prelude::I18nConfig;
use dioxus_i18n::prelude::Locale;
use dioxus_i18n::unic_langid::langid;
use dioxus_i18n::{prelude::*, unic_langid::langid};
use layout::footer::Footer;
use layout::header::Header;
use tracing::Level;
use tracing::{Level, info};
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
pub mod components;
mod cv;
@@ -35,32 +33,42 @@ pub enum Route {
PublicationsProjects {},
#[route("/resume")]
CV {},
#[route("/health")]
Health {},
#[end_layout]
#[route("/:..route")]
PageNotFound { route: Vec<String> },
}
fn main() {
dioxus_logger::init(Level::DEBUG).expect("failed to init logger");
LaunchBuilder::new().launch(App)
// Configure tracing to output JSON logs
tracing_subscriber::registry()
.with(EnvFilter::from_default_env().add_directive(Level::INFO.into()))
.with(fmt::layer().json())
.init();
info!("Starting Dioxus application...");
LaunchBuilder::new().launch(App);
}
#[component]
fn Health() -> Element {
rsx! { "OK" }
}
fn App() -> Element {
use_init_i18n(|| {
I18nConfig::new(langid!("en-GB"))
.with_locale((langid!("de-DE"), include_str!("../languages/de-DE.ftl")))
.with_locale(Locale::new_static(
langid!("en-GB"),
include_str!("./languages/en-GB.ftl"),
))
.with_locale(Locale::new_static(
langid!("de-DE"),
include_str!("./languages/de-DE.ftl"),
include_str!("../languages/en-GB.ftl"),
))
});
rsx! {
document::Link { rel: "stylesheet", href: asset!("./assets/tailwind.css") }
document::Link { rel: "icon", href: asset!("./assets/favicon.ico") }
document::Link { rel: "stylesheet", href: asset!("/assets/tailwind.css") }
document::Link { rel: "icon", href: asset!("/assets/favicon.ico") }
meta {
name: "description",
content: "Visit Tuan-Dat Tran's website for his CV, publications, projects, and consulting services. Connect for collaboration.",

View File

@@ -1,7 +1,7 @@
use dioxus::prelude::*;
use dioxus_i18n::t;
use crate::components::{Bolding, H1, HR};
use crate::components::{BadgeList, Bolding, H1, HR};
#[component]
pub fn PublicationsProjects() -> Element {
@@ -21,108 +21,13 @@ pub fn PublicationsProjects() -> Element {
}
}
#[derive(Clone, PartialEq, Props)]
struct PublicationProp {
#[props(default = "".to_string())]
doi: String,
authors: String,
title: String,
conference: String,
#[props(default = "".to_string())]
description: String,
}
fn Publications() -> Element {
rsx! {
div {
class: "flex gap-4 items-center flex-wrap",
Publication {
title: t!("publications_projects_publications_rpm_title"),
authors: t!("publications_projects_publications_rpm_authors"),
conference: t!("publications_projects_publications_rpm_conference"),
doi: t!("publications_projects_publications_rpm_url"),
description: t!("publications_projects_publications_rpm_description")
},
Publication {
title: t!("publications_projects_publications_iot_fuzzers_title"),
authors: t!("publications_projects_publications_iot_fuzzers_authors"),
conference: t!("publications_projects_publications_iot_fuzzers_conference"),
doi: "/#",
description: t!("publications_projects_publications_iot_fuzzers_description")
},
}
}
}
fn Publication(prop: PublicationProp) -> Element {
let pattern = vec!["T.-D. Tran".to_string(), "Tuan-Dat Tran".to_string()];
rsx! {
Link {
class:"block max-w-sm p-6 border rounded-lg shadow bg-gray-800 border-gray-700 hover:bg-gray-700",
to:"{prop.doi}",
new_tab: true,
h5 {
class:"mb-2 text-2xl font-bold tracking-tight text-white",
"{prop.title}",
},
span { class: "text-lg text-white", "{prop.conference}" },
p {
class:"font-normal text-gray-400 italic",
Bolding {
authors: "{prop.authors}",
patterns: pattern,
},
}
p {
class:"font-normal text-gray-400",
"{prop.description}",
}
}
}
}
fn Projects() -> Element {
rsx! {
div {
class: "flex gap-4 items-center flex-wrap",
Project {
title: t!("publications_projects_projects_bpba_title"),
authors: t!("publications_projects_projects_bpba_authors"),
kind: t!("publications_projects_projects_bpba_kind"),
url: "/#",
description: t!("publications_projects_projects_bpba_description")
},
Project {
title: t!("publications_projects_projects_dotfiles_title"),
authors: t!("publications_projects_projects_dotfiles_authors"),
kind: t!("publications_projects_projects_dotfiles_kind"),
url: "/#",
description: t!("publications_projects_projects_dotfiles_description")
},
Project {
title: t!("publications_projects_projects_homelab_title"),
authors: t!("publications_projects_projects_homelab_authors"),
kind: t!("publications_projects_projects_homelab_kind"),
url: "/#",
description: t!("publications_projects_projects_homelab_description")
}
Project {
title: t!("publications_projects_projects_athome_title"),
authors: t!("publications_projects_projects_athome_authors"),
kind: t!("publications_projects_projects_athome_kind"),
url: "/#",
description: t!("publications_projects_projects_athome_description")
}
}
}
}
#[derive(Clone, PartialEq, Props)]
struct ProjectProp {
#[props(default = "".to_string())]
url: String,
authors: String,
title: String,
technologies: Vec<String>,
kind: String,
#[props(default = "".to_string())]
description: String,
@@ -140,6 +45,7 @@ fn Project(prop: ProjectProp) -> Element {
class:"mb-2 text-2xl font-bold tracking-tight text-white",
"{prop.title}",
},
p {}
p { class: "text-lg text-white", "{prop.kind}" },
p {
class:"font-normal text-gray-400",
@@ -147,7 +53,8 @@ fn Project(prop: ProjectProp) -> Element {
authors: "{prop.authors}",
patterns: pattern,
},
}
},
BadgeList{list: prop.technologies},
p {
class:"font-normal text-gray-400",
"{prop.description}",
@@ -155,3 +62,68 @@ fn Project(prop: ProjectProp) -> Element {
}
}
}
fn Publications() -> Element {
rsx! {
div {
class: "flex gap-4 items-center flex-wrap",
Project {
title: t!("publications_projects_publications_rpm_title"),
authors: t!("publications_projects_publications_rpm_authors"),
technologies: vec![],
kind: t!("publications_projects_publications_rpm_conference"),
url: t!("publications_projects_publications_rpm_url"),
description: t!("publications_projects_publications_rpm_description")
},
Project {
title: t!("publications_projects_publications_iot_fuzzers_title"),
authors: t!("publications_projects_publications_iot_fuzzers_authors"),
technologies: vec![],
kind: t!("publications_projects_publications_iot_fuzzers_conference"),
url: "/publications/#",
description: t!("publications_projects_publications_iot_fuzzers_description")
},
}
}
}
fn Projects() -> Element {
rsx! {
div {
class: "flex gap-4 items-center flex-wrap",
Project {
title: t!("publications_projects_projects_bpba_title"),
authors: t!("publications_projects_projects_bpba_authors"),
technologies: vec![],
kind: t!("publications_projects_projects_bpba_kind"),
url: "/publications/#",
description: t!("publications_projects_projects_bpba_description")
},
Project {
title: t!("publications_projects_projects_dotfiles_title"),
authors: t!("publications_projects_projects_dotfiles_authors"),
technologies: vec![],
kind: t!("publications_projects_projects_dotfiles_kind"),
url: "/publications/#",
description: t!("publications_projects_projects_dotfiles_description")
},
Project {
title: t!("publications_projects_projects_homelab_title"),
authors: t!("publications_projects_projects_homelab_authors"),
technologies: vec![],
kind: t!("publications_projects_projects_homelab_kind"),
url: "/publications/#",
description: t!("publications_projects_projects_homelab_description")
}
Project {
title: t!("publications_projects_projects_athome_title"),
authors: t!("publications_projects_projects_athome_authors"),
technologies: vec![],
kind: t!("publications_projects_projects_athome_kind"),
url: "/publications/#",
description: t!("publications_projects_projects_athome_description")
}
}
}
}