diff --git a/.dockerignore b/.dockerignore index 54a76d2..d470bcd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,3 @@ .idea -node_modules +.ignore assets diff --git a/Dockerfile b/Dockerfile index b17f89e..9654932 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ -FROM node:alpine as base -WORKDIR /app +FROM oven/bun:alpine as base +LABEL Maintainer="Shahrad Elahi " +WORKDIR /usr/src/app ENV TZ=UTC RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone @@ -7,51 +8,58 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone COPY --from=chriswayg/tor-alpine:latest /usr/local/bin/obfs4proxy /usr/local/bin/obfs4proxy COPY --from=chriswayg/tor-alpine:latest /usr/local/bin/meek-server /usr/local/bin/meek-server +# Set the mirror list +RUN echo "https://uk.alpinelinux.org/alpine/latest-stable/main" > /etc/apk/repositories && \ + echo "https://mirror.bardia.tech/alpine/latest-stable/main" >> /etc/apk/repositories && \ + echo "https://uk.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ + echo "https://mirror.bardia.tech/alpine/latest-stable/community" >> /etc/apk/repositories + +# Update and upgrade packages +RUN apk update && apk upgrade + +# Install required packages RUN apk add -U --no-cache \ - iproute2 iptables net-tools \ - screen vim curl bash \ - wireguard-tools \ - openssl \ - dumb-init \ - tor \ - redis + iproute2 iptables net-tools \ + screen vim curl bash \ + wireguard-tools \ + openssl \ + dumb-init \ + tor \ + redis + +# Clear cache +RUN rm -rf /var/cache/apk/* -FROM node:alpine as builder -WORKDIR /app +FROM base AS deps -COPY /src/package.json /src/package-lock.json ./ -RUN npm install +RUN mkdir -p /temp/dev +COPY web/package.json web/bun.lockb /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile +RUN mkdir -p /temp/prod +COPY web/package.json web/bun.lockb /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + + +FROM base AS build +COPY --from=deps /temp/dev/node_modules node_modules +COPY web . + +# build ENV NODE_ENV=production -COPY /src/ . - -RUN npm run build +RUN bun run build -FROM base -WORKDIR /app +FROM base AS release + +COPY --from=deps /temp/prod/node_modules node_modules +COPY --from=build /usr/src/app/build . +COPY --from=build /usr/src/app/package.json . ENV NODE_ENV=production -LABEL Maintainer="Shahrad Elahi " - -COPY /config/torrc /etc/tor/torrc - -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/next.config.js ./next.config.js -COPY --from=builder /app/public ./public - -COPY /src/package.json /src/package-lock.json ./ -RUN npm install --omit dev - +# run the app +USER bun EXPOSE 3000/tcp - -HEALTHCHECK --interval=60s --timeout=10s --start-period=5s --retries=3 \ - CMD curl -f http://127.0.0.1:3000/api/healthcheck || exit 1 - -COPY docker-entrypoint.sh /usr/bin/entrypoint -RUN chmod +x /usr/bin/entrypoint -ENTRYPOINT ["/usr/bin/entrypoint"] - -CMD ["npm", "run", "start"] +CMD [ "bun", "start" ] diff --git a/Dockerfile-Dev b/Dockerfile-Dev index 90d4daf..52a54f8 100644 --- a/Dockerfile-Dev +++ b/Dockerfile-Dev @@ -8,45 +8,41 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone COPY --from=chriswayg/tor-alpine:latest /usr/local/bin/obfs4proxy /usr/local/bin/obfs4proxy COPY --from=chriswayg/tor-alpine:latest /usr/local/bin/meek-server /usr/local/bin/meek-server +# Set the mirror list +RUN echo "https://uk.alpinelinux.org/alpine/latest-stable/main" > /etc/apk/repositories && \ + echo "https://mirror.bardia.tech/alpine/latest-stable/main" >> /etc/apk/repositories && \ + echo "https://uk.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\ + echo "https://mirror.bardia.tech/alpine/latest-stable/community" >> /etc/apk/repositories + +# Update and upgrade packages +RUN apk update && apk upgrade + +# Install required packages RUN apk add -U --no-cache \ - iproute2 iptables net-tools \ - screen vim curl bash \ - wireguard-tools \ - openssl \ - dumb-init \ - tor \ - redis + iproute2 iptables net-tools \ + screen vim curl bash \ + wireguard-tools \ + openssl \ + dumb-init \ + tor \ + redis + +# Clear cache +RUN rm -rf /var/cache/apk/* FROM base AS deps RUN mkdir -p /temp/dev -COPY web/package.json web/bun.lockb /temp/dev/ +COPY web/package.json web/bun.lockb web/node_modules* /temp/dev/ RUN cd /temp/dev && bun install --frozen-lockfile -RUN mkdir -p /temp/prod -COPY web/package.json web/bun.lockb /temp/prod/ -RUN cd /temp/prod && bun install --frozen-lockfile --production +FROM base AS runner -FROM install AS build COPY --from=deps /temp/dev/node_modules node_modules COPY web . -# build -ENV NODE_ENV=production -RUN bun run build - - -FROM base AS release - -COPY --from=deps /temp/prod/node_modules node_modules -COPY --from=build /usr/src/app/build . -COPY --from=build /usr/src/app/package.json . - -ENV NODE_ENV=production - -# run the app -USER bun -EXPOSE 3000/tcp -CMD [ "bun", "start" ] \ No newline at end of file +# run the appc +EXPOSE 5173/tcp +CMD [ "bun", "dev", "--host" ] \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 3df93d3..d9842a2 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -3,7 +3,9 @@ services: wireadmin: image: wireadmin volumes: - - ./src/:/app/ + - ./web/:/usr/src/app/ + ports: + - "5173:5173" environment: - UI_PASSWORD=password - WG_HOST=192.168.1.102 diff --git a/package.json b/package.json index 2edc0fb..eee8d4a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "scripts": { "dev:image": "DOCKER_BUILDKIT=1 docker build --tag wireadmin -f Dockerfile-Dev .", - "dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up; docker rm -f wireadmin" + "dev": "docker compose rm -fsv && docker compose -f docker-compose.yml -f docker-compose.dev.yml up && docker compose rm -fsv" }, "keywords": [], "author": "Shahrad Elahi ", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..2b9f188 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/web/.prettierrc b/web/.prettierrc index e3ef508..61b4a62 100644 --- a/web/.prettierrc +++ b/web/.prettierrc @@ -1,5 +1,5 @@ { - "useTabs": false, + "useTabs": true, "singleQuote": true, "trailingComma": "none", "printWidth": 100, @@ -9,7 +9,6 @@ "pluginSearchDirs": [ "." ], - "tabWidth": 2, "overrides": [ { "files": "*.svelte", diff --git a/web/bun.lockb b/web/bun.lockb index cc5e752..05028ed 100644 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/components.json b/web/components.json new file mode 100644 index 0000000..83e6d22 --- /dev/null +++ b/web/components.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "style": "default", + "tailwind": { + "config": "tailwind.config.js", + "css": "src/app.postcss", + "baseColor": "gray" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils" + } +} \ No newline at end of file diff --git a/web/package.json b/web/package.json index 9dbd64c..ad5a4ee 100644 --- a/web/package.json +++ b/web/package.json @@ -8,20 +8,32 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test": "vitest", "lint": "prettier --plugin-search-dir . --check .", - "format": "prettier --plugin-search-dir . --write .", - "start": "bun ./build/index.js" + "format": "prettier --plugin-search-dir . --write ." }, "devDependencies": { "@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/kit": "^1.20.4", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.24", + "postcss-load-config": "^4.0.1", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", "svelte": "^4.0.5", "svelte-check": "^3.4.3", + "tailwindcss": "^3.3.2", "tslib": "^2.4.1", "typescript": "^5.0.0", - "vite": "^4.4.2" + "vite": "^4.4.2", + "vitest": "^0.34.0" }, - "type": "module" -} + "type": "module", + "dependencies": { + "bits-ui": "^0.9.0", + "clsx": "^2.0.0", + "lucide-svelte": "^0.292.0", + "tailwind-merge": "^2.0.0", + "tailwind-variants": "^0.1.18" + } +} \ No newline at end of file diff --git a/web/postcss.config.cjs b/web/postcss.config.cjs new file mode 100644 index 0000000..42c343c --- /dev/null +++ b/web/postcss.config.cjs @@ -0,0 +1,13 @@ +const tailwindcss = require('tailwindcss'); +const autoprefixer = require('autoprefixer'); + +const config = { + plugins: [ + //Some plugins, like tailwindcss/nesting, need to run before Tailwind, + tailwindcss(), + //But others, like autoprefixer, need to run after, + autoprefixer + ] +}; + +module.exports = config; \ No newline at end of file diff --git a/web/src/app.postcss b/web/src/app.postcss new file mode 100644 index 0000000..f8b695a --- /dev/null +++ b/web/src/app.postcss @@ -0,0 +1,78 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 224 71.4% 4.1%; + + --muted: 220 14.3% 95.9%; + --muted-foreground: 220 8.9% 46.1%; + + --popover: 0 0% 100%; + --popover-foreground: 224 71.4% 4.1%; + + --card: 0 0% 100%; + --card-foreground: 224 71.4% 4.1%; + + --border: 220 13% 91%; + --input: 220 13% 91%; + + --primary: 220.9 39.3% 11%; + --primary-foreground: 210 20% 98%; + + --secondary: 220 14.3% 95.9%; + --secondary-foreground: 220.9 39.3% 11%; + + --accent: 220 14.3% 95.9%; + --accent-foreground: 220.9 39.3% 11%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 20% 98%; + + --ring: 224 71.4% 4.1%; + + --radius: 0.5rem; + } + + .dark { + --background: 224 71.4% 4.1%; + --foreground: 210 20% 98%; + + --muted: 215 27.9% 16.9%; + --muted-foreground: 217.9 10.6% 64.9%; + + --popover: 224 71.4% 4.1%; + --popover-foreground: 210 20% 98%; + + --card: 224 71.4% 4.1%; + --card-foreground: 210 20% 98%; + + --border: 215 27.9% 16.9%; + --input: 215 27.9% 16.9%; + + --primary: 210 20% 98%; + --primary-foreground: 220.9 39.3% 11%; + + --secondary: 215 27.9% 16.9%; + --secondary-foreground: 210 20% 98%; + + --accent: 215 27.9% 16.9%; + --accent-foreground: 210 20% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 20% 98%; + + --ring: 216 12.2% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/web/src/index.test.ts b/web/src/index.test.ts new file mode 100644 index 0000000..aa93426 --- /dev/null +++ b/web/src/index.test.ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from 'vitest'; + +describe('sum test', () => { + it('adds 1 + 2 to equal 3', () => { + expect(1 + 2).toBe(3); + }); +}); diff --git a/web/src/lib/components/ui/button/button.svelte b/web/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..c08852d --- /dev/null +++ b/web/src/lib/components/ui/button/button.svelte @@ -0,0 +1,24 @@ + + + + + diff --git a/web/src/lib/components/ui/button/index.ts b/web/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..8363f96 --- /dev/null +++ b/web/src/lib/components/ui/button/index.ts @@ -0,0 +1,51 @@ +import Root from './button.svelte'; +import { tv, type VariantProps } from 'tailwind-variants'; +import type { Button as ButtonPrimitive } from 'bits-ui'; + +const buttonVariants = tv({ + base: 'inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: + 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: + 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: + 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline' + }, + size: { + default: 'h-10 px-4 py-2', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8', + icon: 'h-10 w-10' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } +}); + +type Variant = VariantProps['variant']; +type Size = VariantProps['size']; + +type Props = ButtonPrimitive.Props & { + variant?: Variant; + size?: Size; +}; + +type Events = ButtonPrimitive.Events; + +export { + Root, + type Props, + type Events, + // + Root as Button, + type Props as ButtonProps, + type Events as ButtonEvents, + buttonVariants +}; diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts new file mode 100644 index 0000000..9829373 --- /dev/null +++ b/web/src/lib/utils.ts @@ -0,0 +1,62 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; +import { cubicOut } from 'svelte/easing'; +import type { TransitionConfig } from 'svelte/transition'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +type FlyAndScaleParams = { + y?: number; + x?: number; + start?: number; + duration?: number; +}; + +export const flyAndScale = ( + node: Element, + params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } +): TransitionConfig => { + const style = getComputedStyle(node); + const transform = style.transform === 'none' ? '' : style.transform; + + const scaleConversion = ( + valueA: number, + scaleA: [ number, number ], + scaleB: [ number, number ] + ) => { + const [ minA, maxA ] = scaleA; + const [ minB, maxB ] = scaleB; + + const percentage = (valueA - minA) / (maxA - minA); + const valueB = percentage * (maxB - minB) + minB; + + return valueB; + }; + + const styleToString = ( + style: Record + ): string => { + return Object.keys(style).reduce((str, key) => { + if (style[key] === undefined) return str; + return str + `${key}:${style[key]};`; + }, ''); + }; + + return { + duration: params.duration ?? 200, + delay: 0, + css: (t) => { + const y = scaleConversion(t, [ 0, 1 ], [ params.y ?? 5, 0 ]); + const x = scaleConversion(t, [ 0, 1 ], [ params.x ?? 0, 0 ]); + const scale = scaleConversion(t, [ 0, 1 ], [ params.start ?? 0.95, 1 ]); + + return styleToString({ + transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, + opacity: t + }); + }, + easing: cubicOut + }; +}; \ No newline at end of file diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte new file mode 100644 index 0000000..bfbbe64 --- /dev/null +++ b/web/src/routes/+layout.svelte @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 5982b0a..954fd17 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,2 +1,8 @@ + +

