diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index c3ff282..cf840cb 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,40 +1,66 @@ -FROM mcr.microsoft.com/vscode/devcontainers/base:bookworm as kind +FROM mcr.microsoft.com/devcontainers/base:ubuntu +# Change to root only when running locally on MAC, otherwise leave it to vscode ARG USERNAME=vscode -ARG USER_UID=1000 -ARG USER_GID=$USER_UID +ARG HELM_VERSION=v3.17.0 +# Multiarchitecture, plattform is passed on when building the image. +ARG TARGETPLATFORM +ARG BUILDPLATFORM +ARG TARGETARCH -RUN export DEBIAN_FRONTEND=noninteractive +RUN echo "Host is running on $BUILDPLATFORM and the binaries will be downloaded for '$TARGETARCH', building for $TARGETPLATFORM" -COPY docker.sh /tmp/scripts/ -RUN chmod +x /tmp/scripts/docker.sh +# Install Docker CLI (not daemon) +RUN apt update && apt install -y \ + docker.io \ + curl \ + ca-certificates \ + iptables \ + socat \ + ebtables \ + ethtool \ + conntrack \ + sudo \ + gh \ + python3-pip \ + && rm -rf /var/lib/apt/lists/* -# update the container -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install zsh ca-certificates gnupg jq -y && \ - apt-get autoremove -y && \ - apt-get clean -y - -# Install kubectl -RUN curl -sSL -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl \ - && chmod +x /usr/local/bin/kubectl +# Install kubectl (x86_64/amd64, aarch64/arm64) +RUN curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/$TARGETARCH/kubectl" \ + && install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl \ + && rm kubectl # Install Helm -RUN curl -s https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - +RUN curl -LO https://get.helm.sh/helm-$HELM_VERSION-linux-$TARGETARCH.tar.gz \ + && tar -zxvf helm-$HELM_VERSION-linux-$TARGETARCH.tar.gz \ + && mv linux-$TARGETARCH/helm /usr/local/bin/helm \ + && chmod +x /usr/local/bin/helm \ + && rm -rf helm-$HELM_VERSION-linux-$TARGETARCH.tar.gz linux-$TARGETARCH # Install kind RUN KIND_RELEASE=$(curl --silent "https://api.github.com/repos/kubernetes-sigs/kind/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') \ - && curl -sSL -o /usr/local/bin/kind https://kind.sigs.k8s.io/dl/$KIND_RELEASE/kind-linux-amd64 \ + && curl -sSL -o /usr/local/bin/kind https://kind.sigs.k8s.io/dl/$KIND_RELEASE/kind-linux-$TARGETARCH \ && chmod +x /usr/local/bin/kind + +# Install Node.js and npm (multi-architecture support) +RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ + && apt-get install -y nodejs \ + && npm install -g npm@latest \ + && rm -rf /var/lib/apt/lists/* + +COPY entrypoint.sh / + +RUN chmod +x /entrypoint.sh -RUN /tmp/scripts/docker.sh +# In case the user is root, easy workaround when running locally on MAC so root can access the docker.socket of the hosts, otherwise user should be vscode. +# Change ownership carefully and use only locally. Container should run with --privileged +RUN if [ "$USERNAME" != "root" ]; then \ + chown -R $USERNAME:$USERNAME /home/$USERNAME; \ + fi -# change ownership of the home directory -RUN chown -R ${USERNAME}:${USERNAME} /home/${USERNAME} +USER $USERNAME -WORKDIR /home/${USERNAME} -USER ${USERNAME} +# Entrypoint is on the mounted volume using the framework, post-create.sh +ENTRYPOINT ["/entrypoint.sh"] -ENTRYPOINT [ "/usr/local/share/docker-init.sh" ] CMD [ "sleep", "infinity" ] \ No newline at end of file diff --git a/.devcontainer/Makefile b/.devcontainer/Makefile new file mode 100644 index 0000000..b0dbbe9 --- /dev/null +++ b/.devcontainer/Makefile @@ -0,0 +1,37 @@ +# Make file for building the image, cleaning, pushing and running locally and remote. + +# If the image does not exits, it will build it first, then run it. +# If the image exists but is not started it will create a new container. +# If the container is stopped then it will start it again. +# if the container is running it will attach a new shell to it. +start: + @bash -c ' \ + source ./makefile.sh; \ + start \ + echo "Done"; ' + +# Target for building the image +build-nocache: + @bash -c ' \ + source ./makefile.sh; \ + buildNoCache; \ + echo "Done"; ' + +# Target for building the image +build: + @bash -c ' \ + source ./makefile.sh; \ + build; \ + echo "Done"; ' + +buildx: + @bash -c ' \ + source ./makefile.sh; \ + buildx; \ + echo "Done"; ' + +integration: + @bash -c ' \ + source ./makefile.sh; \ + integration \ + echo "Done"; ' \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/.dockerignore b/.devcontainer/apps/ai-travel-advisor/.dockerignore new file mode 100644 index 0000000..eba74f4 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/.dockerignore @@ -0,0 +1 @@ +venv/ \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/Dockerfile b/.devcontainer/apps/ai-travel-advisor/Dockerfile new file mode 100644 index 0000000..0e08396 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.9.20-bookworm + +COPY requirements.txt ./ +RUN pip install -r requirements.txt +RUN pip install opentelemetry-instrumentation-ollama==0.39.4 --no-deps +RUN pip install -i https://test.pypi.org/simple/ dynatrace-opentelemetry-instrumentation-langchain==0.39.3a1 --no-deps + +COPY ./public ./public +COPY ./destinations ./destinations +COPY app.py ./ + +EXPOSE 8080 + +CMD [ "python", "app.py"] \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/Makefile b/.devcontainer/apps/ai-travel-advisor/Makefile new file mode 100644 index 0000000..1e463af --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/Makefile @@ -0,0 +1,17 @@ +IMAGE= shinojosa/ai-travel-advisor +VERSION = v1.0.0 + +build: + docker build -t $(IMAGE):$(VERSION) . + +push: + docker push $(IMAGE):$(VERSION) + +login: + docker logout && docker login + +runnginx: + docker run -p 8080:80 -v /workspaces/enablement-gen-ai-llm-observability/app/public:/usr/share/nginx/html nginx + +rollout: + kubectl set image deployment/ai-travel-advisor ai-travel-advisor=$(IMAGE):$(VERSION) -n ai-travel-advisor \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/app.py b/.devcontainer/apps/ai-travel-advisor/app.py new file mode 100644 index 0000000..d742a01 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/app.py @@ -0,0 +1,337 @@ +import ollama +from langchain.agents import AgentExecutor, create_structured_chat_agent +from langchain_core.tools import tool +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_community.document_loaders import BSHTMLLoader +from langchain_core.runnables import RunnablePassthrough +from langchain_core.output_parsers import StrOutputParser + +from langchain_ollama.chat_models import ChatOllama +from langchain_ollama.embeddings import OllamaEmbeddings +from langchain_text_splitters import RecursiveCharacterTextSplitter + +import logging +import os +from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles +import uvicorn + +from opentelemetry import trace +from traceloop.sdk import Traceloop +from traceloop.sdk.decorators import workflow, task +from colorama import Fore + +import weaviate +import weaviate.classes as wvc +from langchain_weaviate.vectorstores import WeaviateVectorStore + + +# disable traceloop telemetry +os.environ["TRACELOOP_TELEMETRY"] = "false" + +def read_token(): + return os.environ.get("API_TOKEN", read_secret("token")) + + +def read_endpoint(): + return os.environ.get("OTEL_ENDPOINT", read_secret("endpoint")) + +def read_secret(secret: str): + try: + with open(f"/etc/secrets/{secret}", "r") as f: + return f.read().rstrip() + except Exception as e: + print(f"No {secret} was provided") + print(e) + return "" + +# By default we use orca-mini:3b because it's small enough to run easily on codespace +# Make sure if you change this, you need to also change the deployment script +AI_MODEL = os.environ.get("AI_MODEL", "orca-mini:3b") +AI_EMBEDDING_MODEL = os.environ.get("AI_EMBEDDING_MODEL", "orca-mini:3b") + +# Clean up endpoint making sure it is correctly follow the format: +# https://.live.dynatrace.com/api/v2/otlp +OTEL_ENDPOINT = read_endpoint() +if OTEL_ENDPOINT.endswith("/v1/traces"): + OTEL_ENDPOINT = OTEL_ENDPOINT[: OTEL_ENDPOINT.find("/v1/traces")] + +## Configuration of OLLAMA & Weaviate +OLLAMA_ENDPOINT = os.environ.get("OLLAMA_ENDPOINT", "http://localhost:11434") +WEAVIATE_ENDPOINT = os.environ.get("WEAVIATE_ENDPOINT", "localhost") +print(f"{Fore.GREEN} Connecting to Ollama ({AI_MODEL}) LLM: {OLLAMA_ENDPOINT} {Fore.RESET}") +print(f"{Fore.GREEN} Connecting to Weaviate VectorDB: {WEAVIATE_ENDPOINT} {Fore.RESET}") + +llm = ChatOllama(model=AI_MODEL, base_url=OLLAMA_ENDPOINT) +ollama_client = ollama.Client( + host=OLLAMA_ENDPOINT, +) + +MAX_PROMPT_LENGTH = 50 + +# Initialise the logger +logging.basicConfig(level=logging.INFO, filename="run.log") +logger = logging.getLogger(__name__) + +################# +# CONFIGURE TRACELOOP & OTel + +TOKEN = read_token() +headers = {"Authorization": f"Api-Token {TOKEN}"} + +# Use the OTel API to instanciate a tracer to generate Spans +otel_tracer = trace.get_tracer("travel-advisor") + +## force weaviate instrumentor +from opentelemetry.instrumentation.weaviate import WeaviateInstrumentor +from opentelemetry.instrumentation import weaviate as w +w.WRAPPED_METHODS = [ + { + # v4.14.1 + "module": "weaviate.collections.queries.hybrid.query.executor", + "object": "_HybridQueryExecutor", + "method": "hybrid", + "span_name": "db.weaviate.collections.query.hybrid", + }, +] +instrumentor = WeaviateInstrumentor() +if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + +# Initialize OpenLLMetry +Traceloop.init( + app_name="ai-travel-advisor", + api_endpoint=OTEL_ENDPOINT, + disable_batch=True, # This is recomended for testing but NOT for production + headers=headers, +) + +def format_docs(docs): + return "\n\n".join(doc.page_content for doc in docs) + +def prep_rag(): + # Create the embedding and the Weaviate Client + embeddings = OllamaEmbeddings(model=AI_EMBEDDING_MODEL, base_url=OLLAMA_ENDPOINT) + weaviate_client = weaviate.connect_to_local(host=WEAVIATE_ENDPOINT) + # Cleanup the collection containing our documents and recreate it + weaviate_client.collections.delete("KB") + weaviate_client.collections.create( + name = "KB", + vectorizer_config=wvc.config.Configure.Vectorizer.text2vec_ollama(api_endpoint=OLLAMA_ENDPOINT, model=AI_EMBEDDING_MODEL), + properties = [ + wvc.config.Property( + name="text", + data_type=wvc.config.DataType.TEXT, + ), + wvc.config.Property( + name="source", + data_type=wvc.config.DataType.TEXT, + ), + wvc.config.Property( + name="title", + data_type=wvc.config.DataType.TEXT, + ), + ], + ) + + # Retrieve the source data + docs_list = [] + for item in os.listdir(path="destinations"): + if item.endswith(".html"): + item_docs_list = BSHTMLLoader(file_path=f"destinations/{item}").load() + for item in item_docs_list: + docs_list.append(item) + + # Split Document into tokens + text_splitter = RecursiveCharacterTextSplitter() + documents = text_splitter.split_documents(docs_list) + + vector = WeaviateVectorStore.from_documents( + documents, + embeddings, + client=weaviate_client, + index_name="KB" + ) + retriever = vector.as_retriever() + + prompt = ChatPromptTemplate.from_template( + """Answer the following question based only on the provided context: + + {context} + + Question: Give travel advise in a paragraph of max 50 words about {input} + """ + ) + # Build the RAG Pipeline + rag_chain = ( + {"context": retriever | format_docs, "input": RunnablePassthrough()} + | prompt + | llm + | StrOutputParser() + ) + + return rag_chain + +########## +# Agentic Tools + +import re +regex = re.compile('[^a-zA-Z]') + +@tool +def excuse(city: str)->str: + """ Returns an excuse why it cannot provide an answer """ + prompt = f"Provide an excuse on why you cannot provide a travel advice about {city}" + response = ollama_client.generate(model=AI_MODEL, prompt=prompt) + return response.get("response") + +@tool +def valid_city(city: str)->bool: + """ Returns if the input is a valid city""" + prompt = f"Is {city} a city? respond ONLY with yes or no." + response = ollama_client.generate(model=AI_MODEL, prompt=prompt) + response = regex.sub('', response.get("response")).lower() + return response == "yes" or response.startswith("yes") + +@tool +def travel_advice(city: str)->str: + """ Provide travel advice for the given city""" + prompt = f"Give travel advise in a paragraph of max 50 words about {city}" + response = ollama_client.generate(model=AI_MODEL, prompt=prompt) + return "Final Answer:" + response.get("response") + +def prep_agent_executor(): + __tools = [valid_city, travel_advice, excuse] + __system = '''Respond to the human as helpfully and accurately as possible. You have access to the following tools: + +{tools} + +Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input). + +Valid "action" values: "Final Answer" or {tool_names} + +Provide only ONE action per $JSON_BLOB, as shown: + +``` +{{ + "action": $TOOL_NAME, + "action_input": $INPUT +}} +``` + +Follow this format: + +Question: input question to answer +Thought: consider previous and subsequent steps +Action: +``` +$JSON_BLOB +``` +Observation: action result +... (repeat Thought/Action/Observation N times) +Thought: I know what to respond +Action: +``` +{{ + "action": "Final Answer", + "action_input": "Final response to human" +}} + +Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation''' + + __human = ''' +{input} + +{agent_scratchpad} + +(reminder to respond in a JSON blob no matter what)''' + prompt = ChatPromptTemplate.from_messages( + [ + ("system", __system), + MessagesPlaceholder("chat_history", optional=True), + ("human", __human), + ] + ) + agent = create_structured_chat_agent(llm, __tools, prompt) + return AgentExecutor( + agent=agent, + tools=__tools, + verbose=True, + handle_parsing_errors=True, + max_iterations=5, + ) + + +############ +# Setup the endpoints and LangChain + +app = FastAPI() +retrieval_chain = prep_rag() +agentic_executor = prep_agent_executor() + + +#################################### +@app.get("/api/v1/completion") +def submit_completion(framework: str, prompt: str): + with otel_tracer.start_as_current_span(name="/api/v1/completion", kind=trace.SpanKind.SERVER) as span: + if framework == "llm": + return llm_chat(prompt) + if framework == "rag": + return rag_chat(prompt) + if framework == "agentic": + return agentic_chat(prompt) + span.set_status(trace.StatusCode.ERROR, f"{framework} mode is not supported") + return {"message": "invalid Mode"} + + +@task(name="ollama_chat") +def llm_chat(prompt: str): + prompt = f"Give travel advise in a paragraph of max 50 words about {prompt}" + res = ollama_client.generate(model=AI_MODEL, prompt=prompt) + return {"message": res.get("response")} + + +@workflow(name="travelgenerator") +def rag_chat(prompt: str): + if prompt: + logger.info(f"Calling RAG to get the answer to the question: {prompt}...") + response = retrieval_chain.invoke( prompt, config={}) + return {"message": response} + else: # No, or invalid prompt given + err_msg = f"No prompt provided or prompt too long (over {MAX_PROMPT_LENGTH} chars)" + # Try to augment existing Spans with info + span = trace.get_current_span() + span.add_event(err_msg) + span.set_status(trace.StatusCode.ERROR) + return { + "message": err_msg + } + +@task(name="agentic_chat") +def agentic_chat(prompt: str): + task = f"If {prompt} is a city, provide a travel advice. " + response = agentic_executor.invoke({ + "input": task, + }) + return {"message": response['output']} + +#################################### +@app.get("/api/v1/thumbsUp") +@otel_tracer.start_as_current_span("/api/v1/thumbsUp") +def thumbs_up(prompt: str): + logger.info(f"Positive user feedback for search term: {prompt}") + + +@app.get("/api/v1/thumbsDown") +@otel_tracer.start_as_current_span("/api/v1/thumbsDown") +def thumbs_down(prompt: str): + logger.info(f"Negative user feedback for search term: {prompt}") + + +if __name__ == "__main__": + + # Mount static files at the root + app.mount("/", StaticFiles(directory="./public", html=True), name="public") + + # Run the app using uvicorn + uvicorn.run(app, host="0.0.0.0", port=8082) diff --git a/.devcontainer/apps/ai-travel-advisor/destinations/bali.html b/.devcontainer/apps/ai-travel-advisor/destinations/bali.html new file mode 100644 index 0000000..abd8e8b --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/destinations/bali.html @@ -0,0 +1,12 @@ + + + + Destination Bali + + +

Destination Bali

+

Bali is a country on the Moon. It has a tiny population of only 19 people.

+

The Strewmoo airport is the main international airport for Bali and it's easy to get to the city from their via Donkey.

+

Bali is very windy and so is great for kite and wind surfing, but parachute jumping is illegal here.

+ + \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/destinations/sydney.html b/.devcontainer/apps/ai-travel-advisor/destinations/sydney.html new file mode 100644 index 0000000..695218c --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/destinations/sydney.html @@ -0,0 +1,12 @@ + + + + Destination Sydney + + +

Destination syndey

+

Sydney is a city in Western Australia. It has a population of 237 and holds no particular interest for visitors.

+

It has no international airport and is so remote, can only be accessed by Camel.

+

Sydney is known for its mountains and is great for winter sports.

+ + \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/k8s/ai-travel-advisor.yaml b/.devcontainer/apps/ai-travel-advisor/k8s/ai-travel-advisor.yaml new file mode 100644 index 0000000..b00b42b --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/k8s/ai-travel-advisor.yaml @@ -0,0 +1,56 @@ + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ai-travel-advisor + namespace: ai-travel-advisor +spec: + selector: + matchLabels: + name: ai-travel-advisor + template: + metadata: + labels: + name: ai-travel-advisor + spec: + containers: + - name: ai-travel-advisor + image: thisthatdc/ai-travel-advisor:v0.1.1 + ports: + - name: http + containerPort: 8082 + protocol: TCP + env: + - name: OLLAMA_ENDPOINT + value: "http://ollama.ai-travel-advisor.svc.cluster.local:11434" + - name: WEAVIATE_ENDPOINT + value: "weaviate.ai-travel-advisor.svc.cluster.local" + - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE + value: "delta" + imagePullPolicy: Always + volumeMounts: + - name: secrets + readOnly: true + mountPath: "/etc/secrets" + volumes: + - name: secrets + projected: + sources: + - secret: + name: dynatrace +--- +apiVersion: v1 +kind: Service +metadata: + name: ai-travel-advisor + namespace: ai-travel-advisor +spec: + type: NodePort + selector: + name: ai-travel-advisor + ports: + - port: 80 + name: http + targetPort: 8082 + protocol: TCP \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/k8s/namespace.yaml b/.devcontainer/apps/ai-travel-advisor/k8s/namespace.yaml new file mode 100644 index 0000000..30654b0 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/k8s/namespace.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ai-travel-advisor + labels: + name: ai-travel-advisor \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/k8s/ollama.yaml b/.devcontainer/apps/ai-travel-advisor/k8s/ollama.yaml new file mode 100644 index 0000000..c3d79d1 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/k8s/ollama.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ollama + namespace: ai-travel-advisor +spec: + selector: + matchLabels: + name: ollama + template: + metadata: + labels: + name: ollama + spec: + containers: + - name: ollama + image: ollama/ollama:latest + ports: + - name: http + containerPort: 11434 + protocol: TCP + lifecycle: + postStart: + exec: + command: [ "/bin/sh", "-c", "ollama run orca-mini:3b" ] +--- +apiVersion: v1 +kind: Service +metadata: + name: ollama + namespace: ai-travel-advisor +spec: + type: ClusterIP + selector: + name: ollama + ports: + - port: 80 + name: web + targetPort: 11434 + protocol: TCP + - port: 11434 + name: http + targetPort: 11434 + protocol: TCP \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/k8s/weaviate.yaml b/.devcontainer/apps/ai-travel-advisor/k8s/weaviate.yaml new file mode 100644 index 0000000..6f8f81c --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/k8s/weaviate.yaml @@ -0,0 +1,82 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: weaviate-pvc + namespace: ai-travel-advisor +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 8Gi + storageClassName: "standard" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: weaviate + namespace: ai-travel-advisor +spec: + replicas: 1 + selector: + matchLabels: + app: weaviate + template: + metadata: + labels: + app: weaviate + annotations: + metrics.dynatrace.com/scrape: "true" + metrics.dynatrace.com/port: "2112" + metrics.dynatrace.com/path: "/metrics" + spec: + volumes: + - name: weaviate-volume + persistentVolumeClaim: + claimName: weaviate-pvc + containers: + - name: weaviate + image: cr.weaviate.io/semitechnologies/weaviate:1.30.1 + ports: + - containerPort: 8080 + - containerPort: 2112 + - containerPort: 50051 + volumeMounts: + - name: weaviate-volume + mountPath: /var/lib/weaviate + env: + - name: PROMETHEUS_MONITORING_ENABLED + value: "true" + - name: AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED + value: "true" + - name: ENABLE_API_BASED_MODULES + value: "true" + - name: AUTOSCHEMA_ENABLED + value: "false" + - name: ENABLE_MODULES + value: "text2vec-ollama,generative-ollama" + - name: PERSISTENCE_DATA_PATH + value: "/var/lib/weaviate" + resources: + limits: + cpu: "0.1" # Maximum CPU usage + memory: "512Mi" # Maximum memory usage +--- +apiVersion: v1 +kind: Service +metadata: + name: weaviate + namespace: ai-travel-advisor +spec: + selector: + app: weaviate + ports: + - protocol: TCP + port: 8080 + name: web + targetPort: 8080 # container port + - protocol: TCP + port: 50051 + name: grpc + targetPort: 50051 # container port + diff --git a/.devcontainer/apps/ai-travel-advisor/public/_particles/css/ai.css b/.devcontainer/apps/ai-travel-advisor/public/_particles/css/ai.css new file mode 100755 index 0000000..1b60ca6 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/_particles/css/ai.css @@ -0,0 +1,100 @@ +/* ============================================================================= + HTML5 CSS Reset Minified - Eric Meyer + ========================================================================== */ + +html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent} +body{line-height:1} +article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block} +nav ul{list-style:none} +blockquote,q{quotes:none} +blockquote:before,blockquote:after,q:before,q:after{content:none} +a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent;text-decoration:none} +mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold} +del{text-decoration:line-through} +abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help} +table{border-collapse:collapse;border-spacing:0} +hr{display:block;height:1px;border:0;border-top:1px solid #f6f6f6;margin:1em 0;padding:0} +input,select{vertical-align:middle} +li{list-style:none} + + +/* ============================================================================= + My CSS + ========================================================================== */ + +/* ---- base ---- */ + +html,body{ + width:100%; + height:100%; + background:#ffffff; + color:#000; +} + +html{ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body{ + font:normal 75% Arial, Helvetica, sans-serif; +} + +canvas{ + display:block; + vertical-align:bottom; +} + + +/* ---- stats.js ---- */ + +.count-particles{ + background: #000000; + position: absolute; + top: 48px; + left: 0; + width: 80px; + color: #0d5656; + font-size: .8em; + text-align: left; + text-indent: 4px; + line-height: 14px; + padding-bottom: 2px; + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} + +.js-count-particles{ + font-size: 1.1em; +} + +#stats, +.count-particles{ + -webkit-user-select: none; + margin-top: 5px; + margin-left: 5px; +} + +#stats{ + border-radius: 3px 3px 0 0; + overflow: hidden; +} + +.count-particles{ + border-radius: 0 0 3px 3px; +} + + +/* ---- particles.js container ---- +*/ + +#particles-js{ + width: 100%; + height: 350px; + background-size: cover; + position: absolute; /* or relative, fixed, or sticky depending on your layout */ + z-index: 2; + background-color: transparent; + background-image: url(''); + background-position: 50% 50%; + background-repeat: no-repeat; +} diff --git a/.devcontainer/apps/ai-travel-advisor/public/_particles/css/style.css b/.devcontainer/apps/ai-travel-advisor/public/_particles/css/style.css new file mode 100755 index 0000000..6552cc1 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/_particles/css/style.css @@ -0,0 +1,97 @@ +/* ============================================================================= + HTML5 CSS Reset Minified - Eric Meyer + ========================================================================== */ + +html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent} +body{line-height:1} +article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block} +nav ul{list-style:none} +blockquote,q{quotes:none} +blockquote:before,blockquote:after,q:before,q:after{content:none} +a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent;text-decoration:none} +mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold} +del{text-decoration:line-through} +abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help} +table{border-collapse:collapse;border-spacing:0} +hr{display:block;height:1px;border:0;border-top:1px solid #f6f6f6;margin:1em 0;padding:0} +input,select{vertical-align:middle} +li{list-style:none} + + +/* ============================================================================= + My CSS + ========================================================================== */ + +/* ---- base ---- */ + +html,body{ + width:100%; + height:100%; + background:#ffffff; + color:#000; +} + +html{ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body{ + font:normal 75% Arial, Helvetica, sans-serif; +} + +canvas{ + display:block; + vertical-align:bottom; +} + + +/* ---- stats.js ---- */ + +.count-particles{ + background: #000000; + position: absolute; + top: 48px; + left: 0; + width: 80px; + color: #0d5656; + font-size: .8em; + text-align: left; + text-indent: 4px; + line-height: 14px; + padding-bottom: 2px; + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} + +.js-count-particles{ + font-size: 1.1em; +} + +#stats, +.count-particles{ + -webkit-user-select: none; + margin-top: 5px; + margin-left: 5px; +} + +#stats{ + border-radius: 3px 3px 0 0; + overflow: hidden; +} + +.count-particles{ + border-radius: 0 0 3px 3px; +} + + +/* ---- particles.js container ---- */ + +#particles-js{ + width: 100%; + height: 100%; + background-color: #000000; + background-image: url(''); + background-size: cover; + background-position: 50% 50%; + background-repeat: no-repeat; +} diff --git a/.devcontainer/apps/ai-travel-advisor/public/_particles/index.html b/.devcontainer/apps/ai-travel-advisor/public/_particles/index.html new file mode 100755 index 0000000..9459ad8 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/_particles/index.html @@ -0,0 +1,56 @@ + + + + + Dynatrace particles.js + + + + + + + + + + + +
+ + + + + + + + + + + \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/public/_particles/js/app.js b/.devcontainer/apps/ai-travel-advisor/public/_particles/js/app.js new file mode 100644 index 0000000..6b5d73a --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/_particles/js/app.js @@ -0,0 +1,133 @@ +/* ----------------------------------------------- +/* How to use? : Check the GitHub README +/* ----------------------------------------------- */ + +/* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */ +/* +particlesJS.load('particles-js', 'particles.json', function() { + console.log('particles.js loaded - callback'); +}); +*/ + +/* Otherwise just put the config content (json): */ + +particlesJS('particles-js', + + { + "particles": { + "number": { + "value": 80, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 5, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 6, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": true, + "mode": "repulse" + }, + "onclick": { + "enable": false, + "mode": "push" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200 + }, + "push": { + "particles_nb": 0 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true, + "config_demo": { + "hide_card": true, + "background_color": "", + "background_image": "", + "background_position": "50% 50%", + "background_repeat": "no-repeat", + "background_size": "cover" + } + } + +); \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/public/_particles/js/particles.min.js b/.devcontainer/apps/ai-travel-advisor/public/_particles/js/particles.min.js new file mode 100644 index 0000000..b3d46d1 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/_particles/js/particles.min.js @@ -0,0 +1,9 @@ +/* ----------------------------------------------- +/* Author : Vincent Garreau - vincentgarreau.com +/* MIT license: http://opensource.org/licenses/MIT +/* Demo / Generator : vincentgarreau.com/particles.js +/* GitHub : github.com/VincentGarreau/particles.js +/* How to use? : Check the GitHub README +/* v2.0.0 +/* ----------------------------------------------- */ +function hexToRgb(e){var a=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(a,function(e,a,t,i){return a+a+t+t+i+i});var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}function clamp(e,a,t){return Math.min(Math.max(e,a),t)}function isInArray(e,a){return a.indexOf(e)>-1}var pJS=function(e,a){var t=document.querySelector("#"+e+" > .particles-js-canvas-el");this.pJS={canvas:{el:t,w:t.offsetWidth,h:t.offsetHeight},particles:{number:{value:400,density:{enable:!0,value_area:800}},color:{value:"#fff"},shape:{type:"circle",stroke:{width:0,color:"#ff0000"},polygon:{nb_sides:5},image:{src:"",width:100,height:100}},opacity:{value:1,random:!1,anim:{enable:!1,speed:2,opacity_min:0,sync:!1}},size:{value:20,random:!1,anim:{enable:!1,speed:20,size_min:0,sync:!1}},line_linked:{enable:!0,distance:100,color:"#fff",opacity:1,width:1},move:{enable:!0,speed:2,direction:"none",random:!1,straight:!1,out_mode:"out",bounce:!1,attract:{enable:!1,rotateX:3e3,rotateY:3e3}},array:[]},interactivity:{detect_on:"canvas",events:{onhover:{enable:!0,mode:"grab"},onclick:{enable:!0,mode:"push"},resize:!0},modes:{grab:{distance:100,line_linked:{opacity:1}},bubble:{distance:200,size:80,duration:.4},repulse:{distance:200,duration:.4},push:{particles_nb:4},remove:{particles_nb:2}},mouse:{}},retina_detect:!1,fn:{interact:{},modes:{},vendors:{}},tmp:{}};var i=this.pJS;a&&Object.deepExtend(i,a),i.tmp.obj={size_value:i.particles.size.value,size_anim_speed:i.particles.size.anim.speed,move_speed:i.particles.move.speed,line_linked_distance:i.particles.line_linked.distance,line_linked_width:i.particles.line_linked.width,mode_grab_distance:i.interactivity.modes.grab.distance,mode_bubble_distance:i.interactivity.modes.bubble.distance,mode_bubble_size:i.interactivity.modes.bubble.size,mode_repulse_distance:i.interactivity.modes.repulse.distance},i.fn.retinaInit=function(){i.retina_detect&&window.devicePixelRatio>1?(i.canvas.pxratio=window.devicePixelRatio,i.tmp.retina=!0):(i.canvas.pxratio=1,i.tmp.retina=!1),i.canvas.w=i.canvas.el.offsetWidth*i.canvas.pxratio,i.canvas.h=i.canvas.el.offsetHeight*i.canvas.pxratio,i.particles.size.value=i.tmp.obj.size_value*i.canvas.pxratio,i.particles.size.anim.speed=i.tmp.obj.size_anim_speed*i.canvas.pxratio,i.particles.move.speed=i.tmp.obj.move_speed*i.canvas.pxratio,i.particles.line_linked.distance=i.tmp.obj.line_linked_distance*i.canvas.pxratio,i.interactivity.modes.grab.distance=i.tmp.obj.mode_grab_distance*i.canvas.pxratio,i.interactivity.modes.bubble.distance=i.tmp.obj.mode_bubble_distance*i.canvas.pxratio,i.particles.line_linked.width=i.tmp.obj.line_linked_width*i.canvas.pxratio,i.interactivity.modes.bubble.size=i.tmp.obj.mode_bubble_size*i.canvas.pxratio,i.interactivity.modes.repulse.distance=i.tmp.obj.mode_repulse_distance*i.canvas.pxratio},i.fn.canvasInit=function(){i.canvas.ctx=i.canvas.el.getContext("2d")},i.fn.canvasSize=function(){i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i&&i.interactivity.events.resize&&window.addEventListener("resize",function(){i.canvas.w=i.canvas.el.offsetWidth,i.canvas.h=i.canvas.el.offsetHeight,i.tmp.retina&&(i.canvas.w*=i.canvas.pxratio,i.canvas.h*=i.canvas.pxratio),i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i.particles.move.enable||(i.fn.particlesEmpty(),i.fn.particlesCreate(),i.fn.particlesDraw(),i.fn.vendors.densityAutoParticles()),i.fn.vendors.densityAutoParticles()})},i.fn.canvasPaint=function(){i.canvas.ctx.fillRect(0,0,i.canvas.w,i.canvas.h)},i.fn.canvasClear=function(){i.canvas.ctx.clearRect(0,0,i.canvas.w,i.canvas.h)},i.fn.particle=function(e,a,t){if(this.radius=(i.particles.size.random?Math.random():1)*i.particles.size.value,i.particles.size.anim.enable&&(this.size_status=!1,this.vs=i.particles.size.anim.speed/100,i.particles.size.anim.sync||(this.vs=this.vs*Math.random())),this.x=t?t.x:Math.random()*i.canvas.w,this.y=t?t.y:Math.random()*i.canvas.h,this.x>i.canvas.w-2*this.radius?this.x=this.x-this.radius:this.x<2*this.radius&&(this.x=this.x+this.radius),this.y>i.canvas.h-2*this.radius?this.y=this.y-this.radius:this.y<2*this.radius&&(this.y=this.y+this.radius),i.particles.move.bounce&&i.fn.vendors.checkOverlap(this,t),this.color={},"object"==typeof e.value)if(e.value instanceof Array){var s=e.value[Math.floor(Math.random()*i.particles.color.value.length)];this.color.rgb=hexToRgb(s)}else void 0!=e.value.r&&void 0!=e.value.g&&void 0!=e.value.b&&(this.color.rgb={r:e.value.r,g:e.value.g,b:e.value.b}),void 0!=e.value.h&&void 0!=e.value.s&&void 0!=e.value.l&&(this.color.hsl={h:e.value.h,s:e.value.s,l:e.value.l});else"random"==e.value?this.color.rgb={r:Math.floor(256*Math.random())+0,g:Math.floor(256*Math.random())+0,b:Math.floor(256*Math.random())+0}:"string"==typeof e.value&&(this.color=e,this.color.rgb=hexToRgb(this.color.value));this.opacity=(i.particles.opacity.random?Math.random():1)*i.particles.opacity.value,i.particles.opacity.anim.enable&&(this.opacity_status=!1,this.vo=i.particles.opacity.anim.speed/100,i.particles.opacity.anim.sync||(this.vo=this.vo*Math.random()));var n={};switch(i.particles.move.direction){case"top":n={x:0,y:-1};break;case"top-right":n={x:.5,y:-.5};break;case"right":n={x:1,y:-0};break;case"bottom-right":n={x:.5,y:.5};break;case"bottom":n={x:0,y:1};break;case"bottom-left":n={x:-.5,y:1};break;case"left":n={x:-1,y:0};break;case"top-left":n={x:-.5,y:-.5};break;default:n={x:0,y:0}}i.particles.move.straight?(this.vx=n.x,this.vy=n.y,i.particles.move.random&&(this.vx=this.vx*Math.random(),this.vy=this.vy*Math.random())):(this.vx=n.x+Math.random()-.5,this.vy=n.y+Math.random()-.5),this.vx_i=this.vx,this.vy_i=this.vy;var r=i.particles.shape.type;if("object"==typeof r){if(r instanceof Array){var c=r[Math.floor(Math.random()*r.length)];this.shape=c}}else this.shape=r;if("image"==this.shape){var o=i.particles.shape;this.img={src:o.image.src,ratio:o.image.width/o.image.height},this.img.ratio||(this.img.ratio=1),"svg"==i.tmp.img_type&&void 0!=i.tmp.source_svg&&(i.fn.vendors.createSvgImg(this),i.tmp.pushing&&(this.img.loaded=!1))}},i.fn.particle.prototype.draw=function(){function e(){i.canvas.ctx.drawImage(r,a.x-t,a.y-t,2*t,2*t/a.img.ratio)}var a=this;if(void 0!=a.radius_bubble)var t=a.radius_bubble;else var t=a.radius;if(void 0!=a.opacity_bubble)var s=a.opacity_bubble;else var s=a.opacity;if(a.color.rgb)var n="rgba("+a.color.rgb.r+","+a.color.rgb.g+","+a.color.rgb.b+","+s+")";else var n="hsla("+a.color.hsl.h+","+a.color.hsl.s+"%,"+a.color.hsl.l+"%,"+s+")";switch(i.canvas.ctx.fillStyle=n,i.canvas.ctx.beginPath(),a.shape){case"circle":i.canvas.ctx.arc(a.x,a.y,t,0,2*Math.PI,!1);break;case"edge":i.canvas.ctx.rect(a.x-t,a.y-t,2*t,2*t);break;case"triangle":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t,a.y+t/1.66,2*t,3,2);break;case"polygon":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t/(i.particles.shape.polygon.nb_sides/3.5),a.y-t/.76,2.66*t/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,1);break;case"star":i.fn.vendors.drawShape(i.canvas.ctx,a.x-2*t/(i.particles.shape.polygon.nb_sides/4),a.y-t/1.52,2*t*2.66/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,2);break;case"image":if("svg"==i.tmp.img_type)var r=a.img.obj;else var r=i.tmp.img_obj;r&&e()}i.canvas.ctx.closePath(),i.particles.shape.stroke.width>0&&(i.canvas.ctx.strokeStyle=i.particles.shape.stroke.color,i.canvas.ctx.lineWidth=i.particles.shape.stroke.width,i.canvas.ctx.stroke()),i.canvas.ctx.fill()},i.fn.particlesCreate=function(){for(var e=0;e=i.particles.opacity.value&&(a.opacity_status=!1),a.opacity+=a.vo):(a.opacity<=i.particles.opacity.anim.opacity_min&&(a.opacity_status=!0),a.opacity-=a.vo),a.opacity<0&&(a.opacity=0)),i.particles.size.anim.enable&&(1==a.size_status?(a.radius>=i.particles.size.value&&(a.size_status=!1),a.radius+=a.vs):(a.radius<=i.particles.size.anim.size_min&&(a.size_status=!0),a.radius-=a.vs),a.radius<0&&(a.radius=0)),"bounce"==i.particles.move.out_mode)var s={x_left:a.radius,x_right:i.canvas.w,y_top:a.radius,y_bottom:i.canvas.h};else var s={x_left:-a.radius,x_right:i.canvas.w+a.radius,y_top:-a.radius,y_bottom:i.canvas.h+a.radius};switch(a.x-a.radius>i.canvas.w?(a.x=s.x_left,a.y=Math.random()*i.canvas.h):a.x+a.radius<0&&(a.x=s.x_right,a.y=Math.random()*i.canvas.h),a.y-a.radius>i.canvas.h?(a.y=s.y_top,a.x=Math.random()*i.canvas.w):a.y+a.radius<0&&(a.y=s.y_bottom,a.x=Math.random()*i.canvas.w),i.particles.move.out_mode){case"bounce":a.x+a.radius>i.canvas.w?a.vx=-a.vx:a.x-a.radius<0&&(a.vx=-a.vx),a.y+a.radius>i.canvas.h?a.vy=-a.vy:a.y-a.radius<0&&(a.vy=-a.vy)}if(isInArray("grab",i.interactivity.events.onhover.mode)&&i.fn.modes.grabParticle(a),(isInArray("bubble",i.interactivity.events.onhover.mode)||isInArray("bubble",i.interactivity.events.onclick.mode))&&i.fn.modes.bubbleParticle(a),(isInArray("repulse",i.interactivity.events.onhover.mode)||isInArray("repulse",i.interactivity.events.onclick.mode))&&i.fn.modes.repulseParticle(a),i.particles.line_linked.enable||i.particles.move.attract.enable)for(var n=e+1;n0){var c=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+c.r+","+c.g+","+c.b+","+r+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(a.x,a.y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}},i.fn.interact.attractParticles=function(e,a){var t=e.x-a.x,s=e.y-a.y,n=Math.sqrt(t*t+s*s);if(n<=i.particles.line_linked.distance){var r=t/(1e3*i.particles.move.attract.rotateX),c=s/(1e3*i.particles.move.attract.rotateY);e.vx-=r,e.vy-=c,a.vx+=r,a.vy+=c}},i.fn.interact.bounceParticles=function(e,a){var t=e.x-a.x,i=e.y-a.y,s=Math.sqrt(t*t+i*i),n=e.radius+a.radius;n>=s&&(e.vx=-e.vx,e.vy=-e.vy,a.vx=-a.vx,a.vy=-a.vy)},i.fn.modes.pushParticles=function(e,a){i.tmp.pushing=!0;for(var t=0;e>t;t++)i.particles.array.push(new i.fn.particle(i.particles.color,i.particles.opacity.value,{x:a?a.pos_x:Math.random()*i.canvas.w,y:a?a.pos_y:Math.random()*i.canvas.h})),t==e-1&&(i.particles.move.enable||i.fn.particlesDraw(),i.tmp.pushing=!1)},i.fn.modes.removeParticles=function(e){i.particles.array.splice(0,e),i.particles.move.enable||i.fn.particlesDraw()},i.fn.modes.bubbleParticle=function(e){function a(){e.opacity_bubble=e.opacity,e.radius_bubble=e.radius}function t(a,t,s,n,c){if(a!=t)if(i.tmp.bubble_duration_end){if(void 0!=s){var o=n-p*(n-a)/i.interactivity.modes.bubble.duration,l=a-o;d=a+l,"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else if(r<=i.interactivity.modes.bubble.distance){if(void 0!=s)var v=s;else var v=n;if(v!=a){var d=n-p*(n-a)/i.interactivity.modes.bubble.duration;"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else"size"==c&&(e.radius_bubble=void 0),"opacity"==c&&(e.opacity_bubble=void 0)}if(i.interactivity.events.onhover.enable&&isInArray("bubble",i.interactivity.events.onhover.mode)){var s=e.x-i.interactivity.mouse.pos_x,n=e.y-i.interactivity.mouse.pos_y,r=Math.sqrt(s*s+n*n),c=1-r/i.interactivity.modes.bubble.distance;if(r<=i.interactivity.modes.bubble.distance){if(c>=0&&"mousemove"==i.interactivity.status){if(i.interactivity.modes.bubble.size!=i.particles.size.value)if(i.interactivity.modes.bubble.size>i.particles.size.value){var o=e.radius+i.interactivity.modes.bubble.size*c;o>=0&&(e.radius_bubble=o)}else{var l=e.radius-i.interactivity.modes.bubble.size,o=e.radius-l*c;o>0?e.radius_bubble=o:e.radius_bubble=0}if(i.interactivity.modes.bubble.opacity!=i.particles.opacity.value)if(i.interactivity.modes.bubble.opacity>i.particles.opacity.value){var v=i.interactivity.modes.bubble.opacity*c;v>e.opacity&&v<=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}else{var v=e.opacity-(i.particles.opacity.value-i.interactivity.modes.bubble.opacity)*c;v=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}}}else a();"mouseleave"==i.interactivity.status&&a()}else if(i.interactivity.events.onclick.enable&&isInArray("bubble",i.interactivity.events.onclick.mode)){if(i.tmp.bubble_clicking){var s=e.x-i.interactivity.mouse.click_pos_x,n=e.y-i.interactivity.mouse.click_pos_y,r=Math.sqrt(s*s+n*n),p=((new Date).getTime()-i.interactivity.mouse.click_time)/1e3;p>i.interactivity.modes.bubble.duration&&(i.tmp.bubble_duration_end=!0),p>2*i.interactivity.modes.bubble.duration&&(i.tmp.bubble_clicking=!1,i.tmp.bubble_duration_end=!1)}i.tmp.bubble_clicking&&(t(i.interactivity.modes.bubble.size,i.particles.size.value,e.radius_bubble,e.radius,"size"),t(i.interactivity.modes.bubble.opacity,i.particles.opacity.value,e.opacity_bubble,e.opacity,"opacity"))}},i.fn.modes.repulseParticle=function(e){function a(){var a=Math.atan2(d,p);if(e.vx=u*Math.cos(a),e.vy=u*Math.sin(a),"bounce"==i.particles.move.out_mode){var t={x:e.x+e.vx,y:e.y+e.vy};t.x+e.radius>i.canvas.w?e.vx=-e.vx:t.x-e.radius<0&&(e.vx=-e.vx),t.y+e.radius>i.canvas.h?e.vy=-e.vy:t.y-e.radius<0&&(e.vy=-e.vy)}}if(i.interactivity.events.onhover.enable&&isInArray("repulse",i.interactivity.events.onhover.mode)&&"mousemove"==i.interactivity.status){var t=e.x-i.interactivity.mouse.pos_x,s=e.y-i.interactivity.mouse.pos_y,n=Math.sqrt(t*t+s*s),r={x:t/n,y:s/n},c=i.interactivity.modes.repulse.distance,o=100,l=clamp(1/c*(-1*Math.pow(n/c,2)+1)*c*o,0,50),v={x:e.x+r.x*l,y:e.y+r.y*l};"bounce"==i.particles.move.out_mode?(v.x-e.radius>0&&v.x+e.radius0&&v.y+e.radius=m&&a()}else 0==i.tmp.repulse_clicking&&(e.vx=e.vx_i,e.vy=e.vy_i)},i.fn.modes.grabParticle=function(e){if(i.interactivity.events.onhover.enable&&"mousemove"==i.interactivity.status){var a=e.x-i.interactivity.mouse.pos_x,t=e.y-i.interactivity.mouse.pos_y,s=Math.sqrt(a*a+t*t);if(s<=i.interactivity.modes.grab.distance){var n=i.interactivity.modes.grab.line_linked.opacity-s/(1/i.interactivity.modes.grab.line_linked.opacity)/i.interactivity.modes.grab.distance;if(n>0){var r=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+r.r+","+r.g+","+r.b+","+n+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(i.interactivity.mouse.pos_x,i.interactivity.mouse.pos_y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}}},i.fn.vendors.eventsListeners=function(){"window"==i.interactivity.detect_on?i.interactivity.el=window:i.interactivity.el=i.canvas.el,(i.interactivity.events.onhover.enable||i.interactivity.events.onclick.enable)&&(i.interactivity.el.addEventListener("mousemove",function(e){if(i.interactivity.el==window)var a=e.clientX,t=e.clientY;else var a=e.offsetX||e.clientX,t=e.offsetY||e.clientY;i.interactivity.mouse.pos_x=a,i.interactivity.mouse.pos_y=t,i.tmp.retina&&(i.interactivity.mouse.pos_x*=i.canvas.pxratio,i.interactivity.mouse.pos_y*=i.canvas.pxratio),i.interactivity.status="mousemove"}),i.interactivity.el.addEventListener("mouseleave",function(e){i.interactivity.mouse.pos_x=null,i.interactivity.mouse.pos_y=null,i.interactivity.status="mouseleave"})),i.interactivity.events.onclick.enable&&i.interactivity.el.addEventListener("click",function(){if(i.interactivity.mouse.click_pos_x=i.interactivity.mouse.pos_x,i.interactivity.mouse.click_pos_y=i.interactivity.mouse.pos_y,i.interactivity.mouse.click_time=(new Date).getTime(),i.interactivity.events.onclick.enable)switch(i.interactivity.events.onclick.mode){case"push":i.particles.move.enable?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):1==i.interactivity.modes.push.particles_nb?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):i.interactivity.modes.push.particles_nb>1&&i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb);break;case"remove":i.fn.modes.removeParticles(i.interactivity.modes.remove.particles_nb);break;case"bubble":i.tmp.bubble_clicking=!0;break;case"repulse":i.tmp.repulse_clicking=!0,i.tmp.repulse_count=0,i.tmp.repulse_finish=!1,setTimeout(function(){i.tmp.repulse_clicking=!1},1e3*i.interactivity.modes.repulse.duration)}})},i.fn.vendors.densityAutoParticles=function(){if(i.particles.number.density.enable){var e=i.canvas.el.width*i.canvas.el.height/1e3;i.tmp.retina&&(e/=2*i.canvas.pxratio);var a=e*i.particles.number.value/i.particles.number.density.value_area,t=i.particles.array.length-a;0>t?i.fn.modes.pushParticles(Math.abs(t)):i.fn.modes.removeParticles(t)}},i.fn.vendors.checkOverlap=function(e,a){for(var t=0;tv;v++)e.lineTo(i,0),e.translate(i,0),e.rotate(l);e.fill(),e.restore()},i.fn.vendors.exportImg=function(){window.open(i.canvas.el.toDataURL("image/png"),"_blank")},i.fn.vendors.loadImg=function(e){if(i.tmp.img_error=void 0,""!=i.particles.shape.image.src)if("svg"==e){var a=new XMLHttpRequest;a.open("GET",i.particles.shape.image.src),a.onreadystatechange=function(e){4==a.readyState&&(200==a.status?(i.tmp.source_svg=e.currentTarget.response,i.fn.vendors.checkBeforeDraw()):(console.log("Error pJS - Image not found"),i.tmp.img_error=!0))},a.send()}else{var t=new Image;t.addEventListener("load",function(){i.tmp.img_obj=t,i.fn.vendors.checkBeforeDraw()}),t.src=i.particles.shape.image.src}else console.log("Error pJS - No image.src"),i.tmp.img_error=!0},i.fn.vendors.draw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type?i.tmp.count_svg>=i.particles.number.value?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):void 0!=i.tmp.img_obj?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame))},i.fn.vendors.checkBeforeDraw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type&&void 0==i.tmp.source_svg?i.tmp.checkAnimFrame=requestAnimFrame(check):(cancelRequestAnimFrame(i.tmp.checkAnimFrame),i.tmp.img_error||(i.fn.vendors.init(),i.fn.vendors.draw())):(i.fn.vendors.init(),i.fn.vendors.draw())},i.fn.vendors.init=function(){i.fn.retinaInit(),i.fn.canvasInit(),i.fn.canvasSize(),i.fn.canvasPaint(),i.fn.particlesCreate(),i.fn.vendors.densityAutoParticles(),i.particles.line_linked.color_rgb_line=hexToRgb(i.particles.line_linked.color)},i.fn.vendors.start=function(){isInArray("image",i.particles.shape.type)?(i.tmp.img_type=i.particles.shape.image.src.substr(i.particles.shape.image.src.length-3),i.fn.vendors.loadImg(i.tmp.img_type)):i.fn.vendors.checkBeforeDraw()},i.fn.vendors.eventsListeners(),i.fn.vendors.start()};Object.deepExtend=function(e,a){for(var t in a)a[t]&&a[t].constructor&&a[t].constructor===Object?(e[t]=e[t]||{},arguments.callee(e[t],a[t])):e[t]=a[t];return e},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}(),window.cancelRequestAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelRequestAnimationFrame||window.mozCancelRequestAnimationFrame||window.oCancelRequestAnimationFrame||window.msCancelRequestAnimationFrame||clearTimeout}(),window.pJSDom=[],window.particlesJS=function(e,a){"string"!=typeof e&&(a=e,e="particles-js"),e||(e="particles-js");var t=document.getElementById(e),i="particles-js-canvas-el",s=t.getElementsByClassName(i);if(s.length)for(;s.length>0;)t.removeChild(s[0]);var n=document.createElement("canvas");n.className=i,n.style.width="100%",n.style.height="100%";var r=document.getElementById(e).appendChild(n);null!=r&&pJSDom.push(new pJS(e,a))},window.particlesJS.load=function(e,a,t){var i=new XMLHttpRequest;i.open("GET",a),i.onreadystatechange=function(a){if(4==i.readyState)if(200==i.status){var s=JSON.parse(a.currentTarget.response);window.particlesJS(e,s),t&&t()}else console.log("Error pJS - XMLHttpRequest status: "+i.status),console.log("Error pJS - File config not found")},i.send()}; \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/public/_particles/particles.json b/.devcontainer/apps/ai-travel-advisor/public/_particles/particles.json new file mode 100755 index 0000000..deaba4d --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/_particles/particles.json @@ -0,0 +1,116 @@ +{ + "particles": { + "number": { + "value": 80, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 5, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 6, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": false, + "mode": "repulse" + }, + "onclick": { + "enable": true, + "mode": "push" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200 + }, + "push": { + "particles_nb": 4 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true, + "config_demo": { + "hide_card": false, + "background_color": "#b61924", + "background_image": "", + "background_position": "50% 50%", + "background_repeat": "no-repeat", + "background_size": "cover" + } +} \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/public/css/particles.css b/.devcontainer/apps/ai-travel-advisor/public/css/particles.css new file mode 100755 index 0000000..fba35ab --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/css/particles.css @@ -0,0 +1,100 @@ +/* ============================================================================= + HTML5 CSS Reset Minified - Eric Meyer + ========================================================================== */ + +html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent} +body{line-height:1} +article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block} +nav ul{list-style:none} +blockquote,q{quotes:none} +blockquote:before,blockquote:after,q:before,q:after{content:none} +a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent;text-decoration:none} +mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold} +del{text-decoration:line-through} +abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help} +table{border-collapse:collapse;border-spacing:0} +hr{display:block;height:1px;border:0;border-top:1px solid #f6f6f6;margin:1em 0;padding:0} +input,select{vertical-align:middle} +li{list-style:none} + + +/* ============================================================================= + My CSS + ========================================================================== */ + +/* ---- base ---- */ + +html,body{ + width:100%; + height:100%; + background:#ffffff; + color:#000; +} + +html{ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body{ + font:normal 75% Arial, Helvetica, sans-serif; +} + +canvas{ + display:block; + vertical-align:bottom; +} + + +/* ---- stats.js ---- */ + +.count-particles{ + background: #000000; + position: absolute; + top: 48px; + left: 0; + width: 80px; + color: #0d5656; + font-size: .8em; + text-align: left; + text-indent: 4px; + line-height: 14px; + padding-bottom: 2px; + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} + +.js-count-particles{ + font-size: 1.1em; +} + +#stats, +.count-particles{ + -webkit-user-select: none; + margin-top: 5px; + margin-left: 5px; +} + +#stats{ + border-radius: 3px 3px 0 0; + overflow: hidden; +} + +.count-particles{ + border-radius: 0 0 3px 3px; +} + + +/* ---- particles.js container ---- +*/ + +#particles-js{ + width: 100%; + height: 300px; + background-size: cover; + position: absolute; /* or relative, fixed, or sticky depending on your layout */ + z-index: 2; + background-color: transparent; + background-image: url(''); + background-position: 50% 50%; + background-repeat: no-repeat; +} diff --git a/public/styles.360d4f28e3942a9ee9b5.css b/.devcontainer/apps/ai-travel-advisor/public/css/styles.360d4f28e3942a9ee9b5.css similarity index 100% rename from public/styles.360d4f28e3942a9ee9b5.css rename to .devcontainer/apps/ai-travel-advisor/public/css/styles.360d4f28e3942a9ee9b5.css diff --git a/.devcontainer/apps/ai-travel-advisor/public/css/styles.css b/.devcontainer/apps/ai-travel-advisor/public/css/styles.css new file mode 100644 index 0000000..c37b6d6 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/css/styles.css @@ -0,0 +1,327 @@ +.thumbs-up { + fill: #d9d9e3; /* Default color */ + transition: fill 0.3s ease; + cursor: pointer; + } + + .thumbs-up:hover { + fill: #0f0f0f; /* Color on hover */ + } + + .display-none[_ngcontent-aas-c37] { + display: none !important; } + + .fs-container[_ngcontent-aas-c37] { + display: block; + cursor: pointer; + position: fixed; + z-index: 1; + top: 16px; + left: 16px; + width: 46px; + height: 46px; + text-align: center; + padding: 0; + background-color: rgba(0, 0, 0, 0.2); + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; } + .fs-container[_ngcontent-aas-c37]:hover { + background-color: rgba(0, 0, 0, 0.33); } + .fs-container[_ngcontent-aas-c37] .arrow-exitfs[_ngcontent-aas-c37] { + display: block; + width: 30px; + height: 30px; + background: transparent; + border-top: 2px solid #f2f2f2; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; } + .fs-container[_ngcontent-aas-c37] .arrow-exitfs.prev[_ngcontent-aas-c37] { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + position: relative; + left: 18px; + top: 18px; } + .fs-container[_ngcontent-aas-c37] .arrow-exitfs[_ngcontent-aas-c37]:after { + content: ''; + width: 30px; + height: 30px; + background: transparent; + border-top: 2px solid #f2f2f2; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + position: absolute; + left: -15px; + top: -17px; } + + .slideshow-container.slideshow-container-fs[_ngcontent-aas-c37] { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; } + + .slideshow-container[_ngcontent-aas-c37] { + position: relative; + display: block; + margin: auto; + height: 100%; + width: 100%; + overflow: hidden; } + .slideshow-container[_ngcontent-aas-c37] .hide-slide[_ngcontent-aas-c37] { + visibility: hidden; + position: absolute; + top: -100vw; + left: -100vw; + opacity: 0; } + .slideshow-container[_ngcontent-aas-c37] .slides[_ngcontent-aas-c37] { + -ms-touch-action: pan-y; + touch-action: pan-y; + position: absolute; + top: 0; + height: 100%; + width: 100%; + visibility: visible; + opacity: 1; + display: block; } + .slideshow-container[_ngcontent-aas-c37] .slides.selected[_ngcontent-aas-c37] { + left: 0; } + .slideshow-container[_ngcontent-aas-c37] .slides.left-slide[_ngcontent-aas-c37] { + left: -100%; } + .slideshow-container[_ngcontent-aas-c37] .slides.right-slide[_ngcontent-aas-c37] { + left: 100%; } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-in-left[_ngcontent-aas-c37] { + left: 0; + -webkit-animation: slideInLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideInLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-in-right[_ngcontent-aas-c37] { + left: 0; + -webkit-animation: slideInRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideInRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-out-left[_ngcontent-aas-c37] { + left: -100%; + -webkit-animation: slideOutLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideOutLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-out-right[_ngcontent-aas-c37] { + left: 100%; + -webkit-animation: slideOutRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideOutRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.link[_ngcontent-aas-c37] { + cursor: pointer; } + .slideshow-container[_ngcontent-aas-c37] .slides[_ngcontent-aas-c37]:not(.link) { + cursor: default; } + .slideshow-container[_ngcontent-aas-c37] .caption[_ngcontent-aas-c37] { + position: absolute; + bottom: 0; + padding: 10px; + width: 100%; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + position: absolute; + top: 0; + height: 100%; + width: auto; + cursor: pointer; + background-size: 100%; + background-image: -webkit-gradient(linear, left top, left bottom, from(transparent), to(transparent)); + background-image: linear-gradient(transparent, transparent); + z-index: 100; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37]:before { + display: block; + height: 100%; + position: absolute; + top: 0; + left: 0; + opacity: 0; + width: 100%; + z-index: -100; + -webkit-transition: opacity 0.45s; + transition: opacity 0.45s; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.prev[_ngcontent-aas-c37] { + left: 0; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.prev[_ngcontent-aas-c37]:before { + background-image: -webkit-gradient(linear, right top, left top, from(transparent), to(rgba(0, 0, 0, 0.75))); + background-image: linear-gradient(to left, transparent, rgba(0, 0, 0, 0.75)); + content: ''; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.next[_ngcontent-aas-c37] { + right: 0; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.next[_ngcontent-aas-c37]:before { + background-image: -webkit-gradient(linear, left top, right top, from(transparent), to(rgba(0, 0, 0, 0.75))); + background-image: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.75)); + content: ''; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow[_ngcontent-aas-c37] { + display: block; + margin: auto; + width: 30px; + height: 30px; + background: transparent; + border-top: 2px solid #f2f2f2; + border-left: 2px solid #f2f2f2; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow[_ngcontent-aas-c37]:before { + display: block; + height: 200%; + width: 200%; + margin-left: -50%; + margin-top: -50%; + content: ""; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow.prev[_ngcontent-aas-c37] { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + position: relative; + left: 20px; + margin-right: 10px; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow.next[_ngcontent-aas-c37] { + -webkit-transform: rotate(135deg); + transform: rotate(135deg); + position: relative; + right: 20px; + margin-left: 10px; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] { + display: block; + bottom: 15px; + z-index: 1; + text-align: center; + position: absolute; + padding: 0; + left: 0; + right: 0; + margin: 0 auto; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li[_ngcontent-aas-c37] { + display: inline; + margin: 0; + padding: 0; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li[_ngcontent-aas-c37] button[_ngcontent-aas-c37] { + border: none; + background: none; + text-indent: -9999px; + font-size: 0; + width: 20px; + height: 20px; + outline: none; + position: relative; + z-index: 1; + cursor: pointer; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li[_ngcontent-aas-c37] button[_ngcontent-aas-c37]:before { + content: ''; + width: 4px; + height: 4px; + background: var(--dot-color, #FFF); + border-radius: 4px; + display: block; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + opacity: .7; + -webkit-transition: all .5s ease-out; + transition: all .5s ease-out; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li.slick-active[_ngcontent-aas-c37] button[_ngcontent-aas-c37]:before { + -webkit-transform: translate(-50%, -50%) scale(1.4); + transform: translate(-50%, -50%) scale(1.4); + opacity: 1; } + + @media screen and (min-width: 768px) { + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37]:hover:before { + opacity: 1; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37]:hover .arrow[_ngcontent-aas-c37] { + border-width: 4px; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow[_ngcontent-aas-c37]:hover { + border-width: 4px; } } + + @-webkit-keyframes slideInRight { + 0% { + left: -100%; } + 100% { + left: 0; } } + + @keyframes slideInRight { + 0% { + left: -100%; } + 100% { + left: 0; } } + + @-webkit-keyframes slideInLeft { + 0% { + left: 100%; } + 100% { + left: 0; } } + + @keyframes slideInLeft { + 0% { + left: 100%; } + 100% { + left: 0; } } + + @-webkit-keyframes slideOutRight { + 0% { + left: 0; } + 100% { + left: -100%; } } + + @keyframes slideOutRight { + 0% { + left: 0; } + 100% { + left: -100%; } } + + @-webkit-keyframes slideOutLeft { + 0% { + left: 0; } + 100% { + left: 100%; } } + + @keyframes slideOutLeft { + 0% { + left: 0; } + 100% { + left: 100%; } } + + .loader[_ngcontent-aas-c37] { + position: absolute; + left: 50%; + margin-left: -20px; + top: 50%; + margin-top: -20px; + border: 5px solid #f3f3f3; + border-top: 5px solid #555; + border-radius: 50%; + width: 50px; + height: 50px; + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; } + + @-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + + @keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/public/favicon.ico b/.devcontainer/apps/ai-travel-advisor/public/favicon.ico new file mode 100644 index 0000000..a50b8f4 Binary files /dev/null and b/.devcontainer/apps/ai-travel-advisor/public/favicon.ico differ diff --git a/.devcontainer/apps/ai-travel-advisor/public/images/Dt_Logo_Color-Horizontal.png b/.devcontainer/apps/ai-travel-advisor/public/images/Dt_Logo_Color-Horizontal.png new file mode 100644 index 0000000..683e2f2 Binary files /dev/null and b/.devcontainer/apps/ai-travel-advisor/public/images/Dt_Logo_Color-Horizontal.png differ diff --git a/.devcontainer/apps/ai-travel-advisor/public/images/ai-image.jpeg b/.devcontainer/apps/ai-travel-advisor/public/images/ai-image.jpeg new file mode 100644 index 0000000..04b819c Binary files /dev/null and b/.devcontainer/apps/ai-travel-advisor/public/images/ai-image.jpeg differ diff --git a/public/images/easytravel-logo.svg b/.devcontainer/apps/ai-travel-advisor/public/images/easytravel-logo.svg similarity index 100% rename from public/images/easytravel-logo.svg rename to .devcontainer/apps/ai-travel-advisor/public/images/easytravel-logo.svg diff --git a/public/images/hero-bg.jpg b/.devcontainer/apps/ai-travel-advisor/public/images/hero-bg.jpg similarity index 100% rename from public/images/hero-bg.jpg rename to .devcontainer/apps/ai-travel-advisor/public/images/hero-bg.jpg diff --git a/public/images/hero-gradient.png b/.devcontainer/apps/ai-travel-advisor/public/images/hero-gradient.png similarity index 100% rename from public/images/hero-gradient.png rename to .devcontainer/apps/ai-travel-advisor/public/images/hero-gradient.png diff --git a/public/images/icon-community.png b/.devcontainer/apps/ai-travel-advisor/public/images/icon-community.png similarity index 100% rename from public/images/icon-community.png rename to .devcontainer/apps/ai-travel-advisor/public/images/icon-community.png diff --git a/public/images/icon-facebook.png b/.devcontainer/apps/ai-travel-advisor/public/images/icon-facebook.png similarity index 100% rename from public/images/icon-facebook.png rename to .devcontainer/apps/ai-travel-advisor/public/images/icon-facebook.png diff --git a/public/images/icon-instagram.png b/.devcontainer/apps/ai-travel-advisor/public/images/icon-instagram.png similarity index 100% rename from public/images/icon-instagram.png rename to .devcontainer/apps/ai-travel-advisor/public/images/icon-instagram.png diff --git a/public/images/icon-rating-star-dark.png b/.devcontainer/apps/ai-travel-advisor/public/images/icon-rating-star-dark.png similarity index 100% rename from public/images/icon-rating-star-dark.png rename to .devcontainer/apps/ai-travel-advisor/public/images/icon-rating-star-dark.png diff --git a/public/images/icon-search.png b/.devcontainer/apps/ai-travel-advisor/public/images/icon-search.png similarity index 100% rename from public/images/icon-search.png rename to .devcontainer/apps/ai-travel-advisor/public/images/icon-search.png diff --git a/public/images/icon-twitter.png b/.devcontainer/apps/ai-travel-advisor/public/images/icon-twitter.png similarity index 100% rename from public/images/icon-twitter.png rename to .devcontainer/apps/ai-travel-advisor/public/images/icon-twitter.png diff --git a/public/images/loader-animation-freebie.gif b/.devcontainer/apps/ai-travel-advisor/public/images/loader-animation-freebie.gif similarity index 100% rename from public/images/loader-animation-freebie.gif rename to .devcontainer/apps/ai-travel-advisor/public/images/loader-animation-freebie.gif diff --git a/.devcontainer/apps/ai-travel-advisor/public/index.html b/.devcontainer/apps/ai-travel-advisor/public/index.html new file mode 100644 index 0000000..fd1e170 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/index.html @@ -0,0 +1,200 @@ + + + + + + Your AI travel advisor + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + +
+ +
+
+ + +
+
+ +

AItravel advisor +
+ +
+
+
+
+ +
+ + +
+
+
+ +

+
+
+ + +
+
+ +
+
+
+ +
+ + + +
+ +
+
+ +
+ + + +
+
+
+
+ + + + + + + + + diff --git a/.devcontainer/apps/ai-travel-advisor/public/js/app.js b/.devcontainer/apps/ai-travel-advisor/public/js/app.js new file mode 100644 index 0000000..f020af8 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/js/app.js @@ -0,0 +1,133 @@ +/* ----------------------------------------------- +/* How to use? : Check the GitHub README +/* ----------------------------------------------- */ + +/* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */ +/* +particlesJS.load('particles-js', 'particles.json', function() { + console.log('particles.js loaded - callback'); +}); +*/ + +/* Otherwise just put the config content (json): */ + +particlesJS('particles-js', + + { + "particles": { + "number": { + "value": 90, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 5, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 6, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": true, + "mode": "repulse" + }, + "onclick": { + "enable": false, + "mode": "push" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200 + }, + "push": { + "particles_nb": 0 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true, + "config_demo": { + "hide_card": true, + "background_color": "", + "background_image": "", + "background_position": "50% 50%", + "background_repeat": "no-repeat", + "background_size": "cover" + } + } + +); \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/public/js/particles.min.js b/.devcontainer/apps/ai-travel-advisor/public/js/particles.min.js new file mode 100644 index 0000000..b3d46d1 --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/public/js/particles.min.js @@ -0,0 +1,9 @@ +/* ----------------------------------------------- +/* Author : Vincent Garreau - vincentgarreau.com +/* MIT license: http://opensource.org/licenses/MIT +/* Demo / Generator : vincentgarreau.com/particles.js +/* GitHub : github.com/VincentGarreau/particles.js +/* How to use? : Check the GitHub README +/* v2.0.0 +/* ----------------------------------------------- */ +function hexToRgb(e){var a=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(a,function(e,a,t,i){return a+a+t+t+i+i});var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}function clamp(e,a,t){return Math.min(Math.max(e,a),t)}function isInArray(e,a){return a.indexOf(e)>-1}var pJS=function(e,a){var t=document.querySelector("#"+e+" > .particles-js-canvas-el");this.pJS={canvas:{el:t,w:t.offsetWidth,h:t.offsetHeight},particles:{number:{value:400,density:{enable:!0,value_area:800}},color:{value:"#fff"},shape:{type:"circle",stroke:{width:0,color:"#ff0000"},polygon:{nb_sides:5},image:{src:"",width:100,height:100}},opacity:{value:1,random:!1,anim:{enable:!1,speed:2,opacity_min:0,sync:!1}},size:{value:20,random:!1,anim:{enable:!1,speed:20,size_min:0,sync:!1}},line_linked:{enable:!0,distance:100,color:"#fff",opacity:1,width:1},move:{enable:!0,speed:2,direction:"none",random:!1,straight:!1,out_mode:"out",bounce:!1,attract:{enable:!1,rotateX:3e3,rotateY:3e3}},array:[]},interactivity:{detect_on:"canvas",events:{onhover:{enable:!0,mode:"grab"},onclick:{enable:!0,mode:"push"},resize:!0},modes:{grab:{distance:100,line_linked:{opacity:1}},bubble:{distance:200,size:80,duration:.4},repulse:{distance:200,duration:.4},push:{particles_nb:4},remove:{particles_nb:2}},mouse:{}},retina_detect:!1,fn:{interact:{},modes:{},vendors:{}},tmp:{}};var i=this.pJS;a&&Object.deepExtend(i,a),i.tmp.obj={size_value:i.particles.size.value,size_anim_speed:i.particles.size.anim.speed,move_speed:i.particles.move.speed,line_linked_distance:i.particles.line_linked.distance,line_linked_width:i.particles.line_linked.width,mode_grab_distance:i.interactivity.modes.grab.distance,mode_bubble_distance:i.interactivity.modes.bubble.distance,mode_bubble_size:i.interactivity.modes.bubble.size,mode_repulse_distance:i.interactivity.modes.repulse.distance},i.fn.retinaInit=function(){i.retina_detect&&window.devicePixelRatio>1?(i.canvas.pxratio=window.devicePixelRatio,i.tmp.retina=!0):(i.canvas.pxratio=1,i.tmp.retina=!1),i.canvas.w=i.canvas.el.offsetWidth*i.canvas.pxratio,i.canvas.h=i.canvas.el.offsetHeight*i.canvas.pxratio,i.particles.size.value=i.tmp.obj.size_value*i.canvas.pxratio,i.particles.size.anim.speed=i.tmp.obj.size_anim_speed*i.canvas.pxratio,i.particles.move.speed=i.tmp.obj.move_speed*i.canvas.pxratio,i.particles.line_linked.distance=i.tmp.obj.line_linked_distance*i.canvas.pxratio,i.interactivity.modes.grab.distance=i.tmp.obj.mode_grab_distance*i.canvas.pxratio,i.interactivity.modes.bubble.distance=i.tmp.obj.mode_bubble_distance*i.canvas.pxratio,i.particles.line_linked.width=i.tmp.obj.line_linked_width*i.canvas.pxratio,i.interactivity.modes.bubble.size=i.tmp.obj.mode_bubble_size*i.canvas.pxratio,i.interactivity.modes.repulse.distance=i.tmp.obj.mode_repulse_distance*i.canvas.pxratio},i.fn.canvasInit=function(){i.canvas.ctx=i.canvas.el.getContext("2d")},i.fn.canvasSize=function(){i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i&&i.interactivity.events.resize&&window.addEventListener("resize",function(){i.canvas.w=i.canvas.el.offsetWidth,i.canvas.h=i.canvas.el.offsetHeight,i.tmp.retina&&(i.canvas.w*=i.canvas.pxratio,i.canvas.h*=i.canvas.pxratio),i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i.particles.move.enable||(i.fn.particlesEmpty(),i.fn.particlesCreate(),i.fn.particlesDraw(),i.fn.vendors.densityAutoParticles()),i.fn.vendors.densityAutoParticles()})},i.fn.canvasPaint=function(){i.canvas.ctx.fillRect(0,0,i.canvas.w,i.canvas.h)},i.fn.canvasClear=function(){i.canvas.ctx.clearRect(0,0,i.canvas.w,i.canvas.h)},i.fn.particle=function(e,a,t){if(this.radius=(i.particles.size.random?Math.random():1)*i.particles.size.value,i.particles.size.anim.enable&&(this.size_status=!1,this.vs=i.particles.size.anim.speed/100,i.particles.size.anim.sync||(this.vs=this.vs*Math.random())),this.x=t?t.x:Math.random()*i.canvas.w,this.y=t?t.y:Math.random()*i.canvas.h,this.x>i.canvas.w-2*this.radius?this.x=this.x-this.radius:this.x<2*this.radius&&(this.x=this.x+this.radius),this.y>i.canvas.h-2*this.radius?this.y=this.y-this.radius:this.y<2*this.radius&&(this.y=this.y+this.radius),i.particles.move.bounce&&i.fn.vendors.checkOverlap(this,t),this.color={},"object"==typeof e.value)if(e.value instanceof Array){var s=e.value[Math.floor(Math.random()*i.particles.color.value.length)];this.color.rgb=hexToRgb(s)}else void 0!=e.value.r&&void 0!=e.value.g&&void 0!=e.value.b&&(this.color.rgb={r:e.value.r,g:e.value.g,b:e.value.b}),void 0!=e.value.h&&void 0!=e.value.s&&void 0!=e.value.l&&(this.color.hsl={h:e.value.h,s:e.value.s,l:e.value.l});else"random"==e.value?this.color.rgb={r:Math.floor(256*Math.random())+0,g:Math.floor(256*Math.random())+0,b:Math.floor(256*Math.random())+0}:"string"==typeof e.value&&(this.color=e,this.color.rgb=hexToRgb(this.color.value));this.opacity=(i.particles.opacity.random?Math.random():1)*i.particles.opacity.value,i.particles.opacity.anim.enable&&(this.opacity_status=!1,this.vo=i.particles.opacity.anim.speed/100,i.particles.opacity.anim.sync||(this.vo=this.vo*Math.random()));var n={};switch(i.particles.move.direction){case"top":n={x:0,y:-1};break;case"top-right":n={x:.5,y:-.5};break;case"right":n={x:1,y:-0};break;case"bottom-right":n={x:.5,y:.5};break;case"bottom":n={x:0,y:1};break;case"bottom-left":n={x:-.5,y:1};break;case"left":n={x:-1,y:0};break;case"top-left":n={x:-.5,y:-.5};break;default:n={x:0,y:0}}i.particles.move.straight?(this.vx=n.x,this.vy=n.y,i.particles.move.random&&(this.vx=this.vx*Math.random(),this.vy=this.vy*Math.random())):(this.vx=n.x+Math.random()-.5,this.vy=n.y+Math.random()-.5),this.vx_i=this.vx,this.vy_i=this.vy;var r=i.particles.shape.type;if("object"==typeof r){if(r instanceof Array){var c=r[Math.floor(Math.random()*r.length)];this.shape=c}}else this.shape=r;if("image"==this.shape){var o=i.particles.shape;this.img={src:o.image.src,ratio:o.image.width/o.image.height},this.img.ratio||(this.img.ratio=1),"svg"==i.tmp.img_type&&void 0!=i.tmp.source_svg&&(i.fn.vendors.createSvgImg(this),i.tmp.pushing&&(this.img.loaded=!1))}},i.fn.particle.prototype.draw=function(){function e(){i.canvas.ctx.drawImage(r,a.x-t,a.y-t,2*t,2*t/a.img.ratio)}var a=this;if(void 0!=a.radius_bubble)var t=a.radius_bubble;else var t=a.radius;if(void 0!=a.opacity_bubble)var s=a.opacity_bubble;else var s=a.opacity;if(a.color.rgb)var n="rgba("+a.color.rgb.r+","+a.color.rgb.g+","+a.color.rgb.b+","+s+")";else var n="hsla("+a.color.hsl.h+","+a.color.hsl.s+"%,"+a.color.hsl.l+"%,"+s+")";switch(i.canvas.ctx.fillStyle=n,i.canvas.ctx.beginPath(),a.shape){case"circle":i.canvas.ctx.arc(a.x,a.y,t,0,2*Math.PI,!1);break;case"edge":i.canvas.ctx.rect(a.x-t,a.y-t,2*t,2*t);break;case"triangle":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t,a.y+t/1.66,2*t,3,2);break;case"polygon":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t/(i.particles.shape.polygon.nb_sides/3.5),a.y-t/.76,2.66*t/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,1);break;case"star":i.fn.vendors.drawShape(i.canvas.ctx,a.x-2*t/(i.particles.shape.polygon.nb_sides/4),a.y-t/1.52,2*t*2.66/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,2);break;case"image":if("svg"==i.tmp.img_type)var r=a.img.obj;else var r=i.tmp.img_obj;r&&e()}i.canvas.ctx.closePath(),i.particles.shape.stroke.width>0&&(i.canvas.ctx.strokeStyle=i.particles.shape.stroke.color,i.canvas.ctx.lineWidth=i.particles.shape.stroke.width,i.canvas.ctx.stroke()),i.canvas.ctx.fill()},i.fn.particlesCreate=function(){for(var e=0;e=i.particles.opacity.value&&(a.opacity_status=!1),a.opacity+=a.vo):(a.opacity<=i.particles.opacity.anim.opacity_min&&(a.opacity_status=!0),a.opacity-=a.vo),a.opacity<0&&(a.opacity=0)),i.particles.size.anim.enable&&(1==a.size_status?(a.radius>=i.particles.size.value&&(a.size_status=!1),a.radius+=a.vs):(a.radius<=i.particles.size.anim.size_min&&(a.size_status=!0),a.radius-=a.vs),a.radius<0&&(a.radius=0)),"bounce"==i.particles.move.out_mode)var s={x_left:a.radius,x_right:i.canvas.w,y_top:a.radius,y_bottom:i.canvas.h};else var s={x_left:-a.radius,x_right:i.canvas.w+a.radius,y_top:-a.radius,y_bottom:i.canvas.h+a.radius};switch(a.x-a.radius>i.canvas.w?(a.x=s.x_left,a.y=Math.random()*i.canvas.h):a.x+a.radius<0&&(a.x=s.x_right,a.y=Math.random()*i.canvas.h),a.y-a.radius>i.canvas.h?(a.y=s.y_top,a.x=Math.random()*i.canvas.w):a.y+a.radius<0&&(a.y=s.y_bottom,a.x=Math.random()*i.canvas.w),i.particles.move.out_mode){case"bounce":a.x+a.radius>i.canvas.w?a.vx=-a.vx:a.x-a.radius<0&&(a.vx=-a.vx),a.y+a.radius>i.canvas.h?a.vy=-a.vy:a.y-a.radius<0&&(a.vy=-a.vy)}if(isInArray("grab",i.interactivity.events.onhover.mode)&&i.fn.modes.grabParticle(a),(isInArray("bubble",i.interactivity.events.onhover.mode)||isInArray("bubble",i.interactivity.events.onclick.mode))&&i.fn.modes.bubbleParticle(a),(isInArray("repulse",i.interactivity.events.onhover.mode)||isInArray("repulse",i.interactivity.events.onclick.mode))&&i.fn.modes.repulseParticle(a),i.particles.line_linked.enable||i.particles.move.attract.enable)for(var n=e+1;n0){var c=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+c.r+","+c.g+","+c.b+","+r+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(a.x,a.y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}},i.fn.interact.attractParticles=function(e,a){var t=e.x-a.x,s=e.y-a.y,n=Math.sqrt(t*t+s*s);if(n<=i.particles.line_linked.distance){var r=t/(1e3*i.particles.move.attract.rotateX),c=s/(1e3*i.particles.move.attract.rotateY);e.vx-=r,e.vy-=c,a.vx+=r,a.vy+=c}},i.fn.interact.bounceParticles=function(e,a){var t=e.x-a.x,i=e.y-a.y,s=Math.sqrt(t*t+i*i),n=e.radius+a.radius;n>=s&&(e.vx=-e.vx,e.vy=-e.vy,a.vx=-a.vx,a.vy=-a.vy)},i.fn.modes.pushParticles=function(e,a){i.tmp.pushing=!0;for(var t=0;e>t;t++)i.particles.array.push(new i.fn.particle(i.particles.color,i.particles.opacity.value,{x:a?a.pos_x:Math.random()*i.canvas.w,y:a?a.pos_y:Math.random()*i.canvas.h})),t==e-1&&(i.particles.move.enable||i.fn.particlesDraw(),i.tmp.pushing=!1)},i.fn.modes.removeParticles=function(e){i.particles.array.splice(0,e),i.particles.move.enable||i.fn.particlesDraw()},i.fn.modes.bubbleParticle=function(e){function a(){e.opacity_bubble=e.opacity,e.radius_bubble=e.radius}function t(a,t,s,n,c){if(a!=t)if(i.tmp.bubble_duration_end){if(void 0!=s){var o=n-p*(n-a)/i.interactivity.modes.bubble.duration,l=a-o;d=a+l,"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else if(r<=i.interactivity.modes.bubble.distance){if(void 0!=s)var v=s;else var v=n;if(v!=a){var d=n-p*(n-a)/i.interactivity.modes.bubble.duration;"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else"size"==c&&(e.radius_bubble=void 0),"opacity"==c&&(e.opacity_bubble=void 0)}if(i.interactivity.events.onhover.enable&&isInArray("bubble",i.interactivity.events.onhover.mode)){var s=e.x-i.interactivity.mouse.pos_x,n=e.y-i.interactivity.mouse.pos_y,r=Math.sqrt(s*s+n*n),c=1-r/i.interactivity.modes.bubble.distance;if(r<=i.interactivity.modes.bubble.distance){if(c>=0&&"mousemove"==i.interactivity.status){if(i.interactivity.modes.bubble.size!=i.particles.size.value)if(i.interactivity.modes.bubble.size>i.particles.size.value){var o=e.radius+i.interactivity.modes.bubble.size*c;o>=0&&(e.radius_bubble=o)}else{var l=e.radius-i.interactivity.modes.bubble.size,o=e.radius-l*c;o>0?e.radius_bubble=o:e.radius_bubble=0}if(i.interactivity.modes.bubble.opacity!=i.particles.opacity.value)if(i.interactivity.modes.bubble.opacity>i.particles.opacity.value){var v=i.interactivity.modes.bubble.opacity*c;v>e.opacity&&v<=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}else{var v=e.opacity-(i.particles.opacity.value-i.interactivity.modes.bubble.opacity)*c;v=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}}}else a();"mouseleave"==i.interactivity.status&&a()}else if(i.interactivity.events.onclick.enable&&isInArray("bubble",i.interactivity.events.onclick.mode)){if(i.tmp.bubble_clicking){var s=e.x-i.interactivity.mouse.click_pos_x,n=e.y-i.interactivity.mouse.click_pos_y,r=Math.sqrt(s*s+n*n),p=((new Date).getTime()-i.interactivity.mouse.click_time)/1e3;p>i.interactivity.modes.bubble.duration&&(i.tmp.bubble_duration_end=!0),p>2*i.interactivity.modes.bubble.duration&&(i.tmp.bubble_clicking=!1,i.tmp.bubble_duration_end=!1)}i.tmp.bubble_clicking&&(t(i.interactivity.modes.bubble.size,i.particles.size.value,e.radius_bubble,e.radius,"size"),t(i.interactivity.modes.bubble.opacity,i.particles.opacity.value,e.opacity_bubble,e.opacity,"opacity"))}},i.fn.modes.repulseParticle=function(e){function a(){var a=Math.atan2(d,p);if(e.vx=u*Math.cos(a),e.vy=u*Math.sin(a),"bounce"==i.particles.move.out_mode){var t={x:e.x+e.vx,y:e.y+e.vy};t.x+e.radius>i.canvas.w?e.vx=-e.vx:t.x-e.radius<0&&(e.vx=-e.vx),t.y+e.radius>i.canvas.h?e.vy=-e.vy:t.y-e.radius<0&&(e.vy=-e.vy)}}if(i.interactivity.events.onhover.enable&&isInArray("repulse",i.interactivity.events.onhover.mode)&&"mousemove"==i.interactivity.status){var t=e.x-i.interactivity.mouse.pos_x,s=e.y-i.interactivity.mouse.pos_y,n=Math.sqrt(t*t+s*s),r={x:t/n,y:s/n},c=i.interactivity.modes.repulse.distance,o=100,l=clamp(1/c*(-1*Math.pow(n/c,2)+1)*c*o,0,50),v={x:e.x+r.x*l,y:e.y+r.y*l};"bounce"==i.particles.move.out_mode?(v.x-e.radius>0&&v.x+e.radius0&&v.y+e.radius=m&&a()}else 0==i.tmp.repulse_clicking&&(e.vx=e.vx_i,e.vy=e.vy_i)},i.fn.modes.grabParticle=function(e){if(i.interactivity.events.onhover.enable&&"mousemove"==i.interactivity.status){var a=e.x-i.interactivity.mouse.pos_x,t=e.y-i.interactivity.mouse.pos_y,s=Math.sqrt(a*a+t*t);if(s<=i.interactivity.modes.grab.distance){var n=i.interactivity.modes.grab.line_linked.opacity-s/(1/i.interactivity.modes.grab.line_linked.opacity)/i.interactivity.modes.grab.distance;if(n>0){var r=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+r.r+","+r.g+","+r.b+","+n+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(i.interactivity.mouse.pos_x,i.interactivity.mouse.pos_y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}}},i.fn.vendors.eventsListeners=function(){"window"==i.interactivity.detect_on?i.interactivity.el=window:i.interactivity.el=i.canvas.el,(i.interactivity.events.onhover.enable||i.interactivity.events.onclick.enable)&&(i.interactivity.el.addEventListener("mousemove",function(e){if(i.interactivity.el==window)var a=e.clientX,t=e.clientY;else var a=e.offsetX||e.clientX,t=e.offsetY||e.clientY;i.interactivity.mouse.pos_x=a,i.interactivity.mouse.pos_y=t,i.tmp.retina&&(i.interactivity.mouse.pos_x*=i.canvas.pxratio,i.interactivity.mouse.pos_y*=i.canvas.pxratio),i.interactivity.status="mousemove"}),i.interactivity.el.addEventListener("mouseleave",function(e){i.interactivity.mouse.pos_x=null,i.interactivity.mouse.pos_y=null,i.interactivity.status="mouseleave"})),i.interactivity.events.onclick.enable&&i.interactivity.el.addEventListener("click",function(){if(i.interactivity.mouse.click_pos_x=i.interactivity.mouse.pos_x,i.interactivity.mouse.click_pos_y=i.interactivity.mouse.pos_y,i.interactivity.mouse.click_time=(new Date).getTime(),i.interactivity.events.onclick.enable)switch(i.interactivity.events.onclick.mode){case"push":i.particles.move.enable?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):1==i.interactivity.modes.push.particles_nb?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):i.interactivity.modes.push.particles_nb>1&&i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb);break;case"remove":i.fn.modes.removeParticles(i.interactivity.modes.remove.particles_nb);break;case"bubble":i.tmp.bubble_clicking=!0;break;case"repulse":i.tmp.repulse_clicking=!0,i.tmp.repulse_count=0,i.tmp.repulse_finish=!1,setTimeout(function(){i.tmp.repulse_clicking=!1},1e3*i.interactivity.modes.repulse.duration)}})},i.fn.vendors.densityAutoParticles=function(){if(i.particles.number.density.enable){var e=i.canvas.el.width*i.canvas.el.height/1e3;i.tmp.retina&&(e/=2*i.canvas.pxratio);var a=e*i.particles.number.value/i.particles.number.density.value_area,t=i.particles.array.length-a;0>t?i.fn.modes.pushParticles(Math.abs(t)):i.fn.modes.removeParticles(t)}},i.fn.vendors.checkOverlap=function(e,a){for(var t=0;tv;v++)e.lineTo(i,0),e.translate(i,0),e.rotate(l);e.fill(),e.restore()},i.fn.vendors.exportImg=function(){window.open(i.canvas.el.toDataURL("image/png"),"_blank")},i.fn.vendors.loadImg=function(e){if(i.tmp.img_error=void 0,""!=i.particles.shape.image.src)if("svg"==e){var a=new XMLHttpRequest;a.open("GET",i.particles.shape.image.src),a.onreadystatechange=function(e){4==a.readyState&&(200==a.status?(i.tmp.source_svg=e.currentTarget.response,i.fn.vendors.checkBeforeDraw()):(console.log("Error pJS - Image not found"),i.tmp.img_error=!0))},a.send()}else{var t=new Image;t.addEventListener("load",function(){i.tmp.img_obj=t,i.fn.vendors.checkBeforeDraw()}),t.src=i.particles.shape.image.src}else console.log("Error pJS - No image.src"),i.tmp.img_error=!0},i.fn.vendors.draw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type?i.tmp.count_svg>=i.particles.number.value?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):void 0!=i.tmp.img_obj?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame))},i.fn.vendors.checkBeforeDraw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type&&void 0==i.tmp.source_svg?i.tmp.checkAnimFrame=requestAnimFrame(check):(cancelRequestAnimFrame(i.tmp.checkAnimFrame),i.tmp.img_error||(i.fn.vendors.init(),i.fn.vendors.draw())):(i.fn.vendors.init(),i.fn.vendors.draw())},i.fn.vendors.init=function(){i.fn.retinaInit(),i.fn.canvasInit(),i.fn.canvasSize(),i.fn.canvasPaint(),i.fn.particlesCreate(),i.fn.vendors.densityAutoParticles(),i.particles.line_linked.color_rgb_line=hexToRgb(i.particles.line_linked.color)},i.fn.vendors.start=function(){isInArray("image",i.particles.shape.type)?(i.tmp.img_type=i.particles.shape.image.src.substr(i.particles.shape.image.src.length-3),i.fn.vendors.loadImg(i.tmp.img_type)):i.fn.vendors.checkBeforeDraw()},i.fn.vendors.eventsListeners(),i.fn.vendors.start()};Object.deepExtend=function(e,a){for(var t in a)a[t]&&a[t].constructor&&a[t].constructor===Object?(e[t]=e[t]||{},arguments.callee(e[t],a[t])):e[t]=a[t];return e},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}(),window.cancelRequestAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelRequestAnimationFrame||window.mozCancelRequestAnimationFrame||window.oCancelRequestAnimationFrame||window.msCancelRequestAnimationFrame||clearTimeout}(),window.pJSDom=[],window.particlesJS=function(e,a){"string"!=typeof e&&(a=e,e="particles-js"),e||(e="particles-js");var t=document.getElementById(e),i="particles-js-canvas-el",s=t.getElementsByClassName(i);if(s.length)for(;s.length>0;)t.removeChild(s[0]);var n=document.createElement("canvas");n.className=i,n.style.width="100%",n.style.height="100%";var r=document.getElementById(e).appendChild(n);null!=r&&pJSDom.push(new pJS(e,a))},window.particlesJS.load=function(e,a,t){var i=new XMLHttpRequest;i.open("GET",a),i.onreadystatechange=function(a){if(4==i.readyState)if(200==i.status){var s=JSON.parse(a.currentTarget.response);window.particlesJS(e,s),t&&t()}else console.log("Error pJS - XMLHttpRequest status: "+i.status),console.log("Error pJS - File config not found")},i.send()}; \ No newline at end of file diff --git a/.devcontainer/apps/ai-travel-advisor/requirements.txt b/.devcontainer/apps/ai-travel-advisor/requirements.txt new file mode 100644 index 0000000..f8b7dfa --- /dev/null +++ b/.devcontainer/apps/ai-travel-advisor/requirements.txt @@ -0,0 +1,10 @@ +fastapi == 0.115.12 +uvicorn == 0.34.2 +traceloop-sdk == 0.36.1 +ollama~=0.4.8 +langchain==0.3.23 +langchain-weaviate==0.0.4 +langchain-community==0.3.27 +langchain-ollama==0.3.2 +beautifulsoup4==4.13.4 +lxml==5.3.2 \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/config-map-flagd-patch.yaml b/.devcontainer/apps/astroshop/astroshop/config-map-flagd-patch.yaml new file mode 100644 index 0000000..6ea81cc --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/config-map-flagd-patch.yaml @@ -0,0 +1,107 @@ +data: + demo.flagd.json: | + { + "$schema": "https://flagd.dev/schema/v0/flags.json", + "flags": { + "productCatalogFailure": { + "description": "Fail product catalog service on a specific product", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "recommendationServiceCacheFailure": { + "description": "Fail recommendation service cache", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "adServiceManualGc": { + "description": "Triggers full manual garbage collections in the ad service", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "adServiceHighCpu": { + "description": "Triggers high cpu load in the ad service", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "adServiceFailure": { + "description": "Fail ad service", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "kafkaQueueProblems": { + "description": "Overloads Kafka queue while simultaneously introducing a consumer side delay leading to a lag spike", + "state": "ENABLED", + "variants": { + "on": 100, + "off": 0 + }, + "defaultVariant": "off" + }, + "cartServiceFailure": { + "description": "Fail cart service", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "paymentServiceFailure": { + "description": "Fail payment service charge requests", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "paymentServiceUnreachable": { + "description": "Payment service is unavailable", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, + "loadgeneratorFloodHomepage": { + "description": "Flood the frontend with a large amount of requests.", + "state": "ENABLED", + "variants": { + "on": 100, + "off": 0 + }, + "defaultVariant": "off" + }, + "imageSlowLoad": { + "description": "slow loading images in the frontend", + "state": "ENABLED", + "variants": { + "10sec": 10000, + "5sec": 5000, + "off": 0 + }, + "defaultVariant": "off" + } + } + } diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-accountingservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-accountingservice-patch.yaml new file mode 100644 index 0000000..e0ad880 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-accountingservice-patch.yaml @@ -0,0 +1,47 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: ".NET" + spec: + containers: + - name: accountingservice + image: ghcr.io/open-telemetry/demo:1.12.0-accountingservice + imagePullPolicy: Always + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 120Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + env: + - name: Logging__LogLevel__Default # https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-8.0 + value: "Debug" + - name: Logging__LogLevel__Microsoft + value: "Information" + - name: Logging__LogLevel__Microsoft.AspNetCore + value: "Information" + - name: Logging__LogLevel__Microsoft.AspNetCore.Mvc + value: "Warning" + - name: Logging__LogLevel__Microsoft.AspNetCore.Routing + value: "Warning" + - name: Logging__LogLevel__Microsoft.Hosting.Lifetime + value: "Warning" + - name: Logging__LogLevel__Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware + value: "Information" + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_DOTNET_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED + value: 'false' # Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/net/instrumentations/ + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-adservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-adservice-patch.yaml new file mode 100644 index 0000000..93a8170 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-adservice-patch.yaml @@ -0,0 +1,33 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "Java" + spec: + containers: + - name: adservice + image: ghcr.io/open-telemetry/demo:1.12.0-adservice + imagePullPolicy: Always + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 300Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + env: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_JAVA_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: JAVA_TOOL_OPTIONS + value: '' # '-javaagent:/usr/src/app/opentelemetry-javaagent.jar' # - Duplicate spans from OA and Otel are avoided automatically - https://docs.dynatrace.com/docs/shortlink/opentelemetry-oneagent#java-span-dropping + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-cartservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-cartservice-patch.yaml new file mode 100644 index 0000000..05822c9 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-cartservice-patch.yaml @@ -0,0 +1,47 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: ".NET" + spec: + containers: + - name: cartservice + image: ghcr.io/open-telemetry/demo:1.12.0-cartservice + imagePullPolicy: Always + resources: + limits: + memory: 512Mi # Original 120Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + env: + - name: Logging__LogLevel__Default # https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-8.0 + value: "Debug" + - name: Logging__LogLevel__Microsoft + value: "Information" + - name: Logging__LogLevel__Microsoft.AspNetCore + value: "Information" + - name: Logging__LogLevel__Microsoft.AspNetCore.Mvc + value: "Warning" + - name: Logging__LogLevel__Microsoft.AspNetCore.Routing + value: "Warning" + - name: Logging__LogLevel__Microsoft.Hosting.Lifetime + value: "Warning" + - name: Logging__LogLevel__Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware + value: "Information" + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_DOTNET_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED + value: 'false' # Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/net/instrumentations/ + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-checkoutservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-checkoutservice-patch.yaml new file mode 100644 index 0000000..c11abb2 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-checkoutservice-patch.yaml @@ -0,0 +1,16 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "go" + spec: + containers: + - name: checkoutservice + image: ghcr.io/open-telemetry/demo:1.12.0-checkoutservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + # - name: OTEL_GO_AUTO_INSTRUMENTATION_ENABLED # Not currently supported - https://github.com/open-telemetry/opentelemetry-go-instrumentation/issues/241 + # value: 'false' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-currencyservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-currencyservice-patch.yaml new file mode 100644 index 0000000..e20095f --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-currencyservice-patch.yaml @@ -0,0 +1,14 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "cpp" + spec: + containers: + - name: currencyservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' # TODO: should be 'localhost' for sidecar but getting a strange error, but according to otc logs sending to the OpenTelemetry Collector sidecar still seems to work + # [Error] File: /opentelemetry-cpp/exporters/otlp/src/otlp_grpc_log_record_exporter.cc:128 [OTLP LOG GRPC Exporter] Export() failed: failed to connect to all addresses; last error: UNKNOWN: ipv4:127.0.0.1:4317: Failed to connect to remote host: Connection refused diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-emailservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-emailservice-patch.yaml new file mode 100644 index 0000000..13e6bea --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-emailservice-patch.yaml @@ -0,0 +1,13 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "ruby" + spec: + containers: + - name: emailservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-flagd-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-flagd-patch.yaml new file mode 100644 index 0000000..085ae44 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-flagd-patch.yaml @@ -0,0 +1,13 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "flagd" + spec: + containers: + - name: flagd + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-frauddetectionservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-frauddetectionservice-patch.yaml new file mode 100644 index 0000000..c2557da --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-frauddetectionservice-patch.yaml @@ -0,0 +1,16 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "Java" + spec: + containers: + - name: frauddetectionservice + image: ghcr.io/open-telemetry/demo:1.12.0-frauddetectionservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: JAVA_TOOL_OPTIONS + value: '-javaagent:/app/opentelemetry-javaagent.jar' # '-javaagent:/app/opentelemetry-javaagent.jar' # - Duplicate spans from OA and Otel are avoided automatically - https://docs.dynatrace.com/docs/shortlink/opentelemetry-oneagent#java-span-dropping \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-frontend-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-frontend-patch.yaml new file mode 100644 index 0000000..51285b1 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-frontend-patch.yaml @@ -0,0 +1,39 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "nodejs" + spec: + containers: + - name: frontend + image: ghcr.io/open-telemetry/demo:1.12.0-frontend + imagePullPolicy: Always + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 250Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + env: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_NODEJS_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + - name: OTEL_NODE_DISABLED_INSTRUMENTATIONS # https://github.com/open-telemetry/opentelemetry-js-contrib/blob/167dced09de0d2104561542b4f83047fa656505f/metapackages/auto-instrumentations-node/package.json#L51 + value: '' # other examples - http,grpc,dns,net + - name: NODE_OPTIONS + value: '' # - do not instrument at all with things like '-r ./Instrumentation.js' Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/js/ + - name: PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT # This is used on the client-side for sending traces to the backend + value: '' + - name: NEXT_OTEL_VERBOSE + value: '0' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-imageprovider-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-imageprovider-patch.yaml new file mode 100644 index 0000000..54b7177 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-imageprovider-patch.yaml @@ -0,0 +1,21 @@ +spec: + template: + metadata: + annotations: + metrics.dynatrace.com/port: "9113" # https://www.dynatrace.com/news/blog/simplify-observability-for-all-your-custom-metrics-part-4-prometheus/ + metrics.dynatrace.com/scrape: "true" + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "nginx" + spec: + containers: + - name: imageprovider + image: ghcr.io/open-telemetry/demo:1.12.0-imageprovider + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: ngtinx-exporter + image: nginx/nginx-prometheus-exporter:1.3.0 + args: ["--nginx.scrape-uri=http://localhost:8081/status"] + ports: + - containerPort: 9113 \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-kafka-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-kafka-patch.yaml new file mode 100644 index 0000000..c24919e --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-kafka-patch.yaml @@ -0,0 +1,24 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "kafka" + spec: + containers: + - name: kafka + imagePullPolicy: Always + resources: + limits: + memory: 600Mi # To run OneAgent we reccomend at least 512Mi, Original 600Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + env: + - name: KAFKA_OPTS + value: '-Dotel.jmx.target.system=kafka-broker' + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_JAVA_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-loadgenerator-patch-dev.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-loadgenerator-patch-dev.yaml new file mode 100644 index 0000000..8ac56d5 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-loadgenerator-patch-dev.yaml @@ -0,0 +1,19 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "python" + spec: + containers: + - name: loadgenerator + image: ghcr.io/open-telemetry/demo:1.12.0-loadgenerator + imagePullPolicy: Always + env: + - name: LOCUST_HOST + value: http://astroshop-dev.k8s-demo.dynatracelabs.com + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + resources: + limits: + memory: 2Gi \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-loadgenerator-patch-stg.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-loadgenerator-patch-stg.yaml new file mode 100644 index 0000000..0261e90 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-loadgenerator-patch-stg.yaml @@ -0,0 +1,19 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "python" + spec: + containers: + - name: loadgenerator + image: ghcr.io/open-telemetry/demo:1.12.0-loadgenerator + imagePullPolicy: Always + env: + - name: LOCUST_HOST + value: http://astroshop-stg.k8s-demo.dynatracelabs.com + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + resources: + limits: + memory: 2Gi \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-paymentservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-paymentservice-patch.yaml new file mode 100644 index 0000000..855f3b6 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-paymentservice-patch.yaml @@ -0,0 +1,35 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "nodejs" + spec: + containers: + - name: paymentservice + image: ghcr.io/open-telemetry/demo:1.12.0-paymentservice + imagePullPolicy: Always + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 120Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + env: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_NODEJS_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + - name: OTEL_NODE_DISABLED_INSTRUMENTATIONS # https://github.com/open-telemetry/opentelemetry-js-contrib/blob/167dced09de0d2104561542b4f83047fa656505f/metapackages/auto-instrumentations-node/package.json#L51 + value: '' # other examples - http,grpc,dns,net + - name: NODE_OPTIONS + value: '' # - do not instrument at all with things like '-r ./Instrumentation.js' Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/js/ \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-productcatalogservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-productcatalogservice-patch.yaml new file mode 100644 index 0000000..cdc1860 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-productcatalogservice-patch.yaml @@ -0,0 +1,24 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "go" + spec: + containers: + - name: productcatalogservice + image: ghcr.io/open-telemetry/demo:1.12.0-productcatalogservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: OTEL_GO_AUTO_INSTRUMENTATION_ENABLED # Not currently supported - https://github.com/open-telemetry/opentelemetry-go-instrumentation/issues/241 + value: 'false' + volumeMounts: + - mountPath: "/usr/src/app/products/" + name: volume + readOnly: false + volumes: + - name: volume + persistentVolumeClaim: + claimName: product-catalog-storage \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-quoteservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-quoteservice-patch.yaml new file mode 100644 index 0000000..cefeb3a --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-quoteservice-patch.yaml @@ -0,0 +1,18 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "PHP" + spec: + containers: + - name: quoteservice + image: ghcr.io/open-telemetry/demo:1.12.0-quoteservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' + - name: OTEL_PHP_AUTOLOAD_ENABLED + value: 'true' + - name: OTEL_PHP_DISABLED_INSTRUMENTATIONS + value: '' # Disable 'all','slim,psr15,psr18' instrumentations \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-recommendationservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-recommendationservice-patch.yaml new file mode 100644 index 0000000..9d59fa9 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-recommendationservice-patch.yaml @@ -0,0 +1,13 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "python" + spec: + containers: + - name: recommendationservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-shippingservice-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-shippingservice-patch.yaml new file mode 100644 index 0000000..52247d7 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-shippingservice-patch.yaml @@ -0,0 +1,13 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "rust" + spec: + containers: + - name: shippingservice + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/deployment-valkey-patch.yaml b/.devcontainer/apps/astroshop/astroshop/deployment-valkey-patch.yaml new file mode 100644 index 0000000..20be045 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/deployment-valkey-patch.yaml @@ -0,0 +1,19 @@ +spec: + template: + metadata: + annotations: + metrics.dynatrace.com/port: "9121" # https://www.dynatrace.com/news/blog/simplify-observability-for-all-your-custom-metrics-part-4-prometheus/ + metrics.dynatrace.com/scrape: "true" + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "redis" + spec: + containers: + - name: valkey + imagePullPolicy: Always + env: + - name: OTEL_COLLECTOR_NAME + value: 'dynatrace-otel-gateway-collector' # TODO: cannot use 'localhost' for sidecar so currently set to '127.0.0.1', as sending to the OpenTelemetry Collector sidecar in valkey fails + - name: valkey-exporter + image: oliver006/redis_exporter:v1.14.0 + ports: + - containerPort: 9121 \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/ingress-frontendproxy-dev.yaml b/.devcontainer/apps/astroshop/astroshop/ingress-frontendproxy-dev.yaml new file mode 100644 index 0000000..8439d6e --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/ingress-frontendproxy-dev.yaml @@ -0,0 +1,43 @@ +apiVersion: "networking.k8s.io/v1" +kind: Ingress +metadata: + name: astroshop-frontendproxy-rewrite + annotations: + nginx.ingress.kubernetes.io/whitelist-source-range: 10.224.0.0/12,195.50.84.64/27,83.164.160.102/32,83.164.153.224/28,80.80.253.56/32,213.143.108.80/29,157.25.19.96/27,82.177.196.146/32,144.121.39.106/32,50.221.151.250/32,50.247.212.21/32,50.219.104.42/32,71.24.151.161/32,12.188.200.30/32,64.85.148.114/32,216.176.22.146/32,50.219.104.50/32,50.76.51.61/32 + nginx.ingress.kubernetes.io/rewrite-target: /$1 + # nginx.ingress.kubernetes.io/enable-opentelemetry: "true" + # nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-span: "true" +spec: + ingressClassName: nginx + rules: + - host: "astroshop-dev.k8s-demo.dynatracelabs.com" + http: + paths: + - path: /images/(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-imageprovider + port: + number: 8081 + - path: /loadgen/(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-loadgenerator + port: + number: 8089 + - path: /flagservice/(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-flagd + port: + number: 8013 + - path: /(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-frontend + port: + number: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/ingress-frontendproxy-stg.yaml b/.devcontainer/apps/astroshop/astroshop/ingress-frontendproxy-stg.yaml new file mode 100644 index 0000000..6098995 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/ingress-frontendproxy-stg.yaml @@ -0,0 +1,43 @@ +apiVersion: "networking.k8s.io/v1" +kind: Ingress +metadata: + name: astroshop-frontendproxy-rewrite + annotations: + nginx.ingress.kubernetes.io/whitelist-source-range: 10.224.0.0/12,195.50.84.64/27,83.164.160.102/32,83.164.153.224/28,80.80.253.56/32,213.143.108.80/29,157.25.19.96/27,82.177.196.146/32,144.121.39.106/32,50.221.151.250/32,50.247.212.21/32,50.219.104.42/32,71.24.151.161/32,12.188.200.30/32,64.85.148.114/32,216.176.22.146/32,50.219.104.50/32,50.76.51.61/32 + nginx.ingress.kubernetes.io/rewrite-target: /$1 + # nginx.ingress.kubernetes.io/enable-opentelemetry: "true" + # nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-span: "true" +spec: + ingressClassName: nginx + rules: + - host: "astroshop-stg.k8s-demo.dynatracelabs.com" + http: + paths: + - path: /images/(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-imageprovider + port: + number: 8081 + - path: /loadgen/(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-loadgenerator + port: + number: 8089 + - path: /flagservice/(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-flagd + port: + number: 8013 + - path: /(.*) + pathType: ImplementationSpecific + backend: + service: + name: astroshop-frontend + port: + number: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/ingress-nginx-values.yaml b/.devcontainer/apps/astroshop/astroshop/ingress-nginx-values.yaml new file mode 100644 index 0000000..f8beeca --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/ingress-nginx-values.yaml @@ -0,0 +1,6 @@ +controller: + enableAnnotationValidations: true + annotations: + oneagent.dynatrace.com/inject: "true" + podAnnotations: + oneagent.dynatrace.com/inject: "true" diff --git a/.devcontainer/apps/astroshop/astroshop/products.json b/.devcontainer/apps/astroshop/astroshop/products.json new file mode 100644 index 0000000..c0cda9b --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/products.json @@ -0,0 +1,148 @@ +{ + "products": [ + { + "id": "OLJCESPC7Z", + "name": "National Park Foundation Explorascope", + "description": "The National Park Foundation’s (NPF) Explorascope 60AZ is a manual alt-azimuth, refractor telescope perfect for celestial viewing on the go. The NPF Explorascope 60 can view the planets, moon, star clusters and brighter deep sky objects like the Orion Nebula and Andromeda Galaxy.", + "picture": "NationalParkFoundationExplorascope.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 101, + "nanos": 960000000 + }, + "categories": ["telescopes"] + }, + { + "id": "66VCHSJNUP", + "name": "Starsense Explorer Refractor Telescope", + "description": "The first telescope that uses your smartphone to analyze the night sky and calculate its position in real time. StarSense Explorer is ideal for beginners thanks to the app’s user-friendly interface and detailed tutorials. It’s like having your own personal tour guide of the night sky", + "picture": "StarsenseExplorer.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 349, + "nanos": 950000000 + }, + "categories": ["telescopes"] + }, + { + "id": "1YMWWN1N4O", + "name": "Eclipsmart Travel Refractor Telescope", + "description": "Dedicated white-light solar scope for the observer on the go. The 50mm refracting solar scope uses Solar Safe, ISO compliant, full-aperture glass filter material to ensure the safest view of solar events. The kit comes complete with everything you need, including the dedicated travel solar scope, a Solar Safe finderscope, tripod, a high quality 20mm (18x) Kellner eyepiece and a nylon backpack to carry everything in. This Travel Solar Scope makes it easy to share the Sun as well as partial and total solar eclipses with the whole family and offers much higher magnifications than you would otherwise get using handheld solar viewers or binoculars.", + "picture": "EclipsmartTravelRefractorTelescope.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 129, + "nanos": 950000000 + }, + "categories": ["telescopes", "travel"] + }, + { + "id": "L9ECAV7KIM", + "name": "Lens Cleaning Kit", + "description": "Wipe away dust, dirt, fingerprints and other particles on your lenses to see clearly with the Lens Cleaning Kit. This cleaning kit works on all glass and optical surfaces, including telescopes, binoculars, spotting scopes, monoculars, microscopes, and even your camera lenses, computer screens, and mobile devices. The kit comes complete with a retractable lens brush to remove dust particles and dirt and two options to clean smudges and fingerprints off of your optics, pre-moistened lens wipes and a bottled lens cleaning fluid with soft cloth.", + "picture": "LensCleaningKit.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 21, + "nanos": 950000000 + }, + "categories": ["accessories"] + }, + { + "id": "2ZYFJ3GM2N", + "name": "Roof Binoculars", + "description": "This versatile, all-around binocular is a great choice for the trail, the stadium, the arena, or just about anywhere you want a close-up view of the action without sacrificing brightness or detail. It’s an especially great companion for nature observation and bird watching, with ED glass that helps you spot the subtlest field markings and a close focus of just 6.5 feet.", + "picture": "RoofBinoculars.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 209, + "nanos": 950000000 + }, + "categories": ["binoculars"] + }, + { + "id": "0PUK6V6EV0", + "name": "Solar System Color Imager", + "description": "You have your new telescope and have observed Saturn and Jupiter. Now you're ready to take the next step and start imaging them. But where do you begin? The NexImage 10 Solar System Imager is the perfect solution.", + "picture": "SolarSystemColorImager.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 175, + "nanos": 0 + }, + "categories": ["accessories", "telescopes"] + }, + { + "id": "LS4PSXUNUM", + "name": "Red Flashlight", + "description": "This 3-in-1 device features a 3-mode red flashlight, a hand warmer, and a portable power bank for recharging your personal electronics on the go. Whether you use it to light the way at an astronomy star party, a night walk, or wildlife research, ThermoTorch 3 Astro Red’s rugged, IPX4-rated design will withstand your everyday activities.", + "picture": "RedFlashlight.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 57, + "nanos": 80000000 + }, + "categories": ["accessories", "flashlights"] + }, + { + "id": "9SIQT8TOJO", + "name": "Optical Tube Assembly", + "description": "Capturing impressive deep-sky astroimages is easier than ever with Rowe-Ackermann Schmidt Astrograph (RASA) V2, the perfect companion to today’s top DSLR or astronomical CCD cameras. This fast, wide-field f/2.2 system allows for shorter exposure times compared to traditional f/10 astroimaging, without sacrificing resolution. Because shorter sub-exposure times are possible, your equatorial mount won’t need to accurately track over extended periods. The short focal length also lessens equatorial tracking demands. In many cases, autoguiding will not be required.", + "picture": "OpticalTubeAssembly.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 3599, + "nanos": 0 + }, + "categories": ["accessories", "telescopes", "assembly"] + }, + { + "id": "6E92ZMYYFZ", + "name": "Solar Filter", + "description": "Enhance your viewing experience with EclipSmart Solar Filter for 8” telescopes. With two Velcro straps and four self-adhesive Velcro pads for added safety, you can be assured that the solar filter cannot be accidentally knocked off and will provide Solar Safe, ISO compliant viewing.", + "picture": "SolarFilter.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 69, + "nanos": 950000000 + }, + "categories": ["accessories", "telescopes"] + }, + { + "id": "HQTGWGPNH4", + "name": "The Comet Book", + "description": "A 16th-century treatise on comets, created anonymously in Flanders (now northern France) and now held at the Universitätsbibliothek Kassel. Commonly known as The Comet Book (or Kometenbuch in German), its full title translates as “Comets and their General and Particular Meanings, According to Ptolomeé, Albumasar, Haly, Aliquind and other Astrologers”. The image is from https://publicdomainreview.org/collection/the-comet-book, made available by the Universitätsbibliothek Kassel under a CC-BY SA 4.0 license (https://creativecommons.org/licenses/by-sa/4.0/)", + "picture": "TheCometBook.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 0, + "nanos": 990000000 + }, + "categories": ["books"] + }, + { + "id": "HQTGWGPNH4", + "name": "The Comet Book 3", + "description": "A 16th-century treatise on comets, created anonymously in Flanders (now northern France) and now held at the Universitätsbibliothek Kassel. Commonly known as The Comet Book (or Kometenbuch in German), its full title translates as “Comets and their General and Particular Meanings, According to Ptolomeé, Albumasar, Haly, Aliquind and other Astrologers”. The image is from https://publicdomainreview.org/collection/the-comet-book, made available by the Universitätsbibliothek Kassel under a CC-BY SA 4.0 license (https://creativecommons.org/licenses/by-sa/4.0/)", + "picture": "TheCometBook.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 0, + "nanos": 990000000 + }, + "categories": ["books"] + }, + { + "id": "HQTGWGPNH4", + "name": "The Comet Book 4", + "description": "A 16th-century treatise on comets, created anonymously in Flanders (now northern France) and now held at the Universitätsbibliothek Kassel. Commonly known as The Comet Book (or Kometenbuch in German), its full title translates as “Comets and their General and Particular Meanings, According to Ptolomeé, Albumasar, Haly, Aliquind and other Astrologers”. The image is from https://publicdomainreview.org/collection/the-comet-book, made available by the Universitätsbibliothek Kassel under a CC-BY SA 4.0 license (https://creativecommons.org/licenses/by-sa/4.0/)", + "picture": "TheCometBook.jpg", + "priceUsd": { + "currencyCode": "USD", + "units": 0, + "nanos": 990000000 + }, + "categories": ["books"] + } + ] +} diff --git a/.devcontainer/apps/astroshop/astroshop/pv-product-catalog.yaml b/.devcontainer/apps/astroshop/astroshop/pv-product-catalog.yaml new file mode 100644 index 0000000..10d5ee9 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/pv-product-catalog.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: product-catalog-storage + labels: + type: local +spec: + storageClassName: local-path + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/products" \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/pvc-product-catalog.yaml b/.devcontainer/apps/astroshop/astroshop/pvc-product-catalog.yaml new file mode 100644 index 0000000..0c9d70d --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/pvc-product-catalog.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: product-catalog-storage +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/astroshop/statefulset-opensearch.yaml b/.devcontainer/apps/astroshop/astroshop/statefulset-opensearch.yaml new file mode 100644 index 0000000..fb5ee83 --- /dev/null +++ b/.devcontainer/apps/astroshop/astroshop/statefulset-opensearch.yaml @@ -0,0 +1,19 @@ +spec: + template: + metadata: + annotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "elasticsearch" + spec: + containers: + - name: opensearch + imagePullPolicy: Always + env: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_JAVA_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/deploy-astroshop.sh b/.devcontainer/apps/astroshop/deploy-astroshop.sh new file mode 100755 index 0000000..5dc8d5c --- /dev/null +++ b/.devcontainer/apps/astroshop/deploy-astroshop.sh @@ -0,0 +1,42 @@ +#!/bin/bash +x + # Read the domain from CM +#source ../util/loaddomain.sh + +# Load the functions +# Read the variables +#source ../../cluster-setup/functions.sh +#source ../../cluster-setup/resources/dynatrace/credentials.sh + +printInfoSection "Deploying Astroshop" + +# read the credentials and variables +saveReadCredentials + +### +# Instructions to install Astroshop with Helm Chart from R&D and images built in shinojos repo (including code modifications from R&D) +#### +sed 's~domain.placeholder~'"$DOMAIN"'~' $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm/values.yaml > $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm/values.yaml.tmp +mv $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm/values.yaml.tmp $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm/values.yaml + +sed 's~domain.placeholder~'"$DOMAIN"'~' $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml > $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml.tmp +mv $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml.tmp $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml + +helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts + +helm dependency build $REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm + +kubectl create namespace astroshop + +echo "OTEL Configuration URL $DT_OTEL_ENDPOINT and Token $DT_INGEST_TOKEN" + +helm upgrade --install astroshop -f .$REPO_PATH/.devcontainer/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml --set default.image.repository=docker.io/shinojosa/astroshop --set default.image.tag=1.12.0 --set collector_tenant_endpoint=$DT_OTEL_ENDPOINT --set collector_tenant_token=$DT_INGEST_TOKEN -n astroshop ./helm/dt-otel-demo-helm + +printInfo "Stopping all cronjobs from Demo Live since they are not needed with this scenario" + +kubectl get cronjobs -n astroshop -o json | jq -r '.items[] | .metadata.name' | xargs -I {} kubectl patch cronjob {} -n astroshop --patch '{"spec": {"suspend": true}}' + +kubectl get cronjobs -n astroshop + +printInfo "Astroshop available at: " + +kubectl get ing -n astroshop \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml new file mode 100644 index 0000000..f9ef033 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm-deployments/values.yaml @@ -0,0 +1,38 @@ +ingressHostUrl: "astroshop-frontendproxy" +collector_tenant_endpoint: donotstore +collector_tenant_token: donotstore + +opentelemetry-demo: + components: + loadgenerator: + envOverrides: + - name: LOCUST_USERS + value: "1" + - name: LOCUST_HOST + value: "http://astroshop-frontendproxy:8080" + frontend: + envOverrides: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_NODEJS_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + - name: OTEL_NODE_DISABLED_INSTRUMENTATIONS # https://github.com/open-telemetry/opentelemetry-js-contrib/blob/167dced09de0d2104561542b4f83047fa656505f/metapackages/auto-instrumentations-node/package.json#L51 + value: '' # other examples - http,grpc,dns,net + - name: NODE_OPTIONS + value: '' # - do not instrument at all with things like '-r ./Instrumentation.js' Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/js/ + - name: PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT # This is used on the client-side for sending traces to the backend + value: '' + - name: NEXT_OTEL_VERBOSE + value: '0' + - name: ENV_PLATFORM + value: 'DEVCONTAINER' \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/.helmignore b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/Chart.lock b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/Chart.lock new file mode 100644 index 0000000..408a6ea --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: opentelemetry-collector + repository: https://open-telemetry.github.io/opentelemetry-helm-charts + version: 0.104.0 +- name: opentelemetry-demo + repository: https://open-telemetry.github.io/opentelemetry-helm-charts + version: 0.33.5 +digest: sha256:4a0d6eb2e8806f0f1b5e5e4a23ad9374d138b311d4a8632fcc5634c78d072d9e +generated: "2024-12-10T12:36:59.217059+01:00" diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/Chart.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/Chart.yaml new file mode 100644 index 0000000..a0d3211 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/Chart.yaml @@ -0,0 +1,35 @@ +apiVersion: v2 +name: dt-otel-demo-helm +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" + +dependencies: + # - name: cert-manager + # version: "v1.15.3" + # repository: "https://charts.jetstack.io" + - name: opentelemetry-collector + version: "0.104.0" # This helm chart delivers the app version 0.108.0 + repository: "https://open-telemetry.github.io/opentelemetry-helm-charts" + - name: opentelemetry-demo + version: 0.33.5 #0.32.8 + repository: "https://open-telemetry.github.io/opentelemetry-helm-charts" diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/charts/opentelemetry-collector-0.104.0.tgz b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/charts/opentelemetry-collector-0.104.0.tgz new file mode 100644 index 0000000..00fefbb Binary files /dev/null and b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/charts/opentelemetry-collector-0.104.0.tgz differ diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/charts/opentelemetry-demo-0.33.5.tgz b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/charts/opentelemetry-demo-0.33.5.tgz new file mode 100644 index 0000000..c0e1e71 Binary files /dev/null and b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/charts/opentelemetry-demo-0.33.5.tgz differ diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/collector_tenant_secret.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/collector_tenant_secret.yaml new file mode 100644 index 0000000..df560ef --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/collector_tenant_secret.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Secret +metadata: + name: dynatrace-otelcol-dt-api-credentials +type: Opaque +data: + DT_ENDPOINT: {{ .Values.collector_tenant_endpoint | b64enc }} + DT_API_TOKEN: {{ .Values.collector_tenant_token | b64enc }} \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/ingress-frontendproxy.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/ingress-frontendproxy.yaml new file mode 100644 index 0000000..db8640e --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/ingress-frontendproxy.yaml @@ -0,0 +1,104 @@ +# apiVersion: "networking.k8s.io/v1" +# kind: Ingress +# metadata: +# name: {{ include "otel-demo.name" . }}-frontendproxy-root +# annotations: +# #nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.ingressWhitelistSourceRange }} +# #nginx.ingress.kubernetes.io/rewrite-target: / +# spec: +# ingressClassName: traefik +# rules: +# - host: {{ .Values.ingressHostUrl }} +# http: +# paths: +# - path: / +# pathType: Prefix +# backend: +# service: +# name: {{ include "otel-demo.name" . }}-frontend +# port: +# number: 8080 +# --- +# apiVersion: "networking.k8s.io/v1" +# kind: Ingress +# metadata: +# name: {{ include "otel-demo.name" . }}-frontendproxy-rewrite-loadgen +# annotations: +# #nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.ingressWhitelistSourceRange }} +# #nginx.ingress.kubernetes.io/rewrite-target: /$2 +# spec: +# ingressClassName: traefik +# rules: +# - host: {{ .Values.ingressHostUrl }} +# http: +# paths: +# - path: /loadgen(/|$)(.*) +# pathType: Prefix +# backend: +# service: +# name: {{ include "otel-demo.name" . }}-loadgenerator +# port: +# number: 8089 +# --- +# apiVersion: "networking.k8s.io/v1" +# kind: Ingress +# metadata: +# name: {{ include "otel-demo.name" . }}-frontendproxy-rewrite-flags +# annotations: +# #nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.ingressWhitelistSourceRange }} +# #nginx.ingress.kubernetes.io/rewrite-target: /$2 +# spec: +# ingressClassName: traefik +# rules: +# - host: {{ .Values.ingressHostUrl }} +# http: +# paths: +# - path: /flagservice(/|$)(.*) +# pathType: Prefix +# backend: +# service: +# name: {{ include "otel-demo.name" . }}-flagd +# port: +# number: 8013 +# --- +# apiVersion: "networking.k8s.io/v1" +# kind: Ingress +# metadata: +# name: {{ include "otel-demo.name" . }}-frontendproxy-rewrite-images +# annotations: +# #nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.ingressWhitelistSourceRange }} +# #nginx.ingress.kubernetes.io/rewrite-target: /$2 +# spec: +# ingressClassName: traefik +# rules: +# - host: {{ .Values.ingressHostUrl }} +# http: +# paths: +# - path: /images(/|$)(.*) +# pathType: Prefix +# backend: +# service: +# name: {{ include "otel-demo.name" . }}-imageprovider +# port: +# number: 8081 +# --- +# apiVersion: "networking.k8s.io/v1" +# kind: Ingress +# metadata: +# name: {{ include "otel-demo.name" . }}-frontendproxy-rewrite-feature +# annotations: +# #nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.ingressWhitelistSourceRange }} +# #ingress.kubernetes.io/rewrite-target: /feature/$1 +# spec: +# ingressClassName: traefik +# rules: +# - host: {{ .Values.ingressHostUrl }} +# http: +# paths: +# - path: /feature(/|$)(.*) +# pathType: Prefix +# backend: +# service: +# name: {{ include "otel-demo.name" . }}-flagd +# port: +# number: 4000 \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/otel-k8s-enrichment-rbac.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/otel-k8s-enrichment-rbac.yaml new file mode 100644 index 0000000..a10d884 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/otel-k8s-enrichment-rbac.yaml @@ -0,0 +1,62 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: dynatrace-otel-collector-k8s-enrichment + labels: + app: dynatrace-otel-collector-k8s-enrichment +rules: + - apiGroups: + - '' + resources: + - 'pods' + - 'namespaces' + verbs: + - 'get' + - 'watch' + - 'list' + - apiGroups: + - 'apps' + resources: + - 'replicasets' + verbs: + - 'get' + - 'list' + - 'watch' + - apiGroups: + - 'extensions' + resources: + - 'replicasets' + verbs: + - 'get' + - 'list' + - 'watch' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Name }}-otel-gateway-collector-k8s-enrichment + labels: + app: {{ .Release.Name }}-otel-gateway-collector-k8s-enrichment +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: dynatrace-otel-collector-k8s-enrichment +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }}-otel-gateway-collector + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ .Release.Name }}-k8s-enrichment + labels: + app: {{ .Release.Name }}-k8s-enrichment +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: dynatrace-otel-collector-k8s-enrichment +subjects: + - kind: ServiceAccount + name: {{ include "otel-demo.name" . }} + namespace: {{ .Release.Namespace }} \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/oteldemo-trigger-manual-jobs.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/oteldemo-trigger-manual-jobs.yaml new file mode 100644 index 0000000..cd70bb7 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/oteldemo-trigger-manual-jobs.yaml @@ -0,0 +1,505 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "otel-demo.name" . }}-flagd-config-script +data: + update_flag.sh: | + #!/bin/bash + apk --update add curl jq + jq ".flags.$FLAGNAME.defaultVariant |= \"on\"" /tmp/config/demo.flagd.json | jq -c '{ data: . }' > /tmp/output.json + curl -k -X POST --data "@/tmp/output.json" "http://{{ include "otel-demo.name" . }}-flagd:4000/feature/api/write-to-file" + + stop.sh: | + #!/bin/bash + apk --update add curl jq + jq -c '{ data: . }' /tmp/config/demo.flagd.json > /tmp/output.json + curl -k -X POST --data "@/tmp/output.json" "http://{{ include "otel-demo.name" . }}-flagd:4000/feature/api/write-to-file" + sleep 5000 +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: productcatalog-trigger-failure-job-start +spec: + schedule: "0 7 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: productCatalogFailure + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: recommendationservice-trigger-cachefailure-job-start +spec: + schedule: "0 8 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: recommendationServiceCacheFailure + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: adservice-trigger-manualgc-job-start +spec: + schedule: "0 9 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: adServiceManualGc + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: adservice-trigger-highcpu-job-start +spec: + schedule: "0 10 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: adServiceHighCpu + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: adservice-trigger-failure-job-start +spec: + schedule: "0 11 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: adServiceFailure + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: kafka-trigger-queue-problems-job-start +spec: + schedule: "0 12 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: kafkaQueueProblems + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: cartservice-trigger-failure-job-start +spec: + schedule: "0 13 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: cartServiceFailure + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: paymentservice-trigger-failure-job-start +spec: + schedule: "0 14 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: paymentServiceFailure + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: paymentservice-trigger-unreachable-job-start +spec: + schedule: "0 15 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: paymentServiceUnreachable + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: loadgenerator-trigger-flood-job-start +spec: + schedule: "0 16 * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: loadgeneratorFloodHomepage + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: frontendproxy-trigger-image-slowload-job-start +spec: + schedule: "0 17 * * *" + suspend: true # Original envoy proxy job is suspended as we moved to ingress-nginx + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: imageSlowLoad + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/update_flag.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: flagd-config-reset-job +spec: + schedule: "*/30 * * * *" + suspend: false + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + volumes: + - name: scripts + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config-script + defaultMode: 0777 + - name: flagd-flags + configMap: + name: {{ include "otel-demo.name" . }}-flagd-config + containers: + - name: execute + env: + - name: FLAGNAME + value: productCatalogFailure + image: alpine:3.12 + imagePullPolicy: IfNotPresent + command: [ "/bin/sh", "/tmp/scripts/stop.sh"] + volumeMounts: + - mountPath: "/tmp/scripts" + name: scripts + - mountPath: "/tmp/config" + name: flagd-flags +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: productadmin-update-product-job +spec: + schedule: "*/5 * * * *" + suspend: false + jobTemplate: + spec: + suspend: false + template: + spec: + restartPolicy: OnFailure + containers: + - name: execute + env: + - name: FLAGNAME + value: productCatalogFailure + image: alpine:3.12 + imagePullPolicy: IfNotPresent + env: + - name: PRODUCT_ADMIN_USER + value: "product_admin" + - name: PRODUCT_ADMIN_PASSWORD + value: 'P@$$$$w0rd' + - name: PRODUCT_ADMIN_ENDPOINT_URI + value: "https://f22tqn1ew3.execute-api.us-east-1.amazonaws.com/Dev" + command: + - "/bin/sh" + - "-c" + - | + apk --update add curl bash coreutils; + /bin/bash <<'EOF' + + echo starting; + AUTH=$(echo -ne "$PRODUCT_ADMIN_USER:$PRODUCT_ADMIN_PASSWORD" | base64 --wrap 0); + curl -k -X GET --header "Authorization: Basic $AUTH" "$PRODUCT_ADMIN_ENDPOINT_URI/product/OLJCESPC7Z"; + curl -k -X PUT --header "Authorization: Basic $AUTH" --header "Content-Type: application/json" --data '{ "name": "Eclipsmart Travel Refractor Telescope 2026", "description": "Dedicated white-light solar scope for the observer on the go. The 50mm refracting solar scope uses Solar Safe, ISO compliant, full-aperture glass filter material to ensure the safest view of solar events. The kit comes complete with everything you need, including the dedicated travel solar scope, a Solar Safe finderscope, tripod, a high quality 20mm (18x) Kellner eyepiece and a nylon backpack to carry everything in. This Travel Solar Scope makes it easy to share the Sun as well as partial and total solar eclipses with the whole family and offers much higher magnifications than you would otherwise get using handheld solar viewers or binoculars.", "picture": "EclipsmartTravelRefractorTelescope.jpg", "priceUsd": { "currencyCode": "USD", "units": 129, "nanos": 950000000 }, "categories": [ "telescopes", "travel" ]}' "$PRODUCT_ADMIN_ENDPOINT_URI/product/OLJCESPC7Z"; + echo done; + + EOF +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: productadmin-list-products-job +spec: + schedule: "*/1 * * * *" + suspend: true # Original envoy proxy job is suspended as we moved to ingress-nginx + jobTemplate: + spec: + suspend: false + template: + spec: + restartPolicy: OnFailure + containers: + - name: execute + image: alpine:3.12 + imagePullPolicy: IfNotPresent + env: + - name: PRODUCT_ADMIN_USER + value: "product_admin" + - name: PRODUCT_ADMIN_PASSWORD + value: 'P@$$$$w0rd' + - name: PRODUCT_ADMIN_ENDPOINT_URI + value: {{ .Values.lambda_url }} + command: + - "/bin/sh" + - "-c" + - | + apk --update add curl bash coreutils; + /bin/bash <<'EOF' + + echo starting; + AUTH=$(echo -ne "$PRODUCT_ADMIN_USER:$PRODUCT_ADMIN_PASSWORD" | base64 --wrap 0); + curl -k -X GET --header "Authorization: Basic $AUTH" "$PRODUCT_ADMIN_ENDPOINT_URI/product"; + echo done; + + EOF \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/pvc-product-catalog.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/pvc-product-catalog.yaml new file mode 100644 index 0000000..bfd1fb9 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/templates/pvc-product-catalog.yaml @@ -0,0 +1,11 @@ +#apiVersion: v1 +#kind: PersistentVolumeClaim +#metadata: +# name: {{ include "otel-demo.name" . }}-product-catalog-storage +#spec: +# accessModes: +# - ReadWriteMany +# storageClassName: {{ .Values.prodCatalogPvcStorageClass }} +# resources: +# requests: +# storage: {{ .Values.prodCatalogPvcSize }} \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/values.yaml b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/values.yaml new file mode 100644 index 0000000..fa41621 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/dt-otel-demo-helm/values.yaml @@ -0,0 +1,1090 @@ +ingressHostUrl: "astroshop-frontendproxy" +#ingressHostUrl: "astroshop-frontendproxy.astroshop.svc.cluster.local" +#ingressWhitelistSourceRange: 10.224.0.0/12,195.50.84.64/27,83.164.160.102/32,83.164.153.224/28,80.80.253.56/32,213.143.108.80/29,157.25.19.96/27,82.177.196.146/32,144.121.39.106/32,50.221.151.250/32,50.247.212.21/32,50.219.104.42/32,71.24.151.161/32,12.188.200.30/32,64.85.148.114/32,216.176.22.146/32,50.219.104.50/32,50.76.51.61/32 +#prodCatalogPvcStorageClass: azureblob-nfs-premium +#prodCatalogPvcSize: 5Gi +collector_tenant_endpoint: donotstore +collector_tenant_token: donotstore +#lambda_url: "https://lambdaurl.execute-api.us-east-1.amazonaws.com/Dev" + +opentelemetry-demo: + default: + envOverrides: + - name: OTEL_COLLECTOR_NAME + value: '{{ .Release.Name }}-otel-gateway-collector' + + components: + accountingService: + enabled: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-accountingservice + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: ".NET" + envOverrides: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_DOTNET_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED + value: 'false' # Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/net/instrumentations/ + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + initContainers: + - name: wait-for-kafka + image: busybox:latest + command: ['sh', '-c', 'until nc -z -v -w30 {{ include "otel-demo.name" . }}-kafka 9092; do echo waiting for kafka; sleep 2; done;'] + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 120Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + + adService: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-adservice + podAnnotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "Java" + envOverrides: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_JAVA_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: JAVA_TOOL_OPTIONS + value: '' # '-javaagent:/usr/src/app/opentelemetry-javaagent.jar' # - Duplicate spans from OA and Otel are avoided automatically - https://docs.dynatrace.com/docs/shortlink/opentelemetry-oneagent#java-span-dropping + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 300Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + + cartService: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-cartservice + podAnnotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: ".NET" + envOverrides: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_DOTNET_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED + value: 'false' # Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/net/instrumentations/ + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + resources: + limits: + memory: 512Mi # Original 120Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + + checkoutService: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-checkoutservice + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "go" + + currencyService: + enabled: true + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "cpp" + + emailService: + enabled: true + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "ruby" + + flagd: + enabled: true + imageOverride: + repository: "ghcr.io/open-feature/flagd" + tag: "v0.11.1" + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "flagd" + replicas: 1 + service: + port: 8013 + envOverrides: + - name: FLAGD_METRICS_EXPORTER + value: otel + - name: FLAGD_OTEL_COLLECTOR_URI + value: $(OTEL_COLLECTOR_NAME):4317 + resources: + limits: + memory: 300Mi + command: + - "/flagd-build" + - "start" + - "--uri" + - "file:./etc/flagd/demo.flagd.json" + mountedEmptyDirs: + - name: config-rw + mountPath: /etc/flagd + # flgad-ui as a sidecar container in the same pod so the flag json file can be shared + sidecarContainers: + - name: flagd-ui + useDefault: + env: true + service: + port: 4000 + envOverrides: + - name: FLAGD_METRICS_EXPORTER + value: otel + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://$(OTEL_COLLECTOR_NAME):4318 + resources: + limits: + memory: 300Mi + volumeMounts: + - name: config-rw + mountPath: /app/data + initContainers: + - name: init-config + image: busybox + command: ['sh', '-c', 'cp /config-ro/demo.flagd.json /config-rw/demo.flagd.json && cat /config-rw/demo.flagd.json'] + volumeMounts: + - mountPath: /config-ro + name: config-ro + - mountPath: /config-rw + name: config-rw + additionalVolumes: + - name: config-ro + configMap: + name: '{{ include "otel-demo.name" . }}-flagd-config' + + frauddetectionService: + enabled: true + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "Java" + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-frauddetectionservice + envOverrides: + - name: JAVA_TOOL_OPTIONS + value: '-javaagent:/app/opentelemetry-javaagent.jar' # '-javaagent:/app/opentelemetry-javaagent.jar' # - Duplicate spans from OA and Otel are avoided automatically - https://docs.dynatrace.com/docs/shortlink/opentelemetry-oneagent#java-span-dropping + + frontend: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-frontend + podAnnotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "nodejs" + envOverrides: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_NODEJS_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + - name: OTEL_NODE_DISABLED_INSTRUMENTATIONS # https://github.com/open-telemetry/opentelemetry-js-contrib/blob/167dced09de0d2104561542b4f83047fa656505f/metapackages/auto-instrumentations-node/package.json#L51 + value: '' # other examples - http,grpc,dns,net + - name: NODE_OPTIONS + value: '' # - do not instrument at all with things like '-r ./Instrumentation.js' Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/js/ + - name: PUBLIC_OTEL_EXPORTER_OTLP_TRACES_ENDPOINT # This is used on the client-side for sending traces to the backend + value: '' + - name: NEXT_OTEL_VERBOSE + value: '0' # This expects users to use `kubectl port-forward ...` + + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 250Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + + frontendProxy: + enabled: true + podAnnotations: + dt.owner: dev-team-ux + service: + type: LoadBalancer + ingress: + enabled: true + annotations: {} + ingressClassName: nginx + hosts: + - host: "astroshop.domain.placeholder" + paths: + - path: / + pathType: ImplementationSpecific + port: 8080 + + imageprovider: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-imageprovider #TODO: Change this too with extra containers + podAnnotations: + metrics.dynatrace.com/port: "9113" # https://www.dynatrace.com/news/blog/simplify-observability-for-all-your-custom-metrics-part-4-prometheus/ + metrics.dynatrace.com/scrape: "true" + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "nginx" + # TODO: suppported in later helm chart + # containers: + # - name: imageprovider + # image: docker.io/docker.io/shinojosa/astroshop:0.0.12-imageprovider + # imagePullPolicy: Always + # envOverrides: + # - name: OTEL_COLLECTOR_NAME + # value: 'dynatrace-otel-gateway-collector' + # - name: ngtinx-exporter + # image: nginx/nginx-prometheus-exporter:1.3.0 + # args: ["--nginx.scrape-uri=http://localhost:8081/status"] + # ports: + # - containerPort: 9113 + + kafka: + enabled: true + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "kafka" + envOverrides: + - name: KAFKA_OPTS + value: '-Dotel.jmx.target.system=kafka-broker' + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_JAVA_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + resources: + limits: + memory: 600Mi + + loadgenerator: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-loadgenerator + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "python" + resources: + limits: + memory: 2Gi + + paymentService: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-paymentservice + podAnnotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "nodejs" + envOverrides: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_NODEJS_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + - name: OTEL_TRACES_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_LOGS_EXPORTER + value: 'none' # 'console', 'none', 'otlp' + - name: OTEL_METRICS_EXPORTER + value: 'console,otlp' # 'console', 'none', 'otlp' + - name: OTEL_NODE_DISABLED_INSTRUMENTATIONS # https://github.com/open-telemetry/opentelemetry-js-contrib/blob/167dced09de0d2104561542b4f83047fa656505f/metapackages/auto-instrumentations-node/package.json#L51 + value: '' # other examples - http,grpc,dns,net + - name: NODE_OPTIONS + value: '' # - do not instrument at all with things like '-r ./Instrumentation.js' Avoid duplicate spans from OA and Otel - https://opentelemetry.io/docs/zero-code/js/ + resources: + limits: + memory: 512Mi # To run OneAgent we reccomend 512Mi, Original 120Mi - https://docs.dynatrace.com/docs/setup-and-configuration/dynatrace-oneagent/memory-requirements + + productCatalogService: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-productcatalogservice + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "go" + envOverrides: + - name: OTEL_GO_AUTO_INSTRUMENTATION_ENABLED # Not currently supported - https://github.com/open-telemetry/opentelemetry-go-instrumentation/issues/241 + value: 'false' + # TODO: Needs new chart + # volumeMounts: + # - mountPath: "/usr/src/app/products/" + # name: volume + # readOnly: false + # volumes: + # - name: volume + # persistentVolumeClaim: + # claimName: product-catalog-storage + + quoteService: + enabled: true + useDefault: + env: true + imageOverride: + repository: docker.io/shinojosa/astroshop + tag: 1.12.0-quoteservice + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "PHP" + envOverrides: + - name: OTEL_PHP_AUTOLOAD_ENABLED + value: 'true' + - name: OTEL_PHP_DISABLED_INSTRUMENTATIONS + value: '' # Disable 'all','slim,psr15,psr18' instrumentations + + recommendationService: + enabled: true + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "python" + + shippingService: + enabled: true + useDefault: + env: true + podAnnotations: + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "rust" + + valkey: + enabled: true + useDefault: + env: true + imageOverride: + repository: "valkey/valkey" + tag: "7.2-alpine" + podAnnotations: + metrics.dynatrace.com/port: "9121" # https://www.dynatrace.com/news/blog/simplify-observability-for-all-your-custom-metrics-part-4-prometheus/ + metrics.dynatrace.com/scrape: "true" + oneagent.dynatrace.com/inject: "false" + metadata.dynatrace.com/process.technology: "redis" +# TODO: extra container needs newer chart + # - name: valkey-exporter + # image: oliver006/redis_exporter:v1.14.0 + # ports: + # - containerPort: 9121 + + + opensearch: + podAnnotations: + oneagent.dynatrace.com/inject: "true" + metadata.dynatrace.com/process.technology: "elasticsearch" + enabled: false + envOverrides: + - name: DT_LOGLEVELCON # https://www.dynatrace.com/support/help/shortlink/agent-logging + value: "" # info + - name: DT_LOGCON_PROC + value: "" # stdout + - name: DT_LOGGING_DESTINATION + value: "" # stdout + - name: DT_LOGGING_JAVA_FLAGS + value: '' # Exporter=true,SpanProcessor=true,Propagator=true,Core=true + + opentelemetry-collector: + enabled: false + jaeger: + enabled: false + + prometheus: + enabled: false + + grafana: + enabled: false + + +opentelemetry-collector: + #fullnameOverride: "dynatrace-otel-gateway" # If we don't want releaseName prefix before this value + nameOverride: 'otel-gateway-collector' + mode: "deployment" + presets: + logsCollection: + enabled: false + includeCollectorLogs: false + # Enabling this writes checkpoints in /var/lib/otelcol/ host directory. + # Note this changes collector's user to root, so that it can write to host directory. + storeCheckpoints: false + # The maximum bytes size of the recombined field. + # Once the size exceeds the limit, all received entries of the source will be combined and flushed. + maxRecombineLogSize: 102400 + # Configures the collector to collect host metrics. + # Adds the hostmetrics receiver to the metrics pipeline + # and adds the necessary volumes and volume mounts. + # Best used with mode = daemonset. + # See https://opentelemetry.io/docs/kubernetes/collector/components/#host-metrics-receiver for details on the receiver. + hostMetrics: + enabled: false + # Configures the Kubernetes Processor to add Kubernetes metadata. + # Adds the k8sattributes processor to all the pipelines + # and adds the necessary rules to ClusteRole. + # Best used with mode = daemonset. + # See https://opentelemetry.io/docs/kubernetes/collector/components/#kubernetes-attributes-processor for details on the receiver. + kubernetesAttributes: + enabled: false + # When enabled the processor will extra all labels for an associated pod and add them as resource attributes. + # The label's exact name will be the key. + extractAllPodLabels: false + # When enabled the processor will extra all annotations for an associated pod and add them as resource attributes. + # The annotation's exact name will be the key. + extractAllPodAnnotations: false + # Configures the collector to collect node, pod, and container metrics from the API server on a kubelet.. + # Adds the kubeletstats receiver to the metrics pipeline + # and adds the necessary rules to ClusteRole. + # Best used with mode = daemonset. + # See https://opentelemetry.io/docs/kubernetes/collector/components/#kubeletstats-receiver for details on the receiver. + kubeletMetrics: + enabled: false + # Configures the collector to collect kubernetes events. + # Adds the k8sobject receiver to the logs pipeline + # and collects kubernetes events by default. + # Best used with mode = deployment or statefulset. + # See https://opentelemetry.io/docs/kubernetes/collector/components/#kubernetes-objects-receiver for details on the receiver. + kubernetesEvents: + enabled: false + # Configures the Kubernetes Cluster Receiver to collect cluster-level metrics. + # Adds the k8s_cluster receiver to the metrics pipeline + # and adds the necessary rules to ClusteRole. + # Best used with mode = deployment or statefulset. + # See https://opentelemetry.io/docs/kubernetes/collector/components/#kubernetes-cluster-receiver for details on the receiver. + clusterMetrics: + enabled: false + + configMap: + # Specifies whether a configMap should be created (true by default) + create: true + # Specifies an existing ConfigMap to be mounted to the pod + # The ConfigMap MUST include the collector configuration via a key named 'relay' or the collector will not start. + existingName: "" + # Specifies the relative path to custom ConfigMap template file. This option SHOULD be used when bundling a custom + # ConfigMap template, as it enables pod restart via a template checksum annotation. + # existingPath: "" + + # Base collector configuration. + # Supports templating. To escape existing instances of {{ }}, use {{` `}}. + # For example, {{ REDACTED_EMAIL }} becomes {{` {{ REDACTED_EMAIL }} `}}. + config: + receivers: + otlp: + protocols: + grpc: + endpoint: ${env:MY_POD_IP}:4317 + http: + endpoint: ${env:MY_POD_IP}:4318 + hostmetrics: + root_path: /hostfs + scrapers: + cpu: + metrics: + system.cpu.utilization: + enabled: true + disk: {} + load: {} + filesystem: + exclude_mount_points: + mount_points: + - /dev/* + - /proc/* + - /sys/* + - /run/k3s/containerd/* + - /var/lib/docker/* + - /var/lib/kubelet/* + - /snap/* + match_type: regexp + exclude_fs_types: + fs_types: + - autofs + - binfmt_misc + - bpf + - cgroup2 + - configfs + - debugfs + - devpts + - devtmpfs + - fusectl + - hugetlbfs + - iso9660 + - mqueue + - nsfs + - overlay + - proc + - procfs + - pstore + - rpc_pipefs + - securityfs + - selinuxfs + - squashfs + - sysfs + - tracefs + match_type: strict + memory: + metrics: + system.memory.utilization: + enabled: true + network: {} + paging: {} + processes: {} + process: + mute_process_exe_error: true + mute_process_io_error: true + mute_process_user_error: true + processors: + cumulativetodelta: {} + memory_limiter: + check_interval: 1s + limit_percentage: 75 + spike_limit_percentage: 15 + batch: + send_batch_size: 10000 + timeout: 10s + resourcedetection/aks: + detectors: [env, aks] + timeout: 2s + override: false + k8sattributes: + extract: + metadata: + - k8s.pod.name + - k8s.pod.uid + - k8s.deployment.name + - k8s.statefulset.name + - k8s.daemonset.name + - k8s.cronjob.name + - k8s.namespace.name + - k8s.node.name + - k8s.cluster.uid + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.name + - from: resource_attribute + name: k8s.namespace.name + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + transform: + error_mode: ignore + trace_statements: + - context: resource + statements: + - set(attributes["dt.kubernetes.workload.kind"], "statefulset") where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.statefulset.name"]) where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "deployment") where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.deployment.name"]) where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "daemonset") where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.daemonset.name"]) where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.cluster.id"], attributes["k8s.cluster.uid"]) where IsString(attributes["k8s.cluster.uid"]) + - context: span + statements: + # - set(name, "NO_NAME") where name == "" + # could be removed when https://github.com/vercel/next.js/pull/64852 is fixed upstream + - replace_pattern(name, "\\?.*", "") + - replace_match(name, "GET /api/products/*", "GET /api/products/{productId}") + log_statements: + - context: resource + statements: + - set(attributes["dt.kubernetes.workload.kind"], "statefulset") where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.statefulset.name"]) where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "deployment") where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.deployment.name"]) where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "daemonset") where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.daemonset.name"]) where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.cluster.id"], attributes["k8s.cluster.uid"]) where IsString(attributes["k8s.cluster.uid"]) + metric_statements: + - context: resource + statements: + - set(attributes["dt.kubernetes.workload.kind"], "statefulset") where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.statefulset.name"]) where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "deployment") where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.deployment.name"]) where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "daemonset") where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.daemonset.name"]) where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.cluster.id"], attributes["k8s.cluster.uid"]) where IsString(attributes["k8s.cluster.uid"]) + # - context: metric + # statements: + # - set(attributes["span.name"], "NO_NAME") where IsString(attributes["span.name"]) and attributes["span.name"] == "" + + exporters: + # NOTE: Prior to v0.86.0 use `logging` instead of `debug`. + debug: + verbosity: basic + sampling_initial: 5 + sampling_thereafter: 2000 + otlphttp: + endpoint: "${env:DT_ENDPOINT}" + headers: + Authorization: "Api-Token ${env:DT_API_TOKEN}" + + connectors: + spanmetrics: {} + + service: + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, resourcedetection/aks, k8sattributes, transform, batch] + exporters: [otlphttp, spanmetrics, debug] # debug + metrics: + receivers: [otlp, spanmetrics] # hostmetrics - permission denied + processors: [memory_limiter, cumulativetodelta, resourcedetection/aks, k8sattributes, transform, batch] + exporters: [otlphttp, debug] # debug + logs: + receivers: [otlp] + processors: [memory_limiter, resourcedetection/aks, k8sattributes, transform, batch] + exporters: [otlphttp, debug] # debug + extensions: + - health_check + extensions: + health_check: + endpoint: "0.0.0.0:13133" + path: "/" + + image: + # If you want to use the core image `otel/opentelemetry-collector`, you also need to change `command.name` value to `otelcol`. + repository: "otel/opentelemetry-collector-contrib" + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "0.108.0" + imagePullSecrets: [] + + # OpenTelemetry Collector executable + command: + name: "" + extraArgs: [] + + serviceAccount: + create: true + name: "dynatrace-otel-gateway-collector" + + clusterRole: + create: false + + podSecurityContext: {} + securityContext: {} + + nodeSelector: {} + tolerations: [] + affinity: {} + topologySpreadConstraints: [] + + # Allows for pod scheduler prioritisation + priorityClassName: "" + + extraEnvs: [] + extraEnvsFrom: + - secretRef: + name: dynatrace-otelcol-dt-api-credentials + extraVolumes: + - name: hostfs + hostPath: + path: / + + # This also supports template content, which will eventually be converted to yaml. + extraVolumeMounts: + - mountPath: /hostfs + name: hostfs + readOnly: true + + # Configuration for ports + # nodePort is also allowed + ports: + otlp: + enabled: true + containerPort: 4317 + servicePort: 4317 + hostPort: 4317 + protocol: TCP + # nodePort: 30317 + appProtocol: grpc + otlp-http: + enabled: true + containerPort: 4318 + servicePort: 4318 + hostPort: 4318 + protocol: TCP + jaeger-compact: + enabled: false + jaeger-thrift: + enabled: false + jaeger-grpc: + enabled: false + zipkin: + enabled: false + metrics: + enabled: false + containerPort: 8888 + servicePort: 8888 + protocol: TCP + health: + containerPort: 13133 + servicePort: 13133 + protocol: TCP + enabled: true + + useGOMEMLIMIT: true + + resources: + limits: + memory: 512Mi + + podAnnotations: + metrics.dynatrace.com/port: "8888" # https://www.dynatrace.com/news/blog/simplify-observability-for-all-your-custom-metrics-part-4-prometheus/ + metrics.dynatrace.com/scrape: "true" + oneagent.dynatrace.com/inject: "false" + + # Common labels to add to all otel-collector resources. Evaluated as a template. + additionalLabels: {} + # app.kubernetes.io/part-of: my-app + + # Host networking requested for this pod. Use the host's network namespace. + hostNetwork: false + + # Adding entries to Pod /etc/hosts with HostAliases + # https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/ + hostAliases: [] + # - ip: "1.2.3.4" + # hostnames: + # - "my.host.com" + + # Pod DNS policy ClusterFirst, ClusterFirstWithHostNet, None, Default, None + dnsPolicy: "" + + # Custom DNS config. Required when DNS policy is None. + dnsConfig: {} + + # only used with deployment mode + replicaCount: 1 + + # only used with deployment mode + revisionHistoryLimit: 10 + + annotations: {} + + # List of extra sidecars to add. + # This also supports template content, which will eventually be converted to yaml. + extraContainers: [] + # extraContainers: + # - name: test + # command: + # - cp + # args: + # - /bin/sleep + # - /test/sleep + # image: busybox:latest + # volumeMounts: + # - name: test + # mountPath: /test + + # List of init container specs, e.g. for copying a binary to be executed as a lifecycle hook. + # This also supports template content, which will eventually be converted to yaml. + # Another usage of init containers is e.g. initializing filesystem permissions to the OTLP Collector user `10001` in case you are using persistence and the volume is producing a permission denied error for the OTLP Collector container. + initContainers: [] + # initContainers: + # - name: test + # image: busybox:latest + # command: + # - cp + # args: + # - /bin/sleep + # - /test/sleep + # volumeMounts: + # - name: test + # mountPath: /test + # - name: init-fs + # image: busybox:latest + # command: + # - sh + # - '-c' + # - 'chown -R 10001: /var/lib/storage/otc' # use the path given as per `extensions.file_storage.directory` & `extraVolumeMounts[x].mountPath` + # volumeMounts: + # - name: opentelemetry-collector-data # use the name of the volume used for persistence + # mountPath: /var/lib/storage/otc # use the path given as per `extensions.file_storage.directory` & `extraVolumeMounts[x].mountPath` + + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 5 + httpGet: + port: 13133 + path: "/" + readinessProbe: + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 5 + httpGet: + port: 13133 + path: "/" + + # startup probe configuration + # Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + ## + #startupProbe: {} + # Number of seconds after the container has started before startup probes are initiated. + # initialDelaySeconds: 1 + # How often in seconds to perform the probe. + # periodSeconds: 10 + # Number of seconds after which the probe times out. + # timeoutSeconds: 1 + # Minimum consecutive failures for the probe to be considered failed after having succeeded. + # failureThreshold: 1 + # Duration in seconds the pod needs to terminate gracefully upon probe failure. + # terminationGracePeriodSeconds: 10 + # httpGet: + # port: 13133 + # path: / + + service: + # Enable the creation of a Service. + # By default, it's enabled on mode != daemonset. + # However, to enable it on mode = daemonset, its creation must be explicitly enabled + # enabled: true + + type: ClusterIP + # type: LoadBalancer + # loadBalancerIP: 1.2.3.4 + # loadBalancerSourceRanges: [] + + # By default, Service of type 'LoadBalancer' will be created setting 'externalTrafficPolicy: Cluster' + # unless other value is explicitly set. + # Possible values are Cluster or Local (https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip) + # externalTrafficPolicy: Cluster + + annotations: {} + + # By default, Service will be created setting 'internalTrafficPolicy: Local' on mode = daemonset + # unless other value is explicitly set. + # Setting 'internalTrafficPolicy: Cluster' on a daemonset is not recommended + # internalTrafficPolicy: Cluster + + ingress: + enabled: false + # annotations: {} + # ingressClassName: nginx + # hosts: + # - host: collector.example.com + # paths: + # - path: / + # pathType: Prefix + # port: 4318 + # tls: + # - secretName: collector-tls + # hosts: + # - collector.example.com + + # Additional ingresses - only created if ingress.enabled is true + # Useful for when differently annotated ingress services are required + # Each additional ingress needs key "name" set to something unique + additionalIngresses: [] + # - name: cloudwatch + # ingressClassName: nginx + # annotations: {} + # hosts: + # - host: collector.example.com + # paths: + # - path: / + # pathType: Prefix + # port: 4318 + # tls: + # - secretName: collector-tls + # hosts: + # - collector.example.com + + podMonitor: + # The pod monitor by default scrapes the metrics port. + # The metrics port needs to be enabled as well. + enabled: false + metricsEndpoints: + - port: metrics + # interval: 15s + + # additional labels for the PodMonitor + extraLabels: {} + # release: kube-prometheus-stack + + serviceMonitor: + # The service monitor by default scrapes the metrics port. + # The metrics port needs to be enabled as well. + enabled: false + metricsEndpoints: + - port: metrics + # interval: 15s + + # additional labels for the ServiceMonitor + extraLabels: {} + # release: kube-prometheus-stack + # Used to set relabeling and metricRelabeling configs on the ServiceMonitor + # https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config + relabelings: [] + metricRelabelings: [] + + # PodDisruptionBudget is used only if deployment enabled + podDisruptionBudget: + enabled: false + # minAvailable: 2 + # maxUnavailable: 1 + + # autoscaling is used only if mode is "deployment" or "statefulset" + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + behavior: {} + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + rollout: + rollingUpdate: {} + # When 'mode: daemonset', maxSurge cannot be used when hostPort is set for any of the ports + # maxSurge: 25% + # maxUnavailable: 0 + strategy: RollingUpdate + + prometheusRule: + enabled: false + groups: [] + # Create default rules for monitoring the collector + defaultRules: + enabled: false + + # additional labels for the PrometheusRule + extraLabels: {} + + statefulset: + # volumeClaimTemplates for a statefulset + volumeClaimTemplates: [] + podManagementPolicy: "Parallel" + # Controls if and how PVCs created by the StatefulSet are deleted. Available in Kubernetes 1.23+. + persistentVolumeClaimRetentionPolicy: + enabled: false + whenDeleted: Retain + whenScaled: Retain + + networkPolicy: + enabled: false + + # Annotations to add to the NetworkPolicy + annotations: {} + + # Configure the 'from' clause of the NetworkPolicy. + # By default this will restrict traffic to ports enabled for the Collector. If + # you wish to further restrict traffic to other hosts or specific namespaces, + # see the standard NetworkPolicy 'spec.ingress.from' definition for more info: + # https://kubernetes.io/docs/reference/kubernetes-api/policy-resources/network-policy-v1/ + allowIngressFrom: [] + # # Allow traffic from any pod in any namespace, but not external hosts + # - namespaceSelector: {} + # # Allow external access from a specific cidr block + # - ipBlock: + # cidr: 192.168.1.64/32 + # # Allow access from pods in specific namespaces + # - namespaceSelector: + # matchExpressions: + # - key: kubernetes.io/metadata.name + # operator: In + # values: + # - "cats" + # - "dogs" + + # Add additional ingress rules to specific ports + # Useful to allow external hosts/services to access specific ports + # An example is allowing an external prometheus server to scrape metrics + # + # See the standard NetworkPolicy 'spec.ingress' definition for more info: + # https://kubernetes.io/docs/reference/kubernetes-api/policy-resources/network-policy-v1/ + extraIngressRules: [] + # - ports: + # - port: metrics + # protocol: TCP + # from: + # - ipBlock: + # cidr: 192.168.1.64/32 + + # Restrict egress traffic from the OpenTelemetry collector pod + # See the standard NetworkPolicy 'spec.egress' definition for more info: + # https://kubernetes.io/docs/reference/kubernetes-api/policy-resources/network-policy-v1/ + egressRules: [] + # - to: + # - namespaceSelector: {} + # - ipBlock: + # cidr: 192.168.10.10/24 + # ports: + # - port: 1234 + # protocol: TCP + + # Allow containers to share processes across pod namespace + shareProcessNamespace: false \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/helm/readme.md b/.devcontainer/apps/astroshop/helm/readme.md new file mode 100644 index 0000000..139597f --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/readme.md @@ -0,0 +1,2 @@ + + diff --git a/.devcontainer/apps/astroshop/helm/temp-ingress/frontend-root.yaml b/.devcontainer/apps/astroshop/helm/temp-ingress/frontend-root.yaml new file mode 100644 index 0000000..3eb85b0 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/temp-ingress/frontend-root.yaml @@ -0,0 +1,30 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + meta.helm.sh/release-name: astroshop + meta.helm.sh/release-namespace: astroshop + nginx.ingress.kubernetes.io/rewrite-target: / + creationTimestamp: "2024-12-13T15:21:59Z" + generation: 1 + labels: + app.kubernetes.io/managed-by: Helm + name: astroshop-frontendproxy-root + namespace: astroshop + resourceVersion: "64609" + uid: a66e097b-e4f9-41e6-a2ef-4784804d324d +spec: + ingressClassName: nginx + rules: + - host: astroshop.4b1979e0-68dd-4ef1-a694-d6f8c9f7ccbe.dynatrace.training + http: + paths: + - backend: + service: + name: astroshop-frontend + port: + number: 8080 + path: / + pathType: Prefix +status: + loadBalancer: {} diff --git a/.devcontainer/apps/astroshop/helm/temp-ingress/frontend.yaml b/.devcontainer/apps/astroshop/helm/temp-ingress/frontend.yaml new file mode 100644 index 0000000..4859157 --- /dev/null +++ b/.devcontainer/apps/astroshop/helm/temp-ingress/frontend.yaml @@ -0,0 +1,40 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"networking.k8s.io/v1","kind":"Ingress","metadata":{"annotations":{"meta.helm.sh/release-name":"astroshop","meta.helm.sh/release-namespace":"astroshop"},"labels":{"app.kubernetes.io/component":"frontendproxy","app.kubernetes.io/instance":"astroshop","app.kubernetes.io/managed-by":"Helm","app.kubernetes.io/name":"astroshop-frontendproxy","app.kubernetes.io/part-of":"opentelemetry-demo","app.kubernetes.io/version":"1.11.1","helm.sh/chart":"opentelemetry-demo-0.32.8","opentelemetry.io/name":"astroshop-frontendproxy"},"name":"astroshop-frontendproxy","namespace":"astroshop"},"spec":{"ingressClassName":"traefik","rules":[{"host":"astroshop.4b1979e0-68dd-4ef1-a694-d6f8c9f7ccbe.dynatrace.training","http":{"paths":[{"backend":{"service":{"name":"astroshop-frontend","port":{"number":8080}}},"path":"/","pathType":"ImplementationSpecific"}]}}]}} + meta.helm.sh/release-name: astroshop + meta.helm.sh/release-namespace: astroshop + creationTimestamp: "2024-12-13T15:12:49Z" + generation: 3 + labels: + app.kubernetes.io/component: frontendproxy + app.kubernetes.io/instance: astroshop + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: astroshop-frontendproxy + app.kubernetes.io/part-of: opentelemetry-demo + app.kubernetes.io/version: 1.11.1 + helm.sh/chart: opentelemetry-demo-0.32.8 + opentelemetry.io/name: astroshop-frontendproxy + name: astroshop-frontendproxy + namespace: astroshop + resourceVersion: "64534" + uid: cf8df986-5479-41df-98ca-faba73e3b0da +spec: + ingressClassName: traefik + rules: + - host: astroshop.4b1979e0-68dd-4ef1-a694-d6f8c9f7ccbe.dynatrace.training + http: + paths: + - backend: + service: + name: astroshop-frontend + port: + number: 8080 + path: / + pathType: ImplementationSpecific +status: + loadBalancer: + ingress: + - ip: 10.0.101.123 diff --git a/.devcontainer/apps/astroshop/ingress.yaml b/.devcontainer/apps/astroshop/ingress.yaml new file mode 100644 index 0000000..388a7e9 --- /dev/null +++ b/.devcontainer/apps/astroshop/ingress.yaml @@ -0,0 +1,25 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubernetes.io/ingress.class: "nginx" + nginx.ingress.kubernetes.io/rewrite-target: / + nginx.ingress.kubernetes.io/ssl-passthrough: "true" + nginx.ingress.kubernetes.io/ssl-redirect: "false" + name: astroshop-ingress +spec: + rules: + - host: astroshop.domain.placeholder + http: + paths: + - backend: + service: + name: astroshop-frontendproxy + port: + number: 8080 + path: / + pathType: Prefix + tls: + - hosts: + - astroshop.domain.placeholder + #secretName: wdldt-production-astro-tls \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/otel-collector/collector-rbac.yaml b/.devcontainer/apps/astroshop/otel-collector/collector-rbac.yaml new file mode 100644 index 0000000..b0ed062 --- /dev/null +++ b/.devcontainer/apps/astroshop/otel-collector/collector-rbac.yaml @@ -0,0 +1,47 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: collector + labels: + app: collector +rules: + - apiGroups: + - '' + resources: + - 'pods' + - 'namespaces' + verbs: + - 'get' + - 'watch' + - 'list' + - apiGroups: + - 'apps' + resources: + - 'replicasets' + verbs: + - 'get' + - 'list' + - 'watch' + - apiGroups: + - 'extensions' + resources: + - 'replicasets' + verbs: + - 'get' + - 'list' + - 'watch' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: collector + labels: + app: collector +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: collector +subjects: + - kind: ServiceAccount + name: dynatrace-collector-opentelemetry-collector # WORKS. The collector needs to interact with the k8s API for k8sattribute processor + namespace: default \ No newline at end of file diff --git a/.devcontainer/apps/astroshop/otel-collector/crd-dynatrace-collector.yaml b/.devcontainer/apps/astroshop/otel-collector/crd-dynatrace-collector.yaml new file mode 100644 index 0000000..f94d78d --- /dev/null +++ b/.devcontainer/apps/astroshop/otel-collector/crd-dynatrace-collector.yaml @@ -0,0 +1,205 @@ +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: dynatrace-otel-gateway + annotations: + metrics.dynatrace.com/port: "8888" # https://www.dynatrace.com/news/blog/simplify-observability-for-all-your-custom-metrics-part-4-prometheus/ + metrics.dynatrace.com/scrape: "true" + oneagent.dynatrace.com/inject: "false" +spec: + envFrom: + - secretRef: + name: dynatrace-otelcol-dt-api-credentials + env: + - name: MY_POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + mode: "deployment" + # image: "ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector:0.14.0" + image: "otel/opentelemetry-collector-contrib:0.108.0" + resources: + limits: + memory: 512Mi + volumeMounts: + - mountPath: /hostfs + name: hostfs + readOnly: true + volumes: + - name: hostfs + hostPath: + path: / + config: + receivers: + otlp: + protocols: + grpc: + endpoint: ${env:MY_POD_IP}:4317 + http: + endpoint: ${env:MY_POD_IP}:4318 + hostmetrics: + root_path: /hostfs + scrapers: + cpu: + metrics: + system.cpu.utilization: + enabled: true + disk: {} + load: {} + filesystem: + exclude_mount_points: + mount_points: + - /dev/* + - /proc/* + - /sys/* + - /run/k3s/containerd/* + - /var/lib/docker/* + - /var/lib/kubelet/* + - /snap/* + match_type: regexp + exclude_fs_types: + fs_types: + - autofs + - binfmt_misc + - bpf + - cgroup2 + - configfs + - debugfs + - devpts + - devtmpfs + - fusectl + - hugetlbfs + - iso9660 + - mqueue + - nsfs + - overlay + - proc + - procfs + - pstore + - rpc_pipefs + - securityfs + - selinuxfs + - squashfs + - sysfs + - tracefs + match_type: strict + memory: + metrics: + system.memory.utilization: + enabled: true + network: {} + paging: {} + processes: {} + process: + mute_process_exe_error: true + mute_process_io_error: true + mute_process_user_error: true + processors: + cumulativetodelta: {} + memory_limiter: + check_interval: 1s + limit_percentage: 75 + spike_limit_percentage: 15 + batch: + send_batch_size: 10000 + timeout: 10s + resourcedetection/aks: + detectors: [env, aks] + timeout: 2s + override: false + k8sattributes: + extract: + metadata: + - k8s.pod.name + - k8s.pod.uid + - k8s.deployment.name + - k8s.statefulset.name + - k8s.daemonset.name + - k8s.cronjob.name + - k8s.namespace.name + - k8s.node.name + - k8s.cluster.uid + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.name + - from: resource_attribute + name: k8s.namespace.name + - sources: + - from: resource_attribute + name: k8s.pod.ip + - sources: + - from: resource_attribute + name: k8s.pod.uid + - sources: + - from: connection + transform: + error_mode: ignore + trace_statements: + - context: resource + statements: + - set(attributes["dt.kubernetes.workload.kind"], "statefulset") where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.statefulset.name"]) where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "deployment") where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.deployment.name"]) where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "daemonset") where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.daemonset.name"]) where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.cluster.id"], attributes["k8s.cluster.uid"]) where IsString(attributes["k8s.cluster.uid"]) + - context: span + statements: + # - set(name, "NO_NAME") where name == "" + # could be removed when https://github.com/vercel/next.js/pull/64852 is fixed upstream + - replace_pattern(name, "\\?.*", "") + - replace_match(name, "GET /api/products/*", "GET /api/products/{productId}") + log_statements: + - context: resource + statements: + - set(attributes["dt.kubernetes.workload.kind"], "statefulset") where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.statefulset.name"]) where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "deployment") where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.deployment.name"]) where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "daemonset") where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.daemonset.name"]) where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.cluster.id"], attributes["k8s.cluster.uid"]) where IsString(attributes["k8s.cluster.uid"]) + metric_statements: + - context: resource + statements: + - set(attributes["dt.kubernetes.workload.kind"], "statefulset") where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.statefulset.name"]) where IsString(attributes["k8s.statefulset.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "deployment") where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.deployment.name"]) where IsString(attributes["k8s.deployment.name"]) + - set(attributes["dt.kubernetes.workload.kind"], "daemonset") where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.workload.name"], attributes["k8s.daemonset.name"]) where IsString(attributes["k8s.daemonset.name"]) + - set(attributes["dt.kubernetes.cluster.id"], attributes["k8s.cluster.uid"]) where IsString(attributes["k8s.cluster.uid"]) + # - context: metric + # statements: + # - set(attributes["span.name"], "NO_NAME") where IsString(attributes["span.name"]) and attributes["span.name"] == "" + + exporters: + # NOTE: Prior to v0.86.0 use `logging` instead of `debug`. + debug: + verbosity: basic + sampling_initial: 5 + sampling_thereafter: 2000 + otlphttp: + endpoint: "${env:DT_ENDPOINT}" + headers: + Authorization: "Api-Token ${env:DT_API_TOKEN}" + + connectors: + spanmetrics: {} + + service: + pipelines: + traces: + receivers: [otlp] + processors: [memory_limiter, resourcedetection/aks, k8sattributes, transform, batch] + exporters: [otlphttp, spanmetrics, debug] # debug + metrics: + receivers: [otlp, spanmetrics] # hostmetrics - permission denied + processors: [memory_limiter, cumulativetodelta, resourcedetection/aks, k8sattributes, transform, batch] + exporters: [otlphttp, debug] # debug + logs: + receivers: [otlp] + processors: [memory_limiter, resourcedetection/aks, k8sattributes, transform, batch] + exporters: [otlphttp, debug] # debug \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/LICENSE b/.devcontainer/apps/easytrade/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/.devcontainer/apps/easytrade/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/.devcontainer/apps/easytrade/README.md b/.devcontainer/apps/easytrade/README.md new file mode 100644 index 0000000..b945818 --- /dev/null +++ b/.devcontainer/apps/easytrade/README.md @@ -0,0 +1,195 @@ +# EasyTrade + +A project consisting of many small services that connect to each other. +It is made like a stock broking application - it allows it's users to buy&sell some stocks/instruments. +Of course it is all fake data and the price has a 24 hour cycle... + +## Architecture diagram + +![EasyTrade architecture](./img/architecture.jpg) + +## Database diagram + +![EasyTrade database](./img/database.jpg) + +## Service list + +EasyTrade consists of the following services/components: + +| Service | Proxy port | Proxy endpoint | +| -------------------------------------------------------- | ---------- | ------------------ | +| [Account service](./docs/accountservice.md) | 80 | `/accountservice` | +| [Aggregator service](./docs/aggregatorservice.md) | 80 | `---` | +| [Broker service](./docs/brokerservice.md) | 80 | `/broker` | +| [Calculation service](./docs/calculationservice.md) | 80 | `---` | +| [Content creator](./docs/contentcreator.md) | 80 | `---` | +| [Db](./docs/db.md) | 80 | `---` | +| [Engine](./docs/engine.md) | 80 | `/engine` | +| [Flagsmith](./docs/flagsmith.md) | 8000 | `/` | +| [Frontend](./docs/frontend.md) | 80 | `/` | +| [Frontend reverse-proxy](./docs/frontendreverseproxy.md) | 80 | `---` | +| [Login service](./docs/loginservice.md) | 80 | `/login` | +| [Manager](./docs/manager.md) | 80 | `---` | +| [New Frontend](./docs/newfrontend.md) | 80 | `/new/` | +| [Offer service](./docs/offerservice.md) | 80 | `/offerservice` | +| [Pricing service](./docs/pricing-service.md) | 80 | `/pricing-service` | + + +> To learn more about endpoints / swagger for the services go to their respective readmes + +## Docker compose + +To run the easytrade using docker you can use provided `compose.yaml`. +To use it you need to have: + +- Docker with minimal version **v20.10.13** + - you can follow [this](https://docs.docker.com/engine/install/ubuntu/) guide to update Docker + - this guide also covers installing Docker Compose Plugin +- Docker Compose Plugin + ```bash + sudo apt update + sudo apt install docker-compose-plugin + ``` + - more information in [this](https://docs.docker.com/compose/install/linux/) guide + +With this you can run + +```bash +docker compose up +# or to run in the background +docker compose up -d +``` + +You should be able to access the app at `localhost:80` or simply `localhost`. +To use the flagsmith access the app at `localhost:8000`. + +> **NOTE:** It make take a few minutes for the app to stabilize, you may expirience errors in the frontend or see missing data before that happens. + +> **NOTE:** Docker Compose V1 which came as a separate binary (`docker-compose`) will not work with this version. You can check this [guide](https://www.howtogeek.com/devops/how-to-upgrade-to-docker-compose-v2/) on how to upgrade. + +## Kubernetes instructions + +To deploy Easytrade in kubernetes you need to have: + +- `kubectl` tool installed + - here's a [guide](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/) on how to get it +- `kubeconfig` to access the cluster you want to deploy it on + - more info on it [here](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) + +```bash +# first create the namespace for it +kubectl create namespace easytrade + +# then use the manifests to deploy +kubectl -n easytrade apply -f ./kubernetes-manifests + +# to get the ip of reverse proxy +# look for EXTERNAL-IP of frontendreverseproxy +# it may take some time before it gets assigned +kubectl -n easytrade get svc + +# to delete the deployment +kubectl delete namespace easytrade +``` + +## Where to start + +After starting easyTrade application you can: + +- go to the frontend and try it out. Just go to the machines IP address, or "localhost" and you should see the login page. You can either create a new user, or use one of superusers (with easy passwords) like "demouser/demopass" or "specialuser/specialpass". Remember that in order to buy stocks you need money, so visit the deposit page first. +- go to some services swagger endpoint - you will find proper instructions in the dedicated service readmes. +- after some time go to dynatrace to configure your application and see what is going on in easyTrade - to have it work you will need an agent on the machine where you started easyTrade :P + +## EasyTrade users + +If you want to use easyTrade, then you will need a user. You can either: + +- use the existing user - he has some preinserted data and new data is being generated from time to time: + + - login: james_norton + - pass: pass_james_123 + +- create a new user - click on "Sign up" on the login page and create a new user. + +> **NOTE:** After creating a new user there is no confirmation given, no email sent and you are not redirected... Just go back to login page and try to login. It should work :) + +## Problem patterns + +Currently there are only 2 problem patterns supported in easyTrade: + +1. DbNotRespondingPlugin - after turning it on no new trades can be created as the database will throw an error. This problem pattern is kind of proof on concept that problem patterns work. Running it for around 20 minutes should generate a problem in dynatrace. + +2. ErgoAggregatorSlowdownPlugin - after turning it on 2 of the aggregators will start receiving slower responses which will make them stop sending requests after some time. A potential run could take: + + - 15 min - then we will notice a small slowdown (for 150 seconds) followed by 40% lower traffic for 15 minutes on some requests + - 20 min - then we will notice a small slowdown (for 150 seconds) followed by 40% lower traffic for 30 minutes on some requests + +To turn a plugin on/off send a request similar to the following: + +```sh +curl -X PUT "http://{IP_ADDRESS}:8000/api/v1/environments/{API_KEY}/featurestates/{FEATURE_ID}/" + -H "accept: application/json" + -H "Authorization: Token {TOKEN}" + -d "{\"enabled\": {VALUE}}" +``` + +Of course please set the value of "IP_ADDRESS" to the correct host IP and VALUE to false/true. \ +> **NOTE:** More information on flagsmith's parameters available in [flagsmith's doc](./docs/flagsmith.md). + +## EasyTrade on Dynatrace - how to configure + +1. Go to your tenant. + +2. Create a new application. You can either make it based on the traffic detected in "My web application" or just manually. The application detection rule will most probably look like this: + + - "frontendreverseproxy" - in the case of docker-compose based application + - "111.111.111.111" - in the case of kubernetes based application + ![Detection rules](./img/dt/1.png) + +3. Go to you new application. Click on "Edit". + ![Edit application 1](./img/dt/2.png) + ![Edit application 2](./img/dt/3.png) + +4. Go to "Capturing" → "Async web requests and SPAs". + ![Async web requests and SPAs](./img/dt/4.png) + +5. Turn on the capturing of "XmlHttpRequest (XHR)" and "fetch() requests". + +6. Click on the save button. + ![Save button](./img/dt/5.png) + +7. Go to "Capturing" → "User tag". + ![User tag detection](./img/dt/6.png) + +8. Click on the "Add user tag rule". + +9. Select the "Source type" as "CSS selector" and fill in the value of "CSS selector" as "label.labelPositioning". + +10. Turn on the cleanup rule and set the "Regex" as "Hi,.(.\*+)". + +11. Click on the save button. + +## Business events in Dynatrace + +EasyTrade application has been developed in order to showcase business events. Usually business events can be created in two ways: + +- directly - using one of Dynatrace SDKs in the code - so for example in Javascript or Java +- inderectly - configure catch rules for request that are monitored by Dynatrace + +If you want to learn more about business events then we suggest looking at the information on our website: [Business event capture](https://www.dynatrace.com/support/help/platform-modules/business-analytics/ba-events-capturing). There you will find information on how to create events directly (with OpenKit, Javascript, Android and more) and inderectly with capture rules in Dynatrace. + +For those interested in creating capturing rules for easyTrade we suggest to have a look at the configuration exported with Monaco in this repository. Have a look at the [README](./monaco/README.md) + +## Resolve **Span Default Service** showing instead of regular .NET services + +If instead of regular .NET services, like this: + +![Correct dotnet service names](./img/dt/correct_dotnet_services.png) + +You see **Span Default Service** for all of them, like this: + +![Spand default service names](./img/dt/wrong_dotnet_services.png) + +Go into `Settings -> Server-side service monitoring -> Span capturing`, and click `Add item` to add rule that ignores span with name `Microsoft.AspNetCore.Hosting.HttpRequestIn`, like so: + +![Ignore ASP.NET span](./img/dt/span_ignore_rule.png) diff --git a/.devcontainer/apps/easytrade/compose.yaml b/.devcontainer/apps/easytrade/compose.yaml new file mode 100644 index 0000000..0e37519 --- /dev/null +++ b/.devcontainer/apps/easytrade/compose.yaml @@ -0,0 +1,238 @@ +x-logging: &default-logging + driver: json-file + options: + max-file: "5" + max-size: "10m" + +x-service: &default-service + restart: always + logging: *default-logging + +x-aggregator-service: &default-aggregator-service + <<: *default-service + image: ${REGISTRY}/aggregatorservice:${TAG} + depends_on: + - offerservice + +x-aggregator-service-env: &default-aggregator-service-env + NODE_ENV: production + OFFER_SERVICE: offerservice:8080 + +x-flagsmith: &flagsmith-env + FLAGSMITH_PROJECT: easytrade + FLAGSMITH_PROTOCOL: http + FLAGSMITH_BASE_URL: flagsmith + FLAGSMITH_PORT: 8000 + FLAGSMITH_PASS: adminpass123 + FLAGSMITH_EMAIL: admin@mail.com + FLAGSMITH_ENV_KEY: ${FLAGSMITH_ENV_KEY} + +services: + db: + <<: *default-service + image: ${REGISTRY}/db:${TAG} + environment: + SA_PASSWORD: yourStrong(!)Password + postgres: + hostname: postgres + container_name: postgres + image: ${REGISTRY}/postgres:${TAG} + environment: + POSTGRES_PASSWORD: password + POSTGRES_DB: flagsmith + contentcreator: + <<: *default-service + image: ${REGISTRY}/contentcreator:${TAG} + depends_on: + - db + environment: + MSSQL_CONNECTIONSTRING: jdbc:sqlserver://db:1433;database=TradeManagement;user=sa;password=yourStrong(!)Password;encrypt=false;trustServerCertificate=false;loginTimeout=30; + + manager: + <<: *default-service + image: ${REGISTRY}/manager:${TAG} + depends_on: + - db + environment: + <<: *flagsmith-env + MSSQL_CONNECTIONSTRING: Data Source=db;Initial Catalog=TradeManagement;Persist Security Info=True;User ID=sa;Password=yourStrong(!)Password;TrustServerCertificate=true + PROXY_PREFIX: manager + + pricing-service: + <<: *default-service + image: ${REGISTRY}/pricing-service:${TAG} + depends_on: + - db + - rabbitmq + environment: + MSSQL_CONNECTIONSTRING: sqlserver://sa:yourStrong(!)Password@db:1433?database=TradeManagement&connection+encrypt=false&connection+TrustServerCertificate=false&connection+loginTimeout=30 + RABBITMQ_HOST: rabbitmq + RABBITMQ_USER: userxxx + RABBITMQ_PASSWORD: passxxx + RABBITMQ_QUEUE: Trade_Data_Raw + PROXY_PREFIX: pricing-service + + brokerservice: + <<: *default-service + image: ${REGISTRY}/brokerservice:${TAG} + depends_on: + - manager + - accountservice + - pricing-service + environment: + MANAGER_HOSTANDPORT: manager:80 + ACCOUNTSERVICE_HOSTANDPORT: accountservice:8080 + PRICINGSERVICE_HOSTANDPORT: pricing-service:80 + PROXY_PREFIX: broker + + rabbitmq: + <<: *default-service + image: ${REGISTRY}/rabbitmq:${TAG} + + calculationservice: + <<: *default-service + image: ${REGISTRY}/calculationservice:${TAG} + depends_on: + - rabbitmq + + frontend: + <<: *default-service + image: ${REGISTRY}/frontend:${TAG} + depends_on: + - brokerservice + - loginservice + - pricing-service + - accountservice + + newfrontend: + <<: *default-service + image: ${REGISTRY}/newfrontend:${TAG} + + loginservice: + <<: *default-service + image: ${REGISTRY}/loginservice:${TAG} + depends_on: + - db + environment: + MSSQL_CONNECTIONSTRING: Data Source=db;Initial Catalog=TradeManagement;Persist Security Info=True;User ID=sa;Password=yourStrong(!)Password;TrustServerCertificate=true + PROXY_PREFIX: login + + frontendreverseproxy: + <<: *default-service + image: ${REGISTRY}/frontendreverseproxy:${TAG} + depends_on: + - brokerservice + - frontend + - newfrontend + - loginservice + - pricing-service + - flagsmith + - offerservice + - accountservice + - engine + ports: + - 80:80 + - 8000:8000 + + headlessloadgen: + <<: *default-service + image: gcr.io/dynatrace-demoability/headlessloadgen:${LOADGEN_TAG} + depends_on: + - frontendreverseproxy + environment: + EASY_TRADE_DOMAIN: frontendreverseproxy + EASY_TRADE_PORT: 80 + + offerservice: + <<: *default-service + image: ${REGISTRY}/offerservice:${TAG} + depends_on: + - loginservice + - manager + environment: + <<: *flagsmith-env + LOGIN_SERVICE_PORT: 80 + LOGIN_SERVICE_BASE_URL: loginservice + MANAGER_BASE_URL: manager + MANAGER_PORT: 80 + + flagsmith: + hostname: flagsmith + container_name: flagsmith + image: ${REGISTRY}/flagsmith:${TAG} + depends_on: + - postgres + environment: + <<: *flagsmith-env + DJANGO_ALLOWED_HOSTS: "*" + DATABASE_URL: postgresql://postgres:password@postgres:5432/flagsmith + DISABLE_INFLUXDB_FEATURES: "true" # set to 'false' to enable InfluxDB + ENV: prod + restart: always + + accountservice: + <<: *default-service + image: ${REGISTRY}/accountservice:${TAG} + depends_on: + - manager + environment: + MANAGER_HOSTANDPORT: manager:80 + PROXY_PREFIX: accountservice + + engine: + <<: *default-service + image: ${REGISTRY}/engine:${TAG} + depends_on: + - brokerservice + environment: + BROKER_HOSTANDPORT: brokerservice:80 + PROXY_PREFIX: engine + + aggregatorservice_1: + <<: *default-aggregator-service + environment: + <<: *default-aggregator-service-env + PLATFORM: dynatestsieger.at + STARTER_PACKAGE_PROBABILITY: 0.6 + LIGHT_PACKAGE_PROBABILITY: 0.3 + PRO_PACKAGE_PROBABILITY: 0.1 + + aggregatorservice_2: + <<: *default-aggregator-service + environment: + <<: *default-aggregator-service-env + PLATFORM: tradeCom.co.uk + STARTER_PACKAGE_PROBABILITY: 0.8 + LIGHT_PACKAGE_PROBABILITY: 0.2 + PRO_PACKAGE_PROBABILITY: 0 + + aggregatorservice_3: + <<: *default-aggregator-service + environment: + <<: *default-aggregator-service-env + PLATFORM: CryptoTrading.com + FILTER: '["Crypto"]' + MAXFEE: 0 + STARTER_PACKAGE_PROBABILITY: 0.5 + LIGHT_PACKAGE_PROBABILITY: 0.4 + PRO_PACKAGE_PROBABILITY: 0.1 + + aggregatorservice_4: + <<: *default-aggregator-service + environment: + <<: *default-aggregator-service-env + PLATFORM: CheapTrading.mi + MAXFEE: 0 + STARTER_PACKAGE_PROBABILITY: 1 + LIGHT_PACKAGE_PROBABILITY: 0 + PRO_PACKAGE_PROBABILITY: 0 + + aggregatorservice_5: + <<: *default-aggregator-service + environment: + <<: *default-aggregator-service-env + PLATFORM: Stratton-oakmount.com + FILTER: '["Shares"]' + STARTER_PACKAGE_PROBABILITY: 0 + LIGHT_PACKAGE_PROBABILITY: 0.1 + PRO_PACKAGE_PROBABILITY: 0.9 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/img/architecture.jpg b/.devcontainer/apps/easytrade/img/architecture.jpg new file mode 100644 index 0000000..fd337fe Binary files /dev/null and b/.devcontainer/apps/easytrade/img/architecture.jpg differ diff --git a/.devcontainer/apps/easytrade/img/database.jpg b/.devcontainer/apps/easytrade/img/database.jpg new file mode 100644 index 0000000..0c5ae8d Binary files /dev/null and b/.devcontainer/apps/easytrade/img/database.jpg differ diff --git a/.devcontainer/apps/easytrade/img/dt/1.png b/.devcontainer/apps/easytrade/img/dt/1.png new file mode 100644 index 0000000..95912a2 Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/1.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/2.png b/.devcontainer/apps/easytrade/img/dt/2.png new file mode 100644 index 0000000..4f00589 Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/2.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/3.png b/.devcontainer/apps/easytrade/img/dt/3.png new file mode 100644 index 0000000..b6a524a Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/3.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/4.png b/.devcontainer/apps/easytrade/img/dt/4.png new file mode 100644 index 0000000..760aede Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/4.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/5.png b/.devcontainer/apps/easytrade/img/dt/5.png new file mode 100644 index 0000000..5be4c7c Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/5.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/6.png b/.devcontainer/apps/easytrade/img/dt/6.png new file mode 100644 index 0000000..01fdc9b Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/6.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/correct_dotnet_services.png b/.devcontainer/apps/easytrade/img/dt/correct_dotnet_services.png new file mode 100644 index 0000000..8e59bd1 Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/correct_dotnet_services.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/span_ignore_rule.png b/.devcontainer/apps/easytrade/img/dt/span_ignore_rule.png new file mode 100644 index 0000000..06d3710 Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/span_ignore_rule.png differ diff --git a/.devcontainer/apps/easytrade/img/dt/wrong_dotnet_services.png b/.devcontainer/apps/easytrade/img/dt/wrong_dotnet_services.png new file mode 100644 index 0000000..bbbaa36 Binary files /dev/null and b/.devcontainer/apps/easytrade/img/dt/wrong_dotnet_services.png differ diff --git a/.devcontainer/apps/easytrade/manifests/accountservice.yaml b/.devcontainer/apps/easytrade/manifests/accountservice.yaml new file mode 100644 index 0000000..60e5f81 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/accountservice.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: accountservice +spec: + selector: + matchLabels: + app: accountservice + template: + metadata: + labels: + app: accountservice + spec: + containers: + - name: accountservice + image: gcr.io/dynatrace-demoability/easytrade/accountservice:f505fb9 + ports: + - containerPort: 8080 + env: + - name: MANAGER_HOSTANDPORT + value: "manager:80" + - name: PROXY_PREFIX + value: "accountservice" + resources: + requests: + cpu: 100m + memory: 300Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: accountservice +spec: + type: ClusterIP + selector: + app: accountservice + ports: + - name: http + port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/aggregatorservice.yaml b/.devcontainer/apps/easytrade/manifests/aggregatorservice.yaml new file mode 100644 index 0000000..ff82a24 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/aggregatorservice.yaml @@ -0,0 +1,119 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: aggregatorservice-common +data: + NODE_ENV: "production" + OFFER_SERVICE: "offerservice:8080" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: aggregatorservice-1 +data: + PLATFORM: "dynatestsieger.at" + STARTER_PACKAGE_PROBABILITY: "0.6" + LIGHT_PACKAGE_PROBABILITY: "0.3" + PRO_PACKAGE_PROBABILITY: "0.1" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: aggregatorservice-2 +data: + PLATFORM: "tradeCom.co.uk" + STARTER_PACKAGE_PROBABILITY: "0.8" + LIGHT_PACKAGE_PROBABILITY: "0.2" + PRO_PACKAGE_PROBABILITY: "0" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: aggregatorservice-3 +data: + PLATFORM: "CryptoTrading.com" + FILTER: '["Crypto"]' + MAXFEE: "0" + STARTER_PACKAGE_PROBABILITY: "0.5" + LIGHT_PACKAGE_PROBABILITY: "0.4" + PRO_PACKAGE_PROBABILITY: "0.1" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: aggregatorservice-4 +data: + PLATFORM: "CheapTrading.mi" + MAXFEE: "0" + STARTER_PACKAGE_PROBABILITY: "1" + LIGHT_PACKAGE_PROBABILITY: "0" + PRO_PACKAGE_PROBABILITY: "0" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: aggregatorservice-5 +data: + PLATFORM: "Stratton-oakmount.com" + FILTER: '["Shares"]' + STARTER_PACKAGE_PROBABILITY: "0" + LIGHT_PACKAGE_PROBABILITY: "0.1" + PRO_PACKAGE_PROBABILITY: "0.9" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: aggregatorservice +spec: + selector: + matchLabels: + app: aggregatorservice + template: + metadata: + labels: + app: aggregatorservice + spec: + containers: + - name: aggregatorservice-1 + image: gcr.io/dynatrace-demoability/easytrade/aggregatorservice:f505fb9 + envFrom: + - configMapRef: + name: aggregatorservice-common + - configMapRef: + name: aggregatorservice-1 + resources: &default_res + requests: + cpu: 20m + memory: 50Mi + - name: aggregatorservice-2 + image: gcr.io/dynatrace-demoability/easytrade/aggregatorservice:f505fb9 + envFrom: + - configMapRef: + name: aggregatorservice-common + - configMapRef: + name: aggregatorservice-2 + resources: *default_res + - name: aggregatorservice-3 + image: gcr.io/dynatrace-demoability/easytrade/aggregatorservice:f505fb9 + envFrom: + - configMapRef: + name: aggregatorservice-common + - configMapRef: + name: aggregatorservice-3 + resources: *default_res + - name: aggregatorservice-4 + image: gcr.io/dynatrace-demoability/easytrade/aggregatorservice:f505fb9 + envFrom: + - configMapRef: + name: aggregatorservice-common + - configMapRef: + name: aggregatorservice-4 + resources: *default_res + - name: aggregatorservice-5 + image: gcr.io/dynatrace-demoability/easytrade/aggregatorservice:f505fb9 + envFrom: + - configMapRef: + name: aggregatorservice-common + - configMapRef: + name: aggregatorservice-5 + resources: *default_res \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/brokerservice.yaml b/.devcontainer/apps/easytrade/manifests/brokerservice.yaml new file mode 100644 index 0000000..3acb6cb --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/brokerservice.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: brokerservice +spec: + selector: + matchLabels: + app: brokerservice + template: + metadata: + labels: + app: brokerservice + spec: + containers: + - name: brokerservice + image: gcr.io/dynatrace-demoability/easytrade/brokerservice:f505fb9 + ports: + - containerPort: 80 + env: + - name: MANAGER_HOSTANDPORT + value: "manager:80" + - name: ACCOUNTSERVICE_HOSTANDPORT + value: "accountservice:8080" + - name: PRICINGSERVICE_HOSTANDPORT + value: "pricing-service:8080" + - name: PROXY_PREFIX + value: "broker" + resources: + requests: + cpu: 50m + memory: 350Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: brokerservice +spec: + type: ClusterIP + selector: + app: brokerservice + ports: + - name: http + port: 80 + targetPort: 80 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/calculationservice.yaml b/.devcontainer/apps/easytrade/manifests/calculationservice.yaml new file mode 100644 index 0000000..797d6b4 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/calculationservice.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: calculationservice +spec: + selector: + matchLabels: + app: calculationservice + template: + metadata: + labels: + app: calculationservice + spec: + containers: + - name: calculationservice + image: gcr.io/dynatrace-demoability/easytrade/calculationservice:f505fb9 + resources: + requests: + cpu: 10m + memory: 10Mi \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/contentcreator.yaml b/.devcontainer/apps/easytrade/manifests/contentcreator.yaml new file mode 100644 index 0000000..8d1ad30 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/contentcreator.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: contentcreator +spec: + selector: + matchLabels: + app: contentcreator + template: + metadata: + labels: + app: contentcreator + spec: + containers: + - name: contentcreator + image: gcr.io/dynatrace-demoability/easytrade/contentcreator:f505fb9 + env: + - name: MSSQL_CONNECTIONSTRING + value: "jdbc:sqlserver://db:1433;database=TradeManagement;user=sa;password=yourStrong(!)Password;encrypt=false;trustServerCertificate=false;loginTimeout=30;" + resources: + requests: + cpu: 50m + memory: 250Mi \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/db.yaml b/.devcontainer/apps/easytrade/manifests/db.yaml new file mode 100644 index 0000000..15658ae --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/db.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: db +spec: + selector: + matchLabels: + app: db + template: + metadata: + labels: + app: db + spec: + containers: + - name: db + image: gcr.io/dynatrace-demoability/easytrade/db:f505fb9 + ports: + - containerPort: 1433 + env: + - name: SA_PASSWORD + value: "yourStrong(!)Password" + resources: + requests: + cpu: 100m + memory: 1Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: db +spec: + type: ClusterIP + selector: + app: db + ports: + - name: mssql + port: 1433 + targetPort: 1433 diff --git a/.devcontainer/apps/easytrade/manifests/engine.yaml b/.devcontainer/apps/easytrade/manifests/engine.yaml new file mode 100644 index 0000000..d48113e --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/engine.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: engine +spec: + selector: + matchLabels: + app: engine + template: + metadata: + labels: + app: engine + spec: + containers: + - name: engine + image: gcr.io/dynatrace-demoability/easytrade/engine:f505fb9 + ports: + - containerPort: 8080 + env: + - name: BROKER_HOSTANDPORT + value: "brokerservice:80" + - name: PROXY_PREFIX + value: "engine" + resources: + requests: + cpu: 50m + memory: 300Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: engine +spec: + type: ClusterIP + selector: + app: engine + ports: + - name: http + port: 8080 + targetPort: 8080 diff --git a/.devcontainer/apps/easytrade/manifests/flagsmith-setup.yaml b/.devcontainer/apps/easytrade/manifests/flagsmith-setup.yaml new file mode 100644 index 0000000..c88a886 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/flagsmith-setup.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: flagsmith-setup +data: + FLAGSMITH_PROTOCOL: http + FLAGSMITH_BASE_URL: flagsmith + FLAGSMITH_PORT: '8000' + FLAGSMITH_PASS: adminpass123 + FLAGSMITH_EMAIL: admin@mail.com + FLAGSMITH_PROJECT: easytrade + FLAGSMITH_ENV_KEY: 4AYNWx8b4xjhS3Tj9axmwG + \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/flagsmith.yaml b/.devcontainer/apps/easytrade/manifests/flagsmith.yaml new file mode 100644 index 0000000..4a5b911 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/flagsmith.yaml @@ -0,0 +1,50 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: flagsmith-config +data: + DJANGO_ALLOWED_HOSTS: "*" + DATABASE_URL: postgresql://postgres:password@postgres:5432/flagsmith + DISABLE_INFLUXDB_FEATURES: "true" + ENV: prod +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: flagsmith +spec: + selector: + matchLabels: + app: flagsmith + template: + metadata: + labels: + app: flagsmith + spec: + containers: + - name: flagsmith + image: gcr.io/dynatrace-demoability/easytrade/flagsmith:f505fb9 + ports: + - containerPort: 8000 + envFrom: + - configMapRef: + name: flagsmith-config + - configMapRef: + name: flagsmith-setup + resources: + requests: + cpu: 30m + memory: 300Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: flagsmith +spec: + type: ClusterIP + selector: + app: flagsmith + ports: + - name: flagsmith + port: 8000 + targetPort: 8000 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/frontend.yaml b/.devcontainer/apps/easytrade/manifests/frontend.yaml new file mode 100644 index 0000000..0d563dc --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/frontend.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: gcr.io/dynatrace-demoability/easytrade/frontend:f505fb9 + ports: + - containerPort: 3000 + resources: + requests: + cpu: 50m + memory: 50Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 3000 + targetPort: 3000 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/frontendreverseproxy.yaml b/.devcontainer/apps/easytrade/manifests/frontendreverseproxy.yaml new file mode 100644 index 0000000..446b8d6 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/frontendreverseproxy.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontendreverseproxy +spec: + selector: + matchLabels: + app: frontendreverseproxy + template: + metadata: + labels: + app: frontendreverseproxy + spec: + containers: + - name: frontendreverseproxy + image: gcr.io/dynatrace-demoability/easytrade/frontendreverseproxy:f505fb9 + ports: + - containerPort: 80 + - containerPort: 8000 + resources: + requests: + cpu: 25m + memory: 25Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontendreverseproxy-easytrade +spec: + type: LoadBalancer + selector: + app: frontendreverseproxy + ports: + - name: easytrade-frontend + port: 80 + targetPort: 80 + - name: flagsmith-frontend + port: 8000 + targetPort: 8000 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/headlessloadgen.yaml b/.devcontainer/apps/easytrade/manifests/headlessloadgen.yaml new file mode 100644 index 0000000..50e34a0 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/headlessloadgen.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: headlessloadgen + name: headlessloadgen +spec: + selector: + matchLabels: + app: headlessloadgen + template: + metadata: + labels: + app: headlessloadgen + spec: + containers: + - name: headlessloadgen + image: gcr.io/dynatrace-demoability/headlessloadgen:0deedcc + imagePullPolicy: Always + resources: + requests: + memory: "500Mi" + cpu: "500m" + limits: + memory: "1Gi" + cpu: "1000m" + env: + - name: EASY_TRADE_DOMAIN + value: frontendreverseproxy-easytrade + - name: EASY_TRADE_PORT + value: "80" + - name: NODE_LOG_LEVEL + value: "info" \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/loginservice.yaml b/.devcontainer/apps/easytrade/manifests/loginservice.yaml new file mode 100644 index 0000000..c5a236a --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/loginservice.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loginservice +spec: + selector: + matchLabels: + app: loginservice + template: + metadata: + labels: + app: loginservice + spec: + containers: + - name: loginservice + image: gcr.io/dynatrace-demoability/easytrade/loginservice:f505fb9 + ports: + - containerPort: 80 + env: + - name: MSSQL_CONNECTIONSTRING + value: "Data Source=db;Initial Catalog=TradeManagement;Persist Security Info=True;User ID=sa;Password=yourStrong(!)Password;TrustServerCertificate=true" + - name: PROXY_PREFIX + value: "login" + resources: + requests: + cpu: 50m + memory: 150Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: loginservice +spec: + type: ClusterIP + selector: + app: loginservice + ports: + - name: http + port: 80 + targetPort: 80 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/manager.yaml b/.devcontainer/apps/easytrade/manifests/manager.yaml new file mode 100644 index 0000000..f976977 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/manager.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: manager-envs +data: + MSSQL_CONNECTIONSTRING: "Data Source=db;Initial Catalog=TradeManagement;Persist Security Info=True;User ID=sa;Password=yourStrong(!)Password;TrustServerCertificate=true" + PROXY_PREFIX: "manager" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: manager +spec: + selector: + matchLabels: + app: manager + template: + metadata: + labels: + app: manager + spec: + containers: + - name: manager + image: gcr.io/dynatrace-demoability/easytrade/manager:f505fb9 + ports: + - containerPort: 80 + envFrom: + - configMapRef: + name: manager-envs + - configMapRef: + name: flagsmith-setup + resources: + requests: + cpu: 50m + memory: 300Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: manager +spec: + type: ClusterIP + selector: + app: manager + ports: + - name: http + port: 80 + targetPort: 80 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/newfrontend.yaml b/.devcontainer/apps/easytrade/manifests/newfrontend.yaml new file mode 100644 index 0000000..4ebbcad --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/newfrontend.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: newfrontend +spec: + selector: + matchLabels: + app: newfrontend + template: + metadata: + labels: + app: newfrontend + spec: + containers: + - name: newfrontend + image: gcr.io/dynatrace-demoability/easytrade/newfrontend:f505fb9 + ports: + - containerPort: 3000 + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: newfrontend +spec: + type: ClusterIP + selector: + app: newfrontend + ports: + - name: http + port: 3000 + targetPort: 3000 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/offerservice.yaml b/.devcontainer/apps/easytrade/manifests/offerservice.yaml new file mode 100644 index 0000000..4703fbd --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/offerservice.yaml @@ -0,0 +1,50 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: offerservice-envs +data: + LOGIN_SERVICE_PORT: '80' + LOGIN_SERVICE_BASE_URL: loginservice + MANAGER_BASE_URL: manager + MANAGER_PORT: '80' +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: offerservice +spec: + selector: + matchLabels: + app: offerservice + template: + metadata: + labels: + app: offerservice + spec: + containers: + - name: offerservice + image: gcr.io/dynatrace-demoability/easytrade/offerservice:f505fb9 + ports: + - containerPort: 8080 + envFrom: + - configMapRef: + name: offerservice-envs + - configMapRef: + name: flagsmith-setup + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: offerservice +spec: + type: ClusterIP + selector: + app: offerservice + ports: + - name: http + port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/postgres.yaml b/.devcontainer/apps/easytrade/manifests/postgres.yaml new file mode 100644 index 0000000..90d3aac --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/postgres.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres +spec: + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: gcr.io/dynatrace-demoability/easytrade/postgres:f505fb9 + ports: + - containerPort: 5432 + env: + - name: POSTGRES_DB + value: flagsmith + - name: POSTGRES_PASSWORD + value: password + resources: + requests: + cpu: 25m + memory: 50Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres +spec: + type: ClusterIP + selector: + app: postgres + ports: + - name: postgres + port: 5432 + targetPort: 5432 diff --git a/.devcontainer/apps/easytrade/manifests/pricing-service.yaml b/.devcontainer/apps/easytrade/manifests/pricing-service.yaml new file mode 100644 index 0000000..ff639a1 --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/pricing-service.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: pricing-service +spec: + selector: + matchLabels: + app: pricing-service + template: + metadata: + labels: + app: pricing-service + spec: + containers: + - name: pricing-service + image: gcr.io/dynatrace-demoability/easytrade/pricing-service:f505fb9 + ports: + - containerPort: 8080 + env: + - name: MSSQL_CONNECTIONSTRING + value: "sqlserver://sa:yourStrong(!)Password@db:1433?database=TradeManagement&connection+encrypt=false&connection+TrustServerCertificate=false&connection+loginTimeout=30" + - name: RABBITMQ_HOST + value: "rabbitmq" + - name: RABBITMQ_USER + value: "userxxx" + - name: RABBITMQ_PASSWORD + value: "passxxx" + - name: RABBITMQ_QUEUE + value: "Trade_Data_Raw" + - name: PROXY_PREFIX + value: "pricing-service" + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: pricing-service +spec: + type: ClusterIP + selector: + app: pricing-service + ports: + - name: http + port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/manifests/rabbitmq.yaml b/.devcontainer/apps/easytrade/manifests/rabbitmq.yaml new file mode 100644 index 0000000..adf88ef --- /dev/null +++ b/.devcontainer/apps/easytrade/manifests/rabbitmq.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq +spec: + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + containers: + - name: rabbitmq + image: gcr.io/dynatrace-demoability/easytrade/rabbitmq:f505fb9 + ports: + - containerPort: 5672 + - containerPort: 15672 + resources: + requests: + cpu: 50m + memory: 150Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq +spec: + type: ClusterIP + selector: + app: rabbitmq + ports: + - name: rabbitmq-listener + port: 5672 + targetPort: 5672 + - name: rabbitmq-ui + port: 15672 + targetPort: 15672 \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/README.md b/.devcontainer/apps/easytrade/monaco/README.md new file mode 100644 index 0000000..9db4397 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/README.md @@ -0,0 +1,24 @@ +# easytrade monaco configuration +NOTE: use monaco2: https://github.com/Dynatrace/dynatrace-configuration-as-code/releases/tag/v2.0.0 + +# Prerequisities +Export token as environment variable `devToken`. + +Token must have follwoing permissions: +``` +settings.read,settings.write,DataExport,ReadConfig,WriteConfig +``` +Example command for creating token: +``` +curl -X POST "https:///api/v2/apiTokens" -H "accept: application/json; charset=utf-8" -H "Content-Type: application/json; charset=utf-8" -d "{\"name\":\"monaco2\",\"scopes\":[\"settings.read\",\"settings.write\",\"DataExport\",\"ReadConfig\",\"WriteConfig\"]}" -H "Authorization: Api-Token XXXXXXXX" +``` + +# Deploy configuration +``` +monaco deploy manifest.yaml -v -e dev +``` + +# Optional: download configuration +``` +monaco download --manifest manifest.yaml --environment qna -s builtin:bizevents.http.incoming +``` diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/config.yaml b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/config.yaml new file mode 100644 index 0000000..22ba3b7 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/config.yaml @@ -0,0 +1,177 @@ +configs: +- id: e20ddb34-840c-3561-b2b3-a158f60c3315 + config: + name: easytrade_trades-createtrade + template: easytrade_trades-createtrade.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkMmU5NDE3NTAtMGZjNy0zNmZhLTg4ZDQtZTUxMmQ4MzM2OTIzvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: d921730f-21a6-3094-8687-8c7a4fa6f8b4 + config: + name: easytrade_instruments-getinstruments + template: easytrade_instruments-getinstruments.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkZTg4Zjk5OTAtMzNjMC0zMGMyLWI0MjItNDdjZGM5MTY5NDU3vu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 04263a50-d87a-3b47-a231-df3dd286bf18 + config: + name: easytrade_balancehistory-addbalancehistory + template: easytrade_balancehistory-addbalancehistory.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkNmYwMTc4YTYtYzU2Ny0zOTMwLWI3MjEtZjcyYzY2MzEzNzRlvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 8e418b1e-71cf-31b0-be4d-facf5f647039 + config: + name: easytrade_accounts_getaccountbyid + template: easytrade_accounts-getaccountbyid.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkYjUzOWI3NWUtODI3NC0zOThhLTk4MTItYTAxNWZiYmQxMTAxvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 7eb4de5f-3da1-3f96-8abc-dc962ff6d246 + config: + name: easytrade_ownedinstruments-getownedinstrumentsforaccount + template: easytrade_ownedinstruments-getownedinstrumentsforaccount.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkNWNlNDk0NjItOWFjZS0zYTNiLTkxNjUtNGZkOTUzNzVlNjg5vu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 46fe60af-e9db-3305-8436-f01b39ef1c96 + config: + name: easytrade_accounts-getaccountbyusername + template: easytrade_accounts-getaccountbyusername.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkMzdkMzFiNDgtNTZiYy0zMDY3LTg0MTAtOThlZjllNjAyODk3vu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 09491521-6201-39d2-a536-293473a4ac89 + config: + name: easytrade_accounts-modifyaccount + template: easytrade_accounts-modifyaccount.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkYWY5NjI2YWYtYWIyYi0zZmY1LWFkMjktYWFkZGE4YjFhZDQ3vu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: e5f2dbf9-0ed6-37f4-b6fa-bcfc2bf6eae4 + config: + name: easytrade_ownedinstruments-addownedinstruments + template: easytrade_ownedinstruments-addownedinstruments.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkYTE2NThiZWQtNTM2MS0zMzkxLWE2MmQtYTI5N2U4ZmM4OWQyvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: a1203af3-5867-3da3-afd9-ce6c45dadbd6 + config: + name: easytrade_creditcard-withdrawmoney + template: easytrade_creditcard-withdrawmoney.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkY2VkNDFhOTctMjE5MC0zMGVjLWJhYjAtYjZjMWJhMTkzZjc4vu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 15623e4c-195a-30c4-9d13-0c240fd783f4 + config: + name: easytrade_creditcard-depositmoney + template: easytrade_creditcard-depositmoney.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkZDIwZjA4MDctNWRlOC0zNWZiLTg3ZGEtOWI2OTYyNmMzYzc1vu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 611d9894-bbfa-3994-b2d2-5eb49c52b271 + config: + name: easytrade_trade-buyassets + template: easytrade_trade-buyassets.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkMGFiOTEzNzYtMmUwNS0zY2Q1LWEyNTAtNjRjMmY3ZGQ3MGMyvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 97657e11-f7dc-3406-8b96-9b8896b64004 + config: + name: eks_catch_all + template: eks_catch_all.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAApIT1NUX0dST1VQABBCQTQ1MkYxQUMyNjEyNUUyACRmMTczOTJmNS0zZWZiLTMxNTgtODg1ZC00N2IzYjI4Yjg2Mji-71TeFdrerQ + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: HOST_GROUP-BA452F1AC26125E2 +- id: 4900780a-f944-33ff-b74b-a5ae56f847fb + config: + name: easytrade_broker-instruments + template: easytrade_broker-instruments.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkOTU4ZjA5ZTctMjdjOS0zMWU3LThkMmEtMjhhMjcyNjA4YmRlvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: caf3844c-e595-3423-a035-f4768245446b + config: + name: easytrade_ownedinstruments_modifyownedinstruments + template: easytrade_ownedinstruments-modifyownedinstruments.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkNDg2ZWM5MzYtYTM5NS0zMDk2LTg4ZjQtMmJhN2E1NWQ5M2Nkvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: df130abb-4ab0-31f3-a780-cbd1ede62c23 + config: + name: easytrade_pricing-getlatestprices + template: easytrade_pricing-getlatestprices.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkODNlMjZmOTItNGI0ZC0zMTZhLWEwOTEtZjYyYTQwZTQ3ZDY5vu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment +- id: 5d832f9c-e869-3406-b0a5-80b03d23e8eb + config: + name: easytrade_trade-sellassets + template: easytrade_trade-sellassets.json + skip: false + originObjectId: vu9U3hXa3q0AAAABAB9idWlsdGluOmJpemV2ZW50cy5odHRwLmluY29taW5nAAZ0ZW5hbnQABnRlbmFudAAkMTE3YWUyMTMtNDAyYy0zY2M0LTk1MzYtN2FhMWM0MzI4ODRkvu9U3hXa3q0 + type: + settings: + schema: builtin:bizevents.http.incoming + schemaVersion: 1.0.1 + scope: environment diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-getaccountbyid.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-getaccountbyid.json new file mode 100644 index 0000000..c1c5749 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-getaccountbyid.json @@ -0,0 +1,78 @@ +{ + "enabled": true, + "ruleName": ".net] easyTrade - /api/Accounts/GetAccountById/{accountId}", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Accounts/GetAccountById", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.get-account-by-id" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "lastName", + "source": { + "sourceType": "response.body", + "path": "lastName" + } + }, + { + "name": "email", + "source": { + "sourceType": "response.body", + "path": "email" + } + }, + { + "name": "hashedPassword", + "source": { + "sourceType": "response.body", + "path": "hashedPassword" + } + }, + { + "name": "username", + "source": { + "sourceType": "response.body", + "path": "username" + } + }, + { + "name": "id", + "source": { + "sourceType": "response.body", + "path": "id" + } + }, + { + "name": "firstName", + "source": { + "sourceType": "response.body", + "path": "firstName" + } + }, + { + "name": "availableBalance", + "source": { + "sourceType": "response.body", + "path": "availableBalance" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-getaccountbyusername.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-getaccountbyusername.json new file mode 100644 index 0000000..ae1903f --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-getaccountbyusername.json @@ -0,0 +1,78 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Accounts/GetAccountByUsername/{username}", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Accounts/GetAccountByUsername", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.get-account-by-username" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "lastName", + "source": { + "sourceType": "response.body", + "path": "lastName" + } + }, + { + "name": "email", + "source": { + "sourceType": "response.body", + "path": "email" + } + }, + { + "name": "hashedPassword", + "source": { + "sourceType": "response.body", + "path": "hashedPassword" + } + }, + { + "name": "username", + "source": { + "sourceType": "response.body", + "path": "username" + } + }, + { + "name": "id", + "source": { + "sourceType": "response.body", + "path": "id" + } + }, + { + "name": "firstName", + "source": { + "sourceType": "response.body", + "path": "firstName" + } + }, + { + "name": "availableBalance", + "source": { + "sourceType": "response.body", + "path": "availableBalance" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-modifyaccount.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-modifyaccount.json new file mode 100644 index 0000000..c0a712a --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_accounts-modifyaccount.json @@ -0,0 +1,71 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Accounts/ModifyAccount", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Accounts/ModifyAccount", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.modify-account" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "firstName", + "source": { + "sourceType": "request.body", + "path": "firstName" + } + }, + { + "name": "id", + "source": { + "sourceType": "request.body", + "path": "id" + } + }, + { + "name": "availableBalance", + "source": { + "sourceType": "request.body", + "path": "availableBalance" + } + }, + { + "name": "username", + "source": { + "sourceType": "request.body", + "path": "username" + } + }, + { + "name": "email", + "source": { + "sourceType": "request.body", + "path": "email" + } + }, + { + "name": "lastName", + "source": { + "sourceType": "request.body", + "path": "lastName" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_balancehistory-addbalancehistory.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_balancehistory-addbalancehistory.json new file mode 100644 index 0000000..6311f92 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_balancehistory-addbalancehistory.json @@ -0,0 +1,71 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Balancehistory/AddBalancehistory", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Balancehistory/AddBalancehistory", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.add-balance-history" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "accountId", + "source": { + "sourceType": "response.body", + "path": "accountId" + } + }, + { + "name": "actionDate", + "source": { + "sourceType": "response.body", + "path": "actionDate" + } + }, + { + "name": "valueChange", + "source": { + "sourceType": "response.body", + "path": "valueChange" + } + }, + { + "name": "actionType", + "source": { + "sourceType": "response.body", + "path": "actionType" + } + }, + { + "name": "oldValue", + "source": { + "sourceType": "response.body", + "path": "oldValue" + } + }, + { + "name": "id", + "source": { + "sourceType": "response.body", + "path": "id" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_broker-instruments.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_broker-instruments.json new file mode 100644 index 0000000..3c0f1e2 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_broker-instruments.json @@ -0,0 +1,51 @@ +{ + "enabled": true, + "ruleName": "[ws] easyTrade - /broker/api/instruments (array)", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "CONTAINS", + "value": "broker/api/instruments", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.instruments" + }, + "category": { + "sourceType": "constant.string", + "source": "" + }, + "data": [ + { + "name": "firstIndex", + "source": { + "sourceType": "response.body", + "path": "0" + } + }, + { + "name": "rsbody", + "source": { + "sourceType": "response.body", + "path": "*" + } + }, + { + "name": "firstIndexId", + "source": { + "sourceType": "response.body", + "path": "0.id" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_creditcard-depositmoney.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_creditcard-depositmoney.json new file mode 100644 index 0000000..21401f7 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_creditcard-depositmoney.json @@ -0,0 +1,85 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/creditcard/DepositMoney", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "CONTAINS", + "value": "/creditcard/DepositMoney", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.deposit-money" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "address", + "source": { + "sourceType": "request.body", + "path": "address" + } + }, + { + "name": "name", + "source": { + "sourceType": "request.body", + "path": "name" + } + }, + { + "name": "cardType", + "source": { + "sourceType": "request.body", + "path": "cardType" + } + }, + { + "name": "cvv", + "source": { + "sourceType": "request.body", + "path": "cvv" + } + }, + { + "name": "accountId", + "source": { + "sourceType": "request.body", + "path": "accountId" + } + }, + { + "name": "cardNumber", + "source": { + "sourceType": "request.body", + "path": "cardNumber" + } + }, + { + "name": "amount", + "source": { + "sourceType": "request.body", + "path": "amount" + } + }, + { + "name": "email", + "source": { + "sourceType": "request.body", + "path": "email" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_creditcard-withdrawmoney.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_creditcard-withdrawmoney.json new file mode 100644 index 0000000..c0fb8fb --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_creditcard-withdrawmoney.json @@ -0,0 +1,78 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/creditcard/WithdrawMoney", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/creditcard/WithdrawMoney", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.withdraw-money" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "address", + "source": { + "sourceType": "request.body", + "path": "address" + } + }, + { + "name": "name", + "source": { + "sourceType": "request.body", + "path": "name" + } + }, + { + "name": "cardType", + "source": { + "sourceType": "request.body", + "path": "cardType" + } + }, + { + "name": "accountId", + "source": { + "sourceType": "request.body", + "path": "accountId" + } + }, + { + "name": "cardNumber", + "source": { + "sourceType": "request.body", + "path": "cardNumber" + } + }, + { + "name": "amount", + "source": { + "sourceType": "request.body", + "path": "amount" + } + }, + { + "name": "email", + "source": { + "sourceType": "request.body", + "path": "email" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_instruments-getinstruments.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_instruments-getinstruments.json new file mode 100644 index 0000000..673da5a --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_instruments-getinstruments.json @@ -0,0 +1,37 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Instruments/GetInstruments (array)", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Instruments/GetInstruments", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.get-instruments" + }, + "category": { + "sourceType": "constant.string", + "source": "" + }, + "data": [ + { + "name": "rsbody", + "source": { + "sourceType": "response.body", + "path": "*" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-addownedinstruments.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-addownedinstruments.json new file mode 100644 index 0000000..ae701f1 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-addownedinstruments.json @@ -0,0 +1,64 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Ownedinstruments/AddOwnedinstruments", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Ownedinstruments/AddOwnedinstruments", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.add-owned-instruments" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "accountId", + "source": { + "sourceType": "response.body", + "path": "accountId" + } + }, + { + "name": "lastModificationDate", + "source": { + "sourceType": "response.body", + "path": "lastModificationDate" + } + }, + { + "name": "instrumentId", + "source": { + "sourceType": "response.body", + "path": "instrumentId" + } + }, + { + "name": "id", + "source": { + "sourceType": "response.body", + "path": "id" + } + }, + { + "name": "quantity", + "source": { + "sourceType": "response.body", + "path": "quantity" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-getownedinstrumentsforaccount.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-getownedinstrumentsforaccount.json new file mode 100644 index 0000000..85dda69 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-getownedinstrumentsforaccount.json @@ -0,0 +1,28 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Ownedinstruments/GetOwnedinstrumentsForAccount/{accountId} (array)", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "CONTAINS", + "value": "/api/Ownedinstruments/GetOwnedinstrumentsForAccount", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.owned-instruments" + }, + "category": { + "sourceType": "request.path" + }, + "data": [] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-modifyownedinstruments.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-modifyownedinstruments.json new file mode 100644 index 0000000..15f696c --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_ownedinstruments-modifyownedinstruments.json @@ -0,0 +1,57 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Ownedinstruments/ModifyOwnedinstruments", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Ownedinstruments/ModifyOwnedinstruments", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.modify-owned-instruments" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "instrumentId", + "source": { + "sourceType": "request.body", + "path": "instrumentId" + } + }, + { + "name": "quantity", + "source": { + "sourceType": "request.body", + "path": "quantity" + } + }, + { + "name": "lastModificationDate", + "source": { + "sourceType": "request.body", + "path": "lastModificationDate" + } + }, + { + "name": "accountId", + "source": { + "sourceType": "request.body", + "path": "accountId" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_pricing-getlatestprices.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_pricing-getlatestprices.json new file mode 100644 index 0000000..d1bc34b --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_pricing-getlatestprices.json @@ -0,0 +1,42 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Pricing/GetLatestPrices (array)", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Pricing/GetLatestPrices", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.get-latest-prices-2" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "responseCode", + "source": { + "sourceType": "response.statusCode" + } + }, + { + "name": "rsbody", + "source": { + "sourceType": "response.body", + "path": "*" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trade-buyassets.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trade-buyassets.json new file mode 100644 index 0000000..cdec2bd --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trade-buyassets.json @@ -0,0 +1,71 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/trade/BuyAssets", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/trade/BuyAssets", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.buy-assets" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "rsbody", + "source": { + "sourceType": "response.body", + "path": "*" + } + }, + { + "name": "instrumentId", + "source": { + "sourceType": "request.body", + "path": "instrumentId" + } + }, + { + "name": "rqbody", + "source": { + "sourceType": "request.body", + "path": "*" + } + }, + { + "name": "accountId", + "source": { + "sourceType": "request.body", + "path": "accountId" + } + }, + { + "name": "price", + "source": { + "sourceType": "request.body", + "path": "price" + } + }, + { + "name": "amount", + "source": { + "sourceType": "request.body", + "path": "amount" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trade-sellassets.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trade-sellassets.json new file mode 100644 index 0000000..722da72 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trade-sellassets.json @@ -0,0 +1,71 @@ +{ + "enabled": true, + "ruleName": "[ws] easyTrade - /broker/api/trade/SellAssets", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/broker/api/trade/SellAssets", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.sell-assets" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "rsbody", + "source": { + "sourceType": "response.body", + "path": "*" + } + }, + { + "name": "instrumentId", + "source": { + "sourceType": "request.body", + "path": "instrumentId" + } + }, + { + "name": "rqbody", + "source": { + "sourceType": "request.body", + "path": "*" + } + }, + { + "name": "price", + "source": { + "sourceType": "request.body", + "path": "price" + } + }, + { + "name": "accountId", + "source": { + "sourceType": "request.body", + "path": "accountId" + } + }, + { + "name": "amount", + "source": { + "sourceType": "request.body", + "path": "amount" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trades-createtrade.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trades-createtrade.json new file mode 100644 index 0000000..462f424 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/easytrade_trades-createtrade.json @@ -0,0 +1,99 @@ +{ + "enabled": true, + "ruleName": "[.net] easyTrade - /api/Trades/CreateTrade", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "STARTS_WITH", + "value": "/api/Trades/CreateTrade", + "caseSensitive": true + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "www.easytrade.com" + }, + "type": { + "sourceType": "constant.string", + "source": "com.easytrade.create-trade" + }, + "category": { + "sourceType": "request.path" + }, + "data": [ + { + "name": "accountId", + "source": { + "sourceType": "response.body", + "path": "accountId" + } + }, + { + "name": "tradeClosed", + "source": { + "sourceType": "response.body", + "path": "tradeClosed" + } + }, + { + "name": "timestampOpen", + "source": { + "sourceType": "response.body", + "path": "timestampOpen" + } + }, + { + "name": "timestampClose", + "source": { + "sourceType": "response.body", + "path": "timestampClose" + } + }, + { + "name": "id", + "source": { + "sourceType": "response.body", + "path": "id" + } + }, + { + "name": "transactionHappened", + "source": { + "sourceType": "response.body", + "path": "transactionHappened" + } + }, + { + "name": "entryPrice", + "source": { + "sourceType": "response.body", + "path": "entryPrice" + } + }, + { + "name": "direction", + "source": { + "sourceType": "response.body", + "path": "direction" + } + }, + { + "name": "instrumentId", + "source": { + "sourceType": "response.body", + "path": "instrumentId" + } + }, + { + "name": "quantity", + "source": { + "sourceType": "response.body", + "path": "quantity" + } + } + ] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/eks_catch_all.json b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/eks_catch_all.json new file mode 100644 index 0000000..f318e7d --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/bizevents/builtinbizevents.http.incoming/eks_catch_all.json @@ -0,0 +1,26 @@ +{ + "enabled": false, + "ruleName": "[eks] catch all", + "triggers": [ + { + "source": { + "dataSource": "request.path" + }, + "type": "EXISTS" + } + ], + "event": { + "provider": { + "sourceType": "constant.string", + "source": "test" + }, + "type": { + "sourceType": "constant.string", + "source": "test" + }, + "category": { + "sourceType": "request.path" + }, + "data": [] + } +} \ No newline at end of file diff --git a/.devcontainer/apps/easytrade/monaco/manifest.yaml b/.devcontainer/apps/easytrade/monaco/manifest.yaml new file mode 100644 index 0000000..90f8a53 --- /dev/null +++ b/.devcontainer/apps/easytrade/monaco/manifest.yaml @@ -0,0 +1,14 @@ +manifestVersion: 1.0 +projects: + - name: bizevents + path: bizevents + +environmentGroups: + - name: development + environments: + - name: dev + url: + value: "[fill dev environment url]" + auth: + token: + name: "devToken" \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/adservice.yaml b/.devcontainer/apps/hipstershop/manifests/adservice.yaml new file mode 100644 index 0000000..02fcd1d --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/adservice.yaml @@ -0,0 +1,72 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: adservice +spec: + selector: + matchLabels: + app: adservice + template: + metadata: + labels: + app: adservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: adservice + image: gcr.io/dynatrace-demoability/adservice:jdk11 + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + resources: + requests: + cpu: 50m + memory: 100Mi + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + exec: + command: ["/bin/grpc_health_probe", "-addr=:9555"] + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + exec: + command: ["/bin/grpc_health_probe", "-addr=:9555"] +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: adservice +spec: + type: ClusterIP + selector: + app: adservice + ports: + - name: grpc + port: 9555 + targetPort: 9555 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/cartservice.yaml b/.devcontainer/apps/hipstershop/manifests/cartservice.yaml new file mode 100644 index 0000000..e19cccb --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/cartservice.yaml @@ -0,0 +1,69 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: cartservice +spec: + selector: + matchLabels: + app: cartservice + template: + metadata: + labels: + app: cartservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: cartservice + image: gcr.io/dynatrace-demoability/cartservice:original + ports: + - containerPort: 7070 + env: + - name: REDIS_ADDR + value: "redis-cart:6379" + - name: PORT + value: "7070" + - name: LISTEN_ADDR + value: "0.0.0.0" + resources: + requests: + cpu: 50m + memory: 100Mi + readinessProbe: + initialDelaySeconds: 15 + exec: + command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + exec: + command: ["/bin/grpc_health_probe", "-addr=:7070", "-rpc-timeout=5s"] +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: cartservice +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: grpc + port: 7070 + targetPort: 7070 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/checkoutservice.yaml b/.devcontainer/apps/hipstershop/manifests/checkoutservice.yaml new file mode 100644 index 0000000..0cb08c0 --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/checkoutservice.yaml @@ -0,0 +1,81 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: default + containers: + - name: checkoutservice + image: gcr.io/dynatrace-demoability/checkoutservice:9e830c2 + ports: + - containerPort: 5050 + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5050", "-rpc-timeout=5s", "-connect-timeout=5s"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:5050", "-rpc-timeout=5s", "-connect-timeout=5s"] + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: checkoutservice +spec: + type: ClusterIP + selector: + app: checkoutservice + ports: + - name: grpc + port: 5050 + targetPort: 5050 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/currencyservice.yaml b/.devcontainer/apps/hipstershop/manifests/currencyservice.yaml new file mode 100644 index 0000000..7fef6cb --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/currencyservice.yaml @@ -0,0 +1,69 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: currencyservice +spec: + selector: + matchLabels: + app: currencyservice + template: + metadata: + labels: + app: currencyservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: currencyservice + image: gcr.io/dynatrace-demoability/currencyservice:9e830c2 + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: DISABLE_DEBUGGER + # value: "1" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:7000"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:7000"] + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: currencyservice +spec: + type: ClusterIP + selector: + app: currencyservice + ports: + - name: grpc + port: 7000 + targetPort: 7000 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/emailservice.yaml b/.devcontainer/apps/hipstershop/manifests/emailservice.yaml new file mode 100644 index 0000000..a9b1e8d --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/emailservice.yaml @@ -0,0 +1,68 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: emailservice +spec: + selector: + matchLabels: + app: emailservice + template: + metadata: + labels: + app: emailservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: emailservice + image: gcr.io/dynatrace-demoability/emailservice:9e830c2 + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + # - name: DISABLE_TRACING + # value: "1" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + livenessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: emailservice +spec: + type: ClusterIP + selector: + app: emailservice + ports: + - name: grpc + port: 5000 + targetPort: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/frontend.yaml b/.devcontainer/apps/hipstershop/manifests/frontend.yaml new file mode 100644 index 0000000..a8a64ac --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/frontend.yaml @@ -0,0 +1,117 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + containers: + - name: frontend + image: gcr.io/dynatrace-demoability/frontend:v2.2 + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + - name: ENV_PLATFORM + value: "gcp" + - name: OTEL_JAEGER_SERVICE_ADDR + value: "JAEGER_API_IP_ADDRESS:14268/api/traces" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + # - name: CYMBAL_BRANDING + # value: "true" + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: frontend +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: frontend-external +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/loadgenerator.yaml b/.devcontainer/apps/hipstershop/manifests/loadgenerator.yaml new file mode 100644 index 0000000..8e8130f --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/loadgenerator.yaml @@ -0,0 +1,64 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: loadgenerator +spec: + selector: + matchLabels: + app: loadgenerator + replicas: 1 + template: + metadata: + labels: + app: loadgenerator + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + restartPolicy: Always + initContainers: + - command: + - /bin/sh + - -exc + - | + echo "Init container pinging frontend: ${FRONTEND_ADDR}..." + STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') + if test $STATUSCODE -ne 200; then + echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" + exit 1 + fi + name: frontend-check + image: busybox:latest + env: + - name: FRONTEND_ADDR + value: "frontend:80" + containers: + - name: loadgenerator + image: gcr.io/dynatrace-demoability/loadgenerator:9e830c2 + env: + - name: FRONTEND_ADDR + value: "frontend:80" + - name: USERS + value: "10" + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 500m + memory: 512Mi \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/paymentservice.yaml b/.devcontainer/apps/hipstershop/manifests/paymentservice.yaml new file mode 100644 index 0000000..89a66bc --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/paymentservice.yaml @@ -0,0 +1,103 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: paymentservice +spec: + selector: + matchLabels: + app: paymentservice + template: + metadata: + labels: + app: paymentservice + version: v1 + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: paymentservice + image: gcr.io/dynatrace-demoability/paymentservice:dd747d8 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: paymentservice +spec: + type: ClusterIP + selector: + app: paymentservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 + #--- + #apiVersion: apps/v1 + #kind: Deployment + #metadata: + # name: paymentservice-memoryleak + #spec: + # selector: + # matchLabels: + # app: paymentservice + # template: + # metadata: + # labels: + # app: paymentservice + # version: v2 + # spec: + # serviceAccountName: default + # terminationGracePeriodSeconds: 5 + # containers: + # - name: server + # image: paymentservice + # ports: + # - containerPort: 50051 + # env: + # - name: PORT + # value: "50051" + # - name: MEMORYLEAK + # value: "1" + # readinessProbe: + # exec: + # command: ["/bin/grpc_health_probe", "-addr=:50051"] + # livenessProbe: + # exec: + # command: ["/bin/grpc_health_probe", "-addr=:50051"] + # resources: + # requests: + # cpu: 100m + # memory: 64Mi + # limits: + # cpu: 200m + # memory: 512Mi \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/productcatalogservice.yaml b/.devcontainer/apps/hipstershop/manifests/productcatalogservice.yaml new file mode 100644 index 0000000..47f25e6 --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/productcatalogservice.yaml @@ -0,0 +1,70 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: productcatalogservice +spec: + selector: + matchLabels: + app: productcatalogservice + template: + metadata: + labels: + app: productcatalogservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: productcatalogservice + image: gcr.io/dynatrace-demoability/productcatalogservice:9e830c2 + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + - name: DISABLE_STATS + value: "1" + - name: DISABLE_TRACING + value: "1" + - name: DISABLE_PROFILER + value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + readinessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:3550"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:3550"] + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: productcatalogservice +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: grpc + port: 3550 + targetPort: 3550 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/recommendationservice.yaml b/.devcontainer/apps/hipstershop/manifests/recommendationservice.yaml new file mode 100644 index 0000000..549a23a --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/recommendationservice.yaml @@ -0,0 +1,72 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: recommendationservice +spec: + selector: + matchLabels: + app: recommendationservice + template: + metadata: + labels: + app: recommendationservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + containers: + - name: recommendationservice + image: gcr.io/dynatrace-demoability/recommendationservice:9e830c2 + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + livenessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:8080"] + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: DISABLE_TRACING + value: "1" + - name: DISABLE_PROFILER + value: "1" + - name: DISABLE_DEBUGGER + value: "1" + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/redis.yaml b/.devcontainer/apps/hipstershop/manifests/redis.yaml new file mode 100644 index 0000000..351e160 --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/redis.yaml @@ -0,0 +1,65 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: redis-cart +spec: + selector: + matchLabels: + app: redis-cart + template: + metadata: + labels: + app: redis-cart + spec: + containers: + - name: redis + image: redis:alpine + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + requests: + cpu: 50m + memory: 100Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: redis-cart +spec: + type: ClusterIP + selector: + app: redis-cart + ports: + - name: redis + port: 6379 + targetPort: 6379 \ No newline at end of file diff --git a/.devcontainer/apps/hipstershop/manifests/shippingservice.yaml b/.devcontainer/apps/hipstershop/manifests/shippingservice.yaml new file mode 100644 index 0000000..fa1e34e --- /dev/null +++ b/.devcontainer/apps/hipstershop/manifests/shippingservice.yaml @@ -0,0 +1,70 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: hipstershop + name: shippingservice +spec: + selector: + matchLabels: + app: shippingservice + template: + metadata: + labels: + app: shippingservice + spec: + serviceAccountName: default + containers: + - name: shippingservice + image: gcr.io/dynatrace-demoability/shippingservice:9e830c2 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + # - name: DISABLE_STATS + # value: "1" + # - name: DISABLE_TRACING + # value: "1" + # - name: DISABLE_PROFILER + # value: "1" + # - name: JAEGER_SERVICE_ADDR + # value: "jaeger-collector:14268" + readinessProbe: + periodSeconds: 5 + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + livenessProbe: + exec: + command: ["/bin/grpc_health_probe", "-addr=:50051"] + resources: + requests: + cpu: 50m + memory: 100Mi +--- +apiVersion: v1 +kind: Service +metadata: + namespace: hipstershop + name: shippingservice +spec: + type: ClusterIP + selector: + app: shippingservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 \ No newline at end of file diff --git a/.devcontainer/apps/todo-app/Dockerfile b/.devcontainer/apps/todo-app/Dockerfile new file mode 100755 index 0000000..848921c --- /dev/null +++ b/.devcontainer/apps/todo-app/Dockerfile @@ -0,0 +1,26 @@ +FROM openjdk:8-jdk as build + +RUN mkdir -p /app +WORKDIR /app +ADD build.gradle /app +ADD . /app +RUN ./gradlew -i bootJar + +# ---------------------------------------------------------- # + +FROM openjdk:8-jdk as release + +# Update below. +# environmentURL=mss33078.dev.dynatracelabs.com +# TECHNOLOGY=java +#COPY --from=/linux/oneagent-codemodules:java / / +#ENV LD_PRELOAD /opt/dynatrace/oneagent/agent/lib64/liboneagentproc.so +#ENV DT_LIVEDEBUGGER_LABELS=app:josh +#ENV DT_LIVEDEBUGGER_REMOTE_ORIGIN=ssh://git@bitbucket.lab.dynatrace.org/dobs/tutorial-java-workshop.git +#ENV DT_LIVEDEBUGGER_COMMIT=main + +RUN mkdir -p /app +# Copy the jar image (which already include resoures) +COPY --from=build /app/build/libs/todoapp-1.0.0.jar /app/todoapp-1.0.0.jar + +ENTRYPOINT ["java", "-jar", "/app/todoapp-1.0.0.jar"] diff --git a/.devcontainer/apps/todo-app/Makefile b/.devcontainer/apps/todo-app/Makefile new file mode 100644 index 0000000..cf7e87e --- /dev/null +++ b/.devcontainer/apps/todo-app/Makefile @@ -0,0 +1,40 @@ +PUBLISH_VERSION=$(shell echo ${NEW_VERSION} | sed 's/inner-999/1/g') + +GIT_COMMIT=$(shell git rev-parse HEAD) +GIT_ORIGIN=$(shell git config --get remote.origin.url) + +build-jar-with-docker: + #FIXME:PUBLISH_VERSION is empty + #echo this is the GIT_COMMIT ${GIT_COMMIT} + rm -rf build + # Build the build/libs/tutorial-V.V.V - which already includes the project sources in the jar + docker run --rm -v "$(shell pwd)":/home/gradle/project -w /home/gradle/project gradle:4.10.0-jdk8-alpine gradle -i bootJar + +build-jar-local: + # Build the build/libs/tutorial-V.V.V - which already includes the project sources in the jar + gradle -i bootJar + +run-local: + # Run the app locally + java -jar build/libs/todoapp-1.0.0.jar + +build-img: + #build with no arguments + #docker build --tag shinojosa/todoapp:latest --tag shinojosa/todoapp:${PUBLISH_VERSION} --build-arg GIT_COMMIT=${GIT_COMMIT} --build-arg GIT_ORIGIN=${GIT_ORIGIN} . + docker build --tag shinojosa/todoapp:latest --tag shinojosa/todoapp:1.0.0 . + +build-amd: + docker build --platform linux/amd64 -t shinojosa/todoapp:1.0.0 . + +build-arm: + docker build --platform linux/arm64 -t shinojosa/todoapp:1.0.0-arm . + +upload-no-latest: + docker push shinojosa/tutorial-java:${PUBLISH_VERSION} + +upload: upload-no-latest + @if [ ${CIRCLE_BRANCH} = "master" ]; then \ + docker push rookout/tutorial-java:latest; \ + fi + +build-and-upload: build-img upload diff --git a/.devcontainer/apps/todo-app/README.md b/.devcontainer/apps/todo-app/README.md new file mode 100644 index 0000000..4f2d255 --- /dev/null +++ b/.devcontainer/apps/todo-app/README.md @@ -0,0 +1,13 @@ +# The TODO App + +[ ] Explain how to compile it +[ ] Explain how to run in dev.container +[ ] Build image and push to local Kind registry +[ ] Clean Makefile and leave just basic functionality +[ ] Clean code more +[ ] Add a nested variable deep in Object to showcase Complex envs watching vars. +[ ] Add Tutorial: Data Masking +[ ] Add Tutorial: IDE Integration (sprint/dev) + Patch Kubernetes Yaml UseCase +[ ] Add Tutorial: Integrate with Version Control via Patch in K8s -> POC template +https://docs.dynatrace.com/docs/observe/applications-and-microservices/developer-observability/offering-capabilities/additional-settings#integrate-with-your-version-control \ No newline at end of file diff --git a/.devcontainer/apps/todo-app/build.gradle b/.devcontainer/apps/todo-app/build.gradle new file mode 100644 index 0000000..18ab48d --- /dev/null +++ b/.devcontainer/apps/todo-app/build.gradle @@ -0,0 +1,91 @@ +buildscript { + ext { + springBootVersion = '2.2.10.RELEASE' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } +} + + +plugins { + id 'com.palantir.git-version' version '0.12.3' +} + + +apply plugin: 'application' +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' +apply plugin: 'com.palantir.git-version' + +def details = versionDetails() + +mainClassName = "com.dynatrace.todoapp.TutorialApplication" + +group = 'com.dynatrace' +version = '1.0.0' +sourceCompatibility = 8 + +repositories { + mavenCentral() +} + + +dependencies { + compile('org.springframework.boot:spring-boot-starter-web') + + // Specific dependencies for fixing different vulnerabilities + compile("org.hibernate.validator:hibernate-validator:6.1.3.Final") + compile("org.springframework:spring-webmvc:5.2.9.RELEASE") + compile("org.springframework:spring-web:5.2.9.RELEASE") + compile("org.apache.tomcat.embed:tomcat-embed-core:9.0.40") + compile("ch.qos.logback:logback-classic:1.2.3") + compile("org.slf4j:slf4j-api:1.7.30") + + compile("org.yaml:snakeyaml:1.26") + // we disable tracing + //compile group: 'io.opentracing.contrib', name: 'opentracing-spring-jaeger-web-starter', version: '3.3.1' + + testCompile('org.springframework.boot:spring-boot-starter-test') + testCompile('org.springframework.restdocs:spring-restdocs-mockmvc') +} + +class Download extends DefaultTask { + @Input + String sourceUrl + + @OutputFile + File target + + @TaskAction + void download() { + ant.get(src: sourceUrl, dest: target) + } +} + +/* +task downloadRookout(type: Download) { + sourceUrl = "https://oss.sonatype.org/service/local/repositories/releases/content/com/rookout/rook/0.1.263/rook-0.1.263.jar" + target = new File('rook.jar') +} + +// bootJar is added by spring + java plugin, it creates a fat jar, we add the source files +// https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/html/#reacting-to-other-plugins-java +bootJar { + manifest { + attributes('ROOKOUT_COMMIT': details.gitHashFull) + } + from sourceSets.main.allSource +} + +bootJar.dependsOn downloadRookout + +//run { +// jvmArgs += ["-javaagent:rook.jar"] +//} +*/ \ No newline at end of file diff --git a/.devcontainer/apps/todo-app/gradle/wrapper/gradle-wrapper.jar b/.devcontainer/apps/todo-app/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/.devcontainer/apps/todo-app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/.devcontainer/apps/todo-app/gradle/wrapper/gradle-wrapper.properties b/.devcontainer/apps/todo-app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bf72bd5 --- /dev/null +++ b/.devcontainer/apps/todo-app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 02 18:23:47 WEST 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/.devcontainer/apps/todo-app/gradlew b/.devcontainer/apps/todo-app/gradlew new file mode 100755 index 0000000..3da45c1 --- /dev/null +++ b/.devcontainer/apps/todo-app/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright ? 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions ?$var?, ?${var}?, ?${var:-default}?, ?${var+SET}?, +# ?${var#prefix}?, ?${var%suffix}?, and ?$( cmd )?; +# * compound commands having a testable exit status, especially ?case?; +# * various built-in commands including ?command?, ?set?, and ?ulimit?. +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/.devcontainer/apps/todo-app/gradlew.bat b/.devcontainer/apps/todo-app/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/.devcontainer/apps/todo-app/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/.devcontainer/apps/todo-app/patches/set_version_control.sh b/.devcontainer/apps/todo-app/patches/set_version_control.sh new file mode 100644 index 0000000..346e6da --- /dev/null +++ b/.devcontainer/apps/todo-app/patches/set_version_control.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Small bash script for patching deployments via kubectl, more information about integrating the live debugger with your version control see: +# https://docs.dynatrace.com/docs/observe/applications-and-microservices/developer-observability/offering-capabilities/additional-settings#integrate-with-your-version-control + +# Variable definition +version="v1.0.0" +deployment="todoapp" +container="todoapp" +namespace="todoapp" + +DT_LIVEDEBUGGER_COMMIT="" +DT_LIVEDEBUGGER_REMOTE_ORIGIN="" + + +set_version_control_information(){ + DT_LIVEDEBUGGER_REMOTE_ORIGIN=$(git remote get-url origin) + DT_LIVEDEBUGGER_COMMIT=$(git rev-parse $version) + + echo "Fetching git revision for $version in $DT_LIVEDEBUGGER_REMOTE_ORIGIN" + echo $DT_LIVEDEBUGGER_COMMIT + + export DT_LIVEDEBUGGER_REMOTE_ORIGIN=$DT_LIVEDEBUGGER_REMOTE_ORIGIN + export DT_LIVEDEBUGGER_COMMIT=$DT_LIVEDEBUGGER_COMMIT +} + + +patch_deployment(){ +kubectl patch deployment $deployment -n $namespace -p "$(cat < userIdsPool = new ArrayList(){{ + add(generateUserID()); + add(generateUserID()); + add(generateUserID()); + }}; + private static final ArrayList accountIdsPool = new ArrayList(){{ + add(UUID.randomUUID().toString()); + add(UUID.randomUUID().toString()); + add(UUID.randomUUID().toString()); + }}; + + @Override + public void destroy() { } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException + { + Random rand = new Random(); + MDC.put("requestId", UUID.randomUUID().toString()); + MDC.put("userId", userIdsPool.get(rand.nextInt(userIdsPool.size()))); + MDC.put("accountId", accountIdsPool.get(rand.nextInt(accountIdsPool.size()))); + + MDC.put("remoteHost", servletRequest.getRemoteHost()); + MDC.put("localAddress", servletRequest.getLocalAddr()); + MDC.put("name", servletRequest.getLocalName()); + MDC.put("port", String.valueOf(servletRequest.getLocalPort())); + MDC.put("encoding", servletRequest.getCharacterEncoding()); + + filterChain.doFilter(servletRequest, servletResponse); + } + + private static String generateUserID() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 8); + } +} diff --git a/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoController.java b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoController.java new file mode 100644 index 0000000..8409f61 --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoController.java @@ -0,0 +1,111 @@ +package com.dynatrace.todoapp; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.*; + +@RestController +public class TodoController { + private static final Logger logger = LoggerFactory.getLogger(TodoController.class); + private TodoStorage todos = TodoStorage.getInstance(); + // Disable tracing + //private final TracingHandler tracingHandler = new TracingHandler(); + + @RequestMapping(value = "/todos", method = RequestMethod.GET) + public TodoRecord[] getTodos() { + return todos.getAll(); + } + + @RequestMapping(value = "/todos", method = RequestMethod.POST) + public ResponseEntity addTodo(@RequestBody TodoRecord newTodoRecord) { + newTodoRecord.setId(UUID.randomUUID().toString()); + logger.info("Adding a new todo: {}", newTodoRecord); + // The bug in here in is for the bughunt example + String todoTitle = newTodoRecord.getTitle().replaceAll("[^a-zA-Z0-9\\s]+", ""); + newTodoRecord.setTitle(todoTitle); + todos.add(newTodoRecord); + Map entities = new HashMap<>(); + entities.put("status", "ok"); + return new ResponseEntity<>(entities, HttpStatus.OK); + } + + @RequestMapping(value = "/todos", method = RequestMethod.PUT) + public ResponseEntity updateTodo(@RequestBody TodoRecord updatingTodoRecord) { + TodoRecord tempTodoRecord = todos.findById(updatingTodoRecord.getId()); + if (tempTodoRecord != null) { + tempTodoRecord.setTitle(updatingTodoRecord.getTitle()); + tempTodoRecord.setCompleted(updatingTodoRecord.isCompleted()); + logger.info("Updating Todo record: {}", tempTodoRecord); + } + Map entities = new HashMap<>(); + entities.put("status", "ok"); + return new ResponseEntity<>(entities, HttpStatus.OK); + } + + @RequestMapping(value = "/todos/remove_all", method = RequestMethod.DELETE) + public ResponseEntitydeleteAll() { + logger.info("Removing all items"); + todos.remove_all(); + Map entities = new HashMap<>(); + entities.put("status", "ok"); + return new ResponseEntity<>(entities, HttpStatus.OK); + } + + @RequestMapping(value = "/todos/{todoId}", method = RequestMethod.DELETE) + public ResponseEntity deleteTodo(@PathVariable("todoId") String todoId) { + logger.info("Removing Todo record id: {}", todoId); + TodoRecord tempTodoRecord = todos.findById(todoId); + if (tempTodoRecord != null) { + logger.info("Removing Todo record: {}", tempTodoRecord); + todos.remove(tempTodoRecord); + } + Map entities = new HashMap<>(); + entities.put("status", "ok"); + return new ResponseEntity<>(entities, HttpStatus.OK); + } + + + @RequestMapping(value = "/todos/clear_completed", method = RequestMethod.DELETE) + public ResponseEntity clearCompletedTodos() throws InterruptedException { + //tracingHandler.createChildSpansActivity(); + logger.info("Removing completed todo records"); + logger.debug("reading todoStore from database"); + logger.debug("SELECT * FROM todos WHERE status='conpleted'"); + // The bug in here in is for the bughunt example + List todoStore = new ArrayList<>(); + logger.debug("todoStore size is {}", todoStore.size()); + for (TodoRecord todoRecord : todos.getAll()) { + if (todoRecord.isCompleted()) { + // The bug in here in is for the bughunt example + if (todoStore.remove(todoRecord)) { + logger.info("Removing Todo record: {}", todoRecord); + } + } + } + logger.error("failed to delete completed todos"); + Map entities = new HashMap<>(); + entities.put("status", "ok"); + return new ResponseEntity<>(entities, HttpStatus.OK); + } + + @RequestMapping(value = "/todos/dup/{todoId}", method = RequestMethod.POST) + public ResponseEntity duplicateTodo(@PathVariable("todoId") String todoId) { + logger.info("Duplicating todo: {}", todoId); + TodoRecord tempTodoRecord = todos.findById(todoId); + if (tempTodoRecord != null) { + TodoRecord newTodoRecord = new TodoRecord(tempTodoRecord); + // The bug in here in is for the bughunt example + newTodoRecord.setId(tempTodoRecord.getTitle()); + newTodoRecord.setTitle(UUID.randomUUID().toString()); + logger.info("Duplicating todo record: {}", newTodoRecord); + todos.add(newTodoRecord); + } + Map entities = new HashMap<>(); + entities.put("status", "ok"); + return new ResponseEntity<>(entities, HttpStatus.OK); + } +} diff --git a/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoRecord.java b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoRecord.java new file mode 100644 index 0000000..54c36cc --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoRecord.java @@ -0,0 +1,56 @@ +package com.dynatrace.todoapp; + +public class TodoRecord { + private String title; + private String id; + private boolean completed = false; + + public TodoRecord(String inputTitle, String inputId, boolean inputCompleted) { + this.title = inputTitle; + this.id = inputId; + this.completed = inputCompleted; + } + + public TodoRecord() { + + } + + public TodoRecord(TodoRecord newTodoRecord) { + this.title = newTodoRecord.title; + this.id = newTodoRecord.id; + this.completed = newTodoRecord.completed; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String toString() { + return "TodoRecord{" + + "title='" + title + '\'' + + ", id='" + id + '\'' + + ", completed=" + completed + + '}'; + } + + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } +} diff --git a/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoStorage.java b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoStorage.java new file mode 100644 index 0000000..b3de9ec --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TodoStorage.java @@ -0,0 +1,45 @@ +package com.dynatrace.todoapp; + +import java.util.ArrayList; + +/** + * Tdo Storage (in memory list that implements Interface TodoRecord Storage). + */ +public class TodoStorage implements ITodoStorage { + private ArrayList todoStorage; + + private static TodoStorage instance = new TodoStorage(); + + public static TodoStorage getInstance() { + return instance; + } + + private TodoStorage() { + todoStorage = new ArrayList<>(); + } + + public void add(TodoRecord todoRecordObject) { + todoStorage.add(todoRecordObject); + } + + public boolean remove(TodoRecord todoRecord) { + return todoStorage.remove(todoRecord); + } + + public boolean remove_all() { + return todoStorage.removeAll(todoStorage); + } + + public TodoRecord findById(String todoRecordId) { + for (TodoRecord record : todoStorage) { + if (record.getId().equals(todoRecordId)) { + return record; + } + } + return null; + } + + public TodoRecord[] getAll() { + return todoStorage.toArray(new TodoRecord[0]); + } +} diff --git a/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TracingHandler.java b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TracingHandler.java new file mode 100644 index 0000000..0c23ad5 --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TracingHandler.java @@ -0,0 +1,37 @@ +package com.dynatrace.todoapp; + +/* + * Disable tracing atm + */ +// import io.opentracing.Span; +// import io.opentracing.Tracer; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadLocalRandom; + +public class TracingHandler { + // Tracer tracer; + + // public TracingHandler() { + // tracer = io.opentracing.util.GlobalTracer.get(); + // } + + // public void createChildSpansActivity() throws InterruptedException { + // this.openAndCloseChildSpan("getCompletedTasks"); + // this.openAndCloseChildSpan("deleteCompletedTasks"); + // this.openAndCloseChildSpan("updateView"); + // } + + // public void openAndCloseChildSpan(String operationName) throws InterruptedException { + // Span parentSpan = tracer.scopeManager().activeSpan(); + // Span newChildSpan = tracer.buildSpan(operationName).asChildOf(parentSpan).start(); + // tracer.scopeManager().activate(newChildSpan); + + // int randomNum = ThreadLocalRandom.current().nextInt(5, 20); + // TimeUnit.MILLISECONDS.sleep(randomNum); + + // tracer.scopeManager().activate(parentSpan); + // newChildSpan.finish(); + // } + +} diff --git a/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TutorialApplication.java b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TutorialApplication.java new file mode 100644 index 0000000..1a75080 --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/java/com/dynatrace/todoapp/TutorialApplication.java @@ -0,0 +1,27 @@ +package com.dynatrace.todoapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/* + * @SpringBootApplication is a convenience annotation that adds all of the following: + * + * @Configuration tags the class as a source of bean definitions for the application context. + * + * @EnableAutoConfiguration tells Spring Boot to start adding beans based on classpath settings, other beans, + * and various property settings. + * + * Normally you would add @EnableWebMvc for a Spring MVC app, but Spring Boot adds it automatically when it sees + * spring-webmvc on the classpath. This flags the application as a web application and activates key behaviors such + * as setting up a DispatcherServlet. + * + * @ComponentScan tells Spring to look for other components, configurations, and services in the hello package, + * allowing it to find the controllers. + * */ +@SpringBootApplication +public class TutorialApplication { + + public static void main(String[] args) { + SpringApplication.run(TutorialApplication.class, args); + } +} diff --git a/.devcontainer/apps/todo-app/src/main/resources/application.properties b/.devcontainer/apps/todo-app/src/main/resources/application.properties new file mode 100644 index 0000000..2c6b94d --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/resources/application.properties @@ -0,0 +1,4 @@ +spring.application.name=tutorial-java +# Disable tracing +#opentracing.jaeger.udp-sender.host=jaeger-agent +#opentracing.jaeger.udp-sender.port=6831 \ No newline at end of file diff --git a/.devcontainer/apps/todo-app/src/main/resources/static/css/style.css b/.devcontainer/apps/todo-app/src/main/resources/static/css/style.css new file mode 100755 index 0000000..e319cc4 --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/resources/static/css/style.css @@ -0,0 +1,409 @@ +html, +body { + margin: 0; + padding: 0; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; + -webkit-appearance: none; + appearance: none; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #4d4d4d; + min-width: 230px; + max-width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + font-smoothing: antialiased; + font-weight: 300; +} + +button, +input[type="checkbox"] { + outline: none; +} + +.hidden { + display: none; +} + +.todoapp { + background: #fff; + margin: 130px 0 40px 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.1); +} + +.todoapp input::-webkit-input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp input::-moz-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp input::input-placeholder { + font-style: italic; + font-weight: 300; + color: #e6e6e6; +} + +.todoapp h1 { + position: absolute; + top: -155px; + width: 100%; + font-size: 100px; + font-weight: 100; + text-align: center; + color: rgba(153,98,255, 0.9); + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +.new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + font-weight: inherit; + line-height: 1.4em; + border: 0; + outline: none; + color: inherit; + padding: 6px; + border: 1px solid rgba(153,98,255, 0.4); + box-shadow: inset 0 -1px 5px 0 rgba(153,98,255, 0.3); + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +.new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); +} + +.main { + position: relative; + z-index: 2; + border-top: 1px solid #e6e6e6; +} + +label[for='toggle-all'] { + display: none; +} + +.toggle-all { + position: absolute; + top: -55px; + left: -12px; + width: 60px; + height: 34px; + text-align: center; + border: none; /* Mobile Safari */ +} + +.toggle-all:before { + content: '❯'; + font-size: 22px; + color: #9962FF; + padding: 10px 27px 10px 27px; +} + +.toggle-all:checked:before { + color: #737373; +} + +.todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +.todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px solid #ededed; +} + +.todo-list li:last-child { + border-bottom: none; +} + +.todo-list li.editing { + border-bottom: none; + padding: 0; +} + +.todo-list li.editing .edit { + display: block; + width: 506px; + padding: 13px 17px 12px 17px; + margin: 0 0 0 43px; +} + +.todo-list li.editing .view { + display: none; +} + +.todo-list li .toggle { + text-align: center; + width: 40px; + /* auto, since non-WebKit browsers doesn't support input styling */ + height: auto; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; /* Mobile Safari */ + -webkit-appearance: none; + appearance: none; +} + +.todo-list li .toggle:after { + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='-10 -18 100 135'%3E%3Ccircle cx='50' cy='50' r='50' fill='none' stroke='%23ededed' stroke-width='3'/%3E%3C/svg%3E"); +} + +.todo-list li .toggle:checked:after { + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='-10 -18 100 135'%3E%3Ccircle cx='50' cy='50' r='50' fill='none' stroke='%239962FF' stroke-width='3'/%3E%3Cpath fill='%239962FF' d='M72 25L42 71 27 56l-4 4 20 20 34-52z'/%3E%3C/svg%3E"); +} + +.todo-list li label { + white-space: pre-line; + word-break: break-all; + padding: 15px 60px 15px 15px; + margin-left: 45px; + display: block; + line-height: 1.2; + transition: color 0.4s; +} + +.todo-list li.completed label { + color: #d9d9d9; + text-decoration: line-through; +} + +.todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: #cc9a9a; + margin-bottom: 11px; + transition: color 0.2s ease-out; +} + +.todo-list li .destroy:hover { + color: #af5b5e; +} + +.todo-list li .destroy:after { + content: '×'; +} + +.todo-list li:hover .destroy { + display: block; +} + +.todo-list li .duplicate { + display: none; + position: absolute; + top: 0; + right: 40px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: rgba(153,98,255,0.8); + margin-bottom: 11px; + transition: color 0.2s ease-out; +} + +.todo-list li .duplicate:hover { + color: #533C92; +} + +.todo-list li .duplicate:after { + content: '&'; +} + +.todo-list li:hover .duplicate { + display: block; +} + +.todo-list li .edit { + display: none; +} + +.todo-list li.editing:last-child { + margin-bottom: -1px; +} + +.footer { + color: #777; + padding: 10px 15px; + height: 20px; + text-align: center; + border-top: 1px solid #e6e6e6; +} + +.footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50px; + overflow: hidden; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), + 0 8px 0 -3px #f6f6f6, + 0 9px 1px -3px rgba(0, 0, 0, 0.2), + 0 16px 0 -6px #f6f6f6, + 0 17px 2px -6px rgba(0, 0, 0, 0.2); +} + +.todo-count { + float: left; + text-align: left; +} + +.todo-count strong { + font-weight: 300; +} + +.filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +.filters li { + display: inline; +} + +.filters li a { + color: inherit; + margin: 3px; + padding: 3px 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; +} + +.filters li a.selected, +.filters li a:hover { + border-color: rgba(153, 98, 255, 0.3); +} + +.filters li a.selected { + border-color: rgba(153, 98, 255, 0.5); +} + +.clear-completed, +html .clear-completed:active { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + cursor: pointer; + position: relative; +} + +.clear-completed:hover { + text-decoration: underline; + text-decoration-color: #9962FF; +} + +.info { + margin: 65px auto 0; + color: #9962FF; + font-size: 10px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-align: center; +} + +.info p { + line-height: 1; +} + +.info a { + color: inherit; + text-decoration: none; + font-weight: 400; +} + +.info a:hover { + text-decoration: underline; +} +.clear-all-items { + cursor: pointer; +} + +/* + Hack to remove background from Mobile Safari. + Can't use it globally since it destroys checkboxes in Firefox +*/ +@media screen and (-webkit-min-device-pixel-ratio:0) { + .toggle-all, + .todo-list li .toggle { + background: none; + } + + .todo-list li .toggle { + height: 40px; + } + + .toggle-all { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-appearance: none; + appearance: none; + } +} + +@media (max-width: 430px) { + .footer { + height: 50px; + } + + .filters { + bottom: 10px; + } +} \ No newline at end of file diff --git a/.devcontainer/apps/todo-app/src/main/resources/static/index.html b/.devcontainer/apps/todo-app/src/main/resources/static/index.html new file mode 100755 index 0000000..f0d7260 --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/resources/static/index.html @@ -0,0 +1,52 @@ + + + + + 📋 Todo App 🐞 + + + + + + + +
+
+
+

todos

+ +
+
+ + +
    + +
+
+
+ {{filteredTodos.length}} Tasks + + +
+
+
+

Double-click to edit a todo

+

Click here to remove all items

+ +
+ +
+ + + \ No newline at end of file diff --git a/.devcontainer/apps/todo-app/src/main/resources/static/js/app.js b/.devcontainer/apps/todo-app/src/main/resources/static/js/app.js new file mode 100755 index 0000000..4410d6f --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/resources/static/js/app.js @@ -0,0 +1,127 @@ +$().ready(() => { + Vue.component('todo-item', { + props: ['todo'], + data: function () { + return { + editing: false + } + }, + methods: { + saveTodoEdit(e) { + if (e.keyCode != 13) return true; + this.editing = false; + this.$emit('update-todo', this.todo); + }, + onTodoToggle(e) { + this.editing = false; + this.$emit('update-todo', this.todo); + } + }, + template: `
  • +
    + + + + +
    + +
  • ` + }); + + var app = new Vue({ + el: "#app", + data: { + todos: [], + newTodoTitle: '', + filterMode: 'all', + todosFilter: (todo) => true + }, + computed: { + filteredTodos() { + return this.todos.filter(this.todosFilter); + } + }, + methods: { + setFilter(filter) { + this.filterMode = filter; + switch (filter) { + case 'all': + this.todosFilter = (todo) => true; + break; + case 'active': + this.todosFilter = (todo) => !todo.completed; + break; + case 'completed': + this.todosFilter = (todo) => todo.completed; + break; + } + }, + removeAll() { + const action = $.ajax('/todos/remove_all', { + method: 'DELETE' + }); + this.reloadOnFinish(action); + }, + clearCompleted(e) { + const action = $.ajax('/todos/clear_completed', { + method: 'DELETE' + }); + this.reloadOnFinish(action); + }, + updateTodo(todo) { + const action = $.ajax('/todos', { + contentType: 'application/json', + method: 'PUT', + data: JSON.stringify(todo), + dataType: 'json', + }); + this.reloadOnFinish(action); + }, + duplicateTodo(todo) { + const action = $.ajax(`/todos/dup/${todo.id}`, { + method: 'POST' + }); + this.reloadOnFinish(action); + }, + removeTodo(todo) { + const action = $.ajax(`/todos/${todo.id}`, { + method: 'DELETE', + }); + this.reloadOnFinish(action); + }, + reloadOnFinish(promise) { + promise.done((data) => { + data ? console.log(`got status: ${data.status}`) : null; + return this.reloadTodos(); + }).catch(console.log); + }, + reloadTodos() { + const vm = this; + $.ajax('/todos', { + method: 'GET' + }).done((todos) => { + vm.todos = todos; + }); + }, + addTodo(text) { + const action = $.ajax('/todos', { + contentType: 'application/json', + method: 'POST', + data: JSON.stringify({"title": text}), + dataType: 'json' + }); + + this.reloadOnFinish(action); + }, + onAddTodoPressed(e) { + if (e.keyCode !== 13) return; + const title = this.newTodoTitle; + this.addTodo(title); + this.newTodoTitle = ''; + } + }, + mounted() { + this.reloadTodos(); + }, + }); +}); \ No newline at end of file diff --git a/.devcontainer/apps/todo-app/src/main/resources/static/js/vendor/jquery-3.3.1.min.js b/.devcontainer/apps/todo-app/src/main/resources/static/js/vendor/jquery-3.3.1.min.js new file mode 100755 index 0000000..49d1fcf --- /dev/null +++ b/.devcontainer/apps/todo-app/src/main/resources/static/js/vendor/jquery-3.3.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w(" + + + + + + + + \ No newline at end of file diff --git a/app/public/_particles/js/app.js b/app/public/_particles/js/app.js new file mode 100644 index 0000000..6b5d73a --- /dev/null +++ b/app/public/_particles/js/app.js @@ -0,0 +1,133 @@ +/* ----------------------------------------------- +/* How to use? : Check the GitHub README +/* ----------------------------------------------- */ + +/* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */ +/* +particlesJS.load('particles-js', 'particles.json', function() { + console.log('particles.js loaded - callback'); +}); +*/ + +/* Otherwise just put the config content (json): */ + +particlesJS('particles-js', + + { + "particles": { + "number": { + "value": 80, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 5, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 6, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": true, + "mode": "repulse" + }, + "onclick": { + "enable": false, + "mode": "push" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200 + }, + "push": { + "particles_nb": 0 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true, + "config_demo": { + "hide_card": true, + "background_color": "", + "background_image": "", + "background_position": "50% 50%", + "background_repeat": "no-repeat", + "background_size": "cover" + } + } + +); \ No newline at end of file diff --git a/app/public/_particles/js/particles.min.js b/app/public/_particles/js/particles.min.js new file mode 100644 index 0000000..b3d46d1 --- /dev/null +++ b/app/public/_particles/js/particles.min.js @@ -0,0 +1,9 @@ +/* ----------------------------------------------- +/* Author : Vincent Garreau - vincentgarreau.com +/* MIT license: http://opensource.org/licenses/MIT +/* Demo / Generator : vincentgarreau.com/particles.js +/* GitHub : github.com/VincentGarreau/particles.js +/* How to use? : Check the GitHub README +/* v2.0.0 +/* ----------------------------------------------- */ +function hexToRgb(e){var a=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(a,function(e,a,t,i){return a+a+t+t+i+i});var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}function clamp(e,a,t){return Math.min(Math.max(e,a),t)}function isInArray(e,a){return a.indexOf(e)>-1}var pJS=function(e,a){var t=document.querySelector("#"+e+" > .particles-js-canvas-el");this.pJS={canvas:{el:t,w:t.offsetWidth,h:t.offsetHeight},particles:{number:{value:400,density:{enable:!0,value_area:800}},color:{value:"#fff"},shape:{type:"circle",stroke:{width:0,color:"#ff0000"},polygon:{nb_sides:5},image:{src:"",width:100,height:100}},opacity:{value:1,random:!1,anim:{enable:!1,speed:2,opacity_min:0,sync:!1}},size:{value:20,random:!1,anim:{enable:!1,speed:20,size_min:0,sync:!1}},line_linked:{enable:!0,distance:100,color:"#fff",opacity:1,width:1},move:{enable:!0,speed:2,direction:"none",random:!1,straight:!1,out_mode:"out",bounce:!1,attract:{enable:!1,rotateX:3e3,rotateY:3e3}},array:[]},interactivity:{detect_on:"canvas",events:{onhover:{enable:!0,mode:"grab"},onclick:{enable:!0,mode:"push"},resize:!0},modes:{grab:{distance:100,line_linked:{opacity:1}},bubble:{distance:200,size:80,duration:.4},repulse:{distance:200,duration:.4},push:{particles_nb:4},remove:{particles_nb:2}},mouse:{}},retina_detect:!1,fn:{interact:{},modes:{},vendors:{}},tmp:{}};var i=this.pJS;a&&Object.deepExtend(i,a),i.tmp.obj={size_value:i.particles.size.value,size_anim_speed:i.particles.size.anim.speed,move_speed:i.particles.move.speed,line_linked_distance:i.particles.line_linked.distance,line_linked_width:i.particles.line_linked.width,mode_grab_distance:i.interactivity.modes.grab.distance,mode_bubble_distance:i.interactivity.modes.bubble.distance,mode_bubble_size:i.interactivity.modes.bubble.size,mode_repulse_distance:i.interactivity.modes.repulse.distance},i.fn.retinaInit=function(){i.retina_detect&&window.devicePixelRatio>1?(i.canvas.pxratio=window.devicePixelRatio,i.tmp.retina=!0):(i.canvas.pxratio=1,i.tmp.retina=!1),i.canvas.w=i.canvas.el.offsetWidth*i.canvas.pxratio,i.canvas.h=i.canvas.el.offsetHeight*i.canvas.pxratio,i.particles.size.value=i.tmp.obj.size_value*i.canvas.pxratio,i.particles.size.anim.speed=i.tmp.obj.size_anim_speed*i.canvas.pxratio,i.particles.move.speed=i.tmp.obj.move_speed*i.canvas.pxratio,i.particles.line_linked.distance=i.tmp.obj.line_linked_distance*i.canvas.pxratio,i.interactivity.modes.grab.distance=i.tmp.obj.mode_grab_distance*i.canvas.pxratio,i.interactivity.modes.bubble.distance=i.tmp.obj.mode_bubble_distance*i.canvas.pxratio,i.particles.line_linked.width=i.tmp.obj.line_linked_width*i.canvas.pxratio,i.interactivity.modes.bubble.size=i.tmp.obj.mode_bubble_size*i.canvas.pxratio,i.interactivity.modes.repulse.distance=i.tmp.obj.mode_repulse_distance*i.canvas.pxratio},i.fn.canvasInit=function(){i.canvas.ctx=i.canvas.el.getContext("2d")},i.fn.canvasSize=function(){i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i&&i.interactivity.events.resize&&window.addEventListener("resize",function(){i.canvas.w=i.canvas.el.offsetWidth,i.canvas.h=i.canvas.el.offsetHeight,i.tmp.retina&&(i.canvas.w*=i.canvas.pxratio,i.canvas.h*=i.canvas.pxratio),i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i.particles.move.enable||(i.fn.particlesEmpty(),i.fn.particlesCreate(),i.fn.particlesDraw(),i.fn.vendors.densityAutoParticles()),i.fn.vendors.densityAutoParticles()})},i.fn.canvasPaint=function(){i.canvas.ctx.fillRect(0,0,i.canvas.w,i.canvas.h)},i.fn.canvasClear=function(){i.canvas.ctx.clearRect(0,0,i.canvas.w,i.canvas.h)},i.fn.particle=function(e,a,t){if(this.radius=(i.particles.size.random?Math.random():1)*i.particles.size.value,i.particles.size.anim.enable&&(this.size_status=!1,this.vs=i.particles.size.anim.speed/100,i.particles.size.anim.sync||(this.vs=this.vs*Math.random())),this.x=t?t.x:Math.random()*i.canvas.w,this.y=t?t.y:Math.random()*i.canvas.h,this.x>i.canvas.w-2*this.radius?this.x=this.x-this.radius:this.x<2*this.radius&&(this.x=this.x+this.radius),this.y>i.canvas.h-2*this.radius?this.y=this.y-this.radius:this.y<2*this.radius&&(this.y=this.y+this.radius),i.particles.move.bounce&&i.fn.vendors.checkOverlap(this,t),this.color={},"object"==typeof e.value)if(e.value instanceof Array){var s=e.value[Math.floor(Math.random()*i.particles.color.value.length)];this.color.rgb=hexToRgb(s)}else void 0!=e.value.r&&void 0!=e.value.g&&void 0!=e.value.b&&(this.color.rgb={r:e.value.r,g:e.value.g,b:e.value.b}),void 0!=e.value.h&&void 0!=e.value.s&&void 0!=e.value.l&&(this.color.hsl={h:e.value.h,s:e.value.s,l:e.value.l});else"random"==e.value?this.color.rgb={r:Math.floor(256*Math.random())+0,g:Math.floor(256*Math.random())+0,b:Math.floor(256*Math.random())+0}:"string"==typeof e.value&&(this.color=e,this.color.rgb=hexToRgb(this.color.value));this.opacity=(i.particles.opacity.random?Math.random():1)*i.particles.opacity.value,i.particles.opacity.anim.enable&&(this.opacity_status=!1,this.vo=i.particles.opacity.anim.speed/100,i.particles.opacity.anim.sync||(this.vo=this.vo*Math.random()));var n={};switch(i.particles.move.direction){case"top":n={x:0,y:-1};break;case"top-right":n={x:.5,y:-.5};break;case"right":n={x:1,y:-0};break;case"bottom-right":n={x:.5,y:.5};break;case"bottom":n={x:0,y:1};break;case"bottom-left":n={x:-.5,y:1};break;case"left":n={x:-1,y:0};break;case"top-left":n={x:-.5,y:-.5};break;default:n={x:0,y:0}}i.particles.move.straight?(this.vx=n.x,this.vy=n.y,i.particles.move.random&&(this.vx=this.vx*Math.random(),this.vy=this.vy*Math.random())):(this.vx=n.x+Math.random()-.5,this.vy=n.y+Math.random()-.5),this.vx_i=this.vx,this.vy_i=this.vy;var r=i.particles.shape.type;if("object"==typeof r){if(r instanceof Array){var c=r[Math.floor(Math.random()*r.length)];this.shape=c}}else this.shape=r;if("image"==this.shape){var o=i.particles.shape;this.img={src:o.image.src,ratio:o.image.width/o.image.height},this.img.ratio||(this.img.ratio=1),"svg"==i.tmp.img_type&&void 0!=i.tmp.source_svg&&(i.fn.vendors.createSvgImg(this),i.tmp.pushing&&(this.img.loaded=!1))}},i.fn.particle.prototype.draw=function(){function e(){i.canvas.ctx.drawImage(r,a.x-t,a.y-t,2*t,2*t/a.img.ratio)}var a=this;if(void 0!=a.radius_bubble)var t=a.radius_bubble;else var t=a.radius;if(void 0!=a.opacity_bubble)var s=a.opacity_bubble;else var s=a.opacity;if(a.color.rgb)var n="rgba("+a.color.rgb.r+","+a.color.rgb.g+","+a.color.rgb.b+","+s+")";else var n="hsla("+a.color.hsl.h+","+a.color.hsl.s+"%,"+a.color.hsl.l+"%,"+s+")";switch(i.canvas.ctx.fillStyle=n,i.canvas.ctx.beginPath(),a.shape){case"circle":i.canvas.ctx.arc(a.x,a.y,t,0,2*Math.PI,!1);break;case"edge":i.canvas.ctx.rect(a.x-t,a.y-t,2*t,2*t);break;case"triangle":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t,a.y+t/1.66,2*t,3,2);break;case"polygon":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t/(i.particles.shape.polygon.nb_sides/3.5),a.y-t/.76,2.66*t/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,1);break;case"star":i.fn.vendors.drawShape(i.canvas.ctx,a.x-2*t/(i.particles.shape.polygon.nb_sides/4),a.y-t/1.52,2*t*2.66/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,2);break;case"image":if("svg"==i.tmp.img_type)var r=a.img.obj;else var r=i.tmp.img_obj;r&&e()}i.canvas.ctx.closePath(),i.particles.shape.stroke.width>0&&(i.canvas.ctx.strokeStyle=i.particles.shape.stroke.color,i.canvas.ctx.lineWidth=i.particles.shape.stroke.width,i.canvas.ctx.stroke()),i.canvas.ctx.fill()},i.fn.particlesCreate=function(){for(var e=0;e=i.particles.opacity.value&&(a.opacity_status=!1),a.opacity+=a.vo):(a.opacity<=i.particles.opacity.anim.opacity_min&&(a.opacity_status=!0),a.opacity-=a.vo),a.opacity<0&&(a.opacity=0)),i.particles.size.anim.enable&&(1==a.size_status?(a.radius>=i.particles.size.value&&(a.size_status=!1),a.radius+=a.vs):(a.radius<=i.particles.size.anim.size_min&&(a.size_status=!0),a.radius-=a.vs),a.radius<0&&(a.radius=0)),"bounce"==i.particles.move.out_mode)var s={x_left:a.radius,x_right:i.canvas.w,y_top:a.radius,y_bottom:i.canvas.h};else var s={x_left:-a.radius,x_right:i.canvas.w+a.radius,y_top:-a.radius,y_bottom:i.canvas.h+a.radius};switch(a.x-a.radius>i.canvas.w?(a.x=s.x_left,a.y=Math.random()*i.canvas.h):a.x+a.radius<0&&(a.x=s.x_right,a.y=Math.random()*i.canvas.h),a.y-a.radius>i.canvas.h?(a.y=s.y_top,a.x=Math.random()*i.canvas.w):a.y+a.radius<0&&(a.y=s.y_bottom,a.x=Math.random()*i.canvas.w),i.particles.move.out_mode){case"bounce":a.x+a.radius>i.canvas.w?a.vx=-a.vx:a.x-a.radius<0&&(a.vx=-a.vx),a.y+a.radius>i.canvas.h?a.vy=-a.vy:a.y-a.radius<0&&(a.vy=-a.vy)}if(isInArray("grab",i.interactivity.events.onhover.mode)&&i.fn.modes.grabParticle(a),(isInArray("bubble",i.interactivity.events.onhover.mode)||isInArray("bubble",i.interactivity.events.onclick.mode))&&i.fn.modes.bubbleParticle(a),(isInArray("repulse",i.interactivity.events.onhover.mode)||isInArray("repulse",i.interactivity.events.onclick.mode))&&i.fn.modes.repulseParticle(a),i.particles.line_linked.enable||i.particles.move.attract.enable)for(var n=e+1;n0){var c=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+c.r+","+c.g+","+c.b+","+r+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(a.x,a.y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}},i.fn.interact.attractParticles=function(e,a){var t=e.x-a.x,s=e.y-a.y,n=Math.sqrt(t*t+s*s);if(n<=i.particles.line_linked.distance){var r=t/(1e3*i.particles.move.attract.rotateX),c=s/(1e3*i.particles.move.attract.rotateY);e.vx-=r,e.vy-=c,a.vx+=r,a.vy+=c}},i.fn.interact.bounceParticles=function(e,a){var t=e.x-a.x,i=e.y-a.y,s=Math.sqrt(t*t+i*i),n=e.radius+a.radius;n>=s&&(e.vx=-e.vx,e.vy=-e.vy,a.vx=-a.vx,a.vy=-a.vy)},i.fn.modes.pushParticles=function(e,a){i.tmp.pushing=!0;for(var t=0;e>t;t++)i.particles.array.push(new i.fn.particle(i.particles.color,i.particles.opacity.value,{x:a?a.pos_x:Math.random()*i.canvas.w,y:a?a.pos_y:Math.random()*i.canvas.h})),t==e-1&&(i.particles.move.enable||i.fn.particlesDraw(),i.tmp.pushing=!1)},i.fn.modes.removeParticles=function(e){i.particles.array.splice(0,e),i.particles.move.enable||i.fn.particlesDraw()},i.fn.modes.bubbleParticle=function(e){function a(){e.opacity_bubble=e.opacity,e.radius_bubble=e.radius}function t(a,t,s,n,c){if(a!=t)if(i.tmp.bubble_duration_end){if(void 0!=s){var o=n-p*(n-a)/i.interactivity.modes.bubble.duration,l=a-o;d=a+l,"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else if(r<=i.interactivity.modes.bubble.distance){if(void 0!=s)var v=s;else var v=n;if(v!=a){var d=n-p*(n-a)/i.interactivity.modes.bubble.duration;"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else"size"==c&&(e.radius_bubble=void 0),"opacity"==c&&(e.opacity_bubble=void 0)}if(i.interactivity.events.onhover.enable&&isInArray("bubble",i.interactivity.events.onhover.mode)){var s=e.x-i.interactivity.mouse.pos_x,n=e.y-i.interactivity.mouse.pos_y,r=Math.sqrt(s*s+n*n),c=1-r/i.interactivity.modes.bubble.distance;if(r<=i.interactivity.modes.bubble.distance){if(c>=0&&"mousemove"==i.interactivity.status){if(i.interactivity.modes.bubble.size!=i.particles.size.value)if(i.interactivity.modes.bubble.size>i.particles.size.value){var o=e.radius+i.interactivity.modes.bubble.size*c;o>=0&&(e.radius_bubble=o)}else{var l=e.radius-i.interactivity.modes.bubble.size,o=e.radius-l*c;o>0?e.radius_bubble=o:e.radius_bubble=0}if(i.interactivity.modes.bubble.opacity!=i.particles.opacity.value)if(i.interactivity.modes.bubble.opacity>i.particles.opacity.value){var v=i.interactivity.modes.bubble.opacity*c;v>e.opacity&&v<=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}else{var v=e.opacity-(i.particles.opacity.value-i.interactivity.modes.bubble.opacity)*c;v=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}}}else a();"mouseleave"==i.interactivity.status&&a()}else if(i.interactivity.events.onclick.enable&&isInArray("bubble",i.interactivity.events.onclick.mode)){if(i.tmp.bubble_clicking){var s=e.x-i.interactivity.mouse.click_pos_x,n=e.y-i.interactivity.mouse.click_pos_y,r=Math.sqrt(s*s+n*n),p=((new Date).getTime()-i.interactivity.mouse.click_time)/1e3;p>i.interactivity.modes.bubble.duration&&(i.tmp.bubble_duration_end=!0),p>2*i.interactivity.modes.bubble.duration&&(i.tmp.bubble_clicking=!1,i.tmp.bubble_duration_end=!1)}i.tmp.bubble_clicking&&(t(i.interactivity.modes.bubble.size,i.particles.size.value,e.radius_bubble,e.radius,"size"),t(i.interactivity.modes.bubble.opacity,i.particles.opacity.value,e.opacity_bubble,e.opacity,"opacity"))}},i.fn.modes.repulseParticle=function(e){function a(){var a=Math.atan2(d,p);if(e.vx=u*Math.cos(a),e.vy=u*Math.sin(a),"bounce"==i.particles.move.out_mode){var t={x:e.x+e.vx,y:e.y+e.vy};t.x+e.radius>i.canvas.w?e.vx=-e.vx:t.x-e.radius<0&&(e.vx=-e.vx),t.y+e.radius>i.canvas.h?e.vy=-e.vy:t.y-e.radius<0&&(e.vy=-e.vy)}}if(i.interactivity.events.onhover.enable&&isInArray("repulse",i.interactivity.events.onhover.mode)&&"mousemove"==i.interactivity.status){var t=e.x-i.interactivity.mouse.pos_x,s=e.y-i.interactivity.mouse.pos_y,n=Math.sqrt(t*t+s*s),r={x:t/n,y:s/n},c=i.interactivity.modes.repulse.distance,o=100,l=clamp(1/c*(-1*Math.pow(n/c,2)+1)*c*o,0,50),v={x:e.x+r.x*l,y:e.y+r.y*l};"bounce"==i.particles.move.out_mode?(v.x-e.radius>0&&v.x+e.radius0&&v.y+e.radius=m&&a()}else 0==i.tmp.repulse_clicking&&(e.vx=e.vx_i,e.vy=e.vy_i)},i.fn.modes.grabParticle=function(e){if(i.interactivity.events.onhover.enable&&"mousemove"==i.interactivity.status){var a=e.x-i.interactivity.mouse.pos_x,t=e.y-i.interactivity.mouse.pos_y,s=Math.sqrt(a*a+t*t);if(s<=i.interactivity.modes.grab.distance){var n=i.interactivity.modes.grab.line_linked.opacity-s/(1/i.interactivity.modes.grab.line_linked.opacity)/i.interactivity.modes.grab.distance;if(n>0){var r=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+r.r+","+r.g+","+r.b+","+n+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(i.interactivity.mouse.pos_x,i.interactivity.mouse.pos_y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}}},i.fn.vendors.eventsListeners=function(){"window"==i.interactivity.detect_on?i.interactivity.el=window:i.interactivity.el=i.canvas.el,(i.interactivity.events.onhover.enable||i.interactivity.events.onclick.enable)&&(i.interactivity.el.addEventListener("mousemove",function(e){if(i.interactivity.el==window)var a=e.clientX,t=e.clientY;else var a=e.offsetX||e.clientX,t=e.offsetY||e.clientY;i.interactivity.mouse.pos_x=a,i.interactivity.mouse.pos_y=t,i.tmp.retina&&(i.interactivity.mouse.pos_x*=i.canvas.pxratio,i.interactivity.mouse.pos_y*=i.canvas.pxratio),i.interactivity.status="mousemove"}),i.interactivity.el.addEventListener("mouseleave",function(e){i.interactivity.mouse.pos_x=null,i.interactivity.mouse.pos_y=null,i.interactivity.status="mouseleave"})),i.interactivity.events.onclick.enable&&i.interactivity.el.addEventListener("click",function(){if(i.interactivity.mouse.click_pos_x=i.interactivity.mouse.pos_x,i.interactivity.mouse.click_pos_y=i.interactivity.mouse.pos_y,i.interactivity.mouse.click_time=(new Date).getTime(),i.interactivity.events.onclick.enable)switch(i.interactivity.events.onclick.mode){case"push":i.particles.move.enable?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):1==i.interactivity.modes.push.particles_nb?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):i.interactivity.modes.push.particles_nb>1&&i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb);break;case"remove":i.fn.modes.removeParticles(i.interactivity.modes.remove.particles_nb);break;case"bubble":i.tmp.bubble_clicking=!0;break;case"repulse":i.tmp.repulse_clicking=!0,i.tmp.repulse_count=0,i.tmp.repulse_finish=!1,setTimeout(function(){i.tmp.repulse_clicking=!1},1e3*i.interactivity.modes.repulse.duration)}})},i.fn.vendors.densityAutoParticles=function(){if(i.particles.number.density.enable){var e=i.canvas.el.width*i.canvas.el.height/1e3;i.tmp.retina&&(e/=2*i.canvas.pxratio);var a=e*i.particles.number.value/i.particles.number.density.value_area,t=i.particles.array.length-a;0>t?i.fn.modes.pushParticles(Math.abs(t)):i.fn.modes.removeParticles(t)}},i.fn.vendors.checkOverlap=function(e,a){for(var t=0;tv;v++)e.lineTo(i,0),e.translate(i,0),e.rotate(l);e.fill(),e.restore()},i.fn.vendors.exportImg=function(){window.open(i.canvas.el.toDataURL("image/png"),"_blank")},i.fn.vendors.loadImg=function(e){if(i.tmp.img_error=void 0,""!=i.particles.shape.image.src)if("svg"==e){var a=new XMLHttpRequest;a.open("GET",i.particles.shape.image.src),a.onreadystatechange=function(e){4==a.readyState&&(200==a.status?(i.tmp.source_svg=e.currentTarget.response,i.fn.vendors.checkBeforeDraw()):(console.log("Error pJS - Image not found"),i.tmp.img_error=!0))},a.send()}else{var t=new Image;t.addEventListener("load",function(){i.tmp.img_obj=t,i.fn.vendors.checkBeforeDraw()}),t.src=i.particles.shape.image.src}else console.log("Error pJS - No image.src"),i.tmp.img_error=!0},i.fn.vendors.draw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type?i.tmp.count_svg>=i.particles.number.value?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):void 0!=i.tmp.img_obj?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame))},i.fn.vendors.checkBeforeDraw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type&&void 0==i.tmp.source_svg?i.tmp.checkAnimFrame=requestAnimFrame(check):(cancelRequestAnimFrame(i.tmp.checkAnimFrame),i.tmp.img_error||(i.fn.vendors.init(),i.fn.vendors.draw())):(i.fn.vendors.init(),i.fn.vendors.draw())},i.fn.vendors.init=function(){i.fn.retinaInit(),i.fn.canvasInit(),i.fn.canvasSize(),i.fn.canvasPaint(),i.fn.particlesCreate(),i.fn.vendors.densityAutoParticles(),i.particles.line_linked.color_rgb_line=hexToRgb(i.particles.line_linked.color)},i.fn.vendors.start=function(){isInArray("image",i.particles.shape.type)?(i.tmp.img_type=i.particles.shape.image.src.substr(i.particles.shape.image.src.length-3),i.fn.vendors.loadImg(i.tmp.img_type)):i.fn.vendors.checkBeforeDraw()},i.fn.vendors.eventsListeners(),i.fn.vendors.start()};Object.deepExtend=function(e,a){for(var t in a)a[t]&&a[t].constructor&&a[t].constructor===Object?(e[t]=e[t]||{},arguments.callee(e[t],a[t])):e[t]=a[t];return e},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}(),window.cancelRequestAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelRequestAnimationFrame||window.mozCancelRequestAnimationFrame||window.oCancelRequestAnimationFrame||window.msCancelRequestAnimationFrame||clearTimeout}(),window.pJSDom=[],window.particlesJS=function(e,a){"string"!=typeof e&&(a=e,e="particles-js"),e||(e="particles-js");var t=document.getElementById(e),i="particles-js-canvas-el",s=t.getElementsByClassName(i);if(s.length)for(;s.length>0;)t.removeChild(s[0]);var n=document.createElement("canvas");n.className=i,n.style.width="100%",n.style.height="100%";var r=document.getElementById(e).appendChild(n);null!=r&&pJSDom.push(new pJS(e,a))},window.particlesJS.load=function(e,a,t){var i=new XMLHttpRequest;i.open("GET",a),i.onreadystatechange=function(a){if(4==i.readyState)if(200==i.status){var s=JSON.parse(a.currentTarget.response);window.particlesJS(e,s),t&&t()}else console.log("Error pJS - XMLHttpRequest status: "+i.status),console.log("Error pJS - File config not found")},i.send()}; \ No newline at end of file diff --git a/app/public/_particles/particles.json b/app/public/_particles/particles.json new file mode 100755 index 0000000..deaba4d --- /dev/null +++ b/app/public/_particles/particles.json @@ -0,0 +1,116 @@ +{ + "particles": { + "number": { + "value": 80, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 5, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 6, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": false, + "mode": "repulse" + }, + "onclick": { + "enable": true, + "mode": "push" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200 + }, + "push": { + "particles_nb": 4 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true, + "config_demo": { + "hide_card": false, + "background_color": "#b61924", + "background_image": "", + "background_position": "50% 50%", + "background_repeat": "no-repeat", + "background_size": "cover" + } +} \ No newline at end of file diff --git a/app/public/css/particles.css b/app/public/css/particles.css new file mode 100755 index 0000000..fba35ab --- /dev/null +++ b/app/public/css/particles.css @@ -0,0 +1,100 @@ +/* ============================================================================= + HTML5 CSS Reset Minified - Eric Meyer + ========================================================================== */ + +html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent} +body{line-height:1} +article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block} +nav ul{list-style:none} +blockquote,q{quotes:none} +blockquote:before,blockquote:after,q:before,q:after{content:none} +a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent;text-decoration:none} +mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold} +del{text-decoration:line-through} +abbr[title],dfn[title]{border-bottom:1px dotted;cursor:help} +table{border-collapse:collapse;border-spacing:0} +hr{display:block;height:1px;border:0;border-top:1px solid #f6f6f6;margin:1em 0;padding:0} +input,select{vertical-align:middle} +li{list-style:none} + + +/* ============================================================================= + My CSS + ========================================================================== */ + +/* ---- base ---- */ + +html,body{ + width:100%; + height:100%; + background:#ffffff; + color:#000; +} + +html{ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body{ + font:normal 75% Arial, Helvetica, sans-serif; +} + +canvas{ + display:block; + vertical-align:bottom; +} + + +/* ---- stats.js ---- */ + +.count-particles{ + background: #000000; + position: absolute; + top: 48px; + left: 0; + width: 80px; + color: #0d5656; + font-size: .8em; + text-align: left; + text-indent: 4px; + line-height: 14px; + padding-bottom: 2px; + font-family: Helvetica, Arial, sans-serif; + font-weight: bold; +} + +.js-count-particles{ + font-size: 1.1em; +} + +#stats, +.count-particles{ + -webkit-user-select: none; + margin-top: 5px; + margin-left: 5px; +} + +#stats{ + border-radius: 3px 3px 0 0; + overflow: hidden; +} + +.count-particles{ + border-radius: 0 0 3px 3px; +} + + +/* ---- particles.js container ---- +*/ + +#particles-js{ + width: 100%; + height: 300px; + background-size: cover; + position: absolute; /* or relative, fixed, or sticky depending on your layout */ + z-index: 2; + background-color: transparent; + background-image: url(''); + background-position: 50% 50%; + background-repeat: no-repeat; +} diff --git a/app/public/css/styles.360d4f28e3942a9ee9b5.css b/app/public/css/styles.360d4f28e3942a9ee9b5.css new file mode 100644 index 0000000..9ce8ef4 --- /dev/null +++ b/app/public/css/styles.360d4f28e3942a9ee9b5.css @@ -0,0 +1,7 @@ +body,html{margin:0;padding:0;height:100%;overflow:auto}app-root{display:block;min-width:320px;height:100%}app-root,button,h1,h2,h3,h4,h5,h6,input,select,textarea{font:15px/1.2 -apple-system,BlinkMacSystemFont,“Segoe UI”,Roboto,Helvetica,Arial,sans-serif;font-weight:300;letter-spacing:.01em}a,app-root,button,input,select,textarea{color:#666}figure,h1,h2,h3,h4,h5,h6,p{margin:0}a{text-decoration:none}a:hover{text-decoration:underline}::-webkit-input-placeholder{color:#ccc}::-moz-placeholder{color:#ccc}:-ms-input-placeholder{color:#ccc}:-moz-placeholder{color:#ccc}.page-section{max-width:1440px;margin:0 auto;box-sizing:border-box}.journey-stats{display:flex;flex-wrap:wrap}.journey-stats>div{margin-top:20px;flex-basis:50%}.journey-stats h3+a,.journey-stats h3+p{font-size:20px}.journey-stats h2{font-size:23px;color:#555;flex:0 0 100%;margin-left:0!important}@media (min-width:760px){.journey-stats:after{content:"";display:table;clear:both}.journey-stats>div{width:50%;float:left}} + + +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fontawesome-webfont.8b43027f47b20503057d.eot?v=4.7.0);src:url(fontawesome-webfont.8b43027f47b20503057d.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(fontawesome-webfont.20fd1704ea223900efa9.woff2?v=4.7.0) format("woff2"),url(fontawesome-webfont.f691f37e57f04c152e23.woff?v=4.7.0) format("woff"),url(fontawesome-webfont.1e59d2330b4c6deb84b3.ttf?v=4.7.0) format("truetype"),url(fontawesome-webfont.c1e38fd9e0e74ba58f7a.svg?v=4.7.0#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(359deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-y-combinator:before,.fa-yc:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-television:before,.fa-tv:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:"\f2a3"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-address-card:before,.fa-vcard:before{content:"\f2bb"}.fa-address-card-o:before,.fa-vcard-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} \ No newline at end of file diff --git a/app/public/css/styles.css b/app/public/css/styles.css new file mode 100644 index 0000000..c37b6d6 --- /dev/null +++ b/app/public/css/styles.css @@ -0,0 +1,327 @@ +.thumbs-up { + fill: #d9d9e3; /* Default color */ + transition: fill 0.3s ease; + cursor: pointer; + } + + .thumbs-up:hover { + fill: #0f0f0f; /* Color on hover */ + } + + .display-none[_ngcontent-aas-c37] { + display: none !important; } + + .fs-container[_ngcontent-aas-c37] { + display: block; + cursor: pointer; + position: fixed; + z-index: 1; + top: 16px; + left: 16px; + width: 46px; + height: 46px; + text-align: center; + padding: 0; + background-color: rgba(0, 0, 0, 0.2); + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; } + .fs-container[_ngcontent-aas-c37]:hover { + background-color: rgba(0, 0, 0, 0.33); } + .fs-container[_ngcontent-aas-c37] .arrow-exitfs[_ngcontent-aas-c37] { + display: block; + width: 30px; + height: 30px; + background: transparent; + border-top: 2px solid #f2f2f2; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; } + .fs-container[_ngcontent-aas-c37] .arrow-exitfs.prev[_ngcontent-aas-c37] { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + position: relative; + left: 18px; + top: 18px; } + .fs-container[_ngcontent-aas-c37] .arrow-exitfs[_ngcontent-aas-c37]:after { + content: ''; + width: 30px; + height: 30px; + background: transparent; + border-top: 2px solid #f2f2f2; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + position: absolute; + left: -15px; + top: -17px; } + + .slideshow-container.slideshow-container-fs[_ngcontent-aas-c37] { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; } + + .slideshow-container[_ngcontent-aas-c37] { + position: relative; + display: block; + margin: auto; + height: 100%; + width: 100%; + overflow: hidden; } + .slideshow-container[_ngcontent-aas-c37] .hide-slide[_ngcontent-aas-c37] { + visibility: hidden; + position: absolute; + top: -100vw; + left: -100vw; + opacity: 0; } + .slideshow-container[_ngcontent-aas-c37] .slides[_ngcontent-aas-c37] { + -ms-touch-action: pan-y; + touch-action: pan-y; + position: absolute; + top: 0; + height: 100%; + width: 100%; + visibility: visible; + opacity: 1; + display: block; } + .slideshow-container[_ngcontent-aas-c37] .slides.selected[_ngcontent-aas-c37] { + left: 0; } + .slideshow-container[_ngcontent-aas-c37] .slides.left-slide[_ngcontent-aas-c37] { + left: -100%; } + .slideshow-container[_ngcontent-aas-c37] .slides.right-slide[_ngcontent-aas-c37] { + left: 100%; } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-in-left[_ngcontent-aas-c37] { + left: 0; + -webkit-animation: slideInLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideInLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-in-right[_ngcontent-aas-c37] { + left: 0; + -webkit-animation: slideInRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideInRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-out-left[_ngcontent-aas-c37] { + left: -100%; + -webkit-animation: slideOutLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideOutLeft 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.slide-out-right[_ngcontent-aas-c37] { + left: 100%; + -webkit-animation: slideOutRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); + animation: slideOutRight 0.5s cubic-bezier(0.42, 0, 0.58, 1); } + .slideshow-container[_ngcontent-aas-c37] .slides.link[_ngcontent-aas-c37] { + cursor: pointer; } + .slideshow-container[_ngcontent-aas-c37] .slides[_ngcontent-aas-c37]:not(.link) { + cursor: default; } + .slideshow-container[_ngcontent-aas-c37] .caption[_ngcontent-aas-c37] { + position: absolute; + bottom: 0; + padding: 10px; + width: 100%; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + position: absolute; + top: 0; + height: 100%; + width: auto; + cursor: pointer; + background-size: 100%; + background-image: -webkit-gradient(linear, left top, left bottom, from(transparent), to(transparent)); + background-image: linear-gradient(transparent, transparent); + z-index: 100; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37]:before { + display: block; + height: 100%; + position: absolute; + top: 0; + left: 0; + opacity: 0; + width: 100%; + z-index: -100; + -webkit-transition: opacity 0.45s; + transition: opacity 0.45s; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.prev[_ngcontent-aas-c37] { + left: 0; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.prev[_ngcontent-aas-c37]:before { + background-image: -webkit-gradient(linear, right top, left top, from(transparent), to(rgba(0, 0, 0, 0.75))); + background-image: linear-gradient(to left, transparent, rgba(0, 0, 0, 0.75)); + content: ''; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.next[_ngcontent-aas-c37] { + right: 0; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container.next[_ngcontent-aas-c37]:before { + background-image: -webkit-gradient(linear, left top, right top, from(transparent), to(rgba(0, 0, 0, 0.75))); + background-image: linear-gradient(to right, transparent, rgba(0, 0, 0, 0.75)); + content: ''; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow[_ngcontent-aas-c37] { + display: block; + margin: auto; + width: 30px; + height: 30px; + background: transparent; + border-top: 2px solid #f2f2f2; + border-left: 2px solid #f2f2f2; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow[_ngcontent-aas-c37]:before { + display: block; + height: 200%; + width: 200%; + margin-left: -50%; + margin-top: -50%; + content: ""; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow.prev[_ngcontent-aas-c37] { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + position: relative; + left: 20px; + margin-right: 10px; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow.next[_ngcontent-aas-c37] { + -webkit-transform: rotate(135deg); + transform: rotate(135deg); + position: relative; + right: 20px; + margin-left: 10px; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] { + display: block; + bottom: 15px; + z-index: 1; + text-align: center; + position: absolute; + padding: 0; + left: 0; + right: 0; + margin: 0 auto; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li[_ngcontent-aas-c37] { + display: inline; + margin: 0; + padding: 0; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li[_ngcontent-aas-c37] button[_ngcontent-aas-c37] { + border: none; + background: none; + text-indent: -9999px; + font-size: 0; + width: 20px; + height: 20px; + outline: none; + position: relative; + z-index: 1; + cursor: pointer; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li[_ngcontent-aas-c37] button[_ngcontent-aas-c37]:before { + content: ''; + width: 4px; + height: 4px; + background: var(--dot-color, #FFF); + border-radius: 4px; + display: block; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + opacity: .7; + -webkit-transition: all .5s ease-out; + transition: all .5s ease-out; } + .slideshow-container[_ngcontent-aas-c37] .slick-dots[_ngcontent-aas-c37] li.slick-active[_ngcontent-aas-c37] button[_ngcontent-aas-c37]:before { + -webkit-transform: translate(-50%, -50%) scale(1.4); + transform: translate(-50%, -50%) scale(1.4); + opacity: 1; } + + @media screen and (min-width: 768px) { + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37]:hover:before { + opacity: 1; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37]:hover .arrow[_ngcontent-aas-c37] { + border-width: 4px; } + .slideshow-container[_ngcontent-aas-c37] .arrow-container[_ngcontent-aas-c37] .arrow[_ngcontent-aas-c37]:hover { + border-width: 4px; } } + + @-webkit-keyframes slideInRight { + 0% { + left: -100%; } + 100% { + left: 0; } } + + @keyframes slideInRight { + 0% { + left: -100%; } + 100% { + left: 0; } } + + @-webkit-keyframes slideInLeft { + 0% { + left: 100%; } + 100% { + left: 0; } } + + @keyframes slideInLeft { + 0% { + left: 100%; } + 100% { + left: 0; } } + + @-webkit-keyframes slideOutRight { + 0% { + left: 0; } + 100% { + left: -100%; } } + + @keyframes slideOutRight { + 0% { + left: 0; } + 100% { + left: -100%; } } + + @-webkit-keyframes slideOutLeft { + 0% { + left: 0; } + 100% { + left: 100%; } } + + @keyframes slideOutLeft { + 0% { + left: 0; } + 100% { + left: 100%; } } + + .loader[_ngcontent-aas-c37] { + position: absolute; + left: 50%; + margin-left: -20px; + top: 50%; + margin-top: -20px; + border: 5px solid #f3f3f3; + border-top: 5px solid #555; + border-radius: 50%; + width: 50px; + height: 50px; + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; } + + @-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } + + @keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); } } \ No newline at end of file diff --git a/app/public/favicon.ico b/app/public/favicon.ico new file mode 100644 index 0000000..a50b8f4 Binary files /dev/null and b/app/public/favicon.ico differ diff --git a/app/public/images/Dt_Logo_Color-Horizontal.png b/app/public/images/Dt_Logo_Color-Horizontal.png new file mode 100644 index 0000000..683e2f2 Binary files /dev/null and b/app/public/images/Dt_Logo_Color-Horizontal.png differ diff --git a/app/public/images/ai-image.jpeg b/app/public/images/ai-image.jpeg new file mode 100644 index 0000000..04b819c Binary files /dev/null and b/app/public/images/ai-image.jpeg differ diff --git a/app/public/images/easytravel-logo.svg b/app/public/images/easytravel-logo.svg new file mode 100644 index 0000000..d9beb34 --- /dev/null +++ b/app/public/images/easytravel-logo.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/public/images/hero-bg.jpg b/app/public/images/hero-bg.jpg new file mode 100644 index 0000000..84d2060 Binary files /dev/null and b/app/public/images/hero-bg.jpg differ diff --git a/app/public/images/hero-gradient.png b/app/public/images/hero-gradient.png new file mode 100644 index 0000000..622d5d4 Binary files /dev/null and b/app/public/images/hero-gradient.png differ diff --git a/app/public/images/icon-community.png b/app/public/images/icon-community.png new file mode 100644 index 0000000..305432b Binary files /dev/null and b/app/public/images/icon-community.png differ diff --git a/app/public/images/icon-facebook.png b/app/public/images/icon-facebook.png new file mode 100644 index 0000000..94673a4 Binary files /dev/null and b/app/public/images/icon-facebook.png differ diff --git a/app/public/images/icon-instagram.png b/app/public/images/icon-instagram.png new file mode 100644 index 0000000..61a5a4c Binary files /dev/null and b/app/public/images/icon-instagram.png differ diff --git a/app/public/images/icon-rating-star-dark.png b/app/public/images/icon-rating-star-dark.png new file mode 100644 index 0000000..6de4352 Binary files /dev/null and b/app/public/images/icon-rating-star-dark.png differ diff --git a/app/public/images/icon-search.png b/app/public/images/icon-search.png new file mode 100644 index 0000000..4c21d05 Binary files /dev/null and b/app/public/images/icon-search.png differ diff --git a/app/public/images/icon-twitter.png b/app/public/images/icon-twitter.png new file mode 100644 index 0000000..f1c2dff Binary files /dev/null and b/app/public/images/icon-twitter.png differ diff --git a/app/public/images/loader-animation-freebie.gif b/app/public/images/loader-animation-freebie.gif new file mode 100644 index 0000000..92d0b36 Binary files /dev/null and b/app/public/images/loader-animation-freebie.gif differ diff --git a/app/public/index.html b/app/public/index.html new file mode 100644 index 0000000..fd1e170 --- /dev/null +++ b/app/public/index.html @@ -0,0 +1,200 @@ + + + + + + Your AI travel advisor + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +
    + + + + +
    + +
    +
    + + +
    +
    + +

    AItravel advisor +
    + +
    +
    +
    +
    + +
    + + +
    +
    +
    + +

    +
    +
    + + +
    +
    + +
    +
    +
    + +
    + + + +
    + +
    +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + diff --git a/app/public/js/app.js b/app/public/js/app.js new file mode 100644 index 0000000..f020af8 --- /dev/null +++ b/app/public/js/app.js @@ -0,0 +1,133 @@ +/* ----------------------------------------------- +/* How to use? : Check the GitHub README +/* ----------------------------------------------- */ + +/* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */ +/* +particlesJS.load('particles-js', 'particles.json', function() { + console.log('particles.js loaded - callback'); +}); +*/ + +/* Otherwise just put the config content (json): */ + +particlesJS('particles-js', + + { + "particles": { + "number": { + "value": 90, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 5, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 6, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": true, + "mode": "repulse" + }, + "onclick": { + "enable": false, + "mode": "push" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 400, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200 + }, + "push": { + "particles_nb": 0 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true, + "config_demo": { + "hide_card": true, + "background_color": "", + "background_image": "", + "background_position": "50% 50%", + "background_repeat": "no-repeat", + "background_size": "cover" + } + } + +); \ No newline at end of file diff --git a/app/public/js/particles.min.js b/app/public/js/particles.min.js new file mode 100644 index 0000000..b3d46d1 --- /dev/null +++ b/app/public/js/particles.min.js @@ -0,0 +1,9 @@ +/* ----------------------------------------------- +/* Author : Vincent Garreau - vincentgarreau.com +/* MIT license: http://opensource.org/licenses/MIT +/* Demo / Generator : vincentgarreau.com/particles.js +/* GitHub : github.com/VincentGarreau/particles.js +/* How to use? : Check the GitHub README +/* v2.0.0 +/* ----------------------------------------------- */ +function hexToRgb(e){var a=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;e=e.replace(a,function(e,a,t,i){return a+a+t+t+i+i});var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}function clamp(e,a,t){return Math.min(Math.max(e,a),t)}function isInArray(e,a){return a.indexOf(e)>-1}var pJS=function(e,a){var t=document.querySelector("#"+e+" > .particles-js-canvas-el");this.pJS={canvas:{el:t,w:t.offsetWidth,h:t.offsetHeight},particles:{number:{value:400,density:{enable:!0,value_area:800}},color:{value:"#fff"},shape:{type:"circle",stroke:{width:0,color:"#ff0000"},polygon:{nb_sides:5},image:{src:"",width:100,height:100}},opacity:{value:1,random:!1,anim:{enable:!1,speed:2,opacity_min:0,sync:!1}},size:{value:20,random:!1,anim:{enable:!1,speed:20,size_min:0,sync:!1}},line_linked:{enable:!0,distance:100,color:"#fff",opacity:1,width:1},move:{enable:!0,speed:2,direction:"none",random:!1,straight:!1,out_mode:"out",bounce:!1,attract:{enable:!1,rotateX:3e3,rotateY:3e3}},array:[]},interactivity:{detect_on:"canvas",events:{onhover:{enable:!0,mode:"grab"},onclick:{enable:!0,mode:"push"},resize:!0},modes:{grab:{distance:100,line_linked:{opacity:1}},bubble:{distance:200,size:80,duration:.4},repulse:{distance:200,duration:.4},push:{particles_nb:4},remove:{particles_nb:2}},mouse:{}},retina_detect:!1,fn:{interact:{},modes:{},vendors:{}},tmp:{}};var i=this.pJS;a&&Object.deepExtend(i,a),i.tmp.obj={size_value:i.particles.size.value,size_anim_speed:i.particles.size.anim.speed,move_speed:i.particles.move.speed,line_linked_distance:i.particles.line_linked.distance,line_linked_width:i.particles.line_linked.width,mode_grab_distance:i.interactivity.modes.grab.distance,mode_bubble_distance:i.interactivity.modes.bubble.distance,mode_bubble_size:i.interactivity.modes.bubble.size,mode_repulse_distance:i.interactivity.modes.repulse.distance},i.fn.retinaInit=function(){i.retina_detect&&window.devicePixelRatio>1?(i.canvas.pxratio=window.devicePixelRatio,i.tmp.retina=!0):(i.canvas.pxratio=1,i.tmp.retina=!1),i.canvas.w=i.canvas.el.offsetWidth*i.canvas.pxratio,i.canvas.h=i.canvas.el.offsetHeight*i.canvas.pxratio,i.particles.size.value=i.tmp.obj.size_value*i.canvas.pxratio,i.particles.size.anim.speed=i.tmp.obj.size_anim_speed*i.canvas.pxratio,i.particles.move.speed=i.tmp.obj.move_speed*i.canvas.pxratio,i.particles.line_linked.distance=i.tmp.obj.line_linked_distance*i.canvas.pxratio,i.interactivity.modes.grab.distance=i.tmp.obj.mode_grab_distance*i.canvas.pxratio,i.interactivity.modes.bubble.distance=i.tmp.obj.mode_bubble_distance*i.canvas.pxratio,i.particles.line_linked.width=i.tmp.obj.line_linked_width*i.canvas.pxratio,i.interactivity.modes.bubble.size=i.tmp.obj.mode_bubble_size*i.canvas.pxratio,i.interactivity.modes.repulse.distance=i.tmp.obj.mode_repulse_distance*i.canvas.pxratio},i.fn.canvasInit=function(){i.canvas.ctx=i.canvas.el.getContext("2d")},i.fn.canvasSize=function(){i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i&&i.interactivity.events.resize&&window.addEventListener("resize",function(){i.canvas.w=i.canvas.el.offsetWidth,i.canvas.h=i.canvas.el.offsetHeight,i.tmp.retina&&(i.canvas.w*=i.canvas.pxratio,i.canvas.h*=i.canvas.pxratio),i.canvas.el.width=i.canvas.w,i.canvas.el.height=i.canvas.h,i.particles.move.enable||(i.fn.particlesEmpty(),i.fn.particlesCreate(),i.fn.particlesDraw(),i.fn.vendors.densityAutoParticles()),i.fn.vendors.densityAutoParticles()})},i.fn.canvasPaint=function(){i.canvas.ctx.fillRect(0,0,i.canvas.w,i.canvas.h)},i.fn.canvasClear=function(){i.canvas.ctx.clearRect(0,0,i.canvas.w,i.canvas.h)},i.fn.particle=function(e,a,t){if(this.radius=(i.particles.size.random?Math.random():1)*i.particles.size.value,i.particles.size.anim.enable&&(this.size_status=!1,this.vs=i.particles.size.anim.speed/100,i.particles.size.anim.sync||(this.vs=this.vs*Math.random())),this.x=t?t.x:Math.random()*i.canvas.w,this.y=t?t.y:Math.random()*i.canvas.h,this.x>i.canvas.w-2*this.radius?this.x=this.x-this.radius:this.x<2*this.radius&&(this.x=this.x+this.radius),this.y>i.canvas.h-2*this.radius?this.y=this.y-this.radius:this.y<2*this.radius&&(this.y=this.y+this.radius),i.particles.move.bounce&&i.fn.vendors.checkOverlap(this,t),this.color={},"object"==typeof e.value)if(e.value instanceof Array){var s=e.value[Math.floor(Math.random()*i.particles.color.value.length)];this.color.rgb=hexToRgb(s)}else void 0!=e.value.r&&void 0!=e.value.g&&void 0!=e.value.b&&(this.color.rgb={r:e.value.r,g:e.value.g,b:e.value.b}),void 0!=e.value.h&&void 0!=e.value.s&&void 0!=e.value.l&&(this.color.hsl={h:e.value.h,s:e.value.s,l:e.value.l});else"random"==e.value?this.color.rgb={r:Math.floor(256*Math.random())+0,g:Math.floor(256*Math.random())+0,b:Math.floor(256*Math.random())+0}:"string"==typeof e.value&&(this.color=e,this.color.rgb=hexToRgb(this.color.value));this.opacity=(i.particles.opacity.random?Math.random():1)*i.particles.opacity.value,i.particles.opacity.anim.enable&&(this.opacity_status=!1,this.vo=i.particles.opacity.anim.speed/100,i.particles.opacity.anim.sync||(this.vo=this.vo*Math.random()));var n={};switch(i.particles.move.direction){case"top":n={x:0,y:-1};break;case"top-right":n={x:.5,y:-.5};break;case"right":n={x:1,y:-0};break;case"bottom-right":n={x:.5,y:.5};break;case"bottom":n={x:0,y:1};break;case"bottom-left":n={x:-.5,y:1};break;case"left":n={x:-1,y:0};break;case"top-left":n={x:-.5,y:-.5};break;default:n={x:0,y:0}}i.particles.move.straight?(this.vx=n.x,this.vy=n.y,i.particles.move.random&&(this.vx=this.vx*Math.random(),this.vy=this.vy*Math.random())):(this.vx=n.x+Math.random()-.5,this.vy=n.y+Math.random()-.5),this.vx_i=this.vx,this.vy_i=this.vy;var r=i.particles.shape.type;if("object"==typeof r){if(r instanceof Array){var c=r[Math.floor(Math.random()*r.length)];this.shape=c}}else this.shape=r;if("image"==this.shape){var o=i.particles.shape;this.img={src:o.image.src,ratio:o.image.width/o.image.height},this.img.ratio||(this.img.ratio=1),"svg"==i.tmp.img_type&&void 0!=i.tmp.source_svg&&(i.fn.vendors.createSvgImg(this),i.tmp.pushing&&(this.img.loaded=!1))}},i.fn.particle.prototype.draw=function(){function e(){i.canvas.ctx.drawImage(r,a.x-t,a.y-t,2*t,2*t/a.img.ratio)}var a=this;if(void 0!=a.radius_bubble)var t=a.radius_bubble;else var t=a.radius;if(void 0!=a.opacity_bubble)var s=a.opacity_bubble;else var s=a.opacity;if(a.color.rgb)var n="rgba("+a.color.rgb.r+","+a.color.rgb.g+","+a.color.rgb.b+","+s+")";else var n="hsla("+a.color.hsl.h+","+a.color.hsl.s+"%,"+a.color.hsl.l+"%,"+s+")";switch(i.canvas.ctx.fillStyle=n,i.canvas.ctx.beginPath(),a.shape){case"circle":i.canvas.ctx.arc(a.x,a.y,t,0,2*Math.PI,!1);break;case"edge":i.canvas.ctx.rect(a.x-t,a.y-t,2*t,2*t);break;case"triangle":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t,a.y+t/1.66,2*t,3,2);break;case"polygon":i.fn.vendors.drawShape(i.canvas.ctx,a.x-t/(i.particles.shape.polygon.nb_sides/3.5),a.y-t/.76,2.66*t/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,1);break;case"star":i.fn.vendors.drawShape(i.canvas.ctx,a.x-2*t/(i.particles.shape.polygon.nb_sides/4),a.y-t/1.52,2*t*2.66/(i.particles.shape.polygon.nb_sides/3),i.particles.shape.polygon.nb_sides,2);break;case"image":if("svg"==i.tmp.img_type)var r=a.img.obj;else var r=i.tmp.img_obj;r&&e()}i.canvas.ctx.closePath(),i.particles.shape.stroke.width>0&&(i.canvas.ctx.strokeStyle=i.particles.shape.stroke.color,i.canvas.ctx.lineWidth=i.particles.shape.stroke.width,i.canvas.ctx.stroke()),i.canvas.ctx.fill()},i.fn.particlesCreate=function(){for(var e=0;e=i.particles.opacity.value&&(a.opacity_status=!1),a.opacity+=a.vo):(a.opacity<=i.particles.opacity.anim.opacity_min&&(a.opacity_status=!0),a.opacity-=a.vo),a.opacity<0&&(a.opacity=0)),i.particles.size.anim.enable&&(1==a.size_status?(a.radius>=i.particles.size.value&&(a.size_status=!1),a.radius+=a.vs):(a.radius<=i.particles.size.anim.size_min&&(a.size_status=!0),a.radius-=a.vs),a.radius<0&&(a.radius=0)),"bounce"==i.particles.move.out_mode)var s={x_left:a.radius,x_right:i.canvas.w,y_top:a.radius,y_bottom:i.canvas.h};else var s={x_left:-a.radius,x_right:i.canvas.w+a.radius,y_top:-a.radius,y_bottom:i.canvas.h+a.radius};switch(a.x-a.radius>i.canvas.w?(a.x=s.x_left,a.y=Math.random()*i.canvas.h):a.x+a.radius<0&&(a.x=s.x_right,a.y=Math.random()*i.canvas.h),a.y-a.radius>i.canvas.h?(a.y=s.y_top,a.x=Math.random()*i.canvas.w):a.y+a.radius<0&&(a.y=s.y_bottom,a.x=Math.random()*i.canvas.w),i.particles.move.out_mode){case"bounce":a.x+a.radius>i.canvas.w?a.vx=-a.vx:a.x-a.radius<0&&(a.vx=-a.vx),a.y+a.radius>i.canvas.h?a.vy=-a.vy:a.y-a.radius<0&&(a.vy=-a.vy)}if(isInArray("grab",i.interactivity.events.onhover.mode)&&i.fn.modes.grabParticle(a),(isInArray("bubble",i.interactivity.events.onhover.mode)||isInArray("bubble",i.interactivity.events.onclick.mode))&&i.fn.modes.bubbleParticle(a),(isInArray("repulse",i.interactivity.events.onhover.mode)||isInArray("repulse",i.interactivity.events.onclick.mode))&&i.fn.modes.repulseParticle(a),i.particles.line_linked.enable||i.particles.move.attract.enable)for(var n=e+1;n0){var c=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+c.r+","+c.g+","+c.b+","+r+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(a.x,a.y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}},i.fn.interact.attractParticles=function(e,a){var t=e.x-a.x,s=e.y-a.y,n=Math.sqrt(t*t+s*s);if(n<=i.particles.line_linked.distance){var r=t/(1e3*i.particles.move.attract.rotateX),c=s/(1e3*i.particles.move.attract.rotateY);e.vx-=r,e.vy-=c,a.vx+=r,a.vy+=c}},i.fn.interact.bounceParticles=function(e,a){var t=e.x-a.x,i=e.y-a.y,s=Math.sqrt(t*t+i*i),n=e.radius+a.radius;n>=s&&(e.vx=-e.vx,e.vy=-e.vy,a.vx=-a.vx,a.vy=-a.vy)},i.fn.modes.pushParticles=function(e,a){i.tmp.pushing=!0;for(var t=0;e>t;t++)i.particles.array.push(new i.fn.particle(i.particles.color,i.particles.opacity.value,{x:a?a.pos_x:Math.random()*i.canvas.w,y:a?a.pos_y:Math.random()*i.canvas.h})),t==e-1&&(i.particles.move.enable||i.fn.particlesDraw(),i.tmp.pushing=!1)},i.fn.modes.removeParticles=function(e){i.particles.array.splice(0,e),i.particles.move.enable||i.fn.particlesDraw()},i.fn.modes.bubbleParticle=function(e){function a(){e.opacity_bubble=e.opacity,e.radius_bubble=e.radius}function t(a,t,s,n,c){if(a!=t)if(i.tmp.bubble_duration_end){if(void 0!=s){var o=n-p*(n-a)/i.interactivity.modes.bubble.duration,l=a-o;d=a+l,"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else if(r<=i.interactivity.modes.bubble.distance){if(void 0!=s)var v=s;else var v=n;if(v!=a){var d=n-p*(n-a)/i.interactivity.modes.bubble.duration;"size"==c&&(e.radius_bubble=d),"opacity"==c&&(e.opacity_bubble=d)}}else"size"==c&&(e.radius_bubble=void 0),"opacity"==c&&(e.opacity_bubble=void 0)}if(i.interactivity.events.onhover.enable&&isInArray("bubble",i.interactivity.events.onhover.mode)){var s=e.x-i.interactivity.mouse.pos_x,n=e.y-i.interactivity.mouse.pos_y,r=Math.sqrt(s*s+n*n),c=1-r/i.interactivity.modes.bubble.distance;if(r<=i.interactivity.modes.bubble.distance){if(c>=0&&"mousemove"==i.interactivity.status){if(i.interactivity.modes.bubble.size!=i.particles.size.value)if(i.interactivity.modes.bubble.size>i.particles.size.value){var o=e.radius+i.interactivity.modes.bubble.size*c;o>=0&&(e.radius_bubble=o)}else{var l=e.radius-i.interactivity.modes.bubble.size,o=e.radius-l*c;o>0?e.radius_bubble=o:e.radius_bubble=0}if(i.interactivity.modes.bubble.opacity!=i.particles.opacity.value)if(i.interactivity.modes.bubble.opacity>i.particles.opacity.value){var v=i.interactivity.modes.bubble.opacity*c;v>e.opacity&&v<=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}else{var v=e.opacity-(i.particles.opacity.value-i.interactivity.modes.bubble.opacity)*c;v=i.interactivity.modes.bubble.opacity&&(e.opacity_bubble=v)}}}else a();"mouseleave"==i.interactivity.status&&a()}else if(i.interactivity.events.onclick.enable&&isInArray("bubble",i.interactivity.events.onclick.mode)){if(i.tmp.bubble_clicking){var s=e.x-i.interactivity.mouse.click_pos_x,n=e.y-i.interactivity.mouse.click_pos_y,r=Math.sqrt(s*s+n*n),p=((new Date).getTime()-i.interactivity.mouse.click_time)/1e3;p>i.interactivity.modes.bubble.duration&&(i.tmp.bubble_duration_end=!0),p>2*i.interactivity.modes.bubble.duration&&(i.tmp.bubble_clicking=!1,i.tmp.bubble_duration_end=!1)}i.tmp.bubble_clicking&&(t(i.interactivity.modes.bubble.size,i.particles.size.value,e.radius_bubble,e.radius,"size"),t(i.interactivity.modes.bubble.opacity,i.particles.opacity.value,e.opacity_bubble,e.opacity,"opacity"))}},i.fn.modes.repulseParticle=function(e){function a(){var a=Math.atan2(d,p);if(e.vx=u*Math.cos(a),e.vy=u*Math.sin(a),"bounce"==i.particles.move.out_mode){var t={x:e.x+e.vx,y:e.y+e.vy};t.x+e.radius>i.canvas.w?e.vx=-e.vx:t.x-e.radius<0&&(e.vx=-e.vx),t.y+e.radius>i.canvas.h?e.vy=-e.vy:t.y-e.radius<0&&(e.vy=-e.vy)}}if(i.interactivity.events.onhover.enable&&isInArray("repulse",i.interactivity.events.onhover.mode)&&"mousemove"==i.interactivity.status){var t=e.x-i.interactivity.mouse.pos_x,s=e.y-i.interactivity.mouse.pos_y,n=Math.sqrt(t*t+s*s),r={x:t/n,y:s/n},c=i.interactivity.modes.repulse.distance,o=100,l=clamp(1/c*(-1*Math.pow(n/c,2)+1)*c*o,0,50),v={x:e.x+r.x*l,y:e.y+r.y*l};"bounce"==i.particles.move.out_mode?(v.x-e.radius>0&&v.x+e.radius0&&v.y+e.radius=m&&a()}else 0==i.tmp.repulse_clicking&&(e.vx=e.vx_i,e.vy=e.vy_i)},i.fn.modes.grabParticle=function(e){if(i.interactivity.events.onhover.enable&&"mousemove"==i.interactivity.status){var a=e.x-i.interactivity.mouse.pos_x,t=e.y-i.interactivity.mouse.pos_y,s=Math.sqrt(a*a+t*t);if(s<=i.interactivity.modes.grab.distance){var n=i.interactivity.modes.grab.line_linked.opacity-s/(1/i.interactivity.modes.grab.line_linked.opacity)/i.interactivity.modes.grab.distance;if(n>0){var r=i.particles.line_linked.color_rgb_line;i.canvas.ctx.strokeStyle="rgba("+r.r+","+r.g+","+r.b+","+n+")",i.canvas.ctx.lineWidth=i.particles.line_linked.width,i.canvas.ctx.beginPath(),i.canvas.ctx.moveTo(e.x,e.y),i.canvas.ctx.lineTo(i.interactivity.mouse.pos_x,i.interactivity.mouse.pos_y),i.canvas.ctx.stroke(),i.canvas.ctx.closePath()}}}},i.fn.vendors.eventsListeners=function(){"window"==i.interactivity.detect_on?i.interactivity.el=window:i.interactivity.el=i.canvas.el,(i.interactivity.events.onhover.enable||i.interactivity.events.onclick.enable)&&(i.interactivity.el.addEventListener("mousemove",function(e){if(i.interactivity.el==window)var a=e.clientX,t=e.clientY;else var a=e.offsetX||e.clientX,t=e.offsetY||e.clientY;i.interactivity.mouse.pos_x=a,i.interactivity.mouse.pos_y=t,i.tmp.retina&&(i.interactivity.mouse.pos_x*=i.canvas.pxratio,i.interactivity.mouse.pos_y*=i.canvas.pxratio),i.interactivity.status="mousemove"}),i.interactivity.el.addEventListener("mouseleave",function(e){i.interactivity.mouse.pos_x=null,i.interactivity.mouse.pos_y=null,i.interactivity.status="mouseleave"})),i.interactivity.events.onclick.enable&&i.interactivity.el.addEventListener("click",function(){if(i.interactivity.mouse.click_pos_x=i.interactivity.mouse.pos_x,i.interactivity.mouse.click_pos_y=i.interactivity.mouse.pos_y,i.interactivity.mouse.click_time=(new Date).getTime(),i.interactivity.events.onclick.enable)switch(i.interactivity.events.onclick.mode){case"push":i.particles.move.enable?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):1==i.interactivity.modes.push.particles_nb?i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb,i.interactivity.mouse):i.interactivity.modes.push.particles_nb>1&&i.fn.modes.pushParticles(i.interactivity.modes.push.particles_nb);break;case"remove":i.fn.modes.removeParticles(i.interactivity.modes.remove.particles_nb);break;case"bubble":i.tmp.bubble_clicking=!0;break;case"repulse":i.tmp.repulse_clicking=!0,i.tmp.repulse_count=0,i.tmp.repulse_finish=!1,setTimeout(function(){i.tmp.repulse_clicking=!1},1e3*i.interactivity.modes.repulse.duration)}})},i.fn.vendors.densityAutoParticles=function(){if(i.particles.number.density.enable){var e=i.canvas.el.width*i.canvas.el.height/1e3;i.tmp.retina&&(e/=2*i.canvas.pxratio);var a=e*i.particles.number.value/i.particles.number.density.value_area,t=i.particles.array.length-a;0>t?i.fn.modes.pushParticles(Math.abs(t)):i.fn.modes.removeParticles(t)}},i.fn.vendors.checkOverlap=function(e,a){for(var t=0;tv;v++)e.lineTo(i,0),e.translate(i,0),e.rotate(l);e.fill(),e.restore()},i.fn.vendors.exportImg=function(){window.open(i.canvas.el.toDataURL("image/png"),"_blank")},i.fn.vendors.loadImg=function(e){if(i.tmp.img_error=void 0,""!=i.particles.shape.image.src)if("svg"==e){var a=new XMLHttpRequest;a.open("GET",i.particles.shape.image.src),a.onreadystatechange=function(e){4==a.readyState&&(200==a.status?(i.tmp.source_svg=e.currentTarget.response,i.fn.vendors.checkBeforeDraw()):(console.log("Error pJS - Image not found"),i.tmp.img_error=!0))},a.send()}else{var t=new Image;t.addEventListener("load",function(){i.tmp.img_obj=t,i.fn.vendors.checkBeforeDraw()}),t.src=i.particles.shape.image.src}else console.log("Error pJS - No image.src"),i.tmp.img_error=!0},i.fn.vendors.draw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type?i.tmp.count_svg>=i.particles.number.value?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):void 0!=i.tmp.img_obj?(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame)):i.tmp.img_error||(i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw)):(i.fn.particlesDraw(),i.particles.move.enable?i.fn.drawAnimFrame=requestAnimFrame(i.fn.vendors.draw):cancelRequestAnimFrame(i.fn.drawAnimFrame))},i.fn.vendors.checkBeforeDraw=function(){"image"==i.particles.shape.type?"svg"==i.tmp.img_type&&void 0==i.tmp.source_svg?i.tmp.checkAnimFrame=requestAnimFrame(check):(cancelRequestAnimFrame(i.tmp.checkAnimFrame),i.tmp.img_error||(i.fn.vendors.init(),i.fn.vendors.draw())):(i.fn.vendors.init(),i.fn.vendors.draw())},i.fn.vendors.init=function(){i.fn.retinaInit(),i.fn.canvasInit(),i.fn.canvasSize(),i.fn.canvasPaint(),i.fn.particlesCreate(),i.fn.vendors.densityAutoParticles(),i.particles.line_linked.color_rgb_line=hexToRgb(i.particles.line_linked.color)},i.fn.vendors.start=function(){isInArray("image",i.particles.shape.type)?(i.tmp.img_type=i.particles.shape.image.src.substr(i.particles.shape.image.src.length-3),i.fn.vendors.loadImg(i.tmp.img_type)):i.fn.vendors.checkBeforeDraw()},i.fn.vendors.eventsListeners(),i.fn.vendors.start()};Object.deepExtend=function(e,a){for(var t in a)a[t]&&a[t].constructor&&a[t].constructor===Object?(e[t]=e[t]||{},arguments.callee(e[t],a[t])):e[t]=a[t];return e},window.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){window.setTimeout(e,1e3/60)}}(),window.cancelRequestAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelRequestAnimationFrame||window.mozCancelRequestAnimationFrame||window.oCancelRequestAnimationFrame||window.msCancelRequestAnimationFrame||clearTimeout}(),window.pJSDom=[],window.particlesJS=function(e,a){"string"!=typeof e&&(a=e,e="particles-js"),e||(e="particles-js");var t=document.getElementById(e),i="particles-js-canvas-el",s=t.getElementsByClassName(i);if(s.length)for(;s.length>0;)t.removeChild(s[0]);var n=document.createElement("canvas");n.className=i,n.style.width="100%",n.style.height="100%";var r=document.getElementById(e).appendChild(n);null!=r&&pJSDom.push(new pJS(e,a))},window.particlesJS.load=function(e,a,t){var i=new XMLHttpRequest;i.open("GET",a),i.onreadystatechange=function(a){if(4==i.readyState)if(200==i.status){var s=JSON.parse(a.currentTarget.response);window.particlesJS(e,s),t&&t()}else console.log("Error pJS - XMLHttpRequest status: "+i.status),console.log("Error pJS - File config not found")},i.send()}; \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..f8b7dfa --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,10 @@ +fastapi == 0.115.12 +uvicorn == 0.34.2 +traceloop-sdk == 0.36.1 +ollama~=0.4.8 +langchain==0.3.23 +langchain-weaviate==0.0.4 +langchain-community==0.3.27 +langchain-ollama==0.3.2 +beautifulsoup4==4.13.4 +lxml==5.3.2 \ No newline at end of file diff --git a/architecture.jpg b/architecture.jpg deleted file mode 100644 index 4db6122..0000000 Binary files a/architecture.jpg and /dev/null differ diff --git a/collector-values.yaml b/collector-values.yaml deleted file mode 100644 index 0a3a05c..0000000 --- a/collector-values.yaml +++ /dev/null @@ -1,99 +0,0 @@ -mode: deployment -image: - repository: ghcr.io/dynatrace/dynatrace-otel-collector/dynatrace-otel-collector - tag: 0.9.0 - -presets: - logsCollection: - enabled: true - # enables the k8sattributesprocessor and adds it to the traces, metrics, and logs pipelines - kubernetesAttributes: - enabled: true - extractAllPodLabels: true - extractAllPodAnnotations: true - -command: - name: dynatrace-otel-collector -extraEnvs: -- name: DT_API_TOKEN - valueFrom: - secretKeyRef: - name: dynatrace-otelcol-dt-api-credentials - key: DT_API_TOKEN -- name: DT_ENDPOINT - valueFrom: - secretKeyRef: - name: dynatrace-otelcol-dt-api-credentials - key: DT_ENDPOINT -resources: - limits: - memory: 512Mi -config: - receivers: - jaeger: null - prometheus: null - zipkin: null - otlp: - protocols: - grpc: - endpoint: ${env:MY_POD_IP}:4317 - http: - endpoint: ${env:MY_POD_IP}:4318 - - # Configuration to ingest log file - # Uncomment to use when running locally - # filelog: - # include: [ 'c:\someDir\traveladvisor\*.log' ] - - processors: - transform: - metric_statements: - - context: metric - statements: - # Get count from the histogram. The new metric name will be _count - - extract_count_metric(true) where type == METRIC_DATA_TYPE_HISTOGRAM - - # Get sum from the histogram. The new metric name will be _sum - - extract_sum_metric(true) where type == METRIC_DATA_TYPE_HISTOGRAM - - context: datapoint - statements: - # convert the _sum metrics to gauges. - - convert_sum_to_gauge() where IsMatch(metric.name, ".*_sum") - filter: - error_mode: ignore - metrics: - metric: - - 'type == METRIC_DATA_TYPE_HISTOGRAM' - - 'type == METRIC_DATA_TYPE_SUMMARY' - cumulativetodelta: - - # Configuration to scrape weaviate metrics - # Uncomment to use when running locally - # prometheus: - # config: - # scrape_configs: - # - job_name: 'weaviate' - # scrape_interval: 10s - # static_configs: - # - targets: ['localhost:2112'] - exporters: - otlphttp: - endpoint: "${env:DT_ENDPOINT}/api/v2/otlp" - headers: - Authorization: "Api-Token ${env:DT_API_TOKEN}" - service: - pipelines: - traces: - receivers: [otlp] - processors: [] - exporters: [otlphttp] - logs: - receivers: [otlp] - # receivers: [otlp, filelog] # Use when running locally - processors: [] - exporters: [otlphttp] - metrics: - receivers: [otlp] - # receivers: [otlp, prometheus] # Use when running locally - processors: [transform, filter, cumulativetodelta] - exporters: [otlphttp] diff --git a/custom_theme/main.html b/custom_theme/main.html deleted file mode 100644 index 3775521..0000000 --- a/custom_theme/main.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "base.html" %} - -{% block extrahead %} -{% if config.extra and config.extra.dt and config.extra.dt.rumagent and config.extra.dt.rumagent.snippet %} -{{ config.extra.dt.rumagent.snippet }} -{% endif%} -{% endblock %} - -{% block footer %} -{% if config.extra and config.extra.progressbar and config.extra.obslab and config.extra.progressbar[page.title] %} - - -
    -

    Page Progress

    -

    Use these checkmarks to track your progress

    - -
    -
      - {% for item in config.extra.progressbar[page.title] %} -
    • - - -
    • - {% endfor %} -
    -
    -
    -
    -
    -
    -
    -{% endif%} -{% endblock %} \ No newline at end of file diff --git a/deployment/deployment.yaml b/deployment/deployment.yaml deleted file mode 100644 index 3ad3c22..0000000 --- a/deployment/deployment.yaml +++ /dev/null @@ -1,186 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: weaviate-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 8Gi - storageClassName: "standard" ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: weaviate-semantic-cache -spec: - replicas: 1 - selector: - matchLabels: - app: weaviate-semantic-cache - template: - metadata: - labels: - app: weaviate-semantic-cache - annotations: - metrics.dynatrace.com/scrape: "true" - metrics.dynatrace.com/port: "2112" - metrics.dynatrace.com/path: "/metrics" - spec: - volumes: - - name: weaviate-volume - persistentVolumeClaim: - claimName: weaviate-pvc - containers: - - name: weaviate - image: semitechnologies/weaviate:latest - ports: - - containerPort: 8080 - - containerPort: 2112 - volumeMounts: - - name: weaviate-volume - mountPath: /var/lib/weaviate - env: - - name: PROMETHEUS_MONITORING_ENABLED - value: "true" - - name: AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED - value: "true" - - name: DEFAULT_VECTORIZER_MODULE - value: "none" - - name: AUTOSCHEMA_ENABLED - value: "false" - - name: ENABLE_MODULES - value: "text2vec-openai" - - name: PERSISTENCE_DATA_PATH - value: "/var/lib/weaviate" - resources: - limits: - cpu: "0.1" # Maximum CPU usage - memory: "512Mi" # Maximum memory usage ---- -apiVersion: v1 -kind: Service -metadata: - name: weaviate-service -spec: - selector: - app: weaviate-semantic-cache - ports: - - protocol: TCP - port: 80 - name: web - targetPort: 8080 # container port ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: travel-advisor -spec: - replicas: 1 - selector: - matchLabels: - app: travel-advisor - template: - metadata: - labels: - app: travel-advisor - spec: - containers: - - name: travel-advisor - image: gardnera/traveladvisor:main - imagePullPolicy: Always - ports: - - containerPort: 8080 - env: - - name: OPENAI_API_KEY - valueFrom: - secretKeyRef: - name: openai-details - key: API_KEY - - name: COMPLETION_LENGTH - value: "50" - - name: WEAVIATE_ENDPOINT - value: "http://weaviate-service:80" - - name: OTEL_COLLECTOR_ENDPOINT - value: "http://dynatrace-collector-opentelemetry-collector.default.svc.cluster.local:4318" - - name: TRACELOOP_TELEMETRY - value: "False" - - name: OTEL_COLLECTOR_ENDPOINT_INSECURE - value: "True" - - name: "AI_MODEL" - value: "gpt-4o-mini" - - name: "AI_EMBEDDING_MODEL" - value: "text-embedding-ada-002" - resources: - limits: - cpu: "0.1" # Maximum CPU usage - memory: "512Mi" # Maximum memory usage ---- -apiVersion: v1 -kind: Service -metadata: - name: travel-advisor-service -spec: - selector: - app: travel-advisor - ports: - - protocol: TCP - port: 8080 - targetPort: 8080 # Replace with your container port - nodePort: 30100 - type: NodePort ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: k6-config -data: - script.js: | - import http from 'k6/http'; - import { sleep } from 'k6'; - - export const options = { - vus: 1, - duration: '1m', - }; - - export default function () { - http.get('http://travel-advisor-service.travel-advisor.svc.cluster.local:8080/api/v1/completion?prompt=sydney'); - sleep(5); - } ---- -apiVersion: batch/v1 -kind: CronJob -metadata: - name: run-k6 -spec: - schedule: "*/1 * * * *" - jobTemplate: - spec: - template: - spec: - containers: - - name: k6 - image: hrexed/xk6-dynatrace-output:0.11 - args: ["run", "/script.js", "-o", "output-dynatrace"] - volumeMounts: - - name: config-volume - mountPath: /script.js - subPath: script.js - env: - - name: K6_DYNATRACE_URL - valueFrom: - secretKeyRef: - name: dt-details - key: DT_ENDPOINT - - name: K6_DYNATRACE_APITOKEN - valueFrom: - secretKeyRef: - name: dt-details - key: DT_API_TOKEN - volumes: - - name: config-volume - configMap: - name: k6-config - restartPolicy: OnFailure diff --git a/docs/2-getting-started.md b/docs/2-getting-started.md new file mode 100644 index 0000000..ed74edf --- /dev/null +++ b/docs/2-getting-started.md @@ -0,0 +1,33 @@ +--8<-- "snippets/getting-started.js" +--8<-- "snippets/grail-requirements.md" + +## Prerequisites before launching the Codespace + +### Generate a Dynatrace Token + +To create a Dynatrace token + +1. In Dynatrace, go to **Access Tokens**. + To find **Access Tokens**, press **Ctrl/Cmd+K** to search for and select **Access Tokens**. +2. In **Access Tokens**, select **Generate new token**. +3. Enter a **Token name** for your new token. +4. Give your new token the following permissions: +5. Search for and select all of the following scopes. + - **Ingest metrics** (`metrics.ingest`) + - **Ingest logs** (`logs.ingest`) + - **Ingest events** (`events.ingest`) + - **Ingest OpenTelemetry traces** (`openTelemetryTrace.ingest`) + - **Read metrics** (`metrics.read`) + - **Write settings** (`settings.write`) +6. Select **Generate token**. +7. Copy the generated token to the clipboard and be ready to use it in the next step. + +!!! warning "You can only access your token once upon creation. You can't reveal it afterward." + +!!! tip "Let's launch the Codespace" + Now we are ready to launch the Codespace! + + +
    +- [Let's launch Codespaces:octicons-arrow-right-24:](3-codespaces.md) +
    diff --git a/docs/3-codespaces.md b/docs/3-codespaces.md new file mode 100644 index 0000000..8129062 --- /dev/null +++ b/docs/3-codespaces.md @@ -0,0 +1,71 @@ +--8<-- "snippets/3-codespaces.js" + +--8<-- "snippets/dt-enablement.md" + +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dynatrace-wwse/enablement-gen-ai-llm-observability){target="_blank"} + + +## 1.1 Codespaces configuration +!!! tip "Branch, Machine sizing & secrets" + - Branch + - select the **main** branch + - Machine sizing + - As a machine type select **4-core** + - Secrets (enter your credentials within the following variables) + - DT_ENVIRONMENT + - DT_TOKEN + + +## 2. While the Codespace is set-up for you, learn powerful usecases with Dynatrace +We know your time is very valuable. This codespace takes around 6 minutes to be fully operational. A local Kubernetes ([kind](https://kind.sigs.k8s.io/){target="_blank"}) cluster monitored by Dynatrace will be configured and in it a sample AI application, the AI Travel Advisor app will be deployed. To make your experience best, we are also installing and configuring tools like: + +**k9s kubectl helm node jq python3 gh** + +![Codespaces installing](img/codespaces_installing.png) + +## 3. Explore what has been deployed + +Your Codespace has now deployed the following resources: + +- A local Kubernetes ([kind](https://kind.sigs.k8s.io/){target="_blank"}) cluster monitored by Dynatrace, with some pre-deployed apps + that will be used later in the demo. + +- After a couple of minutes, you'll see this screen in your codespaces terminal. It contains the links to the local expose labguide and the UI of the application which we will be doing our Hands-On training. + + +![Codespaces finish](img/codespaces_finish.png) + + +## 4. Tips & Tricks + +We want to boost your learning and try to make your DEV experience as smooth as possible with Dynatrace trainings. Your Codespaces have a couple of convenience features added. + +### Show the greeting +In the terminal, there are functions loaded for your convenience. By creating a new Terminal the Greeting will be shown that includes the links to the exposed apps, the Github pages, the Github Repository, the Dynatrace Tenant that is bound to this devcontainer and some of the tools installed. + +You can create a new Terminal directly in VSCode, type `zsh` or call the function `printGreeting` and that will print the greeting with the most relevant information. + +### Navigating in your local Kubernetes +The client `kubectl` and `k9s`are configured so you can navigate in your local Kubernetes like butter. +![k9s](img/k9s.png) + +### Exposing the app to the public +The app AI Travel Advisor is being exposed in the devcontainer to your localhost via Nodeport. If you want to make the endpoints public accesible, just go to the ports section in VsCode, right click on them and change the visibility to public. + + +## 5. Troubleshooting + + +### Exposing the App +The AI Travel Advisor app is being exposed via NodePort to the Kubernetes Workernode port 30100. You can easily see what is being exposed by typing the function `showOpenPorts` + +```bash +showOpenPorts(){ + sudo netstat -tulnp +} +``` + + +
    +- [Let's start our AI journey:octicons-arrow-right-24:](4-content.md) +
    diff --git a/docs/4-content.md b/docs/4-content.md new file mode 100644 index 0000000..816c734 --- /dev/null +++ b/docs/4-content.md @@ -0,0 +1,73 @@ +# Content +--8<-- "snippets/4-content.js" + +## What is AI and LLM observability? +AI and Large Language Model (LLM) observability provides visibility into all layers AI-powered applications. +This approach covers the complete AI stack, from foundational models and vector databases to RAG orchestration frameworks, ensuring visibility across every layer of modern AI applications. +Complete observability is critical to ensure accuracy and reliability. + +## The need for AI and LLM observability +As large language models have evolved, many use cases have emerged. Common AI implementations include chatbots, data analysis, data extraction, code creation, and content creation. These AI-powered models offer benefits such as speed, scope, and scale. LLMs can quickly handle complex queries using a variety of data types from multiple data sources. + +However, synthesizing more data faster doesn't always mean better results. Models may function perfectly, but if the data sources aren't accurate, outputs will be inaccurate, as well. Furthermore, if the data is valid, but processes are flawed, results won't be reliable. Therefore, observability is necessary to ensure that all aspects of the LLM operations are correct and consistent. + +## Key components of AI and LLM observability +AI observability has three key components: + +### 1.- Output evaluation +Teams must regularly evaluate outputs for accuracy and reliability. Because many organizations use third-party models, teams often accomplish this using a separate evaluation LLM that’s purpose-built for this function. + +### 2.- Prompt analysis +Poorly constructed prompts are a common cause of low-quality results. Therefore, LLM observability regularly analyzes prompts to determine if queries produce desired results and if better prompt templates can improve them. + +### 3.- Retrieval improvement +Data search and retrieval are critical for effective output. Here, the observability solution considers the retrieved data's context and accuracy, and it looks for ways to improve this process. + +## Let's see an example of an AI Application, the AI Travel Advisor + +In VSCode open a new terminal and in the Welcome Message you should see a link to the AI Travel Advisor App UI. +Click on it, you should see something like this: + +![Landing Page](img/ai_travel_advisor.jpg) + +The application is a basic implementation of AI technologies where the user can type the name of a city and then the AI will give back a travel advice depending on the selected Mode enhancing the user input like the following: +```py title="User input converted into travel advice" + Question: Give travel advise in a paragraph of max 50 words about {input} +``` + +This sample application is monitored with [OpenLLMetry](https://github.com/traceloop/openllmetry), +an open source library built on top of OpenTelemetry to provide auto-instrumentation for AI Technologies. +To configure OpenLLMetry, you should add the following code snippet before any library is imported. +It is common practice to add it to the first lines of the main python script. +The initialization requires a Dynatrace Token and a OTLP endpoint. +To find the available OTLP endpoints supported by Dynatrace, please refer to [our documentation](https://docs.dynatrace.com/docs/ingest-from/opentelemetry/getting-started/otlp-export). + + +```py title="OpenLLMetry setup" +from traceloop.sdk import Traceloop +# Dynatrace API Auth Header +headers = {"Authorization": f"Api-Token {TOKEN}"} +# Initialize OpenLLMetry +Traceloop.init( + app_name="ai-travel-advisor", + api_endpoint=OTEL_ENDPOINT, + disable_batch=True, + headers=headers, +) +``` + +!!! Example "OpenLLMetry has to be initialized before the import of the AI technologies because it proxies the import of the libraries and changes their code to provide additional telemetry. If the library is already imported, the Python interpreter won't see the changes introduced by OpenLLMetry." + +The AI Travel Advisor App has three different modes to interact with an LLM: + +- Direct Chat +- RAG +- Agentic + +Let's explore them! + + + +
    +- [Let's interact with the AI:octicons-arrow-right-24:](5-direct.md) +
    diff --git a/docs/5-direct.md b/docs/5-direct.md new file mode 100644 index 0000000..86c47ae --- /dev/null +++ b/docs/5-direct.md @@ -0,0 +1,48 @@ +# Direct Chat +--8<-- "snippets/5-direct.js" + +In this mode, we directly send our prompt to Ollama to generate a travel raccomendation. +Let's use `Sydney` as city to test the AI Travel raccomendation and press `Advise`. +At first, you will see a loading animation and after a few seconds, an answer like the following one: + +![Direct Chat example](./img/direct_chat.jpg) + +!!! Example "If you click on Advise multiple time, you will see that the answer is slightly different every time. LLMs have inherently a random nature which makes Observability important." + +We saw that the response arrived after a few seconds, how can we sure that the response time is not sky rocketing? +How can be sure that the LLM answers correctly to user requests? +If we're using 3rd party LLMs, how can we monitor the cost associated with the requests? + +Luckily, we have Dynatrace 😉 + +## Let's follow the traces + +Opening the Distributed Tracing App, you can filter it by services on the menu on the left-hand side. +You should see the service `ai-travel-advisor`. +Filtering by the service name allows to see all the request that we made so far to our AI Application. +Clicking on one, will reveal the set of operation that are happening when we request a travel advice. + +![First Trace](./img/first_trace.png) + +The request is quite simple, we hit the API exposed by the service, we start a [Task](https://docs.dynatrace.com/docs/analyze-explore-automate/dynatrace-for-ai-observability/terms-and-concepts#traceloop-span-kind) which denotes a specific operation or step within a workflow. +Finally, we fire a call towards Ollama to generate a completion for the prompt. + +!!! Note "If OneAgent is also being deployed, we will see addional child traces that represent the call received by Ollama and the processing of the request." + +From this view, we have an understanding of what's happening and how long it took the service to provide an answer to the user. +But let's navigate a bit deeper on what information are at our disposal! + +Clicking on the span `ollama.completion` we get more insights into what's happening when we call our LLMs. +In particular, the section around GenAI and LLM contain the relevant information for the domain, such as token consumption, prompt details, and model used. +Based on which LLM the application is using, we can get more details, such as temperature, topK, and more deep level type of information. + +![Trace Detail](./img/trace_details.png) + +However, directly chatting with a model is not how moder AI application approach problems. +These LLMs are powerful but don't know about a specific domains and contexes. +In the next section, we're exploring a common approach to overcome this limitation. + + +
    +- [Let's create an advance AI pipeline:octicons-arrow-right-24:](6-rag.md) +
    diff --git a/docs/6-rag.md b/docs/6-rag.md new file mode 100644 index 0000000..c5c6c8a --- /dev/null +++ b/docs/6-rag.md @@ -0,0 +1,59 @@ +# Retrieval-Augmented Generation (RAG) +--8<-- "snippets/6-rag.js" + +Retrieval-Augmented Generation (RAG) is a technique that provides additional context to LLMs to have additional information to carry out a solution to the prompt they receive. +In our AI Travel Advisor App, the RAG pipeline is built using LangChain. The retrieval step reaches out to Weaviate to query for documents relevant to the prompt in input. + +![Architecture](./img/rag-arch.jpg) + + +Let's try again using the same city as input: `Sydney`. +The answer returned should be quite puzzling, like the following: + +![hallucination](./img/bad_rag.jpg) + +In this answer, the LLM is hallucinating an answer which is far from being correct! +Let's investigate what could be the reason of such a weird answer. + +## Tracing to the rescue + +Let's use again the Distributed Tracing App to inspect the request. + +![RAG Trace](./img/rag_trace.png) + +We can see that the request is more complex because there is a step to fetch documents from Weaviate, process them, augment the prompt and finally send the final crafted prompt to Ollama. +Fetching the documents is a fast step that only takes few milliseconds and crafting the final response is taking almost all the time. +Selecting each span, we have at our disposal all the contextual information that describe that AI pipeline step. + +Let's focus on the time expensive call to Ollama and select the `ChatOllama.chat` span. +In the detailed view, we can see the GenAI section. Let's start from the prompt message: + +![RAG Trace Details](./img/rag_details.png) + +We can see that the prompt sent to the LLM contains information about Sydney and Bali. Clearly something is wrong! +LangChain retrieves the top N documents closest to the topic searched. +Let's see the fetched documents by pressing on the `retriever.done.task` span and looking into its attributes. + +![RAG Document Details](./img/rag_docs.png) + +We see that the query is looking for `Sydney` and two documents have been retrieved from Weaviate, one for Sydney and one for Bali. +If we look into the application code, inside the destinations folder, we see only two small documents which contain innacurate information. + + +The lack of coverage of the topic triggered the fetching of additional documents that don't really relate to Sydney. +This is a clear indicator that our Knowledge Base inside Weaviate is not exahustive enough. + +Furthermore, we can also observe that feeding the LLM with garbage information produces garbage responses. +The content of the Sydney or Bali documents provide innacurate facts. +This is telling us that the LLM really made use of the information we provided to it. + +Let's try again using a different city, like `Paris`. +Since we don't have wrong information around Paris, now the LLM should produce a valid answer! + +![Correct RAG](./img/good_rag.jpg) + +!!! bug "There is a bug with OpenLLMetry and Weaviate for which it does generate Spans only for v3 of Weaviate. In this codebase, we fix it by explicity telling the sensor what function it should attach to it." + +
    +- [Let's explore another way of using AI:octicons-arrow-right-24:](7-agentic.md) +
    diff --git a/docs/7-agentic.md b/docs/7-agentic.md new file mode 100644 index 0000000..324945e --- /dev/null +++ b/docs/7-agentic.md @@ -0,0 +1,71 @@ +# Agentic AI +--8<-- "snippets/7-agentic.js" + +Agentic AI is a novel technique where AI systems act independently and proactively, aiming to achieve specific goals without direct human intervention. +Integrating these systems into digital services poses challenges due to their non-deterministic communication, which can create unpredictable behavior. +This makes observability essential to monitor and control their interactions. As the number of AI and cloud-native service instances grows, managing these autonomous systems becomes more complex. +Observability serves as a crucial feedback channel to orchestrate and moderate the outcomes of Agentic AI, ensuring effective communication among the agents. + +In our AI Travel Advisor app, we have three different agents: + +- Valid City: verifies that a prompt is asking about a valid city name +- Travel Advice: provides a travel reccomendation +- Excuse: provides an excuse why the AI chat-bot cannot answer the request + +These Agents are orchestrated through LangChain using a complex system prompt. +The orchestrator is the componenet that moves the control- and data-flow between agents. + + + +In this codespace, we're running a small Ollama model. Hence, most of the time the request will terminate with an error or a timeout limit is reached. +Changing the LLM from Ollama to a foundation one, like a Bedrock model, ChatGPT, or Google Gemini will solve these issues. + +![Agentic Example](./img/agentic_example.jpg) + +# Let's try to understand what's happening + +Why do we get an error? Let's analyze the situation with our handy-dandy Distributed Tracing App! + +In the trace, we can see how many more spans are created. This shows the complexity of Agentic AI and why observability is crucial. + +![Agentic Trace](./img/agentic_trace.png) + +In Agentic AI, an LLM decides what's the next Agent to call and with what data. +Tracing becomes crucial to explain why the AI arrived at that conclusion and we can do that by following the [Chain of Thoughts (CoT)](https://www.ibm.com/think/topics/chain-of-thoughts). +For this, we can click over a `ollama.chat` span to have access to the CoT. Analyzing the span details, we can see in the prompts the system prompts used. + +![](./img/agentic_throughts.png) + +The first system prompt contains the tools, expressed in JSON format, that the LLM has access to and the instructions to answering following the format +that LangChain is expecting the AI to answer. +The second system prompt contains the task that the AI has to solve, verify if the city is correct and provide a travel advice. + +We can then see in the completion attributes that the LLM didn't follow correctly the format expected. +Moving to the next span `JSONAgentOutputParser`, we can see that an exception was recorded. +Clicking on the respective tab, we can see that the format used by Ollama doesn't follow JSON making it impossible to parse the response. + +![](./img/agentic_exception.png) + +We can analyze the rest of the trace to check that te orchestrator tried 3 more times before admiting defeat and provide a standard error message back to us. + +# How does it look like when we use a more powerful model? + +Great question! Let's see the same example if we use Google Gemini! + +![Good Agent](./img/agentic_good.png) + +In the trace, we can see that we first call the `valid_city.tool` and afterwards, the `travel_advice.tool`. +Gemini is smart enought to understand the correct sequence of actions. +Furthermore, if we look at the span details, we can see the CoT follow the correct format with the Thought used by the AI and what action should be called. +For the keen eye, we can also see in the trace a last `VertexAI.completion` span after the `travel_advice.tool`. +This is because LangChain asks the AI if we reached the end and we solved the task or if we should continue solving it. + +![](./img/agent_final.png) + +Analyzing the CoT of this step, we can see that Gemini correctly marked the sequence of reasoning with `Final Answer` so the orchestrator knows that it can return the response to the user. + + +
    +- [Cleanup:octicons-arrow-right-24:](cleanup.md) +
    + diff --git a/docs/bedrock.md b/docs/bedrock.md new file mode 100644 index 0000000..2d5ae2d --- /dev/null +++ b/docs/bedrock.md @@ -0,0 +1,79 @@ +--8<-- "snippets/bedrock.js" + + +!!! info "Branch amazon-bedrock" + This usecase can be found in the branch "amazon-bedrock" + +## EasyTravel Bedrock - Travel Advisor + +Demo application for giving travel advice written in Python. Observability signals by [OpenTelemetry](https://opentelemetry.io). + +Uses [Amazon Bedrock](https://aws.amazon.com/bedrock/) to generate advice for a given destination. + +> **Note** +> This product is not officially supported by Dynatrace! + +### Try it yourself + +* Explore our sample dashboards on the [Dynatrace Playground](https://dt-url.net/v203wj2). +* Implement AI observability in your environments with our detailed [Dynatrace Documentation](https://dt-url.net/oi23w9x). + + +## Configure Bedrock + +You can follow the [Amazon Getting Started guide](https://docs.aws.amazon.com/bedrock/latest/userguide/getting-started.html) +to get access to an Amazon Bedrock foundation model, or deploy your own custom model. + + +## Try it out yourself + +[![Open "RAG" version in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new/Dynatrace/obslab-llm-observability?ref=amazon-bedrock) + +## Developer Information Below + +### Run Locally + +You can start the application locally by running the following command. + +```bash +export AWS_EMBEDDING_MODEL= +export AWS_MODEL= +export AWS_GUARDRAIL_ID= +export AWS_DEFAULT_REGION= +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export OTEL_ENDPOINT=https://.live.dynatrace.com/api/v2/otlp +export API_TOKEN= +python app.py +``` + + +-------------------------- + +### Deploy on a Local K8S Cluster + +You will need [Docker](https://docs.docker.com/engine/install/) or [Podman](https://podman.io/docs/installation) installed. + + +Create a cluster if you do not already have one: +```bash +kind create cluster --config .devcontainer/kind-cluster.yml --wait 300s +``` + +Customise and set some environment variables + +```bash +export AWS_EMBEDDING_MODEL= +export AWS_MODEL= +export AWS_GUARDRAIL_ID= +export AWS_DEFAULT_REGION= +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export DT_ENDPOINT=https://.live.dynatrace.com +export DT_TOKEN= +``` + +Run the deployment script: +```bash +.devcontainer/deployment.sh +``` \ No newline at end of file diff --git a/docs/cleanup.md b/docs/cleanup.md index 4db2e9e..e22f649 100644 --- a/docs/cleanup.md +++ b/docs/cleanup.md @@ -1,3 +1,14 @@ -# Cleanup +--8<-- "snippets/cleanup.js" -Go to [https://github.com/codespaces](https://github.com/codespaces){target="_blank"} and delete the codespace which will delete the demo environment. \ No newline at end of file + +!!! tip "Deleting the codespace from inside the container" + We like to make your life easier, for convenience there is a function loaded in the shell of the Codespace for deleting the codespace, just type `deleteCodespace`. This will trigger the deletion of the codespace. + + +Another way to do this is by going to [https://github.com/codespaces](https://github.com/codespaces){target=_blank} and delete the codespace. + +You may also want to deactivate or delete the API token needed for this lab. + +
    +- [Ressources:octicons-arrow-right-24:](resources.md) +
    diff --git a/docs/how-it-works.md b/docs/how-it-works.md deleted file mode 100644 index 28c3633..0000000 --- a/docs/how-it-works.md +++ /dev/null @@ -1,10 +0,0 @@ -The user interacts with the demo app (travel advisor) on port `30100`. The app is monitored either via native OpenTelemetry. - -The user enters a destination (eg. `Sydney`): - -* The application first checks the cache. - * If a response for `Sydney` is found, the response is returned from the cache. - * If a cached response is not available, the application requests advice from the LLM (OpenAI's ChatGPT). -* The response is returned and cached so that subsequent calls for the same destination (eg. `Sydney`) are served from the cache. This saves roundtrips to ChatGPT and thus `$`. - -## [>> Click here to continue with the exercise](standard-rag-differences.md) \ No newline at end of file diff --git a/docs/images/architecture.jpg b/docs/images/architecture.jpg deleted file mode 100644 index 4db6122..0000000 Binary files a/docs/images/architecture.jpg and /dev/null differ diff --git a/docs/images/blank-terminal.png b/docs/images/blank-terminal.png deleted file mode 100644 index e634ba7..0000000 Binary files a/docs/images/blank-terminal.png and /dev/null differ diff --git a/docs/images/cached-request-not-stale.png b/docs/images/cached-request-not-stale.png deleted file mode 100644 index f071b18..0000000 Binary files a/docs/images/cached-request-not-stale.png and /dev/null differ diff --git a/docs/images/dashboard-upload.png b/docs/images/dashboard-upload.png deleted file mode 100644 index 674ae84..0000000 Binary files a/docs/images/dashboard-upload.png and /dev/null differ diff --git a/docs/images/dashboard.png b/docs/images/dashboard.png deleted file mode 100644 index 3394d1f..0000000 Binary files a/docs/images/dashboard.png and /dev/null differ diff --git a/docs/images/distributed-trace-openai-metadata.png b/docs/images/distributed-trace-openai-metadata.png deleted file mode 100644 index 1a880ed..0000000 Binary files a/docs/images/distributed-trace-openai-metadata.png and /dev/null differ diff --git a/docs/images/distributed-trace-weaviate.png b/docs/images/distributed-trace-weaviate.png deleted file mode 100644 index eef2893..0000000 Binary files a/docs/images/distributed-trace-weaviate.png and /dev/null differ diff --git a/docs/images/distributed-trace-with-openai.png b/docs/images/distributed-trace-with-openai.png deleted file mode 100644 index abf90d0..0000000 Binary files a/docs/images/distributed-trace-with-openai.png and /dev/null differ diff --git a/docs/images/ports-open-in-browser.png b/docs/images/ports-open-in-browser.png deleted file mode 100644 index 02ab795..0000000 Binary files a/docs/images/ports-open-in-browser.png and /dev/null differ diff --git a/docs/images/screenshot.png b/docs/images/screenshot.png deleted file mode 100644 index acb3cdf..0000000 Binary files a/docs/images/screenshot.png and /dev/null differ diff --git a/docs/images/success-message.png b/docs/images/success-message.png deleted file mode 100644 index e917c70..0000000 Binary files a/docs/images/success-message.png and /dev/null differ diff --git a/docs/img/_rag_trace.png b/docs/img/_rag_trace.png new file mode 100644 index 0000000..030d393 Binary files /dev/null and b/docs/img/_rag_trace.png differ diff --git a/docs/img/agent_final.png b/docs/img/agent_final.png new file mode 100644 index 0000000..a80a9ba Binary files /dev/null and b/docs/img/agent_final.png differ diff --git a/docs/img/agentic_error.png b/docs/img/agentic_error.png new file mode 100644 index 0000000..5467d59 Binary files /dev/null and b/docs/img/agentic_error.png differ diff --git a/docs/img/agentic_example.jpg b/docs/img/agentic_example.jpg new file mode 100644 index 0000000..8c53ed3 Binary files /dev/null and b/docs/img/agentic_example.jpg differ diff --git a/docs/img/agentic_exception.png b/docs/img/agentic_exception.png new file mode 100644 index 0000000..6de5d8d Binary files /dev/null and b/docs/img/agentic_exception.png differ diff --git a/docs/img/agentic_good.png b/docs/img/agentic_good.png new file mode 100644 index 0000000..02a8cc7 Binary files /dev/null and b/docs/img/agentic_good.png differ diff --git a/docs/img/agentic_throughts.png b/docs/img/agentic_throughts.png new file mode 100644 index 0000000..275a02d Binary files /dev/null and b/docs/img/agentic_throughts.png differ diff --git a/docs/img/agentic_trace.png b/docs/img/agentic_trace.png new file mode 100644 index 0000000..c83e251 Binary files /dev/null and b/docs/img/agentic_trace.png differ diff --git a/docs/img/ai_travel_advisor.jpg b/docs/img/ai_travel_advisor.jpg new file mode 100644 index 0000000..5a91604 Binary files /dev/null and b/docs/img/ai_travel_advisor.jpg differ diff --git a/docs/img/bad_rag.jpg b/docs/img/bad_rag.jpg new file mode 100644 index 0000000..512b675 Binary files /dev/null and b/docs/img/bad_rag.jpg differ diff --git a/docs/img/bedrock_example.webp b/docs/img/bedrock_example.webp new file mode 100644 index 0000000..6099b9e Binary files /dev/null and b/docs/img/bedrock_example.webp differ diff --git a/docs/img/codespaces_finish.png b/docs/img/codespaces_finish.png new file mode 100644 index 0000000..e1984d9 Binary files /dev/null and b/docs/img/codespaces_finish.png differ diff --git a/docs/img/codespaces_installing.png b/docs/img/codespaces_installing.png new file mode 100644 index 0000000..abb8a62 Binary files /dev/null and b/docs/img/codespaces_installing.png differ diff --git a/docs/img/direct_chat.jpg b/docs/img/direct_chat.jpg new file mode 100644 index 0000000..a94ab5c Binary files /dev/null and b/docs/img/direct_chat.jpg differ diff --git a/docs/img/dt_professors.png b/docs/img/dt_professors.png new file mode 100644 index 0000000..3735baf Binary files /dev/null and b/docs/img/dt_professors.png differ diff --git a/docs/img/first_trace.png b/docs/img/first_trace.png new file mode 100644 index 0000000..5b842af Binary files /dev/null and b/docs/img/first_trace.png differ diff --git a/docs/img/good_rag.jpg b/docs/img/good_rag.jpg new file mode 100644 index 0000000..653d1f8 Binary files /dev/null and b/docs/img/good_rag.jpg differ diff --git a/docs/img/good_rag.png b/docs/img/good_rag.png new file mode 100644 index 0000000..586d3ad Binary files /dev/null and b/docs/img/good_rag.png differ diff --git a/docs/img/k9s.png b/docs/img/k9s.png new file mode 100644 index 0000000..6b045d2 Binary files /dev/null and b/docs/img/k9s.png differ diff --git a/docs/img/landing.png b/docs/img/landing.png new file mode 100644 index 0000000..5ef0731 Binary files /dev/null and b/docs/img/landing.png differ diff --git a/docs/img/rag-arch.jpg b/docs/img/rag-arch.jpg new file mode 100644 index 0000000..a417f16 Binary files /dev/null and b/docs/img/rag-arch.jpg differ diff --git a/docs/img/rag_details.png b/docs/img/rag_details.png new file mode 100644 index 0000000..8f2de5b Binary files /dev/null and b/docs/img/rag_details.png differ diff --git a/docs/img/rag_docs.png b/docs/img/rag_docs.png new file mode 100644 index 0000000..3fd21d0 Binary files /dev/null and b/docs/img/rag_docs.png differ diff --git a/docs/img/rag_response.png b/docs/img/rag_response.png new file mode 100644 index 0000000..21d66a3 Binary files /dev/null and b/docs/img/rag_response.png differ diff --git a/docs/img/rag_trace.png b/docs/img/rag_trace.png new file mode 100644 index 0000000..fdf0575 Binary files /dev/null and b/docs/img/rag_trace.png differ diff --git a/docs/img/trace_details.png b/docs/img/trace_details.png new file mode 100644 index 0000000..ff7d07a Binary files /dev/null and b/docs/img/trace_details.png differ diff --git a/docs/index.md b/docs/index.md index 656906e..6ed7624 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,23 +1,23 @@ -# Large Language Model Observability with Dynatrace +--8<-- "snippets/index.js" -Demo application for giving travel advice written in Python. Observability signals by [OpenTelemetry](https://opentelemetry.io){target="_blank"} and [OpenLLMetry](https://www.traceloop.com/docs/openllmetry/introduction){target="_blank"}. +--8<-- "snippets/disclaimer.md" -Uses OpenAI ChatGPT to generate advice for a given destination. +## What's this tutorial all about +In this tutorial we'll learn the basics about AI & LLM Monitoring. -![title](images/screenshot.png) +Dynatrace supports [AI and LLM Observability](https://docs.dynatrace.com/docs/analyze-explore-automate/dynatrace-for-ai-observability#ai-and-llm-observability) +for more than 40 different technologies providing visibility into the different layers of AI and LLM applications. -This hands-on is also available as an [on-demand webinar](https://info.dynatrace.com/apac-all-wb-ensure-ai-project-success-with-ai-observability-24973-registration.html){target="_blank"}. +- Monitor service health and performance: Track real-time metrics (request counts, durations, and error rates). Stay aligned with SLOs. +- Monitor service quality and cost: Implement error budgets for performance and cost control. Validate model consumption and response times. Prevent quality degradation by monitoring models and usage patterns in real time. +- End-to-end tracing and debugging: Trace prompt flows from initial request to final response for quick root cause analysis and troubleshoot. Gain granular visibility into LLM prompt latencies and model-level metrics. Pinpoint issues in prompts, tokens, or system integrations. +- Build trust, reduce compliance and audit risks: Track every input and output for an audit trail. Query all data in real time and store for future reference. Maintain full data lineage from prompt to response. -## Architecture -![architecture](images/architecture.jpg) +!!! tip "What will we do" + In this tutorial we will learn how it is easy to observe an AI application that uses [Ollama](https://ollama.com/) + as Large Language Model, [Weaviate](https://weaviate.io/) as Vector Database, and [LangChain](https://www.langchain.com/) as an orchestrator + to create [Retrieval augmented generation (RAG)](https://python.langchain.com/docs/concepts/rag/) and [Agentic](https://python.langchain.com/docs/concepts/agents/) AI Pipelines. - -## Compatibility - -| Deployment | Tutorial Compatible | -|--------------------|---------------------| -| Dynatrace Managed | ✔️ | -| Dynatrace SaaS | ✔️ | - -## [>> Click here to start the tutorial...](how-it-works.md) \ No newline at end of file +
    +- [Yes! let's begin :octicons-arrow-right-24:](2-getting-started.md) \ No newline at end of file diff --git a/docs/multi-mode.md b/docs/multi-mode.md new file mode 100644 index 0000000..9aabf74 --- /dev/null +++ b/docs/multi-mode.md @@ -0,0 +1,86 @@ +--8<-- "snippets/multi-mode.js" + +!!! info "Branch Multi-Mode" + This usecase can be found in the branch "multi-mode" + + +## EasyTravel GPT Travel Advisor + +Demo application for giving travel advice written in Python. Observability signals by [OpenTelemetry](https://opentelemetry.io). + +Uses [Ollama](https://ollama.com/) and [PineCone](https://www.pinecone.io/) to generate advice for a given destination. + +> **Note** +> This product is not officially supported by Dynatrace! + +### Try it yourself + +* Explore our sample dashboards on the [Dynatrace Playground](https://dynatr.ac/4dnkuLX). +* Implement AI observability in your environments with our detailed [Dynatrace Documentation](https://dynatr.ac/3XKxKEC). + +

    + +[![See a live demo](http://img.youtube.com/vi/eW2KuWFeZyY/0.jpg)](http://www.youtube.com/watch?v=eW2KuWFeZyY) + +

    + +## Configure Pinecone + +Head over to https://app.pinecone.io/ and login into your account. + +1. Create a new index called `travel-advisor` with the dimensions of **3200** and a `cosine` metric. + + The index will store our knowledge source, which the RAG pipeline will use to augment the LLM's output of the travel recommendation. + The parameter 3200 is because for this demo, we are using the embedding model `orca-mini:3b` which returns vector of 3200 elements. + + ![Pinecone Index Creation](https://dt-cdn.net/images/pinecone-index-creation-1061-dab900f5ff.png) + +2. After creating and running the index, we can create an API key to connect. + + Follow the [Pinecone documentation on authentication](https://dt-url.net/ji63ugh) to get the API key to connect to your Pinecone index and store it as Kubernetes secrets with the following command: + +## Try it out yourself + +[![Open "RAG" version in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dynatrace-perfclinics/obslab-llm-observability?ref=ollama-pinecone) + +## Developer Information Below + +### Run Locally + +Start [Ollama](https://github.com/ollama/ollama) locally by running `ollama serve`. +For this example, we'll use a simple model, `orca-mini:3b`. +You can pull it running `ollama run orca-mini:3b`. +Afterwards, you can start the application locally by running the following command. + +```bash +export PINECONE_API_KEY= +export OTEL_ENDPOINT=https://.live.dynatrace.com/api/v2/otlp +export API_TOKEN= +python app.py +``` + + +-------------------------- + +### Deploy on a Local K8S Cluster + +You will need [Docker](https://docs.docker.com/engine/install/) or [Podman](https://podman.io/docs/installation) installed. + + +Create a cluster if you do not already have one: +```bash +kind create cluster --config .devcontainer/kind-cluster.yml --wait 300s +``` + +Customise and set some environment variables + +```bash +export PINECONE_API_KEY= +export DT_ENDPOINT=https://.live.dynatrace.com +export DT_TOKEN= +``` + +Run the deployment script: +```bash +.devcontainer/deployment.sh +``` \ No newline at end of file diff --git a/docs/ollama-pinecone.md b/docs/ollama-pinecone.md new file mode 100644 index 0000000..b20d1c8 --- /dev/null +++ b/docs/ollama-pinecone.md @@ -0,0 +1,86 @@ +--8<-- "snippets/ollama-pinecone.js" + +!!! info "Branch Ollama-Pinecone" + This usecase can be found in the branch "Ollama-Pinecone" + + +## EasyTravel GPT Travel Advisor + +Demo application for giving travel advice written in Python. Observability signals by [OpenTelemetry](https://opentelemetry.io). + +Uses [Ollama](https://ollama.com/) and [PineCone](https://www.pinecone.io/) to generate advice for a given destination. + +> **Note** +> This product is not officially supported by Dynatrace! + +### Try it yourself + +* Explore our sample dashboards on the [Dynatrace Playground](https://dynatr.ac/4dnkuLX). +* Implement AI observability in your environments with our detailed [Dynatrace Documentation](https://dynatr.ac/3XKxKEC). + +

    + +[![See a live demo](http://img.youtube.com/vi/eW2KuWFeZyY/0.jpg)](http://www.youtube.com/watch?v=eW2KuWFeZyY) + +

    + +## Configure Pinecone + +Head over to https://app.pinecone.io/ and login into your account. + +1. Create a new index called `travel-advisor` with the dimensions of **3200** and a `cosine` metric. + + The index will store our knowledge source, which the RAG pipeline will use to augment the LLM's output of the travel recommendation. + The parameter 3200 is because for this demo, we are using the embedding model `orca-mini:3b` which returns vector of 3200 elements. + + ![Pinecone Index Creation](https://dt-cdn.net/images/pinecone-index-creation-1061-dab900f5ff.png) + +2. After creating and running the index, we can create an API key to connect. + + Follow the [Pinecone documentation on authentication](https://dt-url.net/ji63ugh) to get the API key to connect to your Pinecone index and store it as Kubernetes secrets with the following command: + +## Try it out yourself + +[![Open "RAG" version in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dynatrace-perfclinics/obslab-llm-observability?ref=ollama-pinecone) + +## Developer Information Below + +### Run Locally + +Start [Ollama](https://github.com/ollama/ollama) locally by running `ollama serve`. +For this example, we'll use a simple model, `orca-mini:3b`. +You can pull it running `ollama run orca-mini:3b`. +Afterwards, you can start the application locally by running the following command. + +```bash +export PINECONE_API_KEY= +export OTEL_ENDPOINT=https://.live.dynatrace.com/api/v2/otlp +export API_TOKEN= +python app.py +``` + + +-------------------------- + +### Deploy on a Local K8S Cluster + +You will need [Docker](https://docs.docker.com/engine/install/) or [Podman](https://podman.io/docs/installation) installed. + + +Create a cluster if you do not already have one: +```bash +kind create cluster --config .devcontainer/kind-cluster.yml --wait 300s +``` + +Customise and set some environment variables + +```bash +export PINECONE_API_KEY= +export DT_ENDPOINT=https://.live.dynatrace.com +export DT_TOKEN= +``` + +Run the deployment script: +```bash +.devcontainer/deployment.sh +``` \ No newline at end of file diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 0000000..4bc3918 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block libs %} + + {{ super() }} + + +{% endblock %} diff --git a/docs/prerequisites.md b/docs/prerequisites.md deleted file mode 100644 index 2389e19..0000000 --- a/docs/prerequisites.md +++ /dev/null @@ -1,12 +0,0 @@ -To run this demo you will need: - -- A Dynatrace SaaS account ([free trial](https://dynatrace.com/trial){target="_blank"}) -- An OpenAI account with credit added - -## Why is a Paid OpenAI Account Required? - -OpenAI / ChatGPT severely limits the ability for API access if you do not have credit. Adding a small amount of credit ($2-$3) is the best way to make this (and all other ChatGPT demos) run smoothly. - -This demo uses [gpt 4o mini](https://platform.openai.com/docs/models/gpt-4o-mini){target="_blank"}. We have developed, tested and demoed this repository hundreds of times and still have money left from the initial $5 credit load. - -## [>> Click here to continue with the exercise](setup.md) diff --git a/docs/rag.md b/docs/rag.md new file mode 100644 index 0000000..2d0f6c5 --- /dev/null +++ b/docs/rag.md @@ -0,0 +1,182 @@ +--8<-- "snippets/rag.js" + +!!! info "Branch RAG" + This usecase can be found in the branch "RAG" + + +## EasyTravel GPT Travel Advisor + +Demo Retrieval Augmented Generation (RAG) LLM application for giving travel advice. + +Uses OpenAI ChatGPT to generate advice for a given destination. + +To demonstrate that the RAG is answering from the custom data, the RAG is only trained on two destinations: [Bali](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/destinations/bali.html) and [Sydney](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/destinations/sydney.html). + +The content is obviously false, again to demonstrate that the data really is pulling from the custom dataset. + +- The application will use the contextual (training) information we have provided +- If data is available (ie. you have searched for either Bali or Sydney), data will be returned +- A message of "Sorry, I have no data on " will be returned for ALL other destinations + +![tps://raw.githubusercontent.com/user/repo/branch/path/to/image.png + +![traveladvisor app](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/screenshot.png) + +![RAG architecture](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/architecture.jpg) + +## How it works +The user interacts with the demo app (travel advisor) on port `30100`. The app is monitored either via native OpenTelemetry (as per the demo) or (if the user chooses) the OneAgent (eg. the nodeJS version). In both cases, the user enters a destination (eg. Sydney). The application first checks the cache. If a response for Sydney is found, the response is returned from the cache. If a cached response is not available, the application requests advice from the LLM (OpenAI's ChatGPT). + +**The call to the LLM is enriched with the custom destination advice ([loaded from the HTML files](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/destinations)). The LLM is instructed to only provide an answer based on the provided context (ie. the file content)**. + +When the response is returned, it is cached so that subsequent calls for the same destination (eg. Sydney) are served from the cache. This saves roundtrips to ChatGPT and thus $. + +## ⚠️ OpenAI Paid Account Required + +You need an OpenAI account with credit added to run this demo! + +### Create OpenAI API Token + +Go to `https://platform.openai.com/api-keys` and create a new API Key. + +## Format Dynatrace URL + +Make a note of your Dynatrace URL, it should be in the following format: + +``` +https://ENVIRONMENT-ID.live.dynatrace.com +``` + +For example: + +``` +https://abc12345.live.dynatrace.com +``` + +## Create Dynatrace Tokens + +In Dynatrace, press `Ctrl + k` and search for `access tokens`. Choose the first option. + +### DT_OPERATOR_TOKEN + +Create a new access token with the following permissions: + +- Create ActiveGate tokens +- Read entities +- Read Settings +- Write Settings +- Access problem and event feed, metrics, and topology +- Read configuration +- Write configuration +- PaaS integration - installer download + +### DT_API_TOKEN + +Create a second token with these permissions: + +- Ingest metrics +- Ingest logs +- Ingest events +- Ingest OpenTelemetry +- Read metrics + +### DT_WRITE_SETTINGS_TOKEN + +Create a third token with this permission: + +- Write settings + +## 🔁 Recap + +You should now have `5` pieces of information: + +- The `DT_URL` (eg. `https://abc12345.live.dynatrace`) +- The `DT_OPERATOR_TOKEN` +- The `DT_API_TOKEN` +- The `DT_WRITE_SETTINGS_TOKEN` +- The `OPEN_AI_TOKEN` + +When you have these pieces of information, you can proceed. + +## 🆙 Time to Fire it up + +- On the repo page of Github.com, click the green `Code` button and toggle to `Codespaces`. +- Click the "three dots" and choose "New with Options". +- Enter the values you've from above, in the form fields. +- Create the codespace. + +![post creation script](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/.devcontainer/images/create-codespace.png) + +After the codespaces has started (in a new browser tab), the post creation script should begin. This will install everything and will take a few moments. + +When the script has completed, a success message will briefly be displayed (it is so quick you'll probably miss it) and an empty terminal window will be shown. + +![brief success message](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/.devcontainer/images/success-message.png) + +![blank terminal window](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/.devcontainer/images/blank-terminal.png) + +You may now proceed... + +## Accessing and Using Demo + +In the codespace, switch to the `Ports` tab. Right click port `30100` and choose `Open in Browser` + +![ports open in browser](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/.devcontainer/images/ports-open-in-browser.png) + +A new browser tab will open and you should see the demo. + +![application user interface](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/screenshot.png) + +## Visualising Data in Dynatrace + +### Uploading the Dashboards +This demo comes with several prebuilt dashboards. Do the following in Dynatrace. + +- Save the contents of [dynatrace/dashboards/openai/Travel-Advisor-Overview.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/openai/Travel-Advisor-Overview.json) to your computer +- Press `Ctrl + k` and search for `dashboards` or select the icon from the left toolbar +- Select the `Upload` button and upload the JSON file. + +![upload button](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/.devcontainer/images/dashboard-upload.png) + +![dashboard image](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboard.png) + +Repeat this process for the following dashboards: + +- [dynatrace/dashboards/weaviate/Weaviate-Snapshots.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Snapshots.json) +- [dynatrace/dashboards/weaviate/Weaviate-Importing-Data.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Importing-Data.json) +- [dynatrace/dashboards/weaviate/Weaviate-LSM-Store.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-LSM-Store.json) +- [dynatrace/dashboards/weaviate/Weaviate-Object-Operations.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Object-Operations.json) +- [dynatrace/dashboards/weaviate/Weaviate-Query-Performance.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Query-Performance.json) +- [dynatrace/dashboards/weaviate/Weaviate-Usage.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Usage.json) +- [dynatrace/dashboards/weaviate/Weaviate-Schema-Transactions.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Schema-Transactions.json) +- [dynatrace/dashboards/weaviate/Weaviate-Startup-Times.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Startup-Times.json) +- [dynatrace/dashboards/weaviate/Weaviate-Tombstone-Analysis.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Tombstone-Analysis.json) +- [dynatrace/dashboards/weaviate/Weaviate-Vector-Index.json](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/dynatrace/dashboards/weaviate/Weaviate-Vector-Index.json) + +## Run Locally with Weaviate Cache + +- Download the [latest Weaviate binary from GitHub](https://github.com/weaviate/weaviate/releases/latest). Add it to your `PATH`. +- Download the [latest Dynatrace OpenTelemetry collector binary from GitHub](https://github.com/Dynatrace/dynatrace-otel-collector/releases). Add it to your `PATH`. + +``` +##### 1. Start Weaviate + +set PROMETHEUS_MONITORING_ENABLED=true +weaviate --host 0.0.0.0 --port 8000 --scheme http + +##### 2. Configure these variables and Start Collector +##### Token needs: logs.ingest, metrics.ingest and openTelemetryTrace.ingest permissions + +set DT_ENDPOINT=https://abc12345.live.dynatrace.com/api/v2/otlp +set API_TOKEN=dt0c01.******.****** +dynatrace-otel-collector.exe --config ./run-locally/otelcol-config.yaml + +##### Start app +set OPENAI_API_KEY=sk-proj-********** +set WEAVIATE_ENDPOINT=http://localhost:8000 +# Disable usage telemetry that is sent to Traceloop +set TRACELOOP_TELEMETRY=false +python app.py +``` + +![opentelemetry trace](https://raw.githubusercontent.com/Dynatrace/obslab-llm-observability/rag/.devcontainer/images/get-completion-trace.png) \ No newline at end of file diff --git a/docs/requirements/requirements-mkdocs.txt b/docs/requirements/requirements-mkdocs.txt new file mode 100644 index 0000000..0f17a2f --- /dev/null +++ b/docs/requirements/requirements-mkdocs.txt @@ -0,0 +1 @@ +mkdocs-material == 9.5.42 diff --git a/docs/resources.md b/docs/resources.md index 82589e7..3456cb1 100644 --- a/docs/resources.md +++ b/docs/resources.md @@ -1,4 +1,29 @@ -# Resources +--8<-- "snippets/resources.js" -- [This repository and documentation on GitHub](https://github.com/dynatrace-perfclinics/obslab-release-validation){target="_blank"} -- [LLM Observability On-Demand Webinar (Video)](https://info.dynatrace.com/apac-all-wb-ensure-ai-project-success-with-ai-observability-24973-registration.html){target="_blank"} \ No newline at end of file +### Get your Dynatrace environment + +- [Create a Free Trial in Dynatrace](https://www.dynatrace.com/signup/){target="_blank"} + +### Documentation + +- [Dynatrace documentation - AI and LLM Observability](https://docs.dynatrace.com/docs/analyze-explore-automate/dynatrace-for-ai-observability){target="_blank"} +- [Dynatrace AI Observability Solution](https://www.dynatrace.com/solutions/ai-observability/){target="_blank"} +- [Technology supported](https://www.dynatrace.com/hub/?filter=ai-ml-observability){target="_blank"} + +### Dynatrace news +- [Deliver secure, safe, and trustworthy GenAI applications with Amazon Bedrock and Dynatrace](https://www.dynatrace.com/news/blog/deliver-secure-safe-and-trustworthy-genai-applications-with-amazon-bedrock-and-dynatrace/){target="_blank"} +- [Dynatrace accelerates business transformation with new AI observability solution](https://www.dynatrace.com/news/blog/dynatrace-accelerates-business-transformation-with-new-ai-observability-solution/){target="_blank"} +- [Why 85% of AI projects fail and how Dynatrace can save yours](https://www.dynatrace.com/news/blog/why-ai-projects-fail/){target="_blank"} +- [Dynatrace Blog](https://www.dynatrace.com/news/blog/){target="_blank"} + +### Miscellaneous + +- [App/Ready-Made dashboards from the Playground that can be installed to all DPS/Saas instances](https://wkf10640.apps.dynatrace.com/ui/apps/dynatrace.hub/browse/ai-llm-observability?details=dynatrace.genai.observability&detailsTab=contents){target="_blank"} + +
    +- [What's Next? :octicons-arrow-right-24:](whats-next.md) +
    diff --git a/docs/setup.md b/docs/setup.md deleted file mode 100644 index f999928..0000000 --- a/docs/setup.md +++ /dev/null @@ -1,48 +0,0 @@ -## Create OpenAI API Token - -Go to [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys){target="_blank"} and create a new API Key. - -## Format Dynatrace URL - -Make a note of your Dynatrace URL, it should be in the following format: - -``` -https://ENVIRONMENT-ID.live.dynatrace.com -``` - -For example: - -``` -https://abc12345.live.dynatrace.com -``` - -## Create Dynatrace Token - -In Dynatrace, press `Ctrl + k` and search for `access tokens`. Choose the first option. - -### DT_API_TOKEN - -Create an API token with these permissions: - -- Ingest metrics (`metrics.ingest`) -- Ingest logs (`logs.ingest`) -- Ingest events (`events.ingest`) -- Ingest OpenTelemetry traces (`openTelemetryTrace.ingest`) -- Read metrics (`metrics.read`) -- Write settings (`settings.write`) - -This token will be used by the OpenTelemetry collector and k6 to send data to Dynatrace. -The setup script which runs automatically when the codespace is created also uses this to [configure span attribute capture rules in Dynatrace](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/82cedeac2bbe2a6e59c5a94f8a798ff81e204660/.devcontainer/deployment.sh#L5){target="_blank"} -this means the relevant OpenTelemetry span attributes will automatically be stored. - -## 🔁 Recap - -You should now have `3` pieces of information: - -- The `DT_ENDPOINT` (eg. `https://abc12345.live.dynatrace`) -- The `DT_API_TOKEN` -- The `OPEN_AI_TOKEN` - -When you have these pieces of information, you can proceed. - -## [>> Click here to continue with the exercise](startup.md) \ No newline at end of file diff --git a/docs/snippets/2-getting-started.js b/docs/snippets/2-getting-started.js new file mode 100644 index 0000000..3da4471 --- /dev/null +++ b/docs/snippets/2-getting-started.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/3-codespaces.js b/docs/snippets/3-codespaces.js new file mode 100644 index 0000000..3bf39d4 --- /dev/null +++ b/docs/snippets/3-codespaces.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/4-content.js b/docs/snippets/4-content.js new file mode 100644 index 0000000..0e58d43 --- /dev/null +++ b/docs/snippets/4-content.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/5-direct.js b/docs/snippets/5-direct.js new file mode 100644 index 0000000..a85c96c --- /dev/null +++ b/docs/snippets/5-direct.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/6-rag.js b/docs/snippets/6-rag.js new file mode 100644 index 0000000..83ae23b --- /dev/null +++ b/docs/snippets/6-rag.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/7-agentic.js b/docs/snippets/7-agentic.js new file mode 100644 index 0000000..9839160 --- /dev/null +++ b/docs/snippets/7-agentic.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/admonitions.md b/docs/snippets/admonitions.md new file mode 100644 index 0000000..d5abe3c --- /dev/null +++ b/docs/snippets/admonitions.md @@ -0,0 +1,20 @@ +!!! warning "Warning" + This is a Warning + +--- + +!!! note "Note" + This is a Note + +--- + + +!!! important "Important" + This is important + +--- + +!!! tip "Tipp" + This is a tipp + + diff --git a/docs/snippets/bedrock.js b/docs/snippets/bedrock.js new file mode 100644 index 0000000..2568368 --- /dev/null +++ b/docs/snippets/bedrock.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/cleanup.js b/docs/snippets/cleanup.js new file mode 100644 index 0000000..d5ec804 --- /dev/null +++ b/docs/snippets/cleanup.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/disclaimer.md b/docs/snippets/disclaimer.md new file mode 100644 index 0000000..69ef1cf --- /dev/null +++ b/docs/snippets/disclaimer.md @@ -0,0 +1,4 @@ +!!! warning "Support Policy" + This is an enablement project created by the Center of Excellence - Enablement Team at Dynatrace. + + **Support is provided via GitHub issues only**. The materials provided in this repository are offered "as-is" without any warranties, express or implied. Use them at your own risk. diff --git a/docs/snippets/dt-enablement.md b/docs/snippets/dt-enablement.md new file mode 100644 index 0000000..35af842 --- /dev/null +++ b/docs/snippets/dt-enablement.md @@ -0,0 +1,10 @@ +??? example "[![dt-badge](https://img.shields.io/badge/powered_by-DT_enablement-8A2BE2?logo=dynatrace)](https://dynatrace-wwse.github.io/codespaces-framework)" + This Codespace leverages the Dynatrace Enablement Framework, providing a robust and flexible development environment. Key features include: + + - Seamless operation within GitHub Codespaces, as a remote container, or locally via Docker. + - Cross-compilation support for both AMD and ARM architectures, ensuring broad compatibility. + - Adherence to industry standards and best practices to optimize the developer experience. + - Real-time observability of Kubernetes clusters using [Dynatrace Full-Stack monitoring](https://dynatrace-wwse.github.io/codespaces-framework/dynatrace-integration/). + - Integrated [Dynatrace MCP Server](https://dynatrace-wwse.github.io/codespaces-framework/dynatrace-integration/) to deliver deep, actionable insights across distributed systems. + + To learn more about the Dynatrace Enablement Framework and how it can enhance your development workflow, please refer to the [official documentation](https://dynatrace-wwse.github.io/codespaces-framework) diff --git a/docs/snippets/e2e-sample.sh b/docs/snippets/e2e-sample.sh new file mode 100644 index 0000000..691acf6 --- /dev/null +++ b/docs/snippets/e2e-sample.sh @@ -0,0 +1,12 @@ +# --8<-- [start:shebang] +#!/bin/bash +# --8<-- [end:shebang] + +# --8<-- [start:SayFirstHello] +echo "Hello world 1" +ls -al +# --8<-- [end:SayFirstHello] + +# --8<-- [start:SaySecondHello] +echo "Hello world 2" +# --8<-- [end:SaySecondHello] diff --git a/docs/snippets/feedback.md b/docs/snippets/feedback.md new file mode 100644 index 0000000..0e1ce34 --- /dev/null +++ b/docs/snippets/feedback.md @@ -0,0 +1,9 @@ +!!! example "Help shape our next content — we’d love your feedback 📣" + We're always working to improve and make our content more useful and relevant to you. If you have a few minutes, we’d really appreciate your input: + + - Take 4 minutes to complete our feedback form + - Or [open an issue](https://github.com/dynatrace-wwse/codespaces-framework/issues) to share suggestions, topics you'd like us to cover, or examples you'd find helpful. + + Your feedback directly shapes what we build next. If there's strong interest in a topic, we’ll prioritize detailed guides, practical examples, and hands-on walkthroughs. + + Thank you for helping us create better enablement resources for everyone ❤️ diff --git a/docs/snippets/grail-requirements.md b/docs/snippets/grail-requirements.md new file mode 100644 index 0000000..16bc352 --- /dev/null +++ b/docs/snippets/grail-requirements.md @@ -0,0 +1,3 @@ +!!! info "Requirements" + - A **Grail enabled Dynatrace SaaS Tenant** ([sign up here](https://dt-url.net/trial){target="_blank"}). + - A **GitHub account** to interact with the demo repository. diff --git a/docs/snippets/index.js b/docs/snippets/index.js new file mode 100644 index 0000000..c792745 --- /dev/null +++ b/docs/snippets/index.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/multi-mode.js b/docs/snippets/multi-mode.js new file mode 100644 index 0000000..e357780 --- /dev/null +++ b/docs/snippets/multi-mode.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/ollama-pinecone.js b/docs/snippets/ollama-pinecone.js new file mode 100644 index 0000000..f35a1c3 --- /dev/null +++ b/docs/snippets/ollama-pinecone.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/rag.js b/docs/snippets/rag.js new file mode 100644 index 0000000..fdbadef --- /dev/null +++ b/docs/snippets/rag.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/resources.js b/docs/snippets/resources.js new file mode 100644 index 0000000..10722df --- /dev/null +++ b/docs/snippets/resources.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/snippets/view-code.md b/docs/snippets/view-code.md new file mode 100644 index 0000000..275b5b6 --- /dev/null +++ b/docs/snippets/view-code.md @@ -0,0 +1,2 @@ +!!! tip "View the Code" + The code for this repository is hosted on GitHub. Click the "View Code on GitHub" link above. \ No newline at end of file diff --git a/docs/snippets/whats-next.js b/docs/snippets/whats-next.js new file mode 100644 index 0000000..f6d62ad --- /dev/null +++ b/docs/snippets/whats-next.js @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/docs/standard-rag-differences.md b/docs/standard-rag-differences.md deleted file mode 100644 index f72e81c..0000000 --- a/docs/standard-rag-differences.md +++ /dev/null @@ -1,14 +0,0 @@ -This demo is available in two flavours. - -The "standard" demo uses OpenAI's ChatGPT (coupled with an on-cluster Weaviate cache) to look up destination advice for any destination. - -The "RAG" version (available [on the rag branch](https://github.com/dynatrace-perfclinics/obslab-llm-observability/tree/rag){target="_blank"}) will **only** produce destination advice for places the system has explicitly been trained on (the files in the [destinations folder on the `rag` branch](https://github.com/dynatrace-perfclinics/obslab-llm-observability/tree/rag/destinations){target="_blank"}). Namely, `Bali` and `Sydney`. - -This is achieved by: - -* [Reading each file from disk when the app starts](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/a893c0e8e93b29a0ca1b5482cb0589f9bce0b4cc/app.py#L79){target="_blank"} -* Sending the contents of the [bali and sydney HTML pages](https://github.com/dynatrace-perfclinics/obslab-llm-observability/tree/rag/destinations){target="_blank"} along with each request and [explicitly telling the model to only use the information provided in those documents](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/a893c0e8e93b29a0ca1b5482cb0589f9bce0b4cc/app.py#L100){target="_blank"}. - -The RAG version of the demo mimicks training an LLM on an internal knowledgebase. - -## [>> Click here to continue with the exercise](prerequisites.md) \ No newline at end of file diff --git a/docs/startup.md b/docs/startup.md deleted file mode 100644 index bcd1d45..0000000 --- a/docs/startup.md +++ /dev/null @@ -1,26 +0,0 @@ -## 🆙 Time to Fire it up - -Choose one of the following options to start the codespace: - - -### Launch Standard Version - -[![Open "standard" version in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dynatrace-perfclinics/obslab-llm-observability?ref=main){target="_blank"} - -### Launch RAG Version - -[![Open "RAG" version in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dynatrace-perfclinics/obslab-llm-observability?ref=rag){target="_blank"} - -Leave the top section blank and provide your values in the `Recommended secrets` form. - -After the codespaces has started (in a new browser tab), the post creation script should begin. This will install everything and will take a few moments. - -When the script has completed, a success message will briefly be displayed (it is so quick you'll probably miss it) and an empty terminal window will be shown. - -![brief success message](images/success-message.png) - -![blank terminal window](images/blank-terminal.png) TODO: Needs new image - -You may now proceed. - -## [>> Click here to continue with the exercise](use-demo.md) \ No newline at end of file diff --git a/docs/use-demo.md b/docs/use-demo.md deleted file mode 100644 index 8de8d4f..0000000 --- a/docs/use-demo.md +++ /dev/null @@ -1,29 +0,0 @@ -## Accessing and Using Demo - -In the codespace, switch to the `Ports` tab. Right click port `30100` and choose `Open in Browser` - -![ports open in browser](images/ports-open-in-browser.png) - -A new browser tab will open and you should see the demo. - -![application user interface](images/screenshot.png) - -## Using LLM-based Destination Search - -Type the name of a destination (eg. `Vienna`) into the search bar and click the `Advise` button. - -### What Happens Next? - -- The application will request information for your destination from OpenAI using ChatGPT 4o mini. -- A result will be returned from OpenAI -- The result is cached in the weviate vector cache - -If you search for `Vienna` again, this time, the re1sult will be served from the cache - saving you the roundtrip (time and $) to OpenAI / ChatGPT. - -## Customer Feedback - -Click the 👍 and / or 👎 buttons to indicate your satisfaction level of the result. - -Clicking these icons will [log a message](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/905b38cf85adaafd87f83ff1f40c640206abdb82/app.py#L151){target="_blank"}. This log line is then retrieved and processed using DQL in the "User Sentiment Analysis" section of the dashboard. - -## [>> Click here to continue with the exercise](visualise-dt.md) \ No newline at end of file diff --git a/docs/usecases.md b/docs/usecases.md new file mode 100644 index 0000000..4c1b940 --- /dev/null +++ b/docs/usecases.md @@ -0,0 +1,9 @@ +--8<-- "snippets/usecases.js" + +Other UseCases + + +
    +- [Cleanup:octicons-arrow-right-24:](cleanup.md) +
    + diff --git a/docs/visualise-dt.md b/docs/visualise-dt.md deleted file mode 100644 index 4ad3a87..0000000 --- a/docs/visualise-dt.md +++ /dev/null @@ -1,54 +0,0 @@ -# Visualising Data in Dynatrace - -## Uploading the Dashboards -This demo comes with several prebuilt dashboards. Do the following in Dynatrace. - -- Save the contents of [dynatrace/dashboards/openai/Travel-Advisor-Overview.json](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/main/dynatrace/dashboards/openai/Travel-Advisor-Overview.json){target="_blank"} to your computer -- Press `Ctrl + k` and search for `dashboards` or select the icon from the left toolbar -- Select the `Upload` button and upload the JSON file. - -![upload button](images/dashboard-upload.png) - -![dashboard image](images/dashboard.png) - -Repeat this process for all the dashboards inside [dynatrace/dashboards/*](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/main/dynatrace/dashboards){target="_blank"} - -## Distributed Traces - -The application emits distributed traces which can be viewed in Dynatrace: - -* Press `ctrl + k` search for `distributed traces` -* Traces for `/api/v1/completion` are created for each call to either OpenAI or a call to the Weaviate cache. - -Remember that only the very first requests for a given destination will go out to OpenAI. So expect many many more cached traces than "live" traces. - -### Trace with OpenAI - -A "full" call to OpenAI looks like this. Notice the long call halfway through the trace to `openai.chat`. These traces take much longer (3 seconds vs. 500ms). - -![distributed trace calling OpenAI](images/distributed-trace-with-openai.png) - -![distributed trace metadata](images/distributed-trace-openai-metadata.png) - -### Trace to Weaviate Cache - -A call which instead only hits the on-cluster [Weaviate](https://github.com/weaviate/weaviate){target="_blank"} cache looks like this. - -Notice that it is much quicker. - -The [response TTL](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/905b38cf85adaafd87f83ff1f40c640206abdb82/app.py#L29){target="_blank"} (max time that a cached prompt is considered "fresh") is [checked and if the response is "still fresh"](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/905b38cf85adaafd87f83ff1f40c640206abdb82/app.py#L119){target="_blank"} (ie. `TTL < stale time`) the cached value is returned. - -![distributed trace returning from Weaviate](images/distributed-trace-weaviate.png) - -Notice the cached prompt is `123s`. [The max age (TTL](https://github.com/dynatrace-perfclinics/obslab-llm-observability/blob/905b38cf85adaafd87f83ff1f40c640206abdb82/app.py#L29){target="_blank"} is (by default) `60` minutes. Therefore the prompt is not outdated and thus returned to the user as valid. - -![cached request not stale](images/cached-request-not-stale.png) - -## 🎉 Demo Complete - -The demo is now complete. Continue to cleanup your environment. - -## [>> Cleanup resources to avoid GitHub charges](cleanup.md) - - - diff --git a/docs/whats-next.md b/docs/whats-next.md index 9cda8c7..ae25c8c 100644 --- a/docs/whats-next.md +++ b/docs/whats-next.md @@ -1,5 +1,22 @@ -# What's Next? +--8<-- "snippets/whats-next.js" -Head to the resources page to see other LLM Observability content. +--8<-- "snippets/feedback.md" -## [Resources](resources.md) \ No newline at end of file +!!! tip "More to come" + - Stay tuned, more enablements are coming whith more AI technologies, models, etc. + +In this example, we only touched the surface of AI and LLM Observability. +Different AI Vendors have different key signals and additional enterprise features. + +For example, Bedorock supports Guardrails - a mechanism to protect AI system from Toxic language, PII leaks or talking about forbiden topics, or OpenAI provides Prompt Caching to save on cost and performance. + +To showcase the additional information, we built several dashboard that are shipped with the [AI Observability app](https://www.dynatrace.com/hub/detail/ai-and-llm-observability/?filter=ai-ml-observability). + +The app is available in the [Playground Tenant](https://wkf10640.apps.dynatrace.com/ui/apps/dynatrace.hub/browse/ai-llm-observability?details=dynatrace.genai.observability&detailsTab=contents) and provides examples for: + +- Amazon Bedrock +- Azure AI Foundry +- Google Vertex and Gemini +- OpenAI + +![](./img/bedrock_example.webp) \ No newline at end of file diff --git a/dynatrace/dashboard.png b/dynatrace/dashboard.png deleted file mode 100644 index 3394d1f..0000000 Binary files a/dynatrace/dashboard.png and /dev/null differ diff --git a/dynatrace/dashboards/k6/Grafana k6 Dashboard.json b/dynatrace/dashboards/k6/Grafana k6 Dashboard.json deleted file mode 100644 index 9b81bc3..0000000 --- a/dynatrace/dashboards/k6/Grafana k6 Dashboard.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"data","title":"Average VUs","query":"timeseries avg(k6.vus)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.vus","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.vus)"],"leftAxisDimensions":[]}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto"},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]}}},"1":{"type":"data","title":"Max VUs","query":"timeseries max(k6.vus_max)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.vus_max","aggregation":"max","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["max(k6.vus_max)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"dt.system.bucket","valueAxisLabel":"interval"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","recordField":"value","autoscale":true,"alignment":"center","colorThresholdTarget":"value","trend":{"isRelative":false},"sparklineSettings":{"isVisible":false}},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]}}},"2":{"type":"data","title":"Iterations","query":"timeseries sum(k6.iterations)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.iterations","aggregation":"sum","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["sum(k6.iterations)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"scenario","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"scenario","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]}}},"3":{"type":"markdown","title":"","content":"## Virtual User (VU) Stats"},"4":{"type":"data","title":"Data sent","query":"timeseries sum(k6.data_sent)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.data_sent","aggregation":"sum","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["sum(k6.data_sent)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"scenario","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"scenario","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[]}},"5":{"type":"data","title":"Data Received","query":"timeseries sum(k6.data_received)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.data_received","aggregation":"sum","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["sum(k6.data_received)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"scenario","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"scenario","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":null,"unitCategory":"data","baseUnit":"megabyte","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717633878077}]}},"6":{"type":"markdown","title":"","content":"## Data Statistics"},"7":{"type":"markdown","title":"","content":"## HTTP Request Stats"},"8":{"type":"data","title":"Request count (by URL, Status Code and Method)","query":"timeseries sum(k6.http_reqs), by:{url, status, method}\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_reqs","aggregation":"sum","by":["url","status","method"]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["sum(k6.http_reqs)"],"leftAxisDimensions":["url","status","method"]},"categoricalBarChartSettings":{"categoryAxis":"url","valueAxis":"interval","categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{"category":"url","value":"status"}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":null,"unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634621203}]}},"9":{"type":"data","title":"Failed request count (by URL, Status Code and Method)","query":"timeseries sum(k6.http_req_failed), by:{url, method}\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_failed","aggregation":"sum","by":["url","method"]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["sum(k6.http_req_failed)"],"leftAxisDimensions":["url","method"]},"categoricalBarChartSettings":{"categoryAxis":"url","valueAxis":"interval","categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{"category":"url","value":"method"}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_failed)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634555997}]}},"10":{"type":"data","title":"Blocked request count","query":"timeseries avg(k6.http_req_blocked)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_blocked","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_blocked)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_blocked)","unitCategory":"time","baseUnit":"second","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634644404}]}},"11":{"type":"data","title":"Request sending time","query":"timeseries avg(k6.http_req_sending)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_sending","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_sending)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_sending)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634357619}]}},"12":{"type":"data","title":"Request waiting time","query":"timeseries avg(k6.http_req_waiting)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_waiting","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_waiting)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_waiting)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634357619}]}},"13":{"type":"data","title":"Request receiving time (ms)","query":"timeseries avg(k6.http_req_receiving)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_receiving","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_receiving)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[]}},"14":{"type":"data","title":"Iteration Duration","query":"timeseries avg(k6.iteration_duration)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.iteration_duration","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.iteration_duration)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"scenario","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"scenario","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.iteration_duration)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717635093695}]}},"15":{"type":"data","title":"Request Duration","query":"timeseries avg(k6.http_req_duration)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_duration","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_duration)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_duration)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634621203}]}},"16":{"type":"data","title":"Request connecting","query":"timeseries avg(k6.http_req_connecting)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_connecting","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_connecting)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_connecting)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717635368106}]}},"17":{"type":"data","title":"Request TLS Handshaking","query":"timeseries avg(k6.http_req_tls_handshaking)\n| limit 20","queryConfig":{"limit":20,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_tls_handshaking","aggregation":"avg","by":[]},"subType":"dql-builder-metrics","visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_tls_handshaking)"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[]}},"18":{"type":"markdown","title":"","content":"![k6 logo](https://raw.githubusercontent.com/grafana/k6/master/assets/logo.svg)"},"19":{"type":"data","title":"Max VUs","query":"timeseries max(k6.vus_max)\n| fieldsAdd value = arrayMax(`max(k6.vus_max)`)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.vus_max","aggregation":"max","by":[],"convertToValue":"Max"},"subType":"dql-builder-metrics","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["max(k6.vus_max)"],"leftAxisDimensions":["value"]},"categoricalBarChartSettings":{"categoryAxisLabel":"dt.system.bucket","valueAxisLabel":"interval"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","recordField":"value","autoscale":true,"alignment":"center","colorThresholdTarget":"value","trend":{"isRelative":false},"sparklineSettings":{"isVisible":false}},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""},{"valueAxis":"value","rangeAxis":""}]}}},"20":{"type":"markdown","title":"","content":"## Quick Glance"},"21":{"type":"data","title":"Average VUs","query":"timeseries avg(k6.vus)\n| fieldsAdd value = arrayAvg(`avg(k6.vus)`)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.vus","aggregation":"avg","by":[],"convertToValue":"Avg"},"subType":"dql-builder-metrics","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.vus)"],"leftAxisDimensions":["value"]},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value","recordField":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""},{"valueAxis":"value","rangeAxis":""}]}}},"22":{"type":"data","title":"Min Request Duration","query":"timeseries min(k6.http_req_duration)\n| fieldsAdd value = arrayMin(`min(k6.http_req_duration)`)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_duration","aggregation":"min","by":[],"convertToValue":"Min"},"subType":"dql-builder-metrics","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["min(k6.http_req_duration)"],"leftAxisDimensions":["value"]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","recordField":"value","autoscale":true,"alignment":"center","colorThresholdTarget":"value","trend":{"isVisible":true},"sparklineSettings":{"isVisible":true}},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""},{"valueAxis":"value","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_duration)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634621203}]}},"23":{"type":"data","title":"Avg Request Duration","query":"timeseries avg(k6.http_req_duration)\n| fieldsAdd value = arrayAvg(`avg(k6.http_req_duration)`)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_duration","aggregation":"avg","by":[],"convertToValue":"Avg"},"subType":"dql-builder-metrics","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_duration)"],"leftAxisDimensions":["value"]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","recordField":"value","autoscale":true,"alignment":"center","colorThresholdTarget":"value","trend":{"isVisible":true},"sparklineSettings":{"isVisible":true}},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""},{"valueAxis":"value","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_duration)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634621203}]}},"24":{"type":"data","title":"Max Request Duration","query":"timeseries max(k6.http_req_duration)\n| fieldsAdd value = arrayMax(`max(k6.http_req_duration)`)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_duration","aggregation":"max","by":[],"convertToValue":"Max"},"subType":"dql-builder-metrics","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["max(k6.http_req_duration)"],"leftAxisDimensions":["value"]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","recordField":"value","autoscale":true,"alignment":"center","colorThresholdTarget":"value","trend":{"isVisible":true},"sparklineSettings":{"isVisible":true}},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""},{"valueAxis":"value","rangeAxis":""}]},"unitsOverrides":[{"identifier":"value","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717651836821}]}},"25":{"type":"data","title":"Failed request count","query":"timeseries avg(k6.http_req_failed)\n| fieldsAdd value = arraySum(`avg(k6.http_req_failed)`)","queryConfig":{"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_req_failed","aggregation":"avg","by":[],"convertToValue":"Sum"},"subType":"dql-builder-metrics","visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["avg(k6.http_req_failed)"],"leftAxisDimensions":["value"]},"categoricalBarChartSettings":{"categoryAxisLabel":"url","valueAxisLabel":"interval"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","recordField":"value","autoscale":true,"alignment":"center","colorThresholdTarget":"value","trend":{"isVisible":true},"sparklineSettings":{"isVisible":true}},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""},{"valueAxis":"value","rangeAxis":""}]},"unitsOverrides":[{"identifier":"avg(k6.http_req_failed)","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"","delimiter":false,"added":1717634555997}]}},"26":{"type":"data","title":"Top 10 Endpoints (by URL, Status Code and Method)","query":"timeseries sum(k6.http_reqs), by:{url, status, method}\n| fieldsAdd value = arraySum(`sum(k6.http_reqs)`)\n| limit 10","queryConfig":{"limit":10,"filters":{},"version":"5.4.0","datatype":"metrics","metricKey":"k6.http_reqs","aggregation":"sum","by":["url","status","method"],"convertToValue":"Sum"},"subType":"dql-builder-metrics","visualization":"table","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"hiddenLegendFields":[],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["sum(k6.http_reqs)"],"leftAxisDimensions":["url","status","method","value"]},"categoricalBarChartSettings":{"categoryAxis":"url","valueAxis":"value","categoryAxisLabel":"url","valueAxisLabel":"value"}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"url","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[["interval"],["timeframe"],["sum(k6.http_reqs)"]],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{"category":"url","value":"status"}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""},{"valueAxis":"value","rangeAxis":""}]},"unitsOverrides":[{"identifier":"value","unitCategory":"time","baseUnit":"millisecond","displayUnit":null,"decimals":2,"suffix":"requests","delimiter":false,"added":1717634621203}]}},"27":{"type":"data","title":"","query":"timeseries {\n {failures = sum(k6.http_req_failed, default: 10)},\n {all = sum(k6.http_reqs, default: 100)}\n }\n| fieldsAdd result = 100 * (failures[] / all[])\n//| filter arrayAvg(result) > 0\n| sort arrayAvg(result) desc","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["failures","all","result"],"leftAxisDimensions":[]},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"result","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":"auto","dataMappings":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]}}}},"layouts":{"0":{"x":0,"y":7,"w":12,"h":5},"1":{"x":12,"y":7,"w":12,"h":5},"2":{"x":0,"y":12,"w":12,"h":5},"3":{"x":0,"y":6,"w":24,"h":1},"4":{"x":0,"y":19,"w":12,"h":5},"5":{"x":12,"y":19,"w":12,"h":5},"6":{"x":0,"y":17,"w":15,"h":1},"7":{"x":0,"y":18,"w":17,"h":1},"8":{"x":0,"y":24,"w":24,"h":4},"9":{"x":0,"y":31,"w":12,"h":4},"10":{"x":12,"y":31,"w":12,"h":4},"11":{"x":0,"y":35,"w":12,"h":4},"12":{"x":12,"y":35,"w":12,"h":4},"13":{"x":0,"y":39,"w":12,"h":4},"14":{"x":12,"y":12,"w":12,"h":5},"15":{"x":0,"y":28,"w":24,"h":3},"16":{"x":0,"y":43,"w":12,"h":4},"17":{"x":12,"y":39,"w":12,"h":4},"18":{"x":1,"y":0,"w":5,"h":4},"19":{"x":8,"y":1,"w":2,"h":2},"20":{"x":6,"y":0,"w":17,"h":1},"21":{"x":6,"y":1,"w":2,"h":2},"22":{"x":14,"y":1,"w":3,"h":2},"23":{"x":17,"y":1,"w":3,"h":2},"24":{"x":20,"y":1,"w":3,"h":2},"25":{"x":10,"y":1,"w":4,"h":2},"26":{"x":6,"y":3,"w":17,"h":3},"27":{"x":0,"y":47,"w":8,"h":6}},"importedWithCode":false} \ No newline at end of file diff --git a/dynatrace/dashboards/openai/Travel-Advisor-Overview.json b/dynatrace/dashboards/openai/Travel-Advisor-Overview.json deleted file mode 100644 index 943e8b4..0000000 --- a/dynatrace/dashboards/openai/Travel-Advisor-Overview.json +++ /dev/null @@ -1 +0,0 @@ -{"version":15,"variables":[{"key":"KubernetesMode","type":"query","visible":true,"input":"fetch logs\n| filter isNotNull(k8s.namespace.name)\n| fields k8s.namespace.name\n| dedup k8s.namespace.name\n| append [data record(entity.name=\"\")]","multiple":false,"defaultValue":""},{"key":"GPTVersion","type":"csv","visible":true,"input":"gpt-3.5-turbo,gpt-4o,gpt-4o-mini","multiple":false,"defaultValue":"gpt-4o-mini"}],"tiles":{"0":{"type":"data","title":"Total Search Count","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, $GPTVersion) or matchesPhrase(content, \"text-embedding-ada-002\")\n| makeTimeseries count()","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"legend":{"hidden":true},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["count()"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"interval"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"1":{"type":"data","title":"Total","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, $GPTVersion) or matchesPhrase(content, \"text-embedding-ada-002\")\n| summarize count()","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"count()"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"count()"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"count()","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"2":{"type":"data","title":"Cache Hit Rate","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| summarize genCount = toDouble(countIf(matchesPhrase(content, $GPTVersion))),\n vecEmbeddingCount = toDouble(countIf(matchesPhrase(content, \"text-embedding-ada-002\")))\n| fieldsAdd totalCount = genCount + vecEmbeddingCount\n| fieldsAdd cacheHitRate = vecEmbeddingCount / totalCount * 100\n| fieldsKeep cacheHitRate","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"cacheHitRate"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"cacheHitRate"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"unitsOverrides":[{"identifier":"cacheHitRate","unitCategory":"percentage","baseUnit":"percent","displayUnit":null,"decimals":0,"suffix":"","delimiter":false,"added":1702472811365}],"histogram":{"dataMappings":[{"valueAxis":"cacheHitRate","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"3":{"type":"markdown","title":"","content":"## OpenAI Requests"},"4":{"type":"data","title":"Generations","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, $GPTVersion) \n| summarize count()","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"served from OpenAI","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"count()"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"count()"},"legend":"auto","displayedFields":[null],"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"count()","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"5":{"type":"data","title":"Vectors","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, \"text-embedding-ada-002\")\n| summarize count()","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"served from cache","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"count()"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"count()"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"count()","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"6":{"type":"markdown","title":"","content":"### Semantic Cache Quick View\nSee also dedicated Weaviate dashboards for deeper statistics"},"7":{"type":"markdown","title":"","content":"## OpenAI Token Costs"},"8":{"type":"data","title":"Prompt Token Count","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, $GPTVersion)\n| parse content, \"LD 'prompt_tokens=' INT:promptTokens\"\n| summarize promptTokensCount = sum(promptTokens)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{"categoryAxisLabel":"log.file.name","valueAxisLabel":"promptTokens"},"hiddenLegendFields":[]},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"promptTokensCount"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{},"sortBy":{"columnId":"[\"timestamp\"]","direction":"descending"}},"honeycomb":{"shape":"square","dataMappings":{"value":"promptTokensCount"},"legend":"auto","displayedFields":[null],"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"promptTokensCount","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"9":{"type":"data","title":"Completion Token Count","query":"fetch logs\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, $GPTVersion) \n//| parse content, \"LD 'prompt_tokens=' INT:promptTokens\"\n| parse content, \"LD 'completion_tokens=' INT:completionTokens\"\n//| parse content, \"LD 'model=' STRING:llmModel\"\n| summarize completionTokensCount = sum(completionTokens)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{"categoryAxisLabel":"content","valueAxisLabel":"promptTokens"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"completionTokensCount"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"completionTokensCount"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"completionTokensCount","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"11":{"type":"data","title":"Costs calc by DQL","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, $GPTVersion) \n| parse content, \"LD 'prompt_tokens=' INT:promptTokens\"\n| parse content, \"LD 'completion_tokens=' INT:completionTokens\"\n//| parse content, \"LD 'model=' STRING:llmModel\"\n| summarize cost = (sum(promptTokens) / 1000.0 * 0.0010) + (sum(completionTokens) / 1000.0 * 0.0020) ","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{"categoryAxisLabel":"content","valueAxisLabel":"promptTokens"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"cost"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"cost"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"unitsOverrides":[{"identifier":"cost","unitCategory":"currency","baseUnit":"usd","displayUnit":null,"decimals":4,"suffix":"","delimiter":false,"added":1702478013644}],"histogram":{"dataMappings":[{"valueAxis":"cost","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"13":{"type":"data","title":"Cache Saving","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| parse content, \"LD 'prompt_tokens=' INT:promptTokens\"\n| parse content, \"LD 'completion_tokens=' INT:completionTokens\"\n//| parse content, \"LD 'model=' STRING:llmModel\"\n| summarize genCount = countIf(matchesPhrase(content, $GPTVersion)),\n vecEmbeddingCount = countIf(matchesPhrase(content, \"text-embedding-ada-002\")),\n sumCompletions = sum(completionTokens),\n sumPrompts = sum(promptTokens)\n| fieldsAdd cacheSaving = sumCompletions / genCount * vecEmbeddingCount / 1000 * 0.0020\n| fieldsKeep cacheSaving","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{"categoryAxisLabel":"content","valueAxisLabel":"promptTokens"}},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"cacheSaving"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"cacheSaving"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"unitsOverrides":[{"identifier":"cacheSaving","unitCategory":"currency","baseUnit":"usd","displayUnit":null,"decimals":3,"suffix":"","delimiter":false,"added":1702494249230}],"histogram":{"dataMappings":[{"valueAxis":"cacheSaving","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"14":{"type":"markdown","title":"","content":"![openai logo](https://upload.wikimedia.org/wikipedia/commons/4/4d/OpenAI_Logo.svg)"},"15":{"type":"data","title":"Search Volume by Prompt","query":"fetch logs\n| filter matchesPhrase(content, \"GET /api/v1/completion?prompt=\")\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| parse content, \"LD 'prompt=' STRING:prompt\"\n| fieldsKeep timestamp, prompt\n// Make a timeseries to chart count of prompts, split by the prompt phrase\n| makeTimeseries { count(), by: prompt }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{"categoryAxis":"prompt","categoryAxisLabel":"prompt","valueAxis":"interval","valueAxisLabel":"interval"},"hiddenLegendFields":["count()"],"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["count()"],"leftAxisDimensions":["prompt"]},"leftYAxisSettings":{"label":"Search Volume by Prompt","min":"auto"},"colorPalette":"fireplace","seriesOverrides":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"prompt","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","dataMappings":{"value":"interval"},"displayedFields":["prompt"],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"unitsOverrides":[]},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"16":{"type":"data","title":"Total Search Count","query":"fetch logs\n// filter by the namespace or set the filter to if running demo locally\n| filter ($KubernetesMode!=\"\" and k8s.namespace.name == $KubernetesMode)\n or ($KubernetesMode==\"\" and isNull(k8s.namespace.name))\n| filter matchesPhrase(content, $GPTVersion) or matchesPhrase(content, \"text-embedding-ada-002\")\n| fieldsKeep content\n| summarize count()\n//| makeTimeseries count()","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"singleValue","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"legend":{"hidden":true},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":false,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","recordField":"count()","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[["content"]],"columnWidths":{}},"honeycomb":{"shape":"square","dataMappings":{"value":"count()"},"displayedFields":[null],"legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"blue"},"histogram":{"dataMappings":[{"valueAxis":"count()","rangeAxis":""}]}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":100,"defaultSamplingRatio":10,"enableSampling":false}},"17":{"type":"markdown","title":"","content":"## Search Intelligence"},"18":{"type":"data","title":"","query":"fetch dt.entity.apm_security_gateway\n| summarize ","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"honeycomb":{"shape":"hexagon","legend":{"hidden":false,"position":"auto"},"colorMode":"color-palette","colorPalette":"categorical"}},"querySettings":{"maxResultRecords":1000,"defaultScanLimitGbytes":500,"maxResultMegaBytes":1,"defaultSamplingRatio":10,"enableSampling":false}}},"layouts":{"0":{"x":0,"y":11,"w":13,"h":4},"1":{"x":0,"y":7,"w":3,"h":3},"2":{"x":0,"y":21,"w":3,"h":3},"3":{"x":0,"y":6,"w":9,"h":1},"4":{"x":3,"y":7,"w":3,"h":3},"5":{"x":6,"y":7,"w":3,"h":3},"6":{"x":0,"y":20,"w":17,"h":1},"7":{"x":9,"y":0,"w":10,"h":1},"8":{"x":9,"y":1,"w":5,"h":4},"9":{"x":9,"y":5,"w":5,"h":2},"11":{"x":14,"y":1,"w":4,"h":3},"13":{"x":14,"y":4,"w":4,"h":2},"14":{"x":0,"y":0,"w":8,"h":6},"15":{"x":0,"y":15,"w":17,"h":5},"16":{"x":13,"y":11,"w":3,"h":4},"17":{"x":0,"y":10,"w":18,"h":1},"18":{"x":0,"y":24,"w":8,"h":6}},"importedWithCode":false} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/README.md b/dynatrace/dashboards/weaviate/README.md deleted file mode 100644 index de4bf10..0000000 --- a/dynatrace/dashboards/weaviate/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Weaviate Dashboards - -DT clones of these Grafana dashboards: https://github.com/weaviate/weaviate/tree/main/tools/dev/grafana/dashboards - -Most of the dashboards have at least one `TODO` tile because I don't know / it isn't clear how PromQL maps to DQL. diff --git a/dynatrace/dashboards/weaviate/Weaviate-Importing-Data.json b/dynatrace/dashboards/weaviate/Weaviate-Importing-Data.json deleted file mode 100644 index 3339c6a..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Importing-Data.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"data","title":"Active Tombstones in HNSW Index","query":"timeseries vector_index_tombstones=count(vector_index_tombstones)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_tombstones","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_tombstones"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"1":{"type":"markdown","title":"","content":"TODO: Batch Objects Latency (Components & Totals)\n\nLine 108: https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/importing.json\n```\nrate(batch_durations_ms_sum{class_name=\\\"n/a\\\"}[$__interval])/rate(batch_durations_ms_count{class_name=\\\"n/a\\\"}[$__interval])\n```"},"2":{"type":"data","title":"Currently Active Tombstones in HNSW Index","query":"timeseries vector_index_tombstones=sum(vector_index_tombstones)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_tombstones","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_tombstones"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"3":{"type":"markdown","title":"","content":"TODO: Object Storage ({{ class_name }} - {{ shard_name }})\n\nLine 353: https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/importing.json\n```\nrate(batch_durations_ms_sum{operation=\"object_storage\"}[$__interval])/rate(batch_durations_ms_count{operation=\"object_storage\"}[$__interval])\n```"},"4":{"type":"markdown","title":"","content":"TODO: Vector Storage ({{ class_name }} - {{ shard_name }})\n\nLine 364: https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/importing.json\n```\nrate(batch_durations_ms_sum{operation=\"vector_storage\"}[$__interval])/rate(batch_durations_ms_count{operation=\"vector_storage\"}[$__interval])\n```"},"5":{"type":"data","title":"Active Tombstone Cleanup Threads","query":"timeseries vector_index_tombstone_cleanup_threads=count(vector_index_tombstone_cleanup_threads)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_tombstones","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_tombstone_cleanup_threads"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"6":{"type":"data","title":"Active Tombstone Cleanup Threads (Sum)","query":"timeseries vector_index_tombstone_cleanup_threads=sum(vector_index_tombstone_cleanup_threads)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_tombstones","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_tombstone_cleanup_threads"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"7":{"type":"data","title":"Reassigned Nodes","query":"timeseries vector_index_tombstone_cleaned=sum(vector_index_tombstone_cleaned)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_tombstones","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_tombstone_cleaned"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"8":{"type":"data","title":"Total \"Replace\" (Object Storage)","query":"timeseries lsm_active_segments=sum(lsm_active_segments), filter: { strategy==\"replace\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_active_segments","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_active_segments"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"9":{"type":"markdown","title":"","content":"## Active LSM Segments (Summed by type)"},"10":{"type":"data","title":"Total \"Map\" (Inverted storage with term frequency)","query":"timeseries lsm_active_segments=sum(lsm_active_segments), filter: { strategy==\"mapcollection\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_active_segments","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_active_segments"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"11":{"type":"data","title":"Total \"Set\" (Inverted storage without frequency)","query":"timeseries lsm_active_segments=sum(lsm_active_segments), filter: { strategy==\"setcollection\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_active_segments","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_active_segments"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"12":{"type":"markdown","title":"","content":"## Vector Index Statistics"},"13":{"type":"data","title":"Vectors Inserted","query":"timeseries vector_index_operations=sum(vector_index_operations), filter: { operation==\"create\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_active_segments","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_operations"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"14":{"type":"data","title":"Vectors Deleted","query":"timeseries vector_index_operations=sum(vector_index_operations), filter: { operation==\"delete\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_active_segments","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_operations"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"15":{"type":"markdown","title":"","content":"TODO: Net Vectors\n\nLine 779: https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/importing.json\n```\nsum(vector_index_operations{operation=\\\"create\\\"}) - ignoring(operation) sum(vector_index_operations{operation=\\\"delete\\\"})\n```"},"16":{"type":"data","title":"Heap in use (bytes)","query":"timeseries interval: 1h, go_memstats_heap_inuse_bytes=avg(go_memstats_heap_inuse_bytes)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"go_memstats_heap_inuse_bytes","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["go_memstats_heap_inuse_bytes"],"leftAxisDimensions":[]},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"17":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Importing Data into Weaviate"},"18":{"type":"markdown","title":"","content":"## Persistence Tasks (Object, Vector)"}},"layouts":{"0":{"x":8,"y":0,"w":8,"h":6},"1":{"x":8,"y":6,"w":8,"h":4},"2":{"x":16,"y":0,"w":8,"h":6},"3":{"x":0,"y":11,"w":8,"h":4},"4":{"x":8,"y":11,"w":8,"h":4},"5":{"x":0,"y":15,"w":5,"h":6},"6":{"x":5,"y":15,"w":5,"h":6},"7":{"x":10,"y":15,"w":5,"h":6},"8":{"x":0,"y":22,"w":8,"h":6},"9":{"x":0,"y":21,"w":22,"h":1},"10":{"x":8,"y":22,"w":8,"h":6},"11":{"x":0,"y":28,"w":8,"h":6},"12":{"x":0,"y":34,"w":22,"h":1},"13":{"x":0,"y":35,"w":8,"h":6},"14":{"x":8,"y":35,"w":8,"h":6},"15":{"x":0,"y":41,"w":8,"h":4},"16":{"x":0,"y":45,"w":8,"h":6},"17":{"x":0,"y":0,"w":8,"h":6},"18":{"x":0,"y":10,"w":24,"h":1}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-LSM-Store.json b/dynatrace/dashboards/weaviate/Weaviate-LSM-Store.json deleted file mode 100644 index 21331fd..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-LSM-Store.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Weaviate LSM Store"},"1":{"type":"markdown","title":"","content":"TODO: LSM Strategy Replace by Level (Object Storage)\n\nLine 84 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/lsm.json`\n\n```\nsum by (level, unit) (lsm_segment_size{strategy=\\\"replace\\\"})\n```"},"2":{"type":"markdown","title":"","content":"TODO: LSM Strategy SetCollection by Level (Inverted without frequency)\n\nLine 147 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/lsm.json`\n\n```\nsum by (level, unit) (lsm_segment_size{strategy=\\\"setcollection\\\"})\n```"},"3":{"type":"markdown","title":"","content":"TODO: LSM Strategy MapCollection by Level (Inverted with frequency)\n\nLine 210 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/lsm.json`\n\n```\nsum by (level, unit) (lsm_segment_size{strategy=\\\"mapcollection\\\"})\n```"},"4":{"type":"data","title":"Memtable Size (Individual)","query":"timeseries interval: 1h, lsm_memtable_size=avg(lsm_memtable_size)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_memtable_size","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_memtable_size"],"leftAxisDimensions":[]},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"5":{"type":"markdown","title":"","content":"TODO: Memtable Operations (individual)\n\nLine 389 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/lsm.json`\n\n```\nrate(lsm_memtable_durations_ms_sum{}[$__interval])/rate(lsm_memtable_durations_ms_count{}[$__interval])\n```"},"6":{"type":"markdown","title":"","content":"TODO: Bloom Filter Hits and Misses Duration\n\nLine 479 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/lsm.json`\n\n```\nrate(lsm_bloom_filters_duration_ms_sum{strategy=\\\"replace\\\"}[$__interval])/rate(lsm_bloom_filters_duration_ms_count{strategy=\\\"replace\\\"}[$__interval])\n```"},"7":{"type":"markdown","title":"","content":"TODO: Bloom Filter False Positive Ratio\n\nLine 543 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/lsm.json`\n\n```\nsum(lsm_bloom_filters_duration_ms_count{operation=\\\"get_false_positive\\\"})/(sum(lsm_bloom_filters_duration_ms_count{operation=\\\"get_true_negative\\\"})+sum(lsm_bloom_filters_duration_ms_count{operation=\\\"get_true_positive\\\"})+sum(lsm_bloom_filters_duration_ms_count{operation=\\\"get_false_positive\\\"}))\n```"},"8":{"type":"data","title":"True Positives","query":"timeseries lsm_bloom_filters_duration_ms_sum=sum(lsm_bloom_filters_duration_ms_sum), filter: { operation==\"get_true_positive\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_bloom_filters_duration_ms_sum","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_bloom_filters_duration_ms_sum"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"9":{"type":"data","title":"True Negatives","query":"timeseries lsm_bloom_filters_duration_ms_sum=sum(lsm_bloom_filters_duration_ms_sum), filter: { operation==\"get_true_negative\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_bloom_filters_duration_ms_sum","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_bloom_filters_duration_ms_sum"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"10":{"type":"markdown","title":"","content":"## Bloom Filter Access"},"11":{"type":"data","title":"False Positives","query":"timeseries lsm_bloom_filters_duration_ms_sum=sum(lsm_bloom_filters_duration_ms_sum), filter: { operation==\"get_false_positive\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_bloom_filters_duration_ms_sum","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_bloom_filters_duration_ms_sum"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"12":{"type":"data","title":"Object Storage","query":"timeseries interval: 1h, lsm_segment_size=sum(lsm_segment_size), filter: { strategy==\"replace\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"lsm_segment_size","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["lsm_segment_size"],"leftAxisDimensions":[]},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"13":{"type":"markdown","title":"","content":"## Estimated Sizes LSM Stores"},"15":{"type":"markdown","title":"","content":"TODO: Inverted Index\n\nLine 704 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/lsm.json`\n\n```\nsum(lsm_segment_size{strategy=\\\"mapcollection\\\"}) + ignoring(strategy) sum(lsm_segment_size{strategy=\\\"setcollection\\\"})\n```"}},"layouts":{"0":{"x":0,"y":0,"w":9,"h":7},"1":{"x":9,"y":4,"w":8,"h":4},"2":{"x":9,"y":0,"w":8,"h":4},"3":{"x":9,"y":8,"w":8,"h":4},"4":{"x":0,"y":7,"w":9,"h":5},"5":{"x":0,"y":16,"w":8,"h":4},"6":{"x":8,"y":12,"w":13,"h":4},"7":{"x":0,"y":12,"w":8,"h":4},"8":{"x":0,"y":21,"w":7,"h":4},"9":{"x":7,"y":21,"w":6,"h":4},"10":{"x":0,"y":20,"w":24,"h":1},"11":{"x":13,"y":21,"w":6,"h":4},"12":{"x":0,"y":26,"w":7,"h":7},"13":{"x":0,"y":25,"w":24,"h":1},"15":{"x":7,"y":26,"w":12,"h":4}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Object-Operations.json b/dynatrace/dashboards/weaviate/Weaviate-Object-Operations.json deleted file mode 100644 index 0a5798e..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Object-Operations.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"markdown","title":"","content":"TODO: PUT (individual operations)\n\nLine 115 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/objects.json`\n\n```\nrate(objects_durations_ms_sum{operation=\\\"put\\\"}[$__interval])/rate(objects_durations_ms_count{operation=\\\"put\\\"}[$__interval])\n```"},"1":{"type":"markdown","title":"","content":"TODO: PUT Object (Total)\n\nLine 212 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/objects.json`\n\n```\nrate(objects_durations_ms_sum{step=\\\"total\\\"}[$__interval]) / rate(objects_durations_ms_count{step=\\\"total\\\"}[$__interval])\n```"},"2":{"type":"markdown","title":"","content":"TODO: PUT Object (Upsert Object)\n\nLine 311 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/objects.json`\n\n```\nrate(objects_durations_ms_sum{step=\\\"upsert_object_store\\\"}[$__interval]) / rate(objects_durations_ms_count{step=\\\"upsert_object_store\\\"}[$__interval])\n```"},"3":{"type":"markdown","title":"","content":"TODO: Batch Delete Durations\n\nLine 406 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/objects.json`\n\n```\nrate(batch_delete_durations_ms_sum{}[$__interval])/rate(batch_delete_durations_ms_count{}[$__interval])\n```"},"4":{"type":"markdown","title":"","content":"TODO: PUT Object (Inverted Index)\n\nLine 503 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/objects.json`\n\n```\nrate(objects_durations_ms_sum{step=\\\"inverted_total\\\"}[$__interval]) / rate(objects_durations_ms_count{step=\\\"inverted_total\\\"}[$__interval])\n```"},"5":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Object Operations"}},"layouts":{"0":{"x":0,"y":6,"w":8,"h":4},"1":{"x":8,"y":0,"w":8,"h":6},"2":{"x":0,"y":10,"w":8,"h":4},"3":{"x":8,"y":6,"w":8,"h":4},"4":{"x":0,"y":14,"w":8,"h":4},"5":{"x":0,"y":0,"w":8,"h":6}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Query-Performance.json b/dynatrace/dashboards/weaviate/Weaviate-Query-Performance.json deleted file mode 100644 index c253e8e..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Query-Performance.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Querying"},"1":{"type":"markdown","title":"","content":"TODO: Average Query Latency\n\nLine 116 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nrate(queries_durations_ms_sum{}[$__interval]) / rate(queries_durations_ms_count{}[$__interval])\n```"},"2":{"type":"markdown","title":"","content":"TODO: Query Rate\n\nLine 210 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nrate(queries_durations_ms_count{}[$__interval])\n```"},"3":{"type":"markdown","title":"","content":"TODO: 90th Percentile Query Latency (Estimated)\n\nLine 304 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nhistogram_quantile(0.90, sum(rate(queries_durations_ms_bucket{}[$__interval])) by (le,class_name))\n```"},"4":{"type":"markdown","title":"","content":"TODO: 95th Percentile Query Latency (Estimated)\n\nLine 398 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nhistogram_quantile(0.95, sum(rate(queries_durations_ms_bucket{}[$__interval])) by (le,class_name))\n```"},"5":{"type":"markdown","title":"","content":"TODO: Query Rate. Operation = Filter\n\nLine 493 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nrate(queries_filtered_vector_durations_ms_sum{operation=\\\"filter\\\"}[$__interval]) / rate(queries_filtered_vector_durations_ms_count{operation=\\\"filter\\\"}[$__interval])\n```"},"6":{"type":"markdown","title":"","content":"## Filtered Query Latency"},"7":{"type":"markdown","title":"","content":"TODO: Query Rate. Operation = Sort\n\nLine 505 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nrate(queries_filtered_vector_durations_ms_sum{operation=\\\"sort\\\"}[$__interval]) / rate(queries_filtered_vector_durations_ms_count{operation=\\\"sort\\\"}[$__interval])\n```"},"8":{"type":"markdown","title":"","content":"TODO: Query Rate. Operation = Vector\n\nLine 518 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nrate(queries_filtered_vector_durations_ms_sum{operation=\\\"vector\\\"}[$__interval]) / rate(queries_filtered_vector_durations_ms_count{operation=\\\"vector\\\"}[$__interval])\n```"},"9":{"type":"markdown","title":"","content":"TODO: Query Rate. Operation = Objects\n\nLine 531 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/querying.json`\n\n```\nrate(queries_filtered_vector_durations_ms_sum{operation=\\\"objects\\\"}[$__interval]) / rate(queries_filtered_vector_durations_ms_count{operation=\\\"objects\\\"}[$__interval])\n```"},"10":{"type":"markdown","title":"","content":"## Concurrent Write Requests\n"},"11":{"type":"data","title":"Batch (Objects)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"batch\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"12":{"type":"data","title":"Batch (References)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"batch_references\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"13":{"type":"data","title":"Add (Objects)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"add_object\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["concurrent_queries_count"],"leftAxisDimensions":[]}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"14":{"type":"data","title":"Update (Objects)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"update_object\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"15":{"type":"data","title":"Merge (Objects)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"merge_object\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"16":{"type":"data","title":"Delete (Objects)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"delete_object\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"17":{"type":"data","title":"Add (Reference)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"add_reference\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"18":{"type":"data","title":"Update (Reference)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"update_reference\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"19":{"type":"data","title":"Delete (Reference)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"delete_reference\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"20":{"type":"markdown","title":"","content":"### Objects"},"21":{"type":"markdown","title":"","content":"### References"},"22":{"type":"data","title":"Aggregate","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"aggregate\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"23":{"type":"markdown","title":"","content":"## Concurrent Read Requests\n"},"24":{"type":"data","title":"GET (GraphQL)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"get_graphql\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["concurrent_queries_count"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"25":{"type":"data","title":"GET (Objects)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"get_object\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"26":{"type":"data","title":"HEAD (Objects)","query":"timeseries concurrent_queries_count=sum(concurrent_queries_count), filter: { query_type==\"head_object\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"concurrent_queries_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}}},"layouts":{"0":{"x":0,"y":0,"w":10,"h":5},"1":{"x":0,"y":5,"w":22,"h":3},"2":{"x":0,"y":8,"w":22,"h":3},"3":{"x":0,"y":11,"w":22,"h":3},"4":{"x":0,"y":14,"w":22,"h":3},"5":{"x":0,"y":18,"w":22,"h":3},"6":{"x":0,"y":17,"w":22,"h":1},"7":{"x":0,"y":21,"w":22,"h":3},"8":{"x":0,"y":24,"w":22,"h":3},"9":{"x":0,"y":27,"w":22,"h":3},"10":{"x":0,"y":30,"w":22,"h":1},"11":{"x":0,"y":38,"w":8,"h":6},"12":{"x":0,"y":51,"w":8,"h":6},"13":{"x":0,"y":32,"w":8,"h":6},"14":{"x":8,"y":32,"w":8,"h":6},"15":{"x":8,"y":38,"w":8,"h":6},"16":{"x":16,"y":32,"w":8,"h":6},"17":{"x":0,"y":45,"w":8,"h":6},"18":{"x":8,"y":45,"w":8,"h":6},"19":{"x":16,"y":45,"w":8,"h":6},"20":{"x":0,"y":31,"w":24,"h":1},"21":{"x":0,"y":44,"w":24,"h":1},"22":{"x":0,"y":58,"w":8,"h":6},"23":{"x":0,"y":57,"w":22,"h":1},"24":{"x":8,"y":58,"w":8,"h":6},"25":{"x":16,"y":58,"w":8,"h":6},"26":{"x":0,"y":64,"w":8,"h":6}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Schema-Transactions.json b/dynatrace/dashboards/weaviate/Weaviate-Schema-Transactions.json deleted file mode 100644 index ff25e97..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Schema-Transactions.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Schema Transactions"},"1":{"type":"markdown","title":"","content":"TODO: Transaction duration by status\n\nLine 238 of `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/schema.json`\n\n```\nrate(schema_tx_duration_seconds_sum[$__rate_interval])/rate(schema_tx_duration_seconds_count[$__rate_interval])\n```"},"2":{"type":"markdown","title":"","content":"TODO: Opened\n\nLine 136 of `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/schema.json`\n\n```\nsum(rate(schema_tx_opened_total[$__rate_interval]))\n```"},"3":{"type":"markdown","title":"","content":"TODO: Closed\n\nLine 147 of `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/schema.json`\n\n```\nsum(rate(schema_tx_closed_total[$__rate_interval]))\n```"},"4":{"type":"markdown","title":"","content":"## Transactions Opened / Closed"}},"layouts":{"0":{"x":0,"y":0,"w":9,"h":7},"1":{"x":0,"y":14,"w":8,"h":6},"2":{"x":0,"y":8,"w":8,"h":6},"3":{"x":9,"y":8,"w":8,"h":6},"4":{"x":0,"y":7,"w":23,"h":1}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Snapshots.json b/dynatrace/dashboards/weaviate/Weaviate-Snapshots.json deleted file mode 100644 index 5bd92da..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Snapshots.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"data","title":"Time spent restoring","query":"timeseries backup_restore_ms_sum=sum(backup_restore_ms_sum)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"recordView","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"1":{"type":"data","title":"Time spent storing","query":"timeseries backup_store_to_backend_ms_sum=sum(backup_store_to_backend_ms_sum)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"recordView","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"2":{"type":"data","title":"Total data backed up","query":"timeseries backup_store_data_transferred=sum(backup_store_data_transferred)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"recordView","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"3":{"type":"data","title":"Total data restored","query":"timeseries backup_restore_data_transferred=sum(backup_restore_data_transferred)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"recordView","visualizationSettings":{"thresholds":[],"chartSettings":{"gapPolicy":"connect","circleChartSettings":{"groupingThresholdType":"relative","groupingThresholdValue":0,"valueType":"relative"},"categoryOverrides":{},"categoricalBarChartSettings":{}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"4":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Weaviate Snapshots"},"5":{"type":"markdown","title":"","content":"## Times"},"6":{"type":"markdown","title":"","content":"## Data"}},"layouts":{"0":{"x":8,"y":7,"w":8,"h":6},"1":{"x":0,"y":7,"w":8,"h":6},"2":{"x":0,"y":14,"w":8,"h":6},"3":{"x":8,"y":14,"w":8,"h":6},"4":{"x":0,"y":0,"w":14,"h":6},"5":{"x":0,"y":6,"w":23,"h":1},"6":{"x":0,"y":13,"w":23,"h":1}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Startup-Times.json b/dynatrace/dashboards/weaviate/Weaviate-Startup-Times.json deleted file mode 100644 index de9777b..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Startup-Times.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Startup Times"},"1":{"type":"markdown","title":"","content":"TODO: Startup Durations (operation by class and shard name)\n\nLine 107 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/startup.json`\n\n```\nrate(startup_durations_ms_sum{}[$__interval])/rate(startup_durations_ms_count{}[$__interval])\n```"},"2":{"type":"data","title":"Startup all LSM Buckets","query":"timeseries startup_durations_ms_sum=sum(startup_durations_ms_sum), filter: { operation==\"lsm_startup_bucket\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"startup_durations_ms_sum","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["startup_durations_ms_sum"],"leftAxisDimensions":[]}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"3":{"type":"data","title":"Startup all HNSW Indexes","query":"timeseries startup_durations_ms_sum=sum(startup_durations_ms_sum), filter: { operation==\"hnsw_read_all_commitlogs\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"startup_durations_ms_sum","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["startup_durations_ms_sum"],"leftAxisDimensions":[]}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"4":{"type":"data","title":"Total Shard Startup","query":"timeseries startup_durations_ms_sum=sum(startup_durations_ms_sum), filter: { operation==\"shard_total_init\" }, by:{ class_name, shard_name }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"startup_durations_ms_sum","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{"categoryAxis":"class_name","valueAxis":"interval","categoryAxisLabel":"class_name","valueAxisLabel":"interval"},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["startup_durations_ms_sum"],"leftAxisDimensions":["class_name","shard_name"]}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"class_name","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"5":{"type":"markdown","title":"","content":"TODO: Startup Disk I/O\n\nLine 397 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/startup.json`\n\n```\nrate(startup_diskio_throughput_sum{}[$__interval])/rate(startup_diskio_throughput_count{}[$__interval])\n```"}},"layouts":{"0":{"x":0,"y":0,"w":13,"h":6},"1":{"x":0,"y":6,"w":8,"h":6},"2":{"x":9,"y":6,"w":8,"h":6},"3":{"x":0,"y":12,"w":8,"h":6},"4":{"x":9,"y":12,"w":8,"h":6},"5":{"x":0,"y":18,"w":8,"h":6}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Tombstone-Analysis.json b/dynatrace/dashboards/weaviate/Weaviate-Tombstone-Analysis.json deleted file mode 100644 index 92c9b63..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Tombstone-Analysis.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Tombstone Analysis"},"1":{"type":"data","title":"Active Tombstones in HNSW index","query":"timeseries vector_index_tombstones=avg(vector_index_tombstones)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_tombstones","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_tombstones"],"leftAxisDimensions":[]},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"2":{"type":"data","title":"Vectors Inserted","query":"timeseries vector_index_operations=sum(vector_index_operations), filter: { operation==\"create\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_operations","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_operations"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"3":{"type":"markdown","title":"","content":"## Vector Index Statistics"},"4":{"type":"data","title":"Vectors Deleted","query":"timeseries vector_index_operations=sum(vector_index_operations), filter: { operation==\"delete\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_operations","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_operations"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"5":{"type":"markdown","title":"","content":"TODO: Net Vectors\n\nLine 199 of `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/tombstones.json`\n\n```\nsum(vector_index_operations{operation=\\\"create\\\"}) - ignoring(operation) sum(vector_index_operations{operation=\\\"delete\\\"})\n```"},"6":{"type":"data","title":"Weaviate goroutines","query":"timeseries go_goroutines=avg(go_goroutines), filter: { k8s.deployment.name==\"weaviate-semantic-cache\"}","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"go_goroutines","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value","recordField":"error"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square","dataMappings":{}}}},"7":{"type":"data","title":"Reassign by Shard Name","query":"timeseries tombstone_reassign_neighbors=sum(tombstone_reassign_neighbors), by: { shard_name }\n| sort arraySum(tombstone_reassign_neighbors) desc\n| limit 20","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"tombstone_reassign_neighbors","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{"categoryAxis":"shard_name","categoryAxisLabel":"shard_name","valueAxis":"interval","valueAxisLabel":"interval"},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["tombstone_reassign_neighbors"],"leftAxisDimensions":["shard_name"]}},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"shard_name","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"8":{"type":"markdown","title":"","content":"## Weaviate goroutines\n"},"9":{"type":"markdown","title":"","content":"## Tombstone Reassign Neighbors"},"10":{"type":"data","title":"Global Entrypoint by Shard Name","query":"timeseries tombstone_find_global_entrypoint=sum(tombstone_find_global_entrypoint), by: { shard_name }\n| sort arraySum(tombstone_find_global_entrypoint) desc\n| limit 20","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"tombstone_find_global_entrypoint","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{"categoryAxis":"shard_name","categoryAxisLabel":"shard_name","valueAxis":"interval","valueAxisLabel":"interval"},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["tombstone_find_global_entrypoint"],"leftAxisDimensions":["shard_name"]},"hiddenLegendFields":["tombstone_find_global_entrypoint"]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"shard_name","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"11":{"type":"data","title":"Local Entrypoint by Shard Name","query":"timeseries tombstone_find_local_entrypoint=sum(tombstone_find_local_entrypoint), by: { shard_name }\n| sort arraySum(tombstone_find_local_entrypoint) desc\n| limit 20","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"tombstone_find_global_entrypoint","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{"categoryAxis":"shard_name","categoryAxisLabel":"shard_name","valueAxis":"interval","valueAxisLabel":"interval"},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["tombstone_find_local_entrypoint"],"leftAxisDimensions":["shard_name"]},"hiddenLegendFields":["tombstone_find_local_entrypoint"]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"shard_name","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[]},"honeycomb":{"shape":"square"}}},"12":{"type":"markdown","title":"","content":"## Find Local & Global Entrypoints"},"13":{"type":"data","title":"Tombstone Delete List Size by Class & Shard","query":"timeseries tombstone_delete_list_size=avg(tombstone_delete_list_size), by: { shard_name, class_name }\n| sort arrayAvg(tombstone_delete_list_size) desc\n| limit 20","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"tombstone_delete_list_size","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{"categoryAxis":"shard_name","categoryAxisLabel":"shard_name","valueAxis":"interval","valueAxisLabel":"interval"},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["tombstone_delete_list_size"],"leftAxisDimensions":["shard_name","class_name"]},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"shard_name","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square","dataMappings":{"category":"shard_name","value":"class_name"}}}},"14":{"type":"markdown","title":"","content":"## Tombstone Delete List"}},"layouts":{"0":{"x":0,"y":0,"w":10,"h":7},"1":{"x":0,"y":7,"w":8,"h":6},"2":{"x":0,"y":14,"w":8,"h":6},"3":{"x":0,"y":13,"w":24,"h":1},"4":{"x":8,"y":14,"w":8,"h":6},"5":{"x":16,"y":14,"w":8,"h":6},"6":{"x":0,"y":21,"w":8,"h":6},"7":{"x":8,"y":21,"w":9,"h":6},"8":{"x":0,"y":20,"w":8,"h":1},"9":{"x":8,"y":20,"w":10,"h":1},"10":{"x":0,"y":28,"w":8,"h":6},"11":{"x":8,"y":28,"w":8,"h":6},"12":{"x":0,"y":27,"w":24,"h":1},"13":{"x":0,"y":35,"w":8,"h":6},"14":{"x":0,"y":34,"w":24,"h":1}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Usage.json b/dynatrace/dashboards/weaviate/Weaviate-Usage.json deleted file mode 100644 index 0c24053..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Usage.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"data","title":"Overall Object Count","query":"timeseries object_count=sum(object_count)","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"object_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["object_count"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"1":{"type":"data","title":"Object Count per Class / Shard","query":"timeseries object_count=sum(object_count), by: { class_name, shard_name }\n| sort arraySum(object_count) desc\n| limit 20","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"object_count","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{"categoryAxis":"class_name","categoryAxisLabel":"class_name","valueAxis":"interval","valueAxisLabel":"interval"},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["object_count"],"leftAxisDimensions":["class_name","shard_name"]},"hiddenLegendFields":["object_count"]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"class_name","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square","dataMappings":{"category":"class_name","value":"shard_name"}}}},"2":{"type":"data","title":"Vector Additions","query":"timeseries vector_index_operations=sum(vector_index_operations), filter: { operation==\"create\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_operations","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_operations"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"3":{"type":"data","title":"Vector Deletions","query":"timeseries vector_index_operations=sum(vector_index_operations), filter: { operation==\"delete\" }","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_operations","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":true},"leftYAxisSettings":{"label":""},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_operations"],"leftAxisDimensions":[]},"categoricalBarChartSettings":{},"hiddenLegendFields":[]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square"}}},"5":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n\n# Weaviate Usage"},"6":{"type":"markdown","title":"","content":"TODO: Net Vectors\n\nLine 289 of: https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/usage.json\n\n```\nsum(vector_index_operations{operation=\\\"create\\\"}) - ignoring(operation) sum(vector_index_operations{operation=\\\"delete\\\"})\n```"},"8":{"type":"markdown","title":"","content":"TODO: Overall Dimension Count\n\nMetric Missing: `vector_dimensions_sum`\n\nNeither in Weaviate docs nor DT ??"},"9":{"type":"markdown","title":"","content":"## Vector Statistics across all Classes"}},"layouts":{"0":{"x":0,"y":7,"w":8,"h":6},"1":{"x":8,"y":7,"w":8,"h":6},"2":{"x":0,"y":14,"w":8,"h":6},"3":{"x":8,"y":14,"w":8,"h":6},"5":{"x":0,"y":0,"w":10,"h":7},"6":{"x":0,"y":20,"w":8,"h":6},"8":{"x":8,"y":20,"w":8,"h":3},"9":{"x":0,"y":13,"w":23,"h":1}}} \ No newline at end of file diff --git a/dynatrace/dashboards/weaviate/Weaviate-Vector-Index.json b/dynatrace/dashboards/weaviate/Weaviate-Vector-Index.json deleted file mode 100644 index 6f10e52..0000000 --- a/dynatrace/dashboards/weaviate/Weaviate-Vector-Index.json +++ /dev/null @@ -1 +0,0 @@ -{"version":14,"variables":[],"tiles":{"0":{"type":"markdown","title":"","content":"![weaviate logo](https://weaviate.io/assets/images/weaviate-nav-logo-light-532fdca50b4e5f974111eaf70c36080c.svg)\n# Vector Index"},"2":{"type":"data","title":"Vector Index Size by Class and Shard Name","query":"timeseries vector_index_size=avg(vector_index_size), by: { class_name, shard_name }\n| sort arrayAvg(vector_index_size) desc\n| limit 20","davis":{"enabled":false,"davisVisualization":{"isAvailable":true}},"visualization":"lineChart","visualizationSettings":{"thresholds":[{"id":"0","title":"","field":"vector_index_size","rules":[{"id":"0","label":"","comparator":"≥","color":"#7dc540"},{"id":"1","label":"","comparator":"≥","color":"#f5d30f"},{"id":"2","label":"","comparator":"≥","color":"#dc172a"}],"isEnabled":true}],"chartSettings":{"seriesOverrides":[],"gapPolicy":"gap","legend":{"hidden":false},"leftYAxisSettings":{"label":""},"categoricalBarChartSettings":{"categoryAxis":"class_name","categoryAxisLabel":"class_name","valueAxis":"interval","valueAxisLabel":"interval"},"fieldMapping":{"timestamp":"timeframe","leftAxisValues":["vector_index_size"],"leftAxisDimensions":["class_name","shard_name"]},"hiddenLegendFields":["vector_index_size"]},"singleValue":{"showLabel":true,"label":"","prefixIcon":"","recordField":"class_name","autoscale":true,"alignment":"center","colorThresholdTarget":"value"},"table":{"rowDensity":"condensed","enableSparklines":false,"hiddenColumns":[],"lineWrapIds":[],"columnWidths":{}},"histogram":{"dataMappings":[{"valueAxis":"interval","rangeAxis":""}]},"honeycomb":{"shape":"square","dataMappings":{"category":"class_name","value":"shard_name"}}}},"3":{"type":"markdown","title":"","content":"TODO: Vector Index Maintenance Operations (Sync & Async)\n\nLine 208 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/vectorindex.json`\n\n```\nrate(vector_index_maintenance_durations_ms_sum{}[$__interval]) / rate(vector_index_maintenance_durations_ms_count{}[$__interval])\n```"},"4":{"type":"markdown","title":"","content":"TODO: Insert Operations by step, class and shard name\n\nLine 303 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/vectorindex.json`\n\n```\nrate(vector_index_durations_ms_sum{operation=\\\"create\\\"}[$__interval])/rate(vector_index_durations_ms_count{operation=\\\"create\\\"}[$__interval])\n```"},"5":{"type":"markdown","title":"","content":"## Insert Operations\n"},"6":{"type":"markdown","title":"","content":"TODO: Concurrent goroutines\n\nLine 315 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/vectorindex.json`\n\nCould not find `concurrent_goroutines` metric in DT or Weaviate docs\n\n```\nconcurrent_goroutines{class_name=\\\"object batcher\\\"}\n```"},"7":{"type":"markdown","title":"","content":"TODO: Delete Operations by step, class and shard name\n\nLine 303 of: `https://github.com/weaviate/weaviate/blob/main/tools/dev/grafana/dashboards/vectorindex.json`\n\n```\nrate(vector_index_durations_ms_sum{operation=\\\"delete\\\"}[$__interval])/rate(vector_index_durations_ms_count{operation=\\\"delete\\\"}[$__interval])\n```"},"8":{"type":"markdown","title":"","content":"## Delete Operations\n"}},"layouts":{"0":{"x":0,"y":0,"w":8,"h":6},"2":{"x":0,"y":6,"w":14,"h":5},"3":{"x":14,"y":0,"w":8,"h":6},"4":{"x":0,"y":12,"w":8,"h":6},"5":{"x":0,"y":11,"w":24,"h":1},"6":{"x":8,"y":12,"w":8,"h":6},"7":{"x":0,"y":19,"w":8,"h":6},"8":{"x":0,"y":18,"w":24,"h":1}}} \ No newline at end of file diff --git a/mkdocs.yaml b/mkdocs.yaml index bef8def..87cc512 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -1,27 +1,33 @@ -site_name: "Dynatrace Observability Lab: LLM Observability" -# Enable link back to GitHub repo -repo_name: "GitHub" +site_name: "Dynatrace Observability Lab: Gen AI & LLM Observability" +repo_name: "View Code on GitHub" repo_url: "https://github.com/dynatrace/obslab-llm-observability" - nav: - '1. About': index.md - - '2. How it works': how-it-works.md - - '3. Standard vs. RAG Version': standard-rag-differences.md - - '4. Demo Prerequisites': prerequisites.md - - '5. Setup': setup.md - - '6. Start The Demo': startup.md - - '7. Use the Demo': use-demo.md - - '8. Visualise Data in Dynatrace': visualise-dt.md + - '2. Getting started': 2-getting-started.md + - '3. Codespaces': 3-codespaces.md + - '4. Content': 4-content.md + - '5. Direct Chat': 5-direct.md + - '6. RAG': 6-rag.md + - '7. Agentic': 7-agentic.md + - '8. Other LLM 🧠': + - 'Amazon Bedrock': bedrock.md + - 'Multi-Mode': multi-mode.md + - 'Ollama-Pinecone': ollama-pinecone.md + - 'RAG': rag.md - '9. Cleanup': cleanup.md - '10. Resources': resources.md - - "11. What's Next?": whats-next.md + - "11. What's next?": whats-next.md theme: name: material - custom_dir: custom_theme + custom_dir: docs/overrides + logo: https://cdn.bfldr.com/B686QPH3/at/w5hnjzb32k5wcrcxnwcx4ckg/Dynatrace_signet_RGB_HTML.svg?auto=webp&format=pngg + features: + - content.code.copy + palette: - # Palette toggle for automatic mode - media: "(prefers-color-scheme)" + primary: deep-purple toggle: icon: material/brightness-auto name: Switch to light mode @@ -29,6 +35,7 @@ theme: # Palette toggle for light mode - media: "(prefers-color-scheme: light)" scheme: default + primary: deep-purple toggle: icon: material/brightness-7 name: Switch to dark mode @@ -36,14 +43,23 @@ theme: # Palette toggle for dark mode - media: "(prefers-color-scheme: dark)" scheme: slate + primary: deep-purple toggle: icon: material/brightness-4 name: Switch to system preference + markdown_extensions: - - attr_list - toc: permalink: '#' - + - md_in_html + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.snippets: + base_path: ["docs"] + - attr_list plugins: - - search - - markdownextradata + - search diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index 596ffa9..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 44b9fdc..0000000 --- a/public/index.html +++ /dev/null @@ -1,500 +0,0 @@ - - - - - - Your AI travel advisor - - - - - - - - - - - - - - - - - - - - - -
    -
    - -
    -
    -
    -
    -
    -

    easyTravel

    AItravel advisor. -
    - -
    -
    -
    -
    -
    -
    -
    -
    - -

    -
    -
    - - -
    -
    - -
    -
    -
    -
    -
    - -
    -
    - - -
    -
    - - - - - diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index a91ec5f..0000000 --- a/requirements-docs.txt +++ /dev/null @@ -1,2 +0,0 @@ -mkdocs-material == 9.5.30 -mkdocs-markdownextradata-plugin == 0.2.5 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 59ae9ec..0000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -openai == 1.26.0 -fastapi == 0.111.0 -uvicorn == 0.29.0 -weaviate-client == 3.26.2 -opentelemetry-api == 1.25.0 -opentelemetry-sdk == 1.25.0 -opentelemetry-exporter-otlp-proto-http == 1.25.0 -traceloop-sdk == 0.22.0 \ No newline at end of file diff --git a/run-locally/logconf.ini b/run-locally/logconf.ini deleted file mode 100644 index 5b47861..0000000 --- a/run-locally/logconf.ini +++ /dev/null @@ -1,27 +0,0 @@ -[loggers] -keys=root - -[handlers] -keys=logfile,logconsole - -[formatters] -keys=logformatter - -[logger_root] -level=INFO -handlers=logfile, logconsole - -[formatter_logformatter] -format=[%(asctime)s.%(msecs)03d] %(levelname)s [%(thread)d] - %(message)s - -[handler_logfile] -class=handlers.RotatingFileHandler -level=INFO -args=('logfile.log','a') -formatter=logformatter - -[handler_logconsole] -class=handlers.logging.StreamHandler -level=INFO -args=() -formatter=logformatter \ No newline at end of file diff --git a/run-locally/otelcol-config.yaml b/run-locally/otelcol-config.yaml deleted file mode 100644 index a62f535..0000000 --- a/run-locally/otelcol-config.yaml +++ /dev/null @@ -1,63 +0,0 @@ -receivers: - otlp: - protocols: - grpc: - endpoint: localhost:4317 - http: - endpoint: localhost:4318 - filelog: - include: [ 'c:\path\to\traveladvisor\*.log' ] - - prometheus: - config: - scrape_configs: - - job_name: 'weaviate' - scrape_interval: 10s - static_configs: - - targets: ['localhost:2112'] - - -processors: - transform: - metric_statements: - - context: metric - statements: - # Get count from the histogram. The new metric name will be _count - - extract_count_metric(true) where type == METRIC_DATA_TYPE_HISTOGRAM - - # Get sum from the histogram. The new metric name will be _sum - - extract_sum_metric(true) where type == METRIC_DATA_TYPE_HISTOGRAM - - context: datapoint - statements: - # convert the _sum metrics to gauges. - - convert_sum_to_gauge() where IsMatch(metric.name, ".*_sum") - filter: - error_mode: ignore - metrics: - metric: - - 'type == METRIC_DATA_TYPE_HISTOGRAM' - - 'type == METRIC_DATA_TYPE_SUMMARY' - cumulativetodelta: - -exporters: - debug: - verbosity: detailed - otlphttp: - endpoint: "${env:DT_ENDPOINT}/api/v2/otlp" - headers: - Authorization: "Api-Token ${env:API_TOKEN}" - -service: - pipelines: - traces: - receivers: [otlp] - processors: [] - exporters: [otlphttp, debug] - logs: - receivers: [filelog] - processors: [] - exporters: [otlphttp, debug] - metrics: - receivers: [prometheus] - processors: [transform, filter, cumulativetodelta] - exporters: [otlphttp, debug] \ No newline at end of file diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index acb3cdf..0000000 Binary files a/screenshot.png and /dev/null differ