diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..517d63e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..885103d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto eol=lf +*.bat eol=crlf + +**/*.v linguist-language=V +**/*.vv linguist-language=V +**/*.vsh linguist-language=V +**/v.mod linguist-language=V \ No newline at end of file diff --git a/.gitignore b/.gitignore index 552a506..7fcb2f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ # ---> V +main +*.dylib +*.exe~ *.exe *.o *.so @@ -11,3 +14,9 @@ *.bak *.out +bin/ + +.DS_Store +.idea/ +.vscode/ +*.iml diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/app/src/assets/dev/index.html b/app/src/assets/dev/index.html new file mode 100644 index 0000000..454e480 --- /dev/null +++ b/app/src/assets/dev/index.html @@ -0,0 +1,276 @@ + + + + + + File Browser + + + +
+
+ +
+ +
+
+
+
+ 0 directories + 0 files + 0 B total +
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+ + + + diff --git a/app/src/assets/dev/ref.html b/app/src/assets/dev/ref.html new file mode 100644 index 0000000..fe4f7c4 --- /dev/null +++ b/app/src/assets/dev/ref.html @@ -0,0 +1,260 @@ + + + + + + File Browser + + + + + +
+
+ +
+ +
+
+
+
+ 0 directories + 0 files + 0 B total +
+ +
+
+
+ +
+
+
+ +
+
+
+
+ + + diff --git a/app/src/assets/style/error.css b/app/src/assets/style/error.css new file mode 100644 index 0000000..4e03a8c --- /dev/null +++ b/app/src/assets/style/error.css @@ -0,0 +1,101 @@ + +* { margin:0; padding:0; box-sizing:border-box; } + +:root[data-theme="light"] { + --bg-primary: #ffffff; + --bg-secondary: #f5f7fb; + --bg-tertiary: #eff2f5; + --text-primary: #1a202c; + --text-secondary: #6c757d; + --border-color: #d4d7de; + --accent: #0051ba; + --accent-hover: #003e8f; +} + +:root[data-theme="dark"] { + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-tertiary: #21262d; + --text-primary: #e6edf3; + --text-secondary: #8b949e; + --border-color: #30363d; + --accent: #58a6ff; + --accent-hover: #79c0ff; +} + +:root { + --bg-primary:#0d1117; + --bg-secondary:#161b22; + --bg-tertiary:#21262d; + --text-primary:#e6edf3; + --text-secondary:#8b949e; + --border-color:#30363d; + --accent:#58a6ff; + --accent-hover:#79c0ff; + + @media (prefers-color-scheme: light) { + --bg-primary: #ffffff; + --bg-secondary: #f5f7fb; + --bg-tertiary: #eff2f5; + --text-primary: #1a202c; + --text-secondary: #6c757d; + --border-color: #d4d7de; + --accent: #0051ba; + --accent-hover: #003e8f; + } +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background-color: var(--bg-primary); + color: var(--text-primary); + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + flex-direction: column; + text-align: center; + transition: background-color 0.3s, color 0.3s; +} + +h1 { + font-size: 6rem; + margin-bottom: 1rem; + color: var(--accent); +} + +p { + font-size: 1.25rem; + margin-bottom: 2rem; + color: var(--text-secondary); +} + +a.btn { + display: inline-block; + padding: 0.75rem 1.5rem; + background-color: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: 0.375rem; + text-decoration: none; + transition: all 0.2s ease; +} + +a.btn:hover { + background-color: var(--accent); + border-color: var(--accent); + color: #fff; +} + +@media (prefers-color-scheme: light) { + :root:not([data-theme]) { + --bg-primary: #ffffff; + --bg-secondary: #f5f7fb; + --bg-tertiary: #eff2f5; + --text-primary: #1a202c; + --text-secondary: #6c757d; + --border-color: #d4d7de; + --accent: #0051ba; + --accent-hover: #003e8f; + } +} diff --git a/app/src/assets/style/style.css b/app/src/assets/style/style.css new file mode 100644 index 0000000..7449460 --- /dev/null +++ b/app/src/assets/style/style.css @@ -0,0 +1,476 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* Light Mode (Cloudflare Dashboard Style) */ +:root[data-theme="light"] { + --bg-primary: #ffffff; + --bg-secondary: #f5f7fb; + --bg-tertiary: #eff2f5; + --text-primary: #1a202c; + --text-secondary: #6c757d; + --border-color: #d4d7de; + --accent: #0051ba; + --accent-hover: #003e8f; + --file-hover: #f0f2f5; +} + +/* Dark Mode (GitHub Style) */ +:root[data-theme="dark"] { + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-tertiary: #21262d; + --text-primary: #e6edf3; + --text-secondary: #8b949e; + --border-color: #30363d; + --accent: #58a6ff; + --accent-hover: #79c0ff; + --file-hover: #1c2128; +} + +/* Default to dark mode */ +:root { + --bg-primary: #0d1117; + --bg-secondary: #161b22; + --bg-tertiary: #21262d; + --text-primary: #e6edf3; + --text-secondary: #8b949e; + --border-color: #30363d; + --accent: #58a6ff; + --accent-hover: #79c0ff; + --file-hover: #1c2128; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background-color: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.header { + background-color: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + padding: 1.5rem; + position: sticky; + top: 0; + z-index: 10; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +.breadcrumbs { + text-transform: uppercase; + font-size: 10px; + letter-spacing: 1px; + color: var(--text-secondary); + margin-bottom: 0.5rem; + padding-left: 3px; +} + +.path-container { + display: flex; + align-items: center; + gap: 0; + flex-wrap: wrap; + word-break: break-all; + margin-bottom: 1rem; + font-size: 1rem; +} + +.breadcrumb-link { + color: var(--accent); + text-decoration: none; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + transition: all 0.2s ease; + cursor: pointer; +} + +.breadcrumb-link:hover { + background-color: var(--bg-tertiary); + color: var(--accent-hover); +} + +.breadcrumb-text { + color: var(--text-secondary); + padding: 0.25rem 0.5rem; +} + +.breadcrumb-sep { + color: var(--text-secondary); + margin: 0 0.25rem; +} + +.controls { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; +} + +.btn { + padding: 0.5rem 1rem; + background-color: var(--bg-tertiary); + border: 1px solid var(--border-color); + color: var(--text-primary); + border-radius: 0.375rem; + cursor: pointer; + font-size: 0.875rem; + transition: all 0.2s ease; + white-space: nowrap; +} + +.btn:hover { + background-color: var(--accent); + border-color: var(--accent); + color: #ffffff; +} + +.btn:active { + transform: scale(0.98); +} + +.content { + padding: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; + border-bottom: 1px solid var(--border-color); + background-color: var(--bg-secondary); +} + +.meta { + display: flex; + gap: 2rem; + font-size: 0.875rem; + flex-wrap: wrap; +} + +#summary { + display: flex; + gap: 2rem; + align-items: center; +} + +.meta-item { + white-space: nowrap; +} + +.meta-item b { + color: var(--text-primary); + font-weight: 600; +} + +.layout-toggle { + display: flex; + gap: 0.5rem; + border: 1px solid var(--border-color); + border-radius: 0.375rem; + padding: 0.25rem; + background-color: var(--bg-tertiary); +} + +.layout-btn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.375rem 0.75rem; + background: transparent; + border: none; + color: var(--text-secondary); + cursor: pointer; + border-radius: 0.25rem; + transition: all 0.2s ease; + font-size: 0.875rem; +} + +.layout-btn:hover { + color: var(--text-primary); + background-color: var(--bg-secondary); +} + +.layout-btn.active { + background-color: var(--accent); + color: white; +} + +.listing { + flex: 1; + padding: 1.5rem; + overflow-x: auto; +} + +/* Table Layout */ +.file-table { + width: 100%; + border-collapse: collapse; +} + +.file-table thead { + position: sticky; + top: 0; + background-color: var(--bg-secondary); + z-index: 5; +} + +.file-table th { + text-align: left; + padding: 0.75rem; + border-bottom: 1px solid var(--border-color); + color: var(--text-secondary); + font-size: 0.875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.file-table tbody tr { + border-bottom: 1px solid var(--border-color); + transition: all 0.2s ease; + cursor: pointer; +} + +.file-table tbody tr:hover { + background-color: var(--file-hover); +} + +.file-table td { + padding: 0.75rem; + color: var(--text-primary); +} + +.file-table td.icon { + width: 2.5rem; + font-size: 1.25rem; + text-align: center; +} + +.file-table td.name { + font-weight: 500; +} + +.file-table tr.directory td.name { + color: var(--accent); + font-weight: 600; +} + +.file-table td.size, +.file-table td.date { + color: var(--text-secondary); + font-size: 0.875rem; + white-space: nowrap; +} + +/* Grid Layout */ +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 1rem; +} + +.grid-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: 1rem; + background-color: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 0.5rem; + cursor: pointer; + transition: all 0.2s ease; + text-align: center; +} + +.grid-item:hover { + background-color: var(--file-hover); + border-color: var(--accent); + transform: translateY(-2px); +} + +.grid-icon { + font-size: 2.5rem; + margin-bottom: 0.75rem; +} + +.grid-name { + font-weight: 500; + color: var(--text-primary); + word-break: break-word; + overflow-wrap: break-word; + font-size: 0.875rem; + margin-bottom: 0.5rem; +} + +.grid-item:has(.grid-name) .grid-name { + color: var(--accent); + font-weight: 600; +} + +.grid-size { + font-size: 0.75rem; + color: var(--text-secondary); + margin-top: auto; +} + +.empty-state { + text-align: center; + padding: 3rem 1rem; + color: var(--text-secondary); +} + +.empty-state-icon { + font-size: 3rem; + margin-bottom: 1rem; + opacity: 0.5; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .header { + padding: 1rem; + } + + .listing { + padding: 1rem; + } + + .content { + padding: 1rem; + flex-direction: column; + align-items: flex-start; + } + + .file-table td.date { + display: none; + } + + .file-table th:last-child { + display: none; + } + + .grid { + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); + gap: 0.75rem; + } + + .grid-item { + padding: 0.75rem; + } + + .path-container { + font-size: 0.9rem; + margin-bottom: 0.75rem; + } + + .meta { + gap: 1rem; + font-size: 0.8rem; + } +} + +@media (max-width: 480px) { + .container { + min-height: auto; + } + + .header { + padding: 0.75rem; + } + + .listing { + padding: 0.75rem; + } + + .content { + padding: 0.75rem; + gap: 0.5rem; + } + + .path-container { + font-size: 0.8rem; + margin-bottom: 0.5rem; + } + + .breadcrumb-link, + .breadcrumb-text, + .breadcrumb-sep { + padding: 0.125rem 0.25rem; + } + + .controls { + width: 100%; + gap: 0.5rem; + } + + .btn { + flex: 1; + padding: 0.5rem; + font-size: 0.75rem; + } + + .meta { + gap: 1rem; + font-size: 0.75rem; + flex-direction: column; + } + + .file-table th, + .file-table td { + padding: 0.5rem 0.25rem; + } + + .file-table td.size { + display: none; + } + + .grid { + grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); + gap: 0.5rem; + } + + .grid-item { + padding: 0.5rem; + } + + .grid-icon { + font-size: 2rem; + margin-bottom: 0.5rem; + } + + .grid-name { + font-size: 0.7rem; + } +} + +/* Scrollbar Styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} diff --git a/app/src/assets/style/style.css.zst b/app/src/assets/style/style.css.zst new file mode 100644 index 0000000..3fe1c4e Binary files /dev/null and b/app/src/assets/style/style.css.zst differ diff --git a/app/src/main.v b/app/src/main.v new file mode 100644 index 0000000..878cd33 --- /dev/null +++ b/app/src/main.v @@ -0,0 +1,126 @@ +module main + +import veb +import os +import log + +struct Config { +pub mut: + root string + port int +} + +struct Embedded { +pub mut: + style_css string + error_css string +} + +pub struct User { +pub mut: + name string + id int +} + +pub struct Context { + veb.Context +pub mut: + embed Embedded + user User + session_id string +} + +pub struct App { + veb.Controller + veb.StaticHandler + veb.Middleware[Context] +pub mut: + embed Embedded +} + +pub struct Auth {} + +// endpoints + +@['/:path...'] +pub fn (app &App) root(mut ctx Context, path string) veb.Result { + style := app.embed.style_css + return ctx.html($tmpl('template/dashboard.html')) +} + +@['/error'] +pub fn (app &App) error(mut ctx Context) veb.Result { + return ctx.html(ctx.error_page(500, 'Internal Server Error', 'Oops! Seems like something went wrong here.')) +} + +// auth endpoints + +@[get; post] +pub fn (auth &Auth) login(mut ctx Context) veb.Result { + return ctx.text('') +} + +@[get; post] +pub fn (auth &Auth) logout(mut ctx Context) veb.Result { + return ctx.text('') +} + +@[get; post; put] +pub fn (auth &Auth) register(mut ctx Context) veb.Result { + return ctx.text('') +} + +// utility + +fn (mut ctx Context) error_page(code int, short string, long string) string { + style := ctx.embed.error_css + return $tmpl('template/error.html') +} + +pub fn (mut ctx Context) not_found() veb.Result { + ctx.res.set_status(.not_found) + return ctx.html(ctx.error_page(404, 'Not found', 'Oops! The page you are looking for does not exist.')) +} + +// -- + +fn populate() &Config { + mut cfg := &Config{ + root: $d('root', '.') + port: $d('port', 6767) + } + + if os.getenv('CDN_ROOT') != '' { + cfg.root = os.getenv('CDN_ROOT').str() + } + if os.getenv('CDN_PORT') != '' { + cfg.port = os.getenv('CDN_PORT').int() + } + + return cfg +} + +fn main() { + cfg := populate() + + mut app := &App{} + mut auth := &Auth{} + app.embed = &Embedded{ + style_css: $embed_file('assets/style/style.css', .zlib).to_string() + error_css: $embed_file('assets/style/error.css', .zlib).to_string() + } + + app.register_controller[Auth, Context]('/auth', mut auth)! + + app.enable_static_compression = true + app.use(veb.encode_auto[Context]()) + + app.use(veb.MiddlewareOptions[Context]{ + handler: fn [app] (mut ctx Context) bool { + ctx.embed = &app.embed + return true + } + }) + + veb.run[App, Context](mut app, cfg.port) +} diff --git a/app/src/template/dashboard.html b/app/src/template/dashboard.html new file mode 100644 index 0000000..80704af --- /dev/null +++ b/app/src/template/dashboard.html @@ -0,0 +1,36 @@ + + + + + + File Browser + + + + +
+
+ +
+
+
+
+
+ 0 directories + 0 files + 0 B total +
+ +
+
+
+ +
+
+
+
+
+
+
+ + diff --git a/app/src/template/error.html b/app/src/template/error.html new file mode 100644 index 0000000..df0b2fa --- /dev/null +++ b/app/src/template/error.html @@ -0,0 +1,18 @@ + + + + + + @code - @short + + + +

@code

+

@long

+Go Home + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f9059ef --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +services: + app: + build: + context: ./app + dockerfile: Dockerfile + container_name: cdn_web + depends_on: + - db + ports: + - "8080:8080" + environment: + # env shee + DB_HOST: db + DB_USER: appuser + DB_PASS: s3cret + DB_NAME: appdb + restart: unless-stopped + + db: + image: postgres:16 + container_name: cdn_db + environment: + POSTGRES_USER: appuser + POSTGRES_PASSWORD: s3cret + POSTGRES_DB: appdb + volumes: + - dbdata:/var/lib/postgresql/data + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql + restart: unless-stopped + +volumes: + dbdata: diff --git a/v.mod b/v.mod new file mode 100644 index 0000000..38fbabf --- /dev/null +++ b/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'CDN' + description: '' + version: '0.0.1' + license: 'MIT' + dependencies: [] +}