<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<link
href="https://unpkg.com/jsoneditor@9.7.4/dist/jsoneditor.min.css"
rel="stylesheet"
/>
<script src="https://unpkg.com/jsoneditor@9.7.4/dist/jsoneditor.min.js"></script>
<style>
* {
box-sizing: border-box;
}
body {
max-width: 800px;
margin: 0 auto;
padding: 36px 12px;
color: #3f3f3f;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
font-size: 18px;
line-height: 1.5;
}
h1 {
margin: 0 0 24px;
color: black;
font-weight: 600;
}
h1 small {
white-space: nowrap;
font-size: 0.65em;
color: #a0a0a0;
}
a {
color: #1e1eff;
}
pre {
background: #efefef;
padding: 12px 16px;
overflow-x: auto;
}
code {
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
font-size: 0.9em;
}
button {
appearance: none;
border: 0;
border-radius: 8px;
padding: 10px 16px;
background: #c838ff;
font-weight: 600;
color: white;
cursor: pointer;
}
button:hover {
background: #b134e3;
}
button:active {
background: #a31ed7;
}
button:disabled {
background: #e092ff;
cursor: default;
}
</style>
<title>cadre</title>
</head>
<body>
<h1>cadre <small>configure apps</small></h1>
<p>
<em>This is the cadre web interface.</em> To use, edit the JSON
configuration below. Templates are stored in S3 immediately after saving.
Secrets are parsed and populated at request time.
</p>
<pre><code><span style="color: #7b0091">curl</span> <span id="base-url">$BASE_URL</span>/c/<span style="font-style: italic; color: rgb(179, 80, 0)">$ENVIRONMENT</span></code></pre>
<p>
Note that <em>cadre</em> may cache configuration templates and secrets in
memory for up to a minute after a given request.
</p>
<form id="secret-form" style="margin: 20px 0">
Secret:
<input id="secret-input" type="text" value="" />
</form>
<form id="environment-form" style="margin: 20px 0">
Environment:
<input id="environment-input" type="text" value="default" />
</form>
<div style="margin: 20px 0; display: flex; align-items: center">
<button id="submit" disabled>Save Changes</button>
<div id="indicator" style="margin-left: 10px; font-size: 24px"></div>
<div
id="changes"
style="
margin-left: 10px;
font-size: 0.8em;
font-style: italic;
display: none;
"
>
(pending changes)
</div>
</div>
<div id="jsoneditor" style="height: 480px"></div>
<script type="module">
document.getElementById("base-url").innerText = window.location.origin;
let submitting = false;
let clearIndicator = 0;
const environmentForm = document.getElementById("environment-form");
const environmentInput = document.getElementById("environment-input");
const secretForm = document.getElementById("secret-form");
const secretInput = document.getElementById("secret-input");
const submit = document.getElementById("submit");
const indicator = document.getElementById("indicator");
const changes = document.getElementById("changes");
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
secretInput.value = urlParams.get("secret");
let editor = null;
let environment = environmentInput.value;
let secret = secretInput.value;
let currentValue = {};
let editorValue = {};
async function updateEnv() {
try {
const resp = await fetch(`/t/${environment}`, {
headers: { "X-Cadre-Secret": secret },
});
let value;
if (resp.ok) {
value = await resp.json();
} else {
value = {};
}
currentValue = editorValue = value;
editor?.set(value);
} catch (error) {
alert(error.toString());
}
}
await updateEnv();
environmentForm.addEventListener("submit", (event) => {
event.preventDefault();
environment = environmentInput.value;
updateEnv();
});
secretForm.addEventListener("submit", (event) => {
event.preventDefault();
secret = secretInput.value;
updateEnv();
});
function refresh() {
submit.disabled = submitting;
changes.style.display = _.isEqual(currentValue, editorValue)
? "none"
: "block";
}
const container = document.getElementById("jsoneditor");
const options = {
mode: "code",
onValidate: (value) => {
editorValue = value;
refresh();
},
};
editor = new JSONEditor(container, options, editorValue);
submit.addEventListener("click", async () => {
submitting = true;
clearTimeout(clearIndicator);
indicator.innerText = "🔵";
submit.disabled = true;
try {
const newValue = editorValue;
const resp = await fetch(`/t/${environment}`, {
method: "PUT",
body: JSON.stringify(newValue),
headers: {
"Content-Type": "application/json",
"X-Cadre-Secret": secret,
},
});
if (resp.status === 200) {
currentValue = newValue;
indicator.innerText = "✅";
} else {
alert("Request error: " + resp.status);
indicator.innerText = "❌";
}
clearIndicator = setTimeout(() => (indicator.innerText = ""), 2500);
} finally {
submitting = false;
refresh();
}
});
</script>
</body>
</html>