First commit.
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
.idea
|
1107
Cargo.lock
generated
Normal file
20
Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "homepage"
|
||||||
|
authors = ["Viyurz <viyurz@viyurz.fr>"]
|
||||||
|
edition = "2021"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://git.ahur.ac/Viyurz/homepage"
|
||||||
|
license = "WTFPL"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.7.4"
|
||||||
|
# fluent = "0.16.0"
|
||||||
|
minijinja = "1.0.12"
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
|
toml = "0.8.10"
|
||||||
|
tower-http = { version = "0.5.1", features = ["fs", "timeout", "trace"] }
|
||||||
|
tracing = "0.1.40"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
47
Dockerfile
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# Build image
|
||||||
|
FROM docker.io/rust:alpine AS build
|
||||||
|
|
||||||
|
RUN apk add --no-cache musl-dev
|
||||||
|
|
||||||
|
WORKDIR /usr/src
|
||||||
|
|
||||||
|
# Create blank project
|
||||||
|
RUN cargo new homepage
|
||||||
|
|
||||||
|
WORKDIR /usr/src/homepage
|
||||||
|
|
||||||
|
COPY Cargo.* .
|
||||||
|
|
||||||
|
# Dummy build to cache dependencies
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
COPY templates ./templates
|
||||||
|
|
||||||
|
# Build the actual app
|
||||||
|
RUN touch src/main.rs && \
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Runtime image
|
||||||
|
FROM docker.io/alpine
|
||||||
|
|
||||||
|
WORKDIR /homepage
|
||||||
|
|
||||||
|
ARG GID=8686
|
||||||
|
ARG UID=8686
|
||||||
|
|
||||||
|
RUN addgroup -g ${GID} homepage && \
|
||||||
|
adduser -u ${UID} -D -H -G homepage homepage
|
||||||
|
|
||||||
|
USER homepage:homepage
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
COPY --from=build /usr/src/homepage/target/release/homepage /usr/local/bin/homepage
|
||||||
|
|
||||||
|
COPY assets ./assets
|
||||||
|
|
||||||
|
ENTRYPOINT ["homepage"]
|
13
LICENSE.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 2, December 2004
|
||||||
|
|
||||||
|
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim or modified
|
||||||
|
copies of this license document, and changing it is allowed as long
|
||||||
|
as the name is changed.
|
||||||
|
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# homepage
|
||||||
|
|
||||||
|
Source for my [homepage](https://viyurz.fr/), built with Axum & Tailwind (CSS gives me nightmares).
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Rename `services.toml.example` to `services.toml` & define your services.
|
BIN
assets/element.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
assets/etesync.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
assets/favicon-16x16.png
Normal file
After Width: | Height: | Size: 978 B |
BIN
assets/favicon-32x32.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/favicon.ico
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
assets/git.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/hedgedoc.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
744
assets/index.css
Normal file
|
@ -0,0 +1,744 @@
|
||||||
|
/*
|
||||||
|
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||||
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||||
|
*/
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* 1 */
|
||||||
|
border-width: 0;
|
||||||
|
/* 2 */
|
||||||
|
border-style: solid;
|
||||||
|
/* 2 */
|
||||||
|
border-color: currentColor;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
--tw-content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use a consistent sensible line-height in all browsers.
|
||||||
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
3. Use a more readable tab size.
|
||||||
|
4. Use the user's configured `sans` font-family by default.
|
||||||
|
5. Use the user's configured `sans` font-feature-settings by default.
|
||||||
|
6. Use the user's configured `sans` font-variation-settings by default.
|
||||||
|
7. Disable tap highlights on iOS
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
:host {
|
||||||
|
line-height: 1.5;
|
||||||
|
/* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
/* 2 */
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
/* 3 */
|
||||||
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
/* 4 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 5 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 6 */
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
/* 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove the margin in all browsers.
|
||||||
|
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Add the correct height in Firefox.
|
||||||
|
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||||
|
3. Ensure horizontal rules are visible by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-top-width: 1px;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr:where([title]) {
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the default font size and weight for headings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset links to optimize for opt-in styling instead of opt-out.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font weight in Edge and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use the user's configured `mono` font-family by default.
|
||||||
|
2. Use the user's configured `mono` font-feature-settings by default.
|
||||||
|
3. Use the user's configured `mono` font-variation-settings by default.
|
||||||
|
4. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp,
|
||||||
|
pre {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: normal;
|
||||||
|
/* 2 */
|
||||||
|
font-variation-settings: normal;
|
||||||
|
/* 3 */
|
||||||
|
font-size: 1em;
|
||||||
|
/* 4 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||||
|
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||||
|
3. Remove gaps between table borders by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
text-indent: 0;
|
||||||
|
/* 1 */
|
||||||
|
border-color: inherit;
|
||||||
|
/* 2 */
|
||||||
|
border-collapse: collapse;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Change the font styles in all browsers.
|
||||||
|
2. Remove the margin in Firefox and Safari.
|
||||||
|
3. Remove default padding in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-feature-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-variation-settings: inherit;
|
||||||
|
/* 1 */
|
||||||
|
font-size: 100%;
|
||||||
|
/* 1 */
|
||||||
|
font-weight: inherit;
|
||||||
|
/* 1 */
|
||||||
|
line-height: inherit;
|
||||||
|
/* 1 */
|
||||||
|
color: inherit;
|
||||||
|
/* 1 */
|
||||||
|
margin: 0;
|
||||||
|
/* 2 */
|
||||||
|
padding: 0;
|
||||||
|
/* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inheritance of text transform in Edge and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Remove default button styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type='button'],
|
||||||
|
[type='reset'],
|
||||||
|
[type='submit'] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
background-color: transparent;
|
||||||
|
/* 2 */
|
||||||
|
background-image: none;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the modern Firefox focus style for all focusable elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct vertical alignment in Chrome and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Correct the cursor style of increment and decrement buttons in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type='search'] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
/* 1 */
|
||||||
|
outline-offset: -2px;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
/* 1 */
|
||||||
|
font: inherit;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct display in Chrome and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes the default spacing and border for appropriate elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset default styling for dialogs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent resizing textareas horizontally by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||||
|
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input::-moz-placeholder, textarea::-moz-placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
/* 1 */
|
||||||
|
color: #9ca3af;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the default cursor for buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make sure disabled buttons don't get the pointer cursor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||||
|
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block;
|
||||||
|
/* 1 */
|
||||||
|
vertical-align: middle;
|
||||||
|
/* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, ::before, ::after {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
::backdrop {
|
||||||
|
--tw-border-spacing-x: 0;
|
||||||
|
--tw-border-spacing-y: 0;
|
||||||
|
--tw-translate-x: 0;
|
||||||
|
--tw-translate-y: 0;
|
||||||
|
--tw-rotate: 0;
|
||||||
|
--tw-skew-x: 0;
|
||||||
|
--tw-skew-y: 0;
|
||||||
|
--tw-scale-x: 1;
|
||||||
|
--tw-scale-y: 1;
|
||||||
|
--tw-pan-x: ;
|
||||||
|
--tw-pan-y: ;
|
||||||
|
--tw-pinch-zoom: ;
|
||||||
|
--tw-scroll-snap-strictness: proximity;
|
||||||
|
--tw-gradient-from-position: ;
|
||||||
|
--tw-gradient-via-position: ;
|
||||||
|
--tw-gradient-to-position: ;
|
||||||
|
--tw-ordinal: ;
|
||||||
|
--tw-slashed-zero: ;
|
||||||
|
--tw-numeric-figure: ;
|
||||||
|
--tw-numeric-spacing: ;
|
||||||
|
--tw-numeric-fraction: ;
|
||||||
|
--tw-ring-inset: ;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-color: rgb(59 130 246 / 0.5);
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-colored: 0 0 #0000;
|
||||||
|
--tw-blur: ;
|
||||||
|
--tw-brightness: ;
|
||||||
|
--tw-contrast: ;
|
||||||
|
--tw-grayscale: ;
|
||||||
|
--tw-hue-rotate: ;
|
||||||
|
--tw-invert: ;
|
||||||
|
--tw-saturate: ;
|
||||||
|
--tw-sepia: ;
|
||||||
|
--tw-drop-shadow: ;
|
||||||
|
--tw-backdrop-blur: ;
|
||||||
|
--tw-backdrop-brightness: ;
|
||||||
|
--tw-backdrop-contrast: ;
|
||||||
|
--tw-backdrop-grayscale: ;
|
||||||
|
--tw-backdrop-hue-rotate: ;
|
||||||
|
--tw-backdrop-invert: ;
|
||||||
|
--tw-backdrop-opacity: ;
|
||||||
|
--tw-backdrop-saturate: ;
|
||||||
|
--tw-backdrop-sepia: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-4 {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-4 {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-16 {
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-20 {
|
||||||
|
height: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-60 {
|
||||||
|
height: 15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-7 {
|
||||||
|
height: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-h-screen {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-16 {
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basis-1\/3 {
|
||||||
|
flex-basis: 33.333333%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.basis-3\/12 {
|
||||||
|
flex-basis: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-cols-2 {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.place-content-around {
|
||||||
|
place-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-end {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divide-x-2 > :not([hidden]) ~ :not([hidden]) {
|
||||||
|
--tw-divide-x-reverse: 0;
|
||||||
|
border-right-width: calc(2px * var(--tw-divide-x-reverse));
|
||||||
|
border-left-width: calc(2px * calc(1 - var(--tw-divide-x-reverse)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-lg {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-2 {
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-transparent {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-background {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(41 7 24 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(96 18 55 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-0 {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-2 {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-3 {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-6 {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-12 {
|
||||||
|
padding-left: 3rem;
|
||||||
|
padding-right: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-2 {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-4 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-white {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-50 {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opacity-75 {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover\:border-white:hover {
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
||||||
|
}
|
BIN
assets/logo.jpg
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
assets/mail-server.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
assets/matrix.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/nextcloud.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
assets/searxng.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
assets/stump.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
assets/vaultwarden.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/wallpaper.jpg
Normal file
After Width: | Height: | Size: 576 KiB |
7
services.toml.example
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[[services]]
|
||||||
|
name = "My service"
|
||||||
|
description = "My service description."
|
||||||
|
domain = "myservice.viyurz.fr"
|
||||||
|
image = "/assets/myservice.png"
|
||||||
|
language = "MyLanguage"
|
||||||
|
repository_url = "https://github.com/myservicerepository"
|
121
src/main.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::time::Duration;
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::{response::Html, routing::get, Router};
|
||||||
|
use minijinja::{context, Environment};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
use toml::from_str;
|
||||||
|
use tokio::signal;
|
||||||
|
use tower_http::timeout::TimeoutLayer;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
env: Environment<'static>,
|
||||||
|
services: Vec<Service>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ServiceVec {
|
||||||
|
services: Vec<Service>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
struct Service {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
domain: String,
|
||||||
|
image: String,
|
||||||
|
language: String,
|
||||||
|
repository_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
|
// axum logs rejections from built-in extractors with the `axum::rejection`
|
||||||
|
// target, at `TRACE` level. `axum::rejection=trace` enables showing those events
|
||||||
|
"homepage=debug,tower_http=debug,axum::rejection=trace".into()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Read services from file
|
||||||
|
let services_str = fs::read_to_string("services.toml")
|
||||||
|
.expect("Failed to read services.toml file.");
|
||||||
|
let service_vec: ServiceVec = from_str::<ServiceVec>(&services_str).unwrap();
|
||||||
|
|
||||||
|
// init template engine and add templates
|
||||||
|
let mut env = Environment::new();
|
||||||
|
env.add_template("_base", include_str!("../templates/_base.html"))
|
||||||
|
.unwrap();
|
||||||
|
env.add_template("_navbar", include_str!("../templates/_navbar.html"))
|
||||||
|
.unwrap();
|
||||||
|
env.add_template("home", include_str!("../templates/home.html"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// pass env & services to handlers via state
|
||||||
|
let app_state = Arc::new(AppState { env, services: service_vec.services });
|
||||||
|
|
||||||
|
// define routes
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(handler_home))
|
||||||
|
.nest_service("/assets", ServeDir::new("assets"))
|
||||||
|
.layer((
|
||||||
|
TraceLayer::new_for_http(),
|
||||||
|
// Graceful shutdown will wait for outstanding requests to complete. Add a timeout so
|
||||||
|
// requests don't hang forever.
|
||||||
|
TimeoutLayer::new(Duration::from_secs(10)),
|
||||||
|
))
|
||||||
|
.with_state(app_state);
|
||||||
|
|
||||||
|
// run it
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
println!("listening on {}", listener.local_addr().unwrap());
|
||||||
|
axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handler_home(State(state): State<Arc<AppState>>) -> Result<Html<String>, StatusCode> {
|
||||||
|
let template = state.env.get_template("home").unwrap();
|
||||||
|
|
||||||
|
let rendered = template
|
||||||
|
.render(context! {
|
||||||
|
title => "Home",
|
||||||
|
services => state.services
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(Html(rendered))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn shutdown_signal() {
|
||||||
|
let ctrl_c = async {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to install Ctrl+C handler");
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
let terminate = async {
|
||||||
|
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||||
|
.expect("failed to install signal handler")
|
||||||
|
.recv()
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let terminate = std::future::pending::<()>();
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = ctrl_c => {},
|
||||||
|
_ = terminate => {},
|
||||||
|
}
|
||||||
|
}
|
16
tailwind.config.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ["./templates/**/*.html"],
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
transparent: 'transparent',
|
||||||
|
current: 'currentColor',
|
||||||
|
white: '#ffffff',
|
||||||
|
primary: "#601237",
|
||||||
|
background: "#290718",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
|
||||||
|
}
|
||||||
|
|
26
tailwind.css
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
h1, h2 {
|
||||||
|
@apply p-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
@apply font-bold;
|
||||||
|
@apply text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply text-3xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@apply text-2xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
@apply text-xl;
|
||||||
|
}
|
||||||
|
}
|
22
templates/_base.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>{{ title }} - Viyurz</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png">
|
||||||
|
<meta property="og:title" content="{{ title }} - Viyurz">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="https://viyurz.fr">
|
||||||
|
<meta property="og:description" content="{% block description %}Home of Viyurz.{% endblock %}">
|
||||||
|
<meta property="og:image" content="https://viyurz.fr/assets/logo.jpg">
|
||||||
|
<link href="/assets/index.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen bg-background text-white">
|
||||||
|
{% include "_navbar" %}
|
||||||
|
<main class="flex flex-col items-center p-6">
|
||||||
|
{% block main %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
18
templates/_navbar.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<nav class="flex flex-row place-content-around h-20 bg-primary px-12">
|
||||||
|
<div class="basis-1/3 flex flex-row items-center">
|
||||||
|
<a href="/" class="flex flex-row items-center p-0">
|
||||||
|
<img src="/assets/logo.jpg" alt="logo.jpg" class="size-16 rounded-lg p-3">
|
||||||
|
<h2>Viyurz</h2>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<ul class="basis-1/3 flex flex-row justify-center items-center">
|
||||||
|
<li><h2><a href="/">Home</a></h2></li>
|
||||||
|
<li><h2><a href="mailto:viyurz@viyurz.fr">Contact</a></h2></li>
|
||||||
|
</ul>
|
||||||
|
<div class="basis-1/3 flex flex-row justify-end items-center">
|
||||||
|
<div class="grid grid-cols-2 divide-x-2 rounded-lg bg-background p-2">
|
||||||
|
<button class="px-2">English</button>
|
||||||
|
<button class="opacity-50 px-2">Français</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
23
templates/home.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "_base" %}
|
||||||
|
{% block main %}
|
||||||
|
<h1>Services</h1>
|
||||||
|
<div class="flex flex-row flex-wrap justify-center p-4">
|
||||||
|
{% for service in services %}
|
||||||
|
<a href="https://{{ service.domain }}/"
|
||||||
|
class="flex flex-col basis-3/12 bg-primary rounded-lg p-4 m-4 h-60 border-transparent border-2 hover:border-white">
|
||||||
|
<div class="flex flex-row flex-center basis-1/3">
|
||||||
|
<img src="{{ service.image }}" alt="{{ service.name }} logo" class="w-16">
|
||||||
|
<div class="flex flex-col justify-center ml-4">
|
||||||
|
<h3>{{ service.name }}</h3>
|
||||||
|
<p class="opacity-75">{{ service.domain }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mt-4 basis-1/3">{{ service.description }}</p>
|
||||||
|
<div class="flex flex-row items-end basis-1/3">
|
||||||
|
<button class="rounded-lg bg-background h-7 px-4 text-center"></> {{ service.language }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|