From 76c3a56bbc6594d25fb786717566f14fa98320be Mon Sep 17 00:00:00 2001 From: joaovictor3g Date: Wed, 17 Apr 2024 21:21:33 -0300 Subject: [PATCH] handles new json format and apply changes --- web/assets/css/tabs.css | 20 ++--- web/assets/examples/cel.json | 28 +++--- web/assets/examples/vap.json | 12 ++- .../{web_hooks.json => webhooks.json} | 15 ++-- web/assets/js/StorageValues.js | 15 ++++ .../js/components/modals/playground-mode.js | 34 ++++--- web/assets/js/editor.js | 12 ++- web/assets/js/main.js | 80 +++++++++-------- web/assets/js/utils/render-functions.js | 90 +++++++++++++------ web/assets/modes.json | 34 ++++--- web/index.html | 7 +- 11 files changed, 213 insertions(+), 134 deletions(-) rename web/assets/examples/{web_hooks.json => webhooks.json} (97%) create mode 100644 web/assets/js/StorageValues.js diff --git a/web/assets/css/tabs.css b/web/assets/css/tabs.css index af0f90f..d2ae1fa 100644 --- a/web/assets/css/tabs.css +++ b/web/assets/css/tabs.css @@ -14,7 +14,7 @@ * limitations under the License. */ -.vap__tabs { +.tabs { --purple: #8447d1; --purple-dark: #ab75f0; --tab-button-width: 150px; @@ -23,7 +23,7 @@ align-items: center; } -.vap__tabs::after { +.tabs::after { content: ""; width: var(--tab-button-width); height: 2px; @@ -36,7 +36,7 @@ transition: transform 300ms; } -.vap__tabs button { +.tabs button { background-color: transparent; border: none; width: var(--tab-button-width); @@ -53,30 +53,30 @@ cursor: pointer; } -.dark .vap__tabs button { +.dark .tabs button { color: white; } -.vap__tabs button:not(.active):hover { +.tabs button:not(.active):hover { background-color: rgba(132, 71, 209, 0.08); color: var(--purple); padding: 4px 8px; border-radius: 4px; } -.dark .vap__tabs button:not(.active):hover { +.dark .tabs button:not(.active):hover { color: var(--purple-dark); } -.vap__tabs button.active { +.tabs button.active { color: var(--purple); } -.dark .vap__tabs button.active { +.dark .tabs button.active { color: var(--purple-dark); } -/* .vap__tabs button.active::after { +/* .tabs button.active::after { content: ""; width: 150px; height: 2px; @@ -87,6 +87,6 @@ bottom: 0; } */ -.dark .vap__tabs button.active::after { +.dark .tabs button.active::after { background-color: var(--purple-dark); } diff --git a/web/assets/examples/cel.json b/web/assets/examples/cel.json index dcff520..7fd0d3c 100644 --- a/web/assets/examples/cel.json +++ b/web/assets/examples/cel.json @@ -3,85 +3,85 @@ { "name": "default", "cel": "// Welcome to the CEL Playground!\n// CEL Playground is an interactive WebAssembly powered environment to explore and experiment with the Common Expression Language (CEL).\n//\n// - Write your CEL expression here\n// - Use the area on the side for input data, in YAML or JSON format\n// - Press 'Run' to evaluate your CEL expression against the input data\n// - Explore our collection of examples for inspiration\n\naccount.balance >= transaction.withdrawal\n || (account.overdraftProtection\n && account.overdraftLimit >= transaction.withdrawal - account.balance)\n", - "data": "# Here is the input data in YAML or JSON format.\n\naccount:\n balance: 500\n overdraftProtection: true\n overdraftLimit: 1000\ntransaction:\n withdrawal: 700\n", + "dataInput": "# Here is the input data in YAML or JSON format.\n\naccount:\n balance: 500\n overdraftProtection: true\n overdraftLimit: 1000\ntransaction:\n withdrawal: 700\n", "category": "default" }, { "name": "Check image registry", "cel": "object.spec.template.spec.containers.all(container,\n params.allowedRegistries.exists(registry,\n ((registry in ['docker.io', 'docker.io/library']) && !container.image.contains('/')) ||\n container.image.startsWith(registry)\n )\n)\n", - "data": "params:\n allowedRegistries: \n - myregistry.com\n - docker.io # use 'docker.io' for Docker Hub\nobject:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: nginx\n image: nginx # the expression looks for this field\n selector:\n matchLabels:\n app: nginx\n", + "dataInput": "params:\n allowedRegistries: \n - myregistry.com\n - docker.io # use 'docker.io' for Docker Hub\nobject:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: nginx\n image: nginx # the expression looks for this field\n selector:\n matchLabels:\n app: nginx\n", "category": "Kubernetes" }, { "name": "Disallow HostPorts", "cel": "// According the Pod Security Standards, HostPorts should be disallowed entirely.\n// https://kubernetes.io/docs/concepts/security/pod-security-standards/#baseline\n\nobject.spec.template.spec.containers.all(container,\n !has(container.ports) ||\n container.ports.all(port,\n !has(port.hostPort) ||\n port.hostPort == 0\n )\n)\n", - "data": "object:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: nginx\n image: nginx\n ports:\n - containerPort: 80\n hostPort: 80 # the expression looks for this field\n selector:\n matchLabels:\n app: nginx\n", + "dataInput": "object:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: nginx\n image: nginx\n ports:\n - containerPort: 80\n hostPort: 80 # the expression looks for this field\n selector:\n matchLabels:\n app: nginx\n", "category": "Kubernetes" }, { "name": "Require non-root containers", "cel": "// According the Pod Security Standards, Containers must be required to run as non-root users.\n// https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\n// Pod or Containers must set `securityContext.runAsNonRoot`\n(\n (has(object.spec.template.spec.securityContext) && has(object.spec.template.spec.securityContext.runAsNonRoot)) ||\n object.spec.template.spec.containers.all(container,\n has(container.securityContext) && has(container.securityContext.runAsNonRoot)\n )\n)\n&&\n\n// Neither Pod nor Containers should set `securityContext.runAsNonRoot` to false\n(\n (!has(object.spec.template.spec.securityContext) || !has(object.spec.template.spec.securityContext.runAsNonRoot) || object.spec.template.spec.securityContext.runAsNonRoot != false)\n &&\n object.spec.template.spec.containers.all(container,\n !has(container.securityContext) || !has(container.securityContext.runAsNonRoot) || container.securityContext.runAsNonRoot != false\n )\n)\n", - "data": "object:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n securityContext:\n runAsNonRoot: true # the expression looks for this field\n containers:\n - name: nginx\n image: nginx\n securityContext:\n runAsNonRoot: false # and this one\n selector:\n matchLabels:\n app: nginx\n", + "dataInput": "object:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n securityContext:\n runAsNonRoot: true # the expression looks for this field\n containers:\n - name: nginx\n image: nginx\n securityContext:\n runAsNonRoot: false # and this one\n selector:\n matchLabels:\n app: nginx\n", "category": "Kubernetes" }, { "name": "Drop ALL capabilities", "cel": "// According the Pod Security Standards, Containers must drop `ALL` capabilities, and are only permitted to add back the `NET_BIND_SERVICE` capability.\n// https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted\n\n// Containers must drop `ALL` capabilities,\nobject.spec.template.spec.containers.all(container,\n has(container.securityContext) &&\n has(container.securityContext.capabilities) &&\n has(container.securityContext.capabilities.drop) &&\n size(container.securityContext.capabilities.drop) >= 1 &&\n container.securityContext.capabilities.drop.exists(c, c == 'ALL')\n)\n&&\n// and are only permitted to add back the `NET_BIND_SERVICE` capability\nobject.spec.template.spec.containers.all(container,\n !has(container.securityContext) ||\n !has(container.securityContext.capabilities) ||\n !has(container.securityContext.capabilities.add) ||\n container.securityContext.capabilities.add.all(cap, cap in params.allowedCapabilities)\n)\n", - "data": "params:\n allowedCapabilities: [NET_BIND_SERVICE]\nobject:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: nginx\n image: nginx\n securityContext:\n capabilities: # the expression looks for this object\n drop: [ALL]\n add: [NET_BIND_SERVICE]\n selector:\n matchLabels:\n app: nginx\n", + "dataInput": "params:\n allowedCapabilities: [NET_BIND_SERVICE]\nobject:\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n name: nginx\n spec:\n template:\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: nginx\n image: nginx\n securityContext:\n capabilities: # the expression looks for this object\n drop: [ALL]\n add: [NET_BIND_SERVICE]\n selector:\n matchLabels:\n app: nginx\n", "category": "Kubernetes" }, { "name": "Semantic version check for image tags (Regex)", "cel": "// Checks if the container images are tagged following the semantic version.\n\nobject.spec.containers.all(container,\n container.image.contains(\"@sha256\") || // allow digest\n container.image.lastIndexOf(\":\") > -1 &&\n container.image.substring(container.image.lastIndexOf(\":\") + 1)\n .matches('^v?(0|[1-9]\\\\d*)\\\\.(0|[1-9]\\\\d*)\\\\.(0|[1-9]\\\\d*)(?:-((?:0|[1-9]\\\\d*|\\\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\\\.(?:0|[1-9]\\\\d*|\\\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\\\+([0-9a-zA-Z-]+(?:\\\\.[0-9a-zA-Z-]+)*))?$')\n // the regex above is suggested by semver.org: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string\n // allowing the \"v\" prefix\n)\n", - "data": "object:\n apiVersion: v1\n kind: Pod\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: ok1\n image: registry.com:80/nginx:v1.2.3-rc.1\n - name: ok2\n image: registry.com:80/nginx@sha256:asdf\n - name: wrong\n image: registry.com:80/nginx:latest # comment the wrong container to test a success scenario\n", + "dataInput": "object:\n apiVersion: v1\n kind: Pod\n metadata:\n name: nginx\n labels:\n app: nginx\n spec:\n containers:\n - name: ok1\n image: registry.com:80/nginx:v1.2.3-rc.1\n - name: ok2\n image: registry.com:80/nginx@sha256:asdf\n - name: wrong\n image: registry.com:80/nginx:latest # comment the wrong container to test a success scenario\n", "category": "Kubernetes" }, { "name": "URLs", "cel": "// Examples of Kubernetes URL CEL library that is available in the playground.\n// https://kubernetes.io/docs/reference/using-api/cel/#kubernetes-url-library\n\nisURL(object.href) \n&& url(object.href).getScheme() == 'https' \n&& url(object.href).getHost() == 'example.com:80'\n&& url(object.href).getHostname() == 'example.com'\n&& url(object.href).getPort() == '80'\n&& url(object.href).getEscapedPath() == '/path'\n&& url(object.href).getQuery().size() == 1\n", - "data": "{\n \"object\": {\n \"href\": \"https://user:pass@example.com:80/path?query=val#fragment\"\n }\n}\n", + "dataInput": "{\n \"object\": {\n \"href\": \"https://user:pass@example.com:80/path?query=val#fragment\"\n }\n}\n", "category": "General" }, { "name": "Check JWT custom claims", "cel": "// Exercise provided in CEL-Go Google Codelab.\n// https://codelabs.developers.google.com/codelabs/cel-go/index.html#10\n// \n// Determine whether the jwt.extra_claims has at least one key that starts\n// with the group prefix, and ensure that all group-like keys have list\n// values containing only strings that end with '@acme.co'.\n\njwt.extra_claims.exists(c, c.startsWith('group'))\n&& jwt.extra_claims\n .filter(c, c.startsWith('group'))\n .all(c, jwt.extra_claims[c]\n .all(g, g.endsWith('@acme.co')))\n", - "data": "jwt: {\n \"iss\": \"auth.acme.com:12350\",\n \"sub\": \"serviceAccount:delegate@acme.co\",\n \"aud\": \"my-project\",\n \"extra_claims\": {\n \"group1\": [\n \"admin@acme.co\",\n \"analyst@acme.co\"\n ],\n \"groupN\": [\n \"forever@acme.co\"\n ],\n \"labels\": [ \"metadata\", \"prod\", \"pii\" ]\n }\n}\n", + "dataInput": "jwt: {\n \"iss\": \"auth.acme.com:12350\",\n \"sub\": \"serviceAccount:delegate@acme.co\",\n \"aud\": \"my-project\",\n \"extra_claims\": {\n \"group1\": [\n \"admin@acme.co\",\n \"analyst@acme.co\"\n ],\n \"groupN\": [\n \"forever@acme.co\"\n ],\n \"labels\": [ \"metadata\", \"prod\", \"pii\" ]\n }\n}\n", "category": "General" }, { "name": "Optional", "cel": "object.?foo.orValue(\"fallback\")", - "data": "object: {}", + "dataInput": "object: {}", "category": "General" }, { "name": "Duration and timestamp", "cel": "// Validate that 'expired' date is after a 'created' date plus a 'ttl' duration\nhas(object.expired) && \ntimestamp(object.created) + duration(object.ttl) < timestamp(object.expired)\n", - "data": "object:\n created: \"2023-06-14T02:00:14+00:00\"\n ttl: \"5m\"\n expired: \"2023-06-14T02:06:14+00:00\"\n", + "dataInput": "object:\n created: \"2023-06-14T02:00:14+00:00\"\n ttl: \"5m\"\n expired: \"2023-06-14T02:06:14+00:00\"\n", "category": "General" }, { "name": "Quantity", "cel": "// Quantity library introduced in Kubernetes 1.28\n\nisQuantity(object.memory) && \nquantity(object.memory)\n .add(quantity(\"700M\"))\n .sub(1) // test without this subtraction\n .isLessThan(quantity(object.limit))\n", - "data": "object:\n memory: 1.3G\n limit: 2G\n", + "dataInput": "object:\n memory: 1.3G\n limit: 2G\n", "category": "General" }, { "name": "Access Log Filtering", "cel": "// Use CEL to filter access logs in Istio by response code or target cluster.\n// https://istio.io/latest/docs/tasks/observability/logs/telemetry-api/#get-started-with-telemetry-api\n//\n// apiVersion: telemetry.istio.io/v1alpha1\n// kind: Telemetry\n// metadata:\n// name: default-exception-logging\n// namespace: istio-system\n// spec:\n// accessLogging:\n// - providers:\n// - name: otel\n// filter:\n// expression: \"response.code >= 400 || xds.cluster_name == 'BlackHoleCluster' || xds.cluster_name == 'PassthroughCluster' \"\n\nresponse.code >= 400 || (xds.cluster_name == 'BlackHoleCluster' || xds.cluster_name == 'PassthroughCluster')\n", - "data": "# The following configuration is true access logs only when the response code is greater or equal to 400\n# or the request went to the BlackHoleCluster or the PassthroughCluster\nrequest:\n duration: \"173.403244ms\"\n headers:\n x-request-id: \"e8e687ab-fbbd-4662-8416-11761a29de36\"\n host: \"httpbin.org\"\n id: \"e8e687ab-fbbd-4662-8416-11761a29de36\"\n method: \"GET\"\n path: \"/get\"\n protocol: \"HTTP/1.1\"\n query: \"\"\n referer: null\n scheme: \"http\"\n size: 0\n time: \"2023-10-13T20:32:04.7006+00:00\"\n total_size: 1000\n url_path: \"/get\"\n useragent: \"curl/8.2.1\"\nresponse:\n code: 200\n code_details: \"via_upstream\"\n flags: 0\n grpc_status: 2\n headers:\n content-type: \"application/json\"\n size: 1181\n total_size: 1377\nconnection:\n id: 269\n mtls: false\n requested_server_name: \"\"\nupstream:\n address: \"54.80.46.162:80\"\n local_address: \"10.244.0.37:51128\"\n port: 80\n transport_failure_reason: \"\"\nxds:\n cluster_metadata: \"\"\n cluster_name: \"PassthroughCluster\"\n filter_chain_name: \"\"\n route_metadata: \"\"\n route_name: \"allow_any\"\n upstream_host_metadata: \"NULL\"\n", + "dataInput": "# The following configuration is true access logs only when the response code is greater or equal to 400\n# or the request went to the BlackHoleCluster or the PassthroughCluster\nrequest:\n duration: \"173.403244ms\"\n headers:\n x-request-id: \"e8e687ab-fbbd-4662-8416-11761a29de36\"\n host: \"httpbin.org\"\n id: \"e8e687ab-fbbd-4662-8416-11761a29de36\"\n method: \"GET\"\n path: \"/get\"\n protocol: \"HTTP/1.1\"\n query: \"\"\n referer: null\n scheme: \"http\"\n size: 0\n time: \"2023-10-13T20:32:04.7006+00:00\"\n total_size: 1000\n url_path: \"/get\"\n useragent: \"curl/8.2.1\"\nresponse:\n code: 200\n code_details: \"via_upstream\"\n flags: 0\n grpc_status: 2\n headers:\n content-type: \"application/json\"\n size: 1181\n total_size: 1377\nconnection:\n id: 269\n mtls: false\n requested_server_name: \"\"\nupstream:\n address: \"54.80.46.162:80\"\n local_address: \"10.244.0.37:51128\"\n port: 80\n transport_failure_reason: \"\"\nxds:\n cluster_metadata: \"\"\n cluster_name: \"PassthroughCluster\"\n filter_chain_name: \"\"\n route_metadata: \"\"\n route_name: \"allow_any\"\n upstream_host_metadata: \"NULL\"\n", "category": "Istio" }, { "name": "Custom Metrics", "cel": "// Use CEL to customize the metrics that Istio generates\n// https://istio.io/latest/docs/tasks/observability/metrics/customize-metrics/#use-expressions-for-values\n// \n// apiVersion: telemetry.istio.io/v1alpha1\n// kind: Telemetry\n// metadata:\n// name: namespace-metrics\n// spec:\n// metrics:\n// - providers:\n// - name: prometheus\n// overrides:\n// - match:\n// metric: REQUEST_COUNT\n// tagOverrides:\n// destination_port:\n// value: \"string(destination.port)\" # <--- CEL\n// request_host:\n// value: \"request.host\" # <--- CEL\n\nhas(request.host) ? request.host : \"unknown\"\n", - "data": "request:\n duration: \"4.144461ms\"\n headers:\n x-request-id: \"7a61a297-e508-43b7-94e8-b3919367e2d2\"\n host: \"echo\"\n id: \"7a61a297-e508-43b7-94e8-b3919367e2d2\"\n method: \"GET\"\n path: \"/\"\n protocol: \"HTTP/1.1\"\n query: \"\"\n referer: null\n scheme: \"http\"\n size: 0\n time: \"2023-10-13T20:30:38.106932+00:00\"\n total_size: 478\n url_path: \"/\"\n useragent: \"curl/8.2.1\"\nresponse:\n code: \"200\"\n code_details: \"via_upstream\"\n flags: \"0\"\n grpc_status: \"2\"\n headers:\n content-type: \"application/json\"\n size: 714\n total_size: 1594\nconnection:\n id: 36\n mtls: true\n dns_san_local_certificate: null\n dns_san_peer_certificate: null\n requested_server_name: \"outbound_.80_._.echo.default.svc.cluster.local\"\n sha256_peer_certificate_digest: \"1386a353d125910412d0ecfa7abb2f3fbee9ff3c77dd4d5c19312a8d51e27557\"\n subject_local_certificate: \"\"\n subject_peer_certificate: \"\"\n termination_details: null\n tls_version: \"TLSv1.3\"\n uri_san_local_certificate: \"spiffe://cluster.local/ns/default/sa/default\"\n uri_san_peer_certificate: \"spiffe://cluster.local/ns/default/sa/default\"\nupstream:\n address: \"10.244.0.38:80\"\n dns_san_local_certificate: null\n dns_san_peer_certificate: null\n local_address: \"127.0.0.6:58023\"\n port: 80\n sha256_peer_certificate_digest: null\n subject_local_certificate: null\n subject_peer_certificate: null\n tls_version: null\n transport_failure_reason: \"\"\n uri_san_local_certificate: null\n uri_san_peer_certificate: null\nxds:\n cluster_metadata:\n filter_metadata:\n istio:\n services:\n - host: \"echo.default.svc.cluster.local\"\n name: \"echo\"\n namespace: \"default\"\n cluster_name: \"inbound|80||\"\n filter_chain_name: \"0.0.0.0_80\"\n route_metadata: \"\"\n route_name: \"default\"\n upstream_host_metadata: \"NULL\"\n", + "dataInput": "request:\n duration: \"4.144461ms\"\n headers:\n x-request-id: \"7a61a297-e508-43b7-94e8-b3919367e2d2\"\n host: \"echo\"\n id: \"7a61a297-e508-43b7-94e8-b3919367e2d2\"\n method: \"GET\"\n path: \"/\"\n protocol: \"HTTP/1.1\"\n query: \"\"\n referer: null\n scheme: \"http\"\n size: 0\n time: \"2023-10-13T20:30:38.106932+00:00\"\n total_size: 478\n url_path: \"/\"\n useragent: \"curl/8.2.1\"\nresponse:\n code: \"200\"\n code_details: \"via_upstream\"\n flags: \"0\"\n grpc_status: \"2\"\n headers:\n content-type: \"application/json\"\n size: 714\n total_size: 1594\nconnection:\n id: 36\n mtls: true\n dns_san_local_certificate: null\n dns_san_peer_certificate: null\n requested_server_name: \"outbound_.80_._.echo.default.svc.cluster.local\"\n sha256_peer_certificate_digest: \"1386a353d125910412d0ecfa7abb2f3fbee9ff3c77dd4d5c19312a8d51e27557\"\n subject_local_certificate: \"\"\n subject_peer_certificate: \"\"\n termination_details: null\n tls_version: \"TLSv1.3\"\n uri_san_local_certificate: \"spiffe://cluster.local/ns/default/sa/default\"\n uri_san_peer_certificate: \"spiffe://cluster.local/ns/default/sa/default\"\nupstream:\n address: \"10.244.0.38:80\"\n dns_san_local_certificate: null\n dns_san_peer_certificate: null\n local_address: \"127.0.0.6:58023\"\n port: 80\n sha256_peer_certificate_digest: null\n subject_local_certificate: null\n subject_peer_certificate: null\n tls_version: null\n transport_failure_reason: \"\"\n uri_san_local_certificate: null\n uri_san_peer_certificate: null\nxds:\n cluster_metadata:\n filter_metadata:\n istio:\n services:\n - host: \"echo.default.svc.cluster.local\"\n name: \"echo\"\n namespace: \"default\"\n cluster_name: \"inbound|80||\"\n filter_chain_name: \"0.0.0.0_80\"\n route_metadata: \"\"\n route_name: \"default\"\n upstream_host_metadata: \"NULL\"\n", "category": "Istio" }, { "name": "Blank", "cel": "", - "data": "", + "dataInput": "", "category": "Blank" } ], diff --git a/web/assets/examples/vap.json b/web/assets/examples/vap.json index ffce93c..b29967a 100644 --- a/web/assets/examples/vap.json +++ b/web/assets/examples/vap.json @@ -7,7 +7,8 @@ "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 3\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: gcr.io/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataNamespace": "", "dataRequest": "", - "dataAuthorizer": "" + "dataAuthorizer": "", + "category": "Validation" }, { "name": "Variables in Validation", @@ -16,7 +17,8 @@ "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n exempt: false\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 3\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: prod.policy.example.com/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataNamespace": "apiVersion: v1\nkind: Namespace\nmetadata:\n creationTimestamp: \"2023-03-10T13:50:03Z\"\n labels:\n kubernetes.io/metadata.name: default\n environment: prod\n name: default\n resourceVersion: \"5932\"\n uid: 01d428dd-9515-4e9c-98a3-d8a278ee0125\nspec:\n finalizers:\n - kubernetes\nstatus:\n phase: Active\n", "dataRequest": "", - "dataAuthorizer": "" + "dataAuthorizer": "", + "category": "Validation" }, { "name": "Match Conditions", @@ -25,7 +27,8 @@ "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n environment: prod\n exempt: false\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 3\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: prod.registry.io/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataNamespace": "", "dataRequest": "uid: 705ab4f5-6393-11e8-b7cc-42010a800002\nkind:\n group: apps\n version: v1\n resource: deployments\nresource:\n group: apps\n version: v1\n resource: deployments\nrequestKind:\n group: apps\n version: v1\n resource: deployments\nrequestResource:\n group: apps\n version: v1\n resource: deployments\nname: kubernetes-bootcamp\nnamespace: default\noperation: CREATE\nuserInfo:\n username: admin\n uid: 014fbff9a07c\n groups:\n - system:authenticated\n - my-admin-group\n extra:\n some-key:\n - some-value1\n - some-value2\n", - "dataAuthorizer": "" + "dataAuthorizer": "", + "category": "Conditions" }, { "name": "Audit Annotations", @@ -34,7 +37,8 @@ "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n environment: prod\n exempt: false\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 3\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: prod.registry.io/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataNamespace": "", "dataRequest": "", - "dataAuthorizer": "" + "dataAuthorizer": "", + "category": "Audit" } ], "versions": { diff --git a/web/assets/examples/web_hooks.json b/web/assets/examples/webhooks.json similarity index 97% rename from web/assets/examples/web_hooks.json rename to web/assets/examples/webhooks.json index 857932e..681da02 100644 --- a/web/assets/examples/web_hooks.json +++ b/web/assets/examples/webhooks.json @@ -6,7 +6,8 @@ "dataOriginal": "", "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 1\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: gcr.io/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataRequest": "uid: 705ab4f5-6393-11e8-b7cc-42010a800002\nkind:\n group: apps\n version: v1\n resource: deployments\nresource:\n group: apps\n version: v1\n resource: deployments\nrequestKind:\n group: apps\n version: v1\n resource: deployments\nrequestResource:\n group: apps\n version: v1\n resource: deployments\nname: kubernetes-bootcamp\nnamespace: default\noperation: CREATE\nuserInfo:\n username: admin\n uid: 014fbff9a07c\n groups:\n - system:authenticated\n - my-admin-group\n extra:\n some-key:\n - some-value1\n - some-value2\n", - "dataAuthorizer": "" + "dataAuthorizer": "", + "category": "Request" }, { "name": "Request Ignore Leases", @@ -14,7 +15,8 @@ "dataOriginal": "", "dataUpdated": "apiVersion: coordination.k8s.io/v1\nkind: Lease\nmetadata:\n name: ingress-nginx-leader\n namespace: ingress-nginx\nspec:\n acquireTime: \"2023-11-24T16:51:02.229818Z\"\n holderIdentity: ingress-nginx-controller-6597456577-s5h9w\n leaseDurationSeconds: 30\n leaseTransitions: 7\n renewTime: \"2024-04-09T21:59:30.694589Z\"\n", "dataRequest": "uid: 705ab4f5-6393-11e8-b7cc-42010a800002\nkind:\n group: coordination.k8s.io\n version: v1\n resource: leases\nresource:\n group: coordination.k8s.io\n version: v1\n resource: leases\nrequestKind:\n group: coordination.k8s.io\n version: v1\n resource: leases\nrequestResource:\n group: coordination.k8s.io\n version: v1\n resource: leases\nname: ingress-nginx-leader\nnamespace: ingress-nginx\noperation: CREATE\nuserInfo:\n username: admin\n uid: 014fbff9a07c\n groups:\n - system:authenticated\n - my-admin-group\n extra:\n some-key:\n - some-value1\n - some-value2\n", - "dataAuthorizer": "" + "dataAuthorizer": "", + "category": "Request" }, { "name": "Request Ignore Kubelet", @@ -22,7 +24,8 @@ "dataOriginal": "", "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 1\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: gcr.io/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataRequest": "uid: 705ab4f5-6393-11e8-b7cc-42010a800002\nkind:\n group: apps\n version: v1\n resource: deployments\nresource:\n group: apps\n version: v1\n resource: deployments\nrequestKind:\n group: apps\n version: v1\n resource: deployments\nrequestResource:\n group: apps\n version: v1\n resource: deployments\nname: kubernetes-bootcamp\nnamespace: default\noperation: CREATE\nuserInfo:\n username: node1\n uid: 014fbff9a07c\n groups:\n - system:nodes\n extra:\n some-key:\n - some-value1\n - some-value2\n", - "dataAuthorizer": "" + "dataAuthorizer": "", + "category": "Request" }, { "name": "Authorizer Accept", @@ -30,7 +33,8 @@ "dataOriginal": "", "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 1\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: gcr.io/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataRequest": "uid: 705ab4f5-6393-11e8-b7cc-42010a800002\nkind:\n group: apps\n version: v1\n resource: deployments\nresource:\n group: apps\n version: v1\n resource: deployments\nrequestKind:\n group: apps\n version: v1\n resource: deployments\nrequestResource:\n group: apps\n version: v1\n resource: deployments\nname: kubernetes-bootcamp\nnamespace: default\noperation: CREATE\nuserInfo:\n username: admin\n uid: 014fbff9a07c\n groups:\n - system:authenticated\n - my-admin-group\n extra:\n some-key:\n - some-value1\n - some-value2\n", - "dataAuthorizer": "paths:\ngroups:\nserviceAccounts:\n" + "dataAuthorizer": "paths:\ngroups:\nserviceAccounts:\n", + "category": "Authorizer" }, { "name": "Authorizer Ignore breakglass", @@ -38,7 +42,8 @@ "dataOriginal": "", "dataUpdated": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: kubernetes-bootcamp\n name: kubernetes-bootcamp\n namespace: default\nspec:\n progressDeadlineSeconds: 600\n replicas: 1\n revisionHistoryLimit: 10\n selector:\n matchLabels:\n app: kubernetes-bootcamp\n strategy:\n rollingUpdate:\n maxSurge: 25%\n maxUnavailable: 25%\n type: RollingUpdate\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: kubernetes-bootcamp\n spec:\n containers:\n - image: gcr.io/google-samples/kubernetes-bootcamp:v1\n imagePullPolicy: IfNotPresent\n name: kubernetes-bootcamp\n resources: {}\n terminationMessagePath: /dev/termination-log\n terminationMessagePolicy: File\n dnsPolicy: ClusterFirst\n restartPolicy: Always\n schedulerName: default-scheduler\n securityContext: {}\n terminationGracePeriodSeconds: 30\n", "dataRequest": "uid: 705ab4f5-6393-11e8-b7cc-42010a800002\nkind:\n group: apps\n version: v1\n resource: deployments\nresource:\n group: apps\n version: v1\n resource: deployments\nrequestKind:\n group: apps\n version: v1\n resource: deployments\nrequestResource:\n group: apps\n version: v1\n resource: deployments\nname: kubernetes-bootcamp\nnamespace: default\noperation: CREATE\nuserInfo:\n username: admin\n uid: 014fbff9a07c\n groups:\n - system:authenticated\n - my-admin-group\n extra:\n some-key:\n - some-value1\n - some-value2\n", - "dataAuthorizer": "paths:\ngroups:\n admissionregistration.k8s.io:\n resources:\n validatingwebhookconfigurations:\n checks:\n \"\":\n rbac.my-webhook.example.com:\n breakglass:\n decision: allow\nserviceAccounts:\n" + "dataAuthorizer": "paths:\ngroups:\n admissionregistration.k8s.io:\n resources:\n validatingwebhookconfigurations:\n checks:\n \"\":\n rbac.my-webhook.example.com:\n breakglass:\n decision: allow\nserviceAccounts:\n", + "category": "Authorizer" } ], "versions": { diff --git a/web/assets/js/StorageValues.js b/web/assets/js/StorageValues.js new file mode 100644 index 0000000..4a18f91 --- /dev/null +++ b/web/assets/js/StorageValues.js @@ -0,0 +1,15 @@ +const KEY = "values"; + +export class StorageValues { + constructor() { + this.values = JSON.parse(localStorage.getItem(KEY)); + } + + setValues(values) { + localStorage.setItem(KEY, JSON.stringify(values)); + } + + getValues() { + return this.values; + } +} diff --git a/web/assets/js/components/modals/playground-mode.js b/web/assets/js/components/modals/playground-mode.js index 264a639..b84241b 100644 --- a/web/assets/js/components/modals/playground-mode.js +++ b/web/assets/js/components/modals/playground-mode.js @@ -16,14 +16,13 @@ import { renderExamplesInSelectInstance, + renderExpressionContent, renderTabs, } from "../../utils/render-functions.js"; -import { AceEditor } from "../../editor.js"; import { ModesService } from "../../services/modes.js"; import { ExampleService } from "../../services/examples.js"; - -const celEditor = new AceEditor("cel-input"); -const dataEditor = new AceEditor("data-input"); +import { StorageValues } from "../../StorageValues.js"; +import { loadCurrentTheme } from "../../main.js"; const playgroundModesModalEl = document.getElementById( "playground-modes__modal" @@ -119,13 +118,9 @@ function handleModeClick(event, mode, element) { .querySelectorAll(".playground-modes__options--option") .forEach((option) => option.classList.remove("active")); - localStorage.removeItem("example-selected"); - element.classList.add("active"); renderUIChangesByMode(mode); localStorage.setItem(localStorageModeKey, value); - celEditor.setValue("", -1); - dataEditor.setValue("", -1); setTimeout(() => modal.hide(), 1000); } @@ -186,15 +181,26 @@ function createInputElement(mode) { function renderUIChangesByMode(mode) { const titleEl = document.querySelector(".title.expression__square"); const toggleModeHolder = document.querySelector(".modes__container-holder"); - titleEl.innerHTML = mode.name; toggleModeHolder.innerHTML = mode.name; ExampleService.getExampleContentById(mode).then((examples) => { - renderExamplesInSelectInstance(examples, callbackFns); - - function callbackFns(example) { - renderTabs(example); - } + renderExpressionContent(mode, examples); + renderTabs(mode, examples); + renderExamplesInSelectInstance(mode, examples, handleSaveValues); + loadCurrentTheme(); }); } + +export function handleSaveValues(mode, example) { + const storageValues = new StorageValues(); + + delete example.category; + delete example.name; + + const valuesToSave = { + [mode.id]: mode.id, + ...example, + }; + storageValues.setValues(valuesToSave); +} diff --git a/web/assets/js/editor.js b/web/assets/js/editor.js index a558e56..c2acdf7 100644 --- a/web/assets/js/editor.js +++ b/web/assets/js/editor.js @@ -25,12 +25,14 @@ const EDITOR_DEFAULTS = { }, }; +const DEFAULT_THEME = "ace/theme/clouds"; + class AceEditor { - constructor(id) { + constructor(id, mode) { this.editor = ace.edit(id); - this.editor.setTheme(EDITOR_DEFAULTS[id].theme); + this.editor.setTheme(DEFAULT_THEME); this.editor.setShowPrintMargin(false); - this.editor.getSession().setMode(EDITOR_DEFAULTS[id].mode); + this.editor.getSession().setMode(mode); this.editor.getSession().setUseWorker(false); } @@ -41,10 +43,6 @@ class AceEditor { getValue() { return this.editor.getValue(); } - - setExpressionSyntax(syntax) { - this.editor.getSession().setMode(`ace/mode/${syntax ?? "javascript"}`); - } } export { AceEditor }; diff --git a/web/assets/js/main.js b/web/assets/js/main.js index 12f2e42..58872ce 100644 --- a/web/assets/js/main.js +++ b/web/assets/js/main.js @@ -15,8 +15,8 @@ */ import { setCost } from "./utils/render-functions.js"; +import { StorageValues } from "./StorageValues.js"; import { AceEditor } from "./editor.js"; -import { renderResultAccordions } from "./components/accordions/result.js"; // Add the following polyfill for Microsoft Edge 17/18 support: // @@ -29,59 +29,68 @@ if (!WebAssembly.instantiateStreaming) { }; } -const celEditor = new AceEditor("cel-input"); -const dataEditor = new AceEditor("data-input"); const output = document.getElementById("output"); +function getRunValues() { + const storageValues = new StorageValues(); + return storageValues.getValues(); +} + function run() { - const data = dataEditor.getValue(); - const expression = celEditor.getValue(); const cost = document.getElementById("cost"); - + const values = getRunValues(); output.value = "Evaluating..."; setCost(""); - - // const result = eval(expression, data); - const result = { - output: { - validations: [ - { cost: 8, result: true, isError: false }, - { cost: 18, result: false, isError: false }, - { cost: 9, result: null, isError: true }, - ], - }, - isError: false, - }; + const result = eval(values); const { output: resultOutput, isError } = result; - // if (isError) { - // output.value = resultOutput; - // output.style.color = "red"; - // } else { - const [firstOutputKey] = Object.keys(resultOutput); - renderResultAccordions(result.output[firstOutputKey], firstOutputKey); - // output.value = JSON.stringify(result); - // output.style.color = "white"; - // setCost(cost); - // } + if (isError) { + output.value = resultOutput; + output.style.color = "red"; + } else { + output.value = JSON.stringify(result); + output.style.color = "white"; + setCost(cost); + } } window.addEventListener("load", () => { let theme = localStorage.getItem("theme"); if (theme === "dark") { - toggleMode("dark"); + toggleTheme("dark"); } }); const toggleBtn = document.getElementsByClassName("toggle-theme")[0]; toggleBtn.addEventListener("click", function () { let currTheme = localStorage.getItem("theme"); - if (currTheme === "dark") toggleMode("light"); - else toggleMode("dark"); + if (currTheme === "dark") toggleTheme("light"); + else toggleTheme("dark"); }); -function toggleMode(theme) { +export function loadCurrentTheme() { + const theme = localStorage.getItem("theme"); + const exprEditor = new AceEditor( + localStorage.getItem(localStorageModeKey) ?? "cel", + "ace/theme/clouds" + ); + const editorsInputEl = document.querySelectorAll(".tabs-button"); + + if (theme === "dark") { + exprEditor.editor.setTheme("ace/theme/tomorrow_night"); + editorsInputEl.forEach((editor) => { + new AceEditor(editor.id, "ace/theme/tomorrow_night"); + }); + } else { + exprEditor.editor.setTheme("ace/theme/clouds"); + editorsInputEl.forEach((editor) => { + new AceEditor(editor.id, "ace/theme/clouds"); + }); + } +} + +function toggleTheme(theme) { let toggleIcon = document.getElementsByClassName("toggle-theme__icon")[0]; let celLogo = document.getElementsByClassName("cel-logo")[0]; let copyIcon = document.querySelectorAll(".editor-copy-icon"); @@ -89,22 +98,17 @@ function toggleMode(theme) { if (theme === "dark") { document.body.classList.add("dark"); toggleIcon.src = "./assets/img/moon.svg"; - celEditor.editor.setTheme("ace/theme/tomorrow_night"); - dataEditor.editor.setTheme("ace/theme/tomorrow_night"); celLogo.src = "./assets/img/logo-dark.svg"; copyIcon[0].src = "./assets/img/copy-dark.svg"; copyIcon[1].src = "./assets/img/copy-dark.svg"; - localStorage.setItem("theme", "dark"); } else { document.body.classList.remove("dark"); toggleIcon.src = "./assets/img/sun.svg"; - celEditor.editor.setTheme("ace/theme/clouds"); - dataEditor.editor.setTheme("ace/theme/clouds"); celLogo.src = "./assets/img/logo.svg"; copyIcon[0].src = "./assets/img/copy.svg"; copyIcon[1].src = "./assets/img/copy.svg"; - localStorage.setItem("theme", "light"); } + localStorage.setItem("theme", theme); } function share() { diff --git a/web/assets/js/utils/render-functions.js b/web/assets/js/utils/render-functions.js index bd21330..ff26f18 100644 --- a/web/assets/js/utils/render-functions.js +++ b/web/assets/js/utils/render-functions.js @@ -16,12 +16,10 @@ import { AceEditor } from "../editor.js"; -const celEditor = new AceEditor("cel-input"); -const dataEditor = new AceEditor("data-input"); const examplesList = document.getElementById("examples"); const selectInstance = NiceSelect.bind(examplesList); -export function renderExamplesInSelectInstance(examples, callbackFn) { +export function renderExamplesInSelectInstance(mode, examples, callbackFn) { examplesList.innerHTML = ``; @@ -39,12 +37,6 @@ export function renderExamplesInSelectInstance(examples, callbackFn) { ); examplesByCategory.forEach((example, i) => { - if (i === 0) { - const [firstValue] = example.value; - setEditors(firstValue.data, firstValue.inputs[0].data); - renderTabs(firstValue); - } - const optGroup = document.createElement("optgroup"); optGroup.label = example.label; @@ -58,8 +50,9 @@ export function renderExamplesInSelectInstance(examples, callbackFn) { }); if (example.label === "default") { - if (!urlParams.has("content")) - setEditors(example.value[0].data, example.value[0].inputs[0].data); + if (!urlParams.has("content")) { + } + // setEditors(example.value[0].data, example.value[0].inputs[0].data); } else if (example.label === "Blank") { return; } else { @@ -73,15 +66,16 @@ export function renderExamplesInSelectInstance(examples, callbackFn) { examplesList.appendChild(blankOption); selectInstance.update(); - examplesList.addEventListener("change", (event) => { const example = examples.find( (example) => example.name === event.target.value ); - if (event.target.value === "Blank") setEditors("", ""); + if (event.target.value === "Blank") return; if (!example) return; - setEditors(example.data, example.inputs[0].data); - callbackFn(example); + + handleFillExpressionContent(mode, example); + handleFillTabContent(mode, example); + callbackFn(mode, example); setCost(""); output.value = ""; }); @@ -92,27 +86,60 @@ export function setCost(cost) { costElem.innerText = cost || "-"; } -export function renderTabs(example) { - const { inputs } = example; +export function handleFillExpressionContent(mode, example) { + const exprEditor = new AceEditor(mode.id, mode.mode); + exprEditor.setValue(example[mode.id], -1); +} + +export function handleFillTabContent(mode, example) { + mode.tabs.forEach((tab) => { + const containerId = tab.id; + const inputEditor = new AceEditor(containerId, mode.mode); + inputEditor.setValue(example[containerId], -1); + }); +} + +export function renderExpressionContent(mode, examples) { + const exprInput = document.querySelector(".editor__input.expr__input"); + exprInput.id = mode.id; + + const currentExample = getCurrentExample(mode, examples); + + const exprEditor = new AceEditor(mode.id, mode.mode); + exprEditor.setValue(currentExample?.[mode.id] ?? mode[mode.id], -1); +} + +export function renderTabs(mode, examples) { + const { tabs } = mode; + + const dataInput = document.querySelector(".editor__input.data__input"); + const holderElement = document.getElementById("tab"); holderElement.innerHTML = ""; const divParent = document.createElement("div"); - divParent.className = "vap__tabs"; - divParent.id = "vap__tabs"; + divParent.className = "tabs"; + divParent.id = "tabs"; + + tabs.forEach((tab, idx) => { + const currentExample = getCurrentExample(mode, examples); - inputs.forEach((input, idx) => { + if (!currentExample) return; + + dataInput.id = tab.id; + const inputEditor = new AceEditor(tab.id, mode.mode); const tabButton = document.createElement("button"); - tabButton.innerHTML = input.name; - tabButton.className = "vap__tabs-button"; - tabButton.id = input.id; + tabButton.innerHTML = tab.name; + tabButton.className = "tabs-button"; + tabButton.id = tab.id; tabButton.onclick = () => { - const allButtons = divParent?.querySelectorAll(".vap__tabs-button"); + const allButtons = divParent?.querySelectorAll(".tabs-button"); allButtons.forEach(removeActiveClass); if (tabButton.classList.contains("active")) removeActiveClass(tabButton); else addActiveClass(tabButton); divParent.setAttribute("style", `--current-tab: ${idx}`); - setEditors(example.data, input.data); + + inputEditor.setValue(currentExample[tab.id], -1); }; if (idx === 0) addActiveClass(tabButton); @@ -120,7 +147,9 @@ export function renderTabs(example) { }); holderElement.appendChild(divParent); - if (inputs.length <= 1) holderElement.innerHTML = ""; + // handleFillTabContent(mode, examples[0]); + + if (tabs.length <= 1) holderElement.innerHTML = ""; function removeActiveClass(element) { element.classList.remove("active"); @@ -131,7 +160,10 @@ export function renderTabs(example) { } } -function setEditors(expressionEditorValue, inputEditorValue) { - celEditor.setValue(expressionEditorValue, -1); - dataEditor.setValue(inputEditorValue, -1); +function getCurrentExample(mode, examples) { + const currentExample = examples.find((example) => + Object.keys(example).includes(mode.id) + ); + + return currentExample; } diff --git a/web/assets/modes.json b/web/assets/modes.json index 2f3490d..98e134e 100644 --- a/web/assets/modes.json +++ b/web/assets/modes.json @@ -2,58 +2,72 @@ { "id": "cel", "name": "CEL Expression", + "mode": "ace/mode/javascript", "tabs": [ { "id": "dataInput", - "Name": "Input" + "name": "Input", + "mode": "ace/mode/javascript" } ] }, { "id": "vap", "name": "Validating Admission Policy", + "mode": "ace/mode/yaml", "tabs": [ { "id": "dataOriginal", - "Name": "Original" + "name": "Original", + "mode": "ace/mode/yaml" }, { "id": "dataUpdated", - "Name": "Updated" + "name": "Updated", + "mode": "ace/mode/yaml" }, { "id": "dataNamespace", - "Name": "Namespace" + "name": "Namespace", + "mode": "ace/mode/yaml" }, { "id": "dataRequest", - "Name": "Request" + "name": "Request", + "mode": "ace/mode/yaml" }, { "id": "dataAuthorizer", - "Name": "Authorizer" + "name": "Authorizer", + "mode": "ace/mode/yaml" } ] }, { "id": "webhooks", "name": "Web Hooks", + "mode": "ace/mode/yaml", + "tabs": [ { "id": "dataOriginal", - "Name": "Original" + "name": "Original", + "mode": "ace/mode/yaml" }, { "id": "dataUpdated", - "Name": "Updated" + "name": "Updated", + "mode": "ace/mode/yaml" }, { "id": "dataRequest", - "Name": "Request" + "name": "Request", + "mode": "ace/mode/yaml" }, { "id": "dataAuthorizer", - "Name": "Authorizer" + "name": "Authorizer", + "mode": "ace/mode/yaml" } ] } diff --git a/web/index.html b/web/index.html index 60fb6c8..77e74a0 100644 --- a/web/index.html +++ b/web/index.html @@ -24,7 +24,7 @@ - +
Copied!
-
+
@@ -222,7 +222,7 @@
Copied!
-
+
@@ -313,6 +313,7 @@

Playground modes

+