feat: dark mode (#10)

This commit is contained in:
Shahrad Elahi 2024-04-27 02:53:33 +03:30 committed by GitHub
parent 0fabc4a2de
commit 66a1fe2ece
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 426 additions and 363 deletions

View File

@ -0,0 +1,5 @@
---
"wireadmin": patch
---
feat: dark mode

View File

@ -1,4 +1,7 @@
# WireGuard GUI (Easy Admin UI)
# WireGuard (Easy Admin UI)
[![CI](https://github.com/shahradelahi/wireadmin/actions/workflows/ci.yml/badge.svg)](https://github.com/shahradelahi/wireadmin/actions/workflows/ci.yml)
[![GPL-3.0 Licensed](https://img.shields.io/badge/License-GPL3.0-blue.svg?style=flat)](https://opensource.org/licenses/GPL-3.0)
![Screenshot](assets/screenshot-1.png)
@ -8,11 +11,14 @@
## Features
- Easy-to-use web-based admin UI
- Simple and friendly UI
- Support for multiple users and servers
- Support for **Tor for anonymized connections**
- Server connection statistics
- List, create, delete, or modify any server or user
- Scan QR codes or easily download the client configurations.
- Create QR codes
- Easily download the client configurations.
- Automatic Light/Dark Mode
## Installation
@ -22,20 +28,20 @@
### 2. Docker Image
#### Build from source
#### Build from source (recommended)
```bash
git clone https://github.com/shahradelahi/wireadmin
docker buildx build --tag litehex/wireadmin ./wireadmin
```
#### Pull from Docker Hub (recommended)
#### Pull from Docker Hub
```bash
docker pull litehex/wireadmin
docker pull litehex/wireadmin # OR ghcr.io/shahradelahi/wireadmin
```
### 4. Persistent Data
### 3. Persistent Data
WireAdmin store configurations at `/data`. It's important to mount a volume at this location to ensure that
your data is not lost during container restarts or updates.
@ -46,18 +52,19 @@ your data is not lost during container restarts or updates.
docker volume create wireadmin-data --driver local
```
### 4. Run
### 4. Run WireAdmin
When creating each server, ensure that you add the port exposure through Docker. In the below command, the port `51820`
is added for the WireGuard server.
**NOTE:** The port `3000` is for the WebUI, and can be changed with `PORT` environment variable, but for security
reasons, it's recommended to NOT expose **_any kind of WebUI_** to the public. It's up to you to remove it after
configuring
the Servers/Peers.
> 💡 The port `3000` is for the WebUI, and can be changed with `PORT` environment variable, but for security
> reasons, it's recommended to NOT expose **_any kind of WebUI_** to the public. It's up to you to remove it after
> configuring
> the Servers/Peers.
```bash
docker run --rm \
```shell
docker run --detach \
--name wireadmin \
-e WG_HOST=<YOUR_SERVER_IP> \
-e UI_PASSWORD=<ADMIN_PASSWORD> \
-p "3000:3000/tcp" \
@ -70,18 +77,24 @@ docker run --rm \
litehex/wireadmin
```
## Environment Options
> 💡 Replace `<YOUR_SERVER_IP>` with the IP address of your server.
> 💡 Replace `<ADMIN_PASSWORD>` with the password for the admin UI.
The Web UI will now be available on `http://0.0.0.0:3000`.
## Options
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
| Option | Description | Optional |
| ----------------- | ------------------------------------------------------------------------------- | -------- |
| `WG_HOST` | The public IP address of the WireGuard server. | |
| `UI_PASSWORD` | The password for the admin UI. | |
| `HOST` | The hostname for the WebUI. (default: `127.0.0.1`) | ✔️ |
| `PORT` | The port for the WebUI. (default: `3000`) | ✔️ |
| `TOR_USE_BRIDGES` | Set this to `1` and then mount the bridges file at `/etc/torrc.d/bridges.conf`. | ✔️ |
| `TOR_*` | The `Torrc` proxy configuration. (e.g. `SocksPort` as `TOR_SOCKSPORT="9050"`) | ✔️ |
| Option | Description | Default | Optional |
| ----------------- | ------------------------------------------------------------------------------- | ------------------- | -------- |
| `WG_HOST` | The public IP address of the WireGuard server. | - | |
| `UI_PASSWORD` | The password for the admin UI. | `insecure-password` | |
| `HOST` | The hostname for the WebUI. | `127.0.0.1` | ✔️ |
| `PORT` | The port for the WebUI. | `3000` | ✔️ |
| `TOR_USE_BRIDGES` | Set this to `1` and then mount the bridges file at `/etc/torrc.d/bridges.conf`. | - | ✔️ |
| `TOR_*` | The `Torrc` proxy configuration. (e.g. `SocksPort` as `TOR_SOCKS_PORT="9050"`) | - | ✔️ |
## Reporting

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -55,6 +55,7 @@
"formsnap": "^1.0.0",
"jsonwebtoken": "^9.0.2",
"lucide-svelte": "^0.330.0",
"mode-watcher": "^0.3.0",
"node-netkit": "0.1.0-canary.2",
"pino": "^8.18.0",
"pino-pretty": "^10.3.1",

File diff suppressed because it is too large Load Diff

View File

@ -4,68 +4,67 @@
@layer base {
:root {
--background: 210 20% 98%;
--foreground: 224 71.4% 4.1%;
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--muted: 220 14.3% 95.9%;
--muted-foreground: 220 8.9% 46.1%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--popover: 0 0% 100%;
--popover-foreground: 224 71.4% 4.1%;
--popover-foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 224 71.4% 4.1%;
--card-foreground: 240 10% 3.9%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--primary: 358 72% 31%;
--primary-foreground: 0 0% 100%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 220 14.3% 95.9%;
--secondary-foreground: 220.9 39.3% 11%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--accent: 220 14.3% 95.9%;
--accent-foreground: 220.9 39.3% 11%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--destructive-foreground: 0 0% 98%;
/*--ring: 224 71.4% 4.1%;*/
--ring: var(--primary);
--ring: 240 5% 64.9%;
--radius: 0.5rem;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--muted: 215 27.9% 16.9%;
--muted-foreground: 217.9 10.6% 64.9%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--popover: 224 71.4% 4.1%;
--popover-foreground: 210 20% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 215 27.9% 16.9%;
--secondary-foreground: 210 20% 98%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--accent: 215 27.9% 16.9%;
--accent-foreground: 210 20% 98%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
--destructive-foreground: 0 85.7% 97.3%;
--ring: 216 12.2% 83.9%;
--ring: 240 3.7% 15.9%;
}
}

View File

@ -8,6 +8,6 @@
export { className as class };
</script>
<div class={cn('text-black/25', className)} {...$$restProps}>
<div class={cn('text-black/50 dark:text-white/50', className)} {...$$restProps}>
<slot />
</div>

View File

@ -1,5 +1,6 @@
<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { mode } from 'mode-watcher';
import { cn } from '$lib/utils';
type $$Props = HTMLAttributes<SVGImageElement> & {
@ -11,7 +12,12 @@
let className: $$Props['class'] = undefined;
export let borderColor: $$Props['borderColor'] = '#d9d9d9';
export let shadowColor: $$Props['shadowColor'] = '#f5f5f5';
export let contentColor: $$Props['contentColor'] = '#fafafa';
export let contentColor: $$Props['contentColor'] = 'transparent';
$: $mode, (contentColor = $mode === 'dark' ? 'transparent' : 'transparent');
$: $mode, (shadowColor = $mode === 'dark' ? '#1e2021' : '#f5f5f5');
$: $mode, (borderColor = $mode === 'dark' ? '#d0ccc6' : '#d9d9d9');
export { className as class };
</script>

View File

@ -1,4 +1,11 @@
<script lang="ts">
import Sun from 'lucide-svelte/icons/sun';
import Moon from 'lucide-svelte/icons/moon';
import { toggleMode } from 'mode-watcher';
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils';
export let showLogout: boolean = false;
</script>
@ -9,7 +16,7 @@
<h1 class="max-sm:text-lg">WireAdmin</h1>
</div>
<div class={'flex items-center gap-x-8'}>
<div class={'flex items-center gap-x-3'}>
<a
href={'https://github.com/shahradelahi/wireadmin'}
title={'Giv me a star on Github'}
@ -17,21 +24,35 @@
>
<img
src={'https://img.shields.io/github/stars/shahradelahi/wireadmin.svg?style=social&label=Star'}
alt={'Giv me a star on Github'}
alt={'Gimme a Star'}
/>
</a>
<Button on:click={toggleMode} variant="ghost" size="icon">
<Sun
class="h-[1rem] w-[1rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
/>
<Moon
class="absolute h-[1rem] w-[1rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
/>
<span class="sr-only">Toggle theme</span>
</Button>
{#if showLogout}
<a
href="/logout"
rel="external"
title="Logout"
class="group text-sm/2 font-medium text-neutral-700 hover:text-neutral-800"
>
<i
class="far fa-arrow-right-from-arc text-sm text-neutral-500 group-hover:text-neutral-800 mr-0.5"
></i>
Logout
<a href="/logout" rel="external" title="Logout">
<Button variant="ghost" class="group text-sm/2 gap-x-2 font-medium">
<i
class={cn(
'far fa-arrow-right-from-arc text-sm mr-0.5',
'text-neutral-500 group-hover:text-neutral-800',
'dark:text-neutral-400 dark:group-hover:text-neutral-100',
)}
></i>
<span
class="text-neutral-700 hover:text-neutral-800 dark:text-neutral-100 dark:hover:text-neutral-100"
>Logout</span
>
</Button>
</a>
{/if}
</div>

View File

@ -1,7 +1,9 @@
<script lang="ts">
import '../app.css';
import { Toaster } from 'svelte-french-toast';
import { ModeWatcher } from 'mode-watcher';
</script>
<ModeWatcher />
<slot />
<Toaster />

View File

@ -60,6 +60,6 @@
</div>
<a href={`/${server.id}`} title="Manage the Server" class="hidden md:block">
<Button variant="ghost" size="sm">Manage</Button>
<Button variant="outline" size="sm">Manage</Button>
</a>
</div>

View File

@ -45,6 +45,6 @@
</div>
<a href={`/service/${slug}`} title="Manage the Server" class="hidden md:block">
<Button variant="ghost" size="sm">Manage</Button>
<Button variant="outline" size="sm">Manage</Button>
</a>
</div>

View File

@ -7,6 +7,7 @@
import { createEventDispatcher, onMount } from 'svelte';
import { getPeerConf } from '$lib/wireguard/utils';
import { QRCodeDialog } from '$lib/components/qrcode-dialog';
import { cn } from '$lib/utils';
export let peer: Peer;
@ -56,7 +57,10 @@
</script>
<div
class="flex items-center justify-between p-4 border border-neutral-200/60 rounded-md hover:border-neutral-200"
class={cn(
'flex items-center justify-between p-4 rounded-md',
'border border-input bg-background hover:bg-accent/30 hover:text-accent-foreground',
)}
>
<div class="flex items-center gap-x-2">
<div
@ -84,18 +88,18 @@
<!-- QRCode -->
<QRCodeDialog let:builder content={conf}>
<PeerActionButton builders={[builder]} disabled={isLoading}>
<i class={'fal text-neutral-700 group-hover:text-primary fa-qrcode'} />
<i class={'fal fa-qrcode'} />
</PeerActionButton>
</QRCodeDialog>
<!-- Download -->
<PeerActionButton disabled={isLoading} on:click={handleDownload}>
<i class={'fal text-neutral-700 group-hover:text-primary fa-download'} />
<i class={'fal fa-download'} />
</PeerActionButton>
<!-- Remove -->
<PeerActionButton loading={isLoading} on:click={handleRemove}>
<i class={'fal text-neutral-700 group-hover:text-primary text-lg fa-trash-can'} />
<i class={'fal fa-trash-can'} />
</PeerActionButton>
</div>
</div>

View File

@ -25,7 +25,8 @@
{...$$restProps}
class={cn(
'group flex items-center justify-center w-10 aspect-square rounded-md',
'bg-gray-200/80 hover:bg-gray-100/50',
'bg-gray-200/80 hover:bg-gray-100/50 dark:bg-neutral-800/80 dark:hover:bg-neutral-800/50',
'text-neutral-700 dark:text-neutral-300 hover:text-primary dark:hover:text-primary',
'border border-transparent hover:border-primary',
'transition-colors duration-200 ease-in-out',
'cursor-pointer',

View File

@ -73,7 +73,7 @@
<CardTitle>Logs</CardTitle>
</CardHeader>
<CardContent class="relative">
<textarea class="w-full h-80 p-2 bg-gray-100" readonly bind:value={logs} />
<textarea class="w-full h-80 p-2" readonly bind:value={logs} />
{#if !logs}
<div class="absolute inset-0 flex items-center justify-center">
<i class="text-4xl animate-spin fas fa-circle-notch"></i>
@ -81,7 +81,7 @@
{/if}
</CardContent>
<CardFooter class="flex justify-end gap-2">
<Button on:click={restart}>Restart</Button>
<Button on:click={restart} variant="outline">Restart</Button>
<Button variant="destructive" on:click={clearLogs} disabled={!logs}>Clear</Button>
</CardFooter>
</Card>