feat: add TrueNAS UI panel via injected JS — no pvemanagerlib.js patch

Instead of diffing and patching the 50k-line pvemanagerlib.js each PVE
release, we ship truenas-storage.js as our own file and inject one
<script> line into index.html.tpl (rarely changes, ~60 lines).

truenas-storage.js adds 'truenas' to PVE.Utils.storageSchema at runtime
and defines PVE.storage.TrueNASInputPanel with all config fields:
  - TrueNAS host, API key, pool, dataset (column 1)
  - SSL toggle, cert verify, portal IP, target IQN (column 2)
API key field: required on create, optional on edit (only submitted if
the user types a new value).

postinst: copies TrueNAS.pm + truenas-storage.js, adds script tag
postrm:   removes both files, strips script tag

Closes #225 (core interface), #226 (iSCSI lifecycle activate/deactivate),
#227 (API pool listing replaces SSH).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kevin Adams 2026-05-23 20:50:15 -04:00
parent f6ca0c9ef5
commit 8a487ef4a8
3 changed files with 145 additions and 11 deletions

View File

@ -1,11 +1,14 @@
#!/bin/bash
# postinst: freenas-proxmox v3.x install/upgrade script
# Installs the TrueNAS custom storage plugin for Proxmox VE.
# No patches, no internet access required.
# Installs the TrueNAS custom storage plugin and UI panel for Proxmox VE.
# No patches to PVE core files — only adds our own files and one <script> line.
set -e
PLUGIN_SRC="/usr/share/freenas-proxmox/TrueNAS.pm"
INSTALL_DIR="/usr/share/freenas-proxmox"
PLUGIN_DST="/usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm"
JS_DST="/usr/share/pve-manager/js/truenas-storage.js"
TPL="/usr/share/pve-manager/index.html.tpl"
SCRIPT_TAG=' <script type="text/javascript" src="/pve2/js/truenas-storage.js?ver=[% version %]"></script>'
LOG_FILE="/var/log/freenas-proxmox-install.log"
log() {
@ -15,21 +18,34 @@ log() {
install_plugin() {
log "Installing ${PLUGIN_DST}"
mkdir -p "$(dirname "$PLUGIN_DST")"
cp "$PLUGIN_SRC" "$PLUGIN_DST"
log "Plugin installed"
cp "${INSTALL_DIR}/TrueNAS.pm" "$PLUGIN_DST"
}
install_ui() {
log "Installing ${JS_DST}"
cp "${INSTALL_DIR}/truenas-storage.js" "$JS_DST"
if grep -qF 'truenas-storage.js' "$TPL" 2>/dev/null; then
log "index.html.tpl already has truenas-storage.js — skipping"
return 0
fi
log "Adding <script> tag to ${TPL}"
sed -i "/pvemanagerlib\.js/a\\${SCRIPT_TAG}" "$TPL"
}
restart_pve_services() {
log "Restarting Proxmox VE services ..."
pvedaemon restart && log "pvedaemon restarted"
pveproxy restart && log "pveproxy restarted"
log "Done. Refresh your Proxmox browser tab to load the updated UI."
log "Done. Refresh your Proxmox browser tab."
}
case "$1" in
configure)
log "Configuring freenas-proxmox (previous version: ${2:-none})"
install_plugin
install_ui
restart_pve_services
;;
abort-upgrade|abort-remove|abort-deconfigure)

View File

