feat(badge): Streamlined badging

Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
This commit is contained in:
Tuan-Dat Tran
2025-04-06 17:11:44 +02:00
parent a387293f94
commit a1fd3ea358
9 changed files with 135 additions and 156 deletions

6
Cargo.lock generated
View File

@@ -1190,13 +1190,15 @@ dependencies = [
[[package]] [[package]]
name = "dioxus-i18n" name = "dioxus-i18n"
version = "0.3.0" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaa2df724b94e2a93229609951a0570a4b8807215c588e78f88a8532bdde319f" checksum = "a97da8c5cbb956baaa8faffeb7ffba342dc8f2dc02f16aeaf9d94708bcf2b221"
dependencies = [ dependencies = [
"dioxus-lib", "dioxus-lib",
"fluent", "fluent",
"thiserror 2.0.12",
"unic-langid", "unic-langid",
"walkdir",
] ]
[[package]] [[package]]

View File

@@ -13,7 +13,7 @@ dioxus = { version = "0.6", features = ["fullstack", "router"] }
# Debug # Debug
tracing = "0.1.40" tracing = "0.1.40"
dioxus-logger = "0.6.0" dioxus-logger = "0.6.0"
dioxus-i18n = "0.3.0" dioxus-i18n = "0.4.0"
[features] [features]
default = [] default = []

View File

@@ -61,15 +61,14 @@ cv_workexperience_ra_ude_description = Während meiner Tätigkeit bei der
cv_workexperience_dd_devops_title = DevOps Engineer @ DextraData cv_workexperience_dd_devops_title = DevOps Engineer @ DextraData
cv_workexperience_dd_devops_time = 2025 - Jetzt cv_workexperience_dd_devops_time = 2025 - Jetzt
cv_workexperience_dd_devops_description = Als DevOps Engineer bei DextraData cv_workexperience_dd_devops_description = Als DevOps Engineer bei DextraData
bin ich Teil eines horizontal strukturierten DevOps-Teams und verantwortlich bin ich für die Cloud-basierte Infrastruktur hinter den vielfältigen
für die Verwaltung und Optimierung sowohl cloudbasierter als auch On-Premise- SaaS-Produkten des Unternehmens verantwortlich. Ich arbeite eng mit
Infrastrukturen, die unsere Single-Tenant-SaaS-Lösungen hosten. Meine Aufgabe Software-Entwicklern, Customer Success Managern und Customer Success Engineers
besteht nicht nur darin, diese Umgebungen zu warten und zu verbessern, sondern zusammen, um betriebliche Exzellenz und unterbrechungsfreie Servicebereitstellung
auch Automatisierung und Effizienz durch moderne DevOps-Praktiken für unsere Kunden zu gewährleisten, wobei ihre spezifischen Anforderungen im
voranzutreiben. Ich war maßgeblich an der Umstellung unserer Kubernetes- Vordergrund stehen. Ich verwalte Kubernetes-Cluster-Deployments, überwache
basierten Deployments von manuellen Prozessen auf einen stärker kontinuierlich Deployment-Gesundheitsmetriken und implementiere
automatisierten und skalierbaren Ansatz beteiligt, indem ich Infrastructure as Infrastructure-as-Code-Lösungen.
Code mit Azure Resource Manager und Ansible eingesetzt habe.
cv_socials_title = Profile cv_socials_title = Profile
cv_education_title = Bildungsweg cv_education_title = Bildungsweg
cv_education_bachelor_title = BSc Angewandte Informatik - Systems Engineering cv_education_bachelor_title = BSc Angewandte Informatik - Systems Engineering

View File

@@ -56,15 +56,14 @@ cv_workexperience_ra_ude_description = While working at the Network
infractructure, inventory system and online presence. infractructure, inventory system and online presence.
cv_workexperience_dd_devops_title = DevOps Engineer @ DextraData cv_workexperience_dd_devops_title = DevOps Engineer @ DextraData
cv_workexperience_dd_devops_time = 2025 - now cv_workexperience_dd_devops_time = 2025 - now
cv_workexperience_dd_devops_description = As a DevOps Engineer at DextraData, cv_workexperience_dd_devops_description = At DextraData as a DevOps Engineer,
I am part of a horizontally structured DevOps team, responsible for managing I am responsible for the cloud-based infrastructure hosting our companies
and optimizing both cloud and on-premise infrastructure that hosts our diverse portfolio of SaaS products, collaborating with Software Developers,
single-tenant SaaS solutions. My role involves not only maintaining and Customer Success Managers, and Customer Success Engineers to ensure
improving these environments but also driving automation and efficiency operational excellence and uninterrupted service delivery for our customers
through modern DevOps practices. I have been actively involved in with their specific requirements at the forefront. I manage
transitioning our Kubernetes-based deployments from manual processes to a Kubernetes cluster deployments, continuously monitor deployment health metrics,
more automated and scalable approach by leveraging Infrastructure as Code and implement Infrastructure as Code solutions.
with Azure Resource Manager and Ansible.
cv_socials_title = Socials cv_socials_title = Socials
cv_education_title = Education cv_education_title = Education
cv_education_bachelor_title = BSc Systems Engineering cv_education_bachelor_title = BSc Systems Engineering

View File

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

View File

@@ -242,3 +242,41 @@ 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()
}

