rtl layout progress and updates to arabic translations

This commit is contained in:
Henry Dollman
2024-10-31 16:48:28 -04:00
parent 6419178d87
commit df0f3a154f
25 changed files with 361 additions and 322 deletions

View File

@@ -84,18 +84,22 @@ func (a *Agent) Run(pubKey []byte, addr string) {
func (a *Agent) gatherStats() system.CombinedData {
slog.Debug("Getting stats")
systemData := system.CombinedData{
Stats: a.getSystemStats(),
Info: a.systemInfo,
}
slog.Debug("System stats", "data", systemData)
// add docker stats
// systemData := system.CombinedData{
// Stats: a.getSystemStats(),
// Info: a.systemInfo,
// }
systemData := system.CombinedData{}
// add docker stats (testing doing this first for docker 24)
if containerStats, err := a.dockerManager.getDockerStats(); err == nil {
systemData.Containers = containerStats
slog.Debug("Docker stats", "data", systemData.Containers)
} else {
slog.Debug("Error getting docker stats", "err", err)
}
systemData.Stats = a.getSystemStats()
systemData.Info = a.systemInfo
slog.Debug("System stats", "data", systemData)
// add extra filesystems
systemData.Stats.ExtraFs = make(map[string]*system.FsStats)
for name, stats := range a.fsStats {

View File

@@ -25,18 +25,23 @@ type dockerManager struct {
apiContainerList *[]container.ApiInfo // List of containers from Docker API
containerStatsMap map[string]*container.Stats // Keeps track of container stats
validIds map[string]struct{} // Map of valid container ids, used to prune invalid containers from containerStatsMap
goodDockerVersion bool // Whether docker version is at least 25.0.0 (one-shot works correctly)
}
// Add goroutine to the queue
func (d *dockerManager) queue() {
d.sem <- struct{}{}
d.wg.Add(1)
if d.goodDockerVersion {
d.sem <- struct{}{}
}
}
// Remove goroutine from the queue
func (d *dockerManager) dequeue() {
<-d.sem
d.wg.Done()
if d.goodDockerVersion {
<-d.sem
}
}
// Returns stats for all running containers
@@ -47,6 +52,11 @@ func (dm *dockerManager) getDockerStats() ([]*container.Stats, error) {
}
defer resp.Body.Close()
// test sleeping for 1 second if docker 24
if !dm.goodDockerVersion {
time.Sleep(time.Millisecond * 1100)
}
if err := json.NewDecoder(resp.Body).Decode(&dm.apiContainerList); err != nil {
return nil, err
}
@@ -89,11 +99,11 @@ func (dm *dockerManager) getDockerStats() ([]*container.Stats, error) {
// retry failed containers separately so we can run them in parallel (docker 24 bug)
if len(failedContainters) > 0 {
slog.Debug("Retrying failed containers", "count", len(failedContainters))
// time.Sleep(time.Millisecond * 1100)
time.Sleep(time.Millisecond * 1100) // this is a test for docker 24 bug
for _, ctr := range failedContainters {
dm.wg.Add(1)
dm.queue()
go func() {
defer dm.wg.Done()
defer dm.dequeue()
err = dm.updateContainerStats(ctr)
if err != nil {
slog.Error("Error getting container stats", "err", err)
@@ -251,12 +261,9 @@ func newDockerManager() *dockerManager {
Transport: transport,
},
containerStatsMap: make(map[string]*container.Stats),
sem: make(chan struct{}, 5),
}
// Make sure sem is initialized
concurrency := 200
defer func() { dockerClient.sem = make(chan struct{}, concurrency) }()
// Check docker version
// (versions before 25.0.0 have a bug with one-shot which requires all requests to be made in one batch)
var versionInfo struct {
@@ -273,9 +280,10 @@ func newDockerManager() *dockerManager {
// if version > 24, one-shot works correctly and we can limit concurrent operations
if dockerVersion, err := semver.Parse(versionInfo.Version); err == nil && dockerVersion.Major > 24 {
concurrency = 5
dockerClient.goodDockerVersion = true
} else {
slog.Info(fmt.Sprintf("Docker %s is outdated. Upgrade if possible. See https://github.com/henrygd/beszel/issues/58", versionInfo.Version))
}
slog.Debug("Docker", "version", versionInfo.Version, "concurrency", concurrency)
return dockerClient
}

Binary file not shown.

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />

View File

@@ -14,6 +14,7 @@
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-direction": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",
@@ -31,7 +32,6 @@
"cmdk": "^1.0.0",
"d3-time": "^3.1.0",
"i18next": "^23.16.4",
"i18next-browser-languagedetector": "^8.0.0",
"lucide-react": "^0.452.0",
"nanostores": "^0.11.3",
"pocketbase": "^0.21.5",
@@ -50,6 +50,7 @@
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"tailwindcss-rtl": "^0.9.0",
"typescript": "^5.6.3",
"vite": "^5.4.9"
},
@@ -4153,15 +4154,6 @@
"@babel/runtime": "^7.23.2"
}
},
"node_modules/i18next-browser-languagedetector": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
"integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
@@ -5344,6 +5336,13 @@
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tailwindcss-rtl": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/tailwindcss-rtl/-/tailwindcss-rtl-0.9.0.tgz",
"integrity": "sha512-y7yC8QXjluDBEFMSX33tV6xMYrf0B3sa+tOB5JSQb6/G6laBU313a+Z+qxu55M1Qyn8tDMttjomsA8IsJD+k+w==",
"dev": true,
"license": "MIT"
},
"node_modules/tailwindcss/node_modules/picocolors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",

View File

@@ -15,6 +15,7 @@
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-direction": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",
@@ -50,6 +51,7 @@
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"tailwindcss-rtl": "^0.9.0",
"typescript": "^5.6.3",
"vite": "^5.4.9"
},

View File

@@ -41,8 +41,7 @@ export function AddSystemButton({ className }: { className?: string }) {
# - /mnt/disk1/.beszel:/extra-filesystems/disk1:ro
environment:
PORT: ${port}
KEY: "${publicKey}"
# FILESYSTEM: /dev/sda1 # override the root partition / device for disk I/O stats`)
KEY: "${publicKey}"`)
}
function copyInstallCommand(port: string) {
@@ -73,7 +72,7 @@ export function AddSystemButton({ className }: { className?: string }) {
variant="outline"
className={cn("flex gap-1 max-xs:h-[2.4rem]", className, isReadOnlyUser() && "hidden")}
>
<PlusIcon className="h-4 w-4 -ml-1" />
<PlusIcon className="h-4 w-4 -ms-1" />
{t("add")}
<span className="hidden sm:inline">{t("system")}</span>
</Button>
@@ -104,31 +103,31 @@ export function AddSystemButton({ className }: { className?: string }) {
<form onSubmit={handleSubmit as any}>
<div className="grid gap-3 mt-1 mb-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
<Label htmlFor="name" className="text-end">
{t("add_system.name")}
</Label>
<Input id="name" name="name" className="col-span-3" required />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="host" className="text-right">
<Label htmlFor="host" className="text-end">
{t("add_system.host_ip")}
</Label>
<Input id="host" name="host" className="col-span-3" required />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="port" className="text-right">
<Label htmlFor="port" className="text-end">
{t("add_system.port")}
</Label>
<Input ref={port} name="port" id="port" defaultValue="45876" className="col-span-3" required />
</div>
<div className="grid grid-cols-4 items-center gap-4 relative">
<Label htmlFor="pkey" className="text-right whitespace-pre">
<Label htmlFor="pkey" className="text-end whitespace-pre">
{t("add_system.key")}
</Label>
<Input readOnly id="pkey" value={publicKey} className="col-span-3" required></Input>
<div
className={
"h-6 w-24 bg-gradient-to-r from-transparent to-background to-65% absolute right-1 pointer-events-none"
"h-6 w-24 bg-gradient-to-r rtl:bg-gradient-to-l from-transparent to-background to-65% absolute end-1 pointer-events-none"
}
></div>
<TooltipProvider delayDuration={100}>
@@ -137,7 +136,7 @@ export function AddSystemButton({ className }: { className?: string }) {
<Button
type="button"
variant={"link"}
className="absolute right-0"
className="absolute end-0"
onClick={() => copyToClipboard(publicKey)}
>
<Copy className="h-4 w-4 " />
@@ -152,7 +151,7 @@ export function AddSystemButton({ className }: { className?: string }) {
</div>
{/* Docker */}
<TabsContent value="docker">
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ms-[20px]">
<Button type="button" variant={"ghost"} onClick={() => copyDockerCompose(port.current.value)}>
{t("copy")} docker compose
</Button>
@@ -161,7 +160,7 @@ export function AddSystemButton({ className }: { className?: string }) {
</TabsContent>
{/* Binary */}
<TabsContent value="binary">
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ml-[20px]">
<DialogFooter className="flex justify-end gap-2 sm:w-[calc(100%+20px)] sm:-ms-[20px]">
<Button type="button" variant={"ghost"} onClick={() => copyInstallCommand(port.current.value)}>
{t("copy")} linux {t("add_system.command")}
</Button>

View File

@@ -78,11 +78,11 @@ function TheContent({
<Tabs defaultValue="system">
<TabsList className="mb-1 -mt-0.5">
<TabsTrigger value="system">
<ServerIcon className="mr-2 h-3.5 w-3.5" />
<ServerIcon className="me-2 h-3.5 w-3.5" />
{system.name}
</TabsTrigger>
<TabsTrigger value="global">
<GlobeIcon className="mr-1.5 h-3.5 w-3.5" />
<GlobeIcon className="me-1.5 h-3.5 w-3.5" />
{t("all_systems")}
</TabsTrigger>
</TabsList>

View File

@@ -59,7 +59,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
setOpen(false)
}}
>
<Server className="mr-2 h-4 w-4" />
<Server className="me-2 h-4 w-4" />
<span>{system.name}</span>
<CommandShortcut>{system.host}</CommandShortcut>
</CommandItem>
@@ -76,7 +76,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
setOpen(false)
}}
>
<LayoutDashboard className="mr-2 h-4 w-4" />
<LayoutDashboard className="me-2 h-4 w-4" />
<span>{t("command.dashboard")}</span>
<CommandShortcut>{t("command.page")}</CommandShortcut>
</CommandItem>
@@ -86,7 +86,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
setOpen(false)
}}
>
<SettingsIcon className="mr-2 h-4 w-4" />
<SettingsIcon className="me-2 h-4 w-4" />
<span>{t("settings.settings")}</span>
<CommandShortcut>{t("settings.settings")}</CommandShortcut>
</CommandItem>
@@ -97,7 +97,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
setOpen(false)
}}
>
<MailIcon className="mr-2 h-4 w-4" />
<MailIcon className="me-2 h-4 w-4" />
<span>{t("settings.notifications.title")}</span>
<CommandShortcut>{t("settings.settings")}</CommandShortcut>
</CommandItem>
@@ -107,7 +107,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
window.location.href = "https://github.com/henrygd/beszel/blob/main/readme.md"
}}
>
<Github className="mr-2 h-4 w-4" />
<Github className="me-2 h-4 w-4" />
<span>{t("command.documentation")}</span>
<CommandShortcut>GitHub</CommandShortcut>
</CommandItem>
@@ -123,7 +123,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
window.open("/_/", "_blank")
}}
>
<UsersIcon className="mr-2 h-4 w-4" />
<UsersIcon className="me-2 h-4 w-4" />
<span>{t("user_dm.users")}</span>
<CommandShortcut>{t("command.admin")}</CommandShortcut>
</CommandItem>
@@ -133,7 +133,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
window.open("/_/#/logs", "_blank")
}}
>
<LogsIcon className="mr-2 h-4 w-4" />
<LogsIcon className="me-2 h-4 w-4" />
<span>{t("user_dm.logs")}</span>
<CommandShortcut>{t("command.admin")}</CommandShortcut>
</CommandItem>
@@ -143,7 +143,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
window.open("/_/#/settings/backups", "_blank")
}}
>
<DatabaseBackupIcon className="mr-2 h-4 w-4" />
<DatabaseBackupIcon className="me-2 h-4 w-4" />
<span>{t("user_dm.backups")}</span>
<CommandShortcut>{t("command.admin")}</CommandShortcut>
</CommandItem>
@@ -154,7 +154,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
window.open("/_/#/settings/auth-providers", "_blank")
}}
>
<LockKeyholeIcon className="mr-2 h-4 w-4" />
<LockKeyholeIcon className="me-2 h-4 w-4" />
<span>{t("user_dm.auth_providers")}</span>
<CommandShortcut>{t("command.admin")}</CommandShortcut>
</CommandItem>
@@ -165,7 +165,7 @@ export default function CommandPalette({ open, setOpen }: { open: boolean; setOp
window.open("/_/#/settings/mail", "_blank")
}}
>
<MailIcon className="mr-2 h-4 w-4" />
<MailIcon className="me-2 h-4 w-4" />
<span>{t("command.SMTP_settings")}</span>
<CommandShortcut>{t("command.admin")}</CommandShortcut>
</CommandItem>

View File

@@ -2,7 +2,16 @@ export function Logo({ className }: { className?: string }) {
return (
// Righteous
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 285 75" className={className}>
<path d="M146.4 73.1h-30.5V59.8h30.5a3.2 3.2 0 0 0 2.3-1 3.2 3.2 0 0 0 1-2.3q0-.8-.3-1.3a1.5 1.5 0 0 0-.7-.6 4.7 4.7 0 0 0-1-.3l-1.3-.1h-13.9q-3.4 0-6.5-1.3-3-1.3-5.2-3.6a16.9 16.9 0 0 1-3.6-5.3 16.3 16.3 0 0 1-1.3-6.5 16.4 16.4 0 0 1 1.3-6.4q1.3-3.1 3.6-5.4 2.2-2.2 5.2-3.5a16.3 16.3 0 0 1 6.5-1.3h27v13.3h-27a3.2 3.2 0 0 0-2.3 1 3.2 3.2 0 0 0-1 2.3 3.3 3.3 0 0 0 1 2.4 3.3 3.3 0 0 0 1.2.8 3.2 3.2 0 0 0 1.1.2h13.9a18.1 18.1 0 0 1 6 1 17.3 17.3 0 0 1 .4.2q3 1.1 5.3 3.2a15.1 15.1 0 0 1 3.6 4.9 14.7 14.7 0 0 1 1.3 5.4 17.2 17.2 0 0 1 0 .9 16 16 0 0 1-1 5.8 15.4 15.4 0 0 1-.3.7 17.3 17.3 0 0 1-3.6 5.2 16.4 16.4 0 0 1-5.3 3.6 16.2 16.2 0 0 1-6.4 1.3Zm64.5-13.3v13.3h-43.6l22-39h-22V21h43.6l-22 39h22ZM35 73.1H0v-70h35q4.4 0 8.2 1.6a21.4 21.4 0 0 1 6.6 4.6q2.9 2.8 4.5 6.6 1.7 3.8 1.7 8.2a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5 1.4 1.6 2.4 3.5a18.3 18.3 0 0 1 1.5 4A17.4 17.4 0 0 1 56 51a15.3 15.3 0 0 1 0 1.1q0 4.3-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.5-3.8 1.7-8.2 1.7Zm76-43L86 60.4l1.5.3a16.7 16.7 0 0 0 1.6 0q2 0 3.8-.4 1.8-.6 3.4-1.6 1.6-1 2.8-2.4a12.8 12.8 0 0 0 2-3.2l9.8 9.8q-1.9 2.6-4.3 4.7a27 27 0 0 1-5.2 3.6 26.1 26.1 0 0 1-6 2.2 26.8 26.8 0 0 1-6.3.8 26.4 26.4 0 0 1-10.4-2 26.2 26.2 0 0 1-8.5-5.8 26.7 26.7 0 0 1-5.5-8.3 30.4 30.4 0 0 1-.2-.4q-2.1-5-2.1-11.1a31.9 31.9 0 0 1 .7-7 27 27 0 0 1 1.4-4.3 27 27 0 0 1 3.8-6.6 24.5 24.5 0 0 1 2-2.2 26 26 0 0 1 8.4-5.6 27 27 0 0 1 10.4-2 26.3 26.3 0 0 1 6.4.8 26.9 26.9 0 0 1 6 2.2q2.7 1.5 5.2 3.6 2.4 2.1 4.3 4.8Zm152.3 0-25 30.2 1.5.3a16.7 16.7 0 0 0 1.6 0q2 0 3.8-.4 1.8-.6 3.4-1.6 1.5-1 2.8-2.4a12.8 12.8 0 0 0 2-3.2l9.8 9.8q-1.9 2.6-4.3 4.7a27 27 0 0 1-5.2 3.6 26.1 26.1 0 0 1-6 2.2 26.8 26.8 0 0 1-6.3.8 26.4 26.4 0 0 1-10.4-2 26.2 26.2 0 0 1-8.5-5.8A26.7 26.7 0 0 1 217 58a30.4 30.4 0 0 1-.2-.4q-2.1-5-2.1-11.1a31.9 31.9 0 0 1 .7-7 27 27 0 0 1 1.4-4.3 27 27 0 0 1 3.8-6.6 24.5 24.5 0 0 1 2-2.2 26 26 0 0 1 8.4-5.6 27 27 0 0 1 10.4-2 26.3 26.3 0 0 1 6.4.8 26.9 26.9 0 0 1 6 2.2q2.7 1.5 5.2 3.6 2.4 2.1 4.3 4.8ZM283.4 0v73.1H270V0h13.4ZM14 17v14.1h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 30 40 29a6.9 6.9 0 0 0 1.5-2.3q.5-1.3.5-2.7a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.5q-.6-1.2-1.5-2.2a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.5 7.9 7.9 0 0 0-.2 0H14Zm0 28.1v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.2Q39 58 40 57.1a7 7 0 0 0 1.5-2.3 6.9 6.9 0 0 0 .5-2.5 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 48 40 47a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm63.3 8.3 15.5-20.6a8 8 0 0 0-1.4-.4 7 7 0 0 0-.4 0 17.2 17.2 0 0 0-1.6-.1 19.2 19.2 0 0 0-.3 0 13.3 13.3 0 0 0-5.1 1q-2.5 1-4.2 2.8a13.1 13.1 0 0 0-2.5 3.6 15.5 15.5 0 0 0-.3.9 14.7 14.7 0 0 0-1 3.5 18.7 18.7 0 0 0 0 2.4 17.6 17.6 0 0 0 0 .7v.8a29.4 29.4 0 0 0 0 .1 19.2 19.2 0 0 0 .2 2 20.2 20.2 0 0 0 .4 1.6 18.6 18.6 0 0 0 0 .2 7.5 7.5 0 0 0 .4.9 6 6 0 0 0 .3.6Zm152.3 0L245 32.8a8 8 0 0 0-1.4-.4 7 7 0 0 0-.4 0 17.2 17.2 0 0 0-1.6-.1 19.2 19.2 0 0 0-.3 0 13.3 13.3 0 0 0-5.1 1q-2.5 1-4.2 2.8a13.1 13.1 0 0 0-2.5 3.6 15.5 15.5 0 0 0-.4.9 14.7 14.7 0 0 0-.8 3.5 18.7 18.7 0 0 0-.2 2.4 17.6 17.6 0 0 0 0 .7v.8a29.4 29.4 0 0 0 .1.1 19.2 19.2 0 0 0 .2 2 20.2 20.2 0 0 0 .4 1.6 18.6 18.6 0 0 0 0 .2 7.5 7.5 0 0 0 .4.9 6 6 0 0 0 .3.6Z" />
{/* <defs>
<linearGradient id="gradient" x1="0%" y1="20%" x2="100%" y2="120%">
<stop offset="0%" style={{ stopColor: "#747bff" }} />
<stop offset="100%" style={{ stopColor: "#24eb5c" }} />
</linearGradient>
</defs> */}
<path
// fill="url(#gradient)"
d="M146.4 73.1h-30.5V59.8h30.5a3.2 3.2 0 0 0 2.3-1 3.2 3.2 0 0 0 1-2.3q0-.8-.3-1.3a1.5 1.5 0 0 0-.7-.6 4.7 4.7 0 0 0-1-.3l-1.3-.1h-13.9q-3.4 0-6.5-1.3-3-1.3-5.2-3.6a16.9 16.9 0 0 1-3.6-5.3 16.3 16.3 0 0 1-1.3-6.5 16.4 16.4 0 0 1 1.3-6.4q1.3-3.1 3.6-5.4 2.2-2.2 5.2-3.5a16.3 16.3 0 0 1 6.5-1.3h27v13.3h-27a3.2 3.2 0 0 0-2.3 1 3.2 3.2 0 0 0-1 2.3 3.3 3.3 0 0 0 1 2.4 3.3 3.3 0 0 0 1.2.8 3.2 3.2 0 0 0 1.1.2h13.9a18.1 18.1 0 0 1 6 1 17.3 17.3 0 0 1 .4.2q3 1.1 5.3 3.2a15.1 15.1 0 0 1 3.6 4.9 14.7 14.7 0 0 1 1.3 5.4 17.2 17.2 0 0 1 0 .9 16 16 0 0 1-1 5.8 15.4 15.4 0 0 1-.3.7 17.3 17.3 0 0 1-3.6 5.2 16.4 16.4 0 0 1-5.3 3.6 16.2 16.2 0 0 1-6.4 1.3Zm64.5-13.3v13.3h-43.6l22-39h-22V21h43.6l-22 39h22ZM35 73.1H0v-70h35q4.4 0 8.2 1.6a21.4 21.4 0 0 1 6.6 4.6q2.9 2.8 4.5 6.6 1.7 3.8 1.7 8.2a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5 1.4 1.6 2.4 3.5a18.3 18.3 0 0 1 1.5 4A17.4 17.4 0 0 1 56 51a15.3 15.3 0 0 1 0 1.1q0 4.3-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.5-3.8 1.7-8.2 1.7Zm76-43L86 60.4l1.5.3a16.7 16.7 0 0 0 1.6 0q2 0 3.8-.4 1.8-.6 3.4-1.6 1.6-1 2.8-2.4a12.8 12.8 0 0 0 2-3.2l9.8 9.8q-1.9 2.6-4.3 4.7a27 27 0 0 1-5.2 3.6 26.1 26.1 0 0 1-6 2.2 26.8 26.8 0 0 1-6.3.8 26.4 26.4 0 0 1-10.4-2 26.2 26.2 0 0 1-8.5-5.8 26.7 26.7 0 0 1-5.5-8.3 30.4 30.4 0 0 1-.2-.4q-2.1-5-2.1-11.1a31.9 31.9 0 0 1 .7-7 27 27 0 0 1 1.4-4.3 27 27 0 0 1 3.8-6.6 24.5 24.5 0 0 1 2-2.2 26 26 0 0 1 8.4-5.6 27 27 0 0 1 10.4-2 26.3 26.3 0 0 1 6.4.8 26.9 26.9 0 0 1 6 2.2q2.7 1.5 5.2 3.6 2.4 2.1 4.3 4.8Zm152.3 0-25 30.2 1.5.3a16.7 16.7 0 0 0 1.6 0q2 0 3.8-.4 1.8-.6 3.4-1.6 1.5-1 2.8-2.4a12.8 12.8 0 0 0 2-3.2l9.8 9.8q-1.9 2.6-4.3 4.7a27 27 0 0 1-5.2 3.6 26.1 26.1 0 0 1-6 2.2 26.8 26.8 0 0 1-6.3.8 26.4 26.4 0 0 1-10.4-2 26.2 26.2 0 0 1-8.5-5.8A26.7 26.7 0 0 1 217 58a30.4 30.4 0 0 1-.2-.4q-2.1-5-2.1-11.1a31.9 31.9 0 0 1 .7-7 27 27 0 0 1 1.4-4.3 27 27 0 0 1 3.8-6.6 24.5 24.5 0 0 1 2-2.2 26 26 0 0 1 8.4-5.6 27 27 0 0 1 10.4-2 26.3 26.3 0 0 1 6.4.8 26.9 26.9 0 0 1 6 2.2q2.7 1.5 5.2 3.6 2.4 2.1 4.3 4.8ZM283.4 0v73.1H270V0h13.4ZM14 17v14.1h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 30 40 29a6.9 6.9 0 0 0 1.5-2.3q.5-1.3.5-2.7a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.5q-.6-1.2-1.5-2.2a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.5 7.9 7.9 0 0 0-.2 0H14Zm0 28.1v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.2Q39 58 40 57.1a7 7 0 0 0 1.5-2.3 6.9 6.9 0 0 0 .5-2.5 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 48 40 47a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm63.3 8.3 15.5-20.6a8 8 0 0 0-1.4-.4 7 7 0 0 0-.4 0 17.2 17.2 0 0 0-1.6-.1 19.2 19.2 0 0 0-.3 0 13.3 13.3 0 0 0-5.1 1q-2.5 1-4.2 2.8a13.1 13.1 0 0 0-2.5 3.6 15.5 15.5 0 0 0-.3.9 14.7 14.7 0 0 0-1 3.5 18.7 18.7 0 0 0 0 2.4 17.6 17.6 0 0 0 0 .7v.8a29.4 29.4 0 0 0 0 .1 19.2 19.2 0 0 0 .2 2 20.2 20.2 0 0 0 .4 1.6 18.6 18.6 0 0 0 0 .2 7.5 7.5 0 0 0 .4.9 6 6 0 0 0 .3.6Zm152.3 0L245 32.8a8 8 0 0 0-1.4-.4 7 7 0 0 0-.4 0 17.2 17.2 0 0 0-1.6-.1 19.2 19.2 0 0 0-.3 0 13.3 13.3 0 0 0-5.1 1q-2.5 1-4.2 2.8a13.1 13.1 0 0 0-2.5 3.6 15.5 15.5 0 0 0-.4.9 14.7 14.7 0 0 0-.8 3.5 18.7 18.7 0 0 0-.2 2.4 17.6 17.6 0 0 0 0 .7v.8a29.4 29.4 0 0 0 .1.1 19.2 19.2 0 0 0 .2 2 20.2 20.2 0 0 0 .4 1.6 18.6 18.6 0 0 0 0 .2 7.5 7.5 0 0 0 .4.9 6 6 0 0 0 .3.6Z"
/>
</svg>
)
}

View File

@@ -38,13 +38,12 @@ export default function Navbar() {
const { t } = useTranslation()
return (
<div className="flex items-center h-14 md:h-16 bg-card px-4 pr-3 sm:px-6 border bt-0 rounded-md my-4">
<Link href="/" aria-label="Home" className={"p-2 pl-0"}>
<Logo className="h-[1.15rem] md:h-[1.25em] fill-foreground" />
<Link href="/" aria-label="Home" className="p-2 pl-0 me-3">
<Logo className="h-[1.1rem] md:h-5 fill-foreground" />
</Link>
<SearchButton t={t} />
<div className={"flex ml-auto items-center"}>
<div className="flex items-center ms-auto">
<LangToggle />
<ModeToggle />
<Link
@@ -106,7 +105,7 @@ export default function Navbar() {
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AddSystemButton className="ml-2" />
<AddSystemButton className="ms-2" />
</div>
</div>
)
@@ -125,14 +124,14 @@ function SearchButton({ t }: { t: TFunction<"translation", undefined> }) {
<>
<Button
variant="outline"
className="hidden md:block text-sm text-muted-foreground px-4 mx-3"
className="hidden md:block text-sm text-muted-foreground px-4"
onClick={() => setOpen(true)}
>
<span className="flex items-center">
<SearchIcon className="mr-1.5 h-4 w-4" />
<SearchIcon className="me-1.5 h-4 w-4" />
{t("search")}
<span className="sr-only">{t("search")}</span>
<span className="flex items-center ml-3.5">
<span className="flex items-center ms-3.5">
<Kbd>{isMac ? "⌘" : "Ctrl"}</Kbd>
<Kbd>K</Kbd>
</span>

View File

@@ -110,7 +110,7 @@ export default function () {
<Input
placeholder={t("filter")}
onChange={(e) => setFilter(e.target.value)}
className="w-full md:w-56 lg:w-72 ml-auto px-4"
className="w-full md:w-56 lg:w-72 ms-auto px-4"
/>
</div>
</CardHeader>
@@ -122,7 +122,7 @@ export default function () {
</Card>
{hubVersion && (
<div className="flex gap-1.5 justify-end items-center pr-3 sm:pr-6 mt-3.5 text-xs opacity-80">
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 text-xs opacity-80">
<a
href="https://github.com/henrygd/beszel"
target="_blank"

View File

@@ -66,6 +66,7 @@ export default function ConfigYaml() {
</div>
{configContent && (
<Textarea
dir="ltr"
autoFocus
defaultValue={configContent}
spellCheck="false"

View File

@@ -78,8 +78,12 @@ function CellFormatter(info: CellContext<SystemRecord, unknown>) {
function sortableHeader(column: Column<SystemRecord, unknown>, name: string, Icon: any, hideSortIcon = false) {
return (
<Button variant="ghost" className="h-9 px-3" onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}>
<Icon className="mr-2 h-4 w-4" />
<Button
variant="ghost"
className="h-9 px-3 rtl:ml-auto flex"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
<Icon className="me-2 h-4 w-4" />
{name}
{!hideSortIcon && <ArrowUpDownIcon className="ml-2 h-4 w-4" />}
</Button>
@@ -110,7 +114,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
cell: (info) => {
const { status } = info.row.original
return (
<span className="flex gap-0.5 items-center text-base md:pr-5">
<span className="flex gap-0.5 items-center text-base md:pe-5">
<span
className={cn("w-2 h-2 left-0 rounded-full", {
"bg-green-500": status === "up",
@@ -176,7 +180,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
return null
}
return (
<span className="flex gap-2 items-center md:pr-5 tabular-nums pl-1">
<span className="flex gap-2 items-center md:pe-5 tabular-nums ps-1">
<span
className={cn("w-2 h-2 left-0 rounded-full", version === hubVersion ? "bg-green-500" : "bg-yellow-500")}
style={{ marginBottom: "-1px" }}

View File

@@ -44,12 +44,12 @@ const AlertDialogContent = React.forwardRef<
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
<div className={cn("flex flex-col space-y-2 text-center sm:text-start", className)} {...props} />
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-2", className)} {...props} />
)
AlertDialogFooter.displayName = "AlertDialogFooter"

View File

@@ -40,7 +40,7 @@ const CommandInput = React.forwardRef<
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<Search className="me-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
@@ -115,7 +115,7 @@ const CommandItem = React.forwardRef<
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span className={cn("ml-auto text-xs tracking-wide text-muted-foreground", className)} {...props} />
return <span className={cn("ms-auto text-xs tracking-wide text-muted-foreground", className)} {...props} />
}
CommandShortcut.displayName = "CommandShortcut"

View File

@@ -42,7 +42,7 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<DialogPrimitive.Close className="absolute end-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
@@ -52,12 +52,12 @@ const DialogContent = React.forwardRef<
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-start", className)} {...props} />
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} {...props} />
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-3.5", className)} {...props} />
)
DialogFooter.displayName = "DialogFooter"

View File

@@ -17,7 +17,7 @@ const Switch = React.forwardRef<
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 rtl:data-[state=checked]:-translate-x-5 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>

View File

@@ -46,7 +46,7 @@ const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
"h-12 px-4 text-start align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}

View File

@@ -1,6 +1,7 @@
import i18n from "i18next"
import { initReactI18next } from "react-i18next"
import enTranslations from "../locales/en/translation.json"
import { $direction } from "./stores"
// Custom language detector to use localStorage
const languageDetector: any = {
@@ -62,6 +63,9 @@ export async function setLang(locale: string) {
const messages = await loadMessages(locale)
i18n.addResourceBundle(locale, "translation", messages)
await i18n.changeLanguage(locale)
const dir = i18n.dir(locale)
document.dir = dir
$direction.set(dir)
}
// Initialize with detected/saved language

View File

@@ -1,4 +1,8 @@
[
{
"lang": "ar",
"label": "العربية"
},
{
"lang": "de",
"label": "Deutsch"

View File

@@ -39,3 +39,6 @@ export const $containerFilter = atom("")
/** Fallback copy to clipboard dialog content */
export const $copyContent = atom("")
/** Direction for localization */
export const $direction = atom<"ltr" | "rtl">("ltr")

View File

@@ -8,30 +8,30 @@
"systems": "الأنظمة",
"cancel": "إلغاء",
"continue": "متابعة",
"minutes_zero": "",
"minutes_zero": "٠ دقائق",
"minutes_one": "{{count}} دقيقة",
"minutes_two": "",
"minutes_few": "",
"minutes_many": "",
"minutes_two": "{{count}} دقيقتان",
"minutes_few": "{{count}} دقائق",
"minutes_many": "{{count}} دقيقة",
"minutes_other": "{{count}} دقائق",
"hours_zero": "",
"hours_zero": "٠ ساعات",
"hours_one": "{{count}} ساعة",
"hours_two": "",
"hours_few": "",
"hours_many": "",
"hours_two": "{{count}} ساعتان",
"hours_few": "{{count}} ساعات",
"hours_many": "{{count}} ساعة",
"hours_other": "{{count}} ساعات",
"days_zero": "",
"days_one": "",
"days_two": "",
"days_few": "",
"days_many": "",
"days_zero": "٠ أيام",
"days_one": "{{count}} يوم",
"days_two": "{{count}} يومان",
"days_few": "{{count}} أيام",
"days_many": "{{count}} يومًا",
"days_other": "{{count}} أيام",
"weeks_zero": "",
"weeks_zero": "٠ أسابيع",
"weeks_one": "{{count}} أسبوع",
"weeks_two": "",
"weeks_few": "",
"weeks_many": "",
"weeks_other": "",
"weeks_two": "{{count}} أسبوعان",
"weeks_few": "{{count}} أسابيع",
"weeks_many": "{{count}} أسبوعًا",
"weeks_other": "{{count}} أسابيع",
"home": {
"active_alerts": "التنبيهات النشطة",
"active_des": "يتجاوز {{value}}{{unit}} في آخر ",
@@ -71,8 +71,8 @@
"disk_usage_des": "يتم التشغيل عندما يتجاوز استخدام أي قرص الحد.",
"bandwidth": "النطاق الترددي",
"bandwidth_des": "يتم التشغيل عندما يتجاوز استخدام الشبكة الحد.",
"temperature": "",
"temperature_des": ""
"temperature": "درجة الحرارة",
"temperature_des": "يتم التشغيل عندما يتجاوز أي مستشعر الحد المسموح به."
},
"average_exceeds": "المتوسط يتجاوز",
"for": "لمدة"

View File

@@ -4,7 +4,8 @@ import { Suspense, lazy, useEffect } from "react"
import ReactDOM from "react-dom/client"
import Home from "./components/routes/home.tsx"
import { ThemeProvider } from "./components/theme-provider.tsx"
import { $authenticated, $systems, pb, $publicKey, $hubVersion, $copyContent } from "./lib/stores.ts"
import { DirectionProvider } from "@radix-ui/react-direction"
import { $authenticated, $systems, pb, $publicKey, $hubVersion, $copyContent, $direction } from "./lib/stores.ts"
import { updateUserSettings, updateAlerts, updateFavicon, updateSystemList } from "./lib/utils.ts"
import { useStore } from "@nanostores/react"
import { Toaster } from "./components/ui/toaster.tsx"
@@ -77,16 +78,15 @@ const App = () => {
const Layout = () => {
const authenticated = useStore($authenticated)
const copyContent = useStore($copyContent)
const direction = useStore($direction)
if (!authenticated) {
return (
<DirectionProvider dir={direction}>
{!authenticated ? (
<Suspense>
<LoginPage />
</Suspense>
)
}
return (
) : (
<>
<div className="container">
<Navbar />
@@ -100,6 +100,8 @@ const Layout = () => {
)}
</div>
</>
)}
</DirectionProvider>
)
}

View File

@@ -92,6 +92,7 @@ module.exports = {
},
plugins: [
require("tailwindcss-animate"),
require("tailwindcss-rtl"),
function ({ addVariant }) {
addVariant("light", ".light &")
},