diff --git a/requirements.txt b/requirements.txt index d5c5b93..d368ce0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,4 +19,7 @@ libvirt-python # I think requires libvirt-dev? # Alternativly apt-get install libvirt-dev netifaces -jwt \ No newline at end of file +jwt +docker +python-dotenv +psutil \ No newline at end of file diff --git a/sandbox/router_container/Dockerfile b/sandbox/router_container/Dockerfile new file mode 100644 index 0000000..78726a4 --- /dev/null +++ b/sandbox/router_container/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:22.04 + +# Avoid interactive prompts during package install +ENV DEBIAN_FRONTEND=noninteractive + +# Install required packages +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + python3 \ + curl \ + iproute2 \ + iptables \ + net-tools \ + tcpdump \ + iputils-ping \ + arp-scan && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Enable packet forwarding +RUN echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \ + echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf + +# Apply sysctl settings at container startup +CMD sysctl -p && tail -f /dev/null diff --git a/streamlit_server/views/containers.py b/streamlit_server/views/containers.py index 180fd48..34f8302 100644 --- a/streamlit_server/views/containers.py +++ b/streamlit_server/views/containers.py @@ -9,14 +9,10 @@ def render_list(): st.header("Container Workload Management") # Initialize session state flags for pod deletion - if 'pod_delete_all_confirmation_shown' not in st.session_state: - st.session_state.pod_delete_all_confirmation_shown = False - if 'pod_delete_all_confirmed' not in st.session_state: - st.session_state.pod_delete_all_confirmed = False - if 'pod_delete_confirmation_shown' not in st.session_state: - st.session_state.pod_delete_confirmation_shown = False - if 'pod_delete_confirmed' not in st.session_state: - st.session_state.pod_delete_confirmed = False + for flag in ['pod_delete_all_confirmation_shown', 'pod_delete_all_confirmed', + 'pod_delete_confirmation_shown', 'pod_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() @@ -26,134 +22,80 @@ def render_list(): # Create Container Workload Form with st.expander("Create New Container Workload"): - # The form only contains form-specific widgets with st.form("create_container"): - # Dropdown for VDC selection selected_vdc_name_create = st.selectbox("VDC", list(vdc_options.keys())) - container_name = st.text_input("Container Name", value="my-container") - # Suggestions for common images - docker_image_options = ["nginx", "traefik/whoami:latest", "whoami81","noisy_container"] - docker_image = st.selectbox( - "Docker Image", - options=docker_image_options + ["Custom..."], - index=0 - ) + # 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="") - # If 'Custom...' is selected, let the user input their own image - if docker_image == "Custom...": - docker_image = st.text_input("Custom Docker Image", value="") + docker_image = custom_input.strip() if custom_input.strip() else selected_predefined.strip() - # Port mappings (multiple) + # Port mappings st.markdown("**Port Mappings**") - - # Initialize port mappings in session state if not exists if 'port_mappings' not in st.session_state: st.session_state.port_mappings = [{ - 'internal': None, - 'external': None, - 'use_dns': True, - 'protocol': 'tcp' + 'internal': None, 'external': None, + 'use_dns': True, 'protocol': 'tcp' }] - # Display each port mapping for i, mapping in enumerate(st.session_state.port_mappings): with st.container(): - cols = st.columns([2, 2, 2, 1, 1]) + cols = st.columns([2, 2, 2, 1]) with cols[0]: - internal_port = st.number_input( - "Internal Port", - min_value=1, - max_value=65535, - step=1, - value=mapping['internal'], - key=f"internal_{i}" - ) + 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", - min_value=1, - max_value=65535, - step=1, - value=mapping['external'], - key=f"external_{i}" - ) + 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}" - ) + 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=mapping['use_dns'], - key=f"use_dns_{i}" - ) - # with cols[4]: - # # Only show remove button for additional port mappings - # if i > 0: - # if st.button("❌", key=f"remove_{i}"): - # st.session_state.port_mappings.pop(i) - # st.rerun() + use_dns = st.checkbox("Use DNS", value=mapping['use_dns'], key=f"use_dns_{i}") - # Form submit button for creating the container submit = st.form_submit_button("Create Container Workload") - - if submit and selected_vdc_name_create and container_name: - vdc_id = vdc_options[selected_vdc_name_create] + 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 = { + "vdc": 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() - container_payload = { - "docker_image": docker_image, - "container_name": container_name, - "networks": "bridge", # Default network - } - - # Prepare port mappings from session state - 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: # Only add if both ports are specified - port_mappings.append({ - "internal": int(internal), - "external": int(external), - "protocol": protocol, - "use_dns": use_dns - }) - - if port_mappings: - container_payload["ports"] = port_mappings - - payload = { - "vdc": vdc_id, - "containers": [container_payload] - } - - result = st.session_state.client.create_container(payload) - if result: - st.success("Container workload created successfully!") - # Clear port mappings after successful creation - st.session_state.port_mappings = [{ - 'internal': None, - 'external': None, - 'use_dns': True, - 'protocol': 'tcp' - }] - st.rerun() - - # The button to add a new port mapping is now placed outside the form if st.button("➕ Add Another Port Mapping"): st.session_state.port_mappings.append({ - 'internal': None, - 'external': None, - 'use_dns': True, - 'protocol': 'tcp' + 'internal': None, 'external': None, + 'use_dns': True, 'protocol': 'tcp' }) st.rerun() @@ -162,24 +104,22 @@ def render_list(): pods = st.session_state.client.get_container_pods() if pods: - # Filter by selected VDC selected_vdc_id = vdc_options[selected_vdc_name] filtered_pods = [pod for pod in pods if pod['vdc_id'] == selected_vdc_id] - # Delete All Pods if not st.session_state.pod_delete_all_confirmation_shown: - if st.button("Delete All Pods", key="delete_all_pods", type="primary", help="This will delete all visible pods"): + 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? This action cannot be undone.") - confirm_col1, confirm_col2 = st.columns([1, 1]) - with confirm_col1: + 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 confirm_col2: + with col2: if st.button("Cancel", key="cancel_delete_all_pods"): st.session_state.pod_delete_all_confirmation_shown = False st.rerun() @@ -195,37 +135,26 @@ def render_list(): st.session_state.pod_delete_all_confirmed = False st.rerun() - # Render Pods nicely for pod in filtered_pods: - with st.expander(f"🛰️ Pod: {pod['pod_name']} (ID: {pod['pod_id']})", expanded=False): + 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']}`") - # Port Forwardings - if 'port_forwardings' in pod and pod['port_forwardings']: + 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, 3, 2]) - 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) - - if pf.get('dns_record_hostname'): - st.page_link(f"http://{pf['dns_record_hostname']}", label=f"http://{pf['dns_record_hostname']}") - else: - st.page_link(f"http://{pf['ip_address']}:{pf['external_port']}", label=f"http://{pf['ip_address']}:{pf['external_port']}") - + 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) + 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") - # Container List for container in pod['containers']: with st.container(): cols = st.columns([3, 2, 1]) @@ -247,7 +176,6 @@ def render_list(): ) st.markdown("---") - # Delete pod button 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: diff --git a/streamlit_server/views/workload_hosts.py b/streamlit_server/views/workload_hosts.py index 2c38d2d..fe21262 100644 --- a/streamlit_server/views/workload_hosts.py +++ b/streamlit_server/views/workload_hosts.py @@ -15,6 +15,16 @@ def render_list(): col1, col2, col3 = st.columns([3, 1, 1]) with col1: st.markdown(f"### {host['name']}") + + # Status indicator + status = host.get('status', 'unknown').lower() + if status == 'online': + st.markdown("**🟢 Online**") + elif status == 'offline': + st.markdown("**🔴 Offline**") + else: + st.markdown("**⚪ Unknown**") + st.text(f"Hostname: {host['hostname']}") st.text(f"Region ID: {host['region_id']}") # TODO - this bit doesnt work, we arent pulling the labels when we pull the lists of hosts