Compare commits
48 Commits
4b56557d15
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f1989ebc9 | ||
|
|
a387293f94 | ||
|
|
64aa115ef4 | ||
|
|
8f00360ff8 | ||
|
|
16e04980b5 | ||
|
|
b878c7db55 | ||
|
|
9fdf88555a | ||
|
|
b7678f0ff8 | ||
|
|
6704cc944d | ||
|
|
414678befd | ||
|
|
7fa831ca13 | ||
|
|
8f8c724f5f | ||
|
|
01c3ceb76d | ||
|
|
5ce21498e5 | ||
|
|
8d8b461774 | ||
|
|
96e5cd9fee | ||
|
|
94ad506ce6 | ||
|
|
541fd5297c | ||
|
|
67243080b1 | ||
| 9c43e89f29 | |||
|
|
0d0638fa14 | ||
|
|
bfe89fa811 | ||
|
|
5db82448c4 | ||
|
|
74f68eff5b | ||
|
|
ac80065e82 | ||
|
|
3610f338aa | ||
|
|
bec4c608f1 | ||
|
|
f76a7a8c4c | ||
|
|
0ff5419c23 | ||
|
|
b7a47ca903 | ||
|
|
b3eb962024 | ||
| a54d1f3c3b | |||
| d9f904231a | |||
|
|
c2ab088799 | ||
|
|
f336daf54b | ||
|
|
991cb57c88 | ||
|
|
1fbc2b7521 | ||
|
|
20e1df177c | ||
|
|
4a18e432b2 | ||
|
|
081229d66a | ||
|
|
3fb233ebcc | ||
|
|
8a6ac977b3 | ||
| 837e19e111 | |||
|
|
68bf62ee21 | ||
|
|
c73335c170 | ||
|
|
71798be854 | ||
|
|
0dd9cec4bd | ||
|
|
6ca89b3bd5 |
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
**/target
|
||||
**/dist
|
||||
LICENSES
|
||||
LICENSE
|
||||
temp
|
||||
README.md
|
||||
18
.gitea/workflows/docker.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
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
|
||||
4896
Cargo.lock
generated
27
Cargo.toml
@@ -1,23 +1,34 @@
|
||||
[package]
|
||||
name = "athome"
|
||||
version = "0.1.0"
|
||||
version = "0.4.1"
|
||||
authors = ["Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
||||
dioxus = { version = "0.5", features = ["fullstack", "router"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
dioxus = { version = "0.6", features = ["fullstack", "router"] }
|
||||
|
||||
# Debug
|
||||
tracing = "0.1.40"
|
||||
dioxus-logger = "0.5.0"
|
||||
manganis = "0.2.2"
|
||||
dioxus-free-icons = { version = "0.8", features = ["font-awesome-brands"] }
|
||||
dioxus-logger = "0.6.0"
|
||||
dioxus-i18n = "0.3.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
server = ["dioxus/axum"]
|
||||
web = ["dioxus/web"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
server = ["dioxus/server"]
|
||||
|
||||
[profile]
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
|
||||
[profile.server-dev]
|
||||
inherits = "dev"
|
||||
|
||||
[profile.android-dev]
|
||||
inherits = "dev"
|
||||
|
||||
16
Dioxus.toml
@@ -25,19 +25,3 @@ reload_html = true
|
||||
|
||||
# which files or dirs will be watcher monitoring
|
||||
watch_path = ["src", "assets"]
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# CSS style file
|
||||
|
||||
style = ["tailwind.css"]
|
||||
|
||||
# Javascript code file
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Javascript code file
|
||||
# serve: [dev-server] only
|
||||
script = []
|
||||
|
||||
48
Dockerfile
@@ -1,17 +1,33 @@
|
||||
FROM rust:1.77.2 as builder
|
||||
WORKDIR /athome/
|
||||
RUN cargo install dioxus-cli@^0.5
|
||||
RUN apt update && apt install nodejs npm -y && rm -rf /var/lib/apt/lists/*
|
||||
RUN npm install -D tailwindcss
|
||||
FROM rust:1 AS chef
|
||||
RUN cargo install cargo-chef
|
||||
WORKDIR /app
|
||||
|
||||
FROM builder
|
||||
WORKDIR /athome/
|
||||
COPY ./src/ ./src/
|
||||
COPY ./assets/ ./assets/
|
||||
COPY ./Cargo.toml ./Cargo.toml
|
||||
COPY ./input.css ./input.css
|
||||
COPY ./Dioxus.toml ./Dioxus.toml
|
||||
COPY ./tailwind.config.js ./tailwind.config.js
|
||||
RUN npx tailwindcss -i ./input.css -o ./assets/tailwind.css
|
||||
RUN dx build --platform fullstack --release
|
||||
ENTRYPOINT [ "./docs/athome" ]
|
||||
FROM chef AS planner
|
||||
COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM chef AS builder
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
COPY . .
|
||||
|
||||
# Install `dx`
|
||||
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
|
||||
RUN cargo binstall dioxus-cli --root /.cargo -y --force
|
||||
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
|
||||
COPY --from=builder /app/target/dx/athome/release/web/ /usr/local/app
|
||||
|
||||
# set our port and make sure to listen for all connections
|
||||
ENV PORT=8080
|
||||
ENV IP=0.0.0.0
|
||||
|
||||
# expose the port 8080
|
||||
EXPOSE 8080
|
||||
|
||||
WORKDIR /usr/local/app
|
||||
ENTRYPOINT [ "/usr/local/app/server" ]
|
||||
|
||||
12
README.md
@@ -2,6 +2,16 @@
|
||||
|
||||
My personal website.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
npx tailwindcss -i ./input.css -o ./assets/tailwind.css --watch
|
||||
```
|
||||
|
||||
```sh
|
||||
dx serve --platform web
|
||||
```
|
||||
|
||||
## Screenshot
|
||||
|
||||
[[./resources/screenshot.png]]
|
||||
[[./resources/screenshot.webp]]
|
||||
|
||||
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 15 KiB |
1
assets/pictures/Gitea_Logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" style="enable-background:new 0 0 640 640;" xml:space="preserve" viewBox="5.67 143.05 628.65 387.55"> <g> <path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"></path> <g> <g> <path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"></path> <path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z"></path> </g> </g> </g> </svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
5
assets/pictures/LI-Bug.svg.original.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg id="Group_1282" data-name="Group 1282" xmlns="http://www.w3.org/2000/svg" width="76.624" height="65.326" viewBox="0 0 76.624 65.326">
|
||||
<path id="Path_2525" data-name="Path 2525" d="M1165,274.515a1.2,1.2,0,0,0,1.21-1.269c0-.9-.543-1.33-1.657-1.33h-1.8v4.712h.677v-2.054h.832l.019.025,1.291,2.029h.724l-1.389-2.1Zm-.783-.472h-.785V272.45h.995c.514,0,1.1.084,1.1.757,0,.774-.593.836-1.314.836" transform="translate(-1092.136 -213.406)" fill="#0a66c2"/>
|
||||
<path id="Path_2520" data-name="Path 2520" d="M958.98,112.559h-9.6V97.525c0-3.585-.064-8.2-4.993-8.2-5,0-5.765,3.906-5.765,7.939v15.294h-9.6V81.642h9.216v4.225h.129a10.1,10.1,0,0,1,9.093-4.994c9.73,0,11.524,6.4,11.524,14.726ZM918.19,77.416a5.571,5.571,0,1,1,5.57-5.572,5.571,5.571,0,0,1-5.57,5.572m4.8,35.143h-9.61V81.642h9.61Zm40.776-55.2h-55.21a4.728,4.728,0,0,0-4.781,4.67v55.439a4.731,4.731,0,0,0,4.781,4.675h55.21a4.741,4.741,0,0,0,4.8-4.675V62.025a4.738,4.738,0,0,0-4.8-4.67" transform="translate(-903.776 -57.355)" fill="#0a66c2"/>
|
||||
<path id="Path_2526" data-name="Path 2526" d="M1156.525,264.22a4.418,4.418,0,1,0,.085,0h-.085m0,8.33a3.874,3.874,0,1,1,3.809-3.938c0,.022,0,.043,0,.065a3.791,3.791,0,0,1-3.708,3.871h-.1" transform="translate(-1084.362 -207.809)" fill="#0a66c2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 34 KiB |
BIN
assets/pictures/headshot.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 75 KiB |
41
assets/robots.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
User-agent: CCBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: img2dataset
|
||||
Disallow: /
|
||||
|
||||
User-agent: Google-Extended
|
||||
Disallow: /
|
||||
|
||||
User-agent: anthropic-ai
|
||||
Disallow: /
|
||||
|
||||
User-agent: Claude-Web
|
||||
Disallow: /
|
||||
|
||||
User-agent: Omgilibot
|
||||
Disallow: /
|
||||
|
||||
User-agent: Omgili
|
||||
Disallow: /
|
||||
|
||||
User-agent: FacebookBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: Bytespider
|
||||
Disallow: /
|
||||
|
||||
User-agent: magpie-crawler
|
||||
Disallow: /
|
||||
|
||||
User-Agent: GPTBot
|
||||
Disallow: /
|
||||
|
||||
User-agent: *
|
||||
Disallow: /impressum
|
||||
Allow: /
|
||||
Allow: /resume
|
||||
Allow: /publications
|
||||
Allow: /consulting
|
||||
Sitemap: https://www.tudattr.dev/sitemap.xml
|
||||
|
||||
1
assets/site.webmanifest
Normal file
@@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
21
assets/sitemap.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://www.tudattr.dev/</loc>
|
||||
<lastmod>2025-01-12</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.tudattr.dev/resume</loc>
|
||||
<lastmod>2025-01-12</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.tudattr.dev/publications</loc>
|
||||
<lastmod>2025-01-12</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -554,6 +554,40 @@ video {
|
||||
--tw-contain-style: ;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.container {
|
||||
max-width: 640px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.container {
|
||||
max-width: 1024px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.container {
|
||||
max-width: 1280px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
.container {
|
||||
max-width: 1536px;
|
||||
}
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
@@ -570,21 +604,11 @@ video {
|
||||
inset-inline-start: -0.375rem;
|
||||
}
|
||||
|
||||
.mx-16 {
|
||||
margin-left: 4rem;
|
||||
margin-right: 4rem;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.my-4 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.my-8 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
@@ -618,10 +642,6 @@ video {
|
||||
margin-inline-start: 2rem;
|
||||
}
|
||||
|
||||
.mt-0 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
@@ -642,19 +662,6 @@ video {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.inline-flex {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.size-auto {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.h-24 {
|
||||
height: 6rem;
|
||||
}
|
||||
@@ -675,18 +682,10 @@ video {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.w-16 {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.w-24 {
|
||||
width: 6rem;
|
||||
}
|
||||
@@ -695,28 +694,15 @@ video {
|
||||
width: 0.75rem;
|
||||
}
|
||||
|
||||
.w-fit {
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-96 {
|
||||
width: 24rem;
|
||||
}
|
||||
|
||||
.min-w-fit {
|
||||
min-width: -moz-fit-content;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.min-w-full {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.max-w-40 {
|
||||
max-width: 10rem;
|
||||
}
|
||||
@@ -725,8 +711,8 @@ video {
|
||||
max-width: 2rem;
|
||||
}
|
||||
|
||||
.max-w-screen-xl {
|
||||
max-width: 1280px;
|
||||
.max-w-md {
|
||||
max-width: 28rem;
|
||||
}
|
||||
|
||||
.max-w-sm {
|
||||
@@ -737,6 +723,18 @@ video {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -749,10 +747,22 @@ video {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.content-center {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -761,42 +771,38 @@ video {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-stretch {
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
|
||||
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.space-x-1 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.25rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.space-x-8 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(2rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
.space-y-8 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(2rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.self-center {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
.self-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
@@ -811,6 +817,20 @@ video {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-md {
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.rounded-e-lg {
|
||||
border-start-end-radius: 0.5rem;
|
||||
border-end-end-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-s-lg {
|
||||
border-start-start-radius: 0.5rem;
|
||||
border-end-start-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.border {
|
||||
border-width: 1px;
|
||||
}
|
||||
@@ -827,69 +847,69 @@ video {
|
||||
border-inline-start-width: 1px;
|
||||
}
|
||||
|
||||
.border-gray-200 {
|
||||
.border-gray-600 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
||||
border-color: rgb(75 85 99 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-white {
|
||||
.border-gray-700 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
||||
border-color: rgb(55 65 81 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
||||
.border-gray-900 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(17 24 39 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-100 {
|
||||
.bg-blue-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
||||
background-color: rgb(30 58 138 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-200 {
|
||||
.bg-gray-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-50 {
|
||||
.bg-gray-800 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-100 {
|
||||
.bg-gray-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
|
||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-indigo-100 {
|
||||
.bg-green-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(224 231 255 / var(--tw-bg-opacity));
|
||||
background-color: rgb(20 83 45 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-pink-100 {
|
||||
.bg-indigo-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(252 231 243 / var(--tw-bg-opacity));
|
||||
background-color: rgb(49 46 129 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-purple-100 {
|
||||
.bg-pink-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(243 232 255 / var(--tw-bg-opacity));
|
||||
background-color: rgb(131 24 67 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-red-100 {
|
||||
.bg-purple-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
||||
background-color: rgb(88 28 135 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
.bg-red-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
background-color: rgb(127 29 29 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-yellow-100 {
|
||||
.bg-yellow-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(254 249 195 / var(--tw-bg-opacity));
|
||||
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gradient-to-br {
|
||||
@@ -935,22 +955,10 @@ video {
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.p-1 {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.p-5 {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
@@ -965,6 +973,11 @@ video {
|
||||
padding-right: 0.625rem;
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.px-5 {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
@@ -1058,6 +1071,10 @@ video {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.leading-none {
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -1070,14 +1087,24 @@ video {
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.text-blue-800 {
|
||||
.text-blue-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(30 64 175 / var(--tw-text-opacity));
|
||||
color: rgb(147 197 253 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-cyan-600 {
|
||||
.text-blue-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(8 145 178 / var(--tw-text-opacity));
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-cyan-400 {
|
||||
--tw-text-opacity: 1;
|
||||
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 {
|
||||
@@ -1090,44 +1117,34 @@ video {
|
||||
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 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-green-800 {
|
||||
.text-green-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(22 101 52 / var(--tw-text-opacity));
|
||||
color: rgb(134 239 172 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-indigo-800 {
|
||||
.text-indigo-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(55 48 163 / var(--tw-text-opacity));
|
||||
color: rgb(165 180 252 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-pink-800 {
|
||||
.text-pink-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(157 23 77 / var(--tw-text-opacity));
|
||||
color: rgb(249 168 212 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-purple-800 {
|
||||
.text-purple-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 33 168 / var(--tw-text-opacity));
|
||||
color: rgb(216 180 254 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-800 {
|
||||
.text-red-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(153 27 27 / var(--tw-text-opacity));
|
||||
color: rgb(252 165 165 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-transparent {
|
||||
@@ -1139,9 +1156,9 @@ video {
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-yellow-800 {
|
||||
.text-yellow-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(133 77 14 / var(--tw-text-opacity));
|
||||
color: rgb(253 224 71 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.shadow {
|
||||
@@ -1156,6 +1173,12 @@ video {
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-sm {
|
||||
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.grayscale {
|
||||
--tw-grayscale: grayscale(100%);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
@@ -1175,9 +1198,11 @@ video {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.hover\:bg-gray-100:hover {
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
.hover\:bg-gray-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
||||
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-gradient-to-br:hover {
|
||||
@@ -1198,20 +1223,40 @@ video {
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
.focus\:z-10:focus {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.focus\:text-white:focus {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.focus\:outline-none:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.focus\:ring-2:focus {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
.focus\:ring-4:focus {
|
||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
.focus\:ring-cyan-300:focus {
|
||||
.focus\:ring-blue-500:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(103 232 249 / var(--tw-ring-opacity));
|
||||
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-cyan-800:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(21 94 117 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.group:hover .group-hover\:from-green-400 {
|
||||
@@ -1229,24 +1274,28 @@ video {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.sm\:flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sm\:space-x-8 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(2rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0px * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.sm\:text-center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.md\:flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.md\:items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.md\:justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.md\:bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -1259,11 +1308,6 @@ video {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.md\:text-blue-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(29 78 216 / var(--tw-text-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
@@ -1273,152 +1317,3 @@ video {
|
||||
}
|
||||
}
|
||||
|
||||
.rtl\:space-x-reverse:where([dir="rtl"], [dir="rtl"] *) > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 1;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:border-gray-600 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(75 85 99 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.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-800 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(31 41 55 / 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-cyan-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(34 211 238 / 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));
|
||||
}
|
||||
|
||||
.dark\:hover\:bg-gray-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-cyan-800:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(21 94 117 / var(--tw-ring-opacity));
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.md\:dark\:text-blue-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind utilities;
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 170 KiB |
BIN
resources/screenshot.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
3
rust-toolchain.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "1.85.0"
|
||||
components = ["rustfmt", "clippy", "rust-analyzer"]
|
||||
20
scripts/docker-push.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
TAG=$(git describe --exact-match --tags)
|
||||
LOCAL_IMAGE="athome"
|
||||
REGISTRY="mos4"
|
||||
REMOTE_IMAGE="athome"
|
||||
DOCKERFILE="./Dockerfile"
|
||||
|
||||
echo "DOCKERFILE: $DOCKERFILE"
|
||||
echo "TAG: $TAG"
|
||||
echo "LOCAL_IMAGE: $LOCAL_IMAGE"
|
||||
echo "REGISTRY: $REGISTRY"
|
||||
echo "REMOTE_IMAGE: $REMOTE_IMAGE"
|
||||
|
||||
#docker buildx build --platform linux/amd64,linux/arm64 -f ${DOCKERFILE} -t \
|
||||
# ${REGISTRY}/${REMOTE_IMAGE}:${TAG} . --push
|
||||
|
||||
docker buildx build --platform linux/amd64 -f ${DOCKERFILE} \
|
||||
-t ${REGISTRY}/${REMOTE_IMAGE}:${TAG} \
|
||||
-t ${REGISTRY}/${REMOTE_IMAGE}:latest . --push
|
||||
@@ -1,4 +1,5 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_i18n::t;
|
||||
|
||||
#[derive(PartialEq, Props, Clone)]
|
||||
pub struct PProps {
|
||||
@@ -13,20 +14,33 @@ pub fn P(props: PProps) -> Element {
|
||||
div {
|
||||
class: "{props.class}",
|
||||
p {
|
||||
class: "mb-2 font-normal text-gray-900 dark:text-white",
|
||||
class: "mb-2 font-normal text-white",
|
||||
{props.children}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Title(props: PProps) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "{props.class}",
|
||||
h1 {
|
||||
class: "mb-2 text-2xl font-bold tracking-tight text-white",
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn H1(props: PProps) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "{props.class}",
|
||||
h1 {
|
||||
class: "mb-4 text-3xl font-extrabold text-gray-900 dark:text-white md:text-5xl lg:text-6xl",
|
||||
class: "mb-4 text-3xl font-extrabold text-white md:text-5xl lg:text-6xl",
|
||||
span {
|
||||
class: "text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400",
|
||||
{props.children}
|
||||
@@ -42,7 +56,7 @@ pub fn H4(props: PProps) -> Element {
|
||||
class: "{props.class}",
|
||||
class: "mb-4",
|
||||
h3 {
|
||||
class: "text-lg uppercase text-cyan-600 dark:text-cyan-400 tracking-widest mb-4 font-bold",
|
||||
class: "text-lg uppercase text-cyan-400 tracking-widest mb-4 font-bold",
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +69,7 @@ pub fn H5(props: PProps) -> Element {
|
||||
div {
|
||||
class: "{props.class}",
|
||||
h5 {
|
||||
class: "mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white",
|
||||
class: "mb-2 text-2xl font-bold tracking-tight text-white",
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
@@ -66,32 +80,55 @@ pub fn H5(props: PProps) -> Element {
|
||||
pub struct CardProp {
|
||||
#[props(default = "".to_string())]
|
||||
class: String,
|
||||
name: String,
|
||||
gender: String,
|
||||
picture: String,
|
||||
children: Element,
|
||||
}
|
||||
|
||||
pub fn Card(prop: CardProp) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex flex-col py-10 items-center min-w-fit",
|
||||
class: "flex flex-col py-10 items-center min-w-fit text-white bg-gradient-to-r from-purple-500 to-blue-500 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-cyan-800 rounded-lg px-5 py-2.5 min-w-fit max-w-8",
|
||||
div {
|
||||
class: "text-white bg-gradient-to-r from-purple-500 to-blue-500 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-cyan-300 dark:focus:ring-cyan-800 rounded-lg px-5 py-2.5 min-w-fit max-w-8 center",
|
||||
{ prop.children }
|
||||
class: "pb-4",
|
||||
div {
|
||||
class: "justify-between",
|
||||
Picture {src: "{prop.picture}"},
|
||||
}
|
||||
}
|
||||
Title { "{prop.name}", span { class: "text-grey-500 text-lg", " {prop.gender}" } },
|
||||
{ prop.children }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props, Clone)]
|
||||
struct PictureProp {
|
||||
#[props(default = "".to_string())]
|
||||
class: String,
|
||||
src: String,
|
||||
}
|
||||
|
||||
fn Picture(prop: PictureProp) -> Element {
|
||||
rsx! {
|
||||
img {
|
||||
class: "w-24 h-24 rounded-full shadow-lg",
|
||||
src: "{prop.src}",
|
||||
alt: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn UnderConstruction() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class:"rounded flex justify-between w-full p-4 border-b border-gray-200 bg-gray-50 dark:bg-gray-700 dark:border-gray-600 my-8",
|
||||
class:"rounded justify-between w-full p-4 border-b bg-gray-700 border-gray-600 my-8",
|
||||
div {
|
||||
class:"flex items-center mx-auto",
|
||||
class:"items-center mx-auto",
|
||||
p {
|
||||
class:"flex items-center text-sm font-normal text-gray-500 dark:text-gray-400",
|
||||
span {
|
||||
"This site is currenlty under construction",
|
||||
}
|
||||
class:"items-center text-sm font-normal text-gray-400",
|
||||
span { { t!("component_under_construction") } }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +137,108 @@ pub fn UnderConstruction() -> Element {
|
||||
|
||||
pub fn HR() -> Element {
|
||||
rsx! {
|
||||
hr { class:"h-px my-8 bg-gray-200 border-0 dark:bg-gray-700"}
|
||||
hr { class:"h-px my-8 border-0 bg-gray-700"}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Props)]
|
||||
pub struct BoldingProp {
|
||||
authors: String,
|
||||
patterns: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn Bolding(prop: BoldingProp) -> Element {
|
||||
let mut elements = vec![];
|
||||
let mut last_index = 0;
|
||||
let authors_text = &prop.authors;
|
||||
|
||||
let mut matches: Vec<(usize, usize, &str)> = vec![];
|
||||
|
||||
for pattern in &prop.patterns {
|
||||
for (start, _) in authors_text.match_indices(pattern) {
|
||||
matches.push((start, start + pattern.len(), pattern));
|
||||
}
|
||||
}
|
||||
|
||||
matches.sort_by_key(|(start, _, _)| *start);
|
||||
|
||||
for (start, end, matched_pattern) in matches {
|
||||
if last_index < start {
|
||||
elements.push(rsx! { "{&authors_text[last_index..start]}" });
|
||||
}
|
||||
|
||||
elements.push(rsx! { b { "{matched_pattern}" } });
|
||||
last_index = end;
|
||||
}
|
||||
|
||||
if last_index < authors_text.len() {
|
||||
elements.push(rsx! { "{&authors_text[last_index..]}" });
|
||||
}
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
P {
|
||||
for i in elements {
|
||||
{i}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Props)]
|
||||
pub struct UrlingProp {
|
||||
#[props(default = "".to_string())]
|
||||
class: String,
|
||||
text: String,
|
||||
patterns: Vec<String>,
|
||||
url: String,
|
||||
#[props(default = true)]
|
||||
new_tab: bool,
|
||||
}
|
||||
|
||||
pub fn Urling(prop: UrlingProp) -> Element {
|
||||
let mut elements = vec![];
|
||||
let mut last_index = 0;
|
||||
let text = &prop.text;
|
||||
|
||||
let mut matches: Vec<(usize, usize, &str)> = vec![];
|
||||
|
||||
for pattern in &prop.patterns {
|
||||
for (start, _) in text.match_indices(pattern) {
|
||||
matches.push((start, start + pattern.len(), pattern));
|
||||
}
|
||||
}
|
||||
|
||||
matches.sort_by_key(|(start, _, _)| *start);
|
||||
|
||||
for (start, end, matched_pattern) in matches {
|
||||
if last_index < start {
|
||||
elements.push(rsx! { "{&text[last_index..start]}" });
|
||||
}
|
||||
|
||||
elements.push(rsx! {
|
||||
Link {
|
||||
class: "{prop.class}",
|
||||
to: "{prop.url}",
|
||||
new_tab: prop.new_tab,
|
||||
"{matched_pattern}"
|
||||
}
|
||||
});
|
||||
last_index = end;
|
||||
}
|
||||
|
||||
if last_index < text.len() {
|
||||
elements.push(rsx! { "{&text[last_index..]}" });
|
||||
}
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
P {
|
||||
for i in elements {
|
||||
{i}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
175
src/cv.rs
@@ -1,4 +1,5 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_i18n::t;
|
||||
|
||||
use crate::components::{H4, HR};
|
||||
|
||||
@@ -6,16 +7,25 @@ use crate::components::{H4, HR};
|
||||
pub fn CV() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex flex-col ",
|
||||
Introduction {},
|
||||
class: "flex flex-col",
|
||||
div {
|
||||
class: "flex flex-col sm:flex-row justify-center items-center sm:space-x-8 space-y-8 sm:space-y-0",
|
||||
img {
|
||||
class: "rounded-full w-24 h-24",
|
||||
alt: "headshot",
|
||||
src: asset!("./assets/pictures/headshot.webp")
|
||||
}
|
||||
Introduction {},
|
||||
Socials {}
|
||||
},
|
||||
|
||||
HR {}
|
||||
div {
|
||||
class: "flex justify-between",
|
||||
class: "flex flex-col justify-between sm:flex-row",
|
||||
WorkExperience {},
|
||||
Miscellaneous {},
|
||||
},
|
||||
HR {},
|
||||
Socials {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,18 +34,8 @@ fn Introduction() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex",
|
||||
img {
|
||||
class: "rounded-full w-16 h-16 mx-16",
|
||||
src: "/pictures/portrait.webp"
|
||||
}
|
||||
P {
|
||||
"While studying for my bachelors degree I accumulated a lot of
|
||||
industry and academic experience. My professional and personal
|
||||
intererests are DevOps/IaC, Systems/Software Security and Computer
|
||||
Networking. All of which I deepen in personal projects such as my
|
||||
homelab and CTF challenges."
|
||||
}
|
||||
}
|
||||
P { { t!("cv_introduction_0") } },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,56 +43,32 @@ fn WorkExperience() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "ms-8 max-w-3/4",
|
||||
H4 { "Work Experience" },
|
||||
H4 { { t!("cv_workexperience_title") } },
|
||||
ol {
|
||||
class:"relative border-s border-gray-200 dark:border-gray-700",
|
||||
CVEntry {time: "2021 - now", title: "Research Assistant @ UDE",
|
||||
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()],
|
||||
description: t!("cv_workexperience_dd_devops_description")
|
||||
},
|
||||
CVEntry {time: t!("cv_workexperience_ra_ude_time"), title: t!("cv_workexperience_ra_ude_title"),
|
||||
technologies: vec!["Rust".to_string(), "Python".to_string(), "P4".to_string(), "Linux".to_string(), "Docker".to_string(), "Kubernetes".to_string()],
|
||||
description:
|
||||
"While working for the Network Communication System Research
|
||||
Group at the University Duisburg-Essen as a research assistant
|
||||
I've assisted in research around software defined networking,
|
||||
5G, congestion control algorithms and federated machine learning.
|
||||
I've established and managed the research groups on-premise and
|
||||
cloud infractructure, inventory system and online presence."
|
||||
description: t!("cv_workexperience_ra_ude_description")
|
||||
},
|
||||
CVEntry {time: "2021 - 2022", title: "Mentoring @ UDE",
|
||||
CVEntry {time: t!("cv_workexperience_mentoring_ude_time"), title: t!("cv_workexperience_mentoring_ude_title"),
|
||||
technologies: vec!["Powerpoint".to_string()],
|
||||
description:
|
||||
"As a mentor for students enrolling into the computer
|
||||
science program of the University Duisburg-Essen I
|
||||
introduced groups of ~20 freshmen to their new academic
|
||||
environment at the beginning of each semester. Offering
|
||||
additional organizational and technical guidance for
|
||||
their first year in university."
|
||||
description: t!("cv_workexperience_mentoring_ude_description")
|
||||
},
|
||||
CVEntry {time: "2018 - 2020", title: "Software Engineer @ gefeba Engineering GmbH",
|
||||
CVEntry {time: t!("cv_workexperience_se2_gefeba_time"), title: t!("cv_workexperience_se2_gefeba_title"),
|
||||
technologies: vec!["C#".to_string(), "Angular".to_string(), "bootstrap".to_string(), "Entity Framework".to_string()],
|
||||
description:
|
||||
"As a software engineer at gefeba Engineering I worked
|
||||
on the companies main software product, which was a
|
||||
frame-based data exchange system to monitor industry
|
||||
machinery using C# and Entity Framework. Another project
|
||||
I worked on was a real time log visualization application
|
||||
for the same machinery."
|
||||
description: t!("cv_workexperience_se2_gefeba_description")
|
||||
},
|
||||
CVEntry {time: "2016 - 2019", title: "Student Council Member @ UDE",
|
||||
CVEntry {time: t!("cv_workexperience_student_fse_time"), title: t!("cv_workexperience_student_fse_title"),
|
||||
technologies: vec!["Linux".to_string(), "Networking".to_string(), "LaTeX".to_string()],
|
||||
description:
|
||||
"As a student council member I participated in faculty
|
||||
committees and organized social events. My
|
||||
main responsibilities as a member were the management
|
||||
of the IT infrastructure and supporting students, be it
|
||||
organizationally or subject-specific."
|
||||
description: t!("cv_workexperience_student_fse_description")
|
||||
},
|
||||
CVEntry {time: "2013 - 2015", title: "Software Engineer @ gefeba Engineering GmbH",
|
||||
CVEntry {time: t!("cv_workexperience_se1_gefeba_time"), title: t!("cv_workexperience_se1_gefeba_title"),
|
||||
technologies: vec!["C#".to_string(), "HTML".to_string(), "Javascript".to_string(), "CSS".to_string()],
|
||||
description:
|
||||
"After a school internship I got offered a job as a
|
||||
Software Engineer. I mostly worked on internal ERP
|
||||
projects, designed a tool which aided in managing
|
||||
project related mail traffic and worked on the
|
||||
internal master data management tool."
|
||||
description: t!("cv_workexperience_se1_gefeba_description")
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -114,10 +90,10 @@ fn Miscellaneous() -> Element {
|
||||
fn Education() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
H4 {"Education"},
|
||||
H4 { { t!("cv_education_title") } },
|
||||
Entry {
|
||||
title: "BSc Systems Engineering",
|
||||
time { class:"mb-1 text-sm font-normal leading-none text-gray-400 dark:text-gray-500", "2015 - now"},
|
||||
title: t!("cv_education_bachelor_title"),
|
||||
time { class:"mb-1 text-sm font-normal leading-none text-gray-500", { t!("cv_education_bachelor_time") } },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -126,17 +102,18 @@ fn Education() -> Element {
|
||||
fn Skills() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
H4 {"Skills"},
|
||||
H4 { { t!("cv_skills_title") }},
|
||||
Entry {
|
||||
title: "DevOps",
|
||||
P { "Ansible" },
|
||||
P { "Kubernetes" },
|
||||
P { "GitOps" },
|
||||
}Entry {
|
||||
title: "Software Development",
|
||||
P { "Rust" },
|
||||
P { "Python" },
|
||||
P { "C#" },
|
||||
title: t!("cv_skills_devops_title"),
|
||||
P { { t!("cv_skills_devops_ansible") } },
|
||||
P { { t!("cv_skills_devops_kubernetes") } },
|
||||
P { { t!("cv_skills_devops_gitops") } },
|
||||
}
|
||||
Entry {
|
||||
title: t!("cv_skills_software_engineering_title"),
|
||||
P { { t!("cv_skills_software_engineering_rust") } },
|
||||
P { { t!("cv_skills_software_engineering_python") } },
|
||||
P { { t!("cv_skills_software_engineering_csharp") } },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,12 +122,12 @@ fn Skills() -> Element {
|
||||
fn Languages() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
H4 {"Languages"},
|
||||
H4 { { t!("cv_languages_title") } },
|
||||
Entry {
|
||||
P { "German (Native)" },
|
||||
P { "English (C2)" },
|
||||
P { "Vietnamese (B1)" },
|
||||
P { "Japanese (A1)" },
|
||||
P { {t!("cv_languages_german") } },
|
||||
P { {t!("cv_languages_english") } },
|
||||
P { {t!("cv_languages_vietnamese") } },
|
||||
P { {t!("cv_languages_japanese") } },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,12 +136,12 @@ fn Languages() -> Element {
|
||||
fn Interests() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
H4 {"Interests"}
|
||||
H4 { { t!("cv_interests_title") } },
|
||||
Entry {
|
||||
P { "Coffee" },
|
||||
P { "Tech/IT" },
|
||||
P { "Guitar" },
|
||||
P { "Mechanical Keyboards" },
|
||||
P { { t!("cv_interests_coffee") } },
|
||||
P { { t!("cv_interests_tech_it") } },
|
||||
P { { t!("cv_interests_guitar") } },
|
||||
P { { t!("cv_interests_mechanical_keyboards") } },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +160,7 @@ fn Entry(props: EntryProps) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "{props.class}",
|
||||
h6 { class: "font-semibold text-gray-900 dark:text-white", "{props.title}"}
|
||||
h6 { class: "font-semibold text-white", "{props.title}"}
|
||||
{props.children},
|
||||
}
|
||||
}
|
||||
@@ -206,16 +183,16 @@ fn CVEntry(props: CVEntryProps) -> Element {
|
||||
li {
|
||||
class: "max-w-xl",
|
||||
class: "{props.class}",
|
||||
div { class:"absolute w-3 h-3 bg-gray-200 rounded-full mt-1.5 -start-1.5 border border-white dark:border-gray-900 dark:bg-gray-700"},
|
||||
time { class:"mb-1 text-sm font-normal leading-none text-gray-400 dark:text-gray-500", "{props.time}"},
|
||||
h6 { class: "text-lg font-semibold text-gray-900 dark:text-white", "{props.title}"}
|
||||
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",
|
||||
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-500 dark:text-gray-400", "{props.description}"},
|
||||
p { class:"text-base font-normal text-gray-400", "{props.description}"},
|
||||
{props.children}
|
||||
}
|
||||
}
|
||||
@@ -226,8 +203,7 @@ 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 ",
|
||||
class: "{badge_color}",
|
||||
class:"text-xs font-medium me-2 px-2.5 py-0.5 rounded {badge_color}",
|
||||
"{text}"
|
||||
}
|
||||
}
|
||||
@@ -235,14 +211,14 @@ fn RandomBadge(text: String) -> Element {
|
||||
|
||||
fn random_badge_color(seed: usize) -> String {
|
||||
let colors = [
|
||||
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
|
||||
"bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",
|
||||
"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
|
||||
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300",
|
||||
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300",
|
||||
"bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-300",
|
||||
"bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300",
|
||||
"bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300",
|
||||
"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()
|
||||
@@ -251,7 +227,12 @@ fn random_badge_color(seed: usize) -> String {
|
||||
fn Socials() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
"todo!()"
|
||||
class: "ms-8 max-w-3/4",
|
||||
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" } }},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +241,7 @@ fn Socials() -> Element {
|
||||
fn P(children: Element) -> Element {
|
||||
rsx! {
|
||||
p {
|
||||
class: "text-base font-normal text-gray-500 dark:text-gray-400",
|
||||
class: "text-base font-normal text-gray-400",
|
||||
{children},
|
||||
}
|
||||
}
|
||||
|
||||
67
src/home.rs
@@ -1,66 +1,31 @@
|
||||
use crate::components::{Card, H5, P};
|
||||
use crate::components::{Card, P};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_i18n::t;
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
rsx! {
|
||||
Card {
|
||||
div {
|
||||
class: "p-5",
|
||||
div {
|
||||
class: "pb-4",
|
||||
div {
|
||||
class: "flex justify-between",
|
||||
Picture {src: "/pictures/comfy.webp"},
|
||||
}
|
||||
}
|
||||
H5 { "Tuan-Dat Tran", span { class: "text-grey-600 dark:text-grey-500 text-lg", " (He/Him)" } },
|
||||
div {
|
||||
class: "container mx-auto p-4 flex items-center justify-center max-w-md w-full",
|
||||
Card {
|
||||
name: t!("home_card_name"),
|
||||
gender: t!("home_card_gender"),
|
||||
picture: asset!("./assets/pictures/headshot.webp"),
|
||||
div {
|
||||
class: "py-4",
|
||||
div {
|
||||
class: "mb-2",
|
||||
P { "Hey there! 👋🏻👋🏼👋🏽👋🏾👋🏿" },
|
||||
P { "Welcome to my little place on the internet." },
|
||||
P { "While you're here, why don't you check out my other projects over on my ",
|
||||
Link {
|
||||
to: "https://git.tudattr.dev/explore/repos",
|
||||
new_tab: true,
|
||||
class: "inline-flex items-center font-medium hover:underline",
|
||||
"gitea"},
|
||||
"?"
|
||||
},
|
||||
for line in t!("home_card_text").split("\n").into_iter() {
|
||||
P {{line}}
|
||||
}
|
||||
},
|
||||
div {
|
||||
P { "I also offer IT-related consulting." },
|
||||
P { "Have a look at my CV and contact me if you're interested." },
|
||||
}
|
||||
},
|
||||
Link {
|
||||
to: "mailto:tuan-dat.tran@tudattr.dev",
|
||||
class: "w-fit relative inline-flex items-center justify-center p-1 mb-2 me-2 overflow-hidden text-sm font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-green-400 to-blue-600 group-hover:from-green-400 group-hover:to-blue-600 hover:text-white dark:text-white w-fill",
|
||||
p {
|
||||
class: "p-1",
|
||||
"Get in touch."
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props, Clone)]
|
||||
struct PictureProp {
|
||||
#[props(default = "".to_string())]
|
||||
class: String,
|
||||
src: String,
|
||||
}
|
||||
|
||||
fn Picture(prop: PictureProp) -> Element {
|
||||
rsx! {
|
||||
img {
|
||||
class: "w-24 h-24 rounded-full shadow-lg",
|
||||
src: "{prop.src}",
|
||||
alt: "",
|
||||
},
|
||||
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",
|
||||
{ t!("home_card_contact_button") }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,59 @@
|
||||
use dioxus::prelude::*;
|
||||
use tracing::info;
|
||||
use dioxus_i18n::t;
|
||||
|
||||
use crate::components::{H1, HR, P};
|
||||
|
||||
#[component]
|
||||
pub fn Impressum() -> Element {
|
||||
let mut show_impressum = use_signal(|| false);
|
||||
let mut impressum = use_signal(Vec::<String>::new);
|
||||
let mut contact = use_signal(Vec::<String>::new);
|
||||
|
||||
rsx! {
|
||||
if show_impressum() {
|
||||
div {
|
||||
div {
|
||||
class: "flex flex-col items-center",
|
||||
button {
|
||||
onclick: move |_| {
|
||||
info!("Hide impressum.");
|
||||
},
|
||||
H1 { "Impressum" },
|
||||
},
|
||||
P { "Tuan-Dat Tran" },
|
||||
P { "c/o AutorenServices.de" },
|
||||
P { "Birkenallee 24" },
|
||||
P { "36037 Fulda" },
|
||||
}
|
||||
HR {}
|
||||
div {
|
||||
class: "flex flex-col items-center",
|
||||
P { "tuan-dat.tran@tudattr.dev" },
|
||||
P { "+49 176 83468388" },
|
||||
}
|
||||
} else {
|
||||
div {
|
||||
class: "flex flex-col items-center p-3",
|
||||
button {
|
||||
onclick: move |_| async move {
|
||||
info!("Showing impressum.");
|
||||
show_impressum.set(true);
|
||||
if let Ok(data) = get_impressum().await {
|
||||
impressum.set(data.clone());
|
||||
}
|
||||
if let Ok(data) = get_contact().await {
|
||||
contact.set(data.clone());
|
||||
}
|
||||
},
|
||||
|
||||
H1 { "Show Impressum" },
|
||||
H1 { { t!("impressum_on") } },
|
||||
},
|
||||
},
|
||||
div {
|
||||
class: "flex flex-col items-center",
|
||||
for line in impressum() {
|
||||
P { {line} }
|
||||
}
|
||||
}
|
||||
if !impressum.read().is_empty() { HR{} },
|
||||
div {
|
||||
class: "flex flex-col items-center",
|
||||
for line in contact() {
|
||||
P { {line} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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(),
|
||||
])
|
||||
}
|
||||
|
||||
159
src/languages/de-DE.ftl
Normal file
@@ -0,0 +1,159 @@
|
||||
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
|
||||
home_card_name = Tuan-Dat Tran
|
||||
home_card_gender = {""}
|
||||
home_card_text =
|
||||
Hallihallo! 👋🏻👋🏼👋🏽👋🏾👋🏿
|
||||
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_0 =
|
||||
DevOps-Engineer und Softwareentwickler mit starkem akademischen Hintergrund
|
||||
in Netzwerktechnologien und Softwareentwicklung. Spezialisiert auf Kubernetes,
|
||||
Ansible, Azure und moderne Cloud-Technologien. Praxisnahe
|
||||
Forschungserfahrung in Software-Defined Networking, 5G und Maschinellem Lernen.
|
||||
Leidenschaft für effiziente IT-Infrastrukturen, Automatisierung und
|
||||
innovative Softwarelösungen.
|
||||
cv_workexperience_title = Berufserfahrung
|
||||
cv_workexperience_se1_gefeba_title = Software Entwickler @ gefeba Engineering GmbH
|
||||
cv_workexperience_se1_gefeba_time = 2013 - 2015
|
||||
cv_workexperience_se1_gefeba_description = Nach einem Schulpraktikum wurde mir
|
||||
eine Stelle als Software Entwickler angeboten. Ich arbeitete hauptsächlich
|
||||
an internen ERP-Projekten, entwickelte ein Tool, das bei der Verwaltung des
|
||||
projektbezogenen E-Mail-Verkehrs half, und arbeitete an dem internen
|
||||
Stammdatenmanagement-Tool.
|
||||
cv_workexperience_student_fse_title = Fachschaftsratsmitglied @ UDE (Ehrenamt)
|
||||
cv_workexperience_student_fse_time = 2016 - 2019
|
||||
cv_workexperience_student_fse_description = Als Fachschaftsratsmitglied wirkte
|
||||
ich in Fakultätsausschüssen mit und organisierte soziale Veranstaltungen.
|
||||
Meine Hauptaufgaben als Mitglied waren die Verwaltung der IT-Infrastruktur
|
||||
und die Unterstützung der Studierenden bei organisatorischen und
|
||||
fachspezifischen Themen.
|
||||
cv_workexperience_se2_gefeba_title = Software Entwickler @ gefeba Engineering GmbH
|
||||
cv_workexperience_se2_gefeba_time = 2018 - 2020
|
||||
cv_workexperience_se2_gefeba_description = Als Software Entwickler bei gefeba
|
||||
Engineering arbeitete ich an dem Hauptprodukt des Unternehmens, einem
|
||||
Frame-basierten Datenaustauschsystem zur Überwachung von Industriemaschinen
|
||||
mit C# und Entity Framework. Ein weiteres Projekt, an dem ich gearbeitet habe,
|
||||
war eine Echtzeit-Anwendung zur Visualisierung von Protokollen für dieselben
|
||||
Maschinen.
|
||||
cv_workexperience_mentoring_ude_title = Mentoring @ UDE
|
||||
cv_workexperience_mentoring_ude_time = 2021 - 2022
|
||||
cv_workexperience_mentoring_ude_description = Als Mentor für Studienanfänger
|
||||
im Studiengang Informatik der Universität Duisburg-Essen habe ich zu Beginn
|
||||
jedes Semesters Gruppen von ~20 Studienanfängern in ihr neues akademisches
|
||||
Umfeld eingeführt. Ich bot zusätzliche organisatorische und technische
|
||||
Unterstützung für das erste Jahr an der Universität an.
|
||||
cv_workexperience_ra_ude_title = Studentische Hilfskraft @ UDE
|
||||
cv_workexperience_ra_ude_time = 2021 - 2024
|
||||
cv_workexperience_ra_ude_description = Während meiner Tätigkeit bei der
|
||||
Network Communication System Research
|
||||
Group an der Universität Duisburg-Essen habe ich an der Forschung rund um
|
||||
Software Defined Networking, 5G, Staukontrollalgorithmen und föderiertes
|
||||
maschinelles Lernen mitgearbeitet. Ich habe die On-Premise- und Cloud-
|
||||
Infrastruktur, das Inventarsystem und die Online-Präsenz der Forschungsgruppe
|
||||
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_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
|
||||
cv_skills_devops_kubernetes = Kubernetes
|
||||
cv_skills_devops_gitops = GitOps
|
||||
cv_skills_software_engineering_title = Programmiersprachen
|
||||
cv_skills_software_engineering_rust = Rust
|
||||
cv_skills_software_engineering_python = Python
|
||||
cv_skills_software_engineering_csharp = C#
|
||||
cv_languages_title = Sprachkenntnisse
|
||||
cv_languages_german = Deutsch (Muttersprache)
|
||||
cv_languages_english = Englisch (C2)
|
||||
cv_languages_vietnamese = Vietnamesisch (B1)
|
||||
cv_languages_japanese = Japanisch (A1)
|
||||
cv_interests_title = Interessen
|
||||
cv_interests_coffee = Kaffee
|
||||
cv_interests_tech_it = Tech/IT
|
||||
cv_interests_guitar = Gitarre
|
||||
cv_interests_mechanical_keyboards = Mechanische Tastaturen
|
||||
publications_projects_publications_title = Veröffentlichungen
|
||||
publications_projects_publications_rpm_title = RPM: Reverse Path Congestion Marking on P4 Programmable Switches
|
||||
publications_projects_publications_rpm_authors = N. Baganal-Krishna, T.-D. Tran, R. Kundel and A. Rizk
|
||||
publications_projects_publications_rpm_conference = IEEE LCN 2023
|
||||
publications_projects_publications_rpm_url = https://doi.org/10.48550/arXiv.2307.09639
|
||||
publications_projects_publications_rpm_description = In diesem Artikel stellen
|
||||
wir Reverse Path Congestion Marking (RPM) vor, um die Reaktion auf
|
||||
Netzwerküberlastungen zu beschleunigen, ohne den End-Host-Stack zu verändern.
|
||||
RPM entkoppelt das Stausignal vom nachgelagerten Pfad nach dem Engpass,
|
||||
während die Stabilität der Staukontrollschleife erhalten bleibt. Wir zeigen,
|
||||
dass RPM die Durchsatzfairness für RTT bei heterogenen TCP-Flüssen sowie die
|
||||
Flussabwicklungszeit verbessert, insbesondere für kleine Data Center TCP
|
||||
(DCTCP)-Flows um P4 programmierbare ASIC-Switches.
|
||||
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.
|
||||
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
|
||||
Konfigurationen für Tools, die ich häufig verwende, sowie eine Dokumentation
|
||||
zur Einrichtung meines täglich genutzten Notebooks. Sie bieten eine Grundlage
|
||||
für jedes persönliche Linux-System, das ich einrichte, und ermöglichen
|
||||
Wiederholbarkeit, was den Prozess der Einrichtung eines Linux-basierten
|
||||
Systems vereinfacht.
|
||||
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
|
||||
verwende Ansible, um mein Homelab einzurichten, das mir als Plattform zum
|
||||
Ausprobieren und Lernen neuer Technologien dient.
|
||||
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 = "© 2026 "
|
||||
footer_name = Tuan-Dat Tran
|
||||
footer_rights = . All Rights Reserved.
|
||||
footer_contact = Kontakt
|
||||
151
src/languages/en-GB.ftl
Normal file
@@ -0,0 +1,151 @@
|
||||
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
|
||||
home_card_name = Tuan-Dat Tran
|
||||
home_card_gender = (He/Him)
|
||||
home_card_text =
|
||||
Hey there! 👋🏻👋🏼👋🏽👋🏾👋🏿
|
||||
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_0 =
|
||||
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_workexperience_title = Work Experience
|
||||
cv_workexperience_se1_gefeba_title = Software Engineer @ gefeba Engineering GmbH
|
||||
cv_workexperience_se1_gefeba_time = 2013 - 2015
|
||||
cv_workexperience_se1_gefeba_description = After a school internship I got
|
||||
offered a job as a Software Engineer. I mostly worked on internal ERP
|
||||
projects, designed a tool which aided in managing project related mail
|
||||
traffic and worked on the internal master data management tool.
|
||||
cv_workexperience_student_fse_title = Student Council Member @ UDE (Volunteer)
|
||||
cv_workexperience_student_fse_time = 2016 - 2019
|
||||
cv_workexperience_student_fse_description = As a student council member I
|
||||
participated in faculty committees and organized social events. My main
|
||||
responsibilities as a member were the management of the IT infrastructure and
|
||||
supporting students, be it organizationally or subject-specific.
|
||||
cv_workexperience_se2_gefeba_title = Software Engineer @ gefeba Engineering GmbH
|
||||
cv_workexperience_se2_gefeba_time = 2018 - 2020
|
||||
cv_workexperience_se2_gefeba_description = As a software engineer at gefeba
|
||||
Engineering I worked on the companies main software product, which was a
|
||||
frame-based data exchange system to monitor industry machinery using C# and
|
||||
Entity Framework. Another project I worked on was a real time log
|
||||
visualization application for the same machinery.
|
||||
cv_workexperience_mentoring_ude_title = Mentoring @ UDE
|
||||
cv_workexperience_mentoring_ude_time = 2021 - 2022
|
||||
cv_workexperience_mentoring_ude_description = As a mentor for students
|
||||
enrolling into the computer science program of the University Duisburg-Essen I
|
||||
regularly introduced groups of ~20 freshmen to their new academic environment at the
|
||||
beginning of each semester. Offering additional organizational and technical
|
||||
guidance for their first year in university.
|
||||
cv_workexperience_ra_ude_title = Research Assistant @ UDE
|
||||
cv_workexperience_ra_ude_time = 2021 - 2024
|
||||
cv_workexperience_ra_ude_description = While working at the Network
|
||||
Communication System Research Group at the University Duisburg-Essen as a
|
||||
research assistant I've assisted in research around software defined
|
||||
networking, 5G, congestion control algorithms and federated machine learning.
|
||||
I've established and managed the research groups on-premise and cloud
|
||||
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_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
|
||||
cv_skills_devops_kubernetes = Kubernetes
|
||||
cv_skills_devops_gitops = GitOps
|
||||
cv_skills_software_engineering_title = Software Engineering
|
||||
cv_skills_software_engineering_rust = Rust
|
||||
cv_skills_software_engineering_python = Python
|
||||
cv_skills_software_engineering_csharp = C#
|
||||
cv_languages_title = Languages
|
||||
cv_languages_german = German (Native)
|
||||
cv_languages_english = English (C2)
|
||||
cv_languages_vietnamese = Vietnamese (B1)
|
||||
cv_languages_japanese = Japanese (A1)
|
||||
cv_interests_title = Interests
|
||||
cv_interests_coffee = Coffee
|
||||
cv_interests_tech_it = Tech/IT
|
||||
cv_interests_guitar = Guitar
|
||||
cv_interests_mechanical_keyboards = Mechanical Keyboards
|
||||
publications_projects_publications_title = Publications
|
||||
publications_projects_publications_rpm_title = RPM: Reverse Path Congestion Marking on P4 Programmable Switches
|
||||
publications_projects_publications_rpm_authors = N. Baganal-Krishna, T.-D. Tran, R. Kundel and A. Rizk
|
||||
publications_projects_publications_rpm_conference = IEEE LCN 2023
|
||||
publications_projects_publications_rpm_url = https://doi.org/10.48550/arXiv.2307.09639
|
||||
publications_projects_publications_rpm_description = In this paper, we present
|
||||
Reverse Path Congestion Marking (RPM) to accelerate the reaction to network
|
||||
congestion events without changing the end-host stack. RPM decouples the
|
||||
congestion signal from the downstream path after the bottleneck while
|
||||
maintaining the stability of the congestion control loop. We show that RPM
|
||||
improves throughput fairness for RTT on heterogeneous TCP flows as well as
|
||||
the flow completion time, especially for small Data Center TCP (DCTCP) flows
|
||||
around P4 programmable ASIC switches.
|
||||
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
|
||||
IoT fuzzers.
|
||||
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
|
||||
on how to set up my daily-use notebook. It provides a baseline for any
|
||||
personal Linux system I set up and allows for repeatability simplifies the
|
||||
process of setting up an ArchLinux based system.
|
||||
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
|
||||
platform for me to try out and learn new technologies.
|
||||
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 = "© 2026 "
|
||||
footer_name = Tuan-Dat Tran
|
||||
footer_rights = . All Rights Reserved.
|
||||
footer_contact = Contact
|
||||
@@ -1,25 +1,24 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::H1;
|
||||
use dioxus_i18n::t;
|
||||
|
||||
pub fn Footer() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
// ToolsUsed {},
|
||||
class: "container mx-auto pb-4",
|
||||
footer {
|
||||
class:"bg-white rounded-lg shadow dark:bg-gray-800 my-4:wa mb-4",
|
||||
class:"rounded-lg shadow bg-gray-800",
|
||||
div {
|
||||
class:"w-full mx-auto max-w-screen-xl p-4 md:flex md:items-center md:justify-between",
|
||||
class:"w-full mx-auto p-4 flex items-center justify-between",
|
||||
span {
|
||||
class:"text-sm text-gray-500 sm:text-center dark:text-gray-400",
|
||||
"© 2024 ",
|
||||
a { href: "#", class: "hover:underline", "Tuan-Dat Tran"},
|
||||
". All Rights Reserved."
|
||||
class:"text-sm sm:text-center text-gray-400",
|
||||
{ t!("footer_year") },
|
||||
a { href: "#", class: "hover:underline", { t!("footer_name") }},
|
||||
{ t!("footer_rights") }
|
||||
}
|
||||
ul {
|
||||
class:"flex flex-wrap items-center mt-3 text-sm font-medium text-gray-500 dark:text-gray-400 sm:mt-0",
|
||||
class:"flex flex-wrap items-center mt-3 text-sm font-medium text-gray-400 sm:mt-0",
|
||||
li {
|
||||
Link { to:"mailto:tuan-dat.tran@tudattr.dev", class:"hover:underline", "Contact" }
|
||||
Link { to:"mailto:tuan-dat.tran@tudattr.dev", class:"hover:underline", { t!("footer_contact") } }
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -28,42 +27,6 @@ pub fn Footer() -> Element {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn ToolsUsed() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class:"items-center bg-white rounded-lg shadow dark:bg-gray-800 p-4 my-4",
|
||||
div {
|
||||
H1 {class: "justify-center", "Tools used" },
|
||||
}
|
||||
div {
|
||||
class: "flex h-fill overflow-x-auto",
|
||||
Logo {
|
||||
src: "https://raw.githubusercontent.com/SAWARATSUKI/Logogs/main/Rust/Rust.png",
|
||||
alt: "Rust"
|
||||
},
|
||||
Logo {
|
||||
src: "https://raw.githubusercontent.com/SAWARATSUKI/Logogs/main/Tailwindcss/Tailwindcss6.png",
|
||||
alt: "Tailwindcss"
|
||||
},
|
||||
Logo {
|
||||
src: "https://raw.githubusercontent.com/SAWARATSUKI/Logogs/main/Html/HTML.png",
|
||||
alt: "HTML"
|
||||
},
|
||||
Logo {
|
||||
src: "https://raw.githubusercontent.com/Aikoyori/ProgrammingVTuberLogos/main/Docker/DockerLogo.png",
|
||||
alt: "Docker"
|
||||
},
|
||||
Logo {
|
||||
src: "https://raw.githubusercontent.com/Aikoyori/ProgrammingVTuberLogos/main/Neovim/NeovimLogo.png",
|
||||
alt: "NeoVim"
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Logo(src: String, alt: String) -> Element {
|
||||
rsx! {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_i18n::{prelude::i18n, t, unic_langid::langid};
|
||||
|
||||
use crate::Route;
|
||||
|
||||
@@ -6,30 +7,53 @@ pub fn Header() -> Element {
|
||||
rsx! {
|
||||
nav {
|
||||
div {
|
||||
class: "flex items-center justify-between p-4 w-96",
|
||||
class: "flex items-center justify-between p-4 flex-wrap mx-auto max-w-screen-xl",
|
||||
Link {
|
||||
to: Route::Home {},
|
||||
class:"flex items-center space-x-3 rtl:space-x-reverse",
|
||||
img { src:"/pictures/ClackCat_t.webp", class:"rounded-full h-8", alt:"Flowbite Logo" },
|
||||
span { class:"self-center text-2xl font-semibold whitespace-nowrap dark:text-white", "" }
|
||||
}
|
||||
div { class:"", id:"navbar-default",
|
||||
ul { class:"flex space-x-8",
|
||||
li { class: "", HeaderLink { url: Route::Home {}, text: "Home" } },
|
||||
li { class: "", HeaderLink { url: Route::CV {}, text: "CV" } },
|
||||
li { class: "", HeaderLink { url: Route::ProjectsPublications {}, text: "Publications/Projects" } },
|
||||
li { class: "", HeaderLink { url: Route::Impressum {}, text: "About" } },
|
||||
}
|
||||
}
|
||||
class: "container mx-auto py-4",
|
||||
|
||||
ul {
|
||||
class:"flex flex-col justify-between items-center justify-center space-y-2 sm:flex-row sm:space-y-0 sm:space-s-4",
|
||||
li {
|
||||
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" },
|
||||
},
|
||||
},
|
||||
li { HeaderLink { url: Route::Home {}, text: t!("headers_home")} },
|
||||
li { HeaderLink { url: Route::CV {}, text: t!("headers_cv") } },
|
||||
li { HeaderLink { url: Route::PublicationsProjects {}, text: t!("headers_publications_projects") } },
|
||||
li { HeaderLink { url: Route::Impressum {}, text: t!("headers_about") } },
|
||||
li { LanguageButtonGroup {} },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn HeaderLink(url: Route, text: String) -> Element {
|
||||
fn LanguageButtonGroup() -> Element {
|
||||
let mut i18n = i18n();
|
||||
|
||||
let change_to_english = move |_| i18n.set_language(langid!("en-GB"));
|
||||
let change_to_german = move |_| i18n.set_language(langid!("de-DE"));
|
||||
|
||||
rsx! {
|
||||
Link { to: url, class:"md:bg-transparent md:text-blue-700 md:p-0 dark:text-white md:dark:text-blue-500", {text} }
|
||||
div {
|
||||
class: "rounded-md shadow-sm justify-end",
|
||||
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",
|
||||
onclick: change_to_english,
|
||||
label { { t!("headers_language_buttons_english") } } },
|
||||
button {
|
||||
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,
|
||||
label { { t!("headers_language_buttons_german") } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn HeaderLink(url: Route, text: String) -> Element {
|
||||
rsx! {
|
||||
Link { to: url, class:"md:bg-transparent md:p-0 text-blue-500", {text} }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
mod footer;
|
||||
mod header;
|
||||
pub mod footer;
|
||||
pub mod header;
|
||||
|
||||
use crate::{Body, Route};
|
||||
use footer::Footer;
|
||||
@@ -10,15 +10,12 @@ use header::Header;
|
||||
pub fn Layout() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex justify-center",
|
||||
div {
|
||||
class: "max-w-screen-xl flex-col" ,
|
||||
Header {},
|
||||
Body {
|
||||
Outlet::<Route> {},
|
||||
}
|
||||
Footer {},
|
||||
}
|
||||
class: "dark flex flex-col min-h-screen",
|
||||
Header {},
|
||||
Body {
|
||||
Outlet::<Route> {},
|
||||
},
|
||||
Footer {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
76
src/main.rs
@@ -1,8 +1,14 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::fullstack::Config;
|
||||
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 layout::footer::Footer;
|
||||
use layout::header::Header;
|
||||
use tracing::Level;
|
||||
|
||||
pub mod components;
|
||||
@@ -16,7 +22,7 @@ use crate::cv::CV;
|
||||
use crate::home::Home;
|
||||
use crate::impressum::Impressum;
|
||||
use crate::layout::Layout;
|
||||
use crate::publications::ProjectsPublications;
|
||||
use crate::publications::PublicationsProjects;
|
||||
|
||||
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Route {
|
||||
@@ -26,7 +32,7 @@ pub enum Route {
|
||||
#[route("/impressum")]
|
||||
Impressum {},
|
||||
#[route("/publications")]
|
||||
ProjectsPublications {},
|
||||
PublicationsProjects {},
|
||||
#[route("/resume")]
|
||||
CV {},
|
||||
#[end_layout]
|
||||
@@ -36,23 +42,46 @@ pub enum Route {
|
||||
|
||||
fn main() {
|
||||
dioxus_logger::init(Level::DEBUG).expect("failed to init logger");
|
||||
let config = server_only!(
|
||||
dioxus::fullstack::Config::new().addr(std::net::SocketAddrV4::new(
|
||||
std::net::Ipv4Addr::new(0, 0, 0, 0),
|
||||
8080,
|
||||
))
|
||||
);
|
||||
LaunchBuilder::fullstack().with_cfg(config).launch(App)
|
||||
LaunchBuilder::new().launch(App)
|
||||
}
|
||||
|
||||
fn App() -> Element {
|
||||
use_init_i18n(|| {
|
||||
I18nConfig::new(langid!("en-GB"))
|
||||
.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"),
|
||||
))
|
||||
});
|
||||
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: asset!("./assets/tailwind.css") }
|
||||
document::Link { rel: "icon", href: asset!("./assets/favicon.ico") }
|
||||
meta {
|
||||
name: "robots",
|
||||
content: "noindex",
|
||||
name: "description",
|
||||
content: "Visit Tuan-Dat Tran's website for his CV, publications, projects, and consulting services. Connect for collaboration.",
|
||||
},
|
||||
script {
|
||||
r#type: "application/ld+json",
|
||||
"
|
||||
{{
|
||||
\"@context\": \"https://schema.org\",
|
||||
\"@type\": \"ProfilePage\",
|
||||
\"mainEntity\": {{
|
||||
\"@type\": \"Person\",
|
||||
\"name\": \"Tuan-Dat Tran\",
|
||||
\"alternateName\": \"TuDatTr\",
|
||||
\"image\": \"https://www.tudattr.dev/pictures/headshot.webp\",
|
||||
}}
|
||||
}}
|
||||
"
|
||||
}
|
||||
div {
|
||||
class: "bg-white dark:bg-gray-900 min-h-screen",
|
||||
class: "bg-gray-900 min-h-screen",
|
||||
Router::<Route> {},
|
||||
}
|
||||
}
|
||||
@@ -62,12 +91,16 @@ fn App() -> Element {
|
||||
fn PageNotFound(route: Vec<String>) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "h-screen flex items-center justify-center",
|
||||
img {
|
||||
class: "size-auto",
|
||||
src: "https://raw.githubusercontent.com/SAWARATSUKI/ServiceLogos/main/404Notfound/NotFound.png"
|
||||
class: "flex flex-col min-h-screen items",
|
||||
Header {},
|
||||
div {
|
||||
class: "container mx-auto p-4 flex items-center justify-center max-w-md w-full",
|
||||
H1 {
|
||||
"Site not found (404)"
|
||||
},
|
||||
}
|
||||
}
|
||||
Footer {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +112,8 @@ pub struct BodyProp {
|
||||
pub fn Body(prop: BodyProp) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "flex justify-center my-4",
|
||||
div {
|
||||
class: "max-w-screen-xl min-w-full",
|
||||
{prop.children}
|
||||
}
|
||||
class: "flex-grow container mx-auto p-4",
|
||||
{prop.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_i18n::t;
|
||||
|
||||
use crate::components::{UnderConstruction, H1, HR};
|
||||
use crate::components::{Bolding, H1, HR};
|
||||
|
||||
#[component]
|
||||
pub fn ProjectsPublications() -> Element {
|
||||
pub fn PublicationsProjects() -> Element {
|
||||
rsx! {
|
||||
UnderConstruction { },
|
||||
div {
|
||||
H1 { "Publications" }
|
||||
Publications { },
|
||||
},
|
||||
HR {},
|
||||
div {
|
||||
H1 { "Projects" }
|
||||
Projects { },
|
||||
class: "flex flex-col ",
|
||||
div {
|
||||
H1 { { t!("publications_projects_publications_title") } }
|
||||
Publications { },
|
||||
},
|
||||
HR {},
|
||||
div {
|
||||
H1 { { t!("publications_projects_projects_title") } }
|
||||
Projects { },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,54 +37,44 @@ fn Publications() -> Element {
|
||||
div {
|
||||
class: "flex gap-4 items-center flex-wrap",
|
||||
Publication {
|
||||
doi: "https://doi.org/10.48550/arXiv.2307.09639",
|
||||
conference: "IEEE LCN 2023",
|
||||
title: "RPM: Reverse Path Congestion Marking on P4 Programmable Switches",
|
||||
authors: "N. Baganal-Krishna, T.-D. Tran, R. Kundel and A. Rizk",
|
||||
description: "
|
||||
In this paper, we present Reverse Path Congestion Marking
|
||||
(RPM) to accelerate the reaction to network congestion
|
||||
events without changing the end-host stack. RPM decouples
|
||||
the congestion signal from the downstream path after the
|
||||
bottleneck while maintaining the stability of the
|
||||
congestion control loop. We show that RPM improves
|
||||
throughput fairness for RTT on heterogeneous TCP flows as
|
||||
well as the flow completion time, especially for small
|
||||
Data Center TCP (DCTCP) flows around P4 programmable ASIC
|
||||
switches."
|
||||
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 {
|
||||
doi: "https://git.tudattr.dev/AISE/seminar/src/branch/main/paper.pdf",
|
||||
conference: "Seminar",
|
||||
title: "Overview of IoT Fuzzing Techniques",
|
||||
authors: "Tuan-Dat Tran",
|
||||
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 IoT fuzzers."
|
||||
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 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700",
|
||||
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-gray-900 dark:text-white",
|
||||
class:"mb-2 text-2xl font-bold tracking-tight text-white",
|
||||
"{prop.title}",
|
||||
},
|
||||
span { class: "text-lg text-gray-900 dark:text-white", "{prop.conference}" },
|
||||
span { class: "text-lg text-white", "{prop.conference}" },
|
||||
p {
|
||||
class:"font-normal text-gray-700 dark:text-gray-400",
|
||||
"{prop.authors}",
|
||||
class:"font-normal text-gray-400 italic",
|
||||
Bolding {
|
||||
authors: "{prop.authors}",
|
||||
patterns: pattern,
|
||||
},
|
||||
}
|
||||
p {
|
||||
class:"font-normal text-gray-700 dark:text-gray-400",
|
||||
class:"font-normal text-gray-400",
|
||||
"{prop.description}",
|
||||
}
|
||||
}
|
||||
@@ -93,39 +86,32 @@ fn Projects() -> Element {
|
||||
div {
|
||||
class: "flex gap-4 items-center flex-wrap",
|
||||
Project {
|
||||
url: "https://git.ude-syssec.de/uni-due-syssec/students/2022_tuan-dat_tran_libAFLEVMFuzzer/ethfuzz/",
|
||||
kind: "Bachelorproject",
|
||||
title: "Undisclosed Ethereum Smart Contract Fuzzer",
|
||||
authors: "Tuan-Dat Tran",
|
||||
description: "
|
||||
In this ingoing project I am building an Ethereum
|
||||
Smart Contract Fuzzer. More info will follow."
|
||||
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 {
|
||||
url: "https://git.tudattr.dev/tudattr/dotfiles",
|
||||
kind: "Personal Project",
|
||||
title: ".dotfiles",
|
||||
authors: "Tuan-Dat Tran",
|
||||
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 on how to set up
|
||||
my daily-use notebook. It provides a baseline for any personal
|
||||
Linux system I set up and allows for repeatability simplifies
|
||||
the process of setting up an ArchLinux based system.
|
||||
"
|
||||
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 {
|
||||
url: "https://git.tudattr.dev/tudattr/ansible",
|
||||
kind: "Personal Project",
|
||||
title: "Ansible Homelab",
|
||||
authors: "Tuan-Dat Tran",
|
||||
description: "
|
||||
Ansible is a automation engine which allows for automatic
|
||||
provisioning, configuration management and application
|
||||
deployment. I use ansible to set up my homelab, which serves as
|
||||
a platform for me to try out and learn new technologies.
|
||||
"
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,22 +129,27 @@ struct ProjectProp {
|
||||
}
|
||||
|
||||
fn Project(prop: ProjectProp) -> Element {
|
||||
let pattern = vec!["T.-D. Tran".to_string(), "Tuan-Dat Tran".to_string()];
|
||||
|
||||
rsx! {
|
||||
Link {
|
||||
class:"block max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700",
|
||||
class:"block max-w-sm p-6 border rounded-lg shadow bg-gray-800 border-gray-700 hover:bg-gray-700",
|
||||
to:"{prop.url}",
|
||||
new_tab: true,
|
||||
h5 {
|
||||
class:"mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white",
|
||||
class:"mb-2 text-2xl font-bold tracking-tight text-white",
|
||||
"{prop.title}",
|
||||
},
|
||||
p { class: "text-lg text-gray-900 dark:text-white", "{prop.kind}" },
|
||||
p { class: "text-lg text-white", "{prop.kind}" },
|
||||
p {
|
||||
class:"font-normal text-gray-700 dark:text-gray-400",
|
||||
"{prop.authors}",
|
||||
class:"font-normal text-gray-400",
|
||||
Bolding {
|
||||
authors: "{prop.authors}",
|
||||
patterns: pattern,
|
||||
},
|
||||
}
|
||||
p {
|
||||
class:"font-normal text-gray-700 dark:text-gray-400",
|
||||
class:"font-normal text-gray-400",
|
||||
"{prop.description}",
|
||||
}
|
||||
}
|
||||
|
||||