diff --git a/.github/workflows/_e2e-test.yml b/.github/workflows/_e2e-test.yml index 8009f58..664ed33 100644 --- a/.github/workflows/_e2e-test.yml +++ b/.github/workflows/_e2e-test.yml @@ -53,7 +53,7 @@ jobs: fi sleep 5 sudo apt install ansible -y - ansible-playbook genai-studio.yml -e "container_registry=${OPEA_IMAGE_REPO}opea" -e "container_tag=${{ inputs.tag }}" + ansible-playbook genai-studio.yml -e "container_registry=${OPEA_IMAGE_REPO}opea" -e "container_tag=${{ inputs.tag }}" -e "mysql_host=$(hostname -I | awk '{print $1}')" sleep 5 kubectl wait --for=condition=ready pod --all --namespace=studio --timeout=300s --field-selector=status.phase!=Succeeded kubectl wait --for=condition=ready pod --all --namespace=monitoring --timeout=300s --field-selector=status.phase!=Succeeded @@ -74,7 +74,7 @@ jobs: - name: Update Playwright Config run: | NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') - sed -i "s|baseURL:.*|baseURL: \"http://$NODE_IP:30007\",|" playwright.config.js + sed -i "s|baseURL:.*|baseURL: \"https://$NODE_IP:30007\",|" playwright.config.js working-directory: ${{ github.workspace }}/tests/playwright - name: Run Playwright Tests diff --git a/app-backend/Dockerfile b/app-backend/Dockerfile index 8f9c4e5..3a47f14 100644 --- a/app-backend/Dockerfile +++ b/app-backend/Dockerfile @@ -12,14 +12,12 @@ RUN useradd -m -s /bin/bash user && \ chown -R user /home/user/ WORKDIR /home/user/ -# temporary pointing to v1.1 GenAIComps for Gateway dependency -RUN git clone https://github.com/opea-project/GenAIComps.git -b v1.1rc +RUN git clone --depth 1 https://github.com/opea-project/GenAIComps.git WORKDIR /home/user/GenAIComps RUN pip install --no-cache-dir --upgrade pip==24.3.1 setuptools==75.3.0 && \ pip install --no-cache-dir -r /home/user/GenAIComps/requirements.txt -COPY ./app_gateway.py /home/user/app_gateway.py COPY ./templates/microservices/* /home/user/templates/microservices/ COPY ./megaservice.py /home/user/megaservice.py COPY config/* /home/user/config/ @@ -32,4 +30,4 @@ WORKDIR /home/user RUN echo 'ulimit -S -n 999999' >> ~/.bashrc -ENTRYPOINT ["python", "megaservice.py"] \ No newline at end of file +ENTRYPOINT ["python", "megaservice.py"] diff --git a/app-backend/app_gateway.py b/app-backend/app_gateway.py deleted file mode 100644 index 9323344..0000000 --- a/app-backend/app_gateway.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -import os -import json -import logging - -# library import -from fastapi import Request -from fastapi.responses import StreamingResponse - -# comps import -from comps import Gateway, MicroService, ServiceOrchestrator, ServiceType -from comps.cores.proto.api_protocol import ( - AudioChatCompletionRequest, - ChatCompletionRequest, - ChatCompletionResponse, - ChatCompletionResponseChoice, - ChatMessage, - EmbeddingRequest, - UsageInfo -) -from comps.cores.proto.docarray import LLMParams, LLMParamsDoc, RerankedDoc, RerankerParms, RetrieverParms, TextDoc - -category_params_map = { - 'LLM': LLMParams, - 'Reranking': RerankerParms, - 'Retreiver': RetrieverParms, -} - -class AppGateway(Gateway): - def __init__(self, megaservice, host='0.0.0.0', port=8888): - try: - with open('config/project-info.json', 'r') as f: - self.project_info = json.load(f) - except: - logging.error('Failed to load project-info.json') - super().__init__( - megaservice, host, port, '/v1/app-backend', ChatCompletionRequest, ChatCompletionResponse - ) - - async def handle_request(self, request: Request): - data = await request.json() - print('\n'*5, '====== handle_request ======\n', data) - if 'chat_completion_ids' in self.project_info: - prompt = self._handle_message(data['messages']) - params = {} - llm_parameters = None - for id, node in self.project_info['nodes'].items(): - if node['category'] in category_params_map: - param_class = category_params_map[node['category']]() - param_keys = [key for key in dir(param_class) if not key.startswith('__') and not callable(getattr(param_class, key))] - print('param_keys', param_keys) - params_dict = {} - for key in param_keys: - if key in data: - params_dict[key] = data[key] - # hadle special case for stream and streaming - if key in ['stream', 'streaming']: - params_dict[key] = data.get('stream', True) and data.get('streaming', True) - elif key in node['inference_params']: - params_dict[key] = node['inference_params'][key] - params[id] = params_dict - if node['category'] == 'LLM': - params[id]['max_new_tokens'] = params[id]['max_tokens'] - llm_parameters = LLMParams(**params[id]) - result_dict, runtime_graph = await self.megaservice.schedule( - initial_inputs={'query':prompt, 'text': prompt}, - llm_parameters=llm_parameters, - params=params, - ) - print('runtime_graph', runtime_graph.graph) - for node, response in result_dict.items(): - if isinstance(response, StreamingResponse): - return response - last_node = runtime_graph.all_leaves()[-1] - print('result_dict:', result_dict) - print('last_node:',last_node) - response = result_dict[last_node]['text'] - choices = [] - usage = UsageInfo() - choices.append( - ChatCompletionResponseChoice( - index=0, - message=ChatMessage(role='assistant', content=response), - finish_reason='stop', - ) - ) - return ChatCompletionResponse(model='custom_app', choices=choices, usage=usage) \ No newline at end of file diff --git a/app-backend/megaservice.py b/app-backend/megaservice.py index 0b4bd44..f27a79f 100644 --- a/app-backend/megaservice.py +++ b/app-backend/megaservice.py @@ -4,23 +4,70 @@ import os import json import importlib +import re # library import +from fastapi import Request +from fastapi.responses import StreamingResponse # comps import -from comps import MicroService, ServiceOrchestrator, ServiceType -from app_gateway import AppGateway +from comps import MicroService, ServiceOrchestrator, ServiceRoleType, ServiceType +from comps.cores.mega.utils import handle_message +from comps.cores.proto.api_protocol import ( + ChatCompletionRequest, + ChatCompletionResponse, + ChatCompletionResponseChoice, + ChatMessage, + UsageInfo, +) +from comps.cores.proto.docarray import LLMParams, RerankerParms, RetrieverParms +from langchain_core.prompts import PromptTemplate -HOST_IP = os.getenv("HOST_IP", "0,0,0,0") +category_params_map = { + 'LLM': LLMParams, + 'Reranking': RerankerParms, + 'Retreiver': RetrieverParms, +} + +HOST_IP = os.getenv("HOST_IP", "0.0.0.0") USE_NODE_ID_AS_IP = os.getenv("USE_NODE_ID_AS_IP","").lower() == 'true' + +class ChatTemplate: + @staticmethod + def generate_rag_prompt(question, documents): + context_str = "\n".join(documents) + if context_str and len(re.findall("[\u4E00-\u9FFF]", context_str)) / len(context_str) >= 0.3: + # chinese context + template = """ +### 你将扮演一个乐于助人、尊重他人并诚实的助手,你的目标是帮助用户解答问题。有效地利用来自本地知识库的搜索结果。确保你的回答中只包含相关信息。如果你不确定问题的答案,请避免分享不准确的信息。 +### 搜索结果:{context} +### 问题:{question} +### 回答: +""" + else: + template = """ +### You are a helpful, respectful and honest assistant to help the user with questions. \ +Please refer to the search results obtained from the local knowledge base. \ +But be careful to not incorporate the information that you think is not relevant to the question. \ +If you don't know the answer to a question, please don't share false information. \n +### Search results: {context} \n +### Question: {question} \n +### Answer: +""" + return template.format(context=context_str, question=question) + class AppService: def __init__(self, host="0.0.0.0", port=8000): self.host = host self.port = port self.megaservice = ServiceOrchestrator() - with open('config/project-info.json', 'r') as f: - self.project_info = json.load(f) + self.megaservice.align_inputs = self.align_inputs + self.megaservice.align_outputs = self.align_outputs + self.megaservice.align_generator = self.align_generator + self.endpoint = "/v1/app-backend" + with open('config/workflow-info.json', 'r') as f: + self.workflow_info = json.load(f) def import_all_microservices_from_template(self): template_dir = os.path.join(os.path.dirname(__file__), 'templates', 'microservices') @@ -35,13 +82,13 @@ def import_all_microservices_from_template(self): def add_remote_service(self): print("add_remote_service") templates = self.import_all_microservices_from_template() - if 'chat_input_ids' not in self.project_info: - raise Exception('chat_input_ids not found in project_info') - nodes = self.project_info['chat_input_ids'] - services = {} + if 'chat_input_ids' not in self.workflow_info: + raise Exception('chat_input_ids not found in workflow_info') + nodes = self.workflow_info['chat_input_ids'] + self.services = {} while nodes: node_id = nodes.pop(0) - node = self.project_info['nodes'][node_id] + node = self.workflow_info['nodes'][node_id] print('node', node) if node['inMegaservice']: print('adding Node', node_id) @@ -50,33 +97,259 @@ def add_remote_service(self): microservice = templates[microservice_name].get_service(host_ip=service_node_ip, node_id_as_ip=USE_NODE_ID_AS_IP) microservice.name = node_id self.megaservice.add(microservice) - services[node_id] = microservice + + self.services[node_id] = microservice for prev_node in node['connected_from']: - if prev_node in services: - self.megaservice.flow_to(services[prev_node], microservice) + if prev_node in self.services: + self.megaservice.flow_to(self.services[prev_node], microservice) for next_node in node['connected_to']: nodes.append(next_node) - self.megaservice.align_inputs = self.align_inputs - self.gateway = AppGateway(megaservice=self.megaservice, host="0.0.0.0", port=self.port) + def align_inputs(self, inputs, *args, **kwargs): """Override this method in megaservice definition.""" print('\n'*2,'align_inputs') node_id = args[0] - # print('node_id', node_id) + llm_parameters_dict = args[2] + print('node_id', node_id) params = kwargs.get('params', {}) + print('original_inputs', inputs) + print('-'*20) # print('params', params) if node_id in params: try: new_input = params[node_id] inputs.update(new_input) - print('inputs', inputs) except Exception as e: print('unable to parse input', e) + if self.services[node_id].service_type == ServiceType.EMBEDDING: + inputs["input"] = inputs["text"] + inputs["inputs"] = inputs["text"] + del inputs["text"] + elif self.services[node_id].service_type == ServiceType.RETRIEVER: + # prepare the retriever params + retriever_parameters = kwargs.get("retriever_parameters", None) + if retriever_parameters: + inputs.update(retriever_parameters.dict()) + elif self.services[node_id].service_type == ServiceType.LLM: + # convert TGI/vLLM to unified OpenAI /v1/chat/completions format + next_inputs = {} + next_inputs["model"] = inputs.get("model") or "Intel/neural-chat-7b-v3-3" + if inputs.get("inputs"): + next_inputs["messages"] = [{"role": "user", "content": inputs["inputs"]}] + elif inputs.get("query") and inputs.get("documents"): + # for rag case + next_inputs["query"] = inputs["query"] + next_inputs["documents"] = inputs.get("documents",[]) + else: + # simple llm case + next_inputs["messages"] = [{"role": "user", "content": next(value for key in ["query", "text", "input", "inputs"] if (value := inputs.get(key)))}] + next_inputs["max_tokens"] = llm_parameters_dict["max_tokens"] + next_inputs["top_p"] = llm_parameters_dict["top_p"] + next_inputs["stream"] = inputs["stream"] + next_inputs["frequency_penalty"] = inputs["frequency_penalty"] + # next_inputs["presence_penalty"] = inputs["presence_penalty"] + # next_inputs["repetition_penalty"] = inputs["repetition_penalty"] + next_inputs["temperature"] = inputs["temperature"] + inputs = next_inputs + print('final_inputs', inputs) + print('-'*20) return inputs + + def align_outputs(self, data, cur_node, inputs, runtime_graph, llm_parameters_dict, **kwargs): + print('\n'*2,'align_outputs') + print('cur_node', cur_node) + print('data', data) + print('-'*20) + print('inputs', inputs) + print('-'*20) + next_data = {} + if self.services[cur_node].service_type == ServiceType.EMBEDDING: + # assert isinstance(data, dict) + next_data = {"text": inputs["inputs"], "embedding": data['data'][0]['embedding']} + elif self.services[cur_node].service_type == ServiceType.RETRIEVER: + + docs = [doc["text"] for doc in data["retrieved_docs"]] + + with_rerank = runtime_graph.downstream(cur_node)[0].startswith("opea_service@rerank") + if with_rerank and docs: + print("Rerank with docs") + # forward to rerank + # prepare inputs for rerank + next_data["initial_query"] = data["initial_query"] + next_data["texts"] = [doc["text"] for doc in data["retrieved_docs"]] + next_data["retrieved_docs"] = data["retrieved_docs"] + else: + print("No rerank") + # forward to llm + if not docs and with_rerank: + # delete the rerank from retriever -> rerank -> llm + for ds in reversed(runtime_graph.downstream(cur_node)): + for nds in runtime_graph.downstream(ds): + runtime_graph.add_edge(cur_node, nds) + runtime_graph.delete_node_if_exists(ds) + + # handle template + # if user provides template, then format the prompt with it + # otherwise, use the default template + prompt = data["initial_query"] + chat_template = llm_parameters_dict["chat_template"] + if chat_template: + prompt_template = PromptTemplate.from_template(chat_template) + input_variables = prompt_template.input_variables + if sorted(input_variables) == ["context", "question"]: + prompt = prompt_template.format(question=data["initial_query"], context="\n".join(docs)) + elif input_variables == ["question"]: + prompt = prompt_template.format(question=data["initial_query"]) + else: + print(f"{prompt_template} not used, we only support 2 input variables ['question', 'context']") + prompt = ChatTemplate.generate_rag_prompt(data["initial_query"], docs) + else: + prompt = ChatTemplate.generate_rag_prompt(data["initial_query"], docs) + + next_data["inputs"] = prompt + + elif self.services[cur_node].service_type == ServiceType.RERANK: + # rerank the inputs with the scores + # reranker_parameters = kwargs.get("reranker_parameters", None) + # top_n = reranker_parameters.top_n if reranker_parameters else 1 + # docs = inputs["texts"] + # reranked_docs = [] + # for best_response in data['documents'][:top_n]: + # reranked_docs.append(docs[best_response["index"]]) + + # # handle template + # # if user provides template, then format the prompt with it + # # otherwise, use the default template + # prompt = inputs["query"] + # chat_template = llm_parameters_dict["chat_template"] + # if chat_template: + # prompt_template = PromptTemplate.from_template(chat_template) + # input_variables = prompt_template.input_variables + # if sorted(input_variables) == ["context", "question"]: + # prompt = prompt_template.format(question=prompt, context="\n".join(reranked_docs)) + # elif input_variables == ["question"]: + # prompt = prompt_template.format(question=prompt) + # else: + # print(f"{prompt_template} not used, we only support 2 input variables ['question', 'context']") + # prompt = ChatTemplate.generate_rag_prompt(prompt, reranked_docs) + # else: + # prompt = ChatTemplate.generate_rag_prompt(prompt, reranked_docs) + + # next_data["inputs"] = prompt + next_data = data + + elif self.services[cur_node].service_type == ServiceType.LLM and not llm_parameters_dict["stream"]: + next_data["text"] = data["choices"][0]["message"]["content"] + else: + next_data = data + + print('next_data', next_data) + print('-'*20) + return next_data + + def align_generator(self, gen, **kwargs): + print('\n'*2,'align_generator') + + buffer = "" + for line in gen: + line = line.decode("utf-8") + print('line', line) + start = line.find("{") + end = line.rfind("}") + 1 + + json_str = line[start:end] + try: + json_data = json.loads(json_str) + if json_data["choices"][0]["finish_reason"] != "eos_token": + choice = json_data["choices"][0] + if "delta" in choice and "content" in choice["delta"]: + buffer += choice["delta"]["content"] + elif "text" in choice: + buffer += choice["text"] + buffer = buffer.replace("\\n", "\n") + print("buffer", buffer) + + words = buffer.split() + if len(words) > 1: + output_word = words[0] + ' ' + yield f"data: {repr(output_word.encode('utf-8'))}\n\n" + buffer = " ".join(words[1:]) + else: + buffer = words[0] if words else "" + except Exception as e: + yield f"data: {repr(json_str.encode('utf-8'))}\n\n" + if buffer: + yield f"data: {repr(buffer.encode('utf-8'))}\n\n" + yield "data: [DONE]\n\n" + + + async def handle_request(self, request: Request): + data = await request.json() + print('\n'*5, '====== handle_request ======\n', data) + if 'chat_completion_ids' in self.workflow_info: + prompt = handle_message(data['messages']) + params = {} + llm_parameters = None + for id, node in self.workflow_info['nodes'].items(): + if node['category'] in category_params_map: + param_class = category_params_map[node['category']]() + param_keys = [key for key in dir(param_class) if not key.startswith('__') and not callable(getattr(param_class, key))] + print('param_keys', param_keys) + params_dict = {} + for key in param_keys: + if key in data: + params_dict[key] = data[key] + # hadle special case for stream and streaming + if key in ['stream', 'streaming']: + params_dict[key] = data.get('stream', True) and data.get('streaming', True) + elif key in node['inference_params']: + params_dict[key] = node['inference_params'][key] + params[id] = params_dict + if node['category'] == 'LLM': + params[id]['max_new_tokens'] = params[id]['max_tokens'] + llm_parameters = LLMParams(**params[id]) + result_dict, runtime_graph = await self.megaservice.schedule( + initial_inputs={'query':prompt, 'text': prompt}, + llm_parameters=llm_parameters, + params=params, + ) + print('runtime_graph', runtime_graph.graph) + for node, response in result_dict.items(): + if isinstance(response, StreamingResponse): + return response + last_node = runtime_graph.all_leaves()[-1] + print('result_dict:', result_dict) + print('last_node:',last_node) + response = result_dict[last_node]['text'] + choices = [] + usage = UsageInfo() + choices.append( + ChatCompletionResponseChoice( + index=0, + message=ChatMessage(role='assistant', content=response), + finish_reason='stop', + ) + ) + return ChatCompletionResponse(model='custom_app', choices=choices, usage=usage) + + def start(self): + + self.service = MicroService( + self.__class__.__name__, + service_role=ServiceRoleType.MEGASERVICE, + host=self.host, + port=self.port, + endpoint=self.endpoint, + input_datatype=ChatCompletionRequest, + output_datatype=ChatCompletionResponse, + ) + self.service.add_route(self.endpoint, self.handle_request, methods=["POST"]) + self.service.start() if __name__ == "__main__": - megaservice_host_ip = None if USE_NODE_ID_AS_IP else HOST_IP - chatqna = AppService(host=HOST_IP, port=8888) + print('pre initialize appService') + app = AppService(host="0.0.0.0", port=8888) print('after initialize appService') - chatqna.add_remote_service() \ No newline at end of file + app.add_remote_service() + app.start() \ No newline at end of file diff --git a/app-backend/templates/microservices/embedding_tei_langchain.py b/app-backend/templates/microservices/embedding_tei_langchain.py index 54b290a..1bea51d 100644 --- a/app-backend/templates/microservices/embedding_tei_langchain.py +++ b/app-backend/templates/microservices/embedding_tei_langchain.py @@ -4,8 +4,7 @@ def get_service(host_ip = "0.0.0.0", **kwargs): return MicroService( name="embedding", host=host_ip, - # port=None if kwargs.get("node_id_as_ip") else 6000, - port=6000, + port=6000 if kwargs.get("node_id_as_ip") else 6009, endpoint="/v1/embeddings", use_remote_service=True, service_type=ServiceType.EMBEDDING diff --git a/app-backend/templates/microservices/llm_tgi.py b/app-backend/templates/microservices/llm_tgi.py index 3f5c942..9977d0b 100644 --- a/app-backend/templates/microservices/llm_tgi.py +++ b/app-backend/templates/microservices/llm_tgi.py @@ -4,8 +4,7 @@ def get_service(host_ip = "0.0.0.0", **kwargs): return MicroService( name="llm", host=host_ip, - # port=None if kwargs.get("node_id_as_ip") else 9000, - port=9000, + port=9000 if kwargs.get("node_id_as_ip") else 9009, endpoint="/v1/chat/completions", use_remote_service=True, service_type=ServiceType.LLM diff --git a/app-backend/templates/microservices/reranking_tei.py b/app-backend/templates/microservices/reranking_tei.py index 2d9cc7f..c5a271f 100644 --- a/app-backend/templates/microservices/reranking_tei.py +++ b/app-backend/templates/microservices/reranking_tei.py @@ -4,8 +4,7 @@ def get_service(host_ip = "0.0.0.0", **kwargs): return MicroService( name="rerank", host=host_ip, - # port=None if kwargs.get("node_id_as_ip") else 8000, - port=8000, + port=8000 if kwargs.get("node_id_as_ip") else 8009, endpoint="/v1/reranking", use_remote_service=True, service_type=ServiceType.RERANK diff --git a/app-backend/templates/microservices/retriever_redis.py b/app-backend/templates/microservices/retriever_redis.py index 23bf2d8..0950ab5 100644 --- a/app-backend/templates/microservices/retriever_redis.py +++ b/app-backend/templates/microservices/retriever_redis.py @@ -4,8 +4,7 @@ def get_service(host_ip = "0.0.0.0", **kwargs): return MicroService( name="retriever", host=host_ip, - # port=None if kwargs.get("node_id_as_ip") else 7000, - port=7000, + port=7000 if kwargs.get("node_id_as_ip") else 7009, endpoint="/v1/retrieval", use_remote_service=True, service_type=ServiceType.RETRIEVER diff --git a/app-frontend/react/.env.production b/app-frontend/react/.env.production index 7f151e6..16b02d1 100644 --- a/app-frontend/react/.env.production +++ b/app-frontend/react/.env.production @@ -1,3 +1 @@ -VITE_CHAT_SERVICE_URL=APP_BACKEND_SERVICE_URL -VITE_DATA_PREP_SERVICE_URL=APP_DATA_PREP_SERVICE_URL VITE_APP_UUID=APP_UUID \ No newline at end of file diff --git a/app-frontend/react/package.json b/app-frontend/react/package.json index c56e0a5..99941f1 100644 --- a/app-frontend/react/package.json +++ b/app-frontend/react/package.json @@ -17,7 +17,7 @@ "@mantine/notifications": "^7.13.4", "@microsoft/fetch-event-source": "^2.0.1", "@reduxjs/toolkit": "^2.2.5", - "@tabler/icons-react": "^3.5.0", + "@tabler/icons-react": "3.7.0", "axios": "^1.7.2", "luxon": "^3.4.4", "react": "^18.2.0", diff --git a/app-frontend/react/src/config.ts b/app-frontend/react/src/config.ts index 2763b33..281cab7 100644 --- a/app-frontend/react/src/config.ts +++ b/app-frontend/react/src/config.ts @@ -3,12 +3,9 @@ // console.log("Environment variables:", import.meta.env); -export const DATA_PREP_URL = import.meta.env.VITE_DATA_PREP_SERVICE_URL; -export const CHAT_QNA_URL = import.meta.env.VITE_CHAT_SERVICE_URL; export const APP_UUID = import.meta.env.VITE_APP_UUID; - -console.log("data prep", DATA_PREP_URL); -console.log("chat qna", CHAT_QNA_URL); +export const CHAT_QNA_URL = "VITE_APP_BACKEND_SERVICE_URL"; +export const DATA_PREP_URL = "VITE_APP_DATA_PREP_SERVICE_URL"; type UiFeatures = { dataprep: boolean; @@ -16,6 +13,9 @@ type UiFeatures = { }; export const UI_FEATURES: UiFeatures = { - dataprep: !!DATA_PREP_URL, - chat: !!CHAT_QNA_URL + chat: CHAT_QNA_URL.startsWith('VITE_') ? false : true, + dataprep: DATA_PREP_URL.startsWith('VITE_') ? false : true }; + +console.log("chat qna", CHAT_QNA_URL, UI_FEATURES.chat); +console.log("data prep", DATA_PREP_URL, UI_FEATURES.dataprep); \ No newline at end of file diff --git a/setup-scripts/setup-genai-studio/genai-studio.yml b/setup-scripts/setup-genai-studio/genai-studio.yml index e4749b5..a15582e 100644 --- a/setup-scripts/setup-genai-studio/genai-studio.yml +++ b/setup-scripts/setup-genai-studio/genai-studio.yml @@ -1,3 +1,6 @@ +- name: Setup mysqldb + import_playbook: playbooks/setup-mysqldb.yml + - name: Deploy monitoring import_playbook: playbooks/deploy-monitoring.yml diff --git a/setup-scripts/setup-genai-studio/manifests/studio-manifest-aws-ecr.yaml b/setup-scripts/setup-genai-studio/manifests/studio-manifest-aws-ecr.yaml index d3bc2f5..988d03b 100644 --- a/setup-scripts/setup-genai-studio/manifests/studio-manifest-aws-ecr.yaml +++ b/setup-scripts/setup-genai-studio/manifests/studio-manifest-aws-ecr.yaml @@ -20,8 +20,13 @@ data: # SPDX-License-Identifier: Apache-2.0 server { - listen 80; - listen [::]:80; + # listen 80; + # listen [::]:80; + listen 443 ssl; + listen [::]:443 ssl; + + ssl_certificate /etc/ssl/tls.crt; + ssl_certificate_key /etc/ssl/tls.key; proxy_connect_timeout 600; proxy_send_timeout 600; @@ -34,7 +39,7 @@ data: resolver_timeout 5s; location /home { - root /usr/share/nginx/html; # Use root to serve files from a directory + root /usr/share/nginx/html; index index.html; } @@ -71,6 +76,15 @@ data: proxy_set_header X-Forwarded-Proto $scheme; } + # Location block for keycloak + location /auth { + proxy_pass https://${KEYCLOAK_DNS}/auth/; + proxy_set_header Host $host:30007; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # Location block for app-backend location /v1/app-backend { # Initialize the variable for namespace @@ -170,10 +184,11 @@ spec: selector: app: studio-nginx ports: - - protocol: TCP - port: 80 - targetPort: 80 - nodePort: 30007 + - name: https + protocol: TCP + port: 443 + targetPort: 443 + nodePort: 30007 type: NodePort --- apiVersion: apps/v1 @@ -201,7 +216,7 @@ spec: envsubst "$(env | grep _DNS= | awk -F= '{print "${"$1"}"}' | tr '\n' ' ')" < /tmp/default.conf > /etc/nginx/conf.d/default.conf envFrom: - configMapRef: - name: internal-dns-config + name: studio-config volumeMounts: - name: tmp-volume mountPath: /tmp @@ -214,6 +229,8 @@ spec: volumeMounts: - name: nginx-conf-volume mountPath: /etc/nginx/conf.d + - name: tls + mountPath: /etc/ssl securityContext: {} volumes: - name: tmp-volume @@ -222,6 +239,9 @@ spec: name: studio-nginx-config - name: nginx-conf-volume emptyDir: {} + - name: tls + secret: + secretName: tls-secret --- apiVersion: v1 kind: Service @@ -272,6 +292,9 @@ rules: - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "create", "list", "watch"] +- apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "create", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -322,7 +345,7 @@ spec: value: ${NO_PROXY} envFrom: - configMapRef: - name: internal-dns-config + name: studio-config ports: - containerPort: 5000 resources: @@ -394,4 +417,122 @@ spec: - name: ecr-registry-secret volumes: - name: tmp - emptyDir: {} \ No newline at end of file + emptyDir: {} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + namespace: studio + labels: + app: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + initContainers: + - name: keycloak-assets + image: curlimages/curl:latest + command: ["/bin/sh", "-c"] + args: + - | + OWNER=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/([^/]+)/([^/]+)/tree/([^/]+)/.*|\1|') + REPO=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/([^/]+)/([^/]+)/tree/([^/]+)/.*|\2|') + BRANCH=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/[^/]+/[^/]+/tree/([^/]+)/.*|\1|') + KC_ASSETS_DIR=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/[^/]+/[^/]+/tree/[^/]+/(.*?)/?$|\1|') + if [[ "${KC_ASSETS_DIR: -1}" == "/" ]]; then KC_ASSETS_DIR="${KC_ASSETS_DIR%/}"; fi + DOWNLOAD_URL="https://codeload.github.com/${OWNER}/${REPO}/tar.gz/${BRANCH}" + curl "${DOWNLOAD_URL}" | tar -xz --strip-components=4 -C /opt/keycloak/themes "${REPO}-${BRANCH}/${KC_ASSETS_DIR}/themes" + curl "${DOWNLOAD_URL}" | tar -xz --strip-components=4 -C /opt/keycloak/data "${REPO}-${BRANCH}/${KC_ASSETS_DIR}/data" + envFrom: + - configMapRef: + name: studio-config + volumeMounts: + - name: keycloak-themes-volume + mountPath: /opt/keycloak/themes + - name: keycloak-dataimport-volume + mountPath: /opt/keycloak/data/import + securityContext: + runAsUser: 0 + runAsGroup: 0 + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:latest + volumeMounts: + - name: tls + mountPath: /etc/ssl + readOnly: true + - name: keycloak-themes-volume + mountPath: /opt/keycloak/themes + - name: keycloak-dataimport-volume + mountPath: /opt/keycloak/data/import + args: + - start + - --import-realm + ports: + - containerPort: 8080 + - containerPort: 8443 + env: + - name: KC_BOOTSTRAP_ADMIN_USERNAME + value: "admin" + - name: KC_BOOTSTRAP_ADMIN_PASSWORD + value: "admin" + - name: KC_PROXY_HEADERS + value: "forwarded" + - name: KC_HTTP_RELATIVE_PATH + value: "/auth" + - name: KC_PROXY + value: edge + - name: KC_HTTPS_CERTIFICATE_FILE + value: /etc/ssl/tls.crt + - name: KC_HTTPS_CERTIFICATE_KEY_FILE + value: /etc/ssl/tls.key + - name: KC_HOSTNAME_STRICT + value: "false" + - name: KC_HOSTNAME_STRICT_HTTPS + value: "true" + readinessProbe: + failureThreshold: 3 + httpGet: + path: auth/realms/master + port: 8443 + scheme: HTTPS + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1" + volumes: + - name: tls + secret: + secretName: tls-secret + - name: keycloak-themes-volume + emptyDir: {} + - name: keycloak-dataimport-volume + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: keycloak + namespace: studio +spec: + type: ClusterIP + ports: + - name: https + protocol: TCP + port: 8443 + targetPort: 8443 + selector: + app: keycloak \ No newline at end of file diff --git a/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml b/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml index 5885f11..ea9945e 100644 --- a/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml +++ b/setup-scripts/setup-genai-studio/manifests/studio-manifest.yaml @@ -10,8 +10,13 @@ data: # SPDX-License-Identifier: Apache-2.0 server { - listen 80; - listen [::]:80; + # listen 80; + # listen [::]:80; + listen 443 ssl; + listen [::]:443 ssl; + + ssl_certificate /etc/ssl/app-tls.crt; + ssl_certificate_key /etc/ssl/app-tls.key; proxy_connect_timeout 600; proxy_send_timeout 600; @@ -24,7 +29,7 @@ data: resolver_timeout 5s; location /home { - root /usr/share/nginx/html; # Use root to serve files from a directory + root /usr/share/nginx/html; index index.html; } @@ -61,6 +66,15 @@ data: proxy_set_header X-Forwarded-Proto $scheme; } + # Location block for keycloak + location /auth { + proxy_pass https://${KEYCLOAK_DNS}/auth/; + proxy_set_header Host $host:30007; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + # Location block for app-backend location /v1/app-backend { # Initialize the variable for namespace @@ -160,10 +174,11 @@ spec: selector: app: studio-nginx ports: - - protocol: TCP - port: 80 - targetPort: 80 - nodePort: 30007 + - name: https + protocol: TCP + port: 443 + targetPort: 443 + nodePort: 30007 type: NodePort --- apiVersion: apps/v1 @@ -191,7 +206,7 @@ spec: envsubst "$(env | grep _DNS= | awk -F= '{print "${"$1"}"}' | tr '\n' ' ')" < /tmp/default.conf > /etc/nginx/conf.d/default.conf envFrom: - configMapRef: - name: internal-dns-config + name: studio-config volumeMounts: - name: tmp-volume mountPath: /tmp @@ -204,6 +219,8 @@ spec: volumeMounts: - name: nginx-conf-volume mountPath: /etc/nginx/conf.d + - name: app-tls + mountPath: /etc/ssl securityContext: {} volumes: - name: tmp-volume @@ -212,6 +229,9 @@ spec: name: studio-nginx-config - name: nginx-conf-volume emptyDir: {} + - name: app-tls + secret: + secretName: app-tls --- apiVersion: v1 kind: Service @@ -262,6 +282,9 @@ rules: - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "create", "list", "watch"] +- apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "create", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -312,7 +335,7 @@ spec: value: ${NO_PROXY} envFrom: - configMapRef: - name: internal-dns-config + name: studio-config ports: - containerPort: 5000 resources: @@ -370,6 +393,21 @@ spec: securityContext: {} image: ${REGISTRY}/studio-frontend:${TAG} imagePullPolicy: Always + env: + - name: DATABASE_TYPE + value: mysql + - name: DATABASE_HOST + value: ${MYSQL_HOST} + - name: DATABASE_PORT + value: "3306" + - name: DATABASE_USER + value: studio + - name: DATABASE_PASSWORD + value: studio + - name: DATABASE_NAME + value: studio + - name: DATABASE_SSL + value: "true" ports: - name: studio-frontend containerPort: 8080 @@ -378,6 +416,147 @@ spec: volumeMounts: - mountPath: /tmp name: tmp + - name: mysql-tls + mountPath: /etc/mysql/ssl + readOnly: true volumes: - name: tmp - emptyDir: {} \ No newline at end of file + emptyDir: {} + - name: mysql-tls + secret: + secretName: mysql-tls +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + namespace: studio + labels: + app: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + initContainers: + - name: keycloak-assets + image: curlimages/curl:latest + command: ["/bin/sh", "-c"] + args: + - | + OWNER=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/([^/]+)/([^/]+)/tree/([^/]+)/.*|\1|') + REPO=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/([^/]+)/([^/]+)/tree/([^/]+)/.*|\2|') + BRANCH=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/[^/]+/[^/]+/tree/([^/]+)/.*|\1|') + KC_ASSETS_DIR=$(echo ${KC_ASSETS_GIT_URL} | sed -E 's|https://github.com/[^/]+/[^/]+/tree/[^/]+/(.*?)/?$|\1|') + if [[ "${KC_ASSETS_DIR: -1}" == "/" ]]; then KC_ASSETS_DIR="${KC_ASSETS_DIR%/}"; fi + DOWNLOAD_URL="https://codeload.github.com/${OWNER}/${REPO}/tar.gz/${BRANCH}" + curl "${DOWNLOAD_URL}" | tar -xz --strip-components=4 -C /opt/keycloak/themes "${REPO}-${BRANCH}/${KC_ASSETS_DIR}/themes" + curl "${DOWNLOAD_URL}" | tar -xz --strip-components=4 -C /opt/keycloak/data "${REPO}-${BRANCH}/${KC_ASSETS_DIR}/data" + envFrom: + - configMapRef: + name: studio-config + volumeMounts: + - name: keycloak-themes-volume + mountPath: /opt/keycloak/themes + - name: keycloak-dataimport-volume + mountPath: /opt/keycloak/data/import + securityContext: + runAsUser: 0 + runAsGroup: 0 + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:latest + volumeMounts: + - name: mysql-tls + mountPath: /etc/mysql/ssl + readOnly: true + - name: app-tls + mountPath: /etc/ssl + readOnly: true + - name: keycloak-themes-volume + mountPath: /opt/keycloak/themes + - name: keycloak-dataimport-volume + mountPath: /opt/keycloak/data/import + args: + - start + - --import-realm + ports: + - containerPort: 8080 + - containerPort: 8443 + env: + - name: KC_BOOTSTRAP_ADMIN_USERNAME + value: "admin" + - name: KC_BOOTSTRAP_ADMIN_PASSWORD + value: "admin" + - name: KC_PROXY_HEADERS + value: "forwarded" + - name: KC_HTTP_RELATIVE_PATH + value: "/auth" + - name: KC_PROXY + value: edge + - name: KC_HTTPS_CERTIFICATE_FILE + value: /etc/ssl/app-tls.crt + - name: KC_HTTPS_CERTIFICATE_KEY_FILE + value: /etc/ssl/app-tls.key + - name: KC_HOSTNAME_STRICT + value: "false" + - name: KC_HOSTNAME_STRICT_HTTPS + value: "true" + # Database Configuration for MySQL + - name: KC_DB + value: "mysql" + - name: KC_DB_URL + value: "jdbc:mysql://${MYSQL_HOST}:3306/keycloak?useSSL=true&requireSSL=true&clientCertificateKeyStoreUrl=file:/etc/mysql/ssl/client-keystore.p12&trustCertificateKeyStoreUrl=file:/etc/mysql/ssl/ca.pem" + - name: KC_DB_USERNAME + value: "studio" + - name: KC_DB_PASSWORD + value: "studio" + - name: KC_DB_DATABASE + value: "keycloak" + readinessProbe: + failureThreshold: 3 + httpGet: + path: auth/realms/master + port: 8443 + scheme: HTTPS + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + memory: "512Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1" + volumes: + - name: app-tls + secret: + secretName: app-tls + - name: mysql-tls + secret: + secretName: mysql-tls + - name: keycloak-themes-volume + emptyDir: {} + - name: keycloak-dataimport-volume + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: keycloak + namespace: studio +spec: + type: ClusterIP + ports: + - name: https + protocol: TCP + port: 8443 + targetPort: 8443 + selector: + app: keycloak \ No newline at end of file diff --git a/setup-scripts/setup-genai-studio/playbooks/deploy-studio.yml b/setup-scripts/setup-genai-studio/playbooks/deploy-studio.yml index 70d2f3a..e1f422a 100644 --- a/setup-scripts/setup-genai-studio/playbooks/deploy-studio.yml +++ b/setup-scripts/setup-genai-studio/playbooks/deploy-studio.yml @@ -4,9 +4,14 @@ - ../vars.yml tasks: + - name: Check if studio namespace exists + command: kubectl get namespace studio + register: studio_namespace + ignore_errors: yes + - name: Create studio namespace command: kubectl create namespace studio - ignore_errors: yes + when: studio_namespace.rc != 0 - name: Check for coredns service shell: kubectl get svc coredns -n kube-system --ignore-not-found @@ -18,13 +23,61 @@ shell: sed -i 's/kube-dns/coredns/g' ../manifests/studio-manifest.yaml when: coredns_check.stdout != '' - - name: Apply internal DNS configuration - command: kubectl apply -f ../internal-dns-config.yaml + - name: Check if app-tls exists in studio namespace + command: kubectl get secret app-tls -n studio + register: app_tls_secret_check + ignore_errors: yes + + - name: Generate TLS certificate and create app-tls + shell: | + openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout app-tls.key -out app-tls.crt -subj "/CN=studio/O=studio" + kubectl create secret generic app-tls --from-file=app-tls.crt --from-file=app-tls.key -n studio + rm app-tls.key app-tls.crt + when: app_tls_secret_check.rc != 0 + + - name: Check if mysql-tls exists in studio namespace + command: kubectl get secret mysql-tls -n studio + register: mysql_tls_secret_check + ignore_errors: yes + + - name: Copy mysql ssl to current user + become: yes + become_user: root + shell: | + cp /var/lib/mysql/ca.pem . + cp /var/lib/mysql/client-cert.pem . + cp /var/lib/mysql/client-key.pem . + chown -R {{ ansible_env.USER }}:{{ ansible_env.USER }} ca.pem + chown -R {{ ansible_env.USER }}:{{ ansible_env.USER }} client-key.pem + chown -R {{ ansible_env.USER }}:{{ ansible_env.USER }} client-cert.pem + when: mysql_tls_secret_check.rc != 0 + + - name: Create mysql-tls from mysql ssl + shell: | + openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem -out client-keystore.p12 -name keycloak -CAfile ca.pem -caname root -password pass: + kubectl create secret generic mysql-tls \ + --from-file=ca.pem \ + --from-file=client-cert.pem \ + --from-file=client-key.pem \ + --from-file=client-keystore.p12 \ + -n studio + rm ca.pem client-key.pem client-cert.pem client-keystore.p12 + when: mysql_tls_secret_check.rc != 0 + + - name: Apply studio configuration + command: kubectl apply -f ../studio-config.yaml - name: Apply customized studio manifest - shell: "envsubst '${REGISTRY} ${TAG} ${HTTP_PROXY} ${NO_PROXY}' < ../manifests/studio-manifest.yaml | kubectl apply -f -" + shell: "envsubst '${REGISTRY} ${TAG} ${HTTP_PROXY} ${NO_PROXY} ${MYSQL_HOST}' < ../manifests/studio-manifest.yaml | kubectl apply -f -" environment: REGISTRY: "{{ container_registry }}" TAG: "{{ container_tag }}" HTTP_PROXY: "{{ http_proxy }}" - NO_PROXY: "{{ no_proxy }}" \ No newline at end of file + NO_PROXY: "{{ no_proxy }}" + MYSQL_HOST: "{{ mysql_host }}" + + - name: Wait for all pods to be ready in studio namespace + shell: kubectl wait --for=condition=ready pod --all --namespace=studio --timeout=180s + register: pod_ready_check + failed_when: pod_ready_check.rc != 0 + changed_when: false \ No newline at end of file diff --git a/setup-scripts/setup-genai-studio/playbooks/setup-mysqldb.yml b/setup-scripts/setup-genai-studio/playbooks/setup-mysqldb.yml new file mode 100644 index 0000000..2f64665 --- /dev/null +++ b/setup-scripts/setup-genai-studio/playbooks/setup-mysqldb.yml @@ -0,0 +1,94 @@ +- name: Setup MySQL Server + hosts: localhost + become: yes + tasks: + + - name: Install PyMySQL module + pip: + name: PyMySQL + state: present + + - name: Check if MySQL user 'studio' exists + mysql_user: + login_user: root + login_password: root + login_unix_socket: /var/run/mysqld/mysqld.sock + name: studio + check_implicit_admin: yes + state: present + check_mode: yes + register: studio_user_exists + ignore_errors: yes + + - name: End playbook if MySQL user 'studio' exists + meta: end_play + when: studio_user_exists.changed == false + + - name: Install MySQL server + apt: + name: mysql-server + state: present + update_cache: yes + + - name: Configure MySQL to listen on all interfaces + lineinfile: + path: /etc/mysql/mysql.conf.d/mysqld.cnf + regexp: '^bind-address' + line: 'bind-address = 0.0.0.0' + state: present + + - name: Restart MySQL service + service: + name: mysql + state: restarted + + - name: Secure MySQL installation updating root password + mysql_user: + name: root + host: localhost + password: root + login_unix_socket: /var/run/mysqld/mysqld.sock + priv: '*.*:ALL,GRANT' + state: present + plugin: mysql_native_password + ignore_errors: yes + + - name: Create MySQL user 'studio' for all hosts + mysql_user: + login_user: root + login_password: root + name: studio + host: '%' + password: studio + priv: '*.*:ALL,GRANT' + state: present + + - name: Enforce SSL for MySQL user 'studio' for all hosts + mysql_query: + login_user: root + login_password: root + query: "ALTER USER 'studio'@'%' REQUIRE X509;" + + - name: Create MySQL user 'studio' for localhost without X509 + mysql_user: + login_user: root + login_password: root + name: studio + host: localhost + password: studio + priv: '*.*:ALL,GRANT' + state: present + + - name: Create database 'keycloak' + mysql_db: + login_user: studio + login_password: studio + name: keycloak + state: present + + - name: Create database 'studio' + mysql_db: + login_user: studio + login_password: studio + name: studio + state: present \ No newline at end of file diff --git a/setup-scripts/setup-genai-studio/readme.md b/setup-scripts/setup-genai-studio/readme.md index 0b9202f..3f326aa 100644 --- a/setup-scripts/setup-genai-studio/readme.md +++ b/setup-scripts/setup-genai-studio/readme.md @@ -4,16 +4,17 @@ The genai-studio playbook script will: -1. Deploy a customized monitoring stack based on prometheus-community/kube-prometheus-stack (which contains both Prometheus and Grafana) in the monitoring namespace with a local-path-provisioner in local-path-storage namespace, for dynamic Persistent Volumes (PVs) provisioning. - -2. Deploy the studio-backend, studio-frontend and also a studio-nginx in the studio namespace. +1. Install and configure a MySQL server in localhost machine. +2. Deploy a customized monitoring stack based on prometheus-community/kube-prometheus-stack (which contains both Prometheus and Grafana) in the monitoring namespace with a local-path-provisioner in local-path-storage namespace, for dynamic Persistent Volumes (PVs) provisioning. +3. Deploy the keycloak, studio-backend, studio-frontend and also a studio-nginx in the studio namespace. ## Pre-requisite +- Disclaimer: The Ansible script has been tested on a fresh machine without any pre-existing MySQL server installation, studio, or monitoring deployment. Any other environment might require modifications to the Ansible playbooks accordingly. - Existing kubernetes cluster available. If not, please install by following the [Kubernetes official setup guide](https://kubernetes.io/docs/setup/). Alternatively, you can try out our [setup onpremise kubernetes script](../setup-onpremise-kubernetes/readme.md). -- Update var.yml accordingly +- Update vars.yml accordingly. By default, if you have a locally installed MySQL server, you will need to update the variable `mysql_host` in vars.yml with the external public IP of your localhost. ## Installation steps: diff --git a/setup-scripts/setup-genai-studio/internal-dns-config.yaml b/setup-scripts/setup-genai-studio/studio-config.yaml similarity index 74% rename from setup-scripts/setup-genai-studio/internal-dns-config.yaml rename to setup-scripts/setup-genai-studio/studio-config.yaml index e87c12e..002efdf 100644 --- a/setup-scripts/setup-genai-studio/internal-dns-config.yaml +++ b/setup-scripts/setup-genai-studio/studio-config.yaml @@ -1,9 +1,11 @@ apiVersion: v1 kind: ConfigMap metadata: - name: internal-dns-config + name: studio-config namespace: studio data: + KC_ASSETS_GIT_URL: "https://github.com/opea-project/GenAIStudio/tree/main/assets/keycloak" + KEYCLOAK_DNS: "keycloak.studio.svc.cluster.local:8443" GRAFANA_DNS: "kube-prometheus-stack-grafana.monitoring.svc.cluster.local" STUDIO_FRONTEND_DNS: "studio-frontend.studio.svc.cluster.local:3000" APP_FRONTEND_DNS: "app-frontend.$namespace.svc.cluster.local:5175" diff --git a/setup-scripts/setup-genai-studio/vars.yml b/setup-scripts/setup-genai-studio/vars.yml index 5fdc49c..131d094 100644 --- a/setup-scripts/setup-genai-studio/vars.yml +++ b/setup-scripts/setup-genai-studio/vars.yml @@ -1,4 +1,5 @@ container_registry: 'opea' container_tag: 'latest' http_proxy: '' -no_proxy: '' \ No newline at end of file +no_proxy: '' +mysql_host: 'Your_External_Host_IP' \ No newline at end of file diff --git a/setup-scripts/setup-onpremise-kubernetes/harbor_cleanup.sh b/setup-scripts/setup-onpremise-kubernetes/cleanup_registry/harbor_cleanup.sh similarity index 100% rename from setup-scripts/setup-onpremise-kubernetes/harbor_cleanup.sh rename to setup-scripts/setup-onpremise-kubernetes/cleanup_registry/harbor_cleanup.sh diff --git a/setup-scripts/setup-onpremise-kubernetes/onpremise-kubernetes.yml b/setup-scripts/setup-onpremise-kubernetes/onpremise-kubernetes.yml index 740036d..8d74987 100644 --- a/setup-scripts/setup-onpremise-kubernetes/onpremise-kubernetes.yml +++ b/setup-scripts/setup-onpremise-kubernetes/onpremise-kubernetes.yml @@ -4,5 +4,5 @@ - name: Setup K8 cluster import_playbook: playbooks/setup-k8-cluster.yml -- name: Setup harbor container registry - import_playbook: playbooks/setup-harbor.yml \ No newline at end of file +- name: Setup local container registry + import_playbook: playbooks/setup-local-registry.yml \ No newline at end of file diff --git a/setup-scripts/setup-onpremise-kubernetes/playbooks/install-requirements.yml b/setup-scripts/setup-onpremise-kubernetes/playbooks/install-requirements.yml index 4dbc94a..4083ee2 100644 --- a/setup-scripts/setup-onpremise-kubernetes/playbooks/install-requirements.yml +++ b/setup-scripts/setup-onpremise-kubernetes/playbooks/install-requirements.yml @@ -161,7 +161,7 @@ line: "{{ item }}" loop: - 'export http_proxy={{ http_proxy }}' - - 'export http_proxy={{ http_proxy }}' + - 'export https_proxy={{ http_proxy }}' - 'export no_proxy={{ no_proxy }}' - name: Set up proxy environment variables for Docker service block: @@ -219,6 +219,10 @@ name: ['kubelet', 'kubeadm', 'kubectl'] state: present update_cache: yes + environment: + http_proxy: "{{ http_proxy }}" + https_proxy: "{{ http_proxy }}" + no_proxy: "{{ no_proxy }}" - name: Hold Kubernetes packages dpkg_selections: diff --git a/setup-scripts/setup-onpremise-kubernetes/playbooks/setup-local-registry.yml b/setup-scripts/setup-onpremise-kubernetes/playbooks/setup-local-registry.yml new file mode 100644 index 0000000..6e71265 --- /dev/null +++ b/setup-scripts/setup-onpremise-kubernetes/playbooks/setup-local-registry.yml @@ -0,0 +1,137 @@ +- name: Update Docker daemon configuration on all hosts and run Docker registry on localhost + hosts: all + become: yes + become_method: sudo + become_user: root + vars_files: + - ../vars.yml + tasks: + - name: Ensure /etc/docker/daemon.json exists + copy: + dest: /etc/docker/daemon.json + content: | + { + "exec-opts": ["native.cgroupdriver=systemd"], + "log-driver" : "json-file", + "log-opts": { + "max-size" : "100m" + }, + "storage-driver": "overlay2". + "insecure-registries": [ + "{{ groups['k8_master'][0] }}:5000" + ] + } + + handlers: + - name: Reload Docker daemon + command: systemctl daemon-reload + + - name: Restart Docker service + service: + name: docker + state: restarted + +- name: Run Docker registry on localhost + hosts: localhost + become: yes + become_method: sudo + become_user: root + vars_files: + - ../vars.yml + tasks: + - name: Install pip3 if not present + apt: + name: python3-pip + state: present + update_cache: yes + environment: + http_proxy: "{{ http_proxy }}" + https_proxy: "{{ http_proxy }}" + no_proxy: "{{ no_proxy }}" + + - name: Install Docker SDK for Python + pip: + name: docker + executable: pip3 + state: present + environment: + http_proxy: "{{ http_proxy }}" + https_proxy: "{{ http_proxy }}" + no_proxy: "{{ no_proxy }}" + + - name: Ensure /var/lib/registry directory exists + file: + path: /var/lib/registry + state: directory + owner: root + group: root + mode: '0755' + + - name: Run Docker registry container + docker_container: + name: registry + image: registry:2 + state: started + restart_policy: always + ports: + - "5000:5000" + volumes: + - ../registry.config.yml:/etc/docker/registry/config.yml + - /var/lib/registry:/var/lib/registry + container_default_behavior: "compatibility" + + - name: Ensure /etc/systemd/system/containerd.service.d directory exists + file: + path: /etc/systemd/system/containerd.service.d + state: directory + owner: root + group: root + mode: '0755' + + - name: Create /etc/systemd/system/containerd.service.d/http-proxy.conf + copy: + dest: /etc/systemd/system/containerd.service.d/http-proxy.conf + content: | + [Service] + Environment="HTTP_PROXY={{ http_proxy }}" + Environment="HTTPS_PROXY={{ http_proxy }}" + Environment="NO_PROXY={{ no_proxy }}" + notify: + - Reload systemd daemon + + - name: Create /etc/containerd/config.toml + copy: + dest: /etc/containerd/config.toml + content: | + version = 2 + root = "/var/lib/containerd" + state = "/run/containerd" + oom_score = 0 + + [grpc] + max_recv_message_size = 16777216 + max_send_message_size = 16777216 + + [debug] + level = "info" + + [metrics] + address = "" + grpc_histogram = false + + [plugins] + [plugins."io.containerd.grpc.v1.cri".registry] + [plugins."io.containerd.grpc.v1.cri".registry.mirrors] + [plugins."io.containerd.grpc.v1.cri".registry.mirrors."{{ groups['k8_master'][0] }}:5000"] + endpoint = ["http://{{ groups['k8_master'][0] }}:5000"] + notify: + - Restart containerd service + + handlers: + - name: Reload systemd daemon + command: systemctl daemon-reload + + - name: Restart containerd service + service: + name: containerd + state: restarted \ No newline at end of file diff --git a/setup-scripts/setup-onpremise-kubernetes/readme.md b/setup-scripts/setup-onpremise-kubernetes/readme.md index 2efe011..54bcfcb 100644 --- a/setup-scripts/setup-onpremise-kubernetes/readme.md +++ b/setup-scripts/setup-onpremise-kubernetes/readme.md @@ -22,6 +22,6 @@ ansible-playbook -i inventory.ini onpremise-kubernetes.yml To push your local docker images into the harbor container registry, run below: ```sh -docker tag : :8443/k8s/: -docker push :8443/k8s/: -``` +docker tag : :5000/opea/: +docker push :5000/opea/: +``` \ No newline at end of file diff --git a/setup-scripts/setup-onpremise-kubernetes/registry.config.yml b/setup-scripts/setup-onpremise-kubernetes/registry.config.yml new file mode 100644 index 0000000..879d3af --- /dev/null +++ b/setup-scripts/setup-onpremise-kubernetes/registry.config.yml @@ -0,0 +1,20 @@ +version: 0.1 +log: + fields: + service: registry +storage: + cache: + blobdescriptor: inmemory + filesystem: + rootdirectory: /var/lib/registry + delete: + enabled: true +http: + addr: :5000 + headers: + X-Content-Type-Options: [nosniff] +health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 \ No newline at end of file diff --git a/studio-backend/app/models/pipeline_model.py b/studio-backend/app/models/pipeline_model.py index c94798b..7563b42 100644 --- a/studio-backend/app/models/pipeline_model.py +++ b/studio-backend/app/models/pipeline_model.py @@ -65,5 +65,5 @@ class PipelineFlow(BaseModel): createdDate: datetime updatedDate: datetime -class ProjectId(BaseModel): +class WorkflowId(BaseModel): id: str \ No newline at end of file diff --git a/studio-backend/app/models/project_info_model.py b/studio-backend/app/models/workflow_info_model.py similarity index 95% rename from studio-backend/app/models/project_info_model.py rename to studio-backend/app/models/workflow_info_model.py index 14a03b5..c507059 100644 --- a/studio-backend/app/models/project_info_model.py +++ b/studio-backend/app/models/workflow_info_model.py @@ -19,7 +19,7 @@ class UIConfigModel(BaseModel): chat_input: bool doc_input: bool -class ProjectInfoModel(BaseModel): +class WorkflowInfoModel(BaseModel): chat_completion_ids: List[str] chat_input_ids: List[str] doc_input_ids: List[str] diff --git a/studio-backend/app/routers/download_router.py b/studio-backend/app/routers/download_router.py index 243a080..a1d0b7c 100644 --- a/studio-backend/app/routers/download_router.py +++ b/studio-backend/app/routers/download_router.py @@ -10,7 +10,7 @@ from app.services.exporter_service import convert_proj_info_to_compose from app.models.pipeline_model import PipelineFlow -from app.services.project_info_service import ProjectInfo +from app.services.workflow_info_service import WorkflowInfo router = APIRouter() @@ -26,12 +26,12 @@ def create_and_download_zip(request: PipelineFlow, background_tasks: BackgroundT env_file_path = os.path.join(temp_dir, ".env") readme_file_path = os.path.join(temp_dir, "readme.MD") compose_file_path = os.path.join(temp_dir, "compose.yaml") - project_info_file_path = os.path.join(temp_dir, "project-info.json") + workflow_info_file_path = os.path.join(temp_dir, "workflow-info.json") zip_path = os.path.join(temp_dir, "docker-compose.zip") - # Covnert request to project info json - project_info_raw = ProjectInfo(request.dict()) - project_info = json.loads(project_info_raw.export_to_json()) + # Covnert request to workflow info json + workflow_info_raw = WorkflowInfo(request.dict()) + workflow_info = json.loads(workflow_info_raw.export_to_json()) # Write the strings to files try: @@ -46,20 +46,20 @@ def create_and_download_zip(request: PipelineFlow, background_tasks: BackgroundT f.write(readme_content) # compose.yaml contents - compose_content = convert_proj_info_to_compose(project_info) + compose_content = convert_proj_info_to_compose(workflow_info) with open(compose_file_path, 'w') as f: f.write(compose_content) - # project-info.json contents - with open(project_info_file_path, 'w') as f: - f.write(json.dumps(project_info, indent=4)) + # workflow-info.json contents + with open(workflow_info_file_path, 'w') as f: + f.write(json.dumps(workflow_info, indent=4)) with zipfile.ZipFile(zip_path, 'w') as zipf: # Specify the folder structure in the arcname zipf.write(env_file_path, arcname=os.path.join("docker-compose", ".env")) zipf.write(readme_file_path, arcname=os.path.join("docker-compose", "readme.MD")) zipf.write(compose_file_path, arcname=os.path.join("docker-compose", "compose.yaml")) - zipf.write(project_info_file_path, arcname=os.path.join("docker-compose", "project-info.json")) + zipf.write(workflow_info_file_path, arcname=os.path.join("docker-compose", "workflow-info.json")) # Schedule the cleanup task to run after the response has been sent background_tasks.add_task(clean_up_temp_dir, temp_dir) diff --git a/studio-backend/app/routers/sandbox_router.py b/studio-backend/app/routers/sandbox_router.py index e04af4f..ab8c101 100644 --- a/studio-backend/app/routers/sandbox_router.py +++ b/studio-backend/app/routers/sandbox_router.py @@ -4,8 +4,8 @@ import json -from app.models.pipeline_model import PipelineFlow, ProjectId -from app.services.project_info_service import ProjectInfo +from app.models.pipeline_model import PipelineFlow, WorkflowId +from app.services.workflow_info_service import WorkflowInfo from app.services.namespace_service import deploy_manifest_in_namespace, delete_namespace, check_ns_status router = APIRouter() @@ -13,18 +13,18 @@ @router.post("/deploy-sandbox") async def deploy_sandbox(request: PipelineFlow): print('deploy-sandbox') - project_info = ProjectInfo(request.dict()) + workflow_info = WorkflowInfo(request.dict()) core_v1_api = client.CoreV1Api() apps_v1_api = client.AppsV1Api() try: - response = deploy_manifest_in_namespace(core_v1_api, apps_v1_api, json.loads(project_info.export_to_json())) + response = deploy_manifest_in_namespace(core_v1_api, apps_v1_api, json.loads(workflow_info.export_to_json())) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) return response @router.post("/delete-sandbox") -async def delete_sandbox(request: ProjectId): +async def delete_sandbox(request: WorkflowId): print('deploy-sandbox') core_v1_api = client.CoreV1Api() try: diff --git a/studio-backend/app/services/exporter_service.py b/studio-backend/app/services/exporter_service.py index ab909b8..5d6bf44 100644 --- a/studio-backend/app/services/exporter_service.py +++ b/studio-backend/app/services/exporter_service.py @@ -7,7 +7,7 @@ def convert_proj_info_to_manifest(proj_info_json, output_file=None): - print("Converting project info json to manifest.") + print("Converting workflow info json to manifest.") opea_services = process_opea_services(proj_info_json) # print(json.dumps(opea_services, indent=4)) @@ -56,7 +56,7 @@ def convert_proj_info_to_manifest(proj_info_json, output_file=None): def convert_proj_info_to_compose(proj_info_json, output_file=None): - print("Converting project info json to compose.") + print("Converting workflow info json to compose.") opea_services = process_opea_services(proj_info_json) # print(json.dumps(opea_services, indent=4)) diff --git a/studio-backend/app/services/project_info_service.py b/studio-backend/app/services/workflow_info_service.py similarity index 99% rename from studio-backend/app/services/project_info_service.py rename to studio-backend/app/services/workflow_info_service.py index b664467..1b7c43b 100644 --- a/studio-backend/app/services/project_info_service.py +++ b/studio-backend/app/services/workflow_info_service.py @@ -1,7 +1,7 @@ import json import re -class ProjectInfo: +class WorkflowInfo: def __init__(self, pipeline): data = pipeline self.id = data['id'] diff --git a/studio-backend/app/templates/app/app.compose.yaml b/studio-backend/app/templates/app/app.compose.yaml index 368b843..4eab4f5 100644 --- a/studio-backend/app/templates/app/app.compose.yaml +++ b/studio-backend/app/templates/app/app.compose.yaml @@ -2,7 +2,7 @@ app-backend: image: __APP_BACKEND_IMAGE__ container_name: app-backend volumes: - - ./project-info.json:/home/user/config/project-info.json + - ./workflow-info.json:/home/user/config/workflow-info.json depends_on: __BACKEND_ENDPOINTS_LIST_PLACEHOLDER__ ports: @@ -25,7 +25,7 @@ app-frontend: - no_proxy=${no_proxy} - https_proxy=${https_proxy} - http_proxy=${http_proxy} - - APP_BACKEND_SERVICE_URL=/v1/app-backend + - VITE_APP_BACKEND_SERVICE_URL=/v1/app-backend __UI_CONFIG_INFO_ENV_PLACEHOLDER__ ipc: host restart: always diff --git a/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml b/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml index a795156..b2c9437 100644 --- a/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml +++ b/studio-backend/app/templates/app/app.manifest.aws.ecr.yaml @@ -13,7 +13,7 @@ kind: ConfigMap metadata: name: app-backend-config data: - project-info.json: |+ + workflow-info.json: |+ __BACKEND_PROJECT_INFO_JSON_PLACEHOLDER__ --- apiVersion: v1 @@ -67,9 +67,9 @@ spec: volumeMounts: - mountPath: /tmp name: tmp - - mountPath: /home/user/config/project-info.json - name: project-info-volume - subPath: project-info.json + - mountPath: /home/user/config/workflow-info.json + name: workflow-info-volume + subPath: workflow-info.json ports: - name: app-backend containerPort: 8888 @@ -80,7 +80,7 @@ spec: volumes: - name: tmp emptyDir: {} - - name: project-info-volume + - name: workflow-info-volume configMap: name: app-backend-config --- @@ -118,7 +118,7 @@ spec: containers: - name: app-frontend env: - - name: APP_BACKEND_SERVICE_URL + - name: VITE_APP_BACKEND_SERVICE_URL value: /v1/app-backend __UI_CONFIG_INFO_ENV_PLACEHOLDER__ securityContext: {} diff --git a/studio-backend/app/templates/app/app.manifest.yaml b/studio-backend/app/templates/app/app.manifest.yaml index 931de63..5b12824 100644 --- a/studio-backend/app/templates/app/app.manifest.yaml +++ b/studio-backend/app/templates/app/app.manifest.yaml @@ -4,7 +4,7 @@ kind: ConfigMap metadata: name: app-backend-config data: - project-info.json: |+ + workflow-info.json: |+ __BACKEND_PROJECT_INFO_JSON_PLACEHOLDER__ --- apiVersion: v1 @@ -58,9 +58,9 @@ spec: volumeMounts: - mountPath: /tmp name: tmp - - mountPath: /home/user/config/project-info.json - name: project-info-volume - subPath: project-info.json + - mountPath: /home/user/config/workflow-info.json + name: workflow-info-volume + subPath: workflow-info.json ports: - name: app-backend containerPort: 8888 @@ -69,7 +69,7 @@ spec: volumes: - name: tmp emptyDir: {} - - name: project-info-volume + - name: workflow-info-volume configMap: name: app-backend-config --- @@ -107,7 +107,7 @@ spec: containers: - name: app-frontend env: - - name: APP_BACKEND_SERVICE_URL + - name: VITE_APP_BACKEND_SERVICE_URL value: /v1/app-backend __UI_CONFIG_INFO_ENV_PLACEHOLDER__ securityContext: {} diff --git a/studio-backend/app/templates/grafana-dashboards/sandbox-dashboard.json b/studio-backend/app/templates/grafana-dashboards/sandbox-dashboard.json index 36eced1..e84fc3f 100644 --- a/studio-backend/app/templates/grafana-dashboards/sandbox-dashboard.json +++ b/studio-backend/app/templates/grafana-dashboards/sandbox-dashboard.json @@ -22,104 +22,138 @@ "id": null, "links": [], "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "panels": [], + "title": "Cluster CPU / MEM / DISK", + "type": "row" + }, { "datasource": { "default": true, "type": "prometheus", "uid": "prometheus" }, + "description": "Resource pressure via PSI", "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } + "mode": "thresholds" }, + "decimals": 1, + "links": [], "mappings": [], + "max": 1, + "min": 0, "thresholds": { - "mode": "absolute", + "mode": "percentage", "steps": [ { "color": "green", "value": null }, { - "color": "red", - "value": 80 + "color": "dark-yellow", + "value": 70 + }, + { + "color": "dark-red", + "value": 90 } ] }, - "unit": "short" + "unit": "percentunit" }, "overrides": [] }, "gridPos": { - "h": 9, - "w": 12, + "h": 4, + "w": 3, "x": 0, - "y": 0 + "y": 1 }, - "id": 2, + "id": 3, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "displayMode": "basic", + "maxVizHeight": 300, + "minVizHeight": 10, + "minVizWidth": 0, + "namePlacement": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "multi", - "sort": "none" - } + "showUnfilled": true, + "sizing": "auto", + "text": {}, + "valueMode": "color" }, - "pluginVersion": "8.0.0", + "pluginVersion": "11.2.0", "targets": [ { "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rate(container_cpu_usage_seconds_total{namespace=\"___EXPR_NAMESPACE___\", container!=\"\"}[5m])) by (pod)", - "interval": "", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" + "exemplar": false, + "expr": "irate(node_pressure_cpu_waiting_seconds_total{job=\"node-exporter\"}[$__rate_interval])", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "CPU", + "range": false, + "refId": "CPU some", + "step": 240 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "irate(node_pressure_memory_waiting_seconds_total{job=\"node-exporter\"}[$__rate_interval])", + "format": "time_series", + "hide": false, + "instant": true, + "intervalFactor": 1, + "legendFormat": "Mem", + "range": false, + "refId": "Memory some", + "step": 240 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "irate(node_pressure_io_waiting_seconds_total{job=\"node-exporter\"}[$__rate_interval])", + "format": "time_series", + "hide": false, + "instant": true, + "intervalFactor": 1, + "legendFormat": "I/O", + "range": false, + "refId": "I/O some", + "step": 240 } ], - "title": "CPU Usage (container)", - "type": "timeseries" + "title": "Pressure", + "type": "bargauge" }, { "datasource": { @@ -127,98 +161,90 @@ "type": "prometheus", "uid": "prometheus" }, + "description": "Busy state of all CPU cores together", "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" + "mode": "thresholds" }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" } - }, - "mappings": [], + ], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", + "color": "rgba(50, 172, 45, 0.97)", "value": null }, { - "color": "red", - "value": 80 + "color": "rgba(237, 129, 40, 0.89)", + "value": 85 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 95 } ] }, - "unit": "bytes" + "unit": "percent" }, "overrides": [] }, "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 0 + "h": 4, + "w": 3, + "x": 3, + "y": 1 }, "id": 4, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "multi", - "sort": "none" - } + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" }, - "pluginVersion": "8.0.0", + "pluginVersion": "11.2.0", "targets": [ { "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(rate(container_memory_usage_bytes{namespace=\"___EXPR_NAMESPACE___\", container!=\"\"}[5m])) by (pod)", - "interval": "", - "legendFormat": "{{pod}}", - "range": true, - "refId": "B" + "exemplar": false, + "expr": "100 * (1 - avg(rate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval])))", + "hide": false, + "instant": true, + "intervalFactor": 1, + "legendFormat": "", + "range": false, + "refId": "A", + "step": 240 } ], - "title": "Memory Usage (container)", - "type": "timeseries" + "title": "CPU Busy", + "type": "gauge" }, { "datasource": { @@ -226,98 +252,90 @@ "type": "prometheus", "uid": "prometheus" }, + "description": "System load over all CPU cores together", "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" + "mode": "thresholds" }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" } - }, - "mappings": [], + ], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", + "color": "rgba(50, 172, 45, 0.97)", "value": null }, { - "color": "red", - "value": 80 + "color": "rgba(237, 129, 40, 0.89)", + "value": 85 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 95 } ] }, - "unit": "short" + "unit": "percent" }, "overrides": [] }, "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 9 + "h": 4, + "w": 3, + "x": 6, + "y": 1 }, "id": 5, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "multi", - "sort": "none" - } + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" }, - "pluginVersion": "8.0.0", + "pluginVersion": "11.2.0", "targets": [ { "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(process_cpu_seconds_total{namespace=\"___EXPR_NAMESPACE___\"}[5m])", - "interval": "", - "legendFormat": "{{pod}}", - "range": true, - "refId": "A" + "exemplar": false, + "expr": "scalar(node_load1{job=\"node-exporter\"}) * 100 / count(count(node_cpu_seconds_total{job=\"node-exporter\"}) by (cpu))", + "format": "time_series", + "hide": false, + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 } ], - "title": "CPU Usage (process)", - "type": "timeseries" + "title": "Sys Load", + "type": "gauge" }, { "datasource": { @@ -325,100 +343,3804 @@ "type": "prometheus", "uid": "prometheus" }, + "description": "Non available RAM memory", "fieldConfig": { "defaults": { "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } + "mode": "thresholds" }, + "decimals": 1, "mappings": [], + "max": 100, + "min": 0, "thresholds": { "mode": "absolute", "steps": [ { - "color": "green", + "color": "rgba(50, 172, 45, 0.97)", "value": null }, { - "color": "red", + "color": "rgba(237, 129, 40, 0.89)", "value": 80 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 90 } ] }, - "unit": "short" + "unit": "percent" }, "overrides": [] }, "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 9 + "h": 4, + "w": 3, + "x": 9, + "y": 1 }, + "hideTimeOverride": false, "id": 6, "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false }, - "tooltip": { - "mode": "multi", - "sort": "none" - } + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" }, - "pluginVersion": "8.0.0", + "pluginVersion": "11.2.0", "targets": [ { "datasource": { "type": "prometheus", - "uid": "prometheus" + "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(process_resident_memory_bytes{namespace=\"___EXPR_NAMESPACE___\"}[5m])", + "exemplar": false, + "expr": "((node_memory_MemTotal_bytes{ job=\"node-exporter\"} - node_memory_MemFree_bytes{ job=\"node-exporter\"}) / node_memory_MemTotal_bytes{ job=\"node-exporter\"}) * 100", + "format": "time_series", + "hide": true, + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "(1 - (node_memory_MemAvailable_bytes{ job=\"node-exporter\"} / node_memory_MemTotal_bytes{ job=\"node-exporter\"})) * 100", + "format": "time_series", + "hide": false, + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "B", + "step": 240 + } + ], + "title": "RAM Used", + "type": "gauge" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Used Swap", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 10 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 25 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 12, + "y": 1 + }, + "id": 7, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "((node_memory_SwapTotal_bytes{job=\"node-exporter\"} - node_memory_SwapFree_bytes{job=\"node-exporter\"}) / (node_memory_SwapTotal_bytes{job=\"node-exporter\"})) * 100", + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 + } + ], + "title": "SWAP Used", + "type": "gauge" + }, + { + "datasource": {}, + "description": "Used Root FS", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 80 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 15, + "y": 1 + }, + "id": 8, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "100 - ((node_filesystem_avail_bytes{job=\"node-exporter\",mountpoint=\"/\",fstype!=\"rootfs\"} * 100) / node_filesystem_size_bytes{job=\"node-exporter\",mountpoint=\"/\",fstype!=\"rootfs\"})", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 + } + ], + "title": "Root FS Used", + "type": "gauge" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Total number of CPU cores", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 18, + "y": 1 + }, + "id": 9, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "count(count(node_cpu_seconds_total{job=\"node-exporter\"}) by (cpu))", + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "title": "CPU Cores", + "type": "stat" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "description": "System uptime", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 20, + "y": 1 + }, + "hideTimeOverride": true, + "id": 10, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "node_time_seconds{job=\"node-exporter\"} - node_boot_time_seconds{job=\"node-exporter\"}", + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Total RootFS", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgba(50, 172, 45, 0.97)", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 70 + }, + { + "color": "rgba(245, 54, 54, 0.9)", + "value": 90 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 18, + "y": 3 + }, + "id": 11, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "node_filesystem_size_bytes{job=\"node-exporter\",mountpoint=\"/\",fstype!=\"rootfs\"}", + "format": "time_series", + "hide": false, + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 + } + ], + "title": "RootFS Total", + "type": "stat" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Total RAM", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 20, + "y": 3 + }, + "id": 12, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "node_memory_MemTotal_bytes{job=\"node-exporter\"}", + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 + } + ], + "title": "RAM Total", + "type": "stat" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Total SWAP", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [ + { + "options": { + "match": "null", + "result": { + "text": "N/A" + } + }, + "type": "special" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 22, + "y": 3 + }, + "id": 13, + "maxDataPoints": 100, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "node_memory_SwapTotal_bytes{job=\"node-exporter\"}", + "instant": true, + "intervalFactor": 1, + "range": false, + "refId": "A", + "step": 240 + } + ], + "title": "SWAP Total", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 14, + "panels": [], + "title": "Sandbox CPU / MEM / REQ", + "type": "row" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 15, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(container_cpu_usage_seconds_total{namespace=\"___EXPR_NAMESPACE___\", container!=\"\"}[5m])) by (pod)", + "interval": "", + "legendFormat": "{{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "CPU Usage (container)", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 6 + }, + "id": 17, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(process_cpu_seconds_total{namespace=\"___EXPR_NAMESPACE___\"}[5m])", + "interval": "", + "legendFormat": "{{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "CPU Usage (process)", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 14 + }, + "id": 16, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(container_memory_usage_bytes{namespace=\"___EXPR_NAMESPACE___\", container!=\"\"}[5m])) by (pod)", + "interval": "", + "legendFormat": "{{pod}}", + "range": true, + "refId": "B" + } + ], + "title": "Memory Usage (container)", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 14 + }, + "id": 18, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "rate(process_resident_memory_bytes{namespace=\"___EXPR_NAMESPACE___\"}[5m])", + "interval": "", + "legendFormat": "{{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "Memory Usage (process)", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 15, + "x": 0, + "y": 22 + }, + "id": 25, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max", + "min" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "$$hashKey": "object:214", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(handler) (rate(http_requests_total{namespace=\"___EXPR_NAMESPACE___\", handler!~\"/metrics|/v1/health_check|none\"}[1m]))", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ method }} {{ handler }}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total requests per second", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "4xx" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "HTTP 500" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#bf1b00", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 9, + "x": 15, + "y": 22 + }, + "id": 26, + "options": { + "legend": { + "calcs": [ + "mean", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "$$hashKey": "object:140", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum by(status) (rate(http_requests_total{namespace=\"___EXPR_NAMESPACE___\"}[1m]))", + "format": "time_series", + "fullMetaSearch": false, + "includeNullMetadata": true, "interval": "", - "legendFormat": "{{pod}}", + "intervalFactor": 1, + "legendFormat": "{{ status }}", "range": true, - "refId": "A" + "refId": "A", + "useBackend": false + } + ], + "title": "Request per second", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 19, + "panels": [ + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1000 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 31 + }, + "id": 20, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "(histogram_quantile(0.5, sum by (le) (rate(tgi_request_queue_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m]))) * 1000) > 0", + "hide": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "(histogram_quantile(0.5, sum by (le) (rate(tgi_batch_inference_duration_bucket{method=\"prefill\", namespace=\"___EXPR_NAMESPACE___\"}[10m]))) * 1000) > 0", + "hide": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "C" + }, + { + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$B + $C", + "hide": false, + "refId": "D", + "type": "math" + } + ], + "title": "Time to first token", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 31 + }, + "id": 21, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "(histogram_quantile(0.5, sum by (le) (rate(tgi_batch_forward_duration_bucket{method=\"decode\", namespace=\"___EXPR_NAMESPACE___\"}[10m]))) * 1000)>0", + "instant": false, + "range": true, + "refId": "A" + } + ], + "title": "Decode per-token latency", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 31 + }, + "id": 22, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum((rate(tgi_request_generated_tokens_sum{namespace=\"___EXPR_NAMESPACE___\"}[10m]) / rate(tgi_request_generated_tokens_count{namespace=\"___EXPR_NAMESPACE___\"}[10m]))>0)", + "instant": false, + "range": true, + "refId": "A" + } + ], + "title": "Throughput (generated tok/s)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 38 + }, + "id": 23, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(tgi_request_input_length_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(tgi_request_input_length_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(tgi_request_input_length_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Number of tokens per prompt", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 38 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(tgi_request_generated_tokens_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(tgi_request_generated_tokens_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(tgi_request_generated_tokens_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Number of generated tokens per request", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 46 + }, + "id": 27, + "maxDataPoints": 100, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(tgi_request_success{namespace=\"___EXPR_NAMESPACE___\"}[1m]))", + "legendFormat": "Success", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(tgi_request_failure{namespace=\"___EXPR_NAMESPACE___\"}[1m])) by (err)", + "hide": false, + "legendFormat": "Error: {{err}}", + "range": true, + "refId": "B" + } + ], + "title": "Requests", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 13, + "w": 9, + "x": 6, + "y": 46 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(tgi_request_mean_time_per_token_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(tgi_request_mean_time_per_token_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(tgi_request_mean_time_per_token_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Mean Time Per Token quantiles", + "type": "timeseries" + }, + { + "cards": {}, + "color": { + "cardColor": "#5794F2", + "colorScale": "linear", + "colorScheme": "interpolateSpectral", + "exponent": 0.5, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 9, + "x": 15, + "y": 46 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 31, + "legend": { + "show": false + }, + "maxDataPoints": 25, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": {}, + "color": { + "exponent": 0.5, + "fill": "#5794F2", + "min": 0, + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Spectral", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": false + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 1, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "11.2.0", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(tgi_request_mean_time_per_token_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[5m])) by (le)", + "format": "heatmap", + "interval": "", + "legendFormat": "{{ le }}", + "range": true, + "refId": "A" + } + ], + "title": "Mean Time Per Token", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 0, + "y": 54 + }, + "id": 28, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "count(tgi_request_count{namespace=\"___EXPR_NAMESPACE___\"})", + "legendFormat": "Replicas", + "range": true, + "refId": "A" + } + ], + "title": "Number of replicas", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 3, + "y": 54 + }, + "id": 29, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(tgi_queue_size{namespace=\"___EXPR_NAMESPACE___\"})", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Queue Size", + "type": "gauge" } ], - "title": "Memory Usage (process)", - "type": "timeseries" + "title": "Sandbox TGI", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 32, + "panels": [ + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 60 + }, + "id": 33, + "maxDataPoints": 100, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(te_embed_success{namespace=\"___EXPR_NAMESPACE___\"}[1m]))", + "legendFormat": "Success", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(increase(te_embed_count{namespace=\"___EXPR_NAMESPACE___\"}[1m]))", + "hide": false, + "legendFormat": "Total Count", + "range": true, + "refId": "B" + } + ], + "title": "Requests", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 13, + "w": 9, + "x": 6, + "y": 60 + }, + "id": 36, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(te_embed_queue_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(te_embed_queue_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(te_embed_queue_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Queue Duration quantiles", + "type": "timeseries" + }, + { + "cards": {}, + "color": { + "cardColor": "#5794F2", + "colorScale": "linear", + "colorScheme": "interpolateSpectral", + "exponent": 0.5, + "min": 0, + "mode": "opacity" + }, + "dataFormat": "tsbuckets", + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 13, + "w": 9, + "x": 15, + "y": 60 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 37, + "legend": { + "show": false + }, + "maxDataPoints": 25, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": {}, + "color": { + "exponent": 0.5, + "fill": "#5794F2", + "min": 0, + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Spectral", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": false + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "maxHeight": 600, + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 1, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "11.2.0", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(increase(te_request_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[5m])) by (le)", + "format": "heatmap", + "interval": "", + "legendFormat": "{{ le }}", + "range": true, + "refId": "A" + } + ], + "title": "E2E Latency", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 1, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 0, + "y": 68 + }, + "id": 34, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.1.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "count(te_request_count{namespace=\"___EXPR_NAMESPACE___\"})", + "legendFormat": "Replicas", + "range": true, + "refId": "A" + } + ], + "title": "Number of replicas", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 70 + }, + { + "color": "red", + "value": 85 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 3, + "y": 68 + }, + "id": 35, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.2.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(te_queue_size{namespace=\"___EXPR_NAMESPACE___\"})", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Queue Size", + "type": "gauge" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 14, + "w": 6, + "x": 0, + "y": 73 + }, + "id": 38, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(te_embed_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(te_embed_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(te_embed_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Latency quantiles", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 14, + "w": 9, + "x": 6, + "y": 73 + }, + "id": 39, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(te_request_tokenization_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(te_request_tokenization_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(te_request_tokenization_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Tokenization Duration quantiles", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 9, + "x": 15, + "y": 73 + }, + "id": 40, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(te_request_inference_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(te_request_inference_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(te_request_inference_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Inference quantiles", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 9, + "x": 15, + "y": 80 + }, + "id": 41, + "options": { + "legend": { + "calcs": [ + "min", + "max" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum by (le) (rate(te_embed_tokenization_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.9, sum by (le) (rate(te_embed_tokenization_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum by (le) (rate(te_embed_tokenization_duration_bucket{namespace=\"___EXPR_NAMESPACE___\"}[10m])))", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + } + ], + "title": "Tokenization Latency quantiles", + "type": "timeseries" + } + ], + "title": "Sandbox TEI", + "type": "row" } ], + "refresh": "30s", "schemaVersion": 39, "tags": [], "templating": { @@ -428,38 +4150,11 @@ "from": "now-30m", "to": "now" }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", + "timepicker": {}, + "timezone": "browser", "title": "___DASHBOARD_TITLE___", "uid": "___DASHBOARD_UID___", - "version": 2, - "weekStart": "", - "overwrite": false, - "folderId": 0, - "message": "Imported by API" + "version": 1, + "weekStart": "" } } \ No newline at end of file diff --git a/studio-backend/app/templates/microsvc-composes/data-prep.yaml b/studio-backend/app/templates/microsvc-composes/data-prep.yaml index 69a86da..b1698e2 100644 --- a/studio-backend/app/templates/microsvc-composes/data-prep.yaml +++ b/studio-backend/app/templates/microsvc-composes/data-prep.yaml @@ -2,8 +2,10 @@ image: ${REGISTRY}/dataprep-redis:${TAG} container_name: "{{endpoint}}" depends_on: - - "{{redis_vector_store_endpoint}}" - - "{{tei_endpoint}}" + "{{redis_vector_store_endpoint}}": + condition: service_started + "{{tei_endpoint}}": + condition: service_healthy ports: - 6007:6007 environment: diff --git a/studio-backend/app/templates/microsvc-composes/embedding-usvc.yaml b/studio-backend/app/templates/microsvc-composes/embedding-usvc.yaml index 4720dce..7baf93b 100644 --- a/studio-backend/app/templates/microsvc-composes/embedding-usvc.yaml +++ b/studio-backend/app/templates/microsvc-composes/embedding-usvc.yaml @@ -1,10 +1,11 @@ "{{endpoint}}": - image: ${REGISTRY}/embedding-tei:${TAG} + image: ${REGISTRY}/embedding:${TAG} container_name: "{{endpoint}}" depends_on: - - "{{tei_endpoint}}" + "{{tei_endpoint}}": + condition: service_healthy ports: - - 6000:6000 + - 6009:6000 ipc: host environment: no_proxy: ${no_proxy} diff --git a/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml b/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml index 17b426f..2047713 100644 --- a/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml +++ b/studio-backend/app/templates/microsvc-composes/llm-uservice.yaml @@ -1,16 +1,17 @@ "{{endpoint}}": - image: ${REGISTRY}/llm-tgi:${TAG} + image: ${REGISTRY}/llm-textgen:${TAG} container_name: "{{endpoint}}" depends_on: - - "{{tgi_endpoint}}" + "{{tgi_endpoint}}": + condition: service_healthy ports: - - 9000:9000 + - 9009:9000 ipc: host environment: no_proxy: ${no_proxy} http_proxy: ${http_proxy} https_proxy: ${https_proxy} - TGI_LLM_ENDPOINT: "http://${public_host_ip}:{{tgi_port}}" + LLM_ENDPOINT: "http://${public_host_ip}:{{tgi_port}}" HUGGINGFACEHUB_API_TOKEN: "{{tgi_huggingFaceToken}}" HF_HUB_DISABLE_PROGRESS_BARS: 1 HF_HUB_ENABLE_HF_TRANSFER: 0 diff --git a/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml b/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml index 04c0b4c..b9c65c9 100644 --- a/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml +++ b/studio-backend/app/templates/microsvc-composes/reranking-usvc.yaml @@ -1,10 +1,11 @@ "{{endpoint}}": - image: ${REGISTRY}/reranking-tei:${TAG} + image: ${REGISTRY}/reranking:${TAG} container_name: "{{endpoint}}" depends_on: - - "{{tei_endpoint}}" + "{{tei_endpoint}}": + condition: service_healthy ports: - - 8000:8000 + - 8009:8000 ipc: host environment: no_proxy: ${no_proxy} diff --git a/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml b/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml index 648d402..11a1e74 100644 --- a/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml +++ b/studio-backend/app/templates/microsvc-composes/retriever-usvc.yaml @@ -1,11 +1,13 @@ "{{endpoint}}": - image: ${REGISTRY}/retriever-redis:${TAG} + image: ${REGISTRY}/retriever:${TAG} container_name: "{{endpoint}}" depends_on: - - "{{redis_vector_store_endpoint}}" - - "{{tei_endpoint}}" + "{{redis_vector_store_endpoint}}": + condition: service_started + "{{tei_endpoint}}": + condition: service_healthy ports: - - 7000:7000 + - 7009:7000 ipc: host environment: no_proxy: ${no_proxy} @@ -15,4 +17,6 @@ INDEX_NAME: "rag-redis" TEI_EMBEDDING_ENDPOINT: "http://${public_host_ip}:{{tei_port}}" HUGGINGFACEHUB_API_TOKEN: "{{tei_huggingFaceToken}}" + LOGFLAG: ${LOGFLAG} + RETRIEVER_COMPONENT_NAME: "OPEA_RETRIEVER_REDIS" restart: unless-stopped \ No newline at end of file diff --git a/studio-backend/app/templates/microsvc-composes/tei.yaml b/studio-backend/app/templates/microsvc-composes/tei.yaml index 71a1482..cb239ad 100644 --- a/studio-backend/app/templates/microsvc-composes/tei.yaml +++ b/studio-backend/app/templates/microsvc-composes/tei.yaml @@ -10,4 +10,9 @@ no_proxy: ${no_proxy} http_proxy: ${http_proxy} https_proxy: ${https_proxy} - command: --model-id {{modelName}} --auto-truncate \ No newline at end of file + entrypoint: /bin/sh -c "apt-get update && apt-get install -y curl && text-embeddings-router --json-output --model-id {{modelName}} --auto-truncate" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/health"] + interval: 30s + retries: 20 + timeout: 10s \ No newline at end of file diff --git a/studio-backend/app/templates/microsvc-composes/tgi.yaml b/studio-backend/app/templates/microsvc-composes/tgi.yaml index 6c603cc..1f31a28 100644 --- a/studio-backend/app/templates/microsvc-composes/tgi.yaml +++ b/studio-backend/app/templates/microsvc-composes/tgi.yaml @@ -13,4 +13,9 @@ HF_TOKEN: "{{huggingFaceToken}}" HF_HUB_DISABLE_PROGRESS_BARS: 1 HF_HUB_ENABLE_HF_TRANSFER: 0 - command: --model-id {{modelName}} --cuda-graphs 0 \ No newline at end of file + command: --model-id {{modelName}} --cuda-graphs 0 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80/health"] + interval: 30s + retries: 20 + timeout: 10s \ No newline at end of file diff --git a/studio-backend/app/templates/microsvc-manifests/data-prep.yaml b/studio-backend/app/templates/microsvc-manifests/data-prep.yaml index 41d50b0..92bbde6 100644 --- a/studio-backend/app/templates/microsvc-manifests/data-prep.yaml +++ b/studio-backend/app/templates/microsvc-manifests/data-prep.yaml @@ -8,6 +8,7 @@ kind: ConfigMap metadata: name: config-{endpoint} data: + HEALTHCHECK_ENDPOINT: "{tei_endpoint}" TEI_ENDPOINT: "http://{tei_endpoint}" EMBED_MODEL: "" REDIS_URL: "redis://{redis_vector_store_endpoint}:{redis_vector_store_port}" @@ -61,6 +62,13 @@ spec: spec: securityContext: {} + initContainers: + - name: wait-for-remote-service + image: busybox + command: ['sh', '-c', 'until nc -z -v -w30 $HEALTHCHECK_ENDPOINT 80; do echo "Waiting for remote service..."; sleep 5; done'] + envFrom: + - configMapRef: + name: config-{endpoint} containers: - name: data-prep envFrom: @@ -77,7 +85,7 @@ spec: seccompProfile: type: RuntimeDefault image: "${REGISTRY}/dataprep-redis:${TAG}" - imagePullPolicy: IfNotPresent + imagePullPolicy: Always ports: - name: data-prep containerPort: 6007 diff --git a/studio-backend/app/templates/microsvc-manifests/embedding-usvc.yaml b/studio-backend/app/templates/microsvc-manifests/embedding-usvc.yaml index efbe8b6..a0c96ba 100644 --- a/studio-backend/app/templates/microsvc-manifests/embedding-usvc.yaml +++ b/studio-backend/app/templates/microsvc-manifests/embedding-usvc.yaml @@ -8,6 +8,7 @@ kind: ConfigMap metadata: name: config-{endpoint} data: + HEALTHCHECK_ENDPOINT: "{tei_endpoint}" TEI_EMBEDDING_ENDPOINT: "http://{tei_endpoint}" http_proxy: "" https_proxy: "" @@ -54,6 +55,13 @@ spec: spec: securityContext: {} + initContainers: + - name: wait-for-remote-service + image: busybox + command: ['sh', '-c', 'until nc -z -v -w30 $HEALTHCHECK_ENDPOINT 80; do echo "Waiting for remote service..."; sleep 5; done'] + envFrom: + - configMapRef: + name: config-{endpoint} containers: - name: embedding-usvc envFrom: @@ -69,8 +77,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: "${REGISTRY}/embedding-tei:${TAG}" - imagePullPolicy: IfNotPresent + image: "${REGISTRY}/embedding:${TAG}" + imagePullPolicy: Always ports: - name: embedding-usvc containerPort: 6000 diff --git a/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml b/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml index f9017c3..b8b7b98 100644 --- a/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml +++ b/studio-backend/app/templates/microsvc-manifests/llm-uservice.yaml @@ -8,7 +8,8 @@ kind: ConfigMap metadata: name: config-{endpoint} data: - TGI_LLM_ENDPOINT: "http://{tgi_endpoint}" + HEALTHCHECK_ENDPOINT: "{tgi_endpoint}" + LLM_ENDPOINT: "http://{tgi_endpoint}" HUGGINGFACEHUB_API_TOKEN: "{tgi_huggingFaceToken}" HF_HOME: "/tmp/.cache/huggingface" http_proxy: "${HTTP_PROXY}" @@ -56,6 +57,13 @@ spec: spec: securityContext: {} + initContainers: + - name: wait-for-remote-service + image: busybox + command: ['sh', '-c', 'until nc -z -v -w30 $HEALTHCHECK_ENDPOINT 80; do echo "Waiting for remote service..."; sleep 5; done'] + envFrom: + - configMapRef: + name: config-{endpoint} containers: - name: llm-uservice envFrom: @@ -71,8 +79,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: "${REGISTRY}/llm-tgi:${TAG}" - imagePullPolicy: IfNotPresent + image: "${REGISTRY}/llm-textgen:${TAG}" + imagePullPolicy: Always ports: - name: llm-uservice containerPort: 9000 diff --git a/studio-backend/app/templates/microsvc-manifests/readme.MD b/studio-backend/app/templates/microsvc-manifests/readme.MD deleted file mode 100644 index 61a51cb..0000000 --- a/studio-backend/app/templates/microsvc-manifests/readme.MD +++ /dev/null @@ -1,17 +0,0 @@ -## Template Manifests - -The manifest files in this directory were sourced from the following GitHub repository: - -- **Repository**: [opea-project/GenAIEval](https://github.com/opea-project/GenAIInfra) -- **Commit ID**: `325126e30a30790305d10646372f32c6af52fed8` -- **Date Copied**: 2024-09-25 - -These files are used as templates for generating new manifests within our application. - -## Modifications - -Modification: Updated templates with variables - -## License - -The original files were distributed under the [Apache-2.0 License](https://opensource.org/licenses/Apache-2.0). A copy of the license can be found in the LICENSE file at the root of the original repository. \ No newline at end of file diff --git a/studio-backend/app/templates/microsvc-manifests/reranking-usvc.yaml b/studio-backend/app/templates/microsvc-manifests/reranking-usvc.yaml index fa2871a..2191348 100644 --- a/studio-backend/app/templates/microsvc-manifests/reranking-usvc.yaml +++ b/studio-backend/app/templates/microsvc-manifests/reranking-usvc.yaml @@ -8,6 +8,7 @@ kind: ConfigMap metadata: name: config-{endpoint} data: + HEALTHCHECK_ENDPOINT: "{tei_endpoint}" TEI_RERANKING_ENDPOINT: "http://{tei_endpoint}" http_proxy: "" https_proxy: "" @@ -54,6 +55,13 @@ spec: spec: securityContext: {} + initContainers: + - name: wait-for-remote-service + image: busybox + command: ['sh', '-c', 'until nc -z -v -w30 $HEALTHCHECK_ENDPOINT 80; do echo "Waiting for remote service..."; sleep 5; done'] + envFrom: + - configMapRef: + name: config-{endpoint} containers: - name: reranking-usvc envFrom: @@ -69,8 +77,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: "${REGISTRY}/reranking-tei:${TAG}" - imagePullPolicy: IfNotPresent + image: "${REGISTRY}/reranking:${TAG}" + imagePullPolicy: Always ports: - name: reranking-usvc containerPort: 8000 diff --git a/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml b/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml index 7b89d73..55b5dcb 100644 --- a/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml +++ b/studio-backend/app/templates/microsvc-manifests/retriever-usvc.yaml @@ -8,6 +8,7 @@ kind: ConfigMap metadata: name: config-{endpoint} data: + HEALTHCHECK_ENDPOINT: "{tei_endpoint}" TEI_EMBEDDING_ENDPOINT: "http://{tei_endpoint}" EMBED_MODEL: "" REDIS_URL: "redis://{redis_vector_store_endpoint}:{redis_vector_store_port}" @@ -19,6 +20,7 @@ data: HF_HOME: "/tmp/.cache/huggingface" HUGGINGFACEHUB_API_TOKEN: "{tei_huggingFaceToken}" LOGFLAG: "" + RETRIEVER_COMPONENT_NAME: "OPEA_RETRIEVER_REDIS" --- # Source: retriever-usvc/templates/service.yaml # Copyright (C) 2024 Intel Corporation @@ -60,6 +62,13 @@ spec: spec: securityContext: {} + initContainers: + - name: wait-for-remote-service + image: busybox + command: ['sh', '-c', 'until nc -z -v -w30 $HEALTHCHECK_ENDPOINT 80; do echo "Waiting for remote service..."; sleep 5; done'] + envFrom: + - configMapRef: + name: config-{endpoint} containers: - name: retriever-usvc envFrom: @@ -75,8 +84,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: "${REGISTRY}/retriever-redis:${TAG}" - imagePullPolicy: IfNotPresent + image: "${REGISTRY}/retriever:${TAG}" + imagePullPolicy: Always ports: - name: retriever-usvc containerPort: 7000 diff --git a/studio-backend/app/utils/placeholders_utils.py b/studio-backend/app/utils/placeholders_utils.py index c4afa29..c37e2b8 100644 --- a/studio-backend/app/utils/placeholders_utils.py +++ b/studio-backend/app/utils/placeholders_utils.py @@ -38,7 +38,7 @@ def replace_manifest_placeholders(obj, variables): if isinstance(obj, dict): for key, value in obj.items(): # Skip nginx.conf as it contains {} that will clashe with .format() - if key == "default.conf" or key == "project-info.json": + if key == "default.conf" or key == "workflow-info.json": continue if isinstance(value, str): # Replace ${REGISTRY} and ${TAG} with the value from environment variables @@ -67,12 +67,12 @@ def replace_dynamic_manifest_placeholder(value_str, service_info, proj_info_json if service_info['service_type'] == 'app': ui_env_config_info_str = "" ui_nginx_config_info_str = "" - backend_project_info_str = "" + backend_workflow_info_str = "" for key, value in service_info['ui_config_info'].items(): # For __UI_CONFIG_INFO_ENV_PLACEHOLDER__ url_name = value['url_name'] endpoint_path = value['endpoint_path'] - env_block = f"{indent_str}- name: {url_name}\n{indent_str} value: {endpoint_path}\n" + env_block = f"{indent_str}- name: VITE_{url_name}\n{indent_str} value: {endpoint_path}\n" ui_env_config_info_str += env_block # For __UI_CONFIG_INFO_ENV_PLACEHOLDER__ @@ -91,7 +91,7 @@ def replace_dynamic_manifest_placeholder(value_str, service_info, proj_info_json ui_nginx_config_info_str += indented_location_block # For __BACKEND_PROJECT_INFO_JSON_PLACEHOLDER__ - backend_project_info_str = json.dumps(proj_info_json, indent=4) + backend_workflow_info_str = json.dumps(proj_info_json, indent=4) # Get app images from environment variables app_frontend_image = os.getenv("APP_FRONTEND_IMAGE", "opea/app-frontend:latest") @@ -100,7 +100,7 @@ def replace_dynamic_manifest_placeholder(value_str, service_info, proj_info_json # Replace the unique placeholders with the actual strings final_config = value_str.replace("__UI_CONFIG_INFO_ENV_PLACEHOLDER__", ui_env_config_info_str.strip()).replace( "__UI_CONFIG_INFO_NGINX_PLACEHOLDER__", ui_nginx_config_info_str.strip()).replace( - "__BACKEND_PROJECT_INFO_JSON_PLACEHOLDER__", backend_project_info_str.replace(f"\n", f"\n{indent_str}")).replace( + "__BACKEND_PROJECT_INFO_JSON_PLACEHOLDER__", backend_workflow_info_str.replace(f"\n", f"\n{indent_str}")).replace( "__APP_FRONTEND_IMAGE__", app_frontend_image).replace( "__APP_BACKEND_IMAGE__", app_backend_image) @@ -144,7 +144,7 @@ def replace_dynamic_compose_placeholder(value_str, service_info): for _, value in service_info['ui_config_info'].items(): url_name = value['url_name'] endpoint_path = value['endpoint_path'] - endpoint_block = f"{indent_str} - {url_name}={endpoint_path}\n" + endpoint_block = f"{indent_str} - VITE_{url_name}={endpoint_path}\n" ui_env_config_info_str += endpoint_block # Get app images from environment variables diff --git a/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml b/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml index c9bcbaa..0cc47fb 100644 --- a/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml +++ b/studio-backend/tests/exporter-groundtruth/gt_app-compose.yaml @@ -64,7 +64,7 @@ services: https_proxy: ${https_proxy} command: --model-id BAAI/bge-base-en-v1.5 --auto-truncate embedding-tei-langchain-0: - image: opea/embedding-tei:latest + image: opea/embedding:latest container_name: embedding-tei-langchain-0 depends_on: - tei-0 @@ -78,7 +78,7 @@ services: TEI_EMBEDDING_ENDPOINT: http://${public_host_ip}:2081 restart: unless-stopped llm-tgi-0: - image: opea/llm-tgi:latest + image: opea/llm-textgen:latest container_name: llm-tgi-0 depends_on: - tgi-0 @@ -112,7 +112,7 @@ services: TEI_ENDPOINT: http://${public_host_ip}:2081 HUGGINGFACEHUB_API_TOKEN: NA reranking-tei-0: - image: opea/reranking-tei:latest + image: opea/reranking:latest container_name: reranking-tei-0 depends_on: - tei-1 @@ -129,7 +129,7 @@ services: HF_HUB_ENABLE_HF_TRANSFER: 0 restart: unless-stopped retriever-redis-0: - image: opea/retriever-redis:latest + image: opea/retriever:latest container_name: retriever-redis-0 depends_on: - redis-vector-store-0 @@ -150,7 +150,7 @@ services: image: opea/app-backend:latest container_name: app-backend volumes: - - ./project-info.json:/home/user/config/project-info.json + - ./workflow-info.json:/home/user/config/workflow-info.json depends_on: - redis-vector-store-0 - tei-0 diff --git a/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml b/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml index 4ab3214..a3d64d6 100644 --- a/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml +++ b/studio-backend/tests/exporter-groundtruth/gt_app-manifest-with-nginx.yaml @@ -180,9 +180,8 @@ spec: resources: {} volumes: - name: model-volume - hostPath: - path: /mnt/opea-models - type: Directory + persistentVolumeClaim: + claimName: model-pvc - name: shm emptyDir: medium: Memory @@ -285,9 +284,8 @@ spec: resources: {} volumes: - name: model-volume - hostPath: - path: /mnt/opea-models - type: Directory + persistentVolumeClaim: + claimName: model-pvc - name: shm emptyDir: medium: Memory @@ -395,9 +393,8 @@ spec: resources: {} volumes: - name: model-volume - hostPath: - path: /mnt/opea-models - type: Directory + persistentVolumeClaim: + claimName: model-pvc - name: shm emptyDir: medium: Memory @@ -505,9 +502,8 @@ spec: resources: {} volumes: - name: model-volume - hostPath: - path: /mnt/opea-models - type: Directory + persistentVolumeClaim: + claimName: model-pvc - name: shm emptyDir: medium: Memory @@ -579,8 +575,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/embedding-tei:latest - imagePullPolicy: IfNotPresent + image: opea/embedding:latest + imagePullPolicy: Always ports: - name: embedding-usvc containerPort: 6000 @@ -678,8 +674,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/llm-tgi:latest - imagePullPolicy: IfNotPresent + image: opea/llm-textgen:latest + imagePullPolicy: Always ports: - name: llm-uservice containerPort: 9000 @@ -783,7 +779,7 @@ spec: seccompProfile: type: RuntimeDefault image: opea/dataprep-redis:latest - imagePullPolicy: IfNotPresent + imagePullPolicy: Always ports: - name: data-prep containerPort: 6007 @@ -879,8 +875,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/reranking-tei:latest - imagePullPolicy: IfNotPresent + image: opea/reranking:latest + imagePullPolicy: Always ports: - name: reranking-usvc containerPort: 8000 @@ -982,8 +978,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/retriever-redis:latest - imagePullPolicy: IfNotPresent + image: opea/retriever:latest + imagePullPolicy: Always ports: - name: retriever-usvc containerPort: 7000 @@ -1023,7 +1019,7 @@ kind: ConfigMap metadata: name: app-backend-config data: - project-info.json: | + workflow-info.json: | { "chat_completion_ids": [ "chat_completion_0" @@ -1043,6 +1039,7 @@ data: "opea_service@llm_tgi_0" ], "connected_to": [], + "dependent_services": null, "hideOutput": true, "id": "chat_completion_0", "inMegaservice": false, @@ -1057,6 +1054,8 @@ data: "connected_to": [ "opea_service@embedding_tei_langchain_0" ], + "dependent_services": null, + "hideOutput": null, "id": "chat_input_0", "inMegaservice": false, "inference_params": {}, @@ -1070,6 +1069,8 @@ data: "connected_to": [ "opea_service@prepare_doc_redis_prep_0" ], + "dependent_services": null, + "hideOutput": null, "id": "doc_input_0", "inMegaservice": false, "inference_params": {}, @@ -1093,6 +1094,7 @@ data: "modelName": "BAAI/bge-large-en-v1.5" } }, + "hideOutput": null, "id": "opea_service@embedding_tei_langchain_0", "inMegaservice": true, "inference_params": {}, @@ -1116,16 +1118,17 @@ data: "modelName": "Intel/neural-chat-7b-v3-3" } }, + "hideOutput": null, "id": "opea_service@llm_tgi_0", "inMegaservice": true, "inference_params": { "chat_template": "### You are a helpful, respectful and honest assistant to help the user with questions.\n### Context: {context}\n### Question: {question}\n### Answer:", - "frequency_penalty": "", - "max_tokens": 17, + "frequency_penalty": 0, + "max_tokens": 17.0, "presence_penalty": 1.03, - "streaming": true, + "streaming": "True", "temperature": 0.01, - "top_k": 10, + "top_k": 10.0, "top_p": 0.95, "typical_p": 0.95 }, @@ -1149,6 +1152,7 @@ data: "modelName": "BAAI/bge-large-en-v1.5" } }, + "hideOutput": null, "id": "opea_service@prepare_doc_redis_prep_0", "inMegaservice": false, "inference_params": {}, @@ -1172,10 +1176,11 @@ data: "modelName": "BAAI/bge-reranker-base" } }, + "hideOutput": null, "id": "opea_service@reranking_tei_0", "inMegaservice": true, "inference_params": { - "top_n": 1 + "top_n": 1.0 }, "name": "opea_service@reranking_tei", "params": {}, @@ -1198,6 +1203,7 @@ data: "modelName": "BAAI/bge-base-en-v1.5" } }, + "hideOutput": null, "id": "opea_service@retriever_redis_0", "inMegaservice": true, "inference_params": { @@ -1215,6 +1221,8 @@ data: "connected_to": [ "opea_service@retriever_redis_0" ], + "dependent_services": null, + "hideOutput": null, "id": "redis_vector_store_0", "inMegaservice": true, "inference_params": {}, @@ -1281,13 +1289,13 @@ spec: seccompProfile: type: RuntimeDefault image: opea/app-backend:latest - imagePullPolicy: IfNotPresent + imagePullPolicy: Always volumeMounts: - mountPath: /tmp name: tmp - - mountPath: /home/user/config/project-info.json - name: project-info-volume - subPath: project-info.json + - mountPath: /home/user/config/workflow-info.json + name: workflow-info-volume + subPath: workflow-info.json ports: - name: app-backend containerPort: 8888 @@ -1296,7 +1304,7 @@ spec: volumes: - name: tmp emptyDir: {} - - name: project-info-volume + - name: workflow-info-volume configMap: name: app-backend-config --- @@ -1405,6 +1413,13 @@ data: proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + + # Disable buffering for SSE + proxy_buffering off; + proxy_cache off; + proxy_http_version 1.1; + proxy_set_header Connection ''; + chunked_transfer_encoding off; } location /v1/embeddings { @@ -1493,3 +1508,17 @@ spec: defaultMode: 420 name: app-nginx-config name: nginx-config-volume +--- +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: model-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi + storageClassName: local-path \ No newline at end of file diff --git a/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml b/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml index 2116558..5f8d992 100644 --- a/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml +++ b/studio-backend/tests/exporter-groundtruth/gt_app-manifest.yaml @@ -579,8 +579,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/embedding-tei:latest - imagePullPolicy: IfNotPresent + image: opea/embedding:latest + imagePullPolicy: Always ports: - name: embedding-usvc containerPort: 6000 @@ -678,8 +678,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/llm-tgi:latest - imagePullPolicy: IfNotPresent + image: opea/llm-textgen:latest + imagePullPolicy: Always ports: - name: llm-uservice containerPort: 9000 @@ -783,7 +783,7 @@ spec: seccompProfile: type: RuntimeDefault image: opea/dataprep-redis:latest - imagePullPolicy: IfNotPresent + imagePullPolicy: Always ports: - name: data-prep containerPort: 6007 @@ -879,8 +879,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/reranking-tei:latest - imagePullPolicy: IfNotPresent + image: opea/reranking:latest + imagePullPolicy: Always ports: - name: reranking-usvc containerPort: 8000 @@ -982,8 +982,8 @@ spec: runAsUser: 1000 seccompProfile: type: RuntimeDefault - image: opea/retriever-redis:latest - imagePullPolicy: IfNotPresent + image: opea/retriever:latest + imagePullPolicy: Always ports: - name: retriever-usvc containerPort: 7000 @@ -1023,7 +1023,7 @@ kind: ConfigMap metadata: name: app-backend-config data: - project-info.json: | + workflow-info.json: | { "chat_completion_ids": [ "chat_completion_0" @@ -1281,13 +1281,13 @@ spec: seccompProfile: type: RuntimeDefault image: opea/app-backend:latest - imagePullPolicy: IfNotPresent + imagePullPolicy: Always volumeMounts: - mountPath: /tmp name: tmp - - mountPath: /home/user/config/project-info.json - name: project-info-volume - subPath: project-info.json + - mountPath: /home/user/config/workflow-info.json + name: workflow-info-volume + subPath: workflow-info.json ports: - name: app-backend containerPort: 8888 @@ -1296,7 +1296,7 @@ spec: volumes: - name: tmp emptyDir: {} - - name: project-info-volume + - name: workflow-info-volume configMap: name: app-backend-config --- @@ -1364,3 +1364,17 @@ spec: volumes: - name: tmp emptyDir: {} +--- +# Copyright (C) 2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: model-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 50Gi + storageClassName: local-path \ No newline at end of file diff --git a/studio-backend/tests/flowise-pipeline-translator/project-info.json b/studio-backend/tests/flowise-pipeline-translator/workflow-info.json similarity index 100% rename from studio-backend/tests/flowise-pipeline-translator/project-info.json rename to studio-backend/tests/flowise-pipeline-translator/workflow-info.json diff --git a/studio-backend/tests/test_compose-exporter.py b/studio-backend/tests/test_compose-exporter.py index 9ee5f71..6bf8c76 100644 --- a/studio-backend/tests/test_compose-exporter.py +++ b/studio-backend/tests/test_compose-exporter.py @@ -11,7 +11,7 @@ def setup_and_teardown(): test_dir = os.path.dirname(os.path.abspath(__file__)) # Paths for the `mega.yaml` and output file - proj_info_file = os.path.join(test_dir, "flowise-pipeline-translator", "project-info.json") + proj_info_file = os.path.join(test_dir, "flowise-pipeline-translator", "workflow-info.json") output_file = os.path.join(test_dir, "exporter-groundtruth", "app-compose.yaml") gt_file = os.path.join(test_dir, "exporter-groundtruth", "gt_app-compose.yaml") diff --git a/studio-backend/tests/test_download-zip.py b/studio-backend/tests/test_download-zip.py index 7ee632a..765e096 100644 --- a/studio-backend/tests/test_download-zip.py +++ b/studio-backend/tests/test_download-zip.py @@ -55,4 +55,4 @@ def test_create_and_download_zip(setup_and_teardown): assert 'docker-compose/.env' in zipf.namelist() assert 'docker-compose/readme.MD' in zipf.namelist() assert 'docker-compose/compose.yaml' in zipf.namelist() - assert 'docker-compose/project-info.json' in zipf.namelist() \ No newline at end of file + assert 'docker-compose/workflow-info.json' in zipf.namelist() \ No newline at end of file diff --git a/studio-backend/tests/test_flowise-pipeline-translator.py b/studio-backend/tests/test_flowise-pipeline-translator.py index bad3499..7c9a9d4 100644 --- a/studio-backend/tests/test_flowise-pipeline-translator.py +++ b/studio-backend/tests/test_flowise-pipeline-translator.py @@ -7,7 +7,7 @@ import json -from app.services.project_info_service import ProjectInfo +from app.services.workflow_info_service import WorkflowInfo class TestFlowisePipelineTranslator(unittest.TestCase): @@ -21,9 +21,9 @@ def setUp(self): def test_flowise_pipeline_translator(self): # Call the function directly - print("converting flowise_pipeline to project_info") - project_info = ProjectInfo(json.loads(self.pipeline_json)) - print('project_info', project_info.export_to_json()) + print("converting flowise_pipeline to workflow_info") + workflow_info = WorkflowInfo(json.loads(self.pipeline_json)) + print('workflow_info', workflow_info.export_to_json()) self.assertTrue = True diff --git a/studio-backend/tests/test_manifest-exporter.py b/studio-backend/tests/test_manifest-exporter.py index 0808f94..7de37af 100644 --- a/studio-backend/tests/test_manifest-exporter.py +++ b/studio-backend/tests/test_manifest-exporter.py @@ -12,7 +12,7 @@ def setup_and_teardown(): test_dir = os.path.dirname(os.path.abspath(__file__)) # Paths for the `mega.yaml` and output file - proj_info_file = os.path.join(test_dir, "flowise-pipeline-translator", "project-info.json") + proj_info_file = os.path.join(test_dir, "flowise-pipeline-translator", "workflow-info.json") output_file = os.path.join(test_dir, "exporter-groundtruth", "app-manifest.yaml") gt_file = os.path.join(test_dir, "exporter-groundtruth", "gt_app-manifest.yaml") gt_nginx_file = os.path.join(test_dir, "exporter-groundtruth", "gt_app-manifest-with-nginx.yaml") @@ -23,7 +23,7 @@ def setup_and_teardown(): os.unlink(output_file) def test_convert_chatqna_proj_info_to_manifest_obj(setup_and_teardown): - proj_info_file, _, gt_file, _ = setup_and_teardown + proj_info_file, output_file, gt_file, _ = setup_and_teardown with open(proj_info_file, "r") as file: proj_info = json.load(file) @@ -39,9 +39,12 @@ def create_identifiers_set(documents): identifiers.add((doc['kind'], doc['metadata']['name'])) return identifiers - manifest_dict = yaml.safe_load_all(output_manifest) + manifest_dict = list(yaml.safe_load_all(output_manifest)) output_identifiers = create_identifiers_set(manifest_dict) + with open(output_file, "w") as f: + yaml.dump_all(manifest_dict, f) + # Load the documents from the gt manifest with open(gt_file, 'r') as f: gt_manifest_docs = list(yaml.safe_load_all(f)) diff --git a/studio-frontend/packages/server/.gitignore b/studio-frontend/packages/server/.gitignore new file mode 100644 index 0000000..b3ab1ae --- /dev/null +++ b/studio-frontend/packages/server/.gitignore @@ -0,0 +1,6 @@ +.idea/ +.vscode/ +node_modules/ +build/ +tmp/ +temp/ \ No newline at end of file diff --git a/studio-frontend/packages/server/bin/run b/studio-frontend/packages/server/bin/run old mode 100644 new mode 100755 diff --git a/studio-frontend/packages/server/src/DataSource.ts b/studio-frontend/packages/server/src/DataSource.ts index 811f62b..5ac39bc 100644 --- a/studio-frontend/packages/server/src/DataSource.ts +++ b/studio-frontend/packages/server/src/DataSource.ts @@ -78,6 +78,7 @@ export const init = async (): Promise => { break default: homePath = process.env.DATABASE_PATH ?? flowisePath + console.log ("***", path.resolve(homePath, 'database.sqlite')) appDataSource = new DataSource({ type: 'sqlite', database: path.resolve(homePath, 'database.sqlite'), @@ -104,7 +105,18 @@ const getDatabaseSSLFromEnv = () => { ca: Buffer.from(process.env.DATABASE_SSL_KEY_BASE64, 'base64') } } else if (process.env.DATABASE_SSL === 'true') { - return true + // return true + try { + return { + rejectUnauthorized: false, + ca: fs.readFileSync('/etc/mysql/ssl/ca.pem'), + cert: fs.readFileSync('/etc/mysql/ssl/client-cert.pem'), + key: fs.readFileSync('/etc/mysql/ssl/client-key.pem') + }; + } catch (error) { + console.error('Error reading certificates from mounted path:', error); + return undefined; + } } return undefined } diff --git a/studio-frontend/packages/server/src/Interface.ts b/studio-frontend/packages/server/src/Interface.ts index 2da6787..228ddc3 100644 --- a/studio-frontend/packages/server/src/Interface.ts +++ b/studio-frontend/packages/server/src/Interface.ts @@ -21,6 +21,7 @@ export enum ChatMessageRatingType { export interface IChatFlow { id: string name: string + userid: string flowData: string updatedDate: Date createdDate: Date diff --git a/studio-frontend/packages/server/src/controllers/chatflows/index.ts b/studio-frontend/packages/server/src/controllers/chatflows/index.ts index 3c91449..5b33000 100644 --- a/studio-frontend/packages/server/src/controllers/chatflows/index.ts +++ b/studio-frontend/packages/server/src/controllers/chatflows/index.ts @@ -58,6 +58,24 @@ const getAllChatflows = async (req: Request, res: Response, next: NextFunction) } } +const getAllChatflowsbyUserId = async (req: Request, res: Response, next: NextFunction) => { + try { + const apiResponse = await chatflowsService.getAllChatflowsbyUserId(req.query.userid as string, req.query.type as ChatflowType) + return res.json(apiResponse) + } catch (error) { + next(error) + } +} + +const importSampleChatflowsbyUserId = async (req: Request, res: Response, next: NextFunction) => { + try { + const apiResponse = await chatflowsService.importSampleChatflowsbyUserId(req.query.userid as string, req.query.type as ChatflowType) + return res.json(apiResponse) + } catch (error) { + next(error) + } +} + // Get specific chatflow via api key const getChatflowByApiKey = async (req: Request, res: Response, next: NextFunction) => { try { @@ -98,6 +116,7 @@ const saveChatflow = async (req: Request, res: Response, next: NextFunction) => const body = req.body const newChatFlow = new ChatFlow() Object.assign(newChatFlow, body) + console.log ('newChatFlow', newChatFlow) const apiResponse = await chatflowsService.saveChatflow(newChatFlow) return res.json(apiResponse) } catch (error) { @@ -257,6 +276,8 @@ export default { checkIfChatflowIsValidForUploads, deleteChatflow, getAllChatflows, + getAllChatflowsbyUserId, + importSampleChatflowsbyUserId, getChatflowByApiKey, getChatflowById, saveChatflow, diff --git a/studio-frontend/packages/server/src/database/entities/ChatFlow.ts b/studio-frontend/packages/server/src/database/entities/ChatFlow.ts index f86caf2..122d097 100644 --- a/studio-frontend/packages/server/src/database/entities/ChatFlow.ts +++ b/studio-frontend/packages/server/src/database/entities/ChatFlow.ts @@ -10,6 +10,9 @@ export class ChatFlow implements IChatFlow { @Column() name: string + @Column({ nullable: true, type: 'text' }) + userid: string + @Column({ type: 'text' }) flowData: string diff --git a/studio-frontend/packages/server/src/database/migrations/mysql/1693840429259-Init.ts b/studio-frontend/packages/server/src/database/migrations/mysql/1693840429259-Init.ts index 9d07206..4e7e8f8 100644 --- a/studio-frontend/packages/server/src/database/migrations/mysql/1693840429259-Init.ts +++ b/studio-frontend/packages/server/src/database/migrations/mysql/1693840429259-Init.ts @@ -6,11 +6,15 @@ export class Init1693840429259 implements MigrationInterface { `CREATE TABLE IF NOT EXISTS \`chat_flow\` ( \`id\` varchar(36) NOT NULL, \`name\` varchar(255) NOT NULL, + \`userid\` varchar(255) DEFAULT NULL, \`flowData\` text NOT NULL, \`deployed\` tinyint DEFAULT NULL, \`isPublic\` tinyint DEFAULT NULL, \`apikeyid\` varchar(255) DEFAULT NULL, \`chatbotConfig\` varchar(255) DEFAULT NULL, + \`sandboxStatus\` varchar(255) DEFAULT NULL, + \`sandboxAppUrl\` varchar(255) DEFAULT NULL, + \`sandboxGrafanaUrl\` varchar(255) DEFAULT NULL, \`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), PRIMARY KEY (\`id\`) diff --git a/studio-frontend/packages/server/src/database/migrations/sqlite/1732778337650-OPEAAddUserIdtoChatFlow.ts b/studio-frontend/packages/server/src/database/migrations/sqlite/1732778337650-OPEAAddUserIdtoChatFlow.ts new file mode 100644 index 0000000..b802061 --- /dev/null +++ b/studio-frontend/packages/server/src/database/migrations/sqlite/1732778337650-OPEAAddUserIdtoChatFlow.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class OPEAAddUserIdtoChatFlow1732778337650 implements MigrationInterface { + name = 'OPEAAddUserIdtoChatFlow1732778337650' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "temporary_chat_flow" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "flowData" text NOT NULL, "deployed" boolean, "isPublic" boolean, "apikeyid" varchar, "chatbotConfig" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "updatedDate" datetime NOT NULL DEFAULT (datetime('now')), "sandboxStatus" text, "sandboxAppUrl" text, "sandboxGrafanaUrl" text, "apiConfig" text, "analytic" text, "category" text, "speechToText" text, "type" text, "userid" text)`); + await queryRunner.query(`INSERT INTO "temporary_chat_flow"("id", "name", "flowData", "deployed", "isPublic", "apikeyid", "chatbotConfig", "createdDate", "updatedDate", "sandboxStatus", "sandboxAppUrl", "sandboxGrafanaUrl", "apiConfig", "analytic", "category", "speechToText", "type", "userid") SELECT "id", "name", "flowData", "deployed", "isPublic", "apikeyid", "chatbotConfig", "createdDate", "updatedDate", "sandboxStatus", "sandboxAppUrl", "sandboxGrafanaUrl", "apiConfig", "analytic", "category", "speechToText", "type", "userid" FROM "chat_flow"`); + await queryRunner.query(`DROP TABLE "chat_flow"`); + await queryRunner.query(`ALTER TABLE "temporary_chat_flow" RENAME TO "chat_flow"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "chat_flow" RENAME TO "temporary_chat_flow"`); + await queryRunner.query(`CREATE TABLE "chat_flow" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "flowData" text NOT NULL, "deployed" text, "isPublic" boolean, "apikeyid" varchar, "chatbotConfig" text, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "updatedDate" datetime NOT NULL DEFAULT (datetime('now')), "sandboxStatus" text, "sandboxAppUrl" text, "sandboxGrafanaUrl" text, "apiConfig" text, "analytic" text, "category" text, "speechToText" text, "type" text, "userid" varchar NOT NULL)`); + await queryRunner.query(`INSERT INTO "chat_flow"("id", "name", "flowData", "deployed", "isPublic", "apikeyid", "chatbotConfig", "createdDate", "updatedDate", "sandboxStatus", "sandboxAppUrl", "sandboxGrafanaUrl", "apiConfig", "analytic", "category", "speechToText", "type", "userid") SELECT "id", "name", "flowData", "deployed", "isPublic", "apikeyid", "chatbotConfig", "createdDate", "updatedDate", "sandboxStatus", "sandboxAppUrl", "sandboxGrafanaUrl", "apiConfig", "analytic", "category", "speechToText", "type", "userid" FROM "temporary_chat_flow"`); + await queryRunner.query(`DROP TABLE "temporary_chat_flow"`); + } + +} diff --git a/studio-frontend/packages/server/src/database/migrations/sqlite/index.ts b/studio-frontend/packages/server/src/database/migrations/sqlite/index.ts index 207dc0c..67401e6 100644 --- a/studio-frontend/packages/server/src/database/migrations/sqlite/index.ts +++ b/studio-frontend/packages/server/src/database/migrations/sqlite/index.ts @@ -26,6 +26,8 @@ import { AddActionToChatMessage1721078251523 } from './1721078251523-AddActionTo import { AddArtifactsToChatMessage1726156258465 } from './1726156258465-AddArtifactsToChatMessage' import { AddCustomTemplate1725629836652 } from './1725629836652-AddCustomTemplate' import { OPEAAddSandboxStatustoChatFlow1727419719000 } from './1727419719000-OPEAAddSandboxStatustoChatFlow' +import { OPEAAddUserIdtoChatFlow1732778337650 } from './1732778337650-OPEAAddUserIdtoChatFlow' + export const sqliteMigrations = [ Init1693835579790, ModifyChatFlow1693920824108, @@ -54,5 +56,6 @@ export const sqliteMigrations = [ AddActionToChatMessage1721078251523, AddArtifactsToChatMessage1726156258465, AddCustomTemplate1725629836652, - OPEAAddSandboxStatustoChatFlow1727419719000 + OPEAAddSandboxStatustoChatFlow1727419719000, + OPEAAddUserIdtoChatFlow1732778337650, ] diff --git a/studio-frontend/packages/server/src/routes/chatflows/index.ts b/studio-frontend/packages/server/src/routes/chatflows/index.ts index b0c5350..495548b 100644 --- a/studio-frontend/packages/server/src/routes/chatflows/index.ts +++ b/studio-frontend/packages/server/src/routes/chatflows/index.ts @@ -4,10 +4,11 @@ const router = express.Router() // CREATE router.post('/', chatflowsController.saveChatflow) +router.post('/importsamples', chatflowsController.importSampleChatflowsbyUserId) router.post('/importchatflows', chatflowsController.importChatflows) // READ -router.get('/', chatflowsController.getAllChatflows) +router.get('/', chatflowsController.getAllChatflowsbyUserId) router.get(['/', '/:id'], chatflowsController.getChatflowById) router.get(['/apikey/', '/apikey/:apikey'], chatflowsController.getChatflowByApiKey) diff --git a/studio-frontend/packages/server/src/services/chatflows/index.ts b/studio-frontend/packages/server/src/services/chatflows/index.ts index 00b674c..f0d80b4 100644 --- a/studio-frontend/packages/server/src/services/chatflows/index.ts +++ b/studio-frontend/packages/server/src/services/chatflows/index.ts @@ -134,6 +134,65 @@ const getAllChatflows = async (type?: ChatflowType): Promise => { } } +const getAllChatflowsbyUserId = async (userid: string, type?: ChatflowType): Promise => { + try { + const appServer = getRunningExpressApp() + + // Use find with a where condition to filter by userid + const dbResponse = await appServer.AppDataSource.getRepository(ChatFlow).find({ + where: { + userid: userid, // Filter by the specific userid + }, + }) + + // Filter further by type if needed + if (type) { + return dbResponse.filter((chatflow) => chatflow.type === type) + } + + return dbResponse + } catch (error) { + throw new InternalFlowiseError( + StatusCodes.INTERNAL_SERVER_ERROR, + `Error: chatflowsService.getAllChatflows - ${getErrorMessage(error)}` + ) + } +} + +const importSampleChatflowsbyUserId = async (userid: string, type?: ChatflowType): Promise => { + try { + const response = await axios.get('https://api.github.com/repos/opea-project/GenAIStudio/contents/sample-workflows'); + const files = response.data.filter((item: any) => item.type === 'file'); + console.log(`Number of files: ${files.length}`); + + const chatflows: Partial[] = []; + + for (const file of files) { + console.log(`Download URL: ${file.download_url}`); + const fileResponse = await axios.get(file.download_url); + const parsedFlowData = fileResponse.data; + + const newChatflow: Partial = { + userid: userid, + name: file.name.replace('.json', ''), + flowData: JSON.stringify(parsedFlowData), + type: 'OPEA', + deployed: false, + isPublic: false + }; + + chatflows.push(newChatflow); + } + const insertResponse = await importChatflows(chatflows); + return insertResponse; + } catch (error) { + throw new InternalFlowiseError( + StatusCodes.INTERNAL_SERVER_ERROR, + `Error: chatflowsService.importSampleChatflowsbyUserId - ${getErrorMessage(error)}` + ); + } +} + const getChatflowByApiKey = async (apiKeyId: string, keyonly?: unknown): Promise => { try { // Here we only get chatflows that are bounded by the apikeyid and chatflows that are not bounded by any apikey @@ -455,6 +514,8 @@ export default { checkIfChatflowIsValidForUploads, deleteChatflow, getAllChatflows, + getAllChatflowsbyUserId, + importSampleChatflowsbyUserId, getChatflowByApiKey, getChatflowById, saveChatflow, diff --git a/studio-frontend/packages/ui/package.json b/studio-frontend/packages/ui/package.json index d8ed6fc..4a31ee1 100644 --- a/studio-frontend/packages/ui/package.json +++ b/studio-frontend/packages/ui/package.json @@ -20,7 +20,8 @@ "@mui/lab": "5.0.0-alpha.156", "@mui/material": "5.15.0", "@mui/x-data-grid": "6.8.0", - "@tabler/icons-react": "^3.3.0", + "@react-keycloak/web": "^3.4.0", + "@tabler/icons-react": "3.7.0", "@uiw/codemirror-theme-sublime": "^4.21.21", "@uiw/codemirror-theme-vscode": "^4.21.21", "@uiw/react-codemirror": "^4.21.21", @@ -34,6 +35,7 @@ "framer-motion": "^4.1.13", "history": "^5.0.0", "html-react-parser": "^3.0.4", + "keycloak-js": "^26.0.5", "lodash": "^4.17.21", "moment": "^2.29.3", "notistack": "^2.0.4", diff --git a/studio-frontend/packages/ui/src/App.jsx b/studio-frontend/packages/ui/src/App.jsx index 857d4ea..f547d3b 100644 --- a/studio-frontend/packages/ui/src/App.jsx +++ b/studio-frontend/packages/ui/src/App.jsx @@ -1,32 +1,26 @@ -import { useSelector } from 'react-redux' - -import { ThemeProvider } from '@mui/material/styles' -import { CssBaseline, StyledEngineProvider } from '@mui/material' - -// routing -import Routes from '@/routes' - -// defaultTheme -import themes from '@/themes' - -// project imports -import NavigationScroll from '@/layout/NavigationScroll' - -// ==============================|| APP ||============================== // +import { useSelector } from 'react-redux'; +import { ThemeProvider } from '@mui/material/styles'; +import { CssBaseline, StyledEngineProvider } from '@mui/material'; +import Routes from '@/routes'; +import themes from '@/themes'; +import NavigationScroll from '@/layout/NavigationScroll'; +import KeycloakProvider from './KeycloakContext'; // Import the updated KeycloakProvider const App = () => { - const customization = useSelector((state) => state.customization) + const customization = useSelector((state) => state.customization); return ( - - - - - - + + + + + + + + - ) -} + ); +}; -export default App +export default App; diff --git a/studio-frontend/packages/ui/src/KeycloakContext.jsx b/studio-frontend/packages/ui/src/KeycloakContext.jsx new file mode 100644 index 0000000..9753ee6 --- /dev/null +++ b/studio-frontend/packages/ui/src/KeycloakContext.jsx @@ -0,0 +1,72 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import Keycloak from 'keycloak-js'; + +// Create the Keycloak context +const KeycloakContext = createContext(null); + +// Provide the Keycloak context to the application +export const KeycloakProvider = ({ children }) => { + const [keycloak, setKeycloak] = useState(null); + const [isInitialized, setIsInitialized] = useState(false); + + useEffect(() => { + if (!window.crypto || !window.crypto.subtle) { + console.error("Web Crypto API is not available. This may cause security issues."); + } + + const initOptions = { + url: '/auth/', + realm: 'genaistudio', + clientId: 'genaistudio', + onLoad: 'login-required', // check-sso | login-required + responseType: 'code', // Corrected from KeycloakResponseType to responseType + silentCheckSsoRedirectUri: window.location.origin + "/silent-check-sso.html", + checkLoginIframe: false, + }; + + const kc = new Keycloak(initOptions); + + kc.init({ + onLoad: initOptions.onLoad, + responseType: 'code', // Corrected from KeycloakResponseType to responseType + }).then((auth) => { + if (!auth) { + window.location.reload(); + } else { + console.info("Authenticated"); + console.log('auth', auth); + console.log('Keycloak', kc); + + kc.onTokenExpired = () => { + console.log('token expired'); + }; + + setKeycloak(kc); // Set the Keycloak instance in state + setIsInitialized(true); // Mark initialization as complete + } + }).catch((error) => { + console.error("Authentication Failed", error); + }); + }, []); + + if (!isInitialized) { + return
Loading...
; // Show a loading state until Keycloak is initialized + } + + return ( + + {children} + + ); +}; + +// Custom hook to use Keycloak context +export const useKeycloak = () => { + const context = useContext(KeycloakContext); + if (!context) { + throw new Error('useKeycloak must be used within a KeycloakProvider'); + } + return context; +}; + +export default KeycloakProvider; \ No newline at end of file diff --git a/studio-frontend/packages/ui/src/api/chatflows.js b/studio-frontend/packages/ui/src/api/chatflows.js index 25ab861..bacd1af 100644 --- a/studio-frontend/packages/ui/src/api/chatflows.js +++ b/studio-frontend/packages/ui/src/api/chatflows.js @@ -7,6 +7,8 @@ const getAllAgentflows = () => client.get('/chatflows?type=MULTIAGENT') const getAllOpeaflows = () => client.get('/chatflows?type=OPEA') +const getUserOpeaflows = (userid) => client.get(`/chatflows?userid=${userid}&type=OPEA`) + const getSpecificChatflow = (id) => client.get(`/chatflows/${id}`) const getSpecificChatflowFromPublicEndpoint = (id) => client.get(`/public-chatflows/${id}`) @@ -29,13 +31,17 @@ const stopSandbox = (id) => client.post(`/chatflows-sandbox/stop/${id}`) const buildDeploymentPackage = (id, body) => client.post(`chatflows-sandbox/build-deployment-package/${id}`, body, {responseType: "arraybuffer"}) +const importSampleChatflowsbyUserId = (userid) => client.post(`/chatflows/importsamples?userid=${userid}&type=OPEA`) + export default { getAllChatflows, getAllAgentflows, getAllOpeaflows, + getUserOpeaflows, getSpecificChatflow, getSpecificChatflowFromPublicEndpoint, createNewChatflow, + importSampleChatflowsbyUserId, importChatflows, updateChatflow, deleteChatflow, diff --git a/studio-frontend/packages/ui/src/layout/MainLayout/Header/index.jsx b/studio-frontend/packages/ui/src/layout/MainLayout/Header/index.jsx index 5d4fba2..8a28478 100644 --- a/studio-frontend/packages/ui/src/layout/MainLayout/Header/index.jsx +++ b/studio-frontend/packages/ui/src/layout/MainLayout/Header/index.jsx @@ -18,6 +18,26 @@ import { IconMenu2 } from '@tabler/icons-react' // store import { SET_DARKMODE } from '@/store/actions' +// keycloak context +import LogoutIcon from '@mui/icons-material/Logout'; +import { useKeycloak } from '../../../KeycloakContext' + +const LogoutButton = () => { + const keycloak = useKeycloak(); // Access the Keycloak instance + + const handleLogout = () => { + keycloak.logout({ + redirectUri: window.location.origin, // Redirect to the home page or desired URL after logout + }); + }; + + return ( + + + + ); +}; + // ==============================|| MAIN NAVBAR / HEADER ||============================== // const MaterialUISwitch = styled(Switch)(({ theme }) => ({ @@ -92,45 +112,43 @@ const Header = ({ handleLeftDrawerToggle }) => { return ( <> - {/* logo & toggler button */} + {/* Container for logo and logout button */} - - + {/* Logo Section */} + + + + + + + {/* Logout Button */} + + - {/* - - - - */} - - {/* */} - - {/* */} + ) } diff --git a/studio-frontend/packages/ui/src/layout/MainLayout/index.jsx b/studio-frontend/packages/ui/src/layout/MainLayout/index.jsx index 5f9acea..83ae6f2 100644 --- a/studio-frontend/packages/ui/src/layout/MainLayout/index.jsx +++ b/studio-frontend/packages/ui/src/layout/MainLayout/index.jsx @@ -12,6 +12,8 @@ import Sidebar from './Sidebar' import { drawerWidth, headerHeight } from '@/store/constant' import { SET_MENU } from '@/store/actions' +import {useKeycloak } from '../../KeycloakContext.jsx' + // styles const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ ...theme.typography.mainContent, @@ -57,6 +59,16 @@ const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })(({ // ==============================|| MAIN LAYOUT ||============================== // const MainLayout = () => { + const keycloak = useKeycloak() + console.log ("login roles", keycloak?.tokenParsed?.resource_access?.genaistudio?.roles[0]) + let userRole = keycloak?.tokenParsed?.resource_access?.genaistudio?.roles[0] + + const handleLogout = () => { + keycloak.logout({ + redirectUri: window.location.origin, // Redirect to the home page or desired URL after logout + }); + }; + const theme = useTheme() const matchDownMd = useMediaQuery(theme.breakpoints.down('lg')) @@ -73,45 +85,51 @@ const MainLayout = () => { }, [matchDownMd]) return ( - - - {/* header */} - - -
- - - - {/* drawer */} - {/* */} - - {/* main content */} -
- - + + {/* header */} + - - FlowiseAI - - -
+ +
+ + - + {/* drawer */} + {/* */} + + {/* main content */} + (
+ + + + FlowiseAI + + +
) + ): + ( + +

You are unauthorised. Please contact your admin for approval.

+ +
+ ) ) } diff --git a/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx b/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx index 8ba6d73..107ef78 100644 --- a/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx +++ b/studio-frontend/packages/ui/src/ui-component/table/FlowListTable.jsx @@ -61,11 +61,12 @@ const getLocalStorageKeyName = (name, isAgentCanvas) => { return (isAgentCanvas ? 'agentcanvas' : 'chatflowcanvas') + '_' + name } -export const FlowListTable = ({ data, images, isLoading, filterFunction, updateFlowsApi, setError, isAgentCanvas, isOpeaCanvas, stopSandboxApi, updateFlowToServerApi }) => { +export const FlowListTable = ({ data, images, isLoading, filterFunction, updateFlowsApi, setError, isAgentCanvas, isOpeaCanvas, stopSandboxApi, updateFlowToServerApi, userRole }) => { // overwrite setError setError = (error) => { console.error(error) } + // console.log ("table user", userRole) const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -89,7 +90,7 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF const handleSortData = () => { if (!data) return []; - console.log('handleSortData', data); + // console.log('handleSortData', data); const sorted = [...data].map((row) => ({ ...row, sandboxStatus: row.sandboxStatus || 'Not Running' // Ensure initial status @@ -288,6 +289,16 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF Last Modified Date + {userRole === 'admin' && + + + User + + } @@ -309,6 +320,9 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF + + + @@ -326,6 +340,9 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF + + + ) : ( @@ -465,6 +482,8 @@ export const FlowListTable = ({ data, images, isLoading, filterFunction, updateF {moment(row.updatedDate).format('MMMM Do, YYYY')} + {userRole=='admin' && {row.userid}} + ))} diff --git a/studio-frontend/packages/ui/src/views/canvas/index.jsx b/studio-frontend/packages/ui/src/views/canvas/index.jsx index cc29da6..6b05709 100644 --- a/studio-frontend/packages/ui/src/views/canvas/index.jsx +++ b/studio-frontend/packages/ui/src/views/canvas/index.jsx @@ -55,12 +55,16 @@ import { usePrompt } from '@/utils/usePrompt' // const import { FLOWISE_CREDENTIAL_ID } from '@/store/constant' +// keycloak context +import { useKeycloak } from '../../KeycloakContext' + const nodeTypes = { customNode: CanvasNode, stickyNote: StickyNote } const edgeTypes = { buttonedge: ButtonEdge } // ==============================|| CANVAS ||============================== // const Canvas = () => { + const keycloak = useKeycloak() const theme = useTheme() const navigate = useNavigate() @@ -223,12 +227,16 @@ const Canvas = () => { rfInstanceObject.nodes = nodes const flowData = JSON.stringify(rfInstanceObject) + console.log (chatflowName) + console.log (keycloak?.tokenParsed) + if (!chatflow.id) { const newChatflowBody = { name: chatflowName, deployed: false, isPublic: false, flowData, + userid: keycloak?.tokenParsed?.email ? keycloak.tokenParsed.email : '', type: isAgentCanvas ? 'MULTIAGENT' : isOpeaCanvas ? 'OPEA' : 'CHATFLOW' } createNewChatflowApi.request(newChatflowBody) diff --git a/studio-frontend/packages/ui/src/views/opeaflows/index.jsx b/studio-frontend/packages/ui/src/views/opeaflows/index.jsx index d2b6423..1beefee 100644 --- a/studio-frontend/packages/ui/src/views/opeaflows/index.jsx +++ b/studio-frontend/packages/ui/src/views/opeaflows/index.jsx @@ -29,9 +29,13 @@ import { baseURL } from '@/store/constant' // icons import { IconPlus, IconLayoutGrid, IconList } from '@tabler/icons-react' +//keycloak +import { useKeycloak } from '../../KeycloakContext' + // ==============================|| OPEAFlows ||============================== // const Opeaflows = () => { + const keycloak = useKeycloak() const navigate = useNavigate() const theme = useTheme() @@ -42,7 +46,22 @@ const Opeaflows = () => { const [loginDialogOpen, setLoginDialogOpen] = useState(false) const [loginDialogProps, setLoginDialogProps] = useState({}) - const getAllOpeaflowsApi = useApi(chatflowsApi.getAllOpeaflows) + console.log ("roles", keycloak?.tokenParsed?.resource_access?.genaistudio?.roles[0]) + let userRole = keycloak?.tokenParsed?.resource_access?.genaistudio?.roles[0] + let getAllOpeaflowsApi = null + if (keycloak.authenticated) { + getAllOpeaflowsApi = useApi(chatflowsApi.getAllOpeaflows) + + if (userRole === 'admin') { + getAllOpeaflowsApi = useApi(chatflowsApi.getAllOpeaflows) + } + else if (userRole === 'user') { + getAllOpeaflowsApi = useApi(() => chatflowsApi.getUserOpeaflows(keycloak.tokenParsed.email)); + console.log("email", keycloak.tokenParsed.email) + console.log ("get user opeaflows", getAllOpeaflowsApi) + } + } + const stopSandboxApi = chatflowsApi.stopSandbox const updateFlowToServerApi = chatflowsApi.updateChatflow const [view, setView] = useState(localStorage.getItem('flowDisplayStyle') || 'list') @@ -74,6 +93,15 @@ const Opeaflows = () => { navigate('/opeacanvas') } + const importSamples = () => { + setLoading(true); + chatflowsApi.importSampleChatflowsbyUserId(keycloak.tokenParsed.email).then(() => { + getAllOpeaflowsApi.request(); + }).catch(() => { + setLoading(false); + }); + } + const goToCanvas = (selectedChatflow) => { navigate(`/opeacanvas/${selectedChatflow.id}`) } @@ -86,15 +114,16 @@ const Opeaflows = () => { useEffect(() => { if (getAllOpeaflowsApi.error) { - if (getAllOpeaflowsApi.error?.response?.status === 401) { - setLoginDialogProps({ - title: 'Login', - confirmButtonName: 'Login' - }) - setLoginDialogOpen(true) - } else { - setError(getAllOpeaflowsApi.error) - } + console.log ("error", getAllOpeaflowsApi.error) + // if (getAllOpeaflowsApi.error?.response?.status === 401) { + // setLoginDialogProps({ + // title: 'Login', + // confirmButtonName: 'Login' + // }) + // setLoginDialogOpen(true) + // } else { + // setError(getAllOpeaflowsApi.error) + // } } }, [getAllOpeaflowsApi.error]) @@ -166,9 +195,14 @@ const Opeaflows = () => { */} - } sx={{ borderRadius: 2, height: 40, width:250}}> + + } sx={{ borderRadius: 2, height: 40, width: 250 }}> Create New Workflow - + + } sx={{ borderRadius: 2, height: 40, width: 250 }}> + Import Sample Workflows + + {!view || view === 'card' ? ( <> {isLoading && !getAllOpeaflowsApi.data ? ( @@ -196,6 +230,7 @@ const Opeaflows = () => { setError={setError} stopSandboxApi={stopSandboxApi} isOpeaCanvas={true} + userRole={userRole} /> )} {!isLoading && (!getAllOpeaflowsApi.data || getAllOpeaflowsApi.data.length === 0) && ( diff --git a/studio-frontend/packages/ui/vite.config.js b/studio-frontend/packages/ui/vite.config.js index 1d51668..c987920 100644 --- a/studio-frontend/packages/ui/vite.config.js +++ b/studio-frontend/packages/ui/vite.config.js @@ -37,8 +37,8 @@ export default defineConfig(async ({ mode }) => { server: { open: true, proxy, - port: process.env.VITE_PORT ?? 8080, - host: process.env.VITE_HOST + port: process.env.VITE_PORT ?? 8088, + host: process.env.VITE_HOST ?? '0.0.0.0' } } }) diff --git a/tests/playwright/studio-e2e/001_test_sandbox_deployment.spec.ts b/tests/playwright/studio-e2e/001_test_sandbox_deployment.spec.ts index 4815eda..146ba7a 100644 --- a/tests/playwright/studio-e2e/001_test_sandbox_deployment.spec.ts +++ b/tests/playwright/studio-e2e/001_test_sandbox_deployment.spec.ts @@ -4,11 +4,18 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; -test('001_test_sandbox_deployment', async ({ page, baseURL }) => { +test('001_test_sandbox_deployment', async ({ browser, baseURL }) => { test.setTimeout(600000); - + const context = await browser.newContext({ + ignoreHTTPSErrors: true + }); + const page = await context.newPage(); const IDC_URL = baseURL || "" await page.goto(IDC_URL); + await page.getByLabel('Username or email').fill('test_automation@gmail.com'); + await page.getByLabel('Password', { exact: true }).click(); + await page.getByLabel('Password', { exact: true }).fill('test'); + await page.getByRole('button', { name: 'Sign In' }).click(); await page.getByRole('button', { name: 'Create New Workflow' }).click(); await page.getByRole('button', { name: 'Settings' }).click(); let fileChooserPromise = page.waitForEvent('filechooser'); diff --git a/tests/playwright/studio-e2e/002_test_sandbox_chatqna.spec.ts b/tests/playwright/studio-e2e/002_test_sandbox_chatqna.spec.ts index 3f2583a..056e3e6 100644 --- a/tests/playwright/studio-e2e/002_test_sandbox_chatqna.spec.ts +++ b/tests/playwright/studio-e2e/002_test_sandbox_chatqna.spec.ts @@ -37,12 +37,19 @@ async function setupResponseListener(page, apiResponse) { }); } -test('002_test_sandbox_chatqna', async ({ page, baseURL }) => { +test('002_test_sandbox_chatqna', async ({ browser, baseURL }) => { test.setTimeout(600000); let apiResponse = { value: '' }; - + const context = await browser.newContext({ + ignoreHTTPSErrors: true + }); + const page = await context.newPage(); const IDC_URL = baseURL || "" await page.goto(IDC_URL); + await page.getByLabel('Username or email').fill('test_automation@gmail.com'); + await page.getByLabel('Password', { exact: true }).click(); + await page.getByLabel('Password', { exact: true }).fill('test'); + await page.getByRole('button', { name: 'Sign In' }).click(); await page.getByRole('button', { name: 'Create New Workflow' }).click(); await page.getByRole('button', { name: 'Settings' }).click(); let fileChooserPromise = page.waitForEvent('filechooser');