Compare commits
32 Commits
| 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 | ||
| a54d1f3c3b | |||
|
|
081229d66a | ||
| 837e19e111 |
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
**/target
|
||||||
|
**/dist
|
||||||
|
LICENSES
|
||||||
|
LICENSE
|
||||||
|
temp
|
||||||
|
README.md
|
||||||
4894
Cargo.lock
generated
29
Cargo.toml
@@ -1,25 +1,34 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "athome"
|
name = "athome"
|
||||||
version = "0.2.1"
|
version = "0.4.1"
|
||||||
authors = ["Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>"]
|
authors = ["Tuan-Dat Tran <tuan-dat.tran@tudattr.dev>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
dioxus = { version = "0.6", features = ["fullstack", "router"] }
|
||||||
dioxus = { version = "0.5", features = ["fullstack", "router"] }
|
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
dioxus-logger = "0.5.0"
|
dioxus-logger = "0.6.0"
|
||||||
manganis = "0.2.2"
|
dioxus-i18n = "0.3.0"
|
||||||
dioxus-free-icons = { version = "0.8", features = ["font-awesome-brands"] }
|
|
||||||
dioxus-sdk = { version = "0.5.0", features = ["i18n"] }
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
server = ["dioxus/axum"]
|
|
||||||
web = ["dioxus/web"]
|
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
|
# which files or dirs will be watcher monitoring
|
||||||
watch_path = ["src", "assets"]
|
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 = []
|
|
||||||
|
|||||||
49
Dockerfile
@@ -1,20 +1,33 @@
|
|||||||
FROM rust:1.79.0 AS dioxus
|
FROM rust:1 AS chef
|
||||||
RUN cargo install dioxus-cli@^0.5
|
RUN cargo install cargo-chef
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
FROM dioxus AS builder
|
FROM chef AS planner
|
||||||
WORKDIR /athome/
|
COPY . .
|
||||||
RUN apt-get update && apt-get install nodejs npm libssl-dev musl-tools -y && rm -rf /var/lib/apt/lists/*
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
RUN npm install -D tailwindcss
|
|
||||||
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
|
|
||||||
|
|
||||||
FROM dioxus AS runner
|
FROM chef AS builder
|
||||||
WORKDIR /app/
|
COPY --from=planner /app/recipe.json recipe.json
|
||||||
COPY --from=builder /athome/docs/ ./docs/
|
RUN cargo chef cook --release --recipe-path recipe.json
|
||||||
CMD [ "./docs/athome" ]
|
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" ]
|
||||||
|
|||||||
10
README.md
@@ -2,6 +2,16 @@
|
|||||||
|
|
||||||
My personal website.
|
My personal website.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx tailwindcss -i ./input.css -o ./assets/tailwind.css --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dx serve --platform web
|
||||||
|
```
|
||||||
|
|
||||||
## Screenshot
|
## Screenshot
|
||||||
|
|
||||||
[[./resources/screenshot.webp]]
|
[[./resources/screenshot.webp]]
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 742 B |
|
Before Width: | Height: | Size: 1.9 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: 190 KiB After Width: | Height: | Size: 24 KiB |
@@ -2,25 +2,19 @@
|
|||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://www.tudattr.dev/</loc>
|
<loc>https://www.tudattr.dev/</loc>
|
||||||
<lastmod>2024-07-25</lastmod>
|
<lastmod>2025-01-12</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>1.0</priority>
|
<priority>1.0</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://www.tudattr.dev/resume</loc>
|
<loc>https://www.tudattr.dev/resume</loc>
|
||||||
<lastmod>2024-07-25</lastmod>
|
<lastmod>2025-01-12</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://www.tudattr.dev/publications</loc>
|
<loc>https://www.tudattr.dev/publications</loc>
|
||||||
<lastmod>2024-07-25</lastmod>
|
<lastmod>2025-01-12</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
|
||||||
<priority>0.8</priority>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://www.tudattr.dev/consulting</loc>
|
|
||||||
<lastmod>2024-07-25</lastmod>
|
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
|
|||||||
@@ -588,10 +588,6 @@ video {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.static {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
|
|
||||||
.absolute {
|
.absolute {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
@@ -608,21 +604,11 @@ video {
|
|||||||
inset-inline-start: -0.375rem;
|
inset-inline-start: -0.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx-16 {
|
|
||||||
margin-left: 4rem;
|
|
||||||
margin-right: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-auto {
|
.mx-auto {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-4 {
|
|
||||||
margin-top: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-8 {
|
.my-8 {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
@@ -676,15 +662,6 @@ video {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.size-auto {
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-16 {
|
|
||||||
height: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-24 {
|
.h-24 {
|
||||||
height: 6rem;
|
height: 6rem;
|
||||||
}
|
}
|
||||||
@@ -705,18 +682,10 @@ video {
|
|||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-screen {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.min-h-screen {
|
.min-h-screen {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-16 {
|
|
||||||
width: 4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-24 {
|
.w-24 {
|
||||||
width: 6rem;
|
width: 6rem;
|
||||||
}
|
}
|
||||||
@@ -754,10 +723,18 @@ video {
|
|||||||
max-width: 36rem;
|
max-width: 36rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-auto {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-grow {
|
.flex-grow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.grow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.cursor-pointer {
|
.cursor-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -770,6 +747,14 @@ video {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-center {
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.items-center {
|
.items-center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -786,14 +771,22 @@ video {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.justify-stretch {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-2 {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-4 {
|
.gap-4 {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-x-8 > :not([hidden]) ~ :not([hidden]) {
|
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
margin-right: calc(2rem * var(--tw-space-x-reverse));
|
margin-right: calc(1rem * var(--tw-space-x-reverse));
|
||||||
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
|
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
|
||||||
}
|
}
|
||||||
|
|
||||||
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
|
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
|
||||||
@@ -802,8 +795,14 @@ video {
|
|||||||
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
||||||
}
|
}
|
||||||
|
|
||||||
.overflow-x-auto {
|
.space-y-8 > :not([hidden]) ~ :not([hidden]) {
|
||||||
overflow-x: auto;
|
--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-stretch {
|
||||||
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded {
|
.rounded {
|
||||||
@@ -848,69 +847,69 @@ video {
|
|||||||
border-inline-start-width: 1px;
|
border-inline-start-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-gray-200 {
|
.border-gray-600 {
|
||||||
--tw-border-opacity: 1;
|
--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;
|
--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 {
|
.border-gray-900 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
border-color: rgb(17 24 39 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-gray-100 {
|
.bg-blue-900 {
|
||||||
--tw-bg-opacity: 1;
|
--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;
|
--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;
|
--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;
|
--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;
|
--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;
|
--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;
|
--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;
|
--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;
|
--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;
|
--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 {
|
.bg-gradient-to-br {
|
||||||
@@ -956,10 +955,6 @@ video {
|
|||||||
background-clip: text;
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-3 {
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-4 {
|
.p-4 {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
@@ -1092,14 +1087,24 @@ video {
|
|||||||
letter-spacing: 0.1em;
|
letter-spacing: 0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-blue-800 {
|
.text-blue-300 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(30 64 175 / var(--tw-text-opacity));
|
color: rgb(147 197 253 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-cyan-600 {
|
.text-blue-500 {
|
||||||
--tw-text-opacity: 1;
|
--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 {
|
.text-gray-400 {
|
||||||
@@ -1112,44 +1117,34 @@ video {
|
|||||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-gray-700 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-800 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(31 41 55 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-gray-900 {
|
.text-gray-900 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-green-800 {
|
.text-green-300 {
|
||||||
--tw-text-opacity: 1;
|
--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;
|
--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;
|
--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;
|
--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;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(153 27 27 / var(--tw-text-opacity));
|
color: rgb(252 165 165 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-transparent {
|
.text-transparent {
|
||||||
@@ -1161,9 +1156,9 @@ video {
|
|||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-yellow-800 {
|
.text-yellow-300 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(133 77 14 / var(--tw-text-opacity));
|
color: rgb(253 224 71 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadow {
|
.shadow {
|
||||||
@@ -1203,20 +1198,17 @@ video {
|
|||||||
transition-duration: 300ms;
|
transition-duration: 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:bg-gray-100:hover {
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
.hover\:bg-gray-700:hover {
|
||||||
--tw-bg-opacity: 1;
|
--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 {
|
.hover\:bg-gradient-to-br:hover {
|
||||||
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
|
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:text-blue-700:hover {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(29 78 216 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:text-white:hover {
|
.hover\:text-white:hover {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
@@ -1235,9 +1227,9 @@ video {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus\:text-blue-700:focus {
|
.focus\:text-white:focus {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(29 78 216 / var(--tw-text-opacity));
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus\:outline-none:focus {
|
.focus\:outline-none:focus {
|
||||||
@@ -1257,14 +1249,14 @@ video {
|
|||||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus\:ring-blue-700:focus {
|
.focus\:ring-blue-500:focus {
|
||||||
--tw-ring-opacity: 1;
|
--tw-ring-opacity: 1;
|
||||||
--tw-ring-color: rgb(29 78 216 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.focus\:ring-cyan-300:focus {
|
.focus\:ring-cyan-800:focus {
|
||||||
--tw-ring-opacity: 1;
|
--tw-ring-opacity: 1;
|
||||||
--tw-ring-color: rgb(103 232 249 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(21 94 117 / var(--tw-ring-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.group:hover .group-hover\:from-green-400 {
|
.group:hover .group-hover\:from-green-400 {
|
||||||
@@ -1286,6 +1278,12 @@ video {
|
|||||||
flex-direction: 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]) {
|
.sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
|
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
|
||||||
@@ -1310,11 +1308,6 @@ video {
|
|||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md\:text-blue-700 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(29 78 216 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
@@ -1324,163 +1317,3 @@ video {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@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\:hover\:text-white:hover {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:focus\:text-white:focus {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark\:focus\:ring-blue-500:focus {
|
|
||||||
--tw-ring-opacity: 1;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / var(--tw-ring-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 base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
|||||||
3
rust-toolchain.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "1.85.0"
|
||||||
|
components = ["rustfmt", "clippy", "rust-analyzer"]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
TAG=$(git branch | grep "*" | awk '{ print $2 }')
|
TAG=$(git describe --exact-match --tags)
|
||||||
LOCAL_IMAGE="athome"
|
LOCAL_IMAGE="athome"
|
||||||
REGISTRY="mos4"
|
REGISTRY="mos4"
|
||||||
REMOTE_IMAGE="athome"
|
REMOTE_IMAGE="athome"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::{i18n::use_i18, translate};
|
use dioxus_i18n::t;
|
||||||
|
|
||||||
#[derive(PartialEq, Props, Clone)]
|
#[derive(PartialEq, Props, Clone)]
|
||||||
pub struct PProps {
|
pub struct PProps {
|
||||||
@@ -14,20 +14,33 @@ pub fn P(props: PProps) -> Element {
|
|||||||
div {
|
div {
|
||||||
class: "{props.class}",
|
class: "{props.class}",
|
||||||
p {
|
p {
|
||||||
class: "mb-2 font-normal text-gray-900 dark:text-white",
|
class: "mb-2 font-normal text-white",
|
||||||
{props.children}
|
{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]
|
#[component]
|
||||||
pub fn H1(props: PProps) -> Element {
|
pub fn H1(props: PProps) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "{props.class}",
|
class: "{props.class}",
|
||||||
h1 {
|
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 {
|
span {
|
||||||
class: "text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400",
|
class: "text-transparent bg-clip-text bg-gradient-to-r to-emerald-600 from-sky-400",
|
||||||
{props.children}
|
{props.children}
|
||||||
@@ -43,7 +56,7 @@ pub fn H4(props: PProps) -> Element {
|
|||||||
class: "{props.class}",
|
class: "{props.class}",
|
||||||
class: "mb-4",
|
class: "mb-4",
|
||||||
h3 {
|
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}
|
{props.children}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +69,7 @@ pub fn H5(props: PProps) -> Element {
|
|||||||
div {
|
div {
|
||||||
class: "{props.class}",
|
class: "{props.class}",
|
||||||
h5 {
|
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}
|
{props.children}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +89,7 @@ pub struct CardProp {
|
|||||||
pub fn Card(prop: CardProp) -> Element {
|
pub fn Card(prop: CardProp) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
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-300 dark:focus:ring-cyan-800 rounded-lg px-5 py-2.5 min-w-fit max-w-8",
|
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 {
|
div {
|
||||||
class: "pb-4",
|
class: "pb-4",
|
||||||
div {
|
div {
|
||||||
@@ -84,7 +97,7 @@ pub fn Card(prop: CardProp) -> Element {
|
|||||||
Picture {src: "{prop.picture}"},
|
Picture {src: "{prop.picture}"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
H5 { "{prop.name}", span { class: "text-grey-600 dark:text-grey-500 text-lg", " {prop.gender}" } },
|
Title { "{prop.name}", span { class: "text-grey-500 text-lg", " {prop.gender}" } },
|
||||||
{ prop.children }
|
{ prop.children }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,16 +121,14 @@ fn Picture(prop: PictureProp) -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn UnderConstruction() -> Element {
|
pub fn UnderConstruction() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class:"rounded 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 {
|
div {
|
||||||
class:"items-center mx-auto",
|
class:"items-center mx-auto",
|
||||||
p {
|
p {
|
||||||
class:"items-center text-sm font-normal text-gray-500 dark:text-gray-400",
|
class:"items-center text-sm font-normal text-gray-400",
|
||||||
span { { translate!(i18, "components.under_construction") } }
|
span { { t!("component_under_construction") } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +137,108 @@ pub fn UnderConstruction() -> Element {
|
|||||||
|
|
||||||
pub fn HR() -> Element {
|
pub fn HR() -> Element {
|
||||||
rsx! {
|
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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
use crate::components::UnderConstruction;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Consulting() -> Element {
|
|
||||||
rsx! {
|
|
||||||
UnderConstruction { },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
154
src/cv.rs
@@ -1,5 +1,5 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::{i18n::use_i18, translate};
|
use dioxus_i18n::t;
|
||||||
|
|
||||||
use crate::components::{H4, HR};
|
use crate::components::{H4, HR};
|
||||||
|
|
||||||
@@ -7,63 +7,68 @@ use crate::components::{H4, HR};
|
|||||||
pub fn CV() -> Element {
|
pub fn CV() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-col ",
|
class: "flex flex-col",
|
||||||
Introduction {},
|
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 {}
|
HR {}
|
||||||
div {
|
div {
|
||||||
class: "flex justify-between",
|
class: "flex flex-col justify-between sm:flex-row",
|
||||||
WorkExperience {},
|
WorkExperience {},
|
||||||
Miscellaneous {},
|
Miscellaneous {},
|
||||||
},
|
},
|
||||||
HR {},
|
HR {},
|
||||||
Socials {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Introduction() -> Element {
|
fn Introduction() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex",
|
class: "flex",
|
||||||
img {
|
P { { t!("cv_introduction_0") } },
|
||||||
class: "rounded-full w-16 h-16 mx-16",
|
},
|
||||||
src: "/pictures/headshot.webp"
|
|
||||||
}
|
|
||||||
P { { translate!(i18, "cv.introduction") }}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn WorkExperience() -> Element {
|
fn WorkExperience() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "ms-8 max-w-3/4",
|
class: "ms-8 max-w-3/4",
|
||||||
H4 { { translate!(i18, "cv.workexperience.title") } },
|
H4 { { t!("cv_workexperience_title") } },
|
||||||
ol {
|
ol {
|
||||||
class:"relative border-s border-gray-200 dark:border-gray-700",
|
class:"relative border-s border-gray-700",
|
||||||
CVEntry {time: translate!(i18, "cv.workexperience.ra_ude.time"), title: translate!(i18, "cv.workexperience.ra_ude.title"),
|
CVEntry {time: t!("cv_workexperience_dd_devops_time"), title: t!("cv_workexperience_dd_devops_title"),
|
||||||
|
technologies: vec!["Kubenertes".to_string(), "ArgoCD".to_string(), "Ansible".to_string(), "Azure".to_string(), "Docker".to_string(), "ELK".to_string()],
|
||||||
|
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()],
|
technologies: vec!["Rust".to_string(), "Python".to_string(), "P4".to_string(), "Linux".to_string(), "Docker".to_string(), "Kubernetes".to_string()],
|
||||||
description: translate!(i18, "cv.workexperience.ra_ude.description")
|
description: t!("cv_workexperience_ra_ude_description")
|
||||||
},
|
},
|
||||||
CVEntry {time: translate!(i18, "cv.workexperience.mentoring_ude.time"), title: translate!(i18, "cv.workexperience.mentoring_ude.title"),
|
CVEntry {time: t!("cv_workexperience_mentoring_ude_time"), title: t!("cv_workexperience_mentoring_ude_title"),
|
||||||
technologies: vec!["Powerpoint".to_string()],
|
technologies: vec!["Powerpoint".to_string()],
|
||||||
description: translate!(i18, "cv.workexperience.mentoring_ude.description")
|
description: t!("cv_workexperience_mentoring_ude_description")
|
||||||
},
|
},
|
||||||
CVEntry {time: translate!(i18, "cv.workexperience.se2_gefeba.time"), title: translate!(i18, "cv.workexperience.se2_gefeba.title"),
|
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()],
|
technologies: vec!["C#".to_string(), "Angular".to_string(), "bootstrap".to_string(), "Entity Framework".to_string()],
|
||||||
description: translate!(i18, "cv.workexperience.se2_gefeba.description")
|
description: t!("cv_workexperience_se2_gefeba_description")
|
||||||
},
|
},
|
||||||
CVEntry {time: translate!(i18, "cv.workexperience.student_fse.time"), title: translate!(i18, "cv.workexperience.student_fse.title"),
|
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()],
|
technologies: vec!["Linux".to_string(), "Networking".to_string(), "LaTeX".to_string()],
|
||||||
description: translate!(i18, "cv.workexperience.student_fse.description")
|
description: t!("cv_workexperience_student_fse_description")
|
||||||
},
|
},
|
||||||
CVEntry {time: translate!(i18, "cv.workexperience.se1_gefeba.time"), title: translate!(i18, "cv.workexperience.se1_gefeba.title"),
|
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()],
|
technologies: vec!["C#".to_string(), "HTML".to_string(), "Javascript".to_string(), "CSS".to_string()],
|
||||||
description: translate!(i18, "cv.workexperience.se1_gefeba.description")
|
description: t!("cv_workexperience_se1_gefeba_description")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -83,67 +88,60 @@ fn Miscellaneous() -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn Education() -> Element {
|
fn Education() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
H4 { { translate!(i18, "cv.education.title") } },
|
H4 { { t!("cv_education_title") } },
|
||||||
Entry {
|
Entry {
|
||||||
title: translate!(i18, "cv.education.bachelor.title"),
|
title: t!("cv_education_bachelor_title"),
|
||||||
time { class:"mb-1 text-sm font-normal leading-none text-gray-400 dark:text-gray-500", { translate!(i18, "cv.education.bachelor.time") } },
|
time { class:"mb-1 text-sm font-normal leading-none text-gray-500", { t!("cv_education_bachelor_time") } },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Skills() -> Element {
|
fn Skills() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
H4 { { translate!(i18, "cv.skills.title") }},
|
H4 { { t!("cv_skills_title") }},
|
||||||
Entry {
|
Entry {
|
||||||
title: translate!(i18, "cv.skills.devops.title"),
|
title: t!("cv_skills_devops_title"),
|
||||||
P { { translate!(i18, "cv.skills.devops.ansible") } },
|
P { { t!("cv_skills_devops_ansible") } },
|
||||||
P { { translate!(i18, "cv.skills.devops.kubernetes") } },
|
P { { t!("cv_skills_devops_kubernetes") } },
|
||||||
P { { translate!(i18, "cv.skills.devops.gitops") } },
|
P { { t!("cv_skills_devops_gitops") } },
|
||||||
}Entry {
|
}
|
||||||
title: translate!(i18, "cv.skills.software_engineering.title"),
|
Entry {
|
||||||
P { { translate!(i18, "cv.skills.software_engineering.rust") } },
|
title: t!("cv_skills_software_engineering_title"),
|
||||||
P { { translate!(i18, "cv.skills.software_engineering.python") } },
|
P { { t!("cv_skills_software_engineering_rust") } },
|
||||||
P { { translate!(i18, "cv.skills.software_engineering.csharp") } },
|
P { { t!("cv_skills_software_engineering_python") } },
|
||||||
|
P { { t!("cv_skills_software_engineering_csharp") } },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Languages() -> Element {
|
fn Languages() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
H4 { { translate!(i18, "cv.languages.title") } },
|
H4 { { t!("cv_languages_title") } },
|
||||||
Entry {
|
Entry {
|
||||||
P { {translate!(i18, "cv.languages.german") } },
|
P { {t!("cv_languages_german") } },
|
||||||
P { {translate!(i18, "cv.languages.english") } },
|
P { {t!("cv_languages_english") } },
|
||||||
P { {translate!(i18, "cv.languages.vietnamese") } },
|
P { {t!("cv_languages_vietnamese") } },
|
||||||
P { {translate!(i18, "cv.languages.japanese") } },
|
P { {t!("cv_languages_japanese") } },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn Interests() -> Element {
|
fn Interests() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
H4 { { translate!(i18, "cv.interests.title") } },
|
H4 { { t!("cv_interests_title") } },
|
||||||
Entry {
|
Entry {
|
||||||
P { { translate!(i18, "cv.interests.coffee") } },
|
P { { t!("cv_interests_coffee") } },
|
||||||
P { { translate!(i18, "cv.interests.tech_it") } },
|
P { { t!("cv_interests_tech_it") } },
|
||||||
P { { translate!(i18, "cv.interests.guitar") } },
|
P { { t!("cv_interests_guitar") } },
|
||||||
P { { translate!(i18, "cv.interests.mechanical_keyboards") } },
|
P { { t!("cv_interests_mechanical_keyboards") } },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +160,7 @@ fn Entry(props: EntryProps) -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "{props.class}",
|
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},
|
{props.children},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,16 +183,16 @@ fn CVEntry(props: CVEntryProps) -> Element {
|
|||||||
li {
|
li {
|
||||||
class: "max-w-xl",
|
class: "max-w-xl",
|
||||||
class: "{props.class}",
|
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"},
|
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-400 dark:text-gray-500", "{props.time}"},
|
time { class:"mb-1 text-sm font-normal leading-none text-gray-500", "{props.time}"},
|
||||||
h6 { class: "text-lg font-semibold text-gray-900 dark:text-white", "{props.title}"}
|
h6 { class: "text-lg font-semibold text-white", "{props.title}"}
|
||||||
ul {
|
ul {
|
||||||
class: "flex",
|
class: "flex flex-wrap gap-2",
|
||||||
for (index, value) in props.technologies.iter().enumerate() {
|
for (index, value) in props.technologies.iter().enumerate() {
|
||||||
li { key: "{index}", RandomBadge { text: "{value}"} }
|
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}
|
{props.children}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,8 +203,7 @@ fn RandomBadge(text: String) -> Element {
|
|||||||
let badge_color = random_badge_color(text.len());
|
let badge_color = random_badge_color(text.len());
|
||||||
rsx! {
|
rsx! {
|
||||||
span {
|
span {
|
||||||
class:"text-xs font-medium me-2 px-2.5 py-0.5 rounded ",
|
class:"text-xs font-medium me-2 px-2.5 py-0.5 rounded {badge_color}",
|
||||||
class: "{badge_color}",
|
|
||||||
"{text}"
|
"{text}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,14 +211,14 @@ fn RandomBadge(text: String) -> Element {
|
|||||||
|
|
||||||
fn random_badge_color(seed: usize) -> String {
|
fn random_badge_color(seed: usize) -> String {
|
||||||
let colors = [
|
let colors = [
|
||||||
"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
|
"bg-blue-900 text-blue-300",
|
||||||
"bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",
|
"bg-gray-700 text-gray-300",
|
||||||
"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
|
"bg-red-900 text-red-300",
|
||||||
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300",
|
"bg-green-900 text-green-300",
|
||||||
"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300",
|
"bg-yellow-900 text-yellow-300",
|
||||||
"bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-300",
|
"bg-indigo-900 text-indigo-300",
|
||||||
"bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300",
|
"bg-purple-900 text-purple-300",
|
||||||
"bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300",
|
"bg-pink-900 text-pink-300",
|
||||||
];
|
];
|
||||||
|
|
||||||
colors[seed % colors.len()].to_string()
|
colors[seed % colors.len()].to_string()
|
||||||
@@ -230,7 +227,12 @@ fn random_badge_color(seed: usize) -> String {
|
|||||||
fn Socials() -> Element {
|
fn Socials() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
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" } }},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +241,7 @@ fn Socials() -> Element {
|
|||||||
fn P(children: Element) -> Element {
|
fn P(children: Element) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
p {
|
p {
|
||||||
class: "text-base font-normal text-gray-500 dark:text-gray-400",
|
class: "text-base font-normal text-gray-400",
|
||||||
{children},
|
{children},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/home.rs
@@ -1,37 +1,29 @@
|
|||||||
use crate::components::{Card, P};
|
use crate::components::{Card, P};
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::{i18n::use_i18, translate};
|
use dioxus_i18n::t;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Home() -> Element {
|
pub fn Home() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "container mx-auto p-4 flex items-center justify-center max-w-md w-full",
|
class: "container mx-auto p-4 flex items-center justify-center max-w-md w-full",
|
||||||
Card {
|
Card {
|
||||||
name: translate!(i18, "home.card.name"),
|
name: t!("home_card_name"),
|
||||||
gender: translate!(i18, "home.card.gender"),
|
gender: t!("home_card_gender"),
|
||||||
picture: "/pictures/headshot.webp",
|
picture: asset!("./assets/pictures/headshot.webp"),
|
||||||
div {
|
div {
|
||||||
class: "py-4",
|
class: "py-4",
|
||||||
div {
|
div {
|
||||||
class: "mb-2",
|
class: "mb-2",
|
||||||
P { { translate!(i18, "home.card.l1") } },
|
for line in t!("home_card_text").split("\n").into_iter() {
|
||||||
P { { translate!(i18, "home.card.l2") } },
|
P {{line}}
|
||||||
P { { translate!(i18, "home.card.l3") },
|
}
|
||||||
Link {
|
|
||||||
to: "https://git.tudattr.dev/explore/repos",
|
|
||||||
new_tab: true,
|
|
||||||
class: "items-center font-medium hover:underline",
|
|
||||||
"gitea"},
|
|
||||||
{ translate!(i18, "home.card.l3_1") }
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Link {
|
Link {
|
||||||
to: "mailto:tuan-dat.tran@tudattr.dev",
|
to: "mailto:tuan-dat.tran@tudattr.dev",
|
||||||
class: "text-gray-900 bg-gradient-to-br from-green-400 to-blue-600 group-hover:from-green-400 group-hover:to-blue-600 hover:text-white rounded-full shadow-lg py-4 px-4",
|
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",
|
||||||
{ translate!(i18, "home.card.contact_button") }
|
{ t!("home_card_contact_button") }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,59 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::{i18n::use_i18, translate};
|
use dioxus_i18n::t;
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
use crate::components::{H1, HR, P};
|
use crate::components::{H1, HR, P};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Impressum() -> Element {
|
pub fn Impressum() -> Element {
|
||||||
let mut show_impressum = use_signal(|| false);
|
let mut impressum = use_signal(Vec::<String>::new);
|
||||||
let i18 = use_i18();
|
let mut contact = use_signal(Vec::<String>::new);
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
if show_impressum() {
|
div {
|
||||||
div {
|
div {
|
||||||
div {
|
class: "flex flex-col items-center",
|
||||||
class: "flex flex-col items-center",
|
|
||||||
button {
|
|
||||||
onclick: move |_| {
|
|
||||||
info!("Hide impressum.");
|
|
||||||
},
|
|
||||||
H1 { { translate!(i18, "impressum.on") } },
|
|
||||||
},
|
|
||||||
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 {
|
button {
|
||||||
onclick: move |_| async move {
|
onclick: move |_| async move {
|
||||||
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 { { t!("impressum_on") } },
|
||||||
H1 { { translate!(i18, "impressum.off") } },
|
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
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(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
pub static EN_GB: &str = include_str!("./languages/en-GB.json");
|
|
||||||
pub static DE_DE: &str = include_str!("./languages/de-DE.json");
|
|
||||||
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
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "de-DE",
|
|
||||||
"texts": {
|
|
||||||
"headers": {
|
|
||||||
"home": "Home",
|
|
||||||
"cv": "Lebenslauf",
|
|
||||||
"publications_projects": "Artikel/Projekte",
|
|
||||||
"consulting": "Consulting",
|
|
||||||
"about": "Impressum",
|
|
||||||
"language_buttons": {
|
|
||||||
"english": "🇬🇧 Englisch",
|
|
||||||
"german": "🇩🇪 Deutsch"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home": {
|
|
||||||
"card": {
|
|
||||||
"name": "Tuan-Dat Tran",
|
|
||||||
"gender": "",
|
|
||||||
"l1": "Hallihallo! 👋🏻👋🏼👋🏽👋🏾👋🏿",
|
|
||||||
"l2": "Willkommen auf meiner kleinen Webseite im World Wide Web.",
|
|
||||||
"l3": "Während du hier bist, schau dir doch meine Projekte auf ",
|
|
||||||
"l3_1": " an.",
|
|
||||||
"contact_button": "Get in touch."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cv": {
|
|
||||||
"introduction": "Während meines Bachelorstudiums habe ich viele Erfahrungen in der Industrie und im Studium gesammelt. Meine beruflichen und persönlichen Interessen sind DevOps/IaC, Systems/Software Security und Computer Networking. All diese Interessen vertiefe ich in persönlichen Projekten wie meinem Homelab und CTF-Challenges.",
|
|
||||||
"workexperience": {
|
|
||||||
"title": "Berufserfahrung",
|
|
||||||
"se1_gefeba": {
|
|
||||||
"title": "Software Entwickler @ gefeba Engineering GmbH",
|
|
||||||
"time": "2013 - 2015",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"student_fse": {
|
|
||||||
"title": "Fachschaftsratsmitglied @ UDE",
|
|
||||||
"time": "2016 - 2019",
|
|
||||||
"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, sei es organisatorisch oder fachspezifisch."
|
|
||||||
},
|
|
||||||
"se2_gefeba": {
|
|
||||||
"title": "Software Entwickler @ gefeba Engineering GmbH",
|
|
||||||
"time": "2018 - 2020",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"mentoring_ude": {
|
|
||||||
"title": "Mentoring @ UDE",
|
|
||||||
"time": "2021 - 2022",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"ra_ude": {
|
|
||||||
"title": "Studentische Hilfskraft @ UDE",
|
|
||||||
"time": "2021 - jetzt",
|
|
||||||
"description": "Während meiner Tätigkeit als wissenschaftlicher Mitarbeiter in 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."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"education": {
|
|
||||||
"title": "Bildungsweg",
|
|
||||||
"bachelor": {
|
|
||||||
"title": "BSc Angewandte Informatik - Systems Engineering",
|
|
||||||
"time": "2015 - jetzt",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"skills": {
|
|
||||||
"title": "Fähigkeiten",
|
|
||||||
"devops": {
|
|
||||||
"title": "DevOps",
|
|
||||||
"ansible": "Ansible",
|
|
||||||
"kubernetes": "Kubernetes",
|
|
||||||
"gitops": "GitOps"
|
|
||||||
},
|
|
||||||
"software_engineering": {
|
|
||||||
"title": "Programmiersprachen",
|
|
||||||
"rust": "Rust",
|
|
||||||
"python": "Python",
|
|
||||||
"csharp": "C#"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"languages": {
|
|
||||||
"title": "Sprachkenntnisse",
|
|
||||||
"german": "Deutsch (Muttersprache)",
|
|
||||||
"english": "Englisch (C2)",
|
|
||||||
"vietnamese": "Vietnamesisch (B1)",
|
|
||||||
"japanese": "Japanisch (A1)"
|
|
||||||
},
|
|
||||||
"interests": {
|
|
||||||
"title": "Interessen",
|
|
||||||
"coffee": "Kaffee",
|
|
||||||
"tech_it": "Tech/IT",
|
|
||||||
"guitar": "Gitarre",
|
|
||||||
"mechanical_keyboards": "Mechanische Tastaturen"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"publications_projects": {
|
|
||||||
"publications": {
|
|
||||||
"title": "Veröffentlichungen",
|
|
||||||
"rpm": {
|
|
||||||
"title": "RPM: Reverse Path Congestion Marking on P4 Programmable Switches",
|
|
||||||
"authors": "N. Baganal-Krishna, T.-D. Tran, R. Kundel and A. Rizk",
|
|
||||||
"conference": "IEEE LCN 2023",
|
|
||||||
"url": "https://doi.org/10.48550/arXiv.2307.09639",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"iot_fuzzers": {
|
|
||||||
"title": "Overview of IoT Fuzzing Techniques",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"conference": "Seminar",
|
|
||||||
"url": "https://git.tudattr.dev/AISE/seminar/src/branch/main/paper.pdf",
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"projects": {
|
|
||||||
"title": "Projekte",
|
|
||||||
"bachelorproject": {
|
|
||||||
"title": "Unbenannter Ethereum Smart Contract Fuzzer",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"kind": "Bachelorprojekt",
|
|
||||||
"url": "https://git.ude-syssec.de/uni-due-syssec/students/2022_tuan-dat_tran_libAFLEVMFuzzer/ethfuzz/",
|
|
||||||
"description": "In diesem aktuell laufendem Projekt entwickle ich einen Ethereum Smart Contract Fuzzer. Mehr Infos folgen..."
|
|
||||||
},
|
|
||||||
"dotfiles": {
|
|
||||||
"title": ".dotfiles",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"kind": "Personal",
|
|
||||||
"url": "https://git.tudattr.dev/tudattr/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 ArchLinux-basierten Systems vereinfacht."
|
|
||||||
},
|
|
||||||
"homelab": {
|
|
||||||
"title": "Homelab",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"kind": "Personal",
|
|
||||||
"url": "https://git.tudattr.dev/tudattr/ansible",
|
|
||||||
"description": "Ansible ist eine Automatisierungs-Engine, die eine automatische Bereitstellung, Konfigurationsverwaltung und Anwendungsbereitstellung ermöglicht. Ich verwende Ansible, um mein Homelab einzurichten, das mir als Plattform zum Ausprobieren und Lernen neuer Technologien dient."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"impressum": {
|
|
||||||
"off": "Impressum anzeigen",
|
|
||||||
"on": "Impressum"
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"under_construction": "Diese Seite befindet sich gerade im Aufbau"
|
|
||||||
},
|
|
||||||
"footer": {
|
|
||||||
"year": "© 2024 ",
|
|
||||||
"name": "Tuan-Dat Tran",
|
|
||||||
"rights": ". All Rights Reserved.",
|
|
||||||
"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,151 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "en-GB",
|
|
||||||
"texts": {
|
|
||||||
"headers": {
|
|
||||||
"home": "Home",
|
|
||||||
"cv": "CV",
|
|
||||||
"publications_projects": "Publications/Projects",
|
|
||||||
"consulting": "Consulting",
|
|
||||||
"about": "About",
|
|
||||||
"language_buttons": {
|
|
||||||
"english": "🇬🇧 English",
|
|
||||||
"german": "🇩🇪 German"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home": {
|
|
||||||
"card": {
|
|
||||||
"name": "Tuan-Dat Tran",
|
|
||||||
"gender": "(He/Him)",
|
|
||||||
"l1": "Hey there! 👋🏻👋🏼👋🏽👋🏾👋🏿",
|
|
||||||
"l2": "Welcome to my little place on the internet.",
|
|
||||||
"l3": "While you're here, why don't you check out my projects over on ",
|
|
||||||
"l3_1": "?",
|
|
||||||
"contact_button": "Get in touch."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cv": {
|
|
||||||
"introduction": "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.",
|
|
||||||
"workexperience": {
|
|
||||||
"title": "Work Experience",
|
|
||||||
"se1_gefeba": {
|
|
||||||
"title": "Software Engineer @ gefeba Engineering GmbH",
|
|
||||||
"time": "2013 - 2015",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"student_fse": {
|
|
||||||
"title": "Student Council Member @ UDE",
|
|
||||||
"time": "2016 - 2019",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"se2_gefeba": {
|
|
||||||
"title": "Software Engineer @ gefeba Engineering GmbH",
|
|
||||||
"time": "2018 - 2020",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"mentoring_ude": {
|
|
||||||
"title": "Mentoring @ UDE",
|
|
||||||
"time": "2021 - 2022",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"ra_ude": {
|
|
||||||
"title": "Research Assistant @ UDE",
|
|
||||||
"time": "2021 - now",
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"education": {
|
|
||||||
"title": "Education",
|
|
||||||
"bachelor": {
|
|
||||||
"title": "BSc Systems Engineering",
|
|
||||||
"time": "2015 - now",
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"skills": {
|
|
||||||
"title": "Skills",
|
|
||||||
"devops": {
|
|
||||||
"title": "DevOps",
|
|
||||||
"ansible": "Ansible",
|
|
||||||
"kubernetes": "Kubernetes",
|
|
||||||
"gitops": "GitOps"
|
|
||||||
},
|
|
||||||
"software_engineering": {
|
|
||||||
"title": "Software Engineering",
|
|
||||||
"rust": "Rust",
|
|
||||||
"python": "Python",
|
|
||||||
"csharp": "C#"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"languages": {
|
|
||||||
"title": "Languages",
|
|
||||||
"german": "German (Native)",
|
|
||||||
"english": "English (C2)",
|
|
||||||
"vietnamese": "Vietnamese (B1)",
|
|
||||||
"japanese": "Japanese (A1)"
|
|
||||||
},
|
|
||||||
"interests": {
|
|
||||||
"title": "Interests",
|
|
||||||
"coffee": "Coffee",
|
|
||||||
"tech_it": "Tech/IT",
|
|
||||||
"guitar": "Guitar",
|
|
||||||
"mechanical_keyboards": "Mechanical Keyboards"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"publications_projects": {
|
|
||||||
"publications": {
|
|
||||||
"title": "Publications",
|
|
||||||
"rpm": {
|
|
||||||
"title": "RPM: Reverse Path Congestion Marking on P4 Programmable Switches",
|
|
||||||
"authors": "N. Baganal-Krishna, T.-D. Tran, R. Kundel and A. Rizk",
|
|
||||||
"conference": "IEEE LCN 2023",
|
|
||||||
"url": "https://doi.org/10.48550/arXiv.2307.09639",
|
|
||||||
"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."
|
|
||||||
},
|
|
||||||
"iot_fuzzers": {
|
|
||||||
"title": "Overview of IoT Fuzzing Techniques",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"conference": "Seminar",
|
|
||||||
"url": "https://git.tudattr.dev/AISE/seminar/src/branch/main/paper.pdf",
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"projects": {
|
|
||||||
"title": "Projects",
|
|
||||||
"bachelorproject": {
|
|
||||||
"title": "Undisclosed Ethereum Smart Contract Fuzzer",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"kind": "Bachelorproject",
|
|
||||||
"url": "https://git.ude-syssec.de/uni-due-syssec/students/2022_tuan-dat_tran_libAFLEVMFuzzer/ethfuzz/",
|
|
||||||
"description": "In this ongoing project I am building an Ethereum Smart Contract Fuzzer. More info will follow."
|
|
||||||
},
|
|
||||||
"dotfiles": {
|
|
||||||
"title": ".dotfiles",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"kind": "Personal",
|
|
||||||
"url": "https://git.tudattr.dev/tudattr/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. "
|
|
||||||
},
|
|
||||||
"homelab": {
|
|
||||||
"title": "Homelab",
|
|
||||||
"authors": "Tuan-Dat Tran",
|
|
||||||
"kind": "Personal",
|
|
||||||
"url": "https://git.tudattr.dev/tudattr/ansible",
|
|
||||||
"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. "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"impressum": {
|
|
||||||
"off": "Show Impressum",
|
|
||||||
"on": "Impressum"
|
|
||||||
},
|
|
||||||
"components": {
|
|
||||||
"under_construction": "This page is currently under construction"
|
|
||||||
},
|
|
||||||
"footer": {
|
|
||||||
"year": "© 2024 ",
|
|
||||||
"name": "Tuan-Dat Tran",
|
|
||||||
"rights": ". All Rights Reserved.",
|
|
||||||
"contact": "Contact"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,24 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::{i18n::use_i18, translate};
|
use dioxus_i18n::t;
|
||||||
|
|
||||||
use crate::components::H1;
|
|
||||||
|
|
||||||
pub fn Footer() -> Element {
|
pub fn Footer() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "container mx-auto",
|
class: "container mx-auto pb-4",
|
||||||
// ToolsUsed {},
|
|
||||||
footer {
|
footer {
|
||||||
class:"bg-white rounded-lg shadow dark:bg-gray-800",
|
class:"rounded-lg shadow bg-gray-800",
|
||||||
div {
|
div {
|
||||||
class:"w-full mx-auto p-4 flex items-center justify-between",
|
class:"w-full mx-auto p-4 flex items-center justify-between",
|
||||||
span {
|
span {
|
||||||
class:"text-sm text-gray-500 sm:text-center dark:text-gray-400",
|
class:"text-sm sm:text-center text-gray-400",
|
||||||
{ translate!(i18, "footer.year") },
|
{ t!("footer_year") },
|
||||||
a { href: "#", class: "hover:underline", { translate!(i18, "footer.name") }},
|
a { href: "#", class: "hover:underline", { t!("footer_name") }},
|
||||||
{ translate!(i18, "footer.rights") }
|
{ t!("footer_rights") }
|
||||||
}
|
}
|
||||||
ul {
|
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 {
|
li {
|
||||||
Link { to:"mailto:tuan-dat.tran@tudattr.dev", class:"hover:underline", { translate!(i18, "footer.contact") } }
|
Link { to:"mailto:tuan-dat.tran@tudattr.dev", class:"hover:underline", { t!("footer_contact") } }
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,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]
|
#[component]
|
||||||
fn Logo(src: String, alt: String) -> Element {
|
fn Logo(src: String, alt: String) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::{i18n::*, translate};
|
use dioxus_i18n::{prelude::i18n, t, unic_langid::langid};
|
||||||
|
|
||||||
use crate::Route;
|
use crate::Route;
|
||||||
|
|
||||||
pub fn Header() -> Element {
|
pub fn Header() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
nav {
|
nav {
|
||||||
div {
|
div {
|
||||||
// class: "justify-between p-4 space-x-8",
|
class: "container mx-auto py-4",
|
||||||
class: "container mx-auto p-4",
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
class:"flex flex-col justify-between sm:flex-row justify-center space-y-2 sm:space-y-0 sm:space-s-4",
|
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 {
|
li {
|
||||||
Link {
|
Link {
|
||||||
to: Route::Home {},
|
to: Route::Home {},
|
||||||
class: "rounded-md shadow-sm",
|
class: "rounded-md shadow-sm",
|
||||||
img { src:"/pictures/ClackCat_t.webp", class:"rounded-full h-8", alt:"TuDatTr Logo" },
|
img { src:asset!("./assets/pictures/ClackCat_t.webp"), class:"rounded-full h-8", alt:"TuDatTr Logo" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
li { HeaderLink { url: Route::Home {}, text: translate!(i18, "headers.home")} },
|
li { HeaderLink { url: Route::Home {}, text: t!("headers_home")} },
|
||||||
li { HeaderLink { url: Route::CV {}, text: translate!(i18, "headers.cv") } },
|
li { HeaderLink { url: Route::CV {}, text: t!("headers_cv") } },
|
||||||
li { HeaderLink { url: Route::PublicationsProjects {}, text: translate!(i18, "headers.publications_projects") } },
|
li { HeaderLink { url: Route::PublicationsProjects {}, text: t!("headers_publications_projects") } },
|
||||||
li { HeaderLink { url: Route::Consulting {}, text: translate!(i18, "headers.consulting") } },
|
li { HeaderLink { url: Route::Impressum {}, text: t!("headers_about") } },
|
||||||
li { HeaderLink { url: Route::Impressum {}, text: translate!(i18, "headers.about") } },
|
|
||||||
li { LanguageButtonGroup {} },
|
li { LanguageButtonGroup {} },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -35,16 +31,22 @@ pub fn Header() -> Element {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn LanguageButtonGroup() -> Element {
|
fn LanguageButtonGroup() -> Element {
|
||||||
let mut i18 = use_i18();
|
let mut i18n = i18n();
|
||||||
|
|
||||||
let change_to_english = move |_| i18.set_language("en-GB".parse().unwrap());
|
let change_to_english = move |_| i18n.set_language(langid!("en-GB"));
|
||||||
let change_to_german = move |_| i18.set_language("de-DE".parse().unwrap());
|
let change_to_german = move |_| i18n.set_language(langid!("de-DE"));
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "rounded-md shadow-sm justify-end",
|
class: "rounded-md shadow-sm justify-end",
|
||||||
button { class: "px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-s-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white", onclick: change_to_english, label { { translate!(i18, "headers.language_buttons.english") } } },
|
button {
|
||||||
button { class: "px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-e-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white", onclick: change_to_german, label { { translate!(i18, "headers.language_buttons.german") } } }
|
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") } } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,6 @@ fn LanguageButtonGroup() -> Element {
|
|||||||
#[component]
|
#[component]
|
||||||
fn HeaderLink(url: Route, text: String) -> Element {
|
fn HeaderLink(url: Route, text: String) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
Link { to: url, class:"md:bg-transparent md:text-blue-700 md:p-0 dark:text-white md:dark:text-blue-500", {text} }
|
Link { to: url, class:"md:bg-transparent md:p-0 text-blue-500", {text} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
mod footer;
|
pub mod footer;
|
||||||
mod header;
|
pub mod header;
|
||||||
|
|
||||||
use crate::{Body, Route};
|
use crate::{Body, Route};
|
||||||
use footer::Footer;
|
use footer::Footer;
|
||||||
@@ -9,17 +9,13 @@ use header::Header;
|
|||||||
|
|
||||||
pub fn Layout() -> Element {
|
pub fn Layout() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
meta {
|
|
||||||
name: "description",
|
|
||||||
content: "Explore Tuan-Dat Tran's personal website featuring his CV, publications, projects, and consulting services. Get insights into his professional journey and connect for collaboration opportunities.",
|
|
||||||
}
|
|
||||||
div {
|
div {
|
||||||
class: "flex flex-col min-h-screen" ,
|
class: "dark flex flex-col min-h-screen",
|
||||||
Header {},
|
Header {},
|
||||||
Body {
|
Body {
|
||||||
Outlet::<Route> {},
|
Outlet::<Route> {},
|
||||||
}
|
},
|
||||||
Footer {},
|
Footer {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
74
src/main.rs
@@ -1,27 +1,26 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use std::str::FromStr;
|
use components::H1;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::i18n::*;
|
|
||||||
|
|
||||||
|
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;
|
use tracing::Level;
|
||||||
|
|
||||||
pub mod components;
|
pub mod components;
|
||||||
mod consulting;
|
|
||||||
mod cv;
|
mod cv;
|
||||||
mod home;
|
mod home;
|
||||||
mod impressum;
|
mod impressum;
|
||||||
mod languages;
|
|
||||||
mod layout;
|
mod layout;
|
||||||
mod publications;
|
mod publications;
|
||||||
|
|
||||||
use crate::consulting::Consulting;
|
|
||||||
use crate::cv::CV;
|
use crate::cv::CV;
|
||||||
use crate::home::Home;
|
use crate::home::Home;
|
||||||
use crate::impressum::Impressum;
|
use crate::impressum::Impressum;
|
||||||
use crate::languages::DE_DE;
|
|
||||||
use crate::languages::EN_GB;
|
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::publications::PublicationsProjects;
|
use crate::publications::PublicationsProjects;
|
||||||
|
|
||||||
@@ -36,8 +35,6 @@ pub enum Route {
|
|||||||
PublicationsProjects {},
|
PublicationsProjects {},
|
||||||
#[route("/resume")]
|
#[route("/resume")]
|
||||||
CV {},
|
CV {},
|
||||||
#[route("/consulting")]
|
|
||||||
Consulting {},
|
|
||||||
#[end_layout]
|
#[end_layout]
|
||||||
#[route("/:..route")]
|
#[route("/:..route")]
|
||||||
PageNotFound { route: Vec<String> },
|
PageNotFound { route: Vec<String> },
|
||||||
@@ -45,23 +42,46 @@ pub enum Route {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_logger::init(Level::DEBUG).expect("failed to init logger");
|
dioxus_logger::init(Level::DEBUG).expect("failed to init logger");
|
||||||
LaunchBuilder::fullstack()
|
LaunchBuilder::new().launch(App)
|
||||||
.with_cfg(server_only!(dioxus::fullstack::Config::new().addr(
|
|
||||||
std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(0, 0, 0, 0), 8080,)
|
|
||||||
)))
|
|
||||||
.launch(App)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn App() -> Element {
|
fn App() -> Element {
|
||||||
use_init_i18n("en-GB".parse().unwrap(), "en-GB".parse().unwrap(), || {
|
use_init_i18n(|| {
|
||||||
let en_gb = Language::from_str(EN_GB).unwrap();
|
I18nConfig::new(langid!("en-GB"))
|
||||||
let de_de = Language::from_str(DE_DE).unwrap();
|
.with_locale(Locale::new_static(
|
||||||
vec![en_gb, de_de]
|
langid!("en-GB"),
|
||||||
|
include_str!("./languages/en-GB.ftl"),
|
||||||
|
))
|
||||||
|
.with_locale(Locale::new_static(
|
||||||
|
langid!("de-DE"),
|
||||||
|
include_str!("./languages/de-DE.ftl"),
|
||||||
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
|
document::Link { rel: "stylesheet", href: asset!("./assets/tailwind.css") }
|
||||||
|
document::Link { rel: "icon", href: asset!("./assets/favicon.ico") }
|
||||||
|
meta {
|
||||||
|
name: "description",
|
||||||
|
content: "Visit Tuan-Dat Tran's website for his CV, publications, projects, and consulting services. Connect for collaboration.",
|
||||||
|
},
|
||||||
|
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 {
|
div {
|
||||||
class: "bg-white dark:bg-gray-900 min-h-screen",
|
class: "bg-gray-900 min-h-screen",
|
||||||
Router::<Route> {},
|
Router::<Route> {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,12 +91,16 @@ fn App() -> Element {
|
|||||||
fn PageNotFound(route: Vec<String>) -> Element {
|
fn PageNotFound(route: Vec<String>) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "h-screen items-center justify-center",
|
class: "flex flex-col min-h-screen items",
|
||||||
img {
|
Header {},
|
||||||
class: "size-auto",
|
div {
|
||||||
src: "https://raw.githubusercontent.com/SAWARATSUKI/ServiceLogos/main/404Notfound/NotFound.png"
|
class: "container mx-auto p-4 flex items-center justify-center max-w-md w-full",
|
||||||
|
H1 {
|
||||||
|
"Site not found (404)"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
Footer {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_sdk::{i18n::use_i18, translate};
|
use dioxus_i18n::t;
|
||||||
|
|
||||||
use crate::components::{UnderConstruction, H1, HR};
|
use crate::components::{Bolding, H1, HR};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PublicationsProjects() -> Element {
|
pub fn PublicationsProjects() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex flex-col ",
|
class: "flex flex-col ",
|
||||||
UnderConstruction { },
|
|
||||||
div {
|
div {
|
||||||
H1 { { translate!(i18, "publications_projects.publications.title") } }
|
H1 { { t!("publications_projects_publications_title") } }
|
||||||
Publications { },
|
Publications { },
|
||||||
},
|
},
|
||||||
HR {},
|
HR {},
|
||||||
div {
|
div {
|
||||||
H1 { { translate!(i18, "publications_projects.projects.title") } }
|
H1 { { t!("publications_projects_projects_title") } }
|
||||||
Projects { },
|
Projects { },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,50 +33,48 @@ struct PublicationProp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn Publications() -> Element {
|
fn Publications() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex gap-4 items-center flex-wrap",
|
class: "flex gap-4 items-center flex-wrap",
|
||||||
Publication {
|
Publication {
|
||||||
title: translate!(i18, "publications_projects.publications.rpm.title"),
|
title: t!("publications_projects_publications_rpm_title"),
|
||||||
authors: translate!(i18, "publications_projects.publications.rpm.authors"),
|
authors: t!("publications_projects_publications_rpm_authors"),
|
||||||
conference: translate!(i18, "publications_projects.publications.rpm.conference"),
|
conference: t!("publications_projects_publications_rpm_conference"),
|
||||||
doi: translate!(i18, "publications_projects.publications.rpm.url"),
|
doi: t!("publications_projects_publications_rpm_url"),
|
||||||
description: translate!(i18, "publications_projects.publications.rpm.description")
|
description: t!("publications_projects_publications_rpm_description")
|
||||||
},
|
},
|
||||||
|
|
||||||
Publication {
|
Publication {
|
||||||
title: translate!(i18, "publications_projects.publications.iot_fuzzers.title"),
|
title: t!("publications_projects_publications_iot_fuzzers_title"),
|
||||||
authors: translate!(i18, "publications_projects.publications.iot_fuzzers.authors"),
|
authors: t!("publications_projects_publications_iot_fuzzers_authors"),
|
||||||
conference: translate!(i18, "publications_projects.publications.iot_fuzzers.conference"),
|
conference: t!("publications_projects_publications_iot_fuzzers_conference"),
|
||||||
doi: translate!(i18, "publications_projects.publications.iot_fuzzers.url"),
|
doi: "/#",
|
||||||
description: translate!(i18, "publications_projects.publications.iot_fuzzers.description")
|
description: t!("publications_projects_publications_iot_fuzzers_description")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn Publication(prop: PublicationProp) -> Element {
|
fn Publication(prop: PublicationProp) -> Element {
|
||||||
let pattern = "T.-D. Tran";
|
let pattern = vec!["T.-D. Tran".to_string(), "Tuan-Dat Tran".to_string()];
|
||||||
rsx! {
|
rsx! {
|
||||||
Link {
|
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}",
|
to:"{prop.doi}",
|
||||||
new_tab: true,
|
new_tab: true,
|
||||||
h5 {
|
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}",
|
"{prop.title}",
|
||||||
},
|
},
|
||||||
span { class: "text-lg text-gray-900 dark:text-white", "{prop.conference}" },
|
span { class: "text-lg text-white", "{prop.conference}" },
|
||||||
p {
|
p {
|
||||||
class:"font-normal text-gray-700 dark:text-gray-400 italic",
|
class:"font-normal text-gray-400 italic",
|
||||||
Authors {
|
Bolding {
|
||||||
authors: "{prop.authors}",
|
authors: "{prop.authors}",
|
||||||
pattern: "{pattern}",
|
patterns: pattern,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
class:"font-normal text-gray-700 dark:text-gray-400",
|
class:"font-normal text-gray-400",
|
||||||
"{prop.description}",
|
"{prop.description}",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,31 +82,36 @@ fn Publication(prop: PublicationProp) -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn Projects() -> Element {
|
fn Projects() -> Element {
|
||||||
let i18 = use_i18();
|
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "flex gap-4 items-center flex-wrap",
|
class: "flex gap-4 items-center flex-wrap",
|
||||||
Project {
|
Project {
|
||||||
title: translate!(i18, "publications_projects.projects.bachelorproject.title"),
|
title: t!("publications_projects_projects_bpba_title"),
|
||||||
authors: translate!(i18, "publications_projects.projects.bachelorproject.authors"),
|
authors: t!("publications_projects_projects_bpba_authors"),
|
||||||
kind: translate!(i18, "publications_projects.projects.bachelorproject.kind"),
|
kind: t!("publications_projects_projects_bpba_kind"),
|
||||||
url: translate!(i18, "publications_projects.projects.bachelorproject.url"),
|
url: "/#",
|
||||||
description: translate!(i18, "publications_projects.projects.bachelorproject.description")
|
description: t!("publications_projects_projects_bpba_description")
|
||||||
},
|
},
|
||||||
Project {
|
Project {
|
||||||
title: translate!(i18, "publications_projects.projects.dotfiles.title"),
|
title: t!("publications_projects_projects_dotfiles_title"),
|
||||||
authors: translate!(i18, "publications_projects.projects.dotfiles.authors"),
|
authors: t!("publications_projects_projects_dotfiles_authors"),
|
||||||
kind: translate!(i18, "publications_projects.projects.dotfiles.kind"),
|
kind: t!("publications_projects_projects_dotfiles_kind"),
|
||||||
url: translate!(i18, "publications_projects.projects.dotfiles.url"),
|
url: "/#",
|
||||||
description: translate!(i18, "publications_projects.projects.dotfiles.description")
|
description: t!("publications_projects_projects_dotfiles_description")
|
||||||
},
|
},
|
||||||
Project {
|
Project {
|
||||||
title: translate!(i18, "publications_projects.projects.homelab.title"),
|
title: t!("publications_projects_projects_homelab_title"),
|
||||||
authors: translate!(i18, "publications_projects.projects.homelab.authors"),
|
authors: t!("publications_projects_projects_homelab_authors"),
|
||||||
kind: translate!(i18, "publications_projects.projects.homelab.kind"),
|
kind: t!("publications_projects_projects_homelab_kind"),
|
||||||
url: translate!(i18, "publications_projects.projects.homelab.url"),
|
url: "/#",
|
||||||
description: translate!(i18, "publications_projects.projects.homelab.description")
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,49 +129,29 @@ struct ProjectProp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn Project(prop: ProjectProp) -> Element {
|
fn Project(prop: ProjectProp) -> Element {
|
||||||
let pattern = "T.-D. Tran";
|
let pattern = vec!["T.-D. Tran".to_string(), "Tuan-Dat Tran".to_string()];
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
Link {
|
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}",
|
to:"{prop.url}",
|
||||||
new_tab: true,
|
new_tab: true,
|
||||||
h5 {
|
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}",
|
"{prop.title}",
|
||||||
},
|
},
|
||||||
p { class: "text-lg text-gray-900 dark:text-white", "{prop.kind}" },
|
p { class: "text-lg text-white", "{prop.kind}" },
|
||||||
p {
|
p {
|
||||||
class:"font-normal text-gray-700 dark:text-gray-400",
|
class:"font-normal text-gray-400",
|
||||||
Authors {
|
Bolding {
|
||||||
authors: "{prop.authors}",
|
authors: "{prop.authors}",
|
||||||
pattern: "{pattern}",
|
patterns: pattern,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
class:"font-normal text-gray-700 dark:text-gray-400",
|
class:"font-normal text-gray-400",
|
||||||
"{prop.description}",
|
"{prop.description}",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Props)]
|
|
||||||
struct AuthorProp {
|
|
||||||
authors: String,
|
|
||||||
pattern: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn Authors(prop: AuthorProp) -> Element {
|
|
||||||
if let Some(start) = prop.authors.find(&prop.pattern) {
|
|
||||||
let end = start + prop.pattern.len();
|
|
||||||
let left = &prop.authors[..start];
|
|
||||||
let middle = &prop.authors[start..end];
|
|
||||||
let right = &prop.authors[end..];
|
|
||||||
rsx! {
|
|
||||||
"{left}" , b { "{middle}" }, "{right}",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rsx! { "{prop.authors}" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||