Welcome to SvelteKit

Visit kit.svelte.dev to read the documentation

+ + \ No newline at end of file diff --git a/web/svelte.config.js b/web/svelte.config.js index ecaf275..577359f 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -3,16 +3,19 @@ import { vitePreprocess } from '@sveltejs/kit/vite'; /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: vitePreprocess(), + // Consult https://kit.svelte.dev/docs/integrations#preprocessors + // for more information about preprocessors + preprocess: [ vitePreprocess({}) ], - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() - } + kit: { + // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. + // If your environment is not supported or you settled on a specific environment, switch out the adapter. + // See https://kit.svelte.dev/docs/adapters for more information about adapters. + adapter: adapter(), + alias: { + $lib: './src/lib' + } + } }; export default config; diff --git a/web/tailwind.config.js b/web/tailwind.config.js new file mode 100644 index 0000000..f15fd8d --- /dev/null +++ b/web/tailwind.config.js @@ -0,0 +1,64 @@ +import { fontFamily } from 'tailwindcss/defaultTheme'; + +/** @type {import('tailwindcss').Config} */ +const config = { + darkMode: [ 'class' ], + content: [ './src/**/*.{html,js,svelte,ts}' ], + safelist: [ 'dark' ], + theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px' + } + }, + extend: { + colors: { + border: 'hsl(var(--border) / )', + input: 'hsl(var(--input) / )', + ring: 'hsl(var(--ring) / )', + background: 'hsl(var(--background) / )', + foreground: 'hsl(var(--foreground) / )', + primary: { + DEFAULT: 'hsl(var(--primary) / )', + foreground: 'hsl(var(--primary-foreground) / )' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary) / )', + foreground: 'hsl(var(--secondary-foreground) / )' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive) / )', + foreground: 'hsl(var(--destructive-foreground) / )' + }, + muted: { + DEFAULT: 'hsl(var(--muted) / )', + foreground: 'hsl(var(--muted-foreground) / )' + }, + accent: { + DEFAULT: 'hsl(var(--accent) / )', + foreground: 'hsl(var(--accent-foreground) / )' + }, + popover: { + DEFAULT: 'hsl(var(--popover) / )', + foreground: 'hsl(var(--popover-foreground) / )' + }, + card: { + DEFAULT: 'hsl(var(--card) / )', + foreground: 'hsl(var(--card-foreground) / )' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + fontFamily: { + sans: [ ...fontFamily.sans ] + } + } + } +}; + +export default config; diff --git a/web/vite.config.ts b/web/vite.config.ts index 0842f4d..d9844e7 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,6 +1,9 @@ import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; +import { defineConfig } from 'vitest/config'; export default defineConfig({ - plugins: [ sveltekit() ] + plugins: [ sveltekit() ], + test: { + include: [ 'src/**/*.{test,spec}.{js,ts}' ] + } });