mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-21 13:02:02 +00:00
fix(oci): preserve Monitor stack editor action
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
"""Keep the Monitor OCI editor path distinct from coordinated stack recreation."""
|
||||||
|
import ast
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from types import ModuleType, SimpleNamespace
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
MENU = ROOT / 'oci/src/proxmenux_oci/management.py'
|
||||||
|
CLI = ROOT / 'oci/src/proxmenux_oci/cli.py'
|
||||||
|
MONITOR = ROOT / 'AppImage/components/virtual-machines.tsx'
|
||||||
|
|
||||||
|
|
||||||
|
def extracted_stack_manager(scope):
|
||||||
|
source = MENU.read_text(encoding='utf-8').replace(
|
||||||
|
' from .stack_recreation import modify_stack\n', '')
|
||||||
|
node = next(item for item in ast.parse(source).body
|
||||||
|
if isinstance(item, ast.FunctionDef) and item.name == '_manage_stack')
|
||||||
|
exec(compile(ast.fix_missing_locations(ast.Module(body=[node], type_ignores=[])),
|
||||||
|
str(MENU), 'exec'), scope)
|
||||||
|
return scope['_manage_stack']
|
||||||
|
|
||||||
|
|
||||||
|
class MonitorModifyContractTests(TestCase):
|
||||||
|
def test_monitor_sends_modify_for_a_stack_and_recreate_for_a_single_instance(self):
|
||||||
|
source = MONITOR.read_text(encoding='utf-8')
|
||||||
|
self.assertIn('action: "update" | "modify" | "recreate" | "recover"', source)
|
||||||
|
self.assertIn('action: ociInstance.stack ? "modify" : "recreate"', source)
|
||||||
|
|
||||||
|
def test_cli_accepts_the_monitor_modify_action(self):
|
||||||
|
self.assertIn('choices=("update", "modify", "recreate")', CLI.read_text(encoding='utf-8'))
|
||||||
|
|
||||||
|
def test_modify_action_opens_the_stack_editor_without_running_lifecycle(self):
|
||||||
|
primary = {'stack': {'members': [{'vmid': 101}]}}
|
||||||
|
instances = ModuleType('oci_instances')
|
||||||
|
instances.ROOT = Path('/inert')
|
||||||
|
instances.read = lambda *_: primary
|
||||||
|
modify_stack = Mock(return_value=True)
|
||||||
|
run_lifecycle = Mock()
|
||||||
|
manager = extracted_stack_manager({
|
||||||
|
'sys': SimpleNamespace(path=[]),
|
||||||
|
'Path': Path,
|
||||||
|
'modify_stack': modify_stack,
|
||||||
|
'_run_lifecycle': run_lifecycle,
|
||||||
|
})
|
||||||
|
ui = SimpleNamespace()
|
||||||
|
with patch.dict(sys.modules, {
|
||||||
|
'oci_instances': instances,
|
||||||
|
'oci_stack_replay': ModuleType('oci_stack_replay'),
|
||||||
|
}):
|
||||||
|
result = manager(Path('/inert'), ui, {'vmid': 101}, action='modify')
|
||||||
|
|
||||||
|
self.assertTrue(result)
|
||||||
|
modify_stack.assert_called_once_with(Path('/inert'), ui, primary, run_lifecycle)
|
||||||
|
run_lifecycle.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import unittest
|
||||||
|
unittest.main()
|
||||||
@@ -954,7 +954,7 @@ export function VirtualMachines() {
|
|||||||
// A container restored from a backup carries the mark of its installation
|
// A container restored from a backup carries the mark of its installation
|
||||||
// and has no record on this host until it is recovered from the menu.
|
// and has no record on this host until it is recovered from the menu.
|
||||||
const [ociRestored, setOciRestored] = useState(false)
|
const [ociRestored, setOciRestored] = useState(false)
|
||||||
const [ociAction, setOciAction] = useState<{ vmid: number; action: "update" | "recreate" | "recover" } | null>(null)
|
const [ociAction, setOciAction] = useState<{ vmid: number; action: "update" | "modify" | "recreate" | "recover" } | null>(null)
|
||||||
|
|
||||||
// Firewall log state — fetched only when the operator opens that tab
|
// Firewall log state — fetched only when the operator opens that tab
|
||||||
// so a CT/VM without firewall use doesn't pay the pvesh cost on every
|
// so a CT/VM without firewall use doesn't pay the pvesh cost on every
|
||||||
@@ -5341,7 +5341,10 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className={neutralBtnCls}
|
className={neutralBtnCls}
|
||||||
onClick={() => setOciAction({ vmid: selectedVM.vmid, action: "recreate" })}
|
onClick={() => setOciAction({
|
||||||
|
vmid: selectedVM.vmid,
|
||||||
|
action: ociInstance.stack ? "modify" : "recreate",
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<RotateCcw className="h-4 w-4 mr-1.5" />
|
<RotateCcw className="h-4 w-4 mr-1.5" />
|
||||||
{t("vmLxc.ociUpdates.recreate")}
|
{t("vmLxc.ociUpdates.recreate")}
|
||||||
|
|||||||
@@ -595,9 +595,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
rclone_parser.add_argument("--host", default="auto")
|
rclone_parser.add_argument("--host", default="auto")
|
||||||
rclone_parser.add_argument("--dry-run", action="store_true")
|
rclone_parser.add_argument("--dry-run", action="store_true")
|
||||||
subparsers.add_parser("recover", help="Register again the OCI applications restored from a backup")
|
subparsers.add_parser("recover", help="Register again the OCI applications restored from a backup")
|
||||||
manage_parser = subparsers.add_parser("manage", help="Update or recreate one installed OCI instance")
|
manage_parser = subparsers.add_parser("manage", help="Update, modify or recreate one installed OCI instance")
|
||||||
manage_parser.add_argument("vmid", type=int)
|
manage_parser.add_argument("vmid", type=int)
|
||||||
manage_parser.add_argument("--action", choices=("update", "recreate"), required=True)
|
manage_parser.add_argument("--action", choices=("update", "modify", "recreate"), required=True)
|
||||||
manage_parser.add_argument("--keep-backup", metavar="STORAGE",
|
manage_parser.add_argument("--keep-backup", metavar="STORAGE",
|
||||||
help="Keep the backup taken before the update in this Proxmox storage")
|
help="Keep the backup taken before the update in this Proxmox storage")
|
||||||
manage_parser.add_argument("--unattended", action="store_true",
|
manage_parser.add_argument("--unattended", action="store_true",
|
||||||
|
|||||||
@@ -285,6 +285,8 @@ def manage_instance(project, ui, row, action=None, lifecycle_args=()):
|
|||||||
return False
|
return False
|
||||||
if row['stack']:
|
if row['stack']:
|
||||||
return _manage_stack(project, ui, row, action, lifecycle_args)
|
return _manage_stack(project, ui, row, action, lifecycle_args)
|
||||||
|
if action == 'modify':
|
||||||
|
return False
|
||||||
if not row['pending']:
|
if not row['pending']:
|
||||||
if row['status'] != 'installed' or row['reason'] != 'matched':
|
if row['status'] != 'installed' or row['reason'] != 'matched':
|
||||||
ui.message(translate('The instance identity or status must be reviewed before updating.'), translate('OCI management'))
|
ui.message(translate('The instance identity or status must be reviewed before updating.'), translate('OCI management'))
|
||||||
|
|||||||
Reference in New Issue
Block a user