begone broken nix
This commit is contained in:
parent
9e857e1d57
commit
3f62e8302e
8 changed files with 134 additions and 28 deletions
|
@ -190,10 +190,10 @@ pub async fn check_compose(arion_bin: &Path, source: &str) -> Result<()> {
|
||||||
// Check that it's a valid nix tree
|
// Check that it's a valid nix tree
|
||||||
rnix::Root::parse(source)
|
rnix::Root::parse(source)
|
||||||
.ok()
|
.ok()
|
||||||
.map_err(|_| AppError::Client {
|
.map_err(|err| AppError::Client {
|
||||||
status: StatusCode::NOT_ACCEPTABLE,
|
status: StatusCode::NOT_ACCEPTABLE,
|
||||||
code: "failed-nix-parse",
|
code: "failed-nix-parse",
|
||||||
message: "The provided source is not valid Nix".to_string(),
|
message: format!("Parse error: {}", err),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Create a temporary stack and check that it generates a YAML tree
|
// Create a temporary stack and check that it generates a YAML tree
|
||||||
|
@ -215,7 +215,7 @@ pub async fn check_compose(arion_bin: &Path, source: &str) -> Result<()> {
|
||||||
Err(AppError::Client {
|
Err(AppError::Client {
|
||||||
status: StatusCode::NOT_ACCEPTABLE,
|
status: StatusCode::NOT_ACCEPTABLE,
|
||||||
code: "failed-arion-check",
|
code: "failed-arion-check",
|
||||||
message: err,
|
message: format!("Arion {}", err),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -74,6 +74,9 @@ async fn create_stack(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Form(form): Form<CreateStackForm>,
|
Form(form): Form<CreateStackForm>,
|
||||||
) -> Result<Redirect, AppError> {
|
) -> Result<Redirect, AppError> {
|
||||||
|
// Make sure body is is ok
|
||||||
|
check_compose(&state.arion_bin, &form.source).await?;
|
||||||
|
|
||||||
create_new(state.repository, &form.name, &form.source).await?;
|
create_new(state.repository, &form.name, &form.source).await?;
|
||||||
|
|
||||||
Ok(Redirect::to(format!("/stack/{}/", form.name).as_str()))
|
Ok(Redirect::to(format!("/stack/{}/", form.name).as_str()))
|
||||||
|
@ -107,6 +110,9 @@ async fn edit_stack(
|
||||||
// Cleanup source (like line endings)
|
// Cleanup source (like line endings)
|
||||||
let source = form.source.replace("\r\n", "\n");
|
let source = form.source.replace("\r\n", "\n");
|
||||||
|
|
||||||
|
// Make sure file is ok
|
||||||
|
check_compose(&state.arion_bin, &source).await?;
|
||||||
|
|
||||||
// Write compose file
|
// Write compose file
|
||||||
write_compose(&state.stack_dir, &stack_name, &source).await?;
|
write_compose(&state.stack_dir, &stack_name, &source).await?;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
& .error {
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #500F1C;
|
||||||
|
color: #FF9592;
|
||||||
|
padding: 4px 8px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor {
|
#editor {
|
||||||
|
@ -28,4 +37,12 @@
|
||||||
min-height: 50vh;
|
min-height: 50vh;
|
||||||
border: 3px solid #5958B1;
|
border: 3px solid #5958B1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
border-color: #46A758;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.err {
|
||||||
|
border-color: #E5484D;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -122,3 +122,8 @@ textarea {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
47
static/js/compose.mjs
Normal file
47
static/js/compose.mjs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Checks if a specificied source is a valid arion-compose.nix file
|
||||||
|
* @param {string} source Source to check
|
||||||
|
* @returns {Promise<CheckResult>} Result of the check
|
||||||
|
*/
|
||||||
|
export async function check_compose(source) {
|
||||||
|
const response = await fetch("/stack/_/check", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "text/plain",
|
||||||
|
},
|
||||||
|
body: source
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return { ok: true };
|
||||||
|
} else {
|
||||||
|
return { ok: false, error: await response.json() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} APIError
|
||||||
|
* @property {string} code - Error code
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CheckSuccess
|
||||||
|
* @property { true } ok - Indicates that the check was successful
|
||||||
|
* /
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} APIError
|
||||||
|
* @property {string} code - Unique error code
|
||||||
|
* @property {string} message - Human readable error details
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CheckFailure
|
||||||
|
* @property {false} ok - Indicates that the check has failed
|
||||||
|
* @property {APIError} error - Info about the encountered error
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {CheckSuccess | CheckFailure} CheckResult
|
||||||
|
*/
|
45
static/js/enhancements/check.mjs
Normal file
45
static/js/enhancements/check.mjs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import Editor from "../ace.mjs";
|
||||||
|
import { check_compose } from "/static/js/compose.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the form require the stack to be checked before submitting
|
||||||
|
* @param {HTMLFormElement} form
|
||||||
|
* @param {Editor} editor
|
||||||
|
*/
|
||||||
|
export function add_check(form, editor) {
|
||||||
|
form.addEventListener("submit", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
check_stack(editor).then((result) => {
|
||||||
|
if (result) {
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the check function and updates some DOM elements
|
||||||
|
* @param {Editor} editor Editor instance
|
||||||
|
* @returns true if the stack is valid, false otherwise
|
||||||
|
*/
|
||||||
|
export async function check_stack(editor) {
|
||||||
|
const source = editor.editor.getValue();
|
||||||
|
const check_result = await check_compose(source);
|
||||||
|
|
||||||
|
const editorEl = document.querySelector(".ace_editor");
|
||||||
|
const editorErrorEl = document.querySelector("#editor-form .error");
|
||||||
|
editorEl.classList.remove("err", "checked");
|
||||||
|
editorErrorEl.style.display = "block";
|
||||||
|
editorErrorEl.classList.add("pending");
|
||||||
|
editorErrorEl.innerHTML = "Checking...";
|
||||||
|
if (check_result.ok) {
|
||||||
|
editorEl.classList.add("checked");
|
||||||
|
editorErrorEl.style.display = "";
|
||||||
|
} else {
|
||||||
|
editorEl.classList.add("err");
|
||||||
|
editorErrorEl.classList.remove("pending");
|
||||||
|
editorErrorEl.innerHTML = check_result.error.message;
|
||||||
|
}
|
||||||
|
return check_result.ok;
|
||||||
|
}
|
|
@ -37,6 +37,7 @@
|
||||||
<section class="editor">
|
<section class="editor">
|
||||||
<h2>Editor</h2>
|
<h2>Editor</h2>
|
||||||
<form method="POST" action="./edit" id="editor-form">
|
<form method="POST" action="./edit" id="editor-form">
|
||||||
|
<div class="error"></div>
|
||||||
<textarea name="source" id="editor">{{file_contents}}</textarea>
|
<textarea name="source" id="editor">{{file_contents}}</textarea>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input style="flex:1" name="commit_message" type="text"
|
<input style="flex:1" name="commit_message" type="text"
|
||||||
|
@ -60,11 +61,6 @@
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.containers {
|
.containers {
|
||||||
& .status {
|
& .status {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -101,31 +97,15 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Editor from "/static/js/ace.mjs";
|
import Editor from "/static/js/ace.mjs";
|
||||||
|
import { add_check } from "/static/js/enhancements/check.mjs";
|
||||||
|
|
||||||
const editor = new Editor("editor");
|
const editor = new Editor("editor");
|
||||||
|
|
||||||
/* Add extra buttons */
|
/* Enforce check pre-submit */
|
||||||
const extraContainer = document.getElementById("editor-extra");
|
const form = document.getElementById("editor-form");
|
||||||
|
add_check(form, editor);
|
||||||
/* Check button */
|
|
||||||
const checkButton = document.createElement("button");
|
|
||||||
checkButton.appendChild(document.createTextNode("Check"));
|
|
||||||
checkButton.type = "button";
|
|
||||||
checkButton.addEventListener("click", async () => {
|
|
||||||
const body = editor.editor.getValue();
|
|
||||||
const response = await fetch("/stack/_/check", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Content-Type": "text/plain",
|
|
||||||
},
|
|
||||||
body
|
|
||||||
});
|
|
||||||
})
|
|
||||||
extraContainer.appendChild(checkButton);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -12,6 +12,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input style="flex:1" name="name" type="text" placeholder="Stack name" required />
|
<input style="flex:1" name="name" type="text" placeholder="Stack name" required />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="error"></div>
|
||||||
<textarea name="source" id="editor" required>{}</textarea>
|
<textarea name="source" id="editor" required>{}</textarea>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<button type="submit">Create & Deploy</button>
|
<button type="submit">Create & Deploy</button>
|
||||||
|
@ -31,8 +32,13 @@
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Editor from "/static/js/ace.mjs";
|
import Editor from "/static/js/ace.mjs";
|
||||||
|
import { add_check } from "/static/js/enhancements/check.mjs";
|
||||||
|
|
||||||
const editor = new Editor("editor");
|
const editor = new Editor("editor");
|
||||||
|
|
||||||
|
/* Enforce check pre-submit */
|
||||||
|
const form = document.getElementById("editor-form");
|
||||||
|
add_check(form, editor);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
Reference in a new issue