4 Commits

Author SHA1 Message Date
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
Tuan-Dat Tran
a387293f94 feat(ftl): Updated description for dd
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2025-03-12 22:15:35 +01:00
Tuan-Dat Tran
64aa115ef4 feat(cargo): update
Signed-off-by: Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>
2025-02-11 19:48:58 +01:00
12 changed files with 652 additions and 691 deletions

743
Cargo.lock generated

File diff suppressed because it is too large Load Diff

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

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

View File

@@ -723,10 +723,18 @@ video {
max-width: 36rem; max-width: 36rem;
} }
.flex-auto {
flex: 1 1 auto;
}
.flex-grow { .flex-grow {
flex-grow: 1; flex-grow: 1;
} }
.grow {
flex-grow: 1;
}
.cursor-pointer { .cursor-pointer {
cursor: pointer; cursor: pointer;
} }
@@ -739,6 +747,14 @@ video {
flex-wrap: wrap; flex-wrap: wrap;
} }
.content-center {
align-content: center;
}
.items-start {
align-items: flex-start;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
@@ -755,6 +771,10 @@ video {
justify-content: space-between; justify-content: space-between;
} }
.justify-stretch {
justify-content: stretch;
}
.gap-2 { .gap-2 {
gap: 0.5rem; gap: 0.5rem;
} }
@@ -781,6 +801,10 @@ video {
margin-bottom: calc(2rem * var(--tw-space-y-reverse)); margin-bottom: calc(2rem * var(--tw-space-y-reverse));
} }
.self-stretch {
align-self: stretch;
}
.rounded { .rounded {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
@@ -797,6 +821,11 @@ video {
border-radius: 0.375rem; border-radius: 0.375rem;
} }
.rounded-e-lg {
border-start-end-radius: 0.5rem;
border-end-end-radius: 0.5rem;
}
.rounded-s-lg { .rounded-s-lg {
border-start-start-radius: 0.5rem; border-start-start-radius: 0.5rem;
border-end-start-radius: 0.5rem; border-end-start-radius: 0.5rem;
@@ -818,11 +847,6 @@ video {
border-inline-start-width: 1px; border-inline-start-width: 1px;
} }
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity));
}
.border-gray-600 { .border-gray-600 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity)); border-color: rgb(75 85 99 / var(--tw-border-opacity));
@@ -833,29 +857,14 @@ video {
border-color: rgb(55 65 81 / var(--tw-border-opacity)); border-color: rgb(55 65 81 / var(--tw-border-opacity));
} }
.border-white {
--tw-border-opacity: 1;
border-color: rgb(255 255 255 / var(--tw-border-opacity));
}
.border-gray-900 { .border-gray-900 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity)); border-color: rgb(17 24 39 / var(--tw-border-opacity));
} }
.bg-blue-100 { .bg-blue-900 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(219 234 254 / var(--tw-bg-opacity)); background-color: rgb(30 58 138 / var(--tw-bg-opacity));
}
.bg-gray-100 {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
}
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
} }
.bg-gray-700 { .bg-gray-700 {
@@ -868,51 +877,11 @@ video {
background-color: rgb(31 41 55 / var(--tw-bg-opacity)); background-color: rgb(31 41 55 / var(--tw-bg-opacity));
} }
.bg-green-100 {
--tw-bg-opacity: 1;
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
}
.bg-indigo-100 {
--tw-bg-opacity: 1;
background-color: rgb(224 231 255 / var(--tw-bg-opacity));
}
.bg-pink-100 {
--tw-bg-opacity: 1;
background-color: rgb(252 231 243 / var(--tw-bg-opacity));
}
.bg-purple-100 {
--tw-bg-opacity: 1;
background-color: rgb(243 232 255 / var(--tw-bg-opacity));
}
.bg-red-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-yellow-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
}
.bg-gray-900 { .bg-gray-900 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity)); background-color: rgb(17 24 39 / var(--tw-bg-opacity));
} }
.bg-blue-900 {
--tw-bg-opacity: 1;
background-color: rgb(30 58 138 / var(--tw-bg-opacity));
}
.bg-green-900 { .bg-green-900 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(20 83 45 / var(--tw-bg-opacity)); background-color: rgb(20 83 45 / var(--tw-bg-opacity));
@@ -1118,9 +1087,14 @@ video {
letter-spacing: 0.1em; letter-spacing: 0.1em;
} }
.text-blue-800 { .text-blue-300 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(30 64 175 / var(--tw-text-opacity)); color: rgb(147 197 253 / var(--tw-text-opacity));
}
.text-blue-500 {
--tw-text-opacity: 1;
color: rgb(59 130 246 / var(--tw-text-opacity));
} }
.text-cyan-400 { .text-cyan-400 {
@@ -1128,6 +1102,11 @@ video {
color: rgb(34 211 238 / var(--tw-text-opacity)); color: rgb(34 211 238 / var(--tw-text-opacity));
} }
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.text-gray-400 { .text-gray-400 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity)); color: rgb(156 163 175 / var(--tw-text-opacity));
@@ -1138,70 +1117,11 @@ video {
color: rgb(107 114 128 / var(--tw-text-opacity)); color: rgb(107 114 128 / var(--tw-text-opacity));
} }
.text-gray-700 {
--tw-text-opacity: 1;
color: rgb(55 65 81 / var(--tw-text-opacity));
}
.text-gray-800 {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity));
}
.text-gray-900 { .text-gray-900 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity)); color: rgb(17 24 39 / var(--tw-text-opacity));
} }
.text-green-800 {
--tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity));
}
.text-indigo-800 {
--tw-text-opacity: 1;
color: rgb(55 48 163 / var(--tw-text-opacity));
}
.text-pink-800 {
--tw-text-opacity: 1;
color: rgb(157 23 77 / var(--tw-text-opacity));
}
.text-purple-800 {
--tw-text-opacity: 1;
color: rgb(107 33 168 / var(--tw-text-opacity));
}
.text-red-800 {
--tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity));
}
.text-transparent {
color: transparent;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-yellow-800 {
--tw-text-opacity: 1;
color: rgb(133 77 14 / var(--tw-text-opacity));
}
.text-blue-300 {
--tw-text-opacity: 1;
color: rgb(147 197 253 / var(--tw-text-opacity));
}
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.text-green-300 { .text-green-300 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(134 239 172 / var(--tw-text-opacity)); color: rgb(134 239 172 / var(--tw-text-opacity));
@@ -1227,6 +1147,15 @@ video {
color: rgb(252 165 165 / var(--tw-text-opacity)); color: rgb(252 165 165 / var(--tw-text-opacity));
} }
.text-transparent {
color: transparent;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-yellow-300 { .text-yellow-300 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(253 224 71 / var(--tw-text-opacity)); color: rgb(253 224 71 / var(--tw-text-opacity));
@@ -1379,11 +1308,6 @@ video {
font-size: 3rem; font-size: 3rem;
line-height: 1; line-height: 1;
} }
.md\:text-blue-500 {
--tw-text-opacity: 1;
color: rgb(59 130 246 / var(--tw-text-opacity));
}
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {
@@ -1393,115 +1317,3 @@ video {
} }
} }
@media (prefers-color-scheme: dark) {
.dark\:border-gray-700 {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.dark\:border-gray-900 {
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.dark\:bg-blue-900 {
--tw-bg-opacity: 1;
background-color: rgb(30 58 138 / var(--tw-bg-opacity));
}
.dark\:bg-gray-700 {
--tw-bg-opacity: 1;
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
}
.dark\:bg-gray-900 {
--tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
}
.dark\:bg-green-900 {
--tw-bg-opacity: 1;
background-color: rgb(20 83 45 / var(--tw-bg-opacity));
}
.dark\:bg-indigo-900 {
--tw-bg-opacity: 1;
background-color: rgb(49 46 129 / var(--tw-bg-opacity));
}
.dark\:bg-pink-900 {
--tw-bg-opacity: 1;
background-color: rgb(131 24 67 / var(--tw-bg-opacity));
}
.dark\:bg-purple-900 {
--tw-bg-opacity: 1;
background-color: rgb(88 28 135 / var(--tw-bg-opacity));
}
.dark\:bg-red-900 {
--tw-bg-opacity: 1;
background-color: rgb(127 29 29 / var(--tw-bg-opacity));
}
.dark\:bg-yellow-900 {
--tw-bg-opacity: 1;
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
}
.dark\:text-blue-300 {
--tw-text-opacity: 1;
color: rgb(147 197 253 / var(--tw-text-opacity));
}
.dark\:text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.dark\:text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.dark\:text-gray-500 {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}
.dark\:text-green-300 {
--tw-text-opacity: 1;
color: rgb(134 239 172 / var(--tw-text-opacity));
}
.dark\:text-indigo-300 {
--tw-text-opacity: 1;
color: rgb(165 180 252 / var(--tw-text-opacity));
}
.dark\:text-pink-300 {
--tw-text-opacity: 1;
color: rgb(249 168 212 / var(--tw-text-opacity));
}
.dark\:text-purple-300 {
--tw-text-opacity: 1;
color: rgb(216 180 254 / var(--tw-text-opacity));
}
.dark\:text-red-300 {
--tw-text-opacity: 1;
color: rgb(252 165 165 / var(--tw-text-opacity));
}
.dark\:text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.dark\:text-yellow-300 {
--tw-text-opacity: 1;
color: rgb(253 224 71 / var(--tw-text-opacity));
}
}

View File

@@ -12,13 +12,15 @@ home_card_text =
Willkommen auf meiner kleinen Webseite im World Wide Web. 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. Mein Name ist Tuan und ich bin Linux-Bastler, IT-Security Enthusiast und IT-Automatisierer aus Leidenschaft.
home_card_contact_button = Get in touch. home_card_contact_button = Get in touch.
cv_introduction_title = DevOps Engineer | Homelab Enthusiast
cv_introduction_0 = 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, in Netzwerktechnologien und Softwareentwicklung. Spezialisiert auf Kubernetes,
Ansible, Azure und moderne Cloud-Technologien. Praxisnahe Ansible, Azure und moderne Cloud-Technologien. Praxisnahe
Forschungserfahrung in Software-Defined Networking, 5G und Maschinellem Lernen. Forschungserfahrung in Software-Defined Networking, 5G und Maschinellem Lernen.
Leidenschaft für effiziente IT-Infrastrukturen, Automatisierung und Leidenschaft für effiziente IT-Infrastrukturen, Automatisierung und
innovative Softwarelösungen. innovative Softwarelösungen.
cv_introduction_tools = Lieblingstools:
cv_workexperience_title = Berufserfahrung cv_workexperience_title = Berufserfahrung
cv_workexperience_se1_gefeba_title = Software Entwickler @ gefeba Engineering GmbH cv_workexperience_se1_gefeba_title = Software Entwickler @ gefeba Engineering GmbH
cv_workexperience_se1_gefeba_time = 2013 - 2015 cv_workexperience_se1_gefeba_time = 2013 - 2015
@@ -60,14 +62,15 @@ cv_workexperience_ra_ude_description = Während meiner Tätigkeit bei der
aufgebaut und verwaltet. aufgebaut und verwaltet.
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 = Bei DextraData, einem führenden cv_workexperience_dd_devops_description = Als DevOps Engineer bei DextraData
SaaS-Anbieter im Bereich Governance, Risk und Compliance (GRC), stelle ich bin ich für die Cloud-basierte Infrastruktur hinter den vielfältigen
die Verfügbarkeit und Stabilität von Software-Deployments für unsere Kunden SaaS-Produkten des Unternehmens verantwortlich. Ich arbeite eng mit
sowie für interne Entwicklungsteams über mehrere Produkte hinweg sicher. In Software-Entwicklern, Customer Success Managern und Customer Success Engineers
Zusammenarbeit mit Customer-Success-Teams und Softwareentwicklern zusammen, um betriebliche Exzellenz und unterbrechungsfreie Servicebereitstellung
gewährleisten wir einen reibungslosen Betrieb unserer Multi-Tenant- und Single für unsere Kunden zu gewährleisten, wobei ihre spezifischen Anforderungen im
-Tenant-Instanzen von der Vorentwicklung bis hin zu den Vordergrund stehen. Ich verwalte Kubernetes-Cluster-Deployments, überwache
Produktionsumgebungen. kontinuierlich Deployment-Gesundheitsmetriken und implementiere
Infrastructure-as-Code-Lösungen.
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

@@ -12,6 +12,7 @@ home_card_text =
Welcome to my little place on the internet 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. 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. home_card_contact_button = Get in touch.
cv_introduction_title = DevOps Engineer | Homelab Enthusiast
cv_introduction_0 = cv_introduction_0 =
DevOps Engineer and Software Developer with a strong academic background in DevOps Engineer and Software Developer with a strong academic background in
networking technologies and software development. Specialized in Kubernetes, networking technologies and software development. Specialized in Kubernetes,
@@ -19,6 +20,7 @@ cv_introduction_0 =
in Software-Defined Networking, 5G, and Machine Learning. Passionate about in Software-Defined Networking, 5G, and Machine Learning. Passionate about
efficient IT infrastructures, automation, and innovative software efficient IT infrastructures, automation, and innovative software
solutions. solutions.
cv_introduction_tools = Favorite Tools:
cv_workexperience_title = Work Experience cv_workexperience_title = Work Experience
cv_workexperience_se1_gefeba_title = Software Engineer @ gefeba Engineering GmbH cv_workexperience_se1_gefeba_title = Software Engineer @ gefeba Engineering GmbH
cv_workexperience_se1_gefeba_time = 2013 - 2015 cv_workexperience_se1_gefeba_time = 2013 - 2015
@@ -56,13 +58,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 = At DextraData, a leading SaaS cv_workexperience_dd_devops_description = At DextraData as a DevOps Engineer,
provider in the Governance, Risk, and Compliance (GRC) sector, I ensure the I am responsible for the cloud-based infrastructure hosting our companies
availability and health of software deployments for our customers and diverse portfolio of SaaS products, collaborating with Software Developers,
internal development teams across multiple products. Collaborating with Customer Success Managers, and Customer Success Engineers to ensure
Customer Success and Software Engineers alike we ensure smooth operations on operational excellence and uninterrupted service delivery for our customers
our multi-tenant as well as single tenant instances from pre-dev to with their specific requirements at the forefront. I manage
production environments. Kubernetes cluster deployments, continuously monitor deployment health metrics,
and implement Infrastructure as Code solutions.
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

3
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,3 @@
[toolchain]
channel = "1.86.0"
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 {
@@ -33,8 +33,18 @@ pub fn CV() -> Element {
fn Introduction() -> Element { fn Introduction() -> Element {
rsx! { rsx! {
div { 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_0") } },
P {
{ t!("cv_introduction_tools") },
" ",
Link { new_tab: true, to: "https://www.lazyvim.org/", "NeoVim (LazyVim)" },
", ",
Link { new_tab: true, to: "https://zellij.dev/", "Zellij" },
", ",
Link { new_tab: true, to: "https://k9scli.io/", "k9s" }
},
}, },
} }
} }
@@ -47,7 +57,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 +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"}, 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

@@ -44,7 +44,7 @@ fn LanguageButtonGroup() -> Element {
onclick: change_to_english, onclick: change_to_english,
label { { t!("headers_language_buttons_english") } } }, label { { t!("headers_language_buttons_english") } } },
button { button {
class: "px-4 py-2 text-sm font-medium border rounded-s-lg focus:z-10 focus:ring-2 bg-gray-800 border-gray-700 text-white hover:text-white hover:bg-gray-700 focus:ring-blue-500 focus:text-white", class: "px-4 py-2 text-sm font-medium border rounded-e-lg focus:z-10 focus:ring-2 bg-gray-800 border-gray-700 text-white hover:text-white hover:bg-gray-700 focus:ring-blue-500 focus:text-white",
onclick: change_to_german, onclick: change_to_german,
label { { t!("headers_language_buttons_german") } } } label { { t!("headers_language_buttons_german") } } }
} }
@@ -54,6 +54,6 @@ fn LanguageButtonGroup() -> Element {
#[component] #[component]
fn HeaderLink(url: Route, text: String) -> Element { fn HeaderLink(url: Route, text: String) -> Element {
rsx! { rsx! {
Link { to: url, class:"md:bg-transparent md:p-0 text-white md:text-blue-500", {text} } Link { to: url, class:"md:bg-transparent md:p-0 text-blue-500", {text} }
} }
} }

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")
}
}
}
}