feat: add RolesPage.tsx, patch_all_modules.py for all 4 addon modules
This commit is contained in:
102
resources/scripts/addons/advanced-admin/roles/RolesPage.tsx
Normal file
102
resources/scripts/addons/advanced-admin/roles/RolesPage.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Roles Management Page — Admin view for role-based permissions
|
||||||
|
* Ball Studios <https://git.balls.studio>
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import http from '@/api/http';
|
||||||
|
import { ServerContext } from '@/state/server';
|
||||||
|
import PageContentBlock from '@/components/elements/PageContentBlock';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
|
interface Role {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
description: string;
|
||||||
|
permissions: string[];
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserRole {
|
||||||
|
role: Role;
|
||||||
|
override: string | null;
|
||||||
|
source: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RolesPage() {
|
||||||
|
const uuid = ServerContext.useStoreState((s) => s.server.data!.uuid);
|
||||||
|
const [userRoles, setUserRoles] = useState<UserRole[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
http.get(`/api/client/servers/${uuid}/addons/permissions/effective/me`)
|
||||||
|
.then(({ data }: any) => setUserRoles(data.data ?? []))
|
||||||
|
.catch(() => setError('Failed to load role data.'))
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [uuid]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContentBlock title={'Role Permissions'}>
|
||||||
|
<div css={tw`mb-6`}>
|
||||||
|
<h2 css={tw`text-2xl font-bold text-neutral-100 mb-1`}>🛡 Role Permissions</h2>
|
||||||
|
<p css={tw`text-neutral-400 text-sm`}>
|
||||||
|
Your effective permissions for this server, managed by the Ball Studios Advanced Admin addon.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<div css={tw`text-neutral-400 py-8 text-center`}>Loading permissions…</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div css={tw`p-4 rounded bg-red-900 border border-red-600 text-red-200 text-sm mb-4`}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && !error && userRoles.length === 0 && (
|
||||||
|
<div css={tw`p-6 rounded bg-neutral-800 border border-neutral-700 text-neutral-400 text-sm text-center`}>
|
||||||
|
No roles assigned. Contact an administrator.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div css={tw`grid gap-4`}>
|
||||||
|
{userRoles.map((ur, i) => (
|
||||||
|
<div key={i} css={tw`p-4 rounded-lg bg-neutral-800 border border-neutral-700`}>
|
||||||
|
<div css={tw`flex items-center gap-3 mb-2`}>
|
||||||
|
<span
|
||||||
|
css={tw`w-3 h-3 rounded-full inline-block flex-shrink-0`}
|
||||||
|
style={{ backgroundColor: ur.role?.color ?? '#6366f1' }}
|
||||||
|
/>
|
||||||
|
<span css={tw`font-semibold text-neutral-100`}>
|
||||||
|
{ur.role?.name ?? 'Unknown Role'}
|
||||||
|
</span>
|
||||||
|
{ur.source && (
|
||||||
|
<span css={tw`ml-auto text-xs text-neutral-500 bg-neutral-900 px-2 py-0.5 rounded`}>
|
||||||
|
via {ur.source}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{ur.role?.description && (
|
||||||
|
<p css={tw`text-neutral-400 text-sm mb-3`}>{ur.role.description}</p>
|
||||||
|
)}
|
||||||
|
{ur.role?.permissions?.length > 0 && (
|
||||||
|
<div css={tw`flex flex-wrap gap-1`}>
|
||||||
|
{ur.role.permissions.map((perm: string) => (
|
||||||
|
<span
|
||||||
|
key={perm}
|
||||||
|
css={tw`text-xs font-mono px-2 py-0.5 rounded bg-indigo-900 text-indigo-300 border border-indigo-700`}
|
||||||
|
>
|
||||||
|
{perm}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</PageContentBlock>
|
||||||
|
);
|
||||||
|
}
|
||||||
197
scripts/patch_all_modules.py
Normal file
197
scripts/patch_all_modules.py
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Patch all 4 addon modules into the panel frontend:
|
||||||
|
1. Live Network → /addon-network nav tab (NetworkTab.tsx)
|
||||||
|
2. File History → FileHistoryButton injected into FileEditContainer.tsx
|
||||||
|
3. Console Autocomplete → CommandInput replaces bare <input> in Console.tsx
|
||||||
|
4. Roles → /addon-roles nav tab (RolesPage.tsx)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, shutil
|
||||||
|
|
||||||
|
PANEL = '/var/www/pterodactyl'
|
||||||
|
ADDON_SRC = '/var/www/pterodactyl/resources/scripts/addons/advanced-admin'
|
||||||
|
|
||||||
|
def read(path): return open(path).read()
|
||||||
|
def write(path, content): open(path, 'w').write(content)
|
||||||
|
|
||||||
|
def patch_file(path, label, check_str, old, new):
|
||||||
|
content = read(path)
|
||||||
|
if check_str in content:
|
||||||
|
print(f" [{label}] Already patched")
|
||||||
|
return False
|
||||||
|
if old not in content:
|
||||||
|
print(f" [{label}] WARN: anchor not found — check manually")
|
||||||
|
return False
|
||||||
|
write(path, content.replace(old, new, 1))
|
||||||
|
print(f" [{label}] ✅ Patched")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 1. routes.ts — register all 4 tabs
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
print("\n=== 1. routes.ts ===")
|
||||||
|
rts = f'{PANEL}/resources/scripts/routers/routes.ts'
|
||||||
|
content = read(rts)
|
||||||
|
|
||||||
|
# Imports to inject
|
||||||
|
IMPORTS = [
|
||||||
|
('NetworkTab', "import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';"),
|
||||||
|
('RolesPage', "import RolesPage from '@/addons/advanced-admin/roles/RolesPage';"),
|
||||||
|
]
|
||||||
|
|
||||||
|
lines = content.splitlines(keepends=True)
|
||||||
|
last_import_idx = max(i for i, l in enumerate(lines) if l.strip().startswith('import '))
|
||||||
|
|
||||||
|
inserts = []
|
||||||
|
for check, imp in IMPORTS:
|
||||||
|
if check not in content:
|
||||||
|
inserts.append(imp + '\n')
|
||||||
|
print(f" Adding import: {check}")
|
||||||
|
else:
|
||||||
|
print(f" Import exists: {check}")
|
||||||
|
|
||||||
|
if inserts:
|
||||||
|
for imp in reversed(inserts):
|
||||||
|
lines.insert(last_import_idx + 1, imp)
|
||||||
|
content = ''.join(lines)
|
||||||
|
|
||||||
|
# Routes to append before closing of server array
|
||||||
|
ROUTES = [
|
||||||
|
('/addon-network', """,
|
||||||
|
{
|
||||||
|
path: '/addon-network',
|
||||||
|
permission: null,
|
||||||
|
name: 'Live Network',
|
||||||
|
component: NetworkTab,
|
||||||
|
}"""),
|
||||||
|
('/addon-roles', """,
|
||||||
|
{
|
||||||
|
path: '/addon-roles',
|
||||||
|
permission: null,
|
||||||
|
name: 'Roles',
|
||||||
|
component: RolesPage,
|
||||||
|
}"""),
|
||||||
|
]
|
||||||
|
|
||||||
|
for route_path, block in ROUTES:
|
||||||
|
if route_path not in content:
|
||||||
|
target = " ],\n} as Routes;"
|
||||||
|
if target in content:
|
||||||
|
content = content.replace(target, f"{block},\n ],\n}} as Routes;")
|
||||||
|
print(f" Added route: {route_path}")
|
||||||
|
else:
|
||||||
|
print(f" WARN: insertion point not found for {route_path}")
|
||||||
|
else:
|
||||||
|
print(f" Route exists: {route_path}")
|
||||||
|
|
||||||
|
write(rts, content)
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 2. FileEditContainer.tsx — inject FileHistoryButton
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
print("\n=== 2. FileEditContainer.tsx — History button ===")
|
||||||
|
fec = f'{PANEL}/resources/scripts/components/server/files/FileEditContainer.tsx'
|
||||||
|
|
||||||
|
# Step A: add import
|
||||||
|
patch_file(
|
||||||
|
fec, 'import',
|
||||||
|
'FileHistoryButton',
|
||||||
|
"import { encodePathSegments, hashToPath } from '@/helpers';",
|
||||||
|
"import { encodePathSegments, hashToPath } from '@/helpers';\nimport FileHistoryButton from '@/addons/advanced-admin/revisions/FileHistoryButton';"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step B: inject button in the footer action row, before Save/Create buttons
|
||||||
|
patch_file(
|
||||||
|
fec, 'JSX button',
|
||||||
|
'<FileHistoryButton',
|
||||||
|
" <div css={tw`flex justify-end mt-4`}>",
|
||||||
|
" <div css={tw`flex justify-end mt-4 gap-2`}>\n {action === 'edit' && <FileHistoryButton filePath={hash.replace(/^#/, '')} />}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 3. Console.tsx — inject CommandInput wrapper
|
||||||
|
#
|
||||||
|
# The console's existing <input> handles history/keyboard; our CommandInput
|
||||||
|
# is a self-contained replacement that renders its own <input> + dropdown.
|
||||||
|
# We inject it BELOW the existing console output area, not replacing the input,
|
||||||
|
# so we don't break the existing keyboard shortcuts. Instead we ADD an
|
||||||
|
# autocomplete overlay above it.
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
print("\n=== 3. Console.tsx — autocomplete ===")
|
||||||
|
con = f'{PANEL}/resources/scripts/components/server/console/Console.tsx'
|
||||||
|
|
||||||
|
# Add import
|
||||||
|
patch_file(
|
||||||
|
con, 'import',
|
||||||
|
'CommandInput',
|
||||||
|
"import { ServerContext } from '@/state/server';",
|
||||||
|
"import { ServerContext } from '@/state/server';\nimport CommandInput from '@/addons/advanced-admin/console/CommandInput';"
|
||||||
|
)
|
||||||
|
|
||||||
|
# We inject CommandInput ABOVE the existing input row as an enhancement panel.
|
||||||
|
# It sits above the native input and provides suggestions; the native input
|
||||||
|
# still works for history navigation (up/down arrow).
|
||||||
|
content = read(con)
|
||||||
|
if 'CommandInput' in content and '<CommandInput' not in content:
|
||||||
|
# Find the container div that wraps the input
|
||||||
|
old = " <input\n className={classNames('peer', styles.command_input)}"
|
||||||
|
new = """ <CommandInput
|
||||||
|
onSend={(cmd) => {
|
||||||
|
setHistory((prev: string[]) => [cmd, ...(prev || [])].slice(0, 32));
|
||||||
|
instance && instance.send('send command', cmd);
|
||||||
|
}}
|
||||||
|
disabled={!connected}
|
||||||
|
/>
|
||||||
|
{/* native input kept for history arrow-key navigation */}
|
||||||
|
<input
|
||||||
|
className={classNames('peer', styles.command_input)}
|
||||||
|
style={{ display: 'none' }}"""
|
||||||
|
if old in content:
|
||||||
|
write(con, content.replace(old, new, 1))
|
||||||
|
print(" ✅ CommandInput injected (native input hidden, replaced)")
|
||||||
|
else:
|
||||||
|
print(" WARN: native input anchor not found — injecting before the input block")
|
||||||
|
# Fallback: add CommandInput just before the div that has command_input
|
||||||
|
fallback_old = " <input"
|
||||||
|
if fallback_old in content:
|
||||||
|
write(con, content.replace(
|
||||||
|
fallback_old,
|
||||||
|
""" <CommandInput
|
||||||
|
onSend={(cmd) => { instance && instance.send('send command', cmd); }}
|
||||||
|
disabled={!connected}
|
||||||
|
/>
|
||||||
|
<input style={{display:'none'}}""",
|
||||||
|
1
|
||||||
|
))
|
||||||
|
print(" ✅ CommandInput injected (fallback mode)")
|
||||||
|
elif '<CommandInput' in content:
|
||||||
|
print(" Already injected")
|
||||||
|
else:
|
||||||
|
print(" WARN: import added but no injection done — check Console.tsx manually")
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# 4. Clean ServerRouter.tsx stray imports
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
print("\n=== 4. ServerRouter.tsx cleanup ===")
|
||||||
|
sr = f'{PANEL}/resources/scripts/routers/ServerRouter.tsx'
|
||||||
|
content = read(sr)
|
||||||
|
changed = False
|
||||||
|
for stray in [
|
||||||
|
"import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';\n",
|
||||||
|
"import RolesPage from '@/addons/advanced-admin/roles/RolesPage';\n",
|
||||||
|
]:
|
||||||
|
if stray in content:
|
||||||
|
content = content.replace(stray, '')
|
||||||
|
print(f" Removed: {stray.strip()}")
|
||||||
|
changed = True
|
||||||
|
if not changed:
|
||||||
|
print(" Already clean")
|
||||||
|
write(sr, content)
|
||||||
|
|
||||||
|
|
||||||
|
print("\n✅ All patches applied. Run: cd /var/www/pterodactyl && yarn build:production")
|
||||||
87
scripts/patch_routes.py
Normal file
87
scripts/patch_routes.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Patch routes.ts and ServerRouter.tsx to inject addon routes (network tab nav link).
|
||||||
|
This patcher understands Pterodactyl's actual routing structure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
PANEL = '/var/www/pterodactyl'
|
||||||
|
ROUTES_FILE = f'{PANEL}/resources/scripts/routers/routes.ts'
|
||||||
|
SERVER_ROUTER = f'{PANEL}/resources/scripts/routers/ServerRouter.tsx'
|
||||||
|
|
||||||
|
# ─── 1. Patch routes.ts ───────────────────────────────────────────────────────
|
||||||
|
print("=== Patching routes.ts ===")
|
||||||
|
content = open(ROUTES_FILE).read()
|
||||||
|
|
||||||
|
ADDON_IMPORT = "import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';"
|
||||||
|
ADDON_ROUTE = """,
|
||||||
|
{
|
||||||
|
path: '/addon-network',
|
||||||
|
permission: null,
|
||||||
|
name: 'Live Network',
|
||||||
|
component: NetworkTab,
|
||||||
|
}"""
|
||||||
|
|
||||||
|
patched = False
|
||||||
|
|
||||||
|
# Add import near top if not present
|
||||||
|
if ADDON_IMPORT not in content:
|
||||||
|
# Insert after last existing import line
|
||||||
|
lines = content.splitlines(keepends=True)
|
||||||
|
last_import = 0
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip().startswith('import ') or line.strip().startswith('const ') and 'lazy' in line:
|
||||||
|
last_import = i
|
||||||
|
lines.insert(last_import + 1, ADDON_IMPORT + '\n')
|
||||||
|
content = ''.join(lines)
|
||||||
|
print(f" Added import at line {last_import + 2}")
|
||||||
|
else:
|
||||||
|
print(" Import already present")
|
||||||
|
|
||||||
|
# Add route entry before closing of server array
|
||||||
|
if '/addon-network' not in content:
|
||||||
|
# Find the last route entry (before closing ], server array end)
|
||||||
|
# Insert before the closing ` ],` that ends the server array
|
||||||
|
# The server array ends with the activity route, then ` ],`
|
||||||
|
insert_anchor = " {\n path: '/activity',\n permission: 'activity.*',\n name: 'Activity',\n component: ServerActivityLogContainer,"
|
||||||
|
closing = " },\n ],\n} as Routes;"
|
||||||
|
if insert_anchor in content and closing in content:
|
||||||
|
# Find and insert our route before the closing `] as Routes`
|
||||||
|
content = content.replace(
|
||||||
|
" },\n ],\n} as Routes;",
|
||||||
|
f" }},{ADDON_ROUTE},\n ],\n}} as Routes;"
|
||||||
|
)
|
||||||
|
patched = True
|
||||||
|
print(" Added /addon-network route entry")
|
||||||
|
else:
|
||||||
|
print(" WARNING: Could not find anchor for route insertion")
|
||||||
|
print(" Trying simpler approach...")
|
||||||
|
# Simpler: insert before `] as Routes`
|
||||||
|
if " ],\n} as Routes;" in content:
|
||||||
|
content = content.replace(
|
||||||
|
" ],\n} as Routes;",
|
||||||
|
f"{ADDON_ROUTE},\n ],\n}} as Routes;"
|
||||||
|
)
|
||||||
|
patched = True
|
||||||
|
print(" Added route entry (simple method)")
|
||||||
|
else:
|
||||||
|
print(" Route already present")
|
||||||
|
patched = True
|
||||||
|
|
||||||
|
open(ROUTES_FILE, 'w').write(content)
|
||||||
|
print(f" routes.ts saved. Patched={patched}")
|
||||||
|
|
||||||
|
# ─── 2. Clean up ServerRouter.tsx (remove the broken import we added) ─────────
|
||||||
|
print("\n=== Cleaning ServerRouter.tsx ===")
|
||||||
|
sr_content = open(SERVER_ROUTER).read()
|
||||||
|
bad_import = "import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';\n"
|
||||||
|
if bad_import in sr_content:
|
||||||
|
sr_content = sr_content.replace(bad_import, '')
|
||||||
|
open(SERVER_ROUTER, 'w').write(sr_content)
|
||||||
|
print(" Removed stray NetworkTab import from ServerRouter.tsx")
|
||||||
|
else:
|
||||||
|
print(" ServerRouter.tsx is clean")
|
||||||
|
|
||||||
|
print("\nDone. Now rebuild the frontend.")
|
||||||
97
scripts/repatch.py
Normal file
97
scripts/repatch.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Re-apply all addon patches after a panel update."""
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
|
||||||
|
PANEL = '/var/www/pterodactyl'
|
||||||
|
|
||||||
|
# ── 1. AppServiceProvider ─────────────────────────────────────────────────────
|
||||||
|
print("=== Patching AppServiceProvider.php ===")
|
||||||
|
asp = f'{PANEL}/app/Providers/AppServiceProvider.php'
|
||||||
|
content = open(asp).read()
|
||||||
|
INJECT = " $this->app->register(\\Pterodactyl\\Addons\\AdvancedAdmin\\AddonServiceProvider::class);"
|
||||||
|
if 'AddonServiceProvider' not in content:
|
||||||
|
old = ' public function register(): void\n {'
|
||||||
|
new = f' public function register(): void\n {{\n{INJECT}'
|
||||||
|
if old in content:
|
||||||
|
content = content.replace(old, new, 1)
|
||||||
|
open(asp, 'w').write(content)
|
||||||
|
print(f" Applied: AddonServiceProvider registered")
|
||||||
|
else:
|
||||||
|
print(f" WARN: anchor not found in AppServiceProvider")
|
||||||
|
else:
|
||||||
|
print(" Already patched")
|
||||||
|
|
||||||
|
# ── 2. routes.ts ─────────────────────────────────────────────────────────────
|
||||||
|
print("\n=== Patching routes.ts ===")
|
||||||
|
rts = f'{PANEL}/resources/scripts/routers/routes.ts'
|
||||||
|
content = open(rts).read()
|
||||||
|
|
||||||
|
IMPORT = "import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';"
|
||||||
|
ROUTE_BLOCK = """,
|
||||||
|
{
|
||||||
|
path: '/addon-network',
|
||||||
|
permission: null,
|
||||||
|
name: 'Live Network',
|
||||||
|
component: NetworkTab,
|
||||||
|
}"""
|
||||||
|
|
||||||
|
# Add import
|
||||||
|
if IMPORT not in content:
|
||||||
|
lines = content.splitlines(keepends=True)
|
||||||
|
last_import = 0
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if line.strip().startswith('import '):
|
||||||
|
last_import = i
|
||||||
|
lines.insert(last_import + 1, IMPORT + '\n')
|
||||||
|
content = ''.join(lines)
|
||||||
|
print(" Added import")
|
||||||
|
else:
|
||||||
|
print(" Import already present")
|
||||||
|
|
||||||
|
# Add route
|
||||||
|
if '/addon-network' not in content:
|
||||||
|
# Insert before the final closing of server array
|
||||||
|
target = " ],\n} as Routes;"
|
||||||
|
if target in content:
|
||||||
|
content = content.replace(target, f"{ROUTE_BLOCK},\n ],\n}} as Routes;")
|
||||||
|
print(" Added /addon-network route")
|
||||||
|
else:
|
||||||
|
print(" WARN: routes.ts closing not found - trying alternate")
|
||||||
|
# Try without the extra comma
|
||||||
|
target2 = " ]\n} as Routes;"
|
||||||
|
if target2 in content:
|
||||||
|
content = content.replace(target2, f"{ROUTE_BLOCK},\n ]\n}} as Routes;")
|
||||||
|
print(" Added /addon-network route (alt)")
|
||||||
|
else:
|
||||||
|
print(" Route already present")
|
||||||
|
|
||||||
|
open(rts, 'w').write(content)
|
||||||
|
|
||||||
|
# ── 3. Clean ServerRouter.tsx of any stray addon imports ─────────────────────
|
||||||
|
print("\n=== Cleaning ServerRouter.tsx ===")
|
||||||
|
sr = f'{PANEL}/resources/scripts/routers/ServerRouter.tsx'
|
||||||
|
content = open(sr).read()
|
||||||
|
stray = "import NetworkTab from '@/addons/advanced-admin/network/NetworkTab';\n"
|
||||||
|
if stray in content:
|
||||||
|
content = content.replace(stray, '')
|
||||||
|
open(sr, 'w').write(content)
|
||||||
|
print(" Removed stray import")
|
||||||
|
else:
|
||||||
|
print(" Clean already")
|
||||||
|
|
||||||
|
# ── 4. Remove non-existent middleware from routes ─────────────────────────────
|
||||||
|
print("\n=== Fixing addon route middleware ===")
|
||||||
|
for route_file in ['addon-client.php', 'addon-application.php']:
|
||||||
|
path = f'{PANEL}/routes/{route_file}'
|
||||||
|
content = open(path).read()
|
||||||
|
# Replace named throttle limiters that aren't registered with standard ones
|
||||||
|
import re
|
||||||
|
# Remove ->middleware('throttle:addon-*') chains that use unregistered limiters
|
||||||
|
content = re.sub(r"->middleware\('throttle:addon-[^']+'\)", "", content)
|
||||||
|
# Remove ->middleware('addon.ratelimit')
|
||||||
|
content = re.sub(r",?\s*'addon\.ratelimit'", "", content)
|
||||||
|
open(path, 'w').write(content)
|
||||||
|
print(f" Cleaned {route_file}")
|
||||||
|
|
||||||
|
print("\nAll patches applied. Rebuild the frontend next.")
|
||||||
Reference in New Issue
Block a user