uplifted the pod concept
added a use_dns flag at container creation time
This commit is contained in:
+16
-1
@@ -299,4 +299,19 @@ class IaaSClient:
|
||||
list: List of label dictionaries or None if error occurs
|
||||
"""
|
||||
endpoint = f'workload_hosts/{host_id}/labels'
|
||||
return self._make_request('GET', endpoint)
|
||||
return self._make_request('GET', endpoint)
|
||||
|
||||
|
||||
# --- Container Pod CRUD ---
|
||||
|
||||
def get_container_pods(self):
|
||||
"""Fetch all container pods"""
|
||||
return self._make_request('GET', 'workloads/pods')
|
||||
|
||||
def get_container_pod(self, pod_id):
|
||||
"""Fetch a single container pod by ID"""
|
||||
return self._make_request('GET', f'workloads/pods/{pod_id}')
|
||||
|
||||
def delete_container_pod(self, pod_id):
|
||||
"""Delete (mark pending-deleted) a container pod"""
|
||||
return self._make_request('DELETE', f'workloads/pods/{pod_id}')
|
||||
|
||||
@@ -2,7 +2,7 @@ import random
|
||||
from flask import json, request, jsonify, abort
|
||||
import requests
|
||||
from app import app, db, logger
|
||||
from app.models.models import Workload, WorkloadHost, WorkloadHostPortMapping, VirtualDataCenter, Label, VolumeWorkloadMapping, ContainerPod, ContainerPodContainer
|
||||
from app.models.models import Workload, WorkloadHost, WorkloadHostPortMapping, VirtualDataCenter, Label, VolumeWorkloadMapping, ContainerPod, ContainerPodContainer, CloudflareDNSRecord, CloudflareTunnel
|
||||
from app.models.network import Network, NetworkPort
|
||||
from datetime import datetime
|
||||
from app.controller import api_bp
|
||||
@@ -44,13 +44,20 @@ def validate_payload(payload):
|
||||
raise ValueError("'containers' key is missing.")
|
||||
if not isinstance(payload['containers'], list) or len(payload['containers']) == 0:
|
||||
raise ValueError("'containers' must be a list with at least one container.")
|
||||
|
||||
|
||||
# Initialize the sanitized payload
|
||||
sanitized_payload = {
|
||||
'vdc': payload['vdc'],
|
||||
'containers': []
|
||||
}
|
||||
|
||||
|
||||
#Confirm if the use_dns flag was set on the request at pod lvel or not
|
||||
if 'use_dns' in payload:
|
||||
sanitized_payload['use_dns']=False
|
||||
if str(payload['use_dns']).lower=="true" or payload['use_dns']==True:
|
||||
sanitized_payload['use_dns']=True
|
||||
|
||||
|
||||
# Validate each container in the 'containers' list and build the sanitized version
|
||||
for container in payload['containers']:
|
||||
if not isinstance(container, dict):
|
||||
@@ -118,6 +125,7 @@ def validate_payload(payload):
|
||||
else:
|
||||
raise ValueError("'networks' must be a string or a list of strings.")
|
||||
|
||||
|
||||
# Add the sanitized container to the sanitized payload
|
||||
sanitized_payload['containers'].append(sanitized_container)
|
||||
|
||||
@@ -148,6 +156,7 @@ def add_container_workload():
|
||||
|
||||
# Validate incoming data
|
||||
validated_data = validate_payload(request_data)
|
||||
logger.debug(validated_data)
|
||||
|
||||
request_vdc = VirtualDataCenter.query.filter_by(id=uuid.UUID(validated_data['vdc'])).first()
|
||||
if request_vdc is None:
|
||||
@@ -171,7 +180,9 @@ def add_container_workload():
|
||||
# --- Step 1: Create a new Pod ---
|
||||
new_pod = ContainerPod(
|
||||
name=f"Pod_{uuid.uuid4()}",
|
||||
workload_host_id=selected_host.id
|
||||
workload_host_id=selected_host.id,
|
||||
vdc_id=request_vdc.id
|
||||
|
||||
)
|
||||
db.session.add(new_pod)
|
||||
db.session.flush() # Flush so we get new_pod.id
|
||||
@@ -249,18 +260,6 @@ def add_container_workload():
|
||||
external_port=assigned_external_port
|
||||
)
|
||||
db.session.add(new_port_mapping)
|
||||
api_token = "ri6lIjM-aRJBY_xZ82w0Haew93U6YgZYHi5jby1-"
|
||||
account_id = "5095a74b62fee53cc5d997c67443bac5"
|
||||
zone_id = "e2cafdd8929869d5db885d8824f514b5"
|
||||
logger.info("Adding Cloudflare")
|
||||
cloudflare_manager = CloudflareTunnelManager(api_token, account_id, zone_id)
|
||||
info = cloudflare_manager.setup_tunnel(
|
||||
tunnel_name=str(new_container.id),
|
||||
dns_hostname=f"{new_container.id}.hawkvelt.tech",
|
||||
local_ip_port=f"127.0.0.1:{_container['ports'][0]['internal']}")
|
||||
cloudflare_tunnel_token=info['token']
|
||||
|
||||
|
||||
|
||||
client_response.append({
|
||||
"pod_id": str(new_pod.id),
|
||||
@@ -269,9 +268,31 @@ def add_container_workload():
|
||||
"container_status": "pending-allocation",
|
||||
})
|
||||
|
||||
if validated_data['use_dns']:
|
||||
logger.debug("use_dns set to true, building Cloudflare tunnel")
|
||||
|
||||
api_token = "ri6lIjM-aRJBY_xZ82w0Haew93U6YgZYHi5jby1-"
|
||||
account_id = "5095a74b62fee53cc5d997c67443bac5"
|
||||
zone_id = "e2cafdd8929869d5db885d8824f514b5"
|
||||
logger.info("Adding Cloudflare")
|
||||
cloudflare_manager = CloudflareTunnelManager(api_token, account_id, zone_id, logger)
|
||||
info = cloudflare_manager.setup_tunnel(
|
||||
tunnel_name=f"tun_{new_container.id}",
|
||||
dns_hostname=f"{new_container.id}.hawkvelt.tech",
|
||||
local_ip_port=f"127.0.0.1:{_container['ports'][0]['internal']}")
|
||||
cloudflare_tunnel_token=info['token']
|
||||
new_cloudflare_tunnel=CloudflareTunnel()
|
||||
new_cloudflare_tunnel.token=cloudflare_tunnel_token
|
||||
new_cloudflare_tunnel.account_id=account_id
|
||||
new_cloudflare_tunnel.tunnel_id=info['tunnel_id']
|
||||
new_cloudflare_tunnel.name=info['tunnel_name']
|
||||
new_cloudflare_tunnel.tunnel_secret=info['tunnel_secret']
|
||||
new_cloudflare_tunnel.nscontroller_workload_id=new_nscontroller.id
|
||||
db.session.add(new_cloudflare_tunnel)
|
||||
db.session.flush()
|
||||
logger.info("Adding Cloudflare complete")
|
||||
|
||||
|
||||
# --- Step 4b: If Cloudflare tunnel token was generated, create sidecar ---
|
||||
if cloudflare_tunnel_token:
|
||||
sidecar_container_name = f"cloudflared-sidecar-{new_container.id}"
|
||||
|
||||
sidecar_launch_params = {
|
||||
@@ -306,6 +327,8 @@ def add_container_workload():
|
||||
"container_name": sidecar_workload.name,
|
||||
"container_status": "pending-allocation",
|
||||
})
|
||||
else:
|
||||
logger.debug("use_dns not True, skipping Cloudflare")
|
||||
|
||||
# --- Step 5: Update NSController launch params with final ports ---
|
||||
new_nscontroller.launch_params = json.dumps(nscontroller_launch_params)
|
||||
@@ -554,3 +577,89 @@ def delete_container_workload(workload_id):
|
||||
def get_container_workloads():
|
||||
workloads = Workload.query.filter(or_(Workload.workload_type == "Container", Workload.workload_type == "NSController"), Workload.deleted==False).all()
|
||||
return jsonify([workload.to_json() for workload in workloads])
|
||||
|
||||
@api_bp.route('/workloads/pods', methods=['GET'])
|
||||
def get_pods():
|
||||
pods = ContainerPod.query.filter_by(deleted=False).all()
|
||||
response = []
|
||||
|
||||
for pod in pods:
|
||||
|
||||
response.append({
|
||||
"pod_id": str(pod.id),
|
||||
"pod_name": pod.name,
|
||||
"workload_host_id": str(pod.workload_host_id),
|
||||
"vdc_id": str(pod.vdc_id),
|
||||
"region_id": str(pod.vdc.region_id) if pod.vdc else None,
|
||||
"nscontroller_workload_id": str(pod.nscontroller_workload_id),
|
||||
"containers": [
|
||||
{
|
||||
"container_id": str(mapping.container.id),
|
||||
"container_name": mapping.container.name,
|
||||
"status": mapping.container.status,
|
||||
"launch_params": json.loads(mapping.container.launch_params or "{}")
|
||||
}
|
||||
for mapping in pod.container_mappings
|
||||
]
|
||||
})
|
||||
|
||||
return jsonify(response), 200
|
||||
|
||||
@api_bp.route('/workloads/pods/<pod_id>', methods=['GET'])
|
||||
def get_pod(pod_id):
|
||||
try:
|
||||
pod_uuid = uuid.UUID(pod_id)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Invalid pod ID"}), 404
|
||||
|
||||
pod = ContainerPod.query.filter_by(id=pod_uuid, deleted=False).first_or_404()
|
||||
|
||||
response = {
|
||||
"pod_id": str(pod.id),
|
||||
"pod_name": pod.name,
|
||||
"workload_host_id": str(pod.workload_host_id),
|
||||
"vdc_id": str(pod.vdc_id),
|
||||
"region_id": str(pod.vdc.region_id) if pod.vdc else None,
|
||||
"nscontroller_workload_id": str(pod.nscontroller_workload_id),
|
||||
"containers": [
|
||||
{
|
||||
"container_id": str(mapping.container.id),
|
||||
"container_name": mapping.container.name,
|
||||
"status": mapping.container.status,
|
||||
"launch_params": json.loads(mapping.container.launch_params or "{}")
|
||||
}
|
||||
for mapping in pod.container_mappings
|
||||
]
|
||||
}
|
||||
|
||||
return jsonify(response), 200
|
||||
|
||||
@api_bp.route('/workloads/pods/<pod_id>', methods=['DELETE'])
|
||||
def delete_pod(pod_id):
|
||||
try:
|
||||
pod_uuid = uuid.UUID(pod_id)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Invalid pod ID"}), 404
|
||||
|
||||
pod = ContainerPod.query.filter_by(id=pod_uuid, deleted=False).first_or_404()
|
||||
|
||||
# Cleanup host port mappings
|
||||
for mapping in pod.container_mappings:
|
||||
port_mappings = WorkloadHostPortMapping.query.filter_by(container_workload_id=mapping.container.id).all()
|
||||
for pm in port_mappings:
|
||||
db.session.delete(pm)
|
||||
|
||||
# Set desired state on the pod
|
||||
pod.status = "pending-deleted"
|
||||
db.session.add(pod)
|
||||
|
||||
# Also optionally mark all containers inside as "pending-deleted" (for tracking)
|
||||
for mapping in pod.container_mappings:
|
||||
container = mapping.container
|
||||
container.set_status("pending-deleted")
|
||||
db.session.add(container)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
logger.info(f"Marked pod {pod.id} and its containers as pending-deleted")
|
||||
return jsonify({'message': 'Pod and containers set to pending-deleted'}), 200
|
||||
|
||||
+39
-2
@@ -419,17 +419,17 @@ class ContainerPodContainer(db.Model):
|
||||
pod = relationship("ContainerPod", back_populates="container_mappings")
|
||||
container = relationship("Workload", back_populates="pod_mappings")
|
||||
|
||||
|
||||
class ContainerPod(BaseModel):
|
||||
__tablename__ = "container_pods"
|
||||
|
||||
workload_host_id = Column(UUID(as_uuid=True), ForeignKey("workload_hosts.id"))
|
||||
nscontroller_workload_id = Column(UUID(as_uuid=True), ForeignKey("workloads.id"))
|
||||
vdc_id = Column(UUID(as_uuid=True), ForeignKey("vdcs.id"), nullable=False) # <-- ADD THIS
|
||||
|
||||
workload_host = relationship("WorkloadHost", backref="container_pods")
|
||||
nscontroller_workload = relationship("Workload", foreign_keys=[nscontroller_workload_id])
|
||||
|
||||
container_mappings = relationship("ContainerPodContainer", back_populates="pod", cascade="all, delete-orphan")
|
||||
vdc = relationship("VirtualDataCenter") # optional but nice for easy joins
|
||||
|
||||
class Image(BaseModel):
|
||||
__tablename__ = "images"
|
||||
@@ -501,3 +501,40 @@ class User(db.Model):
|
||||
}
|
||||
|
||||
|
||||
class CloudflareTunnel(BaseModel):
|
||||
__tablename__ = "cloudflare_tunnels"
|
||||
|
||||
account_id = Column(String, nullable=False)
|
||||
tunnel_id = Column(String, nullable=False, unique=True)
|
||||
tunnel_secret = Column(String, nullable=False)
|
||||
token = Column(String, nullable=False)
|
||||
associated_hostname = Column(String, nullable=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
nscontroller_workload_id = Column(UUID(as_uuid=True), ForeignKey("workloads.id"))
|
||||
|
||||
def to_json(self):
|
||||
data = super().to_json()
|
||||
# Optionally hide the secret/token from normal to_json if you want
|
||||
data.pop("tunnel_secret", None)
|
||||
data.pop("token", None)
|
||||
return data
|
||||
|
||||
def to_sensitive_json(self):
|
||||
"""Return full JSON including secrets"""
|
||||
return super().to_json()
|
||||
|
||||
|
||||
class CloudflareDNSRecord(BaseModel):
|
||||
__tablename__ = "cloudflare_dns_records"
|
||||
|
||||
zone_id = Column(String, nullable=False)
|
||||
dns_record_id = Column(String, nullable=False, unique=True)
|
||||
hostname = Column(String, nullable=False)
|
||||
record_type = Column(String, default="CNAME", nullable=False)
|
||||
content = Column(String, nullable=False)
|
||||
ttl = Column(Integer, default=120)
|
||||
proxied = Column(Boolean, default=True)
|
||||
notes = Column(Text, nullable=True)
|
||||
|
||||
def to_json(self):
|
||||
return super().to_json()
|
||||
|
||||
+71
-23
@@ -5,13 +5,8 @@ import secrets
|
||||
import logging
|
||||
|
||||
CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
# Setup logger
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CloudflareTunnelManager:
|
||||
def __init__(self, api_token: str, account_id: str, zone_id: str):
|
||||
def __init__(self, api_token: str, account_id: str, zone_id: str, logger):
|
||||
self.api_token = api_token
|
||||
self.account_id = account_id
|
||||
self.zone_id = zone_id
|
||||
@@ -19,10 +14,11 @@ class CloudflareTunnelManager:
|
||||
"Authorization": f"Bearer {self.api_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
self.logger=logger
|
||||
|
||||
def create_tunnel(self, tunnel_name: str) -> dict:
|
||||
"""Create a new tunnel or get existing tunnel info."""
|
||||
logger.info(f"Checking if tunnel '{tunnel_name}' already exists...")
|
||||
self.logger.info(f"Checking if tunnel '{tunnel_name}' already exists...")
|
||||
|
||||
try:
|
||||
tunnels = requests.get(
|
||||
@@ -31,16 +27,16 @@ class CloudflareTunnelManager:
|
||||
)
|
||||
tunnels.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch tunnels list: {e}")
|
||||
self.logger.error(f"Failed to fetch tunnels list: {e}")
|
||||
raise
|
||||
|
||||
for tunnel in tunnels.json().get('result', []):
|
||||
if tunnel['name'] == tunnel_name:
|
||||
logger.info(f"Tunnel '{tunnel_name}' already exists (ID: {tunnel['id']})")
|
||||
logger.info(tunnel)
|
||||
self.logger.info(f"Tunnel '{tunnel_name}' already exists (ID: {tunnel['id']})")
|
||||
self.logger.info(tunnel)
|
||||
return tunnel
|
||||
|
||||
logger.info(f"Tunnel '{tunnel_name}' not found. Creating new tunnel...")
|
||||
self.logger.info(f"Tunnel '{tunnel_name}' not found. Creating new tunnel...")
|
||||
payload = {
|
||||
"name": tunnel_name,
|
||||
"tunnel_secret": secrets.token_hex(32)
|
||||
@@ -52,16 +48,16 @@ class CloudflareTunnelManager:
|
||||
json=payload
|
||||
)
|
||||
response.raise_for_status()
|
||||
logger.info(f"Tunnel '{tunnel_name}' created successfully.")
|
||||
logger.info(response.json())
|
||||
self.logger.info(f"Tunnel '{tunnel_name}' created successfully.")
|
||||
self.logger.info(response.json())
|
||||
return response.json()['result']
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create tunnel '{tunnel_name}': {e}")
|
||||
self.logger.error(f"Failed to create tunnel '{tunnel_name}': {e}")
|
||||
raise
|
||||
|
||||
def configure_tunnel_route(self, tunnel_id: str, hostname: str, service_url: str):
|
||||
"""Configure the tunnel's public hostname routing."""
|
||||
logger.info(f"Configuring tunnel route (corrected payload): {hostname} -> {service_url}")
|
||||
self.logger.info(f"Configuring tunnel route (corrected payload): {hostname} -> {service_url}")
|
||||
payload = {
|
||||
"config": {
|
||||
"ingress": [
|
||||
@@ -83,15 +79,15 @@ class CloudflareTunnelManager:
|
||||
json=payload
|
||||
)
|
||||
response.raise_for_status()
|
||||
logger.info(f"Tunnel route configured successfully for hostname '{hostname}'.")
|
||||
self.logger.info(f"Tunnel route configured successfully for hostname '{hostname}'.")
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to configure tunnel route for '{hostname}': {e}")
|
||||
self.logger.error(f"Failed to configure tunnel route for '{hostname}': {e}")
|
||||
raise
|
||||
|
||||
def create_dns_record(self, hostname: str, tunnel_id: str):
|
||||
"""Create DNS CNAME record pointing to the tunnel."""
|
||||
logger.info(f"Creating DNS record for hostname '{hostname}'...")
|
||||
self.logger.info(f"Creating DNS record for hostname '{hostname}'...")
|
||||
payload = {
|
||||
"type": "CNAME",
|
||||
"name": hostname,
|
||||
@@ -106,29 +102,81 @@ class CloudflareTunnelManager:
|
||||
json=payload
|
||||
)
|
||||
response.raise_for_status()
|
||||
logger.info(f"DNS record for '{hostname}' created successfully.")
|
||||
self.logger.info(f"DNS record for '{hostname}' created successfully.")
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create DNS record for '{hostname}': {e}")
|
||||
self.logger.error(f"Failed to create DNS record for '{hostname}': {e}")
|
||||
raise
|
||||
|
||||
def setup_tunnel(self, tunnel_name: str, dns_hostname: str, local_ip_port: str):
|
||||
"""Full workflow: Create tunnel, configure, and create DNS."""
|
||||
logger.info(f"Starting full tunnel setup: {tunnel_name} -> {dns_hostname} -> {local_ip_port}")
|
||||
self.logger.info(f"Starting full tunnel setup: {tunnel_name} -> {dns_hostname} -> {local_ip_port}")
|
||||
try:
|
||||
tunnel = self.create_tunnel(tunnel_name)
|
||||
self.configure_tunnel_route(tunnel['id'], dns_hostname, local_ip_port)
|
||||
self.create_dns_record(dns_hostname,tunnel['id'])
|
||||
|
||||
token=tunnel['token']
|
||||
logger.info(f"Tunnel setup completed successfully.")
|
||||
self.logger.info(f"Tunnel setup completed successfully.")
|
||||
return {
|
||||
"tunnel_id": tunnel['id'],
|
||||
"tunnel_name": tunnel_name,
|
||||
"tunnel_secret": tunnel['credentials_file']['TunnelSecret'],
|
||||
"token": token,
|
||||
"dns_hostname": dns_hostname,
|
||||
"local_service": local_ip_port
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Full tunnel setup failed: {e}")
|
||||
self.logger.error(f"Full tunnel setup failed: {e}")
|
||||
raise
|
||||
|
||||
def delete_tunnel(self, tunnel_id: str):
|
||||
"""Delete a Cloudflare Tunnel."""
|
||||
self.logger.info(f"Attempting to delete tunnel ID '{tunnel_id}'...")
|
||||
try:
|
||||
response = requests.delete(
|
||||
f"{CLOUDFLARE_API_BASE}/accounts/{self.account_id}/cfd_tunnel/{tunnel_id}",
|
||||
headers=self.headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
self.logger.info(f"Tunnel ID '{tunnel_id}' deleted successfully.")
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to delete tunnel '{tunnel_id}': {e}")
|
||||
raise
|
||||
|
||||
def delete_dns_record(self, dns_record_id: str):
|
||||
"""Delete a DNS record."""
|
||||
self.logger.info(f"Attempting to delete DNS record ID '{dns_record_id}'...")
|
||||
try:
|
||||
response = requests.delete(
|
||||
f"{CLOUDFLARE_API_BASE}/zones/{self.zone_id}/dns_records/{dns_record_id}",
|
||||
headers=self.headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
self.logger.info(f"DNS record ID '{dns_record_id}' deleted successfully.")
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to delete DNS record '{dns_record_id}': {e}")
|
||||
raise
|
||||
|
||||
def get_dns_record_id(self, hostname: str):
|
||||
"""Helper: Find DNS record ID for a given hostname."""
|
||||
self.logger.info(f"Fetching DNS record for hostname '{hostname}'...")
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{CLOUDFLARE_API_BASE}/zones/{self.zone_id}/dns_records?type=CNAME&name={hostname}",
|
||||
headers=self.headers
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json().get('result', [])
|
||||
if result:
|
||||
dns_record_id = result[0]['id']
|
||||
self.logger.info(f"Found DNS record ID '{dns_record_id}' for hostname '{hostname}'.")
|
||||
return dns_record_id
|
||||
else:
|
||||
self.logger.warning(f"No DNS record found for hostname '{hostname}'.")
|
||||
return None
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to fetch DNS record for '{hostname}': {e}")
|
||||
raise
|
||||
@@ -2,35 +2,28 @@ import json
|
||||
import streamlit as st
|
||||
from streamlit_server.utils.helpers import format_timestamp
|
||||
|
||||
|
||||
def render_list():
|
||||
"""
|
||||
Renders a list of all container workloads.
|
||||
Renders a list of container pods, each with its containers.
|
||||
"""
|
||||
st.header("Container Workload Management")
|
||||
|
||||
# Initialize session state variables for delete all confirmation
|
||||
if 'container_delete_all_confirmation_shown' not in st.session_state:
|
||||
st.session_state.container_delete_all_confirmation_shown = False
|
||||
if 'container_delete_all_confirmed' not in st.session_state:
|
||||
st.session_state.container_delete_all_confirmed = False
|
||||
# Initialize session state variables for delete confirmation
|
||||
if 'container_delete_confirmation_shown' not in st.session_state:
|
||||
st.session_state.container_delete_confirmation_shown = False
|
||||
if 'container_delete_confirmed' not in st.session_state:
|
||||
st.session_state.container_delete_confirmed = False
|
||||
# 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
|
||||
|
||||
# Fetch all VDCs for the dropdown
|
||||
# Fetch VDCs
|
||||
vdcs = st.session_state.client.get_virtual_data_centers()
|
||||
vdc_options = {vdc['name']: vdc['id'] for vdc in vdcs} if vdcs else {}
|
||||
|
||||
# Add a dropdown to select VDC
|
||||
selected_vdc_name = st.selectbox("Filter by Virtual Data Center", list(vdc_options.keys()))
|
||||
|
||||
# Fetch all Workloads_hosts for the dropdown
|
||||
workload_hosts = st.session_state.client.get_workload_hosts()
|
||||
workload_host_options = {workload_host['hostname']: workload_host['id'] for workload_host in workload_hosts} if workload_hosts else {}
|
||||
|
||||
# Create Container Workload Form
|
||||
with st.expander("Create New Container Workload"):
|
||||
with st.form("create_container"):
|
||||
@@ -56,16 +49,18 @@ def render_list():
|
||||
internal_port = st.number_input("Internal Port", min_value=1, max_value=65535, step=1)
|
||||
external_port = st.number_input("External Port", min_value=1, max_value=65535, step=1)
|
||||
|
||||
# New: Use DNS (Cloudflare) Checkbox
|
||||
use_dns = st.checkbox("Use DNS (Cloudflare)", value=True)
|
||||
|
||||
submit = st.form_submit_button("Create Container Workload")
|
||||
|
||||
if submit and selected_vdc_name_create and container_name:
|
||||
# Use the selected VDC's ID
|
||||
vdc_id = vdc_options[selected_vdc_name_create]
|
||||
|
||||
container_payload = {
|
||||
"docker_image": docker_image,
|
||||
"container_name": container_name,
|
||||
"networks": "bridge" # Default network
|
||||
"networks": "bridge", # Default network
|
||||
}
|
||||
|
||||
# Only add ports if both fields are non-zero
|
||||
@@ -74,6 +69,7 @@ def render_list():
|
||||
|
||||
payload = {
|
||||
"vdc": vdc_id,
|
||||
"use_dns": use_dns,
|
||||
"containers": [container_payload]
|
||||
}
|
||||
|
||||
@@ -82,87 +78,78 @@ def render_list():
|
||||
st.success("Container workload created successfully!")
|
||||
|
||||
|
||||
# List Container Pods
|
||||
st.subheader("Existing Container Pods")
|
||||
pods = st.session_state.client.get_container_pods()
|
||||
|
||||
# List Container Workloads
|
||||
st.subheader("Existing Container Workloads")
|
||||
containers = st.session_state.client.get_containers()
|
||||
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]
|
||||
|
||||
if containers:
|
||||
# Filter containers based on the selected VDC
|
||||
if selected_vdc_name:
|
||||
selected_vdc_id = vdc_options[selected_vdc_name]
|
||||
filtered_containers = [container for container in containers if container['vdc_id'] == selected_vdc_id]
|
||||
else:
|
||||
filtered_containers = containers
|
||||
|
||||
# Delete All functionality
|
||||
if not st.session_state.container_delete_all_confirmation_shown:
|
||||
if st.button("Delete All Containers", key="delete_all_containers", type="primary", help="This will delete all visible containers"):
|
||||
st.session_state.container_delete_all_confirmation_shown = True
|
||||
# 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"):
|
||||
st.session_state.pod_delete_all_confirmation_shown = True
|
||||
st.rerun()
|
||||
|
||||
# Show confirmation if delete was clicked
|
||||
if st.session_state.container_delete_all_confirmation_shown and not st.session_state.container_delete_all_confirmed:
|
||||
st.warning("Are you sure you want to delete all containers? This action cannot be undone.")
|
||||
|
||||
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:
|
||||
if st.button("Yes, Delete All", key="confirm_delete_all_containers"):
|
||||
st.session_state.container_delete_all_confirmed = True
|
||||
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:
|
||||
if st.button("Cancel", key="cancel_delete_all_containers"):
|
||||
st.session_state.container_delete_all_confirmation_shown = False
|
||||
if st.button("Cancel", key="cancel_delete_all_pods"):
|
||||
st.session_state.pod_delete_all_confirmation_shown = False
|
||||
st.rerun()
|
||||
|
||||
# Perform deletion if confirmed
|
||||
if st.session_state.container_delete_all_confirmed:
|
||||
|
||||
if st.session_state.pod_delete_all_confirmed:
|
||||
deletion_count = 0
|
||||
for container in filtered_containers:
|
||||
# Call the delete API for each container
|
||||
result = st.session_state.client.delete_container(container['id'])
|
||||
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} containers.")
|
||||
# Reset the confirmation flags
|
||||
st.session_state.container_delete_all_confirmation_shown = False
|
||||
st.session_state.container_delete_all_confirmed = False
|
||||
|
||||
# Display the containers
|
||||
for container in filtered_containers:
|
||||
with st.container():
|
||||
col1, col2, col3 = st.columns([3, 1, 1])
|
||||
with col1:
|
||||
# Safely handle empty or None launch_params
|
||||
launch_params = container.get('launch_params') # Use .get() to avoid KeyError if 'launch_params' doesn't exist
|
||||
if launch_params: # Check if launch_params is not empty or None
|
||||
try:
|
||||
container_name = json.loads(launch_params).get('docker_image') # Use .get() to avoid KeyError if 'docker_image' doesn't exist
|
||||
except json.JSONDecodeError:
|
||||
print("LP not vald json")
|
||||
# Handle the case where launch_params is not valid JSON
|
||||
container_name = None
|
||||
else:
|
||||
print("lp empty")
|
||||
# Handle the case where launch_params is empty or None
|
||||
container_name = None
|
||||
st.markdown(f"### {container['name']}")
|
||||
st.text(f"Status: {container['status']}")
|
||||
st.text(f"VDC ID: {container['vdc_id']}")
|
||||
st.text(f"Image: {container_name}")
|
||||
with col2:
|
||||
st.text(f"ID: {container['id']}")
|
||||
with col3:
|
||||
if st.button("View Details", key=f"view_{container['id']}"):
|
||||
st.session_state.selected_resource = container
|
||||
st.session_state.view_type = 'detail'
|
||||
st.session_state.selected_resource_type = 'container'
|
||||
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()
|
||||
|
||||
# Render Pods nicely
|
||||
for pod in filtered_pods:
|
||||
with st.expander(f"🛰️ Pod: {pod['pod_name']} (ID: {pod['pod_id']})", expanded=False):
|
||||
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']}`")
|
||||
st.markdown("---")
|
||||
|
||||
# Container List
|
||||
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.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:
|
||||
st.success(f"Deleted Pod {pod['pod_name']}")
|
||||
st.rerun()
|
||||
# st.switch_page("streamlit_server/views/containers.py&container_id=d", query_params={"team": "streamlit"})
|
||||
|
||||
|
||||
else:
|
||||
st.info("No container workloads found.")
|
||||
st.info("No pods found in the selected VDC.")
|
||||
|
||||
|
||||
def render_detail():
|
||||
|
||||
Reference in New Issue
Block a user