configeditor/index.mjs (166 lines of code) (raw):
/**
* Copyright 2024 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.
*/
/**
* @fileoverview Support scripts for browser-based config editor.
*/
// eslint-disable-next-line
import {
JSONEditor,
createAjvValidator,
renderJSONSchemaEnum,
renderValue,
} from "./build/vanilla-jsoneditor/standalone.js";
import schema from "./build/autoscaler-config.schema.json" with { type: "json" };
import * as JsYaml from "./build/js-yaml/dist/js-yaml.mjs";
/** @typedef {import("vanilla-jsoneditor").Content} Content */
/** @type {JSONEditor} */
let editor;
/**
* Checks if the JSON is valid, and if so, copy it to the YAML textarea.
*
* If it is not, but _is_ valid YAML, convert it to JSON and update both the
* JSON editor and the YAML textarea.
*
* @param {Content} content
* @param {Content} previousContent
* @param {{
* contentErrors: import("vanilla-jsoneditor").ContentErrors | null,
* patchResult: import("vanilla-jsoneditor").JSONPatchResult | null
* }} changeStatus
*/
function jsonEditorChangeHandler(newcontent, previousContent, changeStatus) {
const yamlTextarea = document.getElementById("yamlequivalent");
if (changeStatus?.contentErrors?.parseError) {
console.log(
"jsonEditorChangeHandler - got JSON parsing errors %o",
changeStatus.contentErrors,
);
if (newcontent.text?.search("\nkind: ConfigMap\n") >= 0) {
// Check if it is valid YAML text to see if we need to convert it back
// to JSON.
try {
const configMap = JsYaml.load(newcontent.text);
if (
configMap &&
configMap.kind === "ConfigMap" &&
configMap.data &&
Object.values(configMap.data)[0]
) {
// The autoscaler ConfigMap data is YAML stored as text in a YAML,
// so we need to re-parse the data object.
const configMapData = JsYaml.load(Object.values(configMap.data)[0]);
console.log("got yaml configMap data object: %o", configMapData);
// Asynchronously update the content with the parsed configmap data.
// This is because JSON editor likes to finish the onchange before
// anything else happens!
setTimeout(() => {
/** @type {Content} */
const content = { json: configMapData };
editor.updateProps({
content,
mode: "text",
selection: null,
});
editor.refresh();
// Trigger refresh of YAML textarea.
updateYamlWithJsonContent(content);
}, 100);
return;
}
} catch (e) {
console.log("not valid yaml " + e);
}
}
// Some other unparsable JSON.
yamlTextarea.setAttribute("disabled", "true");
} else {
// Got valid JSON, even if it might not be valid Autoscaler config, we
// update the YAML version.
updateYamlWithJsonContent(newcontent);
}
}
/**
* Converts the content from JSONEditor to YAML and stores it in the YAML
* textarea.
*
* @param {Content} content
*/
function updateYamlWithJsonContent(content) {
const yamlTextarea = document.getElementById("yamlequivalent");
yamlTextarea.removeAttribute("disabled");
const json = content.text ? JSON.parse(content.text) : content.json;
const configMap = {
apiVersion: "v1",
kind: "ConfigMap",
metadata: {
name: "autoscaler-config",
namespace: "memorystore-cluster-autoscaler",
},
data: {
// Autoscaler configmap.data is YAML as text.
"autoscaler-config.yaml": JsYaml.dump(json, { lineWidth: -1 }),
},
};
yamlTextarea.value = JsYaml.dump(configMap, { lineWidth: -1 });
}
/**
* Handles addling rendering of Schema enums in JSONEditor.
*
* @param {import("vanilla-jsoneditor").RenderValueProps} props
* @return {import("vanilla-jsoneditor").RenderValueComponentDescription[]}
*/
function onRenderValue(props) {
return renderJSONSchemaEnum(props, schema) || renderValue(props);
}
/** @type {Content} */
const EXAMPLE_CONFIG = {
json: [
{
$comment: "Sample memorystore autoscaler config",
projectId: "memorystore-cluster-project-id",
regionId: "us-central1",
clusterId: "autoscaler-target-memorystore-cluster",
scalingMethod: "STEPWISE",
units: "SHARDS",
maxSize: 10,
minSize: 3,
stepSize: 2,
scalerPubSubTopic:
"projects/memorystore-cluster-project-id/topics/scaler-topic",
stateDatabase: {
name: "firestore",
},
},
],
};
/** Handles DOMContentLoaded event. */
function onDOMContentLoaded() {
editor = new JSONEditor({
target: document.getElementById("jsoneditor"),
props: {
content: EXAMPLE_CONFIG,
mode: "text",
schema,
indentation: 2,
validator: createAjvValidator({ schema }),
onChange: jsonEditorChangeHandler,
onRenderValue,
},
});
updateYamlWithJsonContent(EXAMPLE_CONFIG);
document.getElementById("loading").style.display = "none";
}
document.addEventListener("DOMContentLoaded", onDOMContentLoaded, false);