mirror of
https://github.com/MizuchiLabs/mantrae.git
synced 2025-12-16 20:05:17 -06:00
small ui fixes
This commit is contained in:
BIN
.github/screenshots/dashboard.png
vendored
Normal file
BIN
.github/screenshots/dashboard.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 287 KiB |
@@ -17,7 +17,7 @@
|
||||
- **Middleware Management**: Add middlewares to your routers, including rate limiting, authentication, and more.
|
||||
- **Service Status**: Monitor the status of your services and see their health information.
|
||||
- **DNS Providers**: Support for multiple DNS providers (currently Cloudflare, PowerDNS, Technitium) for automatic DNS record updates.
|
||||
- **Agents**: New agent mode! Instead of defining your routers in the web ui, you can label your containers as usual using traefik labels. Start the agent on the machine and it will automatically set everything up for you.
|
||||
- **Agents**: Instead of defining your routers in the web ui, you can label your containers as usual using traefik labels. Start the agent on the machine and it will automatically set everything up for you.
|
||||
|
||||
## 🚧 Disclaimer 🚧
|
||||
|
||||
@@ -29,7 +29,7 @@ Check out the [docs](https://mizuchi.dev/mantrae/) for more information.
|
||||
|
||||
### Screenshot
|
||||
|
||||

|
||||

|
||||
|
||||
## Roadmap
|
||||
|
||||
|
||||
@@ -21,10 +21,11 @@ services:
|
||||
mantrae-agent:
|
||||
image: ghcr.io/mizuchilabs/mantrae-agent:latest
|
||||
container_name: mantrae-agent
|
||||
network_mode: host # for detecting the hostname of the machine
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock # needed if running as container
|
||||
environment:
|
||||
- TOKEN=<token> # initial token from mantrae server
|
||||
- TOKEN=<token> # token from mantrae server
|
||||
- HOST=https://mantrae.example.com # where to reach mantrae server
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Follow these instructions to install and start using Mantrae with Traefik.
|
||||
1. **Install Traefik**: Ensure you have a running instance of [Traefik](https://traefik.io/).
|
||||
2. **Generate a Secret**: Create a secure, random secret key to use with Mantrae:
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
openssl rand -base64 32
|
||||
```
|
||||
Copy the generated secret as you'll need it in the next steps.
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ Get Mantrae up and running quickly with this guide. This will walk you through i
|
||||
First, generate a secure secret for Mantrae:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Save this secret for the next step.
|
||||
Save this secret for the next step. It has to be either of size 16, 24, or 32 bytes.
|
||||
|
||||
## Step 2: Run Mantrae
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"sveltekit-superforms": "^2.27.1",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^2.1.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tw-animate-css": "^1.3.6",
|
||||
"typescript": "^5.9.2",
|
||||
|
||||
22
web/pnpm-lock.yaml
generated
22
web/pnpm-lock.yaml
generated
@@ -106,8 +106,8 @@ importers:
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1
|
||||
tailwind-variants:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0(tailwind-merge@3.3.1)(tailwindcss@4.1.11)
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0(tailwindcss@4.1.11)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.11
|
||||
version: 4.1.11
|
||||
@@ -1742,18 +1742,17 @@ packages:
|
||||
tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
|
||||
tailwind-merge@3.0.2:
|
||||
resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
|
||||
|
||||
tailwind-merge@3.3.1:
|
||||
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
|
||||
|
||||
tailwind-variants@2.1.0:
|
||||
resolution: {integrity: sha512-82m0eRex0z6A3GpvfoTCpHr+wWJmbecfVZfP3mqLoDxeya5tN4mYJQZwa5Aw1hRZTedwpu1D2JizYenoEdyD8w==}
|
||||
tailwind-variants@1.0.0:
|
||||
resolution: {integrity: sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==}
|
||||
engines: {node: '>=16.x', pnpm: '>=7.x'}
|
||||
peerDependencies:
|
||||
tailwind-merge: '>=3.0.0'
|
||||
tailwindcss: '*'
|
||||
peerDependenciesMeta:
|
||||
tailwind-merge:
|
||||
optional: true
|
||||
|
||||
tailwindcss@4.1.11:
|
||||
resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==}
|
||||
@@ -3518,13 +3517,14 @@ snapshots:
|
||||
|
||||
tabbable@6.2.0: {}
|
||||
|
||||
tailwind-merge@3.0.2: {}
|
||||
|
||||
tailwind-merge@3.3.1: {}
|
||||
|
||||
tailwind-variants@2.1.0(tailwind-merge@3.3.1)(tailwindcss@4.1.11):
|
||||
tailwind-variants@1.0.0(tailwindcss@4.1.11):
|
||||
dependencies:
|
||||
tailwind-merge: 3.0.2
|
||||
tailwindcss: 4.1.11
|
||||
optionalDependencies:
|
||||
tailwind-merge: 3.3.1
|
||||
|
||||
tailwindcss@4.1.11: {}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import * as Select from '$lib/components/ui/select/index.js';
|
||||
import * as ToggleGroup from '$lib/components/ui/toggle-group/index.js';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
|
||||
import {
|
||||
DnsProviderType,
|
||||
@@ -200,16 +201,23 @@
|
||||
<Label class="text-sm">Zone Type</Label>
|
||||
<p class="text-xs text-muted-foreground">DNS zone configuration type</p>
|
||||
</div>
|
||||
<CustomSwitch
|
||||
variant="text"
|
||||
textLabels={{ checked: 'Forwarder', unchecked: 'Primary' }}
|
||||
checked={(item.config?.zoneType || 'primary') === 'forwarder'}
|
||||
onCheckedChange={(value) => {
|
||||
<ToggleGroup.Root
|
||||
type="single"
|
||||
size="sm"
|
||||
value={item.config?.zoneType}
|
||||
onValueChange={(value) => {
|
||||
if (item.config === undefined) item.config = {} as DnsProviderConfig;
|
||||
item.config.zoneType = value ? 'forwarder' : 'primary';
|
||||
item.config.zoneType = value;
|
||||
}}
|
||||
size="md"
|
||||
/>
|
||||
class="border"
|
||||
>
|
||||
<ToggleGroup.Item value="primary" aria-label="Toggle primary">
|
||||
<span class="text-xs">Primary</span>
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item value="forwarder" aria-label="Toggle forwarder" class="px-2">
|
||||
<span class="text-xs">Forwarder</span>
|
||||
</ToggleGroup.Item>
|
||||
</ToggleGroup.Root>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
10
web/src/lib/components/ui/toggle-group/index.ts
Normal file
10
web/src/lib/components/ui/toggle-group/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Root from "./toggle-group.svelte";
|
||||
import Item from "./toggle-group-item.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Item,
|
||||
//
|
||||
Root as ToggleGroup,
|
||||
Item as ToggleGroupItem,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { ToggleGroup as ToggleGroupPrimitive } from "bits-ui";
|
||||
import { getToggleGroupCtx } from "./toggle-group.svelte";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { type ToggleVariants, toggleVariants } from "$lib/components/ui/toggle/index.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
class: className,
|
||||
size,
|
||||
variant,
|
||||
...restProps
|
||||
}: ToggleGroupPrimitive.ItemProps & ToggleVariants = $props();
|
||||
|
||||
const ctx = getToggleGroupCtx();
|
||||
</script>
|
||||
|
||||
<ToggleGroupPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="toggle-group-item"
|
||||
data-variant={ctx.variant || variant}
|
||||
data-size={ctx.size || size}
|
||||
class={cn(
|
||||
toggleVariants({
|
||||
variant: ctx.variant || variant,
|
||||
size: ctx.size || size,
|
||||
}),
|
||||
"min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
|
||||
className
|
||||
)}
|
||||
{value}
|
||||
{...restProps}
|
||||
/>
|
||||
47
web/src/lib/components/ui/toggle-group/toggle-group.svelte
Normal file
47
web/src/lib/components/ui/toggle-group/toggle-group.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts" module>
|
||||
import { getContext, setContext } from "svelte";
|
||||
import type { ToggleVariants } from "$lib/components/ui/toggle/index.js";
|
||||
export function setToggleGroupCtx(props: ToggleVariants) {
|
||||
setContext("toggleGroup", props);
|
||||
}
|
||||
|
||||
export function getToggleGroupCtx() {
|
||||
return getContext<ToggleVariants>("toggleGroup");
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { ToggleGroup as ToggleGroupPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
class: className,
|
||||
size = "default",
|
||||
variant = "default",
|
||||
...restProps
|
||||
}: ToggleGroupPrimitive.RootProps & ToggleVariants = $props();
|
||||
|
||||
setToggleGroupCtx({
|
||||
variant,
|
||||
size,
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Discriminated Unions + Destructing (required for bindable) do not
|
||||
get along, so we shut typescript up by casting `value` to `never`.
|
||||
-->
|
||||
<ToggleGroupPrimitive.Root
|
||||
bind:value={value as never}
|
||||
bind:ref
|
||||
data-slot="toggle-group"
|
||||
data-variant={variant}
|
||||
data-size={size}
|
||||
class={cn(
|
||||
"group/toggle-group data-[variant=outline]:shadow-xs flex w-fit items-center rounded-md",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
@@ -1,13 +1,13 @@
|
||||
import Root from './toggle.svelte';
|
||||
import Root from "./toggle.svelte";
|
||||
export {
|
||||
toggleVariants,
|
||||
type ToggleSize,
|
||||
type ToggleVariant,
|
||||
type ToggleVariants
|
||||
} from './toggle.svelte';
|
||||
type ToggleVariants,
|
||||
} from "./toggle.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Toggle
|
||||
Root as Toggle,
|
||||
};
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<script lang="ts" module>
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
|
||||
export const toggleVariants = tv({
|
||||
base: "hover:bg-muted hover:text-muted-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-[color,box-shadow] focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
'border-input shadow-xs hover:bg-accent hover:text-accent-foreground border bg-transparent'
|
||||
"border-input shadow-xs hover:bg-accent hover:text-accent-foreground border bg-transparent",
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 min-w-9 px-2',
|
||||
sm: 'h-8 min-w-8 px-1.5',
|
||||
lg: 'h-10 min-w-10 px-2.5'
|
||||
}
|
||||
default: "h-9 min-w-9 px-2",
|
||||
sm: "h-8 min-w-8 px-1.5",
|
||||
lg: "h-10 min-w-10 px-2.5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
});
|
||||
|
||||
export type ToggleVariant = VariantProps<typeof toggleVariants>['variant'];
|
||||
export type ToggleSize = VariantProps<typeof toggleVariants>['size'];
|
||||
export type ToggleVariant = VariantProps<typeof toggleVariants>["variant"];
|
||||
export type ToggleSize = VariantProps<typeof toggleVariants>["size"];
|
||||
export type ToggleVariants = VariantProps<typeof toggleVariants>;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Toggle as TogglePrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { Toggle as TogglePrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
pressed = $bindable(false),
|
||||
class: className,
|
||||
size = 'default',
|
||||
variant = 'default',
|
||||
size = "default",
|
||||
variant = "default",
|
||||
...restProps
|
||||
}: TogglePrimitive.RootProps & {
|
||||
variant?: ToggleVariant;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import Progress from '$lib/components/ui/progress/progress.svelte';
|
||||
import { Separator } from '$lib/components/ui/separator/index.js';
|
||||
import TraefikConnection from '$lib/components/utils/TraefikConnection.svelte';
|
||||
import type { Profile } from '$lib/gen/mantrae/v1/profile_pb';
|
||||
@@ -40,8 +41,7 @@
|
||||
Server,
|
||||
Shield,
|
||||
TrendingUp,
|
||||
Users,
|
||||
Wifi
|
||||
Users
|
||||
} from '@lucide/svelte';
|
||||
|
||||
let onlineAgents = $derived.by(() => {
|
||||
@@ -138,10 +138,26 @@
|
||||
<Bot class="h-4 w-4 text-muted-foreground" />
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="text-3xl font-bold">{onlineAgents}</div>
|
||||
<div class="mt-2 flex items-center text-sm">
|
||||
<Wifi class="mr-1 h-3 w-3 text-blue-500" />
|
||||
<span class="text-blue-500">Online now</span>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-3xl font-bold text-primary">{onlineAgents}</span>
|
||||
<span class="text-lg text-muted-foreground">/</span>
|
||||
<span class="text-lg font-medium text-muted-foreground">{$agents?.length}</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Progress
|
||||
value={$agents?.length ? (Number(onlineAgents) / $agents.length) * 100 : 0}
|
||||
class="h-2"
|
||||
/>
|
||||
<div class="flex justify-between text-xs text-muted-foreground">
|
||||
<span>{Number(onlineAgents)} online</span>
|
||||
<span>
|
||||
{$agents?.length ? Math.round((Number(onlineAgents) / $agents.length) * 100) : 0}
|
||||
%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
<div
|
||||
@@ -186,7 +202,7 @@
|
||||
<div class="text-3xl font-bold">{$users?.length}</div>
|
||||
<div class="mt-2 flex items-center text-sm">
|
||||
<Shield class="mr-1 h-3 w-3 text-purple-500" />
|
||||
<span class="text-muted-foreground">Access managed</span>
|
||||
<span class="text-muted-foreground">Active users</span>
|
||||
</div>
|
||||
</Card.Content>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user