mirror of
https://github.com/MizuchiLabs/mantrae.git
synced 2026-04-30 04:59:59 -05:00
fix reactive table error
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/MizuchiLabs/mantrae/internal/db"
|
||||
"github.com/MizuchiLabs/mantrae/internal/source"
|
||||
"github.com/MizuchiLabs/mantrae/internal/traefik"
|
||||
"github.com/MizuchiLabs/mantrae/internal/util"
|
||||
)
|
||||
|
||||
@@ -45,19 +46,19 @@ func GetProfile(DB *sql.DB) http.HandlerFunc {
|
||||
func CreateProfile(DB *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := db.New(DB)
|
||||
var profile db.CreateProfileParams
|
||||
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
|
||||
var params db.CreateProfileParams
|
||||
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
profileID, err := q.CreateProfile(r.Context(), profile)
|
||||
profileID, err := q.CreateProfile(r.Context(), params)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Create default local config
|
||||
if err := q.UpsertTraefikConfig(r.Context(), db.UpsertTraefikConfigParams{
|
||||
if err = q.UpsertTraefikConfig(r.Context(), db.UpsertTraefikConfigParams{
|
||||
ProfileID: profileID,
|
||||
Source: source.Local,
|
||||
}); err != nil {
|
||||
@@ -65,6 +66,12 @@ func CreateProfile(DB *sql.DB) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := q.GetProfile(r.Context(), profileID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
go traefik.UpdateTraefikAPI(DB, profile)
|
||||
util.Broadcast <- util.EventMessage{
|
||||
Type: util.EventTypeCreate,
|
||||
Message: "profile",
|
||||
@@ -76,15 +83,22 @@ func CreateProfile(DB *sql.DB) http.HandlerFunc {
|
||||
func UpdateProfile(DB *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := db.New(DB)
|
||||
var profile db.UpdateProfileParams
|
||||
if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
|
||||
var params db.UpdateProfileParams
|
||||
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if err := q.UpdateProfile(r.Context(), profile); err != nil {
|
||||
if err := q.UpdateProfile(r.Context(), params); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
profile, err := q.GetProfile(r.Context(), params.ID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
go traefik.UpdateTraefikAPI(DB, profile)
|
||||
util.Broadcast <- util.EventMessage{
|
||||
Type: util.EventTypeUpdate,
|
||||
Message: "profile",
|
||||
|
||||
+82
-60
@@ -23,6 +23,86 @@ const (
|
||||
VersionAPI = "/api/version"
|
||||
)
|
||||
|
||||
func UpdateTraefikAPI(DB *sql.DB, profile db.Profile) error {
|
||||
rawResponse, err := fetch(profile, RawAPI)
|
||||
if err != nil {
|
||||
slog.Error("Failed to fetch raw data", "error", err)
|
||||
|
||||
// Clear api data
|
||||
if err = ClearTraefikAPI(DB, profile.ID); err != nil {
|
||||
slog.Error("Failed to update api data", "error", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer rawResponse.Close()
|
||||
|
||||
var config db.TraefikConfiguration
|
||||
if err = json.NewDecoder(rawResponse).Decode(&config); err != nil {
|
||||
return fmt.Errorf("failed to decode raw data: %w", err)
|
||||
}
|
||||
|
||||
epResponse, err := fetch(profile, EntrypointsAPI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch %s: %w", profile.Url+EntrypointsAPI, err)
|
||||
}
|
||||
defer epResponse.Close()
|
||||
|
||||
var entrypoints db.TraefikEntryPoints
|
||||
if err = json.NewDecoder(epResponse).Decode(&entrypoints); err != nil {
|
||||
return fmt.Errorf("failed to decode entrypoints: %w", err)
|
||||
}
|
||||
oResponse, err := fetch(profile, OverviewAPI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch %s: %w", profile.Url+OverviewAPI, err)
|
||||
}
|
||||
defer epResponse.Close()
|
||||
|
||||
var overview db.TraefikOverview
|
||||
if err = json.NewDecoder(oResponse).Decode(&overview); err != nil {
|
||||
return fmt.Errorf("failed to decode overview: %w", err)
|
||||
}
|
||||
|
||||
vResponse, err := fetch(profile, VersionAPI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch %s: %w", profile.Url+VersionAPI, err)
|
||||
}
|
||||
defer vResponse.Close()
|
||||
|
||||
var version db.TraefikVersion
|
||||
if err := json.NewDecoder(vResponse).Decode(&version); err != nil {
|
||||
return fmt.Errorf("failed to decode version: %w", err)
|
||||
}
|
||||
|
||||
q := db.New(DB)
|
||||
if err := q.UpsertTraefikConfig(context.Background(), db.UpsertTraefikConfigParams{
|
||||
ProfileID: profile.ID,
|
||||
Entrypoints: &entrypoints,
|
||||
Overview: &overview,
|
||||
Version: &version.Version,
|
||||
Config: &config,
|
||||
Source: source.API,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to update api data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ClearTraefikAPI(DB *sql.DB, profileID int64) error {
|
||||
q := db.New(DB)
|
||||
if err := q.UpsertTraefikConfig(context.Background(), db.UpsertTraefikConfigParams{
|
||||
ProfileID: profileID,
|
||||
Source: source.API,
|
||||
Entrypoints: nil,
|
||||
Overview: nil,
|
||||
Version: nil,
|
||||
Config: nil,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to update api data: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetTraefikConfig(DB *sql.DB) {
|
||||
q := db.New(DB)
|
||||
profiles, err := q.ListProfiles(context.Background())
|
||||
@@ -36,66 +116,8 @@ func GetTraefikConfig(DB *sql.DB) {
|
||||
continue
|
||||
}
|
||||
|
||||
rawResponse, err := fetch(profile, RawAPI)
|
||||
if err != nil {
|
||||
slog.Error("Failed to fetch raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
defer rawResponse.Close()
|
||||
|
||||
var config db.TraefikConfiguration
|
||||
if err := json.NewDecoder(rawResponse).Decode(&config); err != nil {
|
||||
slog.Error("Failed to decode raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
epResponse, err := fetch(profile, EntrypointsAPI)
|
||||
if err != nil {
|
||||
slog.Error("Failed to fetch raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
defer epResponse.Close()
|
||||
|
||||
var entrypoints db.TraefikEntryPoints
|
||||
if err := json.NewDecoder(epResponse).Decode(&entrypoints); err != nil {
|
||||
slog.Error("Failed to decode raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
oResponse, err := fetch(profile, OverviewAPI)
|
||||
if err != nil {
|
||||
slog.Error("Failed to fetch raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
defer epResponse.Close()
|
||||
|
||||
var overview db.TraefikOverview
|
||||
if err := json.NewDecoder(oResponse).Decode(&overview); err != nil {
|
||||
slog.Error("Failed to decode raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
vResponse, err := fetch(profile, VersionAPI)
|
||||
if err != nil {
|
||||
slog.Error("Failed to fetch raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
defer vResponse.Close()
|
||||
|
||||
var version db.TraefikVersion
|
||||
if err := json.NewDecoder(vResponse).Decode(&version); err != nil {
|
||||
slog.Error("Failed to decode raw data", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := q.UpsertTraefikConfig(context.Background(), db.UpsertTraefikConfigParams{
|
||||
ProfileID: profile.ID,
|
||||
Entrypoints: &entrypoints,
|
||||
Overview: &overview,
|
||||
Version: &version.Version,
|
||||
Config: &config,
|
||||
Source: source.API,
|
||||
}); err != nil {
|
||||
slog.Error("Failed to update Traefik config", "error", err)
|
||||
if err := UpdateTraefikAPI(DB, profile); err != nil {
|
||||
slog.Error("Failed to update api data", "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
+10
-5
@@ -214,8 +214,11 @@ export const api = {
|
||||
|
||||
// Profiles ------------------------------------------------------------------
|
||||
async listProfiles() {
|
||||
const data = await send('/profile');
|
||||
const data: Profile[] = await send('/profile');
|
||||
profiles.set(data);
|
||||
if (profile.isValid()) {
|
||||
profile.value = data.find((item) => item.id === profile.value?.id) ?? data[0];
|
||||
}
|
||||
},
|
||||
|
||||
async getProfile(id: number) {
|
||||
@@ -230,12 +233,15 @@ export const api = {
|
||||
await api.listProfiles(); // Refresh the list
|
||||
},
|
||||
|
||||
async updateProfile(profile: Profile) {
|
||||
async updateProfile(p: Profile) {
|
||||
await send('/profile', {
|
||||
method: 'PUT',
|
||||
body: profile
|
||||
body: p
|
||||
});
|
||||
await api.listProfiles(); // Refresh the list
|
||||
if (p.id === profile.id) {
|
||||
await api.getTraefikConfig(TraefikSource.API);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteProfile(id: number) {
|
||||
@@ -607,9 +613,8 @@ async function fetchTraefikConfig(src: TraefikSource) {
|
||||
toast.error('No valid profile selected');
|
||||
return;
|
||||
}
|
||||
source.value = src;
|
||||
|
||||
const res = await send(`/traefik/${profile.id}/${source.value}`);
|
||||
const res = await send(`/traefik/${profile.id}/${src}`);
|
||||
if (!res) {
|
||||
// Reset stores
|
||||
traefik.set([]);
|
||||
|
||||
@@ -93,13 +93,13 @@
|
||||
<div class="grid grid-cols-4 items-center gap-2 text-sm">
|
||||
<span class="col-span-1">Features</span>
|
||||
<div class="col-span-3 space-x-2">
|
||||
{#if $overview?.features.tracing}
|
||||
{#if $overview?.features?.tracing}
|
||||
<Badge variant="secondary">Tracing</Badge>
|
||||
{/if}
|
||||
{#if $overview?.features.metrics}
|
||||
{#if $overview?.features?.metrics}
|
||||
<Badge variant="secondary">Metrics</Badge>
|
||||
{/if}
|
||||
{#if $overview?.features.accessLog}
|
||||
{#if $overview?.features?.accessLog}
|
||||
<Badge variant="secondary">Access Log</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -126,13 +126,13 @@
|
||||
<span class="col-span-1 font-mono">HTTP</span>
|
||||
<div class="col-span-3 space-x-2">
|
||||
<Badge variant="secondary">
|
||||
Routers: {$overview?.http.routers.total ?? 0}
|
||||
Routers: {$overview?.http?.routers.total ?? 0}
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
Services: {$overview?.http.services.total ?? 0}
|
||||
Services: {$overview?.http?.services.total ?? 0}
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
Middlewares: {$overview?.http.middlewares.total ?? 0}
|
||||
Middlewares: {$overview?.http?.middlewares.total ?? 0}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,13 +142,13 @@
|
||||
<span class="col-span-1 font-mono">TCP</span>
|
||||
<div class="col-span-3 space-x-2">
|
||||
<Badge variant="secondary">
|
||||
Routers: {$overview?.tcp.routers.total ?? 0}
|
||||
Routers: {$overview?.tcp?.routers.total ?? 0}
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
Services: {$overview?.tcp.services.total ?? 0}
|
||||
Services: {$overview?.tcp?.services.total ?? 0}
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
Middlewares: {$overview?.tcp.middlewares.total ?? 0}
|
||||
Middlewares: {$overview?.tcp?.middlewares.total ?? 0}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,10 +158,10 @@
|
||||
<span class="col-span-1 font-mono">UDP</span>
|
||||
<div class="col-span-3 space-x-2">
|
||||
<Badge variant="secondary">
|
||||
Routers: {$overview?.udp.routers.total ?? 0}
|
||||
Routers: {$overview?.udp?.routers.total ?? 0}
|
||||
</Badge>
|
||||
<Badge variant="secondary">
|
||||
Services: {$overview?.udp.services.total ?? 0}
|
||||
Services: {$overview?.udp?.services.total ?? 0}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
},
|
||||
...columns
|
||||
],
|
||||
autoResetAll: true,
|
||||
filterFns: {
|
||||
fuzzy: (row, columnId, value, addMeta) => {
|
||||
const itemRank = rankItem(row.getValue(columnId), value);
|
||||
@@ -182,6 +183,12 @@
|
||||
async function handleTabChange(value: string) {
|
||||
if (!source.isValid(value)) return;
|
||||
source.value = value;
|
||||
// Reset table state
|
||||
table.resetRowSelection();
|
||||
table.resetColumnFilters();
|
||||
table.resetGlobalFilter();
|
||||
table.resetColumnOrder();
|
||||
table.resetPagination();
|
||||
await api.getTraefikConfig(source.value);
|
||||
}
|
||||
function handleLimitChange(value: string) {
|
||||
@@ -248,8 +255,75 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="rounded-md border">
|
||||
{#key source.value + JSON.stringify(data)}
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<div class="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="-ml-3 h-8 data-[sortable=false]:cursor-default"
|
||||
data-sortable={header.column.getCanSort()}
|
||||
onclick={() => header.column.toggleSorting()}
|
||||
>
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{#if header.column.getCanSort()}
|
||||
{#if header.column.getIsSorted() === 'asc'}
|
||||
<ArrowDown />
|
||||
{:else if header.column.getIsSorted() === 'desc'}
|
||||
<ArrowUp />
|
||||
{/if}
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
class={getRowClassName ? getRowClassName(row.original) : ''}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">No results.</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
<Table.Footer>
|
||||
<Table.Row class="border-t">
|
||||
<Table.Cell colspan={columns.length}>Total</Table.Cell>
|
||||
<Table.Cell class="mr-4 text-right"
|
||||
>{table.getPrePaginationRowModel().rows.length}</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
</Table.Footer>
|
||||
</Table.Root>
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
{#if table.getSelectedRowModel().rows.length > 0}
|
||||
<div class="my-2 flex items-center gap-2 rounded-md bg-muted/50 p-2">
|
||||
<div class="my-2 flex items-center gap-2 rounded-lg border bg-muted/50 p-2">
|
||||
<span class="text-sm text-muted-foreground">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{' '}
|
||||
{table.getFilteredRowModel().rows.length} item(s) selected.
|
||||
@@ -258,78 +332,13 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Table -->
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<div class="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="-ml-3 h-8 data-[sortable=false]:cursor-default"
|
||||
data-sortable={header.column.getCanSort()}
|
||||
onclick={() => header.column.toggleSorting()}
|
||||
>
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{#if header.column.getCanSort()}
|
||||
{#if header.column.getIsSorted() === 'asc'}
|
||||
<ArrowDown />
|
||||
{:else if header.column.getIsSorted() === 'desc'}
|
||||
<ArrowUp />
|
||||
{/if}
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
class={getRowClassName ? getRowClassName(row.original) : ''}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">No results.</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
<Table.Footer>
|
||||
<Table.Row class="border-t">
|
||||
<Table.Cell colspan={columns.length}>Total</Table.Cell>
|
||||
<Table.Cell class="mr-4 text-right"
|
||||
>{table.getPrePaginationRowModel().rows.length}</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
</Table.Footer>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="flex items-center justify-between py-4">
|
||||
<div>
|
||||
<Select.Root
|
||||
type="single"
|
||||
allowDeselect={false}
|
||||
bind:value={limit.value}
|
||||
value={limit.value ?? pagination.pageSize.toString()}
|
||||
onValueChange={handleLimitChange}
|
||||
>
|
||||
<Select.Trigger class="w-[180px]">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import ColumnBadge from '$lib/components/tables/ColumnBadge.svelte';
|
||||
import DataTable from '$lib/components/tables/DataTable.svelte';
|
||||
import MiddlewareModal from '$lib/components/modals/middleware.svelte';
|
||||
@@ -158,45 +157,25 @@
|
||||
<title>Middlewares</title>
|
||||
</svelte:head>
|
||||
|
||||
<Tabs.Root value={source.value}>
|
||||
<Tabs.Content value={TraefikSource.LOCAL}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Layers />
|
||||
<h1 class="text-2xl font-bold">Middleware Management</h1>
|
||||
</div>
|
||||
<DataTable
|
||||
{columns}
|
||||
data={$middlewares || []}
|
||||
showSourceTabs={true}
|
||||
createButton={{
|
||||
label: 'Add Middleware',
|
||||
onClick: openCreateModal
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value={TraefikSource.API}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Layers />
|
||||
<h1 class="text-2xl font-bold">Middleware Management</h1>
|
||||
</div>
|
||||
|
||||
<DataTable {columns} data={$middlewares || []} showSourceTabs={true} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value={TraefikSource.AGENT}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Layers />
|
||||
<h1 class="text-2xl font-bold">Middleware Management</h1>
|
||||
</div>
|
||||
|
||||
<DataTable {columns} data={$middlewares || []} showSourceTabs={true} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Layers />
|
||||
<h1 class="text-2xl font-bold">Middleware Management</h1>
|
||||
</div>
|
||||
{#if source.value === TraefikSource.LOCAL}
|
||||
<DataTable
|
||||
{columns}
|
||||
data={$middlewares || []}
|
||||
showSourceTabs={true}
|
||||
createButton={{
|
||||
label: 'Add Middleware',
|
||||
onClick: openCreateModal
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<DataTable {columns} data={$middlewares || []} showSourceTabs={true} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<MiddlewareModal
|
||||
bind:open={modalState.isOpen}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import * as Tabs from '$lib/components/ui/tabs/index.js';
|
||||
import ColumnBadge from '$lib/components/tables/ColumnBadge.svelte';
|
||||
import DataTable from '$lib/components/tables/DataTable.svelte';
|
||||
import RouterModal from '$lib/components/modals/router.svelte';
|
||||
@@ -83,32 +82,33 @@
|
||||
const columns: ColumnDef<RouterWithService>[] = [
|
||||
{
|
||||
header: 'Name',
|
||||
accessorFn: (row) => row.router.name,
|
||||
accessorKey: 'router.name',
|
||||
id: 'name',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const name = row.getValue('name') as string;
|
||||
return name.split('@')[0];
|
||||
return name?.split('@')[0];
|
||||
}
|
||||
},
|
||||
{
|
||||
header: 'Protocol',
|
||||
accessorFn: (row) => row.router.protocol,
|
||||
accessorKey: 'router.protocol',
|
||||
id: 'protocol',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const protocol = row.getValue('protocol') as string;
|
||||
return renderComponent(ColumnBadge, { label: protocol });
|
||||
return renderComponent(ColumnBadge, {
|
||||
label: row.getValue('protocol') as string
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
header: 'Provider',
|
||||
accessorFn: (row) => row.router.name,
|
||||
accessorKey: 'router.name',
|
||||
id: 'provider',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const name = row.getValue('provider') as string;
|
||||
const provider = name.split('@')[1];
|
||||
const provider = name?.split('@')[1];
|
||||
if (!provider && source.value === TraefikSource.AGENT) {
|
||||
return renderComponent(ColumnBadge, {
|
||||
label: 'agent',
|
||||
@@ -129,38 +129,36 @@
|
||||
},
|
||||
{
|
||||
header: 'Entrypoints',
|
||||
accessorFn: (row) => row.router.entryPoints,
|
||||
accessorKey: 'router.entryPoints',
|
||||
id: 'entryPoints',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const ep = row.getValue('entryPoints') as string[];
|
||||
return renderComponent(ColumnBadge, {
|
||||
label: ep,
|
||||
label: row.getValue('entryPoints') as string[],
|
||||
variant: 'secondary'
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
header: 'Middlewares',
|
||||
accessorFn: (row) => row.router.middlewares,
|
||||
accessorKey: 'router.middlewares',
|
||||
id: 'middlewares',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const middlewares = row.getValue('middlewares') as string[];
|
||||
return renderComponent(ColumnBadge, {
|
||||
label: middlewares,
|
||||
label: row.getValue('middlewares') as string[],
|
||||
variant: 'secondary'
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
header: 'Cert Resolver',
|
||||
accessorFn: (row) => row.router.tls,
|
||||
id: 'resolver',
|
||||
accessorKey: 'router.tls',
|
||||
id: 'tls',
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const resolver = row.getValue('resolver') as TLS;
|
||||
if (!resolver?.certResolver) {
|
||||
const tls = row.getValue('tls') as TLS;
|
||||
if (!tls?.certResolver) {
|
||||
return renderComponent(ColumnBadge, {
|
||||
label: 'None',
|
||||
variant: 'secondary',
|
||||
@@ -168,7 +166,7 @@
|
||||
});
|
||||
}
|
||||
return renderComponent(ColumnBadge, {
|
||||
label: resolver.certResolver as string,
|
||||
label: tls.certResolver as string,
|
||||
variant: 'secondary',
|
||||
class: 'bg-slate-300 dark:bg-slate-700'
|
||||
});
|
||||
@@ -181,7 +179,12 @@
|
||||
enableSorting: true,
|
||||
cell: ({ row }) => {
|
||||
const status = row.getValue('serverStatus') as Record<string, string>;
|
||||
if (!status) return renderComponent(ColumnBadge, { label: 'N/A', variant: 'secondary' });
|
||||
if (status === undefined) {
|
||||
return renderComponent(ColumnBadge, {
|
||||
label: 'N/A',
|
||||
variant: 'secondary'
|
||||
});
|
||||
}
|
||||
const upCount = Object.values(status).filter((status) => status === 'UP').length;
|
||||
const totalCount = Object.values(status).length;
|
||||
const greenBadge = 'bg-green-300 dark:bg-green-600';
|
||||
@@ -243,43 +246,25 @@
|
||||
<title>Routers</title>
|
||||
</svelte:head>
|
||||
|
||||
<Tabs.Root value={source.value}>
|
||||
<Tabs.Content value={TraefikSource.LOCAL}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Route />
|
||||
<h1 class="text-2xl font-bold">Router Management</h1>
|
||||
</div>
|
||||
<DataTable
|
||||
{columns}
|
||||
data={$routerServiceMerge || []}
|
||||
showSourceTabs={true}
|
||||
createButton={{
|
||||
label: 'Add Router',
|
||||
onClick: openCreateModal
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value={TraefikSource.API}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Route />
|
||||
<h1 class="text-2xl font-bold">Router Management</h1>
|
||||
</div>
|
||||
<DataTable {columns} data={$routerServiceMerge || []} showSourceTabs={true} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value={TraefikSource.AGENT}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Route />
|
||||
<h1 class="text-2xl font-bold">Router Management</h1>
|
||||
</div>
|
||||
<DataTable {columns} data={$routerServiceMerge || []} showSourceTabs={true} />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center justify-start gap-2">
|
||||
<Route />
|
||||
<h1 class="text-2xl font-bold">Router Management</h1>
|
||||
</div>
|
||||
{#if source.value === TraefikSource.LOCAL}
|
||||
<DataTable
|
||||
{columns}
|
||||
data={$routerServiceMerge || []}
|
||||
showSourceTabs={true}
|
||||
createButton={{
|
||||
label: 'Add Router',
|
||||
onClick: openCreateModal
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<DataTable {columns} data={$routerServiceMerge || []} showSourceTabs={true} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<RouterModal
|
||||
bind:open={modalState.isOpen}
|
||||
|
||||
Reference in New Issue
Block a user