From fbeb7824cf02ffce272001afb6988c2043e591f2 Mon Sep 17 00:00:00 2001 From: Hijanhv Date: Mon, 3 Nov 2025 20:29:32 +0530 Subject: [PATCH 1/5] Refactor naming for clearer backend/frontend code Signed-off-by: Hijanhv --- backend/apps/common/__init__.py | 8 +- backend/apps/common/routes/delete.py | 12 ++- backend/apps/common/routes/get.py | 102 ++++++++++++------- backend/apps/common/utils.py | 146 +++++++++++++++------------ backend/apps/common/versions.py | 18 ++-- backend/apps/v1beta1/__init__.py | 6 +- backend/apps/v1beta1/routes/get.py | 6 +- backend/apps/v1beta1/routes/post.py | 8 +- backend/apps/v1beta1/routes/put.py | 27 ++--- backend/entrypoint.py | 8 +- frontend/src/types/kubeflow.d.ts | 94 +++++++++++++++++ 11 files changed, 295 insertions(+), 140 deletions(-) create mode 100644 frontend/src/types/kubeflow.d.ts diff --git a/backend/apps/common/__init__.py b/backend/apps/common/__init__.py index 10580f01..71ced1ea 100644 --- a/backend/apps/common/__init__.py +++ b/backend/apps/common/__init__.py @@ -8,11 +8,13 @@ log = logging.getLogger(__name__) -def create_app(name=__name__, static_folder="static", cfg: config.Config = None): +def create_app( + name=__name__, static_folder="static", configuration: config.Config = None +): """Create the WSGI app.""" - cfg = config.Config() if cfg is None else cfg + configuration = config.Config() if configuration is None else configuration - app = base.create_app(name, static_folder, cfg) + app = base.create_app(name, static_folder, configuration) # Register the app's blueprints app.register_blueprint(routes_bp) diff --git a/backend/apps/common/routes/delete.py b/backend/apps/common/routes/delete.py index 4d6317c9..e964f4ae 100644 --- a/backend/apps/common/routes/delete.py +++ b/backend/apps/common/routes/delete.py @@ -12,12 +12,14 @@ "/api/namespaces//inferenceservices/", methods=["DELETE"], ) -def delete_inference_service(inference_service, namespace): +def delete_inference_service(namespace, inference_service): """Handle DELETE requests and delete the provided InferenceService.""" - log.info("Deleting InferenceService %s/%s'", namespace, inference_service) - gvk = versions.inference_service_gvk() - api.delete_custom_rsrc(**gvk, name=inference_service, namespace=namespace) + log.info("Deleting InferenceService %s/%s", namespace, inference_service) + group_version_kind = versions.inference_service_group_version_kind() + api.delete_custom_rsrc( + **group_version_kind, name=inference_service, namespace=namespace + ) return api.success_response( "message", - "InferenceService %s/%s successfully deleted." % (namespace, inference_service), + f"InferenceService {namespace}/{inference_service} successfully deleted.", ) diff --git a/backend/apps/common/routes/get.py b/backend/apps/common/routes/get.py index 98305fad..5dc5a7f9 100644 --- a/backend/apps/common/routes/get.py +++ b/backend/apps/common/routes/get.py @@ -12,18 +12,22 @@ @bp.route("/api/namespaces//inferenceservices") def get_inference_services(namespace): - """Return a list of InferenceService CRs as json objects.""" - gvk = versions.inference_service_gvk() - inference_services = api.list_custom_rsrc(**gvk, namespace=namespace) + """Return a list of InferenceService custom resources as JSON objects.""" + group_version_kind = versions.inference_service_group_version_kind() + inference_services = api.list_custom_rsrc( + **group_version_kind, namespace=namespace + ) return api.success_response("inferenceServices", inference_services["items"]) @bp.route("/api/namespaces//inferenceservices/") def get_inference_service(namespace, name): - """Return an InferenceService CR as a json object.""" + """Return an InferenceService custom resource as a JSON object.""" inference_service = api.get_custom_rsrc( - **versions.inference_service_gvk(), namespace=namespace, name=name + **versions.inference_service_group_version_kind(), + namespace=namespace, + name=name, ) if request.args.get("logs", "false") == "true": # find the logs @@ -39,74 +43,86 @@ def get_inference_service(namespace, name): return api.success_response("inferenceService", inference_service) -def get_inference_service_logs(svc): - """Return all logs for all isvc component pods.""" - namespace = svc["metadata"]["namespace"] +def get_inference_service_logs(inference_service): + """Return logs for each InferenceService component pod.""" + namespace = inference_service["metadata"]["namespace"] components = request.args.getlist("component") log.info(components) # Check deployment mode to determine how to get logs - deployment_mode = utils.get_deployment_mode(svc) + deployment_mode = utils.get_deployment_mode(inference_service) if deployment_mode == "ModelMesh": # For ModelMesh, get logs from modelmesh-serving deployment - component_pods_dict = utils.get_modelmesh_pods(svc, components) + component_pods_dict = utils.get_modelmesh_pods( + inference_service, components + ) elif deployment_mode == "RawDeployment": - component_pods_dict = utils.get_raw_inference_service_pods(svc, components) + component_pods_dict = utils.get_raw_inference_service_pods( + inference_service, components + ) else: # Serverless mode - component_pods_dict = utils.get_inference_service_pods(svc, components) + component_pods_dict = utils.get_inference_service_pods( + inference_service, components + ) if len(component_pods_dict.keys()) == 0: return {} - resp = {} + logs_by_component = {} logging.info("Component pods: %s", component_pods_dict) for component, pods in component_pods_dict.items(): - if component not in resp: - resp[component] = [] + if component not in logs_by_component: + logs_by_component[component] = [] for pod in pods: logs = api.get_pod_logs(namespace, pod, "kserve-container", auth=False) - resp[component].append({"podName": pod, "logs": logs.split("\n")}) - return resp + logs_by_component[component].append( + {"podName": pod, "logs": logs.split("\n")} + ) + return logs_by_component @bp.route("/api/namespaces//knativeServices/") def get_knative_service(namespace, name): - """Return a Knative Services object as json.""" - svc = api.get_custom_rsrc( + """Return a Knative Service object as JSON.""" + service = api.get_custom_rsrc( **versions.KNATIVE_SERVICE, namespace=namespace, name=name ) - return api.success_response("knativeService", svc) + return api.success_response("knativeService", service) @bp.route("/api/namespaces//configurations/") def get_knative_configuration(namespace, name): - """Return a Knative Configurations object as json.""" - svc = api.get_custom_rsrc(**versions.KNATIVE_CONF, namespace=namespace, name=name) + """Return a Knative Configuration object as JSON.""" + configuration = api.get_custom_rsrc( + **versions.KNATIVE_CONFIGURATION, namespace=namespace, name=name + ) - return api.success_response("knativeConfiguration", svc) + return api.success_response("knativeConfiguration", configuration) @bp.route("/api/namespaces//revisions/") def get_knative_revision(namespace, name): - """Return a Knative Revision object as json.""" - svc = api.get_custom_rsrc( + """Return a Knative Revision object as JSON.""" + revision = api.get_custom_rsrc( **versions.KNATIVE_REVISION, namespace=namespace, name=name ) - return api.success_response("knativeRevision", svc) + return api.success_response("knativeRevision", revision) @bp.route("/api/namespaces//routes/") def get_knative_route(namespace, name): - """Return a Knative Route object as json.""" - svc = api.get_custom_rsrc(**versions.KNATIVE_ROUTE, namespace=namespace, name=name) + """Return a Knative Route object as JSON.""" + route = api.get_custom_rsrc( + **versions.KNATIVE_ROUTE, namespace=namespace, name=name + ) - return api.success_response("knativeRoute", svc) + return api.success_response("knativeRoute", route) @bp.route("/api/namespaces//inferenceservices//events") @@ -125,27 +141,33 @@ def get_inference_service_events(namespace, name): # RawDeployment mode endpoints @bp.route("/api/namespaces//deployments/") def get_kubernetes_deployment(namespace, name): - """Return a Kubernetes Deployment object as json.""" + """Return a Kubernetes Deployment object as JSON.""" deployment = api.get_custom_rsrc( - **versions.K8S_DEPLOYMENT, namespace=namespace, name=name + **versions.KUBERNETES_DEPLOYMENT_RESOURCE, + namespace=namespace, + name=name, ) return api.success_response("deployment", deployment) @bp.route("/api/namespaces//services/") def get_kubernetes_service(namespace, name): - """Return a Kubernetes Service object as json.""" + """Return a Kubernetes Service object as JSON.""" service = api.get_custom_rsrc( - **versions.K8S_SERVICE, namespace=namespace, name=name + **versions.KUBERNETES_SERVICE_RESOURCE, + namespace=namespace, + name=name, ) return api.success_response("service", service) @bp.route("/api/namespaces//hpas/") def get_kubernetes_hpa(namespace, name): - """Return a Kubernetes HPA object as json.""" - hpa = api.get_custom_rsrc(**versions.K8S_HPA, namespace=namespace, name=name) - return api.success_response("hpa", hpa) + """Return a Kubernetes HorizontalPodAutoscaler object as JSON.""" + horizontal_pod_autoscaler = api.get_custom_rsrc( + **versions.KUBERNETES_HPA_RESOURCE, namespace=namespace, name=name + ) + return api.success_response("hpa", horizontal_pod_autoscaler) @bp.route( @@ -155,7 +177,9 @@ def get_raw_deployment_objects(namespace, name, component): """Return all Kubernetes native resources for a RawDeployment component.""" inference_service = api.get_custom_rsrc( - **versions.inference_service_gvk(), namespace=namespace, name=name + **versions.inference_service_group_version_kind(), + namespace=namespace, + name=name, ) if not utils.is_raw_deployment(inference_service): @@ -172,7 +196,9 @@ def get_modelmesh_objects(namespace, name, component): """Return all ModelMesh-specific resources for a ModelMesh component.""" inference_service = api.get_custom_rsrc( - **versions.inference_service_gvk(), namespace=namespace, name=name + **versions.inference_service_group_version_kind(), + namespace=namespace, + name=name, ) if not utils.is_modelmesh_deployment(inference_service): diff --git a/backend/apps/common/utils.py b/backend/apps/common/utils.py index 4ac9478c..6fde6512 100644 --- a/backend/apps/common/utils.py +++ b/backend/apps/common/utils.py @@ -8,10 +8,10 @@ log = logging.getLogger(__name__) KNATIVE_REVISION_LABEL = "serving.knative.dev/revision" -FILE_ABS_PATH = os.path.abspath(os.path.dirname(__file__)) +ABSOLUTE_FILE_PATH = os.path.abspath(os.path.dirname(__file__)) -INFERENCESERVICE_TEMPLATE_YAML = os.path.join( - FILE_ABS_PATH, "yaml", "inference_service_template.yaml" +INFERENCE_SERVICE_TEMPLATE_YAML = os.path.join( + ABSOLUTE_FILE_PATH, "yaml", "inference_service_template.yaml" ) @@ -24,21 +24,21 @@ def load_inference_service_template(**kwargs): kwargs: the parameters to be replaced in the yaml """ - return helpers.load_param_yaml(INFERENCESERVICE_TEMPLATE_YAML, **kwargs) + return helpers.load_param_yaml(INFERENCE_SERVICE_TEMPLATE_YAML, **kwargs) # helper functions for accessing the logs of an InferenceService in raw # kubernetes mode -def get_raw_inference_service_pods(svc, components=[]): +def get_raw_inference_service_pods(inference_service, components=[]): """ Return a dictionary with (endpoint, component) keys i.e. ("default", "predictor") and a list of pod names as values """ - namespace = svc["metadata"]["namespace"] - svc_name = svc["metadata"]["name"] - label_selector = "serving.kubeflow.org/inferenceservice={}".format(svc_name) + namespace = inference_service["metadata"]["namespace"] + service_name = inference_service["metadata"]["name"] + label_selector = "serving.kubeflow.org/inferenceservice={}".format(service_name) pods = api.v1_core.list_namespaced_pod( namespace, label_selector=label_selector ).items @@ -48,12 +48,15 @@ def get_raw_inference_service_pods(svc, components=[]): if component not in components: continue - curr_pod_names = component_pods_dict.get(component, []) - curr_pod_names.append(pod.metadata.name) - component_pods_dict[component] = curr_pod_names + current_pod_names = component_pods_dict.get(component, []) + current_pod_names.append(pod.metadata.name) + component_pods_dict[component] = current_pod_names if len(component_pods_dict.keys()) == 0: - log.info("No pods are found for inference service: %s", svc["metadata"]["name"]) + log.info( + "No pods are found for inference service: %s", + inference_service["metadata"]["name"], + ) return component_pods_dict @@ -61,17 +64,17 @@ def get_raw_inference_service_pods(svc, components=[]): # helper functions for accessing the logs of an InferenceService -def get_inference_service_pods(svc, components=[]): +def get_inference_service_pods(inference_service, components=[]): """ - Return the Pod names for the different isvc components. + Return the Pod names for the different InferenceService components. Return a dictionary with (endpoint, component) keys, i.e. ("default", "predictor") and a list of pod names as values """ - namespace = svc["metadata"]["namespace"] + namespace = inference_service["metadata"]["namespace"] # dictionary{revisionName: (endpoint, component)} - revisions_dict = get_components_revisions_dict(components, svc) + revisions_dict = get_components_revisions_dict(components, inference_service) if len(revisions_dict.keys()) == 0: return {} @@ -87,24 +90,27 @@ def get_inference_service_pods(svc, components=[]): continue component = revisions_dict[revision] - curr_pod_names = component_pods_dict.get(component, []) - curr_pod_names.append(pod.metadata.name) - component_pods_dict[component] = curr_pod_names + current_pod_names = component_pods_dict.get(component, []) + current_pod_names.append(pod.metadata.name) + component_pods_dict[component] = current_pod_names if len(component_pods_dict.keys()) == 0: - log.info("No pods are found for inference service: %s", svc["metadata"]["name"]) + log.info( + "No pods are found for inference service: %s", + inference_service["metadata"]["name"], + ) return component_pods_dict -def get_modelmesh_pods(svc, components=[]): +def get_modelmesh_pods(inference_service, components=[]): """ Return a dictionary with component keys and ModelMesh pod names as values. For ModelMesh deployments, logs are typically found in the ModelMesh controller managed pods. """ - namespace = svc["metadata"]["namespace"] + namespace = inference_service["metadata"]["namespace"] # Use label selector to find ModelMesh pods label_selector = "app.kubernetes.io/managed-by=modelmesh-controller" @@ -125,7 +131,8 @@ def get_modelmesh_pods(svc, components=[]): if len(component_pods_dict.keys()) == 0: log.info( - "No ModelMesh pods found for inference service: %s", svc["metadata"]["name"] + "No ModelMesh pods found for inference service: %s", + inference_service["metadata"]["name"], ) return component_pods_dict @@ -133,9 +140,9 @@ def get_modelmesh_pods(svc, components=[]): # FIXME(elikatsis,kimwnasptd): Change the logic of this function according to # https://github.com/arrikto/dev/issues/867 -def get_components_revisions_dict(components, svc): +def get_components_revisions_dict(components, inference_service): """Return a dictionary{revisionId: component}.""" - status = svc["status"] + status = inference_service["status"] revisions_dict = {} for component in components: @@ -143,7 +150,7 @@ def get_components_revisions_dict(components, svc): log.info( "Component '%s' not in inference service '%s'", component, - svc["metadata"]["name"], + inference_service["metadata"]["name"], ) continue @@ -151,7 +158,7 @@ def get_components_revisions_dict(components, svc): log.info( "Component '%s' not in inference service '%s'", component, - svc["metadata"]["name"], + inference_service["metadata"]["name"], ) continue @@ -163,19 +170,19 @@ def get_components_revisions_dict(components, svc): if len(revisions_dict.keys()) == 0: log.info( "No revisions found for the inference service's components: %s", - svc["metadata"]["name"], + inference_service["metadata"]["name"], ) return revisions_dict -def is_raw_deployment(svc): +def is_raw_deployment(inference_service): """ Check if an InferenceService is using RawDeployment mode. Returns True if the service uses RawDeployment, False for Serverless mode. """ - annotations = svc.get("metadata", {}).get("annotations", {}) + annotations = inference_service.get("metadata", {}).get("annotations", {}) # Check for the new KServe annotation deployment_mode = annotations.get("serving.kserve.io/deploymentMode", "") @@ -190,43 +197,44 @@ def is_raw_deployment(svc): return False -def is_modelmesh_deployment(svc): +def is_modelmesh_deployment(inference_service): """ Check if an InferenceService is using ModelMesh mode. Returns True if the service uses ModelMesh deployment mode. """ - annotations = svc.get("metadata", {}).get("annotations", {}) + annotations = inference_service.get("metadata", {}).get("annotations", {}) deployment_mode = annotations.get("serving.kserve.io/deploymentMode", "") return deployment_mode.lower() == "modelmesh" -def get_deployment_mode(svc): +def get_deployment_mode(inference_service): """ Get the deployment mode of an InferenceService. Returns one of: "ModelMesh", "RawDeployment", "Serverless" """ - if is_modelmesh_deployment(svc): + if is_modelmesh_deployment(inference_service): return "ModelMesh" - elif is_raw_deployment(svc): + elif is_raw_deployment(inference_service): return "RawDeployment" else: return "Serverless" -def get_raw_deployment_objects(svc, component): +def get_raw_deployment_objects(inference_service, component): """ Get Kubernetes native resources for a RawDeployment InferenceService component. - Returns a dictionary with deployment, service, and hpa objects. + Returns a dictionary with deployment, service, and HorizontalPodAutoscaler + (HPA) objects. """ - namespace = svc["metadata"]["namespace"] - svc_name = svc["metadata"]["name"] + namespace = inference_service["metadata"]["namespace"] + service_name = inference_service["metadata"]["name"] - # RawDeployment resources follow naming convention: {isvc-name}-{component} - resource_name = f"{svc_name}-{component}" + # RawDeployment resources follow naming convention: {inference-service-name}-{component} + resource_name = f"{service_name}-{component}" objects = { "deployment": None, @@ -237,7 +245,9 @@ def get_raw_deployment_objects(svc, component): try: # Get Deployment deployment = api.get_custom_rsrc( - **versions.K8S_DEPLOYMENT, namespace=namespace, name=resource_name + **versions.KUBERNETES_DEPLOYMENT_RESOURCE, + namespace=namespace, + name=resource_name, ) objects["deployment"] = deployment log.info(f"Found deployment {resource_name} for component {component}") @@ -247,7 +257,9 @@ def get_raw_deployment_objects(svc, component): try: # Get Service service = api.get_custom_rsrc( - **versions.K8S_SERVICE, namespace=namespace, name=resource_name + **versions.KUBERNETES_SERVICE_RESOURCE, + namespace=namespace, + name=resource_name, ) objects["service"] = service log.info(f"Found service {resource_name} for component {component}") @@ -255,23 +267,29 @@ def get_raw_deployment_objects(svc, component): log.warning(f"Could not find service {resource_name}: {e}") try: - # Get HPA (optional) - hpa = api.get_custom_rsrc( - **versions.K8S_HPA, namespace=namespace, name=resource_name + # Get HorizontalPodAutoscaler (optional) + horizontal_pod_autoscaler = api.get_custom_rsrc( + **versions.KUBERNETES_HPA_RESOURCE, + namespace=namespace, + name=resource_name, + ) + objects["hpa"] = horizontal_pod_autoscaler + log.info( + f"Found HorizontalPodAutoscaler {resource_name} for component {component}" ) - objects["hpa"] = hpa - log.info(f"Found HPA {resource_name} for component {component}") except Exception as e: - log.debug(f"No HPA found for {resource_name}: {e}") + log.debug( + f"No HorizontalPodAutoscaler found for {resource_name}: {e}" + ) return objects -def get_modelmesh_objects(svc, component): +def get_modelmesh_objects(inference_service, component): """ Get ModelMesh-specific resources for an InferenceService component. """ - namespace = svc["metadata"]["namespace"] + namespace = inference_service["metadata"]["namespace"] objects = { "predictor": None, "servingRuntime": None, @@ -280,17 +298,17 @@ def get_modelmesh_objects(svc, component): } # 1. Get predictor status - if "status" in svc and "components" in svc.get("status", {}): - objects["predictor"] = svc["status"]["components"].get(component) + if "status" in inference_service and "components" in inference_service.get("status", {}): + objects["predictor"] = inference_service["status"]["components"].get(component) # 2. Determine the ServingRuntime name (early exit if not found) - runtime_name = _extract_serving_runtime_name(svc, component) + runtime_name = _extract_serving_runtime_name(inference_service, component) if not runtime_name: log.warning(f"Could not determine ServingRuntime for component {component}") return objects # 3. Get the ServingRuntime object - objects["servingRuntime"] = _get_k8s_object( + objects["servingRuntime"] = _get_kubernetes_object( namespace=namespace, name=runtime_name, group="serving.kserve.io", @@ -305,19 +323,23 @@ def get_modelmesh_objects(svc, component): log.info(f"Constructed ModelMesh resource name: {resource_name}") # 5. Get the Deployment and Service by their specific names - objects["deployment"] = _get_k8s_object( - namespace=namespace, name=resource_name, **versions.K8S_DEPLOYMENT + objects["deployment"] = _get_kubernetes_object( + namespace=namespace, + name=resource_name, + **versions.KUBERNETES_DEPLOYMENT_RESOURCE, ) - objects["service"] = _get_k8s_object( - namespace=namespace, name=resource_name, **versions.K8S_SERVICE + objects["service"] = _get_kubernetes_object( + namespace=namespace, + name=resource_name, + **versions.KUBERNETES_SERVICE_RESOURCE, ) return objects -def _extract_serving_runtime_name(svc, component): +def _extract_serving_runtime_name(inference_service, component): """Extract the ServingRuntime name from an InferenceService spec.""" - component_spec = svc.get("spec", {}).get(component, {}) + component_spec = inference_service.get("spec", {}).get(component, {}) if not component_spec: return None @@ -334,7 +356,7 @@ def _get_modelmesh_service_name(): return default_name -def _get_k8s_object(namespace, name, group, version, kind): +def _get_kubernetes_object(namespace, name, group, version, kind): """A generic helper to get any single Kubernetes resource by its name.""" try: resource = api.get_custom_rsrc( diff --git a/backend/apps/common/versions.py b/backend/apps/common/versions.py index 90071f1b..c63a5bb0 100644 --- a/backend/apps/common/versions.py +++ b/backend/apps/common/versions.py @@ -1,4 +1,4 @@ -"""Helpers with GVK needed, stored in one place.""" +"""Helpers with Kubernetes group/version/kind mappings stored in one place.""" from flask import current_app @@ -8,7 +8,7 @@ "version": "v1", "kind": "revisions", } -KNATIVE_CONF = { +KNATIVE_CONFIGURATION = { "group": "serving.knative.dev", "version": "v1", "kind": "configurations", @@ -16,14 +16,18 @@ KNATIVE_SERVICE = {"group": "serving.knative.dev", "version": "v1", "kind": "services"} # Kubernetes native resources for RawDeployment mode -K8S_DEPLOYMENT = {"group": "apps", "version": "v1", "kind": "deployments"} -K8S_SERVICE = {"group": "", "version": "v1", "kind": "services"} -K8S_HPA = {"group": "autoscaling", "version": "v2", "kind": "horizontalpodautoscalers"} +KUBERNETES_DEPLOYMENT_RESOURCE = {"group": "apps", "version": "v1", "kind": "deployments"} +KUBERNETES_SERVICE_RESOURCE = {"group": "", "version": "v1", "kind": "services"} +KUBERNETES_HPA_RESOURCE = { + "group": "autoscaling", + "version": "v2", + "kind": "horizontalpodautoscalers", +} -def inference_service_gvk(): +def inference_service_group_version_kind(): """ - Return the GVK needed for an InferenceService. + Return the Kubernetes group/version/kind mapping for an InferenceService. This also checks the APP_VERSION env var to detect the version. """ diff --git a/backend/apps/v1beta1/__init__.py b/backend/apps/v1beta1/__init__.py index ec40c2c7..cbc04991 100644 --- a/backend/apps/v1beta1/__init__.py +++ b/backend/apps/v1beta1/__init__.py @@ -10,14 +10,14 @@ log = logging.getLogger(__name__) -def create_app(name=__name__, cfg: config.Config = None): +def create_app(name=__name__, configuration: config.Config = None): """Create a WSGI app.""" - cfg = config.Config() if cfg is None else cfg + configuration = config.Config() if configuration is None else configuration # Properly set the static serving directory static_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "static") - app = create_default_app(name, static_dir, cfg) + app = create_default_app(name, static_dir, configuration) log.info("Setting STATIC_DIR to: " + static_dir) app.config["STATIC_DIR"] = static_dir diff --git a/backend/apps/v1beta1/routes/get.py b/backend/apps/v1beta1/routes/get.py index ed294bf1..202e2f53 100644 --- a/backend/apps/v1beta1/routes/get.py +++ b/backend/apps/v1beta1/routes/get.py @@ -14,7 +14,7 @@ def get_config(): """Handle retrieval of application configuration.""" try: - config = { + configuration_payload = { "grafanaPrefix": os.environ.get("GRAFANA_PREFIX", "/grafana"), "grafanaCpuMemoryDb": os.environ.get( "GRAFANA_CPU_MEMORY_DB", @@ -25,8 +25,8 @@ def get_config(): ), } - log.info("Configuration requested: %s", config) - return jsonify(config) + log.info("Configuration requested: %s", configuration_payload) + return jsonify(configuration_payload) except Exception as e: log.error("Error retrieving configuration: %s", str(e)) return api.error_response("message", "Failed to retrieve configuration"), 500 diff --git a/backend/apps/v1beta1/routes/post.py b/backend/apps/v1beta1/routes/post.py index 034eda69..2fa415df 100644 --- a/backend/apps/v1beta1/routes/post.py +++ b/backend/apps/v1beta1/routes/post.py @@ -15,9 +15,11 @@ @decorators.required_body_params("apiVersion", "kind", "metadata", "spec") def post_inference_service(namespace): """Handle creation of an InferenceService.""" - cr = request.get_json() + custom_resource = request.get_json() - gvk = versions.inference_service_gvk() - api.create_custom_rsrc(**gvk, data=cr, namespace=namespace) + group_version_kind = versions.inference_service_group_version_kind() + api.create_custom_rsrc( + **group_version_kind, data=custom_resource, namespace=namespace + ) return api.success_response("message", "InferenceService successfully created.") diff --git a/backend/apps/v1beta1/routes/put.py b/backend/apps/v1beta1/routes/put.py index 6025d2fe..34f35dec 100644 --- a/backend/apps/v1beta1/routes/put.py +++ b/backend/apps/v1beta1/routes/put.py @@ -8,28 +8,31 @@ log = logging.getLogger(__name__) -@bp.route("/api/namespaces//inferenceservices/", methods=["PUT"]) +@bp.route( + "/api/namespaces//inferenceservices/", + methods=["PUT"], +) @decorators.request_is_json_type @decorators.required_body_params("apiVersion", "kind", "metadata", "spec") -def replace_inference_service(namespace: str, isvc: str): - gvk = versions.inference_service_gvk() +def replace_inference_service(namespace: str, inference_service_name: str): + group_version_kind = versions.inference_service_group_version_kind() api.authz.ensure_authorized( "update", - group=gvk["group"], - version=gvk["version"], - resource=gvk["kind"], + group=group_version_kind["group"], + version=group_version_kind["version"], + resource=group_version_kind["kind"], namespace=namespace, ) - cr = request.get_json() + custom_resource = request.get_json() api.custom_api.replace_namespaced_custom_object( - group=gvk["group"], - version=gvk["version"], - plural=gvk["kind"], + group=group_version_kind["group"], + version=group_version_kind["version"], + plural=group_version_kind["kind"], namespace=namespace, - name=isvc, - body=cr, + name=inference_service_name, + body=custom_resource, ) return api.success_response("message", "InferenceService successfully updated") diff --git a/backend/entrypoint.py b/backend/entrypoint.py index d4f44e7c..c538036c 100755 --- a/backend/entrypoint.py +++ b/backend/entrypoint.py @@ -24,13 +24,13 @@ "GRAFANA_HTTP_REQUESTS_DB", "db/knative-serving-revision-http-requests" ) -cfg = config.get_config(BACKEND_MODE) -cfg.PREFIX = PREFIX -cfg.APP_VERSION = APP_VERSION +backend_configuration = config.get_config(BACKEND_MODE) +backend_configuration.PREFIX = PREFIX +backend_configuration.APP_VERSION = APP_VERSION # Load the app based on APP_VERSION env var if APP_VERSION == "v1beta1": - app = v1beta1.create_app(APP_NAME, cfg) + app = v1beta1.create_app(APP_NAME, backend_configuration) else: log.error("No app for: %s", APP_VERSION) sys.exit(1) diff --git a/frontend/src/types/kubeflow.d.ts b/frontend/src/types/kubeflow.d.ts new file mode 100644 index 00000000..35c8aab7 --- /dev/null +++ b/frontend/src/types/kubeflow.d.ts @@ -0,0 +1,94 @@ +declare module 'kubeflow' { + export enum STATUS_TYPE { + UNINITIALIZED = 'Uninitialized', + TERMINATING = 'Terminating', + WARNING = 'Warning', + READY = 'Ready', + } + + export interface Condition { + type: string; + status: string; + reason?: string; + message?: string; + } + + export interface Status { + phase: STATUS_TYPE; + state: string; + message: string; + } + + export interface K8sObject { + kind?: string; + metadata?: { + name?: string; + namespace?: string; + deletionTimestamp?: string | Date; + [key: string]: any; + }; + status?: { + conditions?: Condition[] | null; + [key: string]: any; + }; + spec?: any; + [key: string]: any; + } + + export enum PredictorType { + Tensorflow = 'tensorflow', + Triton = 'triton', + Sklearn = 'sklearn', + Onnx = 'onnx', + Pytorch = 'pytorch', + Xgboost = 'xgboost', + Pmml = 'pmml', + Lightgbm = 'lightgbm', + MLFlow = 'mlflow', + Custom = 'custom', + } + + export interface PredictorExtensionSpec { + storageUri?: string; + runtimeVersion?: string; + protocolVersion?: string; + image?: string; + name?: string; + env?: Array<{ + name: string; + value: string; + }>; + [key: string]: any; + } + + export interface ModelSpec extends PredictorExtensionSpec { + modelFormat: { + name: string; + version?: string; + }; + runtime?: string; + } + + export interface PredictorSpec { + sklearn?: PredictorExtensionSpec; + xgboost?: PredictorExtensionSpec; + tensorflow?: PredictorExtensionSpec; + pytorch?: PredictorExtensionSpec; + triton?: PredictorExtensionSpec; + onnx?: PredictorExtensionSpec; + pmml?: PredictorExtensionSpec; + lightgbm?: PredictorExtensionSpec; + mlflow?: PredictorExtensionSpec; + model?: ModelSpec; + containers?: Array<{ + name?: string; + image?: string; + env?: Array<{ + name: string; + value: string; + }>; + [key: string]: any; + }>; + [key: string]: any; + } +} From 49aef0724a871e0645fc6fa0dac237a029df23b5 Mon Sep 17 00:00:00 2001 From: Hijanhv Date: Mon, 3 Nov 2025 21:41:12 +0530 Subject: [PATCH 2/5] Fix kubeflow types module definition and logging usage Signed-off-by: Hijanhv --- backend/apps/common/routes/get.py | 2 +- frontend/src/types/kubeflow.d.ts | 160 ++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) diff --git a/backend/apps/common/routes/get.py b/backend/apps/common/routes/get.py index 5dc5a7f9..68167194 100644 --- a/backend/apps/common/routes/get.py +++ b/backend/apps/common/routes/get.py @@ -72,7 +72,7 @@ def get_inference_service_logs(inference_service): return {} logs_by_component = {} - logging.info("Component pods: %s", component_pods_dict) + log.info("Component pods: %s", component_pods_dict) for component, pods in component_pods_dict.items(): if component not in logs_by_component: logs_by_component[component] = [] diff --git a/frontend/src/types/kubeflow.d.ts b/frontend/src/types/kubeflow.d.ts index 35c8aab7..021e4ff5 100644 --- a/frontend/src/types/kubeflow.d.ts +++ b/frontend/src/types/kubeflow.d.ts @@ -4,6 +4,166 @@ declare module 'kubeflow' { TERMINATING = 'Terminating', WARNING = 'Warning', READY = 'Ready', + UNAVAILABLE = 'Unavailable', + } + + export enum SnackType { + Info = 'Info', + Warning = 'Warning', + Error = 'Error', + Success = 'Success', + } + + export enum DashboardState { + Disconnected = 'Disconnected', + } + + export enum DIALOG_RESP { + ACCEPT = 'ACCEPT', + REJECT = 'REJECT', + CANCEL = 'CANCEL', + } + + export interface ActionEvent { + action: string; + data: any; + event: any; + } + + export class ToolbarButton { + constructor(options?: any); + namespaceChanged(namespace: string | string[], resourceLabel: string): void; + } + + export interface SnackBarConfig { + data?: any; + duration?: number; + [key: string]: any; + } + + export class SnackBarService { + open(config: SnackBarConfig): void; + } + + export class ConfirmDialogService { + open(title: string, config?: DialogConfig): any; + } + + export class NamespaceService { + dashboardConnected$?: any; + getSelectedNamespace(): any; + getSelectedNamespace2(): any; + updateSelectedNamespace(namespace: string): any; + } + + export class PollerService { + exponential(request: any): any; + } + + export class BackendService { + constructor(http: any, snack: SnackBarService); + protected handleError(error: any, showSnack?: boolean): any; + protected getObjectsAllNamespaces( + getter: (namespace: string) => any, + namespaces: string[], + ): any; + } + + export class BackendResponse { + [key: string]: any; + } + + export class KubeflowModule { + static ɵmod: any; + static ɵinj: any; + } + + export class EditorModule { + static ɵmod: any; + static ɵinj: any; + } + + export class ConditionsTableModule { + static ɵmod: any; + static ɵinj: any; + } + + export class DetailsListModule { + static ɵmod: any; + static ɵinj: any; + } + + export class HeadingSubheadingRowModule { + static ɵmod: any; + static ɵinj: any; + } + + export class ExponentialBackoff { + constructor(config?: any); + } + + export const LinkType: { + Internal: string; + External?: string; + [key: string]: string | undefined; + }; + + export class PropertyValue { + constructor(options?: any); + } + + export class StatusValue { + constructor(options?: any); + } + + export class ActionListValue { + constructor(actions?: T[]); + } + + export class ActionIconValue { + constructor(options?: any); + } + + export class DateTimeValue { + constructor(options?: any); + } + + export interface DialogConfig { + title?: string; + message?: string; + accept?: string; + applying?: string; + confirmColor?: string; + cancel?: string; + [key: string]: any; + } + + export interface TableConfig { + dynamicNamespaceColumn?: boolean; + columns: any[]; + [key: string]: any; + } + + export class ComponentValue { + constructor(options?: any); + } + + export class LinkValue { + constructor(options?: any); + } + + export class TableColumnComponent {} + + export interface ListEntry { + label: string; + value?: any; + [key: string]: any; + } + + export interface ChipDescriptor { + name: string; + value?: any; + [key: string]: any; } export interface Condition { From 380bd7decd64ed97ec9d81198fb3a75f30a89049 Mon Sep 17 00:00:00 2001 From: Hijanhv Date: Mon, 3 Nov 2025 22:26:42 +0530 Subject: [PATCH 3/5] Improve kubeflow typings and TypeScript configuration Signed-off-by: Hijanhv --- .../server-info/details/details.component.ts | 6 ++++-- .../details/explainer/explainer.component.ts | 15 +++++++++++---- .../shared/container/container.component.ts | 3 ++- .../server-info/overview/overview.component.ts | 1 + frontend/src/types/kubeflow.d.ts | 6 ++++++ frontend/tsconfig.app.json | 16 ++++++++++++++-- 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/pages/server-info/details/details.component.ts b/frontend/src/app/pages/server-info/details/details.component.ts index f550b0cd..25d3bee7 100644 --- a/frontend/src/app/pages/server-info/details/details.component.ts +++ b/frontend/src/app/pages/server-info/details/details.component.ts @@ -61,7 +61,8 @@ export class DetailsComponent { continue; } const chip: ChipDescriptor = { - value: `${annotationKey}: ${annotationVal}`, + name: annotationKey, + value: annotationVal, color: 'primary', }; chips.push(chip); @@ -85,7 +86,8 @@ export class DetailsComponent { const labelVal = this.svc.metadata.labels[l]; const chip: ChipDescriptor = { - value: `${labelKey}: ${labelVal}`, + name: labelKey, + value: labelVal, color: 'primary', }; diff --git a/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts b/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts index f7ff771f..5fd6870b 100644 --- a/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts +++ b/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts @@ -13,6 +13,7 @@ export class ExplainerComponent { @Input() set explainerSpec(spec: ExplainerSpec) { this.explainerPrv = spec; + this.config = this.generateConfig(spec); } get explainerSpec(): ExplainerSpec { return this.explainerPrv; @@ -23,14 +24,20 @@ export class ExplainerComponent { private generateConfig(spec: ExplainerSpec): ChipDescriptor[] { const chips = []; - for (const key in this.explainerSpec.alibi.config) { - if (this.explainerSpec.alibi.config.hasOwnProperty(key)) { + const config = spec?.alibi?.config; + if (!config) { + return chips; + } + + for (const key in config) { + if (!Object.prototype.hasOwnProperty.call(config, key)) { continue; } - const val = this.explainerSpec.alibi.config[key]; + const val = config[key]; chips.push({ - value: `${key}: ${val}`, + name: key, + value: val, color: 'primary', }); } diff --git a/frontend/src/app/pages/server-info/details/shared/container/container.component.ts b/frontend/src/app/pages/server-info/details/shared/container/container.component.ts index c1796f57..98146b5f 100644 --- a/frontend/src/app/pages/server-info/details/shared/container/container.component.ts +++ b/frontend/src/app/pages/server-info/details/shared/container/container.component.ts @@ -31,7 +31,8 @@ export class ContainerComponent { for (const envVar of c.env) { chips.push({ - value: `${envVar.name}: ${envVar.value}`, + name: envVar.name, + value: envVar.value, color: 'primary', tooltip: envVar.value, }); diff --git a/frontend/src/app/pages/server-info/overview/overview.component.ts b/frontend/src/app/pages/server-info/overview/overview.component.ts index e66a9718..4796d1f5 100644 --- a/frontend/src/app/pages/server-info/overview/overview.component.ts +++ b/frontend/src/app/pages/server-info/overview/overview.component.ts @@ -82,6 +82,7 @@ export class OverviewComponent { for (const c of ['predictor', 'transformer', 'explainer']) { if (c in svc.spec) { chips.push({ + name: c, value: c, color: 'primary', }); diff --git a/frontend/src/types/kubeflow.d.ts b/frontend/src/types/kubeflow.d.ts index 021e4ff5..7a2c7db6 100644 --- a/frontend/src/types/kubeflow.d.ts +++ b/frontend/src/types/kubeflow.d.ts @@ -100,6 +100,7 @@ declare module 'kubeflow' { export class ExponentialBackoff { constructor(config?: any); + start(): any; } export const LinkType: { @@ -128,6 +129,11 @@ declare module 'kubeflow' { constructor(options?: any); } + export class DateTimeModule {} + export class PanelModule {} + export class LoadingSpinnerModule {} + export class LogsViewerModule {} + export interface DialogConfig { title?: string; message?: string; diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index df4d05e1..993782ed 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -2,17 +2,29 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": [], + "baseUrl": "./src", + "paths": { + "kubeflow": ["types/kubeflow.d.ts"] + }, + "typeRoots": [ + "src/types", + "node_modules/@types" + ], + "skipLibCheck": true }, "files": [ "src/main.ts", "src/polyfills.ts" ], "include": [ + "src/**/*.ts", "src/**/*.d.ts" ], "exclude": [ "src/test.ts", - "src/**/*.spec.ts" + "src/**/*.spec.ts", + "src/**/*.jest.spec.ts", + "e2e/**" ] } From 6c14a272c2543164d1003434a126d6c0a61045af Mon Sep 17 00:00:00 2001 From: Hijanhv Date: Mon, 3 Nov 2025 23:33:14 +0530 Subject: [PATCH 4/5] Run linters and format code Signed-off-by: Hijanhv --- .../linting_bash_python_yaml_files.yaml | 3 +- .github/workflows/oci-release.yml | 90 ++++----- .github/workflows/stale.yaml | 2 +- .github/workflows/test-node.yaml | 186 +++++++++--------- backend/apps/common/routes/get.py | 8 +- backend/apps/common/utils.py | 8 +- backend/apps/common/versions.py | 6 +- frontend/src/app/pages/index/config.ts | 6 +- .../src/app/pages/index/index.component.html | 2 +- .../src/app/pages/index/index.component.ts | 159 ++++++++------- 10 files changed, 243 insertions(+), 227 deletions(-) diff --git a/.github/workflows/linting_bash_python_yaml_files.yaml b/.github/workflows/linting_bash_python_yaml_files.yaml index f64419ce..61d33585 100644 --- a/.github/workflows/linting_bash_python_yaml_files.yaml +++ b/.github/workflows/linting_bash_python_yaml_files.yaml @@ -129,9 +129,8 @@ jobs: if [ ! -s changed_files_in_PR.txt ]; then echo "No bash files have changed in this PR." fi - - name: Display changed files - if: always() # Always run this step + if: always() # Always run this step run: cat changed_files_in_PR.txt || echo "No bash files have changed in this PR." - name: Run ShellCheck on changed files diff --git a/.github/workflows/oci-release.yml b/.github/workflows/oci-release.yml index f5b6549d..24886ef5 100644 --- a/.github/workflows/oci-release.yml +++ b/.github/workflows/oci-release.yml @@ -4,12 +4,12 @@ on: push: # Publish `master` as `latest` OCI image. branches: - - master - - v* + - master + - v* # Publish `v1.2.3` tags as releases. tags: - - v* + - v* # Run tests for any PRs. pull_request: @@ -22,11 +22,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Run tests - run: | - make docker-build + - name: Run tests + run: | + make docker-build push: needs: test @@ -38,46 +38,46 @@ jobs: contents: read steps: - - name: Check out code - uses: actions/checkout@v4 + - name: Check out code + uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - with: - platforms: amd64,ppc64le,arm64 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: amd64,ppc64le,arm64 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Log into GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Log into GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for OCI image - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=sha - # Add 'latest' tag for master branch pushes - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} + - name: Extract metadata (tags, labels) for OCI image + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=sha + # Add 'latest' tag for master branch pushes + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} - - name: Build and push multi-architecture OCI image - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - platforms: ${{ env.PLATFORMS }} - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Build and push multi-architecture OCI image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: ${{ env.PLATFORMS }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 87a49432..6208ff02 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -7,7 +7,7 @@ name: Mark stale issues and pull requests on: schedule: - - cron: '0 0 * * *' # Run every day at midnight + - cron: '0 0 * * *' # Run every day at midnight workflow_dispatch: jobs: diff --git a/.github/workflows/test-node.yaml b/.github/workflows/test-node.yaml index 29beea62..c403f676 100644 --- a/.github/workflows/test-node.yaml +++ b/.github/workflows/test-node.yaml @@ -2,103 +2,103 @@ name: Frontend Test on: pull_request: paths: - - frontend/** + - frontend/** jobs: frontend-format-linting-check: name: Code format and lint runs-on: ubuntu-latest steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - name: Format code - run: | - npm install prettier@2.8.8 --prefix ./frontend - make prettier-check - - name: Lint code - run: | - cd frontend - npm ci --no-audit - npm run lint-check + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + - name: Format code + run: | + npm install prettier@2.8.8 --prefix ./frontend + make prettier-check + - name: Lint code + run: | + cd frontend + npm ci --no-audit + npm run lint-check frontend-unit-tests: - name: Frontend Unit Tests - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - name: Fetch Kubeflow and Build Common Library - run: | - COMMIT=$(cat frontend/COMMIT) - cd /tmp && git clone https://github.com/kubeflow/kubeflow.git - cd kubeflow - git checkout $COMMIT - cd components/crud-web-apps/common/frontend/kubeflow-common-lib - npm ci --no-audit - npm run build - npm link ./dist/kubeflow - - name: Install Frontend Dependencies and Setup Styles - run: | - cd frontend - npm ci --no-audit - npm link kubeflow - # Copy styles from kubeflow source to local styles directory - mkdir -p ./src/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ - # Also copy to node_modules for the copyCSS script - mkdir -p ./node_modules/kubeflow/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ - - name: Run Unit Tests - run: | - cd frontend - npm run test:jest + name: Frontend Unit Tests + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + - name: Fetch Kubeflow and Build Common Library + run: | + COMMIT=$(cat frontend/COMMIT) + cd /tmp && git clone https://github.com/kubeflow/kubeflow.git + cd kubeflow + git checkout $COMMIT + cd components/crud-web-apps/common/frontend/kubeflow-common-lib + npm ci --no-audit + npm run build + npm link ./dist/kubeflow + - name: Install Frontend Dependencies and Setup Styles + run: | + cd frontend + npm ci --no-audit + npm link kubeflow + # Copy styles from kubeflow source to local styles directory + mkdir -p ./src/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ + # Also copy to node_modules for the copyCSS script + mkdir -p ./node_modules/kubeflow/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ + - name: Run Unit Tests + run: | + cd frontend + npm run test:jest frontend-mock-tests: - name: Frontend Mock Tests - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - name: Fetch Kubeflow and Build Common Library - run: | - COMMIT=$(cat frontend/COMMIT) - cd /tmp && git clone https://github.com/kubeflow/kubeflow.git - cd kubeflow - git checkout $COMMIT - cd components/crud-web-apps/common/frontend/kubeflow-common-lib - npm ci --no-audit - npm run build - npm link ./dist/kubeflow - - name: Install Frontend Dependencies and Setup Styles - run: | - cd frontend - npm ci --no-audit - npm link kubeflow - # Copy styles from kubeflow source to local styles directory - mkdir -p ./src/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ - # Also copy to node_modules for the copyCSS script - mkdir -p ./node_modules/kubeflow/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ - # Copy assets as well - mkdir -p ./node_modules/kubeflow/assets/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/assets/* ./node_modules/kubeflow/assets/ - - name: Run E2E Tests - run: | - cd frontend - npm run e2e:cypress:ci + name: Frontend Mock Tests + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + - name: Fetch Kubeflow and Build Common Library + run: | + COMMIT=$(cat frontend/COMMIT) + cd /tmp && git clone https://github.com/kubeflow/kubeflow.git + cd kubeflow + git checkout $COMMIT + cd components/crud-web-apps/common/frontend/kubeflow-common-lib + npm ci --no-audit + npm run build + npm link ./dist/kubeflow + - name: Install Frontend Dependencies and Setup Styles + run: | + cd frontend + npm ci --no-audit + npm link kubeflow + # Copy styles from kubeflow source to local styles directory + mkdir -p ./src/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ + # Also copy to node_modules for the copyCSS script + mkdir -p ./node_modules/kubeflow/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ + # Copy assets as well + mkdir -p ./node_modules/kubeflow/assets/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/assets/* ./node_modules/kubeflow/assets/ + - name: Run E2E Tests + run: | + cd frontend + npm run e2e:cypress:ci diff --git a/backend/apps/common/routes/get.py b/backend/apps/common/routes/get.py index 68167194..c457c7b3 100644 --- a/backend/apps/common/routes/get.py +++ b/backend/apps/common/routes/get.py @@ -14,9 +14,7 @@ def get_inference_services(namespace): """Return a list of InferenceService custom resources as JSON objects.""" group_version_kind = versions.inference_service_group_version_kind() - inference_services = api.list_custom_rsrc( - **group_version_kind, namespace=namespace - ) + inference_services = api.list_custom_rsrc(**group_version_kind, namespace=namespace) return api.success_response("inferenceServices", inference_services["items"]) @@ -55,9 +53,7 @@ def get_inference_service_logs(inference_service): if deployment_mode == "ModelMesh": # For ModelMesh, get logs from modelmesh-serving deployment - component_pods_dict = utils.get_modelmesh_pods( - inference_service, components - ) + component_pods_dict = utils.get_modelmesh_pods(inference_service, components) elif deployment_mode == "RawDeployment": component_pods_dict = utils.get_raw_inference_service_pods( inference_service, components diff --git a/backend/apps/common/utils.py b/backend/apps/common/utils.py index 6fde6512..c6dedbe2 100644 --- a/backend/apps/common/utils.py +++ b/backend/apps/common/utils.py @@ -278,9 +278,7 @@ def get_raw_deployment_objects(inference_service, component): f"Found HorizontalPodAutoscaler {resource_name} for component {component}" ) except Exception as e: - log.debug( - f"No HorizontalPodAutoscaler found for {resource_name}: {e}" - ) + log.debug(f"No HorizontalPodAutoscaler found for {resource_name}: {e}") return objects @@ -298,7 +296,9 @@ def get_modelmesh_objects(inference_service, component): } # 1. Get predictor status - if "status" in inference_service and "components" in inference_service.get("status", {}): + if "status" in inference_service and "components" in inference_service.get( + "status", {} + ): objects["predictor"] = inference_service["status"]["components"].get(component) # 2. Determine the ServingRuntime name (early exit if not found) diff --git a/backend/apps/common/versions.py b/backend/apps/common/versions.py index c63a5bb0..91d4fd50 100644 --- a/backend/apps/common/versions.py +++ b/backend/apps/common/versions.py @@ -16,7 +16,11 @@ KNATIVE_SERVICE = {"group": "serving.knative.dev", "version": "v1", "kind": "services"} # Kubernetes native resources for RawDeployment mode -KUBERNETES_DEPLOYMENT_RESOURCE = {"group": "apps", "version": "v1", "kind": "deployments"} +KUBERNETES_DEPLOYMENT_RESOURCE = { + "group": "apps", + "version": "v1", + "kind": "deployments", +} KUBERNETES_SERVICE_RESOURCE = {"group": "", "version": "v1", "kind": "services"} KUBERNETES_HPA_RESOURCE = { "group": "autoscaling", diff --git a/frontend/src/app/pages/index/config.ts b/frontend/src/app/pages/index/config.ts index 2f6cb17f..9ad899f8 100644 --- a/frontend/src/app/pages/index/config.ts +++ b/frontend/src/app/pages/index/config.ts @@ -15,9 +15,11 @@ import { getPredictorExtensionSpec } from 'src/app/shared/utils'; import { parseRuntime } from 'src/app/shared/utils'; import { InferenceServiceK8s } from 'src/app/types/kfserving/v1beta1'; -export function generateDeleteConfig(svc: InferenceServiceK8s): DialogConfig { +export function generateDeleteConfig( + inferenceService: InferenceServiceK8s, +): DialogConfig { return { - title: $localize`Delete Endpoint ${svc.metadata.name}?`, + title: $localize`Delete Endpoint ${inferenceService.metadata.name}?`, message: $localize`You cannot undo this action. Are you sure you want to delete this Endpoint?`, accept: $localize`DELETE`, applying: $localize`DELETING`, diff --git a/frontend/src/app/pages/index/index.component.html b/frontend/src/app/pages/index/index.component.html index 0c564e27..25b4cf68 100644 --- a/frontend/src/app/pages/index/index.component.html +++ b/frontend/src/app/pages/index/index.component.html @@ -10,7 +10,7 @@ diff --git a/frontend/src/app/pages/index/index.component.ts b/frontend/src/app/pages/index/index.component.ts index 2fced73a..83a1f62c 100644 --- a/frontend/src/app/pages/index/index.component.ts +++ b/frontend/src/app/pages/index/index.component.ts @@ -35,10 +35,10 @@ import { export class IndexComponent implements OnInit, OnDestroy { env = environment; - nsSub = new Subscription(); - pollSub = new Subscription(); + namespaceSubscription = new Subscription(); + pollingSubscription = new Subscription(); - currNamespace: string | string[]; + currentNamespace: string | string[]; config = defaultConfig; inferenceServices: InferenceServiceIR[] = []; @@ -67,143 +67,155 @@ export class IndexComponent implements OnInit, OnDestroy { ngOnInit(): void { // Reset the poller whenever the selected namespace changes - this.nsSub = this.ns.getSelectedNamespace2().subscribe(ns => { - this.currNamespace = ns; - this.poll(ns); - this.newEndpointButton.namespaceChanged(ns, $localize`Endpoint`); - }); + this.namespaceSubscription = this.ns + .getSelectedNamespace2() + .subscribe(selectedNamespace => { + this.currentNamespace = selectedNamespace; + this.refreshInferenceServices(selectedNamespace); + this.newEndpointButton.namespaceChanged( + selectedNamespace, + $localize`Endpoint`, + ); + }); } ngOnDestroy() { - this.nsSub.unsubscribe(); - this.pollSub.unsubscribe(); + this.namespaceSubscription.unsubscribe(); + this.pollingSubscription.unsubscribe(); } - public poll(ns: string | string[]) { - this.pollSub.unsubscribe(); + public refreshInferenceServices(namespace: string | string[]) { + this.pollingSubscription.unsubscribe(); this.inferenceServices = []; - const request = this.backend.getInferenceServices(ns); + const request = this.backend.getInferenceServices(namespace); - this.pollSub = this.poller.exponential(request).subscribe(svcs => { - this.inferenceServices = this.processIncomingData(svcs); - }); + this.pollingSubscription = this.poller + .exponential(request) + .subscribe(inferenceServiceList => { + this.inferenceServices = this.processIncomingData(inferenceServiceList); + }); } // action handling functions - public reactToAction(a: ActionEvent) { - const svc = a.data as InferenceServiceIR; + public reactToAction(action: ActionEvent) { + const inferenceService = action.data as InferenceServiceIR; - switch (a.action) { + switch (action.action) { case 'delete': - this.deleteClicked(svc); + this.deleteClicked(inferenceService); break; case 'copy-link': - console.log(`Copied to clipboard: ${svc.status.url}`); - this.clipboard.copy(svc.status.url); - const config: SnackBarConfig = { + console.log(`Copied to clipboard: ${inferenceService.status.url}`); + this.clipboard.copy(inferenceService.status.url); + const snackBarConfig: SnackBarConfig = { data: { - msg: `Copied: ${svc.status.url}`, + msg: `Copied: ${inferenceService.status.url}`, snackType: SnackType.Info, }, }; - this.snack.open(config); + this.snack.open(snackBarConfig); break; case 'name:link': /* * don't allow the user to navigate to the details page of a server * that is being deleted */ - if (svc.ui.status.phase === STATUS_TYPE.TERMINATING) { - a.event.stopPropagation(); - a.event.preventDefault(); - const config: SnackBarConfig = { + if (inferenceService.ui.status.phase === STATUS_TYPE.TERMINATING) { + action.event.stopPropagation(); + action.event.preventDefault(); + const snackBarConfig: SnackBarConfig = { data: { msg: $localize`Endpoint is being deleted, cannot show details.`, snackType: SnackType.Info, }, }; - this.snack.open(config); + this.snack.open(snackBarConfig); return; } break; } } - private deleteClicked(svc: InferenceServiceIR) { - const config = generateDeleteConfig(svc); + private deleteClicked(inferenceService: InferenceServiceIR) { + const dialogConfig = generateDeleteConfig(inferenceService); - const dialogRef = this.confirmDialog.open('Endpoint', config); - const applyingSub = dialogRef.componentInstance.applying$.subscribe( - applying => { + const dialogRef = this.confirmDialog.open('Endpoint', dialogConfig); + const applyingSubscription = + dialogRef.componentInstance.applying$.subscribe(applying => { if (!applying) { return; } - this.backend.deleteInferenceService(svc).subscribe( - res => { + this.backend.deleteInferenceService(inferenceService).subscribe( + () => { dialogRef.close(DIALOG_RESP.ACCEPT); }, - err => { - config.error = err; + error => { + dialogConfig.error = error; dialogRef.componentInstance.applying$.next(false); }, ); - }, - ); + }); - dialogRef.afterClosed().subscribe(res => { - applyingSub.unsubscribe(); + dialogRef.afterClosed().subscribe(dialogResponse => { + applyingSubscription.unsubscribe(); - if (res !== DIALOG_RESP.ACCEPT) { + if (dialogResponse !== DIALOG_RESP.ACCEPT) { return; } - svc.ui.status.phase = STATUS_TYPE.TERMINATING; - svc.ui.status.message = $localize`Preparing to delete Endpoint...`; + inferenceService.ui.status.phase = STATUS_TYPE.TERMINATING; + inferenceService.ui.status.message = $localize`Preparing to delete Endpoint...`; }); } // functions for converting the response InferenceServices to the // Internal Representation objects - private processIncomingData(svcs: InferenceServiceK8s[]) { - const svcsCopy: InferenceServiceIR[] = JSON.parse(JSON.stringify(svcs)); + private processIncomingData(inferenceServices: InferenceServiceK8s[]) { + const inferenceServicesCopy: InferenceServiceIR[] = JSON.parse( + JSON.stringify(inferenceServices), + ); - for (const svc of svcsCopy) { - this.parseInferenceService(svc); + for (const inferenceService of inferenceServicesCopy) { + this.parseInferenceService(inferenceService); } - return svcsCopy; + return inferenceServicesCopy; } - private parseInferenceService(svc: InferenceServiceIR) { - svc.ui = { actions: {} }; - svc.ui.status = getK8sObjectUiStatus(svc); - svc.ui.actions.copy = this.getCopyActionStatus(svc); - svc.ui.actions.delete = this.getDeletionActionStatus(svc); - - const predictorType = getPredictorType(svc.spec.predictor); - const predictor = getPredictorExtensionSpec(svc.spec.predictor); - svc.ui.predictorType = predictorType; - svc.ui.runtimeVersion = predictor.runtimeVersion; - svc.ui.storageUri = predictor.storageUri; - svc.ui.protocolVersion = predictor.protocolVersion || 'v1'; - svc.ui.link = { - text: svc.metadata.name, - url: `/details/${svc.metadata.namespace}/${svc.metadata.name}`, + private parseInferenceService(inferenceService: InferenceServiceIR) { + inferenceService.ui = { actions: {} }; + inferenceService.ui.status = getK8sObjectUiStatus(inferenceService); + inferenceService.ui.actions.copy = + this.getCopyActionStatus(inferenceService); + inferenceService.ui.actions.delete = + this.getDeletionActionStatus(inferenceService); + + const predictorType = getPredictorType(inferenceService.spec.predictor); + const predictor = getPredictorExtensionSpec( + inferenceService.spec.predictor, + ); + inferenceService.ui.predictorType = predictorType; + inferenceService.ui.runtimeVersion = predictor.runtimeVersion; + inferenceService.ui.storageUri = predictor.storageUri; + inferenceService.ui.protocolVersion = predictor.protocolVersion || 'v1'; + inferenceService.ui.link = { + text: inferenceService.metadata.name, + url: `/details/${inferenceService.metadata.namespace}/${inferenceService.metadata.name}`, }; } - private getCopyActionStatus(svc: InferenceServiceIR) { - if (svc.ui.status.phase !== STATUS_TYPE.READY) { + private getCopyActionStatus(inferenceService: InferenceServiceIR) { + if (inferenceService.ui.status.phase !== STATUS_TYPE.READY) { return STATUS_TYPE.UNAVAILABLE; } return STATUS_TYPE.READY; } - private getDeletionActionStatus(svc: InferenceServiceIR) { - if (svc.ui.status.phase !== STATUS_TYPE.TERMINATING) { + private getDeletionActionStatus(inferenceService: InferenceServiceIR) { + if (inferenceService.ui.status.phase !== STATUS_TYPE.TERMINATING) { return STATUS_TYPE.READY; } @@ -211,7 +223,10 @@ export class IndexComponent implements OnInit, OnDestroy { } // util functions - public inferenceServiceTrackByFn(index: number, svc: InferenceServiceK8s) { - return `${svc.metadata.name}/${svc.metadata.creationTimestamp}`; + public trackInferenceService( + index: number, + inferenceService: InferenceServiceK8s, + ) { + return `${inferenceService.metadata.name}/${inferenceService.metadata.creationTimestamp}`; } } From 133a8c07c6f1a83faeb45b68c7cb3a424fa769ae Mon Sep 17 00:00:00 2001 From: Hijanhv Date: Thu, 13 Nov 2025 23:11:18 +0530 Subject: [PATCH 5/5] Revert frontend descriptive rename changes --- .../linting_bash_python_yaml_files.yaml | 3 +- .github/workflows/oci-release.yml | 90 +++--- .github/workflows/stale.yaml | 2 +- .github/workflows/test-node.yaml | 186 ++++++------- frontend/src/app/pages/index/config.ts | 6 +- .../src/app/pages/index/index.component.html | 2 +- .../src/app/pages/index/index.component.ts | 159 +++++------ .../server-info/details/details.component.ts | 6 +- .../details/explainer/explainer.component.ts | 15 +- .../shared/container/container.component.ts | 3 +- .../overview/overview.component.ts | 1 - frontend/src/types/kubeflow.d.ts | 260 ------------------ frontend/tsconfig.app.json | 16 +- 13 files changed, 225 insertions(+), 524 deletions(-) delete mode 100644 frontend/src/types/kubeflow.d.ts diff --git a/.github/workflows/linting_bash_python_yaml_files.yaml b/.github/workflows/linting_bash_python_yaml_files.yaml index 61d33585..f64419ce 100644 --- a/.github/workflows/linting_bash_python_yaml_files.yaml +++ b/.github/workflows/linting_bash_python_yaml_files.yaml @@ -129,8 +129,9 @@ jobs: if [ ! -s changed_files_in_PR.txt ]; then echo "No bash files have changed in this PR." fi + - name: Display changed files - if: always() # Always run this step + if: always() # Always run this step run: cat changed_files_in_PR.txt || echo "No bash files have changed in this PR." - name: Run ShellCheck on changed files diff --git a/.github/workflows/oci-release.yml b/.github/workflows/oci-release.yml index 24886ef5..f5b6549d 100644 --- a/.github/workflows/oci-release.yml +++ b/.github/workflows/oci-release.yml @@ -4,12 +4,12 @@ on: push: # Publish `master` as `latest` OCI image. branches: - - master - - v* + - master + - v* # Publish `v1.2.3` tags as releases. tags: - - v* + - v* # Run tests for any PRs. pull_request: @@ -22,11 +22,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Run tests - run: | - make docker-build + - name: Run tests + run: | + make docker-build push: needs: test @@ -38,46 +38,46 @@ jobs: contents: read steps: - - name: Check out code - uses: actions/checkout@v4 + - name: Check out code + uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - with: - platforms: amd64,ppc64le,arm64 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: amd64,ppc64le,arm64 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Log into GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + - name: Log into GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for OCI image - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=sha - # Add 'latest' tag for master branch pushes - type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} + - name: Extract metadata (tags, labels) for OCI image + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=sha + # Add 'latest' tag for master branch pushes + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }} - - name: Build and push multi-architecture OCI image - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - platforms: ${{ env.PLATFORMS }} - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Build and push multi-architecture OCI image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: ${{ env.PLATFORMS }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 6208ff02..87a49432 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -7,7 +7,7 @@ name: Mark stale issues and pull requests on: schedule: - - cron: '0 0 * * *' # Run every day at midnight + - cron: '0 0 * * *' # Run every day at midnight workflow_dispatch: jobs: diff --git a/.github/workflows/test-node.yaml b/.github/workflows/test-node.yaml index c403f676..29beea62 100644 --- a/.github/workflows/test-node.yaml +++ b/.github/workflows/test-node.yaml @@ -2,103 +2,103 @@ name: Frontend Test on: pull_request: paths: - - frontend/** + - frontend/** jobs: frontend-format-linting-check: name: Code format and lint runs-on: ubuntu-latest steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - name: Format code - run: | - npm install prettier@2.8.8 --prefix ./frontend - make prettier-check - - name: Lint code - run: | - cd frontend - npm ci --no-audit - npm run lint-check + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + - name: Format code + run: | + npm install prettier@2.8.8 --prefix ./frontend + make prettier-check + - name: Lint code + run: | + cd frontend + npm ci --no-audit + npm run lint-check frontend-unit-tests: - name: Frontend Unit Tests - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - name: Fetch Kubeflow and Build Common Library - run: | - COMMIT=$(cat frontend/COMMIT) - cd /tmp && git clone https://github.com/kubeflow/kubeflow.git - cd kubeflow - git checkout $COMMIT - cd components/crud-web-apps/common/frontend/kubeflow-common-lib - npm ci --no-audit - npm run build - npm link ./dist/kubeflow - - name: Install Frontend Dependencies and Setup Styles - run: | - cd frontend - npm ci --no-audit - npm link kubeflow - # Copy styles from kubeflow source to local styles directory - mkdir -p ./src/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ - # Also copy to node_modules for the copyCSS script - mkdir -p ./node_modules/kubeflow/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ - - name: Run Unit Tests - run: | - cd frontend - npm run test:jest + name: Frontend Unit Tests + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + - name: Fetch Kubeflow and Build Common Library + run: | + COMMIT=$(cat frontend/COMMIT) + cd /tmp && git clone https://github.com/kubeflow/kubeflow.git + cd kubeflow + git checkout $COMMIT + cd components/crud-web-apps/common/frontend/kubeflow-common-lib + npm ci --no-audit + npm run build + npm link ./dist/kubeflow + - name: Install Frontend Dependencies and Setup Styles + run: | + cd frontend + npm ci --no-audit + npm link kubeflow + # Copy styles from kubeflow source to local styles directory + mkdir -p ./src/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ + # Also copy to node_modules for the copyCSS script + mkdir -p ./node_modules/kubeflow/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ + - name: Run Unit Tests + run: | + cd frontend + npm run test:jest frontend-mock-tests: - name: Frontend Mock Tests - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - name: Fetch Kubeflow and Build Common Library - run: | - COMMIT=$(cat frontend/COMMIT) - cd /tmp && git clone https://github.com/kubeflow/kubeflow.git - cd kubeflow - git checkout $COMMIT - cd components/crud-web-apps/common/frontend/kubeflow-common-lib - npm ci --no-audit - npm run build - npm link ./dist/kubeflow - - name: Install Frontend Dependencies and Setup Styles - run: | - cd frontend - npm ci --no-audit - npm link kubeflow - # Copy styles from kubeflow source to local styles directory - mkdir -p ./src/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ - # Also copy to node_modules for the copyCSS script - mkdir -p ./node_modules/kubeflow/styles/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ - # Copy assets as well - mkdir -p ./node_modules/kubeflow/assets/ - cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/assets/* ./node_modules/kubeflow/assets/ - - name: Run E2E Tests - run: | - cd frontend - npm run e2e:cypress:ci + name: Frontend Mock Tests + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + - name: Fetch Kubeflow and Build Common Library + run: | + COMMIT=$(cat frontend/COMMIT) + cd /tmp && git clone https://github.com/kubeflow/kubeflow.git + cd kubeflow + git checkout $COMMIT + cd components/crud-web-apps/common/frontend/kubeflow-common-lib + npm ci --no-audit + npm run build + npm link ./dist/kubeflow + - name: Install Frontend Dependencies and Setup Styles + run: | + cd frontend + npm ci --no-audit + npm link kubeflow + # Copy styles from kubeflow source to local styles directory + mkdir -p ./src/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./src/styles/ + # Also copy to node_modules for the copyCSS script + mkdir -p ./node_modules/kubeflow/styles/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/styles/* ./node_modules/kubeflow/styles/ + # Copy assets as well + mkdir -p ./node_modules/kubeflow/assets/ + cp -r /tmp/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib/projects/kubeflow/src/assets/* ./node_modules/kubeflow/assets/ + - name: Run E2E Tests + run: | + cd frontend + npm run e2e:cypress:ci diff --git a/frontend/src/app/pages/index/config.ts b/frontend/src/app/pages/index/config.ts index 9ad899f8..2f6cb17f 100644 --- a/frontend/src/app/pages/index/config.ts +++ b/frontend/src/app/pages/index/config.ts @@ -15,11 +15,9 @@ import { getPredictorExtensionSpec } from 'src/app/shared/utils'; import { parseRuntime } from 'src/app/shared/utils'; import { InferenceServiceK8s } from 'src/app/types/kfserving/v1beta1'; -export function generateDeleteConfig( - inferenceService: InferenceServiceK8s, -): DialogConfig { +export function generateDeleteConfig(svc: InferenceServiceK8s): DialogConfig { return { - title: $localize`Delete Endpoint ${inferenceService.metadata.name}?`, + title: $localize`Delete Endpoint ${svc.metadata.name}?`, message: $localize`You cannot undo this action. Are you sure you want to delete this Endpoint?`, accept: $localize`DELETE`, applying: $localize`DELETING`, diff --git a/frontend/src/app/pages/index/index.component.html b/frontend/src/app/pages/index/index.component.html index 25b4cf68..0c564e27 100644 --- a/frontend/src/app/pages/index/index.component.html +++ b/frontend/src/app/pages/index/index.component.html @@ -10,7 +10,7 @@ diff --git a/frontend/src/app/pages/index/index.component.ts b/frontend/src/app/pages/index/index.component.ts index 83a1f62c..2fced73a 100644 --- a/frontend/src/app/pages/index/index.component.ts +++ b/frontend/src/app/pages/index/index.component.ts @@ -35,10 +35,10 @@ import { export class IndexComponent implements OnInit, OnDestroy { env = environment; - namespaceSubscription = new Subscription(); - pollingSubscription = new Subscription(); + nsSub = new Subscription(); + pollSub = new Subscription(); - currentNamespace: string | string[]; + currNamespace: string | string[]; config = defaultConfig; inferenceServices: InferenceServiceIR[] = []; @@ -67,155 +67,143 @@ export class IndexComponent implements OnInit, OnDestroy { ngOnInit(): void { // Reset the poller whenever the selected namespace changes - this.namespaceSubscription = this.ns - .getSelectedNamespace2() - .subscribe(selectedNamespace => { - this.currentNamespace = selectedNamespace; - this.refreshInferenceServices(selectedNamespace); - this.newEndpointButton.namespaceChanged( - selectedNamespace, - $localize`Endpoint`, - ); - }); + this.nsSub = this.ns.getSelectedNamespace2().subscribe(ns => { + this.currNamespace = ns; + this.poll(ns); + this.newEndpointButton.namespaceChanged(ns, $localize`Endpoint`); + }); } ngOnDestroy() { - this.namespaceSubscription.unsubscribe(); - this.pollingSubscription.unsubscribe(); + this.nsSub.unsubscribe(); + this.pollSub.unsubscribe(); } - public refreshInferenceServices(namespace: string | string[]) { - this.pollingSubscription.unsubscribe(); + public poll(ns: string | string[]) { + this.pollSub.unsubscribe(); this.inferenceServices = []; - const request = this.backend.getInferenceServices(namespace); + const request = this.backend.getInferenceServices(ns); - this.pollingSubscription = this.poller - .exponential(request) - .subscribe(inferenceServiceList => { - this.inferenceServices = this.processIncomingData(inferenceServiceList); - }); + this.pollSub = this.poller.exponential(request).subscribe(svcs => { + this.inferenceServices = this.processIncomingData(svcs); + }); } // action handling functions - public reactToAction(action: ActionEvent) { - const inferenceService = action.data as InferenceServiceIR; + public reactToAction(a: ActionEvent) { + const svc = a.data as InferenceServiceIR; - switch (action.action) { + switch (a.action) { case 'delete': - this.deleteClicked(inferenceService); + this.deleteClicked(svc); break; case 'copy-link': - console.log(`Copied to clipboard: ${inferenceService.status.url}`); - this.clipboard.copy(inferenceService.status.url); - const snackBarConfig: SnackBarConfig = { + console.log(`Copied to clipboard: ${svc.status.url}`); + this.clipboard.copy(svc.status.url); + const config: SnackBarConfig = { data: { - msg: `Copied: ${inferenceService.status.url}`, + msg: `Copied: ${svc.status.url}`, snackType: SnackType.Info, }, }; - this.snack.open(snackBarConfig); + this.snack.open(config); break; case 'name:link': /* * don't allow the user to navigate to the details page of a server * that is being deleted */ - if (inferenceService.ui.status.phase === STATUS_TYPE.TERMINATING) { - action.event.stopPropagation(); - action.event.preventDefault(); - const snackBarConfig: SnackBarConfig = { + if (svc.ui.status.phase === STATUS_TYPE.TERMINATING) { + a.event.stopPropagation(); + a.event.preventDefault(); + const config: SnackBarConfig = { data: { msg: $localize`Endpoint is being deleted, cannot show details.`, snackType: SnackType.Info, }, }; - this.snack.open(snackBarConfig); + this.snack.open(config); return; } break; } } - private deleteClicked(inferenceService: InferenceServiceIR) { - const dialogConfig = generateDeleteConfig(inferenceService); + private deleteClicked(svc: InferenceServiceIR) { + const config = generateDeleteConfig(svc); - const dialogRef = this.confirmDialog.open('Endpoint', dialogConfig); - const applyingSubscription = - dialogRef.componentInstance.applying$.subscribe(applying => { + const dialogRef = this.confirmDialog.open('Endpoint', config); + const applyingSub = dialogRef.componentInstance.applying$.subscribe( + applying => { if (!applying) { return; } - this.backend.deleteInferenceService(inferenceService).subscribe( - () => { + this.backend.deleteInferenceService(svc).subscribe( + res => { dialogRef.close(DIALOG_RESP.ACCEPT); }, - error => { - dialogConfig.error = error; + err => { + config.error = err; dialogRef.componentInstance.applying$.next(false); }, ); - }); + }, + ); - dialogRef.afterClosed().subscribe(dialogResponse => { - applyingSubscription.unsubscribe(); + dialogRef.afterClosed().subscribe(res => { + applyingSub.unsubscribe(); - if (dialogResponse !== DIALOG_RESP.ACCEPT) { + if (res !== DIALOG_RESP.ACCEPT) { return; } - inferenceService.ui.status.phase = STATUS_TYPE.TERMINATING; - inferenceService.ui.status.message = $localize`Preparing to delete Endpoint...`; + svc.ui.status.phase = STATUS_TYPE.TERMINATING; + svc.ui.status.message = $localize`Preparing to delete Endpoint...`; }); } // functions for converting the response InferenceServices to the // Internal Representation objects - private processIncomingData(inferenceServices: InferenceServiceK8s[]) { - const inferenceServicesCopy: InferenceServiceIR[] = JSON.parse( - JSON.stringify(inferenceServices), - ); + private processIncomingData(svcs: InferenceServiceK8s[]) { + const svcsCopy: InferenceServiceIR[] = JSON.parse(JSON.stringify(svcs)); - for (const inferenceService of inferenceServicesCopy) { - this.parseInferenceService(inferenceService); + for (const svc of svcsCopy) { + this.parseInferenceService(svc); } - return inferenceServicesCopy; + return svcsCopy; } - private parseInferenceService(inferenceService: InferenceServiceIR) { - inferenceService.ui = { actions: {} }; - inferenceService.ui.status = getK8sObjectUiStatus(inferenceService); - inferenceService.ui.actions.copy = - this.getCopyActionStatus(inferenceService); - inferenceService.ui.actions.delete = - this.getDeletionActionStatus(inferenceService); - - const predictorType = getPredictorType(inferenceService.spec.predictor); - const predictor = getPredictorExtensionSpec( - inferenceService.spec.predictor, - ); - inferenceService.ui.predictorType = predictorType; - inferenceService.ui.runtimeVersion = predictor.runtimeVersion; - inferenceService.ui.storageUri = predictor.storageUri; - inferenceService.ui.protocolVersion = predictor.protocolVersion || 'v1'; - inferenceService.ui.link = { - text: inferenceService.metadata.name, - url: `/details/${inferenceService.metadata.namespace}/${inferenceService.metadata.name}`, + private parseInferenceService(svc: InferenceServiceIR) { + svc.ui = { actions: {} }; + svc.ui.status = getK8sObjectUiStatus(svc); + svc.ui.actions.copy = this.getCopyActionStatus(svc); + svc.ui.actions.delete = this.getDeletionActionStatus(svc); + + const predictorType = getPredictorType(svc.spec.predictor); + const predictor = getPredictorExtensionSpec(svc.spec.predictor); + svc.ui.predictorType = predictorType; + svc.ui.runtimeVersion = predictor.runtimeVersion; + svc.ui.storageUri = predictor.storageUri; + svc.ui.protocolVersion = predictor.protocolVersion || 'v1'; + svc.ui.link = { + text: svc.metadata.name, + url: `/details/${svc.metadata.namespace}/${svc.metadata.name}`, }; } - private getCopyActionStatus(inferenceService: InferenceServiceIR) { - if (inferenceService.ui.status.phase !== STATUS_TYPE.READY) { + private getCopyActionStatus(svc: InferenceServiceIR) { + if (svc.ui.status.phase !== STATUS_TYPE.READY) { return STATUS_TYPE.UNAVAILABLE; } return STATUS_TYPE.READY; } - private getDeletionActionStatus(inferenceService: InferenceServiceIR) { - if (inferenceService.ui.status.phase !== STATUS_TYPE.TERMINATING) { + private getDeletionActionStatus(svc: InferenceServiceIR) { + if (svc.ui.status.phase !== STATUS_TYPE.TERMINATING) { return STATUS_TYPE.READY; } @@ -223,10 +211,7 @@ export class IndexComponent implements OnInit, OnDestroy { } // util functions - public trackInferenceService( - index: number, - inferenceService: InferenceServiceK8s, - ) { - return `${inferenceService.metadata.name}/${inferenceService.metadata.creationTimestamp}`; + public inferenceServiceTrackByFn(index: number, svc: InferenceServiceK8s) { + return `${svc.metadata.name}/${svc.metadata.creationTimestamp}`; } } diff --git a/frontend/src/app/pages/server-info/details/details.component.ts b/frontend/src/app/pages/server-info/details/details.component.ts index 25d3bee7..f550b0cd 100644 --- a/frontend/src/app/pages/server-info/details/details.component.ts +++ b/frontend/src/app/pages/server-info/details/details.component.ts @@ -61,8 +61,7 @@ export class DetailsComponent { continue; } const chip: ChipDescriptor = { - name: annotationKey, - value: annotationVal, + value: `${annotationKey}: ${annotationVal}`, color: 'primary', }; chips.push(chip); @@ -86,8 +85,7 @@ export class DetailsComponent { const labelVal = this.svc.metadata.labels[l]; const chip: ChipDescriptor = { - name: labelKey, - value: labelVal, + value: `${labelKey}: ${labelVal}`, color: 'primary', }; diff --git a/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts b/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts index 5fd6870b..f7ff771f 100644 --- a/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts +++ b/frontend/src/app/pages/server-info/details/explainer/explainer.component.ts @@ -13,7 +13,6 @@ export class ExplainerComponent { @Input() set explainerSpec(spec: ExplainerSpec) { this.explainerPrv = spec; - this.config = this.generateConfig(spec); } get explainerSpec(): ExplainerSpec { return this.explainerPrv; @@ -24,20 +23,14 @@ export class ExplainerComponent { private generateConfig(spec: ExplainerSpec): ChipDescriptor[] { const chips = []; - const config = spec?.alibi?.config; - if (!config) { - return chips; - } - - for (const key in config) { - if (!Object.prototype.hasOwnProperty.call(config, key)) { + for (const key in this.explainerSpec.alibi.config) { + if (this.explainerSpec.alibi.config.hasOwnProperty(key)) { continue; } - const val = config[key]; + const val = this.explainerSpec.alibi.config[key]; chips.push({ - name: key, - value: val, + value: `${key}: ${val}`, color: 'primary', }); } diff --git a/frontend/src/app/pages/server-info/details/shared/container/container.component.ts b/frontend/src/app/pages/server-info/details/shared/container/container.component.ts index 98146b5f..c1796f57 100644 --- a/frontend/src/app/pages/server-info/details/shared/container/container.component.ts +++ b/frontend/src/app/pages/server-info/details/shared/container/container.component.ts @@ -31,8 +31,7 @@ export class ContainerComponent { for (const envVar of c.env) { chips.push({ - name: envVar.name, - value: envVar.value, + value: `${envVar.name}: ${envVar.value}`, color: 'primary', tooltip: envVar.value, }); diff --git a/frontend/src/app/pages/server-info/overview/overview.component.ts b/frontend/src/app/pages/server-info/overview/overview.component.ts index 4796d1f5..e66a9718 100644 --- a/frontend/src/app/pages/server-info/overview/overview.component.ts +++ b/frontend/src/app/pages/server-info/overview/overview.component.ts @@ -82,7 +82,6 @@ export class OverviewComponent { for (const c of ['predictor', 'transformer', 'explainer']) { if (c in svc.spec) { chips.push({ - name: c, value: c, color: 'primary', }); diff --git a/frontend/src/types/kubeflow.d.ts b/frontend/src/types/kubeflow.d.ts deleted file mode 100644 index 7a2c7db6..00000000 --- a/frontend/src/types/kubeflow.d.ts +++ /dev/null @@ -1,260 +0,0 @@ -declare module 'kubeflow' { - export enum STATUS_TYPE { - UNINITIALIZED = 'Uninitialized', - TERMINATING = 'Terminating', - WARNING = 'Warning', - READY = 'Ready', - UNAVAILABLE = 'Unavailable', - } - - export enum SnackType { - Info = 'Info', - Warning = 'Warning', - Error = 'Error', - Success = 'Success', - } - - export enum DashboardState { - Disconnected = 'Disconnected', - } - - export enum DIALOG_RESP { - ACCEPT = 'ACCEPT', - REJECT = 'REJECT', - CANCEL = 'CANCEL', - } - - export interface ActionEvent { - action: string; - data: any; - event: any; - } - - export class ToolbarButton { - constructor(options?: any); - namespaceChanged(namespace: string | string[], resourceLabel: string): void; - } - - export interface SnackBarConfig { - data?: any; - duration?: number; - [key: string]: any; - } - - export class SnackBarService { - open(config: SnackBarConfig): void; - } - - export class ConfirmDialogService { - open(title: string, config?: DialogConfig): any; - } - - export class NamespaceService { - dashboardConnected$?: any; - getSelectedNamespace(): any; - getSelectedNamespace2(): any; - updateSelectedNamespace(namespace: string): any; - } - - export class PollerService { - exponential(request: any): any; - } - - export class BackendService { - constructor(http: any, snack: SnackBarService); - protected handleError(error: any, showSnack?: boolean): any; - protected getObjectsAllNamespaces( - getter: (namespace: string) => any, - namespaces: string[], - ): any; - } - - export class BackendResponse { - [key: string]: any; - } - - export class KubeflowModule { - static ɵmod: any; - static ɵinj: any; - } - - export class EditorModule { - static ɵmod: any; - static ɵinj: any; - } - - export class ConditionsTableModule { - static ɵmod: any; - static ɵinj: any; - } - - export class DetailsListModule { - static ɵmod: any; - static ɵinj: any; - } - - export class HeadingSubheadingRowModule { - static ɵmod: any; - static ɵinj: any; - } - - export class ExponentialBackoff { - constructor(config?: any); - start(): any; - } - - export const LinkType: { - Internal: string; - External?: string; - [key: string]: string | undefined; - }; - - export class PropertyValue { - constructor(options?: any); - } - - export class StatusValue { - constructor(options?: any); - } - - export class ActionListValue { - constructor(actions?: T[]); - } - - export class ActionIconValue { - constructor(options?: any); - } - - export class DateTimeValue { - constructor(options?: any); - } - - export class DateTimeModule {} - export class PanelModule {} - export class LoadingSpinnerModule {} - export class LogsViewerModule {} - - export interface DialogConfig { - title?: string; - message?: string; - accept?: string; - applying?: string; - confirmColor?: string; - cancel?: string; - [key: string]: any; - } - - export interface TableConfig { - dynamicNamespaceColumn?: boolean; - columns: any[]; - [key: string]: any; - } - - export class ComponentValue { - constructor(options?: any); - } - - export class LinkValue { - constructor(options?: any); - } - - export class TableColumnComponent {} - - export interface ListEntry { - label: string; - value?: any; - [key: string]: any; - } - - export interface ChipDescriptor { - name: string; - value?: any; - [key: string]: any; - } - - export interface Condition { - type: string; - status: string; - reason?: string; - message?: string; - } - - export interface Status { - phase: STATUS_TYPE; - state: string; - message: string; - } - - export interface K8sObject { - kind?: string; - metadata?: { - name?: string; - namespace?: string; - deletionTimestamp?: string | Date; - [key: string]: any; - }; - status?: { - conditions?: Condition[] | null; - [key: string]: any; - }; - spec?: any; - [key: string]: any; - } - - export enum PredictorType { - Tensorflow = 'tensorflow', - Triton = 'triton', - Sklearn = 'sklearn', - Onnx = 'onnx', - Pytorch = 'pytorch', - Xgboost = 'xgboost', - Pmml = 'pmml', - Lightgbm = 'lightgbm', - MLFlow = 'mlflow', - Custom = 'custom', - } - - export interface PredictorExtensionSpec { - storageUri?: string; - runtimeVersion?: string; - protocolVersion?: string; - image?: string; - name?: string; - env?: Array<{ - name: string; - value: string; - }>; - [key: string]: any; - } - - export interface ModelSpec extends PredictorExtensionSpec { - modelFormat: { - name: string; - version?: string; - }; - runtime?: string; - } - - export interface PredictorSpec { - sklearn?: PredictorExtensionSpec; - xgboost?: PredictorExtensionSpec; - tensorflow?: PredictorExtensionSpec; - pytorch?: PredictorExtensionSpec; - triton?: PredictorExtensionSpec; - onnx?: PredictorExtensionSpec; - pmml?: PredictorExtensionSpec; - lightgbm?: PredictorExtensionSpec; - mlflow?: PredictorExtensionSpec; - model?: ModelSpec; - containers?: Array<{ - name?: string; - image?: string; - env?: Array<{ - name: string; - value: string; - }>; - [key: string]: any; - }>; - [key: string]: any; - } -} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index 993782ed..df4d05e1 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -2,29 +2,17 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [], - "baseUrl": "./src", - "paths": { - "kubeflow": ["types/kubeflow.d.ts"] - }, - "typeRoots": [ - "src/types", - "node_modules/@types" - ], - "skipLibCheck": true + "types": [] }, "files": [ "src/main.ts", "src/polyfills.ts" ], "include": [ - "src/**/*.ts", "src/**/*.d.ts" ], "exclude": [ "src/test.ts", - "src/**/*.spec.ts", - "src/**/*.jest.spec.ts", - "e2e/**" + "src/**/*.spec.ts" ] }