Added workload id to portforward object Updated streamlit to show workload name
292 lines
14 KiB
Python
292 lines
14 KiB
Python
import json
|
||
import streamlit as st
|
||
from streamlit_server.utils.helpers import format_timestamp
|
||
|
||
def render_list():
|
||
"""
|
||
Renders a list of container pods, each with its containers.
|
||
"""
|
||
st.header("Container Workload Management")
|
||
|
||
# Initialize session state flags for pod deletion
|
||
for flag in ['pod_delete_all_confirmation_shown', 'pod_delete_all_confirmed',
|
||
'pod_delete_confirmation_shown', 'pod_delete_confirmed','container_delete_confirmation_shown','container_delete_confirmed']:
|
||
if flag not in st.session_state:
|
||
st.session_state[flag] = False
|
||
|
||
# Fetch VDCs
|
||
vdcs = st.session_state.client.get_virtual_data_centers()
|
||
vdc_options = {vdc['name']: vdc['id'] for vdc in vdcs} if vdcs else {}
|
||
|
||
selected_vdc_name = st.selectbox("Filter by Virtual Data Center", list(vdc_options.keys()))
|
||
|
||
# Create Container Workload Form
|
||
with st.expander("Create New Container Workload"):
|
||
with st.form("create_container"):
|
||
selected_vdc_name_create = st.selectbox("VDC", list(vdc_options.keys()))
|
||
container_name = st.text_input("Container Name", value="my-container")
|
||
|
||
# Docker image selection
|
||
docker_image_options = ["nginx", "traefik/whoami:latest", "whoami81", "noisy_container", "joke_container"]
|
||
selected_predefined = st.selectbox("Choose a common image", [""] + docker_image_options)
|
||
custom_input = st.text_input("Or enter custom Docker image", value="")
|
||
|
||
docker_image = custom_input.strip() if custom_input.strip() else selected_predefined.strip()
|
||
|
||
# Port mappings
|
||
st.markdown("**Port Mappings**")
|
||
if 'port_mappings' not in st.session_state:
|
||
st.session_state.port_mappings = [{
|
||
'internal': None, 'external': None,
|
||
'use_dns': True, 'protocol': 'tcp'
|
||
}]
|
||
|
||
for i, mapping in enumerate(st.session_state.port_mappings):
|
||
with st.container():
|
||
cols = st.columns([2, 2, 2, 1])
|
||
with cols[0]:
|
||
internal_port = st.number_input("Internal Port", 1, 65535, value=mapping['internal'] or 1, key=f"internal_{i}")
|
||
with cols[1]:
|
||
external_port = st.number_input("External Port", 1, 65535, value=mapping['external'] or 1, key=f"external_{i}")
|
||
with cols[2]:
|
||
protocol = st.selectbox("Protocol", ["tcp", "udp"], index=0 if mapping['protocol'] == 'tcp' else 1, key=f"protocol_{i}")
|
||
with cols[3]:
|
||
use_dns = st.checkbox("Use DNS", value=False, key=f"use_dns_{i}")
|
||
|
||
submit = st.form_submit_button("Create Container Workload")
|
||
if submit:
|
||
if not docker_image:
|
||
st.warning("Please specify a Docker image.")
|
||
else:
|
||
vdc_id = vdc_options[selected_vdc_name_create]
|
||
port_mappings = []
|
||
for i in range(len(st.session_state.port_mappings)):
|
||
internal = st.session_state.get(f"internal_{i}")
|
||
external = st.session_state.get(f"external_{i}")
|
||
protocol = st.session_state.get(f"protocol_{i}")
|
||
use_dns = st.session_state.get(f"use_dns_{i}")
|
||
if internal and external:
|
||
port_mappings.append({
|
||
"internal": int(internal),
|
||
"external": int(external),
|
||
"protocol": protocol,
|
||
"use_dns": use_dns
|
||
})
|
||
container_payload = {
|
||
"docker_image": docker_image,
|
||
"container_name": container_name,
|
||
"networks": "bridge"
|
||
}
|
||
if port_mappings:
|
||
container_payload["ports"] = port_mappings
|
||
payload = {
|
||
"virtual_data_center": vdc_id,
|
||
"containers": [container_payload]
|
||
}
|
||
result = st.session_state.client.create_container(payload)
|
||
if result:
|
||
st.success("Container workload created successfully!")
|
||
st.session_state.port_mappings = [{
|
||
'internal': None, 'external': None,
|
||
'use_dns': True, 'protocol': 'tcp'
|
||
}]
|
||
st.rerun()
|
||
|
||
if st.button("➕ Add Another Port Mapping"):
|
||
st.session_state.port_mappings.append({
|
||
'internal': None, 'external': None,
|
||
'use_dns': True, 'protocol': 'tcp'
|
||
})
|
||
st.rerun()
|
||
|
||
# List Container Pods
|
||
st.subheader("Existing Container Pods")
|
||
pods = st.session_state.client.get_container_pods()
|
||
|
||
if pods:
|
||
selected_vdc_id = vdc_options[selected_vdc_name]
|
||
filtered_pods = [pod for pod in pods if pod['vdc_id'] == selected_vdc_id]
|
||
|
||
if not st.session_state.pod_delete_all_confirmation_shown:
|
||
if st.button("Delete All Pods", key="delete_all_pods", type="primary"):
|
||
st.session_state.pod_delete_all_confirmation_shown = True
|
||
st.rerun()
|
||
|
||
if st.session_state.pod_delete_all_confirmation_shown and not st.session_state.pod_delete_all_confirmed:
|
||
st.warning("Are you sure you want to delete all pods?")
|
||
col1, col2 = st.columns([1, 1])
|
||
with col1:
|
||
if st.button("Yes, Delete All Pods", key="confirm_delete_all_pods"):
|
||
st.session_state.pod_delete_all_confirmed = True
|
||
st.rerun()
|
||
with col2:
|
||
if st.button("Cancel", key="cancel_delete_all_pods"):
|
||
st.session_state.pod_delete_all_confirmation_shown = False
|
||
st.rerun()
|
||
|
||
if st.session_state.pod_delete_all_confirmed:
|
||
deletion_count = 0
|
||
for pod in filtered_pods:
|
||
result = st.session_state.client.delete_container_pod(pod['pod_id'])
|
||
if result:
|
||
deletion_count += 1
|
||
st.success(f"Successfully deleted {deletion_count} pods.")
|
||
st.session_state.pod_delete_all_confirmation_shown = False
|
||
st.session_state.pod_delete_all_confirmed = False
|
||
st.rerun()
|
||
|
||
for pod in filtered_pods:
|
||
with st.expander(f"🛰️ Pod: {pod['pod_name']} (ID: {pod['pod_id']})"):
|
||
st.markdown(f"**Virtual Data Center ID:** `{pod['vdc_id']}`")
|
||
st.markdown(f"**Region ID:** `{pod.get('region_id', 'N/A')}`")
|
||
st.markdown(f"**NSController ID:** `{pod['nscontroller_workload_id']}`")
|
||
|
||
if pod.get('port_forwardings'):
|
||
st.markdown("🔁 **Port Forwarding:**")
|
||
for pf in pod['port_forwardings']:
|
||
dns_status = "🌐 DNS" if pf.get('dns_record_id') else "🚫 No DNS"
|
||
cols = st.columns([1, 2, 2,1])
|
||
with cols[0]: st.markdown(f"**{pf['protocol'].upper()}**")
|
||
with cols[1]: st.text(f"{pf['external_port']} → {pf['internal_port']}")
|
||
with cols[2]: st.text(dns_status)
|
||
with cols[3]: st.text(pf['container_name'])
|
||
link = pf.get('dns_record_hostname') or f"{pf['ip_address']}:{pf['external_port']}"
|
||
st.page_link(f"http://{link}", label=f"http://{link}")
|
||
st.markdown("---")
|
||
else:
|
||
st.text("No port forwardings")
|
||
|
||
for container in pod['containers']:
|
||
with st.container():
|
||
cols = st.columns([3, 2, 1])
|
||
with cols[0]:
|
||
st.markdown(f"**Container Name:** {container['container_name']}")
|
||
st.text(f"Status: {container['status']}")
|
||
with cols[1]:
|
||
st.text(f"Container ID: {container['container_id']}")
|
||
with cols[2]:
|
||
if st.button("View Details", key=f"view_{container['container_id']}"):
|
||
st.session_state.selected_resource = container
|
||
st.session_state.view_type = 'detail'
|
||
st.session_state.selected_resource_type = 'container'
|
||
st.rerun()
|
||
st.page_link(
|
||
f"http://127.0.0.5:5000/ui/container_logs/{container['container_id']}",
|
||
label="Console & Logs",
|
||
icon="🖥️"
|
||
)
|
||
st.markdown("---")
|
||
|
||
if st.button(f"🗑️ Delete Pod {pod['pod_name']}", key=f"delete_pod_{pod['pod_id']}"):
|
||
result = st.session_state.client.delete_container_pod(pod['pod_id'])
|
||
if result:
|
||
st.success(f"Deleted Pod {pod['pod_name']}")
|
||
st.rerun()
|
||
|
||
else:
|
||
st.info("No pods found in the selected VDC.")
|
||
|
||
|
||
def render_detail():
|
||
|
||
"""
|
||
Renders the details of a specific container workload.
|
||
"""
|
||
|
||
container = st.session_state.client.get_container(st.session_state.selected_resource['container_id'])
|
||
if not container:
|
||
st.error("Container workload not found.")
|
||
st.session_state.selected_resource = None
|
||
st.session_state.view_type = 'list'
|
||
st.session_state.selected_resource_type = None
|
||
return
|
||
|
||
st.title(f"Container Workload: {container['container_id']}")
|
||
if st.button("Back to list view"):
|
||
st.session_state.view_type = 'list'
|
||
st.rerun()
|
||
|
||
st.header("Container Workload Details")
|
||
col1, col2 = st.columns(2)
|
||
with col1:
|
||
st.markdown(f"**ID:** `{container['id']}`")
|
||
st.markdown(f"**Created:** {format_timestamp(container.get('created_at', 'Unknown'))}")
|
||
st.markdown(f"**VDC ID:** {container['vdc_id']}")
|
||
st.markdown(f"**Workload Host ID:** {container.get('container_host_id', 'N/A')}")
|
||
with col2:
|
||
if container.get('updated_at'):
|
||
st.markdown(f"**Last Updated:** {format_timestamp(container['updated_at'])}")
|
||
st.markdown(f"**Status:** {container['status']}")
|
||
st.markdown(f"**Launch Parameters:** {container.get('launch_params', 'N/A')}")
|
||
# Define container states once
|
||
CONTAINER_STATES = [
|
||
"running", "stopped", "pending",
|
||
"failed-deleted", "pending-deleted",
|
||
"pending-allocation", "failed-allocation",
|
||
"pending-allocated", "dead"
|
||
]
|
||
|
||
# Edit Container Workload Form
|
||
with st.expander("Edit Container Workload"):
|
||
with st.form("edit_container"):
|
||
new_status = st.selectbox(
|
||
"Status",
|
||
CONTAINER_STATES,
|
||
index=CONTAINER_STATES.index(container['status'])
|
||
)
|
||
new_launch_params = st.text_input("Launch Parameters", value=container.get('launch_params', ''))
|
||
submit = st.form_submit_button("Update Container Workload")
|
||
|
||
if submit:
|
||
result = st.session_state.client.edit_container(container['id'], {
|
||
"status": new_status,
|
||
"launch_params": new_launch_params
|
||
})
|
||
if result:
|
||
st.success("Container workload updated successfully!")
|
||
st.session_state.selected_resource = container
|
||
st.session_state.view_type = 'detail'
|
||
st.session_state.selected_resource_type = 'container'
|
||
st.rerun()
|
||
|
||
# Delete functionality
|
||
if not st.session_state.container_delete_confirmation_shown:
|
||
if st.button("Delete container", key="delete_container", type="primary", help="This will delete visible container"):
|
||
st.session_state.container_delete_confirmation_shown = True
|
||
st.rerun()
|
||
|
||
# Show confirmation if delete was clicked
|
||
if st.session_state.container_delete_confirmation_shown and not st.session_state.container_delete_confirmed:
|
||
st.warning("Are you sure you want to delete container? This action cannot be undone.")
|
||
confirm_col1, confirm_col2 = st.columns([1, 1])
|
||
with confirm_col1:
|
||
if st.button("Yes, Delete", key="confirm_delete_container"):
|
||
st.session_state.container_delete_confirmed = True
|
||
st.rerun()
|
||
with confirm_col2:
|
||
if st.button("Cancel", key="cancel_delete_container"):
|
||
st.session_state.container_delete_confirmation_shown = False
|
||
st.rerun()
|
||
|
||
# Perform deletion if confirmed
|
||
if st.session_state.container_delete_confirmed:
|
||
result = st.session_state.client.delete_container(container['id'])
|
||
st.session_state.container_delete_confirmation_shown = False
|
||
st.session_state.container_delete_confirmed = False
|
||
if result:
|
||
st.success("Container workload deleted successfully!")
|
||
st.session_state.selected_resource = None
|
||
st.session_state.view_type = 'list'
|
||
st.session_state.selected_resource_type = 'container'
|
||
st.rerun()
|
||
|
||
st.success(f"Successfully deleted container.")
|
||
# Reset the confirmation flags
|
||
st.session_state.container_delete_confirmation_shown = False
|
||
st.session_state.container_delete_confirmed = False
|
||
|
||
|
||
if st.session_state.view_type == 'detail' and st.session_state.selected_resource_type=='container':
|
||
render_detail()
|
||
else:
|
||
render_list() |