View File

@@ -1,7 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_i18n::t; use dioxus_i18n::t;
use crate::components::{H4, HR}; use crate::components::*;
#[component] #[component]
pub fn CV() -> Element { pub fn CV() -> Element {
@@ -47,7 +47,7 @@ fn WorkExperience() -> Element {
ol { ol {
class:"relative border-s border-gray-700", class:"relative border-s border-gray-700",
CVEntry {time: t!("cv_workexperience_dd_devops_time"), title: t!("cv_workexperience_dd_devops_title"), 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(), "ELK".to_string(), "Helm".to_string()],
description: t!("cv_workexperience_dd_devops_description") description: t!("cv_workexperience_dd_devops_description")
}, },
CVEntry {time: t!("cv_workexperience_ra_ude_time"), title: t!("cv_workexperience_ra_ude_title"), CVEntry {time: t!("cv_workexperience_ra_ude_time"), title: t!("cv_workexperience_ra_ude_title"),
@@ -186,44 +186,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"}, 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}"}, 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}"} h6 { class: "text-lg font-semibold text-white", "{props.title}"}
ul { BadgeList{ list: props.technologies }
class: "flex flex-wrap gap-2",
for (index, value) in props.technologies.iter().enumerate() {
li { key: "{index}", RandomBadge { text: "{value}"} }
}
}
p { class:"text-base font-normal text-gray-400", "{props.description}"}, p { class:"text-base font-normal text-gray-400", "{props.description}"},
{props.children} {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 { fn Socials() -> Element {
rsx! { rsx! {
div { div {

View File

@@ -50,11 +50,11 @@ fn App() -> Element {
I18nConfig::new(langid!("en-GB")) I18nConfig::new(langid!("en-GB"))
.with_locale(Locale::new_static( .with_locale(Locale::new_static(
langid!("en-GB"), langid!("en-GB"),
include_str!("./languages/en-GB.ftl"), include_str!("../languages/en-GB.ftl"),
)) ))
.with_locale(Locale::new_static( .with_locale(Locale::new_static(
langid!("de-DE"), langid!("de-DE"),
include_str!("./languages/de-DE.ftl"), include_str!("../languages/de-DE.ftl"),
)) ))
}); });

View File

@@ -1,7 +1,7 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_i18n::t; use dioxus_i18n::t;
use crate::components::{Bolding, H1, HR}; use crate::components::{BadgeList, Bolding, H1, HR};
#[component] #[component]
pub fn PublicationsProjects() -> Element { 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)] #[derive(Clone, PartialEq, Props)]
struct ProjectProp { struct ProjectProp {
#[props(default = "".to_string())] #[props(default = "".to_string())]
url: String, url: String,
authors: String, authors: String,
title: String, title: String,
technologies: Vec<String>,
kind: String, kind: String,
#[props(default = "".to_string())] #[props(default = "".to_string())]
description: String, description: String,
@@ -140,6 +45,7 @@ fn Project(prop: ProjectProp) -> Element {
class:"mb-2 text-2xl font-bold tracking-tight text-white", class:"mb-2 text-2xl font-bold tracking-tight text-white",
"{prop.title}", "{prop.title}",
}, },
p {}
p { class: "text-lg text-white", "{prop.kind}" }, p { class: "text-lg text-white", "{prop.kind}" },
p { p {
class:"font-normal text-gray-400", class:"font-normal text-gray-400",
@@ -147,7 +53,8 @@ fn Project(prop: ProjectProp) -> Element {
authors: "{prop.authors}", authors: "{prop.authors}",
patterns: pattern, patterns: pattern,
}, },
} },
BadgeList{list: prop.technologies},
p { p {
class:"font-normal text-gray-400", class:"font-normal text-gray-400",
"{prop.description}", "{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: "/#",
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: "/#",
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: "/#",
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: "/#",
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: "/#",
description: t!("publications_projects_projects_athome_description")
}
}
}
}