This commit is contained in:
2025-12-05 17:09:05 +01:00
parent 19a91c17c6
commit 45900138fc
2 changed files with 146 additions and 30 deletions

View File

@@ -19,7 +19,12 @@ from port import Portainer
from prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.shortcuts import checkboxlist_dialog
from prompt_toolkit.shortcuts import radiolist_dialog
from prompt_toolkit.application import Application
from prompt_toolkit.layout import Layout
from prompt_toolkit.widgets import RadioList
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout.containers import HSplit
VERSION = "0.1.3"
defaults = {
@@ -32,6 +37,91 @@ defaults = {
}
cur_config = {}
def menu(title, options):
"""
Display a menu and return selected value.
options must be list of (value, text) or text (auto-wrapped)
"""
if isinstance(options[0], str):
options = [(item, item) for item in options]
radio = RadioList(options)
kb = KeyBindings()
@kb.add("enter")
def _(event):
event.app.exit(result=radio.current_value)
@kb.add("q")
def _(event):
event.app.exit(result=None)
layout = Layout(HSplit([radio]))
app = Application(
layout=layout,
key_bindings=kb,
full_screen=True
)
return app.run()
# --------------------------
# MULTI-LEVEL MENU
# --------------------------
def main_menu():
while True:
choice = menu("Main Menu", [
"System",
"Network",
"Services",
"Exit"
])
if choice == "System":
system_menu()
elif choice == "Network":
network_menu()
elif choice == "Services":
services_menu()
elif choice == "Exit":
return
def system_menu():
choice = menu("System Menu", [
"Info",
"Reboot",
"Back"
])
print("SYSTEM →", choice)
def network_menu():
choice = menu("Network Menu", [
"Show IP",
"Restart Networking",
"Back"
])
print("NETWORK →", choice)
def services_menu():
choice = menu("Services", [
"Start Service",
"Stop Service",
"Status",
"Back"
])
print("SERVICES →", choice)
def load_config(defaults=defaults):
'''Load configuration from /myapps/portainer.conf if it exists, else from env vars or defaults.'''
if os.path.exists("/myapps/portainer.conf"):
@@ -212,7 +302,7 @@ def wl(msg):
print(msg)
def prompt_missing_args(args_in, defaults_in, fields, action=None):
def prompt_missing_args(args_in, defaults_in, fields, action=None,stacks=None):
"""
fields = [("arg_name", "Prompt text")]
"""
@@ -227,6 +317,7 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None):
value_in = getattr(args_in, field)
default = defaults_in.get(f"PORTAINER_{field}".upper())
cur_site = defaults_in.get("PORTAINER_SITE".upper())
cur_env = defaults_in.get("PORTAINER_ENVIRONMENT_ID".upper())
if value_in is None:
if default is not None:
prompt_text = f"{text} (default={default}) : "
@@ -241,13 +332,19 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None):
commands = por.endpoints_names
elif field == "stack":
if args.action == "create_stack":
# input(json.dumps(stacks, indent=2))
commands = [
'authentik', 'bitwarden', 'bookstack', 'dockermon', 'fail2ban', 'gitea', 'gitlab', 'grafana',
'home-assistant', 'homepage', 'immich', 'influxdb', 'jupyter', 'kestra', 'mailu3',
'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nextcloud', 'nginx',
'mealie', 'mediacenter', 'mosquitto', 'motioneye', 'n8n', 'nebula', 'nextcloud', 'nginx',
'node-red', 'octoprint', 'ollama', 'pihole', 'portainer-ce', 'rancher', 'registry',
'regsync', 'semaphore', 'unifibrowser', 'uptime-kuma', 'watchtower', 'wazuh', 'webhub',
'wud', 'zabbix-server']
for s in por.all_data['stacks'][defaults_in[f"PORTAINER_ENDPOINT_ID".upper()]]['by_name'].keys():
# print(s)
commands.remove(s)
else:
commands = []
if por._debug:
@@ -280,11 +377,11 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None):
).run()
if value_in is None:
print("Cancelled.")
sys.exit(0)
elif "__ALL__" in value_in:
# User selected "Select ALL"
value_in = commands # all real commands
if "pihole" in value_in:
if action == "delete_stack":
value_in.remove("pihole")
value_in.append("pihole")
@@ -333,7 +430,6 @@ def prompt_missing_args(args_in, defaults_in, fields, action=None):
if field == "site" and value_in != cur_site:
por.get_site(value_in)
if field == "stack" and value_in != cur_site:
os.environ[field] = ",".join(value_in)
else:
@@ -364,35 +460,52 @@ if __name__ == "__main__":
os.system("cls" if os.name == "nt" else "clear")
if args.action is None:
actions = [
"create_stack",
"delete_stack",
"stop_stack",
"start_stack",
"list_stacks",
"update_stack",
"secrets",
"print_all_data",
"list_endpoints",
"list_containers",
"stop_containers",
"start_containers",
"refresh_environment",
"refresh_status",
"update_status",
]
print("Possible actions: \n")
i = 1
for a in actions:
print(f" > {i:>2}. {a}")
i += 1
ans = input("\nSelect action to perform: ")
args.action = actions[int(ans) - 1]
("create_stack","create_stack"),
("delete_stack","delete_stack"),
("stop_stack","stop_stack"),
("start_stack","start_stack"),
("list_stacks","list_stacks"),
("update_stack","update_stack"),
("secrets","secrets"),
("print_all_data","print_all_data"),
("list_endpoints","list_endpoints"),
("list_containers","list_containers"),
("stop_containers","stop_containers"),
("start_containers","start_containers"),
("refresh_environment","refresh_environment"),
("refresh_status","refresh_status"),
("update_status","update_status"),
]
selected_action = radiolist_dialog(
title="Select one service",
text="Choose a service:",
values=actions
).run()
print("Selected:", selected_action)
# print("Possible actions: \n")
# i = 1
# for a in actions:
# print(f" > {i:>2}. {a}")
# i += 1
# ans = input("\nSelect action to perform: ")
args.action = selected_action
os.system("cls" if os.name == "nt" else "clear")
# Example: list endpoints
por = Portainer(cur_config["PORTAINER_SITE"], timeout=args.timeout)
por.set_defaults(cur_config)
if args.debug:
por._debug = True
if args.action == "secrets":
args = prompt_missing_args(
args,
@@ -402,7 +515,6 @@ if __name__ == "__main__":
("endpoint_id", "Endpoint ID"),
],
)
secrets = {
"gitea_runner_registration_token": "8nmKqJhkvYwltmNfF2o9vs0tzo70ufHSQpVg6ymb",
"influxdb2-admin-token": "l4c1j4yd33Du5lo",
@@ -437,6 +549,7 @@ if __name__ == "__main__":
sys.exit()
if args.action == "create_stack":
por.action = "create_stack"
args = prompt_missing_args(
args,
cur_config,
@@ -447,6 +560,7 @@ if __name__ == "__main__":
("stack_mode", "Stack mode (swarm or compose)"),
("deploy_mode", "Deploy mode (git or upload)"),
],
por,
)
por.create_stack(
args.endpoint_id,