patch_ui: support TrueNAS 25.x Angular pureFunction binding format
TrueNAS 25.x changed how Angular emits the filterByProviders binding.
Previously a static inline array ("filterByProviders",["STORJ_IX"]),
it is now a pureFunction call:
pe(slot, factory, component.CloudSyncProviderName.Storj)
Add a _PATTERNS list tried in order, with a _match_pattern() helper.
The 25.x pureFunction pattern matches on the stable TypeScript enum
name (CloudSyncProviderName.Storj); the minified variable names and
slot index are matched with \w+ / \d+. Both patterns replace the
binding with ["STORJ_IX","S3","B2"]. MARKER and backup/restore logic
are unchanged.
This commit is contained in:
+36
-18
@@ -3,17 +3,17 @@
|
|||||||
Patches the TrueNAS webui Angular bundle to show S3 and B2 credentials
|
Patches the TrueNAS webui Angular bundle to show S3 and B2 credentials
|
||||||
in the TrueCloud Backup task form, instead of Storj only.
|
in the TrueCloud Backup task form, instead of Storj only.
|
||||||
|
|
||||||
Angular's Ivy compiler inlines TypeScript string enum values as literals in
|
The template binding [filterByProviders]="[CloudSyncProviderName.Storj]" appears
|
||||||
the compiled bundle, so the template binding:
|
in the minified JS in one of two forms depending on TrueNAS / Angular version:
|
||||||
|
|
||||||
[filterByProviders]="[CloudSyncProviderName.Storj]"
|
TrueNAS 24.x (static inline array):
|
||||||
|
"filterByProviders",["STORJ_IX"]
|
||||||
|
|
||||||
appears verbatim in the minified JS as:
|
TrueNAS 25.x+ (Angular pureFunction binding):
|
||||||
|
"filterByProviders",pe(115,Rn,i.CloudSyncProviderName.Storj)
|
||||||
|
|
||||||
"filterByProviders",["STORJ_IX"]
|
Both are replaced so the dropdown includes S3 and B2. The file is backed up
|
||||||
|
before modification so uninstall.sh can restore it.
|
||||||
We replace that array to include S3 and B2. The file is backed up before
|
|
||||||
modification so uninstall.sh can restore it.
|
|
||||||
|
|
||||||
Safe to run multiple times — a marker string detects an already-patched file.
|
Safe to run multiple times — a marker string detects an already-patched file.
|
||||||
Exits 0 in all cases (warnings are printed to stdout and logged by apply.sh).
|
Exits 0 in all cases (warnings are printed to stdout and logged by apply.sh).
|
||||||
@@ -29,22 +29,37 @@ WEBUI_CANDIDATES = [
|
|||||||
"/var/www/truenas",
|
"/var/www/truenas",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Angular's Ivy template compiler serialises the Storj-only filter as this
|
# Patterns tried in order; the first match wins.
|
||||||
# exact substring in every production build we've observed.
|
# Each entry is (compiled_regex, replacement_string).
|
||||||
FIND = re.compile(r'("filterByProviders",)\["STORJ_IX"\]')
|
_PATTERNS = [
|
||||||
REPLACE = r'\1["STORJ_IX","S3","B2"]'
|
# TrueNAS 25.x+: Angular emits a pureFunction call instead of a literal array.
|
||||||
|
# pe / slot-index / factory-var / component-var are all minified and change
|
||||||
|
# across builds; CloudSyncProviderName.Storj is stable (TypeScript enum name).
|
||||||
|
(re.compile(r'("filterByProviders",)\w+\(\d+,\w+,\w+\.CloudSyncProviderName\.Storj\)'),
|
||||||
|
r'\1["STORJ_IX","S3","B2"]'),
|
||||||
|
# TrueNAS 24.x and earlier: static inline array.
|
||||||
|
(re.compile(r'("filterByProviders",)\["STORJ_IX"\]'),
|
||||||
|
r'\1["STORJ_IX","S3","B2"]'),
|
||||||
|
]
|
||||||
|
|
||||||
# A patched file contains both "S3" and "B2" next to "STORJ_IX" in this form.
|
# Present in any patched file. Specific enough not to appear elsewhere.
|
||||||
# This string is specific enough not to appear elsewhere in the bundle.
|
|
||||||
MARKER = '"STORJ_IX","S3","B2"'
|
MARKER = '"STORJ_IX","S3","B2"'
|
||||||
|
|
||||||
|
|
||||||
|
def _match_pattern(content):
|
||||||
|
"""Return (regex, replacement) for the first pattern found in content, or (None, None)."""
|
||||||
|
for find, replace in _PATTERNS:
|
||||||
|
if find.search(content):
|
||||||
|
return find, replace
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
def find_bundle():
|
def find_bundle():
|
||||||
"""
|
"""
|
||||||
Search WEBUI_CANDIDATES for the JS bundle containing the filterByProviders
|
Search WEBUI_CANDIDATES for the JS chunk containing the filterByProviders
|
||||||
binding. Returns (webui_dir, path, content). webui_dir is None if no
|
binding. Returns (webui_dir, path, content). webui_dir is None if no
|
||||||
candidate directory exists; path is None if the directory exists but the
|
candidate directory exists; path is None if the directory exists but the
|
||||||
pattern is not found in any bundle.
|
pattern is not found in any file.
|
||||||
"""
|
"""
|
||||||
webui = next((d for d in WEBUI_CANDIDATES if os.path.isdir(d)), None)
|
webui = next((d for d in WEBUI_CANDIDATES if os.path.isdir(d)), None)
|
||||||
if webui is None:
|
if webui is None:
|
||||||
@@ -58,7 +73,8 @@ def find_bundle():
|
|||||||
try:
|
try:
|
||||||
with open(path, encoding="utf-8", errors="replace") as fh:
|
with open(path, encoding="utf-8", errors="replace") as fh:
|
||||||
content = fh.read()
|
content = fh.read()
|
||||||
if FIND.search(content) or MARKER in content:
|
find, _ = _match_pattern(content)
|
||||||
|
if find is not None or MARKER in content:
|
||||||
return webui, path, content
|
return webui, path, content
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
@@ -88,6 +104,8 @@ def main():
|
|||||||
print(f"[truecloud-patch] UI already patched: {path}")
|
print(f"[truecloud-patch] UI already patched: {path}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
find, replace = _match_pattern(content)
|
||||||
|
|
||||||
backup = path + ".pre-truecloud-patch"
|
backup = path + ".pre-truecloud-patch"
|
||||||
if not os.path.exists(backup):
|
if not os.path.exists(backup):
|
||||||
try:
|
try:
|
||||||
@@ -96,7 +114,7 @@ def main():
|
|||||||
print(f"[truecloud-patch] ERROR: Could not create backup {backup}: {exc}")
|
print(f"[truecloud-patch] ERROR: Could not create backup {backup}: {exc}")
|
||||||
return
|
return
|
||||||
|
|
||||||
patched, count = FIND.subn(REPLACE, content)
|
patched, count = find.subn(replace, content)
|
||||||
if count != 1:
|
if count != 1:
|
||||||
print(
|
print(
|
||||||
f"[truecloud-patch] WARNING: {count} replacement(s) in {path}; "
|
f"[truecloud-patch] WARNING: {count} replacement(s) in {path}; "
|
||||||
|
|||||||
Reference in New Issue
Block a user