Add web frontend
This commit is contained in:
parent
fe8c807b97
commit
081c774e75
21 changed files with 1186 additions and 1 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -1 +1,9 @@
|
|||
.env
|
||||
.DS_STORE
|
||||
|
||||
.env
|
||||
**/*_templ.go
|
||||
**/*_templ.txt
|
||||
|
||||
**/configmap.yaml
|
||||
|
||||
tailwindcss
|
||||
BIN
bin/web
Executable file
BIN
bin/web
Executable file
Binary file not shown.
33
build/docker/web/Dockerfile
Normal file
33
build/docker/web/Dockerfile
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk --no-cache add curl nodejs npm
|
||||
|
||||
RUN npm install -D tailwindcss
|
||||
|
||||
RUN go install github.com/a-h/templ/cmd/templ@latest
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN templ generate
|
||||
|
||||
|
||||
RUN npx tailwindcss -i ./internal/web/assets/css/input.css -o ./internal/web/assets/css/output.css
|
||||
|
||||
RUN go build -o web ./cmd/web
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/web /app/web
|
||||
|
||||
ENV GIN_MODE=release
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/app/web"]
|
||||
|
|
@ -9,3 +9,4 @@ data:
|
|||
DB_HOST: "psql-service"
|
||||
DB_PORT: "5432"
|
||||
KEYGEN_HOST: "keygen-service"
|
||||
API_URL: "http://api-server-service"
|
||||
|
|
|
|||
38
build/kubernetes/web-deployment.yaml
Normal file
38
build/kubernetes/web-deployment.yaml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: fastbin-web
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: fastbin-web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: fastbin-web
|
||||
spec:
|
||||
containers:
|
||||
- name: api-server
|
||||
image: registry.lab.divyam.dev/fastbin-web:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: API_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: fastbin-config
|
||||
key: API_URL
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: fastbin-web-service
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: fastbin-web
|
||||
type: LoadBalancer
|
||||
22
cmd/web/main.go
Normal file
22
cmd/web/main.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fastbin/internal/pkg/env"
|
||||
webserver "fastbin/internal/web"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
port, err := strconv.Atoi(env.GetEnv("WEB_INTERNAL_PORT", "8080"))
|
||||
if err != nil {
|
||||
log.Fatalf("error listening port: %v, err: %v", port, err)
|
||||
}
|
||||
server := webserver.NewServer(port)
|
||||
err = server.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
panic(fmt.Sprintf("http server error: %s", err))
|
||||
}
|
||||
}
|
||||
1
go.mod
1
go.mod
|
|
@ -3,6 +3,7 @@ module fastbin
|
|||
go 1.23.2
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.793
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
google.golang.org/grpc v1.67.1
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -1,3 +1,5 @@
|
|||
github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
|
||||
github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
|
|
|
|||
10
internal/web/assets/css/input.css
Normal file
10
internal/web/assets/css/input.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--primary-color: 29 29 29;
|
||||
--border-color: 69 69 69;
|
||||
}
|
||||
}
|
||||
740
internal/web/assets/css/output.css
Normal file
740
internal/web/assets/css/output.css
Normal file
|
|
@ -0,0 +1,740 @@
|
|||
*, ::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: ;
|
||||
--tw-contain-size: ;
|
||||
--tw-contain-layout: ;
|
||||
--tw-contain-paint: ;
|
||||
--tw-contain-style: ;
|
||||
}
|
||||
|
||||
::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: ;
|
||||
--tw-contain-size: ;
|
||||
--tw-contain-layout: ;
|
||||
--tw-contain-paint: ;
|
||||
--tw-contain-style: ;
|
||||
}
|
||||
|
||||
/*
|
||||
! tailwindcss v3.4.14 | 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: #e5e7eb;
|
||||
/* 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 */
|
||||
letter-spacing: 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,
|
||||
input:where([type='button']),
|
||||
input:where([type='reset']),
|
||||
input:where([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]:where(:not([hidden="until-found"])) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary-color: 29 29 29;
|
||||
--border-color: 69 69 69;
|
||||
}
|
||||
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.h-\[70px\] {
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.h-dvh {
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.w-\[70px\] {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.w-dvw {
|
||||
width: 100dvw;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select-none {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.resize-none {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.items-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.self-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.border-b-2 {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
|
||||
.border-none {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.border-b-border {
|
||||
--tw-border-opacity: 1;
|
||||
border-bottom-color: rgb(var(--border-color) / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(var(--primary-color) / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.px-2\.5 {
|
||||
padding-left: 0.625rem;
|
||||
padding-right: 0.625rem;
|
||||
}
|
||||
|
||||
.px-\[20px\] {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.py-\[10px\] {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.text-4xl {
|
||||
font-size: 2.25rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
|
||||
.text-8xl {
|
||||
font-size: 6rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.outline-none {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.filter {
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.hover\:bg-border\/50:hover {
|
||||
background-color: rgb(var(--border-color) / 0.5);
|
||||
}
|
||||
1
internal/web/assets/js/htmx.min.js
vendored
Normal file
1
internal/web/assets/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
15
internal/web/efs.go
Normal file
15
internal/web/efs.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
//go:embed assets
|
||||
var f embed.FS
|
||||
|
||||
var Files fs.FS
|
||||
|
||||
func init() {
|
||||
Files, _ = fs.Sub(f, "assets")
|
||||
}
|
||||
61
internal/web/renderer.go
Normal file
61
internal/web/renderer.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Credits: https://github.com/a-h/templ/blob/main/examples/integration-gin/gintemplrenderer/renderer.go
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/render"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
var Default = &HTMLTemplRenderer{}
|
||||
|
||||
type HTMLTemplRenderer struct {
|
||||
FallbackHtmlRenderer render.HTMLRender
|
||||
}
|
||||
|
||||
func (r *HTMLTemplRenderer) Instance(s string, d any) render.Render {
|
||||
templData, ok := d.(templ.Component)
|
||||
if !ok {
|
||||
if r.FallbackHtmlRenderer != nil {
|
||||
return r.FallbackHtmlRenderer.Instance(s, d)
|
||||
}
|
||||
}
|
||||
return &Renderer{
|
||||
Ctx: context.Background(),
|
||||
Status: -1,
|
||||
Component: templData,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGinTemplRenderer(ctx context.Context, status int, component templ.Component) *Renderer {
|
||||
return &Renderer{
|
||||
Ctx: ctx,
|
||||
Status: status,
|
||||
Component: component,
|
||||
}
|
||||
}
|
||||
|
||||
type Renderer struct {
|
||||
Ctx context.Context
|
||||
Status int
|
||||
Component templ.Component
|
||||
}
|
||||
|
||||
func (t Renderer) Render(w http.ResponseWriter) error {
|
||||
t.WriteContentType(w)
|
||||
if t.Status != -1 {
|
||||
w.WriteHeader(t.Status)
|
||||
}
|
||||
if t.Component != nil {
|
||||
return t.Component.Render(t.Ctx, w)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Renderer) WriteContentType(w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
}
|
||||
110
internal/web/server.go
Normal file
110
internal/web/server.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"fastbin/internal/pkg/env"
|
||||
"fastbin/internal/web/views"
|
||||
)
|
||||
|
||||
type api_write_response struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type api_read_response struct {
|
||||
Error string `json:"error"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func NewServer(port int) *http.Server {
|
||||
API_URL := env.GetEnv("API_URL", "localhost:8080")
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
engineHTMLRenderer := r.HTMLRender
|
||||
r.HTMLRender = &HTMLTemplRenderer{FallbackHtmlRenderer: engineHTMLRenderer}
|
||||
|
||||
r.StaticFS("/assets", http.FS(Files))
|
||||
|
||||
r.GET("/", func(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "", views.Write())
|
||||
})
|
||||
|
||||
r.POST("/", func(ctx *gin.Context) {
|
||||
|
||||
text := ctx.Request.FormValue("text")
|
||||
postBody, _ := json.Marshal(map[string]string{
|
||||
"text": text,
|
||||
})
|
||||
|
||||
url := API_URL + "/write"
|
||||
response, err := http.Post(url, "application/json", bytes.NewBuffer(postBody))
|
||||
if err != nil {
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||
}
|
||||
|
||||
var api_res api_write_response
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
err = decoder.Decode(&api_res)
|
||||
if err != nil {
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||
}
|
||||
|
||||
if response.StatusCode == http.StatusOK {
|
||||
ctx.Writer.Header().Add("Hx-Redirect", api_res.Key)
|
||||
} else {
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
r.GET("/404", func(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "", views.NotFound())
|
||||
})
|
||||
r.GET("/500", func(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "", views.ServerError())
|
||||
})
|
||||
|
||||
r.GET("/:key", func(ctx *gin.Context) {
|
||||
url := API_URL + "/read/" + ctx.Param("key")
|
||||
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||
}
|
||||
|
||||
var api_res api_read_response
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
|
||||
err = decoder.Decode(&api_res)
|
||||
if err != nil {
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||
}
|
||||
|
||||
if response.StatusCode == http.StatusOK {
|
||||
ctx.HTML(http.StatusOK, "", views.Read(api_res.Text))
|
||||
} else if response.StatusCode == http.StatusNotFound {
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, "/404")
|
||||
} else {
|
||||
ctx.Redirect(http.StatusTemporaryRedirect, "/500")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
Handler: r,
|
||||
IdleTimeout: time.Minute,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
17
internal/web/views/404.templ
Normal file
17
internal/web/views/404.templ
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package views
|
||||
|
||||
templ NotFound() {
|
||||
@Base("fastbin") {
|
||||
@Header() {
|
||||
<a href="/" class="inline-block text-center align-middle">
|
||||
@Button() {
|
||||
<span class="material-symbols-outlined">note_add</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<div class="flex flex-1 w-full text-white text-xl">
|
||||
<div class="h-full px-2.5 select-none">></div>
|
||||
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>404: Page Not Found. </div></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
17
internal/web/views/500.templ
Normal file
17
internal/web/views/500.templ
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package views
|
||||
|
||||
templ ServerError() {
|
||||
@Base("fastbin") {
|
||||
@Header() {
|
||||
<a href="/" class="inline-block text-center align-middle">
|
||||
@Button() {
|
||||
<span class="material-symbols-outlined">note_add</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<div class="flex flex-1 w-full text-white text-xl">
|
||||
<div class="h-full px-2.5 select-none">></div>
|
||||
<div class="flex w-full h-full bg-transparent resize-none text-white justify-center items-center text-8xl"><div>500: Internal Server Error</div></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
34
internal/web/views/base.templ
Normal file
34
internal/web/views/base.templ
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package views
|
||||
|
||||
templ Base(title string) {
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined" />
|
||||
<script src="assets/js/htmx.min.js"></script>
|
||||
<link rel="stylesheet" href="assets/css/output.css"/>
|
||||
<title>{ title }</title>
|
||||
</head>
|
||||
<body>
|
||||
<main class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
|
||||
{ children... }
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ Header() {
|
||||
<div class="w-full flex justify-between items-center text-white border-b-2 border-b-border">
|
||||
<div class="px-[20px] py-[10px] text-4xl italic select-none pointer-events-none">fastbin</div>
|
||||
<div class="flex self-stretch items-stretch">
|
||||
{ children... }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ Button() {
|
||||
<div class="w-[70px] h-[70px] flex justify-center align-middle items-center text-center cursor-pointer select-none hover:bg-border/50">
|
||||
{ children... }
|
||||
</div>
|
||||
}
|
||||
17
internal/web/views/read.templ
Normal file
17
internal/web/views/read.templ
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package views
|
||||
|
||||
templ Read(text string) {
|
||||
@Base("fastbin") {
|
||||
@Header() {
|
||||
<a href="/" class="inline-block text-center align-middle">
|
||||
@Button() {
|
||||
<span class="material-symbols-outlined">note_add</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<div class="flex flex-1 w-full text-white text-xl">
|
||||
<div class="h-full px-2.5 select-none">></div>
|
||||
<textarea required readonly name="text" id="input" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl">{ text } </textarea>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
41
internal/web/views/write.templ
Normal file
41
internal/web/views/write.templ
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package views
|
||||
|
||||
var onceHandle = templ.NewOnceHandle()
|
||||
|
||||
templ Write() {
|
||||
@Base("fastbin") {
|
||||
<form id="input_form" hx-post="/" class="w-dvw h-dvh bg-primary flex flex-col items-start justify-center">
|
||||
@Header() {
|
||||
<a href="/" class="inline-block text-center align-middle">
|
||||
@Button() {
|
||||
<span class="material-symbols-outlined">note_add</span>
|
||||
}
|
||||
</a>
|
||||
<a>
|
||||
<button type="submit">
|
||||
@Button() {
|
||||
<span class="material-symbols-outlined">save</span>
|
||||
}
|
||||
</button>
|
||||
</a>
|
||||
}
|
||||
<script type="text/javascript">
|
||||
function keyDownHandler(e) {
|
||||
if (e.key === "Tab") {
|
||||
e.preventDefault()
|
||||
e.currentTarget.setRangeText(
|
||||
'\t',
|
||||
e.currentTarget.selectionStart,
|
||||
e.currentTarget.selectionStart,
|
||||
'end'
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="flex flex-1 w-full text-white text-xl">
|
||||
<div class="h-full px-2.5 select-none">></div>
|
||||
<textarea required name="text" id="input" onkeydown="keyDownHandler(event)" autofocus wrap="off" spellcheck="false" class="flex-1 w-full h-full bg-transparent resize-none outline-none border-none text-white text-xl"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
# API_INTERNAL_PORT=8080 # When using without docker
|
||||
# KEYGEN_INTERNAL_PORT=8081 # When using without docker
|
||||
|
||||
API_URL=http://api-server
|
||||
|
||||
KEYGEN_PORT=8080
|
||||
KEYGEN_HOST=localhost
|
||||
|
||||
|
|
|
|||
15
tailwind.config.js
Normal file
15
tailwind.config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./internal/**/*.{go,js,templ,html}"
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
"primary": "rgb(var(--primary-color) / <alpha-value>)",
|
||||
"border": "rgb(var(--border-color) / <alpha-value>)"
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
Loading…
Reference in a new issue