<content id="main-content">
<section-title>Process Diagnostics</section-title>
<panel
class="row no-padding"
id="input-form"
data-signals:loading="false"
data-signals:processing="false"
data-signals:output.secure="{{ output_secure }}"
data-signals:archive.download_token="''"
data-signals:archive.pending_token="''"
data-signals:archive.ready_token="''"
data-signals:archive.status="'idle'"
data-signals:archive.error="''"
data-signals:job.collect.mode="'upload'"
data-signals:job.collect.source="'upload-file'"
data-signals:job.collect.save="false"
data-signals:_pending_job_action="''"
data-signals:_service_link_curl="''"
>
<div
hidden
data-effect="
if (!$keystore.locked && $_pending_job_action) {
const buttonId = {
'api-key': 'es-api-key-button',
'service-link': 'service-link-button',
'upload-process': 'main-upload-process-button',
}[$_pending_job_action];
if (buttonId) {
$_pending_job_action = '';
document.getElementById(buttonId)?.click();
}
}
"
></div>
<diagnostic-form id="diagnostic-form">
<tab-links data-signals:tab="'file-upload'">
<button
id="tab-file-upload"
class="tab active"
data-on:click="$tab = 'file-upload'"
data-class:active="$tab === 'file-upload'"
>
File Upload
</button>
<button
id="tab-service-link"
class="tab"
data-on:click="$tab = 'service-link'"
data-class:active="$tab === 'service-link'"
>
Service Link
</button>
<button
id="tab-api-key"
class="tab"
data-on:click="$tab = 'api-key'"
data-class:active="$tab === 'api-key'"
>
API Key
</button>
</tab-links>
<tab-content class="tab-content">
<form
id="upload-form"
data-show="$tab === 'file-upload'"
data-signals:loading="false"
data-signals:file_upload.job_id="0"
enctype="multipart/form-data"
>
<info-text class="margin-after">Process a file uploaded from your local computer</info-text>
<input type="file" id="file-input" name="file" accept=".zip" required value="" style="opacity: 0" />
<div
id="dropzone"
data-on:dragenter="$_drag-on=true"
data-on:dragover="$_drag-on=true"
data-on:dragleave="$_drag-on=false"
data-on:drop="$_drag-on=false"
data-class:drag-over="$_drag-on"
class="dropzone"
>
<div class="file-icon">
<icon-upload></icon-upload>
</div>
<div id="upload-instructions">
<p>Select or drag and drop a <code>.zip</code> file</p>
</div>
<div id="file-info" class="file-info hidden">
<span id="file-name" class="file-name"></span>
</div>
<div class="upload-progress-container" id="upload-progress-container">
<div class="upload-progress-bar" id="upload-progress-bar"></div>
</div>
<div class="upload-progress-text" id="upload-progress-text">0%</div>
</div>
<input id="upload-button" class="button" type="submit" value="Process" disabled />
<button
id="main-upload-process-button"
type="button"
style="display: none"
data-on:click="$loading = true; $message = 'Processing diagnostic...'; @post('/upload/process', {openWhenHidden: true, filterSignals: {include: /^(metadata|archive|job|file_upload)(\.|$)/}})"
></button>
<script>
// File upload handling
const dropzone = document.getElementById("dropzone");
const fileInput = document.getElementById("file-input");
const fileName = document.getElementById("file-name");
const uploadInstructions = document.getElementById("upload-instructions");
// Prevent default drag behaviors
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
dropzone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Handle dropped files
dropzone.addEventListener("drop", handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
function handleFiles(files) {
if (files.length > 0) {
fileInput.files = files;
updateFileInfo(files[0]);
}
}
function updateFileInfo(file) {
const uploadButton = document.getElementById("upload-button");
const fileIcon = document.querySelector(".file-icon");
const fileInfo = document.querySelector(".file-info");
const currentStatus = document.getElementById("current-status");
if (file) {
fileName.textContent = file.name;
fileInfo.classList.remove("hidden");
uploadButton.disabled = false;
uploadInstructions.classList.add("hidden");
dropzone.classList.add("active");
fileIcon.style.color = "var(--color-primary)";
fileIcon.innerHTML = "<icon-check></icon-check>";
} else {
fileInput.value = "";
fileInfo.classList.add("hidden");
uploadButton.disabled = true;
uploadInstructions.classList.remove("hidden");
dropzone.classList.remove("active");
fileIcon.style.color = "var(--text-subdued-color)";
fileIcon.innerHTML = "<icon-upload></icon-upload>";
}
}
// Open file picker when clicking drop area
dropzone.addEventListener("click", () => {
fileInput.click();
});
// Handle selected file when using file picker
fileInput.addEventListener("change", function () {
if (this.files && this.files[0]) {
updateFileInfo(this.files[0]);
}
});
// Initialize button state when page loads
document.addEventListener("DOMContentLoaded", function () {
const uploadButton = document.getElementById("upload-button");
uploadButton.disabled = fileInput.files.length === 0;
});
document.getElementById("upload-form").addEventListener("submit", function (e) {
e.preventDefault();
const formData = new FormData(this);
// Keep the file visible while uploading
const currentFile = fileInput.files[0];
// Use XMLHttpRequest to track upload progress
const xhr = new XMLHttpRequest();
const progressBar = document.getElementById("upload-progress-bar");
const progressContainer = document.getElementById("upload-progress-container");
const progressText = document.getElementById("upload-progress-text");
// Show progress elements
progressContainer.classList.add("active");
progressText.classList.add("active");
// Reset progress bar
progressBar.style.width = "0%";
progressText.textContent = "0%";
xhr.upload.addEventListener("progress", function (event) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
const percentFormatted = Math.round(percentComplete) + "%";
progressBar.style.width = percentFormatted;
progressText.textContent = percentFormatted;
}
});
xhr.onload = function () {
const jobFeed = document.getElementById("job-feed");
if (xhr.status >= 200 && xhr.status < 300) {
setTimeout(() => {
progressBar.style.width = "0%";
progressContainer.classList.remove("active");
progressText.classList.remove("active");
updateFileInfo(null);
document.getElementById("dropzone").style.pointerEvents = "";
}, 500);
} else {
progressContainer.classList.remove("active");
progressText.classList.remove("active");
updateFileInfo(null);
document.getElementById("dropzone").style.pointerEvents = "";
}
jobFeed.insertAdjacentHTML("afterend", xhr.responseText);
};
xhr.onerror = function () {
progressContainer.classList.remove("active");
progressText.classList.remove("active");
updateFileInfo(null);
document.getElementById("dropzone").style.pointerEvents = "";
};
xhr.open("POST", "/upload/submit", true);
xhr.send(formData);
});
</script>
</form>
<form
id="service-link-form"
enctype="multipart/form-data"
data-show="$tab === 'service-link'"
style="display: none"
>
<info-text class="margin-after">
Process from an Elastic upload service link:
<a href="https://upload.elastic.co" target="_blank">https://upload.elastic.co</a>
</info-text>
<input-group>
<label for="curl-command" class="form-label">Curl Command or Case Comment (Optional)</label>
<textarea
id="curl-command"
class="curl-textarea"
placeholder="Paste a curl command or case comment to auto-fill the form"
data-bind:_service_link_curl
data-on:input="{
$service_link.url = parseUrl($_service_link_curl),
$service_link.token = parseToken($_service_link_curl),
$service_link.filename = parseFilename($_service_link_curl)
}"
></textarea>
<text-link
id="curl-command-clear"
data-show="$_service_link_curl !== ''"
data-on:click="{
$_service_link_curl = '';
$service_link.url = '';
$service_link.token = '';
$service_link.filename = '';
}"
style="display: none"
>
Clear
</text-link>
</input-group>
<input-group>
<label for="service-token" class="form-label">Token</label>
<input
type="text"
id="service-token"
name="token"
data-bind:service_link.token
data-attr:readonly="$_service_link_curl !== ''"
class="form-input"
required
placeholder="Enter authentication token"
/>
</input-group>
<input-group>
<label for="service-url" class="form-label">URL</label>
<input
type="url"
id="service-url"
name="url"
data-bind:service_link.url
data-attr:readonly="$_service_link_curl !== ''"
class="form-input"
required
placeholder="https://example.com/path/to/diagnostic.zip"
/>
</input-group>
<input-group>
<label for="service-filename" class="form-label">Filename</label>
<input
type="text"
id="service-filename"
name="filename"
data-bind:service_link.filename
data-attr:readonly="$_service_link_curl !== ''"
class="form-input"
required
placeholder="diagnostic.zip"
/>
</input-group>
<input
id="service-link-button"
class="button"
type="button"
value="Process"
data-on:click="{% if can_use_keystore %}if ($keystore.locked && $output.secure) { $_pending_job_action = 'service-link'; $message = 'Unlock keystore to continue...'; @get('/keystore/modal/process', {filterSignals: {exclude: /.*/}}); } else { $loading = true; $message = 'Connecting to Elastic Upload service...'; @post('/service_link', {filterSignals: {include: /^(metadata|archive|job|service_link)(\.|$)/}}); }{% else %}$loading = true; $message = 'Connecting to Elastic Upload service...'; @post('/service_link', {filterSignals: {include: /^(metadata|archive|job|service_link)(\.|$)/}});{% endif %}"
data-attr:disabled="$loading || !$service_link.url || !$service_link.token"
/>
<script>
{% include "components/service_link_parser.js" %}
</script>
</form>
<form id="api-key-form" data-show="$tab === 'api-key'" style="display: none">
<info-text class="margin-after">Process directly from an Elasticsearch cluster's API.</info-text>
<input-group>
<label for="elasticsearch-url" class="form-label">Elasticsearch URL</label>
<input
type="url"
id="elasticsearch-url"
name="url"
autocomplete="username"
data-bind:es_api.url
class="form-input"
required
placeholder="https://elasticsearch.example.com/"
/>
</input-group>
<input-group>
<label for="api-key" class="form-label">Elasticsearch API Key</label>
<input
type="password"
id="api-key"
name="api-key"
autocomplete="current-password"
data-bind:es_api.key
class="form-input"
required
placeholder="Cluster API key"
/>
<info-text><i>API keys are not saved or logged.</i></info-text>
</input-group>
<input
id="es-api-key-button"
class="button"
type="button"
value="Process"
data-on:click="{% if can_use_keystore %}if ($keystore.locked && $output.secure) { $_pending_job_action = 'api-key'; $message = 'Unlock keystore to continue...'; @get('/keystore/modal/process', {filterSignals: {exclude: /.*/}}); } else { $loading = true; $message = 'Connecting with API key...'; @post('/api_key', {filterSignals: {include: /^(metadata|archive|job|es_api)(\.|$)/}}); }{% else %}$loading = true; $message = 'Connecting with API key...'; @post('/api_key', {filterSignals: {include: /^(metadata|archive|job|es_api)(\.|$)/}});{% endif %}"
data-attr:disabled="$loading || !$es_api.url || !$es_api.key"
/>
</form>
</tab-content>
</diagnostic-form>
<metadata-form id="metadata">
<h3>Metadata</h3>
<input-group>
<label for="username" class="form-label">User</label>
<input
type="text"
id="username"
name="username"
data-bind:metadata.user
placeholder="Username"
autocomplete="none"
class="form-input"
data-attr:disabled="$auth.header || $processing || $loading"
/>
</input-group>
<input-group>
<label for="account" class="form-label">Account</label>
<input
type="text"
id="account"
name="account"
data-bind:metadata.account
placeholder="Account"
class="form-input"
data-attr:disabled="$loading || $processing"
/>
</input-group>
<input-group>
<label for="case-number" class="form-label">Case Number</label>
<input
type="text"
id="case-number"
name="case-number"
data-bind:metadata.case_number
placeholder="Case Number"
class="form-input"
data-attr:disabled="$loading || $processing"
/>
</input-group>
<input-group>
<label for="opportunity" class="form-label">Opportunity</label>
<input
type="text"
id="opportunity"
name="opportunity"
data-bind:metadata.opportunity
placeholder="Opportunity"
class="form-input"
data-attr:disabled="$loading || $processing"
/>
</input-group>
<text-link
id="metadata-clear"
data-show="$metadata.account || $metadata.case_number || $metadata.opportunity"
data-on:click="{
$metadata.account = '';
$metadata.case_number = '';
$metadata.opportunity = '';
}"
style="display: none"
>
Clear Metadata
</text-link>
</metadata-form>
</panel>
<div id="current-status" data-show="$loading && $_job_id === '' " class="status-box loading" style="display: none">
<div class="spinner"></div>
<span id="status-message" data-text="$message">Uploading...</span>
</div>
<div id="job-feed"></div>
{% if let Some(key_id) = key_id %}
<div
id="job-{{ key_id }}"
data-init="$loading = true; $tab='api-key'; $message = 'Processing api key {{ key_id }}'; @post('/api_key/{{ key_id }}', {filterSignals: {exclude: /.*/}})"
data-signals:_job_id="true"
class="status-box history-item status-processing"
>
<div class="spinner"></div>
Processing api key for {{ key_id }}
</div>
{% else if let Some(link_id) = link_id %}
<div
id="job-{{ link_id }}"
data-init="$loading = true; $tab='service-link'; $message = 'Forwarding service link {{ link_id }}'; @post('/service_link/{{ link_id }}', {filterSignals: {exclude: /.*/}})"
data-signals:_job_id="true"
class="status-box history-item status-processing"
>
<div class="spinner"></div>
Loading service link {{ link_id }}
</div>
{% else if let Some(upload_id) = upload_id %}
<div
id="job-{{ upload_id }}"
data-init="$loading = true; $file_upload.job_id={{ upload_id|json }}; $message = 'Processing file upload {{ upload_id }}'; @post('/upload/process', {filterSignals: {include: /^(archive|metadata|job|file_upload)(\.|$)/}})"
data-signals:_job_id="true"
class="status-box history-item status-processing"
>
<div class="spinner"></div>
Loading file upload {{ upload_id }}
</div>
{% endif %}
{% if show_keystore_bootstrap %}
<div
id="keystore-bootstrap-init"
data-init="@get('/keystore/bootstrap-modal', {filterSignals: {exclude: /.*/}})"
></div>
{% endif %}
</content>