mirror of
https://github.com/wireadmin/wireadmin
synced 2025-06-26 18:28:06 +00:00
update
This commit is contained in:
parent
9ee534f2bb
commit
ae787625b9
@ -1,5 +1,5 @@
|
||||
---
|
||||
'wireadmin': patch
|
||||
"wireadmin": patch
|
||||
---
|
||||
|
||||
fix: Improve password hashing method and env loader
|
||||
|
5
.changeset/mighty-kangaroos-talk.md
Normal file
5
.changeset/mighty-kangaroos-talk.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"wireadmin": major
|
||||
---
|
||||
|
||||
BREAKING: `UI_PASSWORD` has been removed. Please use `ADMIN_PASSWORD` instead.
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
'wireadmin': patch
|
||||
"wireadmin": patch
|
||||
---
|
||||
|
||||
fix: tor config generation when container restarts
|
||||
|
@ -9,4 +9,4 @@ Dockerfile
|
||||
*.md
|
||||
tests/
|
||||
*.log
|
||||
tmp/
|
||||
tmp/
|
||||
|
@ -1,7 +1,8 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
web
|
||||
build
|
||||
dist
|
||||
.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
|
17
.prettierrc
17
.prettierrc
@ -1,9 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"trailingComma": "es5",
|
||||
"endOfLine": "lf",
|
||||
"printWidth": 100,
|
||||
"overrides": [
|
||||
{
|
||||
@ -12,6 +14,19 @@
|
||||
"parser": "markdown",
|
||||
"printWidth": 79
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte",
|
||||
"plugins": ["prettier-plugin-svelte"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"importOrder": ["<THIRD_PARTY_MODULES>", "", "^types$", "^@lib/(.*)$", "^@/(.*)$", "", "^[./]"],
|
||||
"plugins": [
|
||||
"@ianvs/prettier-plugin-sort-imports",
|
||||
"prettier-plugin-tailwindcss",
|
||||
"prettier-plugin-sh"
|
||||
]
|
||||
}
|
||||
|
98
Dockerfile
98
Dockerfile
@ -1,95 +1,65 @@
|
||||
ARG ALPINE_VERSION=3.19
|
||||
ARG NODE_VERSION=20
|
||||
ARG VERSION=0.0.0-canary
|
||||
|
||||
FROM --platform=$BUILDPLATFORM chriswayg/tor-alpine:latest as tor
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine${ALPINE_VERSION} as base
|
||||
LABEL Maintainer="Shahrad Elahi <https://github.com/shahradelahi>"
|
||||
WORKDIR /app
|
||||
|
||||
FROM --platform=$BUILDPLATFORM shahradel/torproxy:latest as tor
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine${ALPINE_VERSION} as node
|
||||
ENV TZ=UTC
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
COPY --from=tor /usr/local/bin/obfs4proxy /usr/local/bin/obfs4proxy
|
||||
COPY --from=tor /usr/local/bin/meek-server /usr/local/bin/meek-server
|
||||
|
||||
# Install required packages
|
||||
FROM node as base
|
||||
RUN apk add -U --no-cache \
|
||||
iproute2 iptables net-tools \
|
||||
screen curl bash \
|
||||
iptables net-tools \
|
||||
screen bash \
|
||||
wireguard-tools \
|
||||
tor &&\
|
||||
# NPM packages
|
||||
npm install -g @litehex/node-checksum@0.2 &&\
|
||||
# Clear APK cache
|
||||
rm -rf /var/cache/apk/*
|
||||
tor \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
FROM base AS build
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
COPY web .
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile \
|
||||
&& NODE_ENV=production pnpm build \
|
||||
&& pnpm prune --prod \
|
||||
&& mv node_modules /tmp/node_modules \
|
||||
&& mv build /tmp/build \
|
||||
&& rm -rf ./*
|
||||
|
||||
FROM base
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=tor /usr/local/bin/lyrebird /usr/local/bin/lyrebird
|
||||
|
||||
COPY /config/torrc.template /etc/tor/torrc.template
|
||||
|
||||
# Copy user scripts
|
||||
COPY /bin /usr/local/bin
|
||||
RUN chmod -R +x /usr/local/bin
|
||||
|
||||
COPY web/package.json web/pnpm-lock.yaml ./
|
||||
|
||||
# Base env
|
||||
ENV PROTOCOL_HEADER=x-forwarded-proto
|
||||
ENV HOST_HEADER=x-forwarded-host
|
||||
|
||||
|
||||
FROM base AS build
|
||||
|
||||
# Setup Pnpm - Pnpm only used for build stage
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
COPY web .
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile \
|
||||
# build
|
||||
&& mkdir -p /data \
|
||||
&& echo gA== > /data/storage.b64 \
|
||||
&& NODE_ENV=production pnpm run build \
|
||||
# Omit devDependencies
|
||||
&& pnpm prune --prod \
|
||||
# Move the goods to a temporary location
|
||||
&& mv node_modules /tmp/node_modules \
|
||||
&& mv build /tmp/build \
|
||||
# Remove everything else
|
||||
&& rm -rf ./*
|
||||
|
||||
|
||||
FROM base AS release
|
||||
ENV NODE_ENV=production
|
||||
ENV LOG_LEVEL=error
|
||||
|
||||
# Copy the goods from the build stage
|
||||
COPY --from=build /tmp/package.json package.json
|
||||
COPY --from=build /tmp/node_modules node_modules
|
||||
COPY --from=build /tmp/build build
|
||||
|
||||
# Fix permissions
|
||||
RUN mkdir -p /data && chmod 700 /data
|
||||
RUN mkdir -p /etc/torrc.d && chmod -R 400 /etc/torrc.d
|
||||
RUN mkdir -p /var/vlogs && touch /var/vlogs/web && chmod -R 600 /var/vlogs
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV LOG_LEVEL=error
|
||||
RUN mkdir -p /etc/tor/torrc.d && chmod -R 400 /etc/tor/torrc.d
|
||||
RUN mkdir -p /var/log/wireadmin && touch /var/log/wireadmin/web.log
|
||||
|
||||
# Setup entrypoint
|
||||
COPY docker-entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
# Healthcheck
|
||||
HEALTHCHECK --interval=60s --timeout=3s --start-period=20s --retries=3 \
|
||||
CMD curl -f http://127.0.0.1:3000/api/health || exit 1
|
||||
|
||||
# Volumes
|
||||
VOLUME ["/etc/torrc.d", "/data", "/var/vlogs"]
|
||||
|
||||
# Overwrite package version
|
||||
RUN node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('/app/package.json')); pkg.version = process.env.VERSION; fs.writeFileSync('/app/package.json', JSON.stringify(pkg, null, 2));"
|
||||
VOLUME ["/etc/tor", "/var/lib/tor", "/data"]
|
||||
|
||||
# Run the app
|
||||
EXPOSE 3000/tcp
|
||||
CMD [ "npm", "run", "start" ]
|
||||
CMD [ "node", "./build/index.js" ]
|
||||
|
@ -1,57 +1,48 @@
|
||||
ARG ALPINE_VERSION=3.19
|
||||
ARG NODE_VERSION=20
|
||||
ARG VERSION=0.0.0-dev
|
||||
|
||||
FROM --platform=$BUILDPLATFORM chriswayg/tor-alpine:latest as tor
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine${ALPINE_VERSION} as base
|
||||
LABEL Maintainer="Shahrad Elahi <https://github.com/shahradelahi>"
|
||||
WORKDIR /app
|
||||
|
||||
FROM --platform=$BUILDPLATFORM shahradel/torproxy:latest as tor
|
||||
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine${ALPINE_VERSION} as node
|
||||
ENV TZ=UTC
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
RUN apk update \
|
||||
&& apk upgrade \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
COPY --from=tor /usr/local/bin/obfs4proxy /usr/local/bin/obfs4proxy
|
||||
COPY --from=tor /usr/local/bin/meek-server /usr/local/bin/meek-server
|
||||
|
||||
# Install required packages
|
||||
FROM node as base
|
||||
RUN apk add -U --no-cache \
|
||||
iproute2 iptables net-tools \
|
||||
screen vim curl bash \
|
||||
iptables net-tools \
|
||||
screen logrotate vim bash \
|
||||
wireguard-tools \
|
||||
tor &&\
|
||||
# NPM packages
|
||||
npm install -g @litehex/node-checksum@0.2 &&\
|
||||
# Clear APK cache
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
# Copy Tor Configs
|
||||
COPY /config/torrc.template /etc/tor/torrc.template
|
||||
COPY /config/obfs4-bridges.conf /etc/torrc.d/obfs4-bridges.conf
|
||||
|
||||
# Copy user scripts
|
||||
COPY /bin /usr/local/bin
|
||||
RUN chmod -R +x /usr/local/bin
|
||||
FROM base
|
||||
WORKDIR /app
|
||||
|
||||
# Setup Pnpm
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
# Base env
|
||||
ENV NODE_ENV=development
|
||||
ENV LOG_LEVEL=debug
|
||||
ENV PROTOCOL_HEADER=x-forwarded-proto
|
||||
ENV HOST_HEADER=x-forwarded-host
|
||||
|
||||
COPY --from=tor /usr/local/bin/lyrebird /usr/local/bin/lyrebird
|
||||
|
||||
FROM base AS runner
|
||||
|
||||
ENV NODE_ENV=development
|
||||
ENV LOG_LEVEL=debug
|
||||
# Copy Tor Configs
|
||||
COPY /config/torrc.template /etc/tor/torrc.template
|
||||
COPY /config/obfs4-bridges.conf /etc/tor/torrc.d/obfs4-bridges.conf
|
||||
|
||||
# Fix permissions
|
||||
RUN mkdir -p /data && chmod 700 /data
|
||||
RUN mkdir -p /etc/torrc.d && chmod -R 400 /etc/torrc.d
|
||||
RUN mkdir -p /var/vlogs && touch /var/vlogs/web && chmod -R 600 /var/vlogs
|
||||
RUN mkdir -p /etc/tor/torrc.d && chmod -R 400 /etc/tor/torrc.d
|
||||
RUN mkdir -p /var/log/wireadmin && touch /var/log/wireadmin/web.log
|
||||
|
||||
RUN echo '* * * * * /usr/bin/env logrotate /etc/logrotate.d/rotator' > /etc/crontabs/root
|
||||
|
||||
# Setup entrypoint
|
||||
COPY docker-entrypoint.sh /entrypoint.sh
|
||||
@ -59,7 +50,7 @@ RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
# Volumes
|
||||
VOLUME ["/etc/torrc.d", "/data", "/var/vlogs"]
|
||||
VOLUME ["/etc/tor", "/var/lib/tor", "/data"]
|
||||
|
||||
# Run the app
|
||||
EXPOSE 5173/tcp
|
||||
|
138
README.md
138
README.md
@ -1,6 +1,6 @@
|
||||
# WireGuard (Easy Admin UI)
|
||||
|
||||
[](https://github.com/shahradelahi/wireadmin/actions/workflows/ci.yml)
|
||||
[](https://github.com/wireadmin/wireadmin/actions/workflows/ci.yml)
|
||||
[](https://opensource.org/licenses/GPL-3.0)
|
||||
|
||||

|
||||
@ -9,6 +9,21 @@
|
||||
| :----------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------: |
|
||||
| <img src="assets/screenshot-2.png" alt="screenshot" style="width:100%;max-height:300px;"/> | <img src="assets/screenshot-4.png" alt="screenshot" style="width:100%;max-height:300px;"/> | <img src="assets/screenshot-3.png" alt="screenshot" style="width:100%;max-height:300px;"/> |
|
||||
|
||||
---
|
||||
|
||||
- [Features](#features)
|
||||
- [Build locally](#build-locally)
|
||||
- [Image](#image)
|
||||
- [Ports](#ports)
|
||||
- [Usage](#usage)
|
||||
- [Docker Compose](#docker-compose)
|
||||
- [Command line](#command-line)
|
||||
- [Persistent Data](#persistent-data)
|
||||
- [Environment variables](#environment-variables)
|
||||
- [Upgrade](#upgrade)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
|
||||
- Simple and friendly UI
|
||||
@ -20,61 +35,44 @@
|
||||
- Easily download the client configurations.
|
||||
- Automatic Light/Dark Mode
|
||||
|
||||
## Installation
|
||||
## Image
|
||||
|
||||
### 1. Prerequisites
|
||||
| Registry | Image |
|
||||
| ------------------------------------------------------------------------------------------------------- | ----------------------------- |
|
||||
| [GitHub Container Registry](https://github.com/users/shahradelahi/packages/container/package/cfw-proxy) | `ghcr.io/wireadmin/wireadmin` |
|
||||
|
||||
- [Docker Engine](https://docs.docker.com/engine/install/)
|
||||
## Ports
|
||||
|
||||
### 2. Docker Image
|
||||
- `3000`: WebUI
|
||||
|
||||
#### Build from source (recommended)
|
||||
And for any additional ports of WireGuard instance, should be exposed through Docker.
|
||||
|
||||
## Usage
|
||||
|
||||
### Docker Compose
|
||||
|
||||
Docker compose is the recommended way to run this image. You can use the following
|
||||
[docker compose template](docker-compose.yml), then run the container:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/shahradelahi/wireadmin
|
||||
docker buildx build --tag litehex/wireadmin ./wireadmin
|
||||
docker compose up -d
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
#### Pull from Docker Hub
|
||||
|
||||
```bash
|
||||
docker pull litehex/wireadmin # OR ghcr.io/shahradelahi/wireadmin
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
#### Create a docker volume
|
||||
|
||||
```bash
|
||||
docker volume create wireadmin-data --driver local
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
> 💡 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.
|
||||
### Command line
|
||||
|
||||
```shell
|
||||
docker run --detach \
|
||||
--name wireadmin \
|
||||
-e WG_HOST=<YOUR_SERVER_IP> \
|
||||
-e UI_PASSWORD=<ADMIN_PASSWORD> \
|
||||
-p "3000:3000/tcp" \
|
||||
-p "51820:51820/udp" \
|
||||
-v "wireadmin-data:/data" \
|
||||
--cap-add=NET_ADMIN \
|
||||
--cap-add=SYS_MODULE \
|
||||
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \
|
||||
--sysctl="net.ipv4.ip_forward=1" \
|
||||
litehex/wireadmin
|
||||
$ docker run -d \
|
||||
--name wireadmin \
|
||||
-e WG_HOST= \
|
||||
ADMIN_PASSWORD= \
|
||||
"3000:3000/tcp" \
|
||||
-p "51820:51820/udp" \
|
||||
--cap-add=NET_ADMIN \
|
||||
--cap-add=SYS_MODULE \
|
||||
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \
|
||||
--sysctl="net.ipv4.ip_forward=1" \
|
||||
ghcr.io/wireadmin/wireadmin < YOUR_SERVER_IP > -e < ADMIN_PASSWORD > -p
|
||||
```
|
||||
|
||||
> 💡 Replace `<YOUR_SERVER_IP>` with the IP address of your server.
|
||||
@ -83,24 +81,46 @@ docker run --detach \
|
||||
|
||||
The Web UI will now be available on `http://0.0.0.0:3000`.
|
||||
|
||||
## Options
|
||||
### 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.
|
||||
|
||||
To create a docker volume, use the following command:
|
||||
|
||||
```bash
|
||||
docker volume create wireadmin-data --driver local
|
||||
```
|
||||
|
||||
### Environment variables
|
||||
|
||||
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
|
||||
|
||||
| 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"`) | - | ✔️ |
|
||||
| Option | Description | Default | Optional |
|
||||
| ----------------- | ----------------------------------------------------------------------------------- | ------------------- | -------- |
|
||||
| `WG_HOST` | The public IP address of the WireGuard server. | - | |
|
||||
| `ADMIN_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/tor/torrc.d/bridges.conf`. | - | ✔️ |
|
||||
| `TOR_*` | The `Torrc` proxy configuration. (e.g. `SocksPort` as `TOR_SOCKS_PORT="9050"`) | - | ✔️ |
|
||||
|
||||
## Reporting
|
||||
## Upgrade
|
||||
|
||||
For bug reports, and feature requests, please create an issue
|
||||
on [GitHub](https://github.com/shahradelahi/wireadmin/issues).
|
||||
Recreate the container whenever I push an update:
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Want to contribute? Awesome! To show your support is to star the project, or to raise issues
|
||||
on [GitHub](https://github.com/shahradelahi/docker-cfw-proxy).
|
||||
|
||||
Thanks again for your support, it is much appreciated! 🙏
|
||||
|
||||
## License
|
||||
|
||||
[GPL-3.0](LICENSE) © [Shahrad Elahi](https://github.com/shahradelahi)
|
||||
[GPL-3.0](/LICENSE) © [Shahrad Elahi](https://github.com/shahradelahi)
|
||||
|
16
bin/logs
16
bin/logs
@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is used for getting last 100 lines of a screen session
|
||||
|
||||
SCREEN_NAME="$1"
|
||||
LIMIT="${2:-100}"
|
||||
SCRIPT=$(basename "$0")
|
||||
|
||||
if [ -z "$SCREEN_NAME" ]; then
|
||||
echo "Usage: ${SCRIPT} <screen_name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
screen -S "$SCREEN_NAME" -X hardcopy /tmp/screen-hardcopy
|
||||
tail -n "${LIMIT}" /tmp/screen-hardcopy
|
||||
rm /tmp/screen-hardcopy
|
@ -12,4 +12,11 @@ Bridge obfs4 31.18.117.18:9899 13BD8D1786AB84231D2630840142E81B0DDDAD19 cert=E31
|
||||
Bridge obfs4 86.88.234.28:50001 DE6145637D189CEBF7B052DFC111A511B2BE8072 cert=FXAneGUETzpaw5oxNqO1Wi3EWLBSgbeIN0Z8GVFxromPutq6JkduMpzzvbQpyfYcGYjyJw iat-mode=0
|
||||
Bridge obfs4 65.108.214.170:23909 8ABD0C0130A37EB3F686F883BCE6D5E59F66C228 cert=mJZdHhaAk6VzaOjQA1UWGkVbDbGqLRuNSuBSk0evlfKRKVzb2EmNio2N0ja+JG1to8KWYw iat-mode=0
|
||||
Bridge obfs4 92.243.27.238:46311 2E5DC5F2632535630E87883262F967DA376700E2 cert=1BAr2DKmCPxel2DTMXKyOQgoxHM2q6SqJ0tDrZdlyCCrBXhJhsGCICWWZpBEuVB6bdMVWA iat-mode=0
|
||||
|
||||
Bridge obfs4 129.213.132.232:50913 5F163F907B3CFCCA66639EE297C2CD27006F7235 cert=ojqfACdTxWZNEPZwfEbAbDMMumnxzwoRAVMRwjkVl5RDH1h1j38YALzRhFFVpzsu7ZthQw iat-mode=0
|
||||
Bridge obfs4 79.215.99.47:9531 DC1A7B010A348F3A6BE0750D38428D1EAD976D69 cert=TX3XOj1SX3fAB9yoA4dCx8Geu325i564gwIBgnAMyhP6NBdd9dW90gJpWQXKL/VC2BlTNQ iat-mode=0
|
||||
Bridge obfs4 167.235.71.161:25754 EED9A10892988E28ADCFDDF19AB4F8868C51892D cert=6q19P7O+Zcai7mCxDCVIjiQnrufsMO4X5Ky88dcNBI2H5+LUqNMIcr3kNV3Cd7sKcgUSeg iat-mode=0
|
||||
Bridge obfs4 65.21.6.66:15751 4D0BEE93BABCFBCD837BB33344850B78FFECD9FF cert=29a0bbjME3mTxC5wcafYAS4v43DVyOtSQWx374De7R38ARiVQZZ3fORSwgGCtDMCFZyxcw iat-mode=0
|
||||
Bridge obfs4 185.177.207.205:11205 084113B9A27A8087C26236EF67A16784DF58D7F0 cert=pzuLxMv5n+7nRqX2czUQGh8JZBCMEVUHlkciocGRpX2IsPlTqd1YyXFQxRwfsYEFuuBdBQ iat-mode=2
|
||||
Bridge obfs4 51.75.74.245:8356 18C27C9850967FD4BF4188963C1AEBEC40807823 cert=y6cQEx4d/25KALeqJA+2uB+6rmzoD9KZ0FrQGNwxb10yVj3mDjHtOneqcqhRT+BADhCTYg iat-mode=0
|
||||
Bridge obfs4 91.134.100.128:51106 ABB9F62BEC331EE5DE7B3C3BEA014F8910E0C6BD cert=bC5k/PWVu06cSPhSm6mrQDBevReEpdtpokmDibpK0MBxRaVnn0S3O6YvEi4BDUeasn71bA iat-mode=0
|
||||
Bridge obfs4 51.83.252.216:45918 C2B7E51665111C9BE43894E90B9A65DD8A25490D cert=oQgHCdMhvfF44gwHJssSHXltUE4r8gddEQeZ4iy17XHZMP+ql2QTG9LziiEqNfNCqFDBSw iat-mode=0
|
||||
|
@ -1,5 +1,9 @@
|
||||
##### Auto-Generated by the WireAdmin. Do not edit. #####
|
||||
AutomapHostsOnResolve 1
|
||||
VirtualAddrNetwork 10.192.0.0/10
|
||||
DNSPort {{INET_ADDRESS}}:53530
|
||||
TransPort {{INET_ADDRESS}}:59040
|
||||
ClientTransportPlugin obfs4 exec /usr/local/bin/obfs4proxy managed
|
||||
User tor
|
||||
DataDirectory /var/lib/tor
|
||||
TransPort {{INET_ADDRESS}}:59040 IsolateClientAddr IsolateClientProtocol IsolateDestAddr IsolateDestPort
|
||||
ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec /usr/local/bin/lyrebird
|
||||
%include /etc/tor/torrc.d/*.conf
|
||||
|
34
docker-bake.hcl
Normal file
34
docker-bake.hcl
Normal file
@ -0,0 +1,34 @@
|
||||
variable "DEFAULT_TAG" {
|
||||
default = "wireadmin:local"
|
||||
}
|
||||
|
||||
// Special target: https://github.com/docker/metadata-action#bake-definition
|
||||
target "docker-metadata-action" {
|
||||
tags = ["${DEFAULT_TAG}"]
|
||||
}
|
||||
|
||||
// Default target if none specified
|
||||
group "default" {
|
||||
targets = ["image-local"]
|
||||
}
|
||||
|
||||
target "image" {
|
||||
inherits = ["docker-metadata-action"]
|
||||
}
|
||||
|
||||
target "image-local" {
|
||||
inherits = ["image"]
|
||||
output = ["type=docker"]
|
||||
}
|
||||
|
||||
target "image-all" {
|
||||
inherits = ["image"]
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm/v6",
|
||||
"linux/arm/v7",
|
||||
"linux/arm64",
|
||||
"linux/386",
|
||||
"linux/s390x"
|
||||
]
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
wireadmin:
|
||||
image: wireadmin
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-Dev
|
||||
volumes:
|
||||
- ./web/:/app/
|
||||
ports:
|
||||
- '5173:5173'
|
||||
environment:
|
||||
- WG_HOST=192.168.1.102
|
||||
- UI_PASSWORD=password
|
||||
- WG_HOST=192.168.88.252
|
||||
- ADMIN_PASSWORD=password
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
|
12
docker-compose.yml
Normal file → Executable file
12
docker-compose.yml
Normal file → Executable file
@ -1,4 +1,3 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
wireadmin:
|
||||
environment:
|
||||
@ -7,13 +6,14 @@ services:
|
||||
- WG_HOST=localhost
|
||||
# ⚠️ Required:
|
||||
# You can use `openssl rand -base64 8` to generate a secure password
|
||||
- UI_PASSWORD=super-secret-password
|
||||
- ADMIN_PASSWORD=super-secret-password
|
||||
|
||||
image: wireadmin
|
||||
image: wireadmin/wireadmin
|
||||
container_name: wireadmin
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- persist-data:/data
|
||||
- wireadmin-data:/data
|
||||
- tor-data:/var/lib/tor
|
||||
ports:
|
||||
- '51820:51820/udp'
|
||||
- '3000:3000/tcp'
|
||||
@ -25,5 +25,5 @@ services:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
|
||||
volumes:
|
||||
persist-data:
|
||||
driver: local
|
||||
wireadmin-data:
|
||||
tor-data:
|
||||
|
@ -7,9 +7,7 @@ TOR_CONFIG="/etc/tor/torrc"
|
||||
TOR_CONFIG_TEMPLATE="${TOR_CONFIG}.template"
|
||||
|
||||
log() {
|
||||
local level=$1
|
||||
local message=$2
|
||||
echo "[$(date +"%Y-%m-%d %H:%M:%S")] [$level] $message"
|
||||
echo "[$(date +"%Y-%m-%d %H:%M:%S")] [$1] $2"
|
||||
}
|
||||
|
||||
to_camel_case() {
|
||||
@ -31,19 +29,12 @@ generate_tor_config() {
|
||||
key=$(echo "$line" | awk '{print $1}')
|
||||
value=$(echo "$line" | awk '{print $2}')
|
||||
key=$(to_camel_case "$key")
|
||||
echo "$key $value" >>"${TOR_CONFIG}"
|
||||
echo "$key $value" >> "${TOR_CONFIG}"
|
||||
done
|
||||
|
||||
# Removing duplicated tor options
|
||||
awk -F= '!a[tolower($1)]++' "${TOR_CONFIG}" >"/tmp/$(basename "${TOR_CONFIG}")" &&
|
||||
mv "/tmp/$(basename "${TOR_CONFIG}")" "${TOR_CONFIG}"
|
||||
|
||||
# Checking if there is /etc/torrc.d folder and if there are use globbing to include all files
|
||||
local torrc_files=$(find /etc/torrc.d -type f -name "*.conf")
|
||||
if [ -n "${torrc_files}" ]; then
|
||||
log "notice" "Found torrc.d folder with configuration files"
|
||||
echo "%include /etc/torrc.d/*.conf" >>"${TOR_CONFIG}"
|
||||
fi
|
||||
awk -F= '!a[tolower($1)]++' "${TOR_CONFIG}" > "/tmp/$(basename "${TOR_CONFIG}")" \
|
||||
&& mv "/tmp/$(basename "${TOR_CONFIG}")" "${TOR_CONFIG}"
|
||||
|
||||
# Remove comment line with single Hash
|
||||
sed -i '/^#\([^#]\)/d' "${TOR_CONFIG}"
|
||||
@ -63,54 +54,50 @@ echo "| |/ |/ / / / / __/ ___ / /_/ / / / / / / / / / /"
|
||||
echo "|__/|__/_/_/ \___/_/ |_\__,_/_/ /_/ /_/_/_/ /_/ "
|
||||
echo " "
|
||||
|
||||
mkdir -p /var/vlogs
|
||||
touch "$ENV_FILE"
|
||||
chmod 400 "$ENV_FILE"
|
||||
|
||||
touch "${ENV_FILE}"
|
||||
chmod 400 "${ENV_FILE}"
|
||||
|
||||
if ! grep -q "AUTH_SECRET" "${ENV_FILE}"; then
|
||||
tee -a "${ENV_FILE}" &>/dev/null <<EOF
|
||||
AUTH_SECRET=$(openssl rand -base64 32)
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Checking if there is `UI_PASSWORD` environment variable
|
||||
# if there was, converting it to sha256 and storing it to
|
||||
# the .env
|
||||
if [ -n "$UI_PASSWORD" ]; then
|
||||
sed -i '/^HASHED_PASSWORD/d' "${ENV_FILE}"
|
||||
tee -a "${ENV_FILE}" &>/dev/null <<EOF
|
||||
HASHED_PASSWORD=$(checksum hash -a sha256 -C "${UI_PASSWORD}")
|
||||
EOF
|
||||
unset UI_PASSWORD
|
||||
else
|
||||
log "error" "no password set for the UI"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$WG_HOST" ]; then
|
||||
log "error" "the WG_HOST environment variable is not set"
|
||||
exit 1
|
||||
if [ -z "$ADMIN_PASSWORD" ]; then
|
||||
log warn "No ADMIN_PASSWORD provided, using default password"
|
||||
fi
|
||||
|
||||
# Remove duplicated envs
|
||||
awk -F= '!a[$1]++' "${ENV_FILE}" >"/tmp/$(basename "${ENV_FILE}")" &&
|
||||
mv "/tmp/$(basename "${ENV_FILE}")" "${ENV_FILE}"
|
||||
awk -F= '!a[$1]++' "$ENV_FILE" > "/tmp/$(basename "$ENV_FILE")" \
|
||||
&& mv "/tmp/$(basename "$ENV_FILE")" "$ENV_FILE"
|
||||
|
||||
if [ -z "$WG_HOST" ]; then
|
||||
log "error" "the WG_HOST environment variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Generate Tor configuration
|
||||
generate_tor_config
|
||||
|
||||
tee "/etc/logrotate.d/rotator" &> /dev/null << EOF
|
||||
/var/log/wireadmin/*.log {
|
||||
size 512K
|
||||
rotate 3
|
||||
missingok
|
||||
notifempty
|
||||
create 0640 root adm
|
||||
copytruncate
|
||||
}
|
||||
EOF
|
||||
|
||||
# Start Tor on the background
|
||||
screen -dmS "tor" tor -f "${TOR_CONFIG}"
|
||||
crond
|
||||
_TOR_UID=$(id -u tor)
|
||||
mkdir -p /var/lib/tor && chown -R "$_TOR_UID:$_TOR_UID" /var/lib/tor
|
||||
screen -L -Logfile /var/log/wireadmin/tor.log -dmS tor bash -c "screen -S tor -X wrap off; tor -f $TOR_CONFIG"
|
||||
|
||||
sleep 1
|
||||
echo -e "\n======================== Versions ========================"
|
||||
echo -e "Alpine Version: \c" && cat /etc/alpine-release
|
||||
echo -e "WireGuard Version: \c" && wg -v | head -n 1 | awk '{print $1,$2}'
|
||||
echo -e "Tor Version: \c" && tor --version | head -n 1
|
||||
echo -e "Obfs4proxy Version: \c" && obfs4proxy -version
|
||||
echo -e "Alpine: \c" && cat /etc/alpine-release
|
||||
echo -e "WireGuard: \c" && wg -v | head -n 1 | awk '{print $2}'
|
||||
echo -e "Tor: \c" && tor --version | head -n 1 | cut -d ' ' -f3
|
||||
echo -e "Lyrebird: \c" && lyrebird -version
|
||||
echo -e "\n========================= Torrc ========================="
|
||||
cat "${TOR_CONFIG}"
|
||||
grep -v "^#" "$TOR_CONFIG"
|
||||
echo -e "========================================================\n"
|
||||
sleep 1
|
||||
|
||||
|
21
package.json
21
package.json
@ -5,20 +5,21 @@
|
||||
"private": true,
|
||||
"packageManager": "pnpm@8.15.0",
|
||||
"scripts": {
|
||||
"dev": "pnpm docker:drop && docker compose -f docker-compose.yml -f docker-compose.dev.yml up",
|
||||
"dev:image": "docker buildx build --tag wireadmin -f Dockerfile-Dev .",
|
||||
"build": "pnpm docker:build",
|
||||
"start": "pnpm docker:drop && docker compose -f docker-compose.yml up",
|
||||
"docker:build": "docker buildx build --tag wireadmin .",
|
||||
"docker:drop": "docker compose rm -fsv",
|
||||
"format": "prettier --write . && pnpm --if-present -r format",
|
||||
"format:check": "prettier --check . && pnpm --if-present -r format:check"
|
||||
"dev": "docker compose -f docker-compose.yml -f docker-compose.dev.yml up --force-recreate",
|
||||
"dev:image": "docker buildx build --network host --tag wireadmin -f Dockerfile-Dev .",
|
||||
"build": "docker buildx build --tag wireadmin .",
|
||||
"start": "docker compose -f docker-compose.yml up --force-recreate",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check . "
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"dependencies": {
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.2"
|
||||
"prettier-plugin-sh": "^0.14.0",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"prettier-plugin-tailwindcss": "^0.5.14"
|
||||
}
|
||||
}
|
||||
|
983
pnpm-lock.yaml
983
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/mocharc.json",
|
||||
"require": ["tsx", "chai/register-expect", "mocha.setup.js"],
|
||||
"timeout": 10000
|
||||
}
|
@ -12,4 +12,4 @@ static
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
tsconfig.json
|
||||
tsconfig.json
|
||||
|
@ -1,17 +0,0 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte",
|
||||
"plugins": ["prettier-plugin-svelte"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -2,12 +2,12 @@
|
||||
"$schema": "https://shadcn-svelte.com/schema.json",
|
||||
"style": "default",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/app.css",
|
||||
"baseColor": "gray"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils"
|
||||
"components": "@lib/components",
|
||||
"utils": "@lib/utils"
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
process.env.NODE_ENV = 'test';
|
@ -1,17 +1,13 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.0.0-dev",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"test": "mocha",
|
||||
"check:format": "prettier --check .",
|
||||
"format": "prettier --write .",
|
||||
"start": "node ./build/index.js"
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.0",
|
||||
"engines": {
|
||||
@ -19,50 +15,45 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^5.0.1",
|
||||
"@sveltejs/kit": "^2.5.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@types/chai": "^4.3.14",
|
||||
"@sveltejs/kit": "^2.5.10",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.0",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^20.12.2",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"chai": "^5.1.0",
|
||||
"mocha": "^10.4.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-load-config": "^5.0.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.8",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"sveltekit-superforms": "^2.12.2",
|
||||
"postcss-load-config": "^5.1.0",
|
||||
"svelte": "^4.2.17",
|
||||
"svelte-check": "^3.7.1",
|
||||
"svelte-preprocess": "^5.1.4",
|
||||
"sveltekit-superforms": "^2.13.1",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.4.3",
|
||||
"vite": "^5.2.7",
|
||||
"zod": "^3.22.4"
|
||||
"tsx": "^4.10.5",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@litehex/storage-box": "^0.2.2-canary.0",
|
||||
"@t3-oss/env-core": "0.7.3",
|
||||
"bits-ui": "^0.18.0",
|
||||
"clsx": "^2.1.0",
|
||||
"bits-ui": "^0.21.9",
|
||||
"clsx": "^2.1.1",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dotenv": "^16.4.4",
|
||||
"execa": "^8.0.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"execa": "^9.1.0",
|
||||
"formsnap": "^1.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lucide-svelte": "^0.330.0",
|
||||
"lucide-svelte": "^0.379.0",
|
||||
"mode-watcher": "^0.3.0",
|
||||
"node-netkit": "0.1.0-canary.2",
|
||||
"pino": "^8.18.0",
|
||||
"pino-pretty": "^10.3.1",
|
||||
"node-netkit": "0.1.0-canary.3",
|
||||
"p-safe": "^1.0.0",
|
||||
"pino": "^9.1.0",
|
||||
"pino-pretty": "^11.0.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"storage-box": "^1.0.0-canary.4",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwind-variants": "^0.2.0"
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwind-variants": "^0.2.1"
|
||||
}
|
||||
}
|
||||
|
1129
web/pnpm-lock.yaml
1129
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -74,6 +74,9 @@
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings:
|
||||
'rlig' 1,
|
||||
'calt' 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/fontawesome/all.min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
@ -1,25 +1,25 @@
|
||||
import { type Handle, redirect } from '@sveltejs/kit';
|
||||
import { verifyToken } from '$lib/auth';
|
||||
import { AUTH_COOKIE } from '$lib/constants';
|
||||
import 'dotenv/config';
|
||||
|
||||
import { redirect, type Handle } from '@sveltejs/kit';
|
||||
|
||||
import { verifyToken } from '@lib/auth';
|
||||
import { AUTH_COOKIE } from '@lib/constants';
|
||||
import logger from '@lib/logger';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
if (!AUTH_EXCEPTION.includes(event.url.pathname)) {
|
||||
const token = event.cookies.get(AUTH_COOKIE);
|
||||
const token_valid = await verifyToken(token ?? '');
|
||||
logger.debug(`-> ${event.request.method} ${event.url.pathname}`);
|
||||
|
||||
const is_login_page = event.url.pathname === '/login';
|
||||
if (!token_valid && !is_login_page) {
|
||||
// return redirect;
|
||||
throw redirect(303, '/login');
|
||||
}
|
||||
const token = event.cookies.get(AUTH_COOKIE);
|
||||
const token_valid = await verifyToken(token ?? '');
|
||||
|
||||
if (token_valid && is_login_page) {
|
||||
throw redirect(303, '/');
|
||||
}
|
||||
const is_login_page = event.url.pathname === '/login';
|
||||
if (!token_valid && !is_login_page) {
|
||||
throw redirect(303, '/login');
|
||||
}
|
||||
|
||||
if (token_valid && is_login_page) {
|
||||
throw redirect(303, '/');
|
||||
}
|
||||
|
||||
return resolve(event);
|
||||
};
|
||||
|
||||
const AUTH_EXCEPTION = ['/api/health'];
|
||||
|
@ -1,34 +1,42 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { client } from '$lib/storage';
|
||||
import { env } from '$lib/env';
|
||||
|
||||
export async function generateToken(): Promise<string> {
|
||||
import { WG_AUTH_PATH } from '@lib/constants';
|
||||
import { env } from '@lib/env';
|
||||
import { storage } from '@lib/storage';
|
||||
import { sha256 } from '@lib/utils/hash';
|
||||
|
||||
interface GenerateTokenParams {
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
export async function generateToken(params: GenerateTokenParams): Promise<string> {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const oneHour = 60 * 60;
|
||||
const token = jwt.sign(
|
||||
{
|
||||
ok: true,
|
||||
iat: now,
|
||||
exp: now + oneHour,
|
||||
exp: now + params.expiresIn,
|
||||
},
|
||||
env.AUTH_SECRET,
|
||||
env.AUTH_SECRET
|
||||
);
|
||||
client.setex(token, '1', oneHour);
|
||||
await storage.lpushex(WG_AUTH_PATH, sha256(token), params.expiresIn);
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function verifyToken(token: string): Promise<boolean> {
|
||||
try {
|
||||
const decode = jwt.verify(token, env.AUTH_SECRET);
|
||||
if (!decode) return false;
|
||||
if (!token || !(await storage.lexists(WG_AUTH_PATH, sha256(token)))) return false;
|
||||
|
||||
const exists = client.exists(token);
|
||||
return exists;
|
||||
return !!jwt.verify(token, env.AUTH_SECRET);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function revokeToken(token: string): Promise<void> {
|
||||
client.del(token);
|
||||
if (!token) return;
|
||||
const index = await storage
|
||||
.lgetall(WG_AUTH_PATH)
|
||||
.then((l) => l.findIndex((t) => t === sha256(token)));
|
||||
await storage.ldel(WG_AUTH_PATH, index);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import { ClipboardCopyIcon } from 'lucide-svelte';
|
||||
import { Button } from '@lib/components/ui/button';
|
||||
|
||||
export let showInHover: boolean = false;
|
||||
export let rootClass: string | undefined = undefined;
|
||||
@ -12,20 +14,23 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class={cn('group flex items-center', rootClass)}>
|
||||
<div class={cn('group flex items-center gap-3', rootClass)}>
|
||||
<slot />
|
||||
<i
|
||||
<Button
|
||||
aria-roledescription="Copy to clipboard"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class={cn(
|
||||
'ml-2 mb-0.5 far fa-copy cursor-pointer text-gray-400/80 hover:text-primary',
|
||||
showInHover && 'group-hover:opacity-100 opacity-0',
|
||||
className,
|
||||
)}
|
||||
size="none"
|
||||
variant="ghost"
|
||||
on:click={handleCopy}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') handleCopy();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<ClipboardCopyIcon
|
||||
class={cn(
|
||||
'h-4 w-4 cursor-pointer text-gray-400/80 hover:text-primary',
|
||||
showInHover && 'group-hover:opacity-100 opacity-0',
|
||||
className
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { ZodEffects, ZodString } from 'zod';
|
||||
import { SquarePenIcon } from 'lucide-svelte';
|
||||
import { Button } from '@lib/components/ui/button';
|
||||
|
||||
export let editMode: boolean = false;
|
||||
export let schema: ZodString | ZodEffects<any>;
|
||||
@ -40,7 +42,7 @@
|
||||
editMode ? 'block' : 'hidden',
|
||||
'w-full ring-2 ring-neutral-800 ring-offset-2 rounded transition-colors duration-200 ease-in-out outline-transparent',
|
||||
inputClass,
|
||||
error && 'ring-red-500 rounded',
|
||||
error && 'ring-red-500 rounded'
|
||||
)}
|
||||
{value}
|
||||
on:keydown={(e) => {
|
||||
@ -60,14 +62,16 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<i
|
||||
class="fal fa-pen-to-square text-sm opacity-0 group-hover:opacity-100 text-neutral-400 hover:text-primary cursor-pointer"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
<Button
|
||||
class="opacity-0 group-hover:opacity-100 text-gray-400/80 group-hover:text-primary"
|
||||
aria-roledescription="Edit"
|
||||
size="none"
|
||||
variant="ghost"
|
||||
on:click={handleEnterEditMode}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') handleEnterEditMode();
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<SquarePenIcon class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { mode } from 'mode-watcher';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<SVGImageElement> & {
|
||||
borderColor?: string;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import { EmptyDescription, EmptySimpleImage, type Props } from '.';
|
||||
|
||||
type $$Props = Props;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Root from './empty.svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
import Description from './empty-description.svelte';
|
||||
import SimpleImage from './empty-simple-img.svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import Root from './empty.svelte';
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
description?: string | null;
|
||||
|
20
web/src/lib/components/iconset/icon.svelte
Normal file
20
web/src/lib/components/iconset/icon.svelte
Normal file
@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
export let name: string | undefined = undefined;
|
||||
export let color = 'currentColor';
|
||||
export let size: number | string = 24;
|
||||
export let strokeWidth: number | string = 2;
|
||||
export let absoluteStrokeWidth: boolean = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
{...$$restProps}
|
||||
width={size}
|
||||
height={size}
|
||||
stroke={color}
|
||||
stroke-width={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}
|
||||
class={cn('lucide-icon', 'lucide', name ? `lucide-${name}` : '', $$props.class)}
|
||||
>
|
||||
<slot />
|
||||
</svg>
|
21
web/src/lib/components/iconset/index.ts
Normal file
21
web/src/lib/components/iconset/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { SVGAttributes } from 'svelte/elements';
|
||||
|
||||
import Root from './icon.svelte';
|
||||
import Onion from './onion-icon.svelte';
|
||||
|
||||
interface Props extends SVGAttributes<SVGSVGElement> {
|
||||
color?: string;
|
||||
size?: number | string;
|
||||
strokeWidth?: number | string;
|
||||
absoluteStrokeWidth?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export {
|
||||
Root,
|
||||
Onion,
|
||||
type Props,
|
||||
//
|
||||
Root as Icon,
|
||||
Onion as OnionIcon,
|
||||
};
|
15
web/src/lib/components/iconset/onion-icon.svelte
Normal file
15
web/src/lib/components/iconset/onion-icon.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@lib/components/iconset/icon.svelte';
|
||||
import type { Props } from '@lib/components/iconset';
|
||||
|
||||
type $$Props = Props;
|
||||
</script>
|
||||
|
||||
<Icon {...$$restProps} viewBox="0 0 179.51151 205.15602" fill="currentColor">
|
||||
<g transform="translate(-17.934981,-39.932967)">
|
||||
<path
|
||||
d="m 104.88587,47.145484 -1.60279,-2.804867 q -2.00347,-4.407649 -6.811816,-4.407649 -7.212516,0.801391 -8.414602,8.013907 -2.804867,14.024336 -11.620164,24.041719 -8.815297,9.616688 -20.435462,18.431985 0,0 0,0 0,0 0,0 -14.425031,10.818771 -26.045196,25.644501 -11.219469,14.42503 -12.02086,39.26814 0.400696,24.04172 15.226423,40.47023 14.825727,16.02781 37.66536,23.64102 l -4.407648,12.82225 q -2.003477,8.8153 6.010429,12.42156 8.815298,2.00348 12.421555,-6.01043 l 4.808344,-14.42503 q 4.006954,0.80139 8.414602,1.20208 v 10.01739 q 0.801391,8.81529 9.616685,9.61668 8.8153,-0.80139 9.61669,-9.61668 V 225.4549 q 4.40765,-0.40069 8.4146,-1.20208 l 4.80835,14.42503 q 3.60625,8.01391 12.42155,6.01043 8.01391,-3.60626 6.01043,-12.42156 l -4.40765,-12.82225 q 22.83964,-7.61321 37.66536,-23.64102 14.82573,-16.42851 15.22643,-40.47023 -0.8014,-24.84311 -12.02086,-39.26814 -11.62017,-14.82573 -26.0452,-25.644501 0,0 0,0 0,0 0,0 -11.62016,-8.815297 -20.43546,-18.431985 -8.8153,-10.017383 -11.21947,-24.041719 -1.60278,-7.212516 -8.8153,-8.013907 -4.80834,0 -6.81182,4.407649 l -1.60278,2.804867 q -2.80487,3.606258 -5.60973,0 z M 56.802427,114.4623 v -0.4007 q 0.400695,-0.40069 0.80139,-0.80139 0,0 0,0 1.602782,-2.40417 4.407649,-2.80486 1.202086,0 2.404172,0.40069 0.801391,0 2.003477,0.80139 2.404172,1.60278 2.804867,4.40765 0,1.20209 -0.400695,2.40417 0,0.80139 -0.400696,1.20209 v 0.40069 q -0.400695,0.4007 -0.80139,1.20209 -0.400696,1.60278 -2.003477,4.40765 -2.404172,6.01043 -5.209039,15.22642 -2.804868,9.61669 -3.606258,19.63407 -0.400696,10.01738 2.404172,18.03129 1.602781,6.01043 -4.006953,8.4146 -5.609735,1.60278 -8.013907,-3.60626 -4.006953,-11.62016 -3.205563,-23.64102 1.202086,-12.02086 4.006954,-22.03824 3.205562,-10.41808 6.01043,-16.82921 1.202086,-3.20556 2.003476,-4.80834 0.400695,-0.4007 0.400695,-0.80139 0,-0.4007 0.400696,-0.80139 z M 101.6803,89.218493 q 0.4007,-0.801391 0.4007,-1.202086 0.80139,-1.202086 2.00348,-2.003477 2.00347,-1.602781 4.80834,-1.202086 1.20208,0.400696 2.00348,0.801391 1.20208,0.801391 2.00347,2.003477 0.80139,1.202086 1.20209,2.804867 0,0.801391 0,2.003477 -0.4007,0.80139 -0.4007,1.202086 v 0 l -0.40069,0.80139 q 0,0 0,0.400696 -0.4007,0.80139 -0.80139,1.602781 0,0 0,0.400695 -1.20209,2.804867 -2.40418,7.212516 -3.60625,10.01738 -7.21251,25.6445 -4.006954,15.62712 -5.20904,32.05563 -0.801391,16.8292 3.20556,30.05215 1.60278,5.60973 -4.006951,8.0139 -5.609734,1.20209 -8.013906,-4.00695 -4.808344,-16.02781 -3.606258,-34.4598 1.202086,-18.43198 5.209039,-34.86049 3.606258,-16.02781 7.212516,-26.846589 2.003476,-4.808343 2.80487,-8.013906 0.80139,-1.202086 1.20208,-2.003477 v -0.400695 -0.400695 z m 31.65493,14.825727 q 0.4007,-6.01043 6.41113,-6.411125 0,0 0,0 0,0 0,0 0,0 0,0 0,0 0.40069,0 3.60626,0 5.60974,3.205565 l -0.4007,0.40069 v 0 q 0.4007,-0.40069 0.4007,-0.40069 v 0 0 0 0.40069 l 0.40069,0.4007 q 0.4007,0.40069 0.80139,1.60278 1.20209,2.00348 2.80487,5.60973 3.60626,7.61322 8.01391,19.63407 4.40765,12.02086 6.41112,26.0452 2.00348,14.42503 -1.60278,28.04867 -2.40417,5.60974 -8.0139,4.40765 -5.60974,-2.00347 -4.40765,-7.61321 2.80486,-10.81877 1.20208,-23.24033 -1.60278,-12.02086 -5.60973,-23.64102 -4.00695,-11.21947 -7.61321,-18.03129 -1.60278,-3.60626 -2.40417,-5.60974 v 0 q -2.40418,-1.60278 -2.40418,-4.80834 z"
|
||||
aria-label="Onion"
|
||||
/>
|
||||
</g>
|
||||
</Icon>
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
export let content: string;
|
||||
export let maxLength: number = Math.floor(content.length / 2);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import PageFooter from '$lib/components/page/PageFooter.svelte';
|
||||
import PageHeader from '$lib/components/page/PageHeader.svelte';
|
||||
import { cn } from '@lib/utils';
|
||||
import PageFooter from '@lib/components/page/PageFooter.svelte';
|
||||
import PageHeader from '@lib/components/page/PageHeader.svelte';
|
||||
|
||||
export let rootClass: string | undefined = undefined;
|
||||
let className: string | undefined = undefined;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import DotDivider from '$lib/components/DotDivider.svelte';
|
||||
import DotDivider from '@lib/components/DotDivider.svelte';
|
||||
</script>
|
||||
|
||||
<footer class={'flex items-center justify-center'}>
|
||||
@ -12,7 +12,7 @@
|
||||
</a>
|
||||
<DotDivider className="font-bold text-gray-400" />
|
||||
<a
|
||||
href={'https://github.com/shahradelahi/wireadmin'}
|
||||
href={'https://github.com/wireadmin/wireadmin'}
|
||||
title={'Github'}
|
||||
class={'px-2 font-medium text-gray-400/80 hover:text-gray-500 text-xs'}
|
||||
>
|
||||
|
@ -3,8 +3,8 @@
|
||||
import Moon from 'lucide-svelte/icons/moon';
|
||||
|
||||
import { toggleMode } from 'mode-watcher';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { cn } from '$lib/utils';
|
||||
import { Button } from '@lib/components/ui/button';
|
||||
import { LogOutIcon } from 'lucide-svelte';
|
||||
|
||||
export let showLogout: boolean = false;
|
||||
</script>
|
||||
@ -18,12 +18,12 @@
|
||||
|
||||
<div class={'flex items-center gap-x-3'}>
|
||||
<a
|
||||
href={'https://github.com/shahradelahi/wireadmin'}
|
||||
href={'https://github.com/wireadmin/wireadmin'}
|
||||
title={'Giv me a star on Github'}
|
||||
class="hidden md:block"
|
||||
>
|
||||
<img
|
||||
src={'https://img.shields.io/github/stars/shahradelahi/wireadmin.svg?style=social&label=Star'}
|
||||
src={'https://img.shields.io/github/stars/wireadmin/wireadmin.svg?style=social&label=Star'}
|
||||
alt={'Gimme a Star'}
|
||||
/>
|
||||
</a>
|
||||
@ -41,17 +41,8 @@
|
||||
{#if showLogout}
|
||||
<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
|
||||
>
|
||||
<LogOutIcon class={'w-4 h-4 mr-0.5'} />
|
||||
Logout
|
||||
</Button>
|
||||
</a>
|
||||
{/if}
|
||||
|
@ -6,11 +6,11 @@
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '$lib/components/ui/dialog';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
} from '@lib/components/ui/dialog';
|
||||
import { Skeleton } from '@lib/components/ui/skeleton';
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { SafeReturn } from '$lib/typings';
|
||||
import { Button } from '@lib/components/ui/button';
|
||||
import type { SafeReturn } from '@lib/typings';
|
||||
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import { badgeVariants, type Variant } from '.';
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export { default as Badge } from './badge.svelte';
|
||||
|
||||
export const badgeVariants = tv({
|
||||
|
@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import Ellipsis from 'lucide-svelte/icons/ellipsis';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement> & {
|
||||
el?: HTMLSpanElement;
|
||||
};
|
||||
|
||||
export let el: $$Props['el'] = undefined;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={el}
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn('flex h-9 w-9 items-center justify-center', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<Ellipsis class="h-4 w-4" />
|
||||
<span class="sr-only">More</span>
|
||||
</span>
|
16
web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte
Normal file
16
web/src/lib/components/ui/breadcrumb/breadcrumb-item.svelte
Normal file
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLLiAttributes } from 'svelte/elements';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = HTMLLiAttributes & {
|
||||
el?: HTMLLIElement;
|
||||
};
|
||||
|
||||
export let el: $$Props['el'] = undefined;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<li bind:this={el} class={cn('inline-flex items-center gap-1.5', className)}>
|
||||
<slot />
|
||||
</li>
|
31
web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte
Normal file
31
web/src/lib/components/ui/breadcrumb/breadcrumb-link.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAnchorAttributes } from 'svelte/elements';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = HTMLAnchorAttributes & {
|
||||
el?: HTMLAnchorElement;
|
||||
asChild?: boolean;
|
||||
};
|
||||
|
||||
export let href: $$Props['href'] = undefined;
|
||||
export let el: $$Props['el'] = undefined;
|
||||
export let asChild: $$Props['asChild'] = false;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
|
||||
let attrs: Record<string, unknown>;
|
||||
|
||||
$: attrs = {
|
||||
class: cn('transition-colors hover:text-foreground', className),
|
||||
href,
|
||||
...$$restProps,
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if asChild}
|
||||
<slot {attrs} />
|
||||
{:else}
|
||||
<a bind:this={el} {...attrs} {href}>
|
||||
<slot {attrs} />
|
||||
</a>
|
||||
{/if}
|
23
web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte
Normal file
23
web/src/lib/components/ui/breadcrumb/breadcrumb-list.svelte
Normal file
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLOlAttributes } from 'svelte/elements';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = HTMLOlAttributes & {
|
||||
el?: HTMLOListElement;
|
||||
};
|
||||
|
||||
export let el: $$Props['el'] = undefined;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<ol
|
||||
bind:this={el}
|
||||
class={cn(
|
||||
'flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5',
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</ol>
|
23
web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte
Normal file
23
web/src/lib/components/ui/breadcrumb/breadcrumb-page.svelte
Normal file
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement> & {
|
||||
el?: HTMLSpanElement;
|
||||
};
|
||||
|
||||
export let el: $$Props['el'] = undefined;
|
||||
export let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={el}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
class={cn('font-normal text-foreground', className)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</span>
|
@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLLiAttributes } from 'svelte/elements';
|
||||
import ChevronRight from 'lucide-svelte/icons/chevron-right';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = HTMLLiAttributes & {
|
||||
el?: HTMLLIElement;
|
||||
};
|
||||
|
||||
export let el: $$Props['el'] = undefined;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
class={cn('[&>svg]:size-3.5', className)}
|
||||
bind:this={el}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot>
|
||||
<ChevronRight />
|
||||
</slot>
|
||||
</li>
|
15
web/src/lib/components/ui/breadcrumb/breadcrumb.svelte
Normal file
15
web/src/lib/components/ui/breadcrumb/breadcrumb.svelte
Normal file
@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLElement> & {
|
||||
el?: HTMLElement;
|
||||
};
|
||||
|
||||
export let el: $$Props['el'] = undefined;
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<nav class={className} bind:this={el} aria-label="breadcrumb" {...$$restProps}>
|
||||
<slot />
|
||||
</nav>
|
25
web/src/lib/components/ui/breadcrumb/index.ts
Normal file
25
web/src/lib/components/ui/breadcrumb/index.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Ellipsis from './breadcrumb-ellipsis.svelte';
|
||||
import Item from './breadcrumb-item.svelte';
|
||||
import Link from './breadcrumb-link.svelte';
|
||||
import List from './breadcrumb-list.svelte';
|
||||
import Page from './breadcrumb-page.svelte';
|
||||
import Separator from './breadcrumb-separator.svelte';
|
||||
import Root from './breadcrumb.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Ellipsis,
|
||||
Item,
|
||||
Separator,
|
||||
Link,
|
||||
List,
|
||||
Page,
|
||||
//
|
||||
Root as Breadcrumb,
|
||||
Ellipsis as BreadcrumbEllipsis,
|
||||
Item as BreadcrumbItem,
|
||||
Separator as BreadcrumbSeparator,
|
||||
Link as BreadcrumbLink,
|
||||
List as BreadcrumbList,
|
||||
Page as BreadcrumbPage,
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Button as ButtonPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { type Events, type Props, buttonVariants } from '.';
|
||||
import { type Events, type Props, buttonVariants } from './index';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = Props;
|
||||
type $$Events = Events;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
import type { Button as ButtonPrimitive } from 'bits-ui';
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
import Root from './button.svelte';
|
||||
|
||||
const buttonVariants = tv({
|
||||
@ -8,7 +9,6 @@ const buttonVariants = tv({
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
success: 'bg-green-500 text-white hover:bg-green-500/90 hover:text-gray-50',
|
||||
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',
|
||||
@ -19,6 +19,7 @@ const buttonVariants = tv({
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
none: '',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLParagraphElement>;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { HeadingLevel } from './index.js';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||
tag?: HeadingLevel;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Root from './card.svelte';
|
||||
import Content from './card-content.svelte';
|
||||
import Description from './card-description.svelte';
|
||||
import Footer from './card-footer.svelte';
|
||||
import Header from './card-header.svelte';
|
||||
import Title from './card-title.svelte';
|
||||
import Root from './card.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import Minus from 'lucide-svelte/icons/minus';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = CheckboxPrimitive.Props;
|
||||
type $$Events = CheckboxPrimitive.Events;
|
||||
@ -15,7 +15,7 @@
|
||||
<CheckboxPrimitive.Root
|
||||
class={cn(
|
||||
'peer box-content h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:opacity-50',
|
||||
className,
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
{...$$restProps}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Root from './checkbox.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Collapsible as CollapsiblePrimitive } from 'bits-ui';
|
||||
|
||||
import Content from './collapsible-content.svelte';
|
||||
|
||||
const Root = CollapsiblePrimitive.Root;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import * as Dialog from '.';
|
||||
import { cn, flyAndScale } from '$lib/utils';
|
||||
import { cn, flyAndScale } from '@lib/utils';
|
||||
import { X } from 'lucide-svelte';
|
||||
|
||||
type $$Props = DialogPrimitive.ContentProps;
|
||||
@ -21,7 +21,7 @@
|
||||
{transitionConfig}
|
||||
class={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full',
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = DialogPrimitive.DescriptionProps;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
type $$Props = DialogPrimitive.OverlayProps;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = DialogPrimitive.TitleProps;
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
|
||||
import Content from './dialog-content.svelte';
|
||||
import Description from './dialog-description.svelte';
|
||||
import Footer from './dialog-footer.svelte';
|
||||
import Header from './dialog-header.svelte';
|
||||
import Overlay from './dialog-overlay.svelte';
|
||||
import Portal from './dialog-portal.svelte';
|
||||
import Title from './dialog-title.svelte';
|
||||
|
||||
const Root = DialogPrimitive.Root;
|
||||
const Trigger = DialogPrimitive.Trigger;
|
||||
const Close = DialogPrimitive.Close;
|
||||
|
||||
import Title from './dialog-title.svelte';
|
||||
import Portal from './dialog-portal.svelte';
|
||||
import Footer from './dialog-footer.svelte';
|
||||
import Header from './dialog-header.svelte';
|
||||
import Overlay from './dialog-overlay.svelte';
|
||||
import Content from './dialog-content.svelte';
|
||||
import Description from './dialog-description.svelte';
|
||||
|
||||
export {
|
||||
Root,
|
||||
Title,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import * as Button from '$lib/components/ui/button/index.js';
|
||||
import * as Button from '@lib/components/ui/button/index.js';
|
||||
|
||||
type $$Props = Button.Props;
|
||||
type $$Events = Button.Events;
|
||||
|
@ -1,27 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getFormField } from 'formsnap';
|
||||
import type { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
|
||||
type $$Props = CheckboxPrimitive.Props;
|
||||
type $$Events = CheckboxPrimitive.Events;
|
||||
|
||||
export let onCheckedChange: $$Props['onCheckedChange'] = undefined;
|
||||
|
||||
const { name, setValue, attrStore, value } = getFormField();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { name: nameAttr, value: valueAttr, ...rest } = $attrStore;
|
||||
</script>
|
||||
|
||||
<Checkbox
|
||||
{...rest}
|
||||
checked={typeof $value === 'boolean' ? $value : false}
|
||||
onCheckedChange={(v) => {
|
||||
onCheckedChange?.(v);
|
||||
setValue(v);
|
||||
}}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
/>
|
||||
<input hidden {name} value={$value} />
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPathLeaves, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPathLeaves<T>;
|
||||
</script>
|
||||
@ -8,7 +7,7 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||
errorClasses?: string | undefined | null;
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
@ -8,7 +7,7 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getFormField } from 'formsnap';
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
import { Input, type InputEvents } from '$lib/components/ui/input';
|
||||
|
||||
type $$Props = HTMLInputAttributes;
|
||||
type $$Events = InputEvents;
|
||||
|
||||
const { attrStore, value } = getFormField();
|
||||
</script>
|
||||
|
||||
<Input
|
||||
{...$attrStore}
|
||||
bind:value={$value}
|
||||
{...$$restProps}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:paste
|
||||
on:input
|
||||
/>
|
@ -1,12 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class={cn('space-y-2', className)} {...$$restProps}>
|
||||
<slot />
|
||||
</div>
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { getFormControl } from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { cn } from '@lib/utils.js';
|
||||
import { Label } from '@lib/components/ui/label/index.js';
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = FormPrimitive.LegendProps;
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Form as FormPrimitive } from 'formsnap';
|
||||
import { buttonVariants } from '$lib/components/ui/button';
|
||||
import { cn } from '$lib/utils';
|
||||
import { ChevronDown } from 'lucide-svelte';
|
||||
import type { HTMLSelectAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLSelectAttributes;
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Select
|
||||
class={cn(
|
||||
buttonVariants({ variant: 'outline' }),
|
||||
'appearance-none bg-transparent font-normal',
|
||||
className,
|
||||
)}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</FormPrimitive.Select>
|
||||
<ChevronDown class="absolute right-3 top-2.5 h-4 w-4 opacity-50" />
|
@ -1,22 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getFormField } from 'formsnap';
|
||||
import type { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
|
||||
type $$Props = RadioGroupPrimitive.Props;
|
||||
const { attrStore, setValue, name, value } = getFormField();
|
||||
|
||||
export let onValueChange: $$Props['onValueChange'] = undefined;
|
||||
</script>
|
||||
|
||||
<RadioGroup.Root
|
||||
{...$attrStore}
|
||||
onValueChange={(v) => {
|
||||
onValueChange?.(v);
|
||||
setValue(v);
|
||||
}}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<input hidden {name} value={$value} />
|
||||
</RadioGroup.Root>
|
@ -1,17 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import type { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { getFormField } from 'formsnap';
|
||||
|
||||
type $$Props = SelectPrimitive.TriggerProps & {
|
||||
placeholder?: string;
|
||||
};
|
||||
type $$Events = SelectPrimitive.TriggerEvents;
|
||||
const { attrStore } = getFormField();
|
||||
export let placeholder = '';
|
||||
</script>
|
||||
|
||||
<Select.Trigger {...$$restProps} {...$attrStore} on:click on:keydown>
|
||||
<Select.Value {placeholder} />
|
||||
<slot />
|
||||
</Select.Trigger>
|
@ -1,20 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { getFormField } from 'formsnap';
|
||||
import type { Select as SelectPrimitive } from 'bits-ui';
|
||||
|
||||
type $$Props = SelectPrimitive.Props;
|
||||
const { setValue, name, value } = getFormField();
|
||||
export let onSelectedChange: $$Props['onSelectedChange'] = undefined;
|
||||
</script>
|
||||
|
||||
<Select.Root
|
||||
onSelectedChange={(v) => {
|
||||
onSelectedChange?.(v);
|
||||
setValue(v ? v.value : undefined);
|
||||
}}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
<input hidden {name} value={$value} />
|
||||
</Select.Root>
|
@ -1,25 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getFormField } from 'formsnap';
|
||||
import type { Switch as SwitchPrimitive } from 'bits-ui';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
|
||||
type $$Props = SwitchPrimitive.Props;
|
||||
type $$Events = SwitchPrimitive.Events;
|
||||
|
||||
export let onCheckedChange: $$Props['onCheckedChange'] = undefined;
|
||||
|
||||
const { name, setValue, attrStore, value } = getFormField();
|
||||
</script>
|
||||
|
||||
<Switch
|
||||
{...$attrStore}
|
||||
checked={typeof $value === 'boolean' ? $value : false}
|
||||
onCheckedChange={(v) => {
|
||||
onCheckedChange?.(v);
|
||||
setValue(v);
|
||||
}}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
/>
|
||||
<input hidden {name} value={$value} />
|
@ -1,29 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getFormField } from 'formsnap';
|
||||
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
||||
import type { TextareaGetFormField } from './index';
|
||||
import { Textarea, type TextareaEvents } from '$lib/components/ui/textarea';
|
||||
|
||||
type $$Props = HTMLTextareaAttributes;
|
||||
type $$Events = TextareaEvents;
|
||||
|
||||
const { attrStore, value } = getFormField() as TextareaGetFormField;
|
||||
</script>
|
||||
|
||||
<Textarea
|
||||
{...$attrStore}
|
||||
bind:value={$value}
|
||||
{...$$restProps}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:paste
|
||||
on:input
|
||||
/>
|
@ -1,14 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Form as FormPrimitive } from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLParagraphElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Validation
|
||||
class={cn('text-sm font-medium text-destructive', className)}
|
||||
{...$$restProps}
|
||||
/>
|
@ -1,12 +1,13 @@
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
|
||||
import Button from './form-button.svelte';
|
||||
import Description from './form-description.svelte';
|
||||
import Label from './form-label.svelte';
|
||||
import ElementField from './form-element-field.svelte';
|
||||
import FieldErrors from './form-field-errors.svelte';
|
||||
import Field from './form-field.svelte';
|
||||
import Fieldset from './form-fieldset.svelte';
|
||||
import Label from './form-label.svelte';
|
||||
import Legend from './form-legend.svelte';
|
||||
import ElementField from './form-element-field.svelte';
|
||||
import Button from './form-button.svelte';
|
||||
|
||||
const Control = FormPrimitive.Control;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
import type { InputEvents } from '.';
|
||||
|
||||
type $$Props = HTMLInputAttributes;
|
||||
@ -14,7 +14,7 @@
|
||||
<input
|
||||
class={cn(
|
||||
'flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-foreground file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
on:blur
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils.js';
|
||||
|
||||
type $$Props = LabelPrimitive.Props;
|
||||
type $$Events = LabelPrimitive.Events;
|
||||
@ -12,7 +12,7 @@
|
||||
<LabelPrimitive.Root
|
||||
class={cn(
|
||||
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:mousedown
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
|
||||
|
||||
import Root from './radio-group.svelte';
|
||||
import Item from './radio-group-item.svelte';
|
||||
import Root from './radio-group.svelte';
|
||||
|
||||
const Input = RadioGroupPrimitive.Input;
|
||||
|
||||
export {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
|
||||
import { Circle } from 'lucide-svelte';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = RadioGroupPrimitive.ItemProps;
|
||||
type $$Events = RadioGroupPrimitive.ItemEvents;
|
||||
@ -15,7 +15,7 @@
|
||||
{value}
|
||||
class={cn(
|
||||
'aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = RadioGroupPrimitive.Props;
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
|
||||
import Label from './select-label.svelte';
|
||||
import Item from './select-item.svelte';
|
||||
import Content from './select-content.svelte';
|
||||
import Trigger from './select-trigger.svelte';
|
||||
import Item from './select-item.svelte';
|
||||
import Label from './select-label.svelte';
|
||||
import Separator from './select-separator.svelte';
|
||||
import Trigger from './select-trigger.svelte';
|
||||
|
||||
const Root = SelectPrimitive.Root;
|
||||
const Group = SelectPrimitive.Group;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { cn, flyAndScale } from '$lib/utils';
|
||||
import { cn, flyAndScale } from '@lib/utils';
|
||||
|
||||
type $$Props = SelectPrimitive.ContentProps;
|
||||
type $$Events = SelectPrimitive.ContentEvents;
|
||||
@ -28,7 +28,7 @@
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md outline-none',
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:keydown
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = SelectPrimitive.ItemProps;
|
||||
type $$Events = SelectPrimitive.ItemEvents;
|
||||
@ -19,7 +19,7 @@
|
||||
{label}
|
||||
class={cn(
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { cn } from '@lib/utils';
|
||||
|
||||
type $$Props = SelectPrimitive.LabelProps;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user