@ -1,15 +1,25 @@
#!/bin/bash
# postrm: freenas-proxmox v3.x removal script
# Removes the TrueNAS custom storage plugin for Proxmox VE.
set -e
PLUGIN_DST="/usr/share/perl5/PVE/Storage/Custom/TrueNAS.pm"
JS_DST="/usr/share/pve-manager/js/truenas-storage.js"
TPL="/usr/share/pve-manager/index.html.tpl"
LOG_FILE="/var/log/freenas-proxmox-install.log"
log() {
echo "[freenas-proxmox] $*" | tee -a "$LOG_FILE"
}
remove_ui() {
rm -f "$JS_DST" && log "Removed ${JS_DST}"
if [ -f "$TPL" ] && grep -qF 'truenas-storage.js' "$TPL"; then
log "Removing <script> tag from ${TPL}"
sed -i '/truenas-storage\.js/d' "$TPL"
fi
}
restart_pve_services() {
log "Restarting Proxmox VE services ..."
pvedaemon restart && log "pvedaemon restarted"
@ -19,15 +29,15 @@ restart_pve_services() {
case "$1" in
remove)
log "Removing freenas-proxmox ..."
rm -f "$PLUGIN_DST"
log "Plugin removed"
rm -f "$PLUGIN_DST" && log "Removed ${PLUGIN_DST}"
remove_ui
restart_pve_services
log "freenas-proxmox removed. Refresh your Proxmox browser tab."
;;
purge)
log "Purging freenas-proxmox ..."
rm -f "$PLUGIN_DST"
rm -f "$LOG_FILE"
rm -f "$PLUGIN_DST" "$JS_DST" "$LOG_FILE"
[ -f "$TPL" ] && sed -i '/truenas-storage\.js/d' "$TPL"
;;
upgrade|failed-upgrade|disappear|abort-upgrade|abort-remove|abort-deconfigure)
;;

108
ui/truenas-storage.js Normal file
View File

@ -0,0 +1,108 @@
// TrueNAS Custom Storage Plugin — Proxmox VE UI
//
// Registers the 'truenas' storage type in PVE's UI schema and defines the
// configuration panel shown when adding or editing TrueNAS storage.
//
// Loaded by index.html.tpl after pvemanagerlib.js, so PVE.Utils is available.
// Register TrueNAS in the storage type dropdown
PVE.Utils.storageSchema.truenas = {
name: 'TrueNAS (ZFS/iSCSI)',
ipanel: 'TrueNASInputPanel',
faIcon: 'database',
backups: false,
};
Ext.define('PVE.storage.TrueNASInputPanel', {
extend: 'PVE.panel.StorageBase',
initComponent: function () {
let me = this;
// Column 1 — identity and credentials (left side)
me.column1 = [
{
xtype: me.isCreate ? 'proxmoxtextfield' : 'displayfield',
fieldLabel: gettext('TrueNAS Host'),
name: 'truenas_host',
allowBlank: false,
emptyText: gettext('hostname or IP address'),
},
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('API Key'),
name: 'truenas_api_key',
inputType: 'password',
allowBlank: !me.isCreate,
emptyText: me.isCreate
? gettext('Paste API key from TrueNAS UI')
: gettext('unchanged — paste new key to change'),
listeners: {
// On edit: don't submit unless the user types a new value
afterrender: function (field) {
if (!me.isCreate) {
field.submitValue = false;
}
},
change: function (field, value) {
if (!me.isCreate) {
field.submitValue = (value && value.length > 0);
}
},
},
},
{
xtype: me.isCreate ? 'proxmoxtextfield' : 'displayfield',
fieldLabel: gettext('Pool'),
name: 'truenas_pool',
allowBlank: false,
emptyText: gettext('ZFS pool name (e.g. tank)'),
},
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Dataset'),
name: 'truenas_dataset',
allowBlank: true,
emptyText: gettext('Optional (e.g. proxmox)'),
deleteEmpty: !me.isCreate,
},
];
// Column 2 — connection options (right side)
// Note: Nodes selector and Enable checkbox are prepended by StorageBase
me.column2 = [
{
xtype: 'proxmoxcheckbox',
fieldLabel: gettext('Use SSL'),
name: 'truenas_ssl',
checked: true,
uncheckedValue: 0,
},
{
xtype: 'proxmoxcheckbox',
fieldLabel: gettext('Verify SSL Certificate'),
name: 'truenas_ssl_verify',
checked: false,
uncheckedValue: 0,
},
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Portal IP'),
name: 'truenas_portal_ip',
allowBlank: true,
emptyText: gettext('Optional — defaults to TrueNAS host'),
deleteEmpty: !me.isCreate,
},
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Target IQN'),
name: 'truenas_target',
allowBlank: true,
emptyText: gettext('Optional — auto-discovered if blank'),
deleteEmpty: !me.isCreate,
},
];
me.callParent();
},
});