<content
id="main-content"
class="job-content"
data-signals:loading="false"
data-signals:processing="false"
data-signals:_file_selected="false"
data-signals:_pending_job_action="''"
data-signals:_service_link_curl="''"
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:_collect_secure_hosts="{{ collect_secure_hosts_json }}"
data-signals:_send_secure_hosts="{{ send_secure_hosts_json }}"
data-on:datastar-fetch-error="$loading = false; $processing = false; $message = evt.detail.error || 'Request failed'"
data-on:datastar-fetch-retries-failed="$loading = false; $processing = false"
data-signals:job.collect.mode="'upload'"
data-signals:job.collect.source="'upload-file'"
data-signals:job.collect.known_host="''"
data-signals:job.collect.diagnostic_type="'standard'"
data-signals:job.collect.save="false"
data-signals:job.collect.download_dir="'{{ default_save_dir }}'"
data-signals:job.process.mode="'process'"
data-signals:job.process.enabled="true"
data-signals:job.process.product="'elasticsearch'"
data-signals:job.process.diagnostic_type="'standard'"
data-signals:job.process.selected="''"
data-signals:job.send.mode="'{{ initial_send_mode }}'"
data-signals:job.send.remote_target="{{ initial_remote_target|json }}"
data-signals:job.send.local_target="'{{ initial_local_target }}'"
data-signals:job.send.local_directory="'{% if configured_local_path != "" %}{{ configured_local_path }}{% else %}{{ default_save_dir }}{% endif %}'"
>
<div
hidden
data-effect="
if (!$keystore.locked && $_pending_job_action) {
const buttonId = {
'known-host': 'known-host-button',
'api-key': 'es-api-key-button',
'service-link': 'service-link-button',
'upload-process': 'job-upload-process-button',
}[$_pending_job_action];
if (buttonId) {
$_pending_job_action = '';
document.getElementById(buttonId)?.click();
}
}
"
></div>
<div
hidden
data-effect="
if ($job.collect.save && !$archive.download_token && !$loading && !($job.collect.mode === 'upload' && $job.collect.source === 'upload-file')) {
$archive.download_token = crypto.randomUUID();
}
"
></div>
<div
hidden
data-effect="
if ($archive.ready_token && $archive.pending_token && $archive.ready_token === $archive.pending_token && $archive.status === 'ready') {
window.esdiagStartRetainedDownload?.({ token: $archive.ready_token, anchorId: 'job-download-anchor' });
$archive.pending_token = '';
$archive.ready_token = '';
$archive.status = 'idle';
$archive.error = '';
} else if ($archive.ready_token && $archive.pending_token && $archive.ready_token === $archive.pending_token && $archive.status === 'error') {
$message = $archive.error || 'Download failed';
$archive.pending_token = '';
$archive.ready_token = '';
$archive.status = 'idle';
}
"
></div>
<job-grid>
<card class="job-card">
<card-badge>Collect</card-badge>
<card-body>
<tab-links class="stage-options">
<button
class="tab active"
data-on:click="{
$job.collect.mode = 'upload',
$job.collect.source = $job.collect.source === 'service-link' ? 'service-link' : 'upload-file',
$job.collect.source !== 'service-link' ? ($job.collect.save = false, $archive.download_token = '') : null
}"
data-class:active="$job.collect.mode === 'upload'"
>
Existing
</button>
<button
class="tab"
data-on:click="{
$job.collect.mode = 'collect',
$job.collect.source = {% if runtime_mode == "user" %}'known-host'{% else %}'api-key'{% endif %}
}"
data-class:active="$job.collect.mode === 'collect'"
>
New
</button>
</tab-links>
<div data-show="$job.collect.mode === 'collect'">
<button-group
class="stage-options nested"
role="group"
aria-label="Choose a collection method"
>
{% if runtime_mode == "user" %}
<button
type="button"
data-on:click="$job.collect.source = 'known-host'"
data-attr:aria-pressed="$job.collect.source === 'known-host' ? 'true' : 'false'"
>
Known Host
</button>
{% endif %}
<button
type="button"
data-on:click="$job.collect.source = 'api-key'"
data-attr:aria-pressed="$job.collect.source === 'api-key' ? 'true' : 'false'"
>
API Key
</button>
</button-group>
{% if runtime_mode == "user" %}
<form
id="known-host-form"
data-show="$job.collect.source === 'known-host'"
style="display: none"
>
<info-text class="margin-after">Collect from a saved known host.</info-text>
<input-group>
<label for="collect-known-host" class="form-label">Known Host</label>
<select
id="collect-known-host"
class="form-input"
data-bind:job.collect.known_host
>
<option value="">Select a known host</option>
{% for host in collect_hosts %}
<option value="{{ host }}">{{ host }}</option>
{% endfor %}
</select>
</input-group>
<input-group>
<label for="collect-diagnostic-type" class="form-label">Diagnostic Type</label>
<select
id="collect-diagnostic-type"
class="form-input"
data-bind:job.collect.diagnostic_type
>
<option value="minimal">minimal</option>
<option value="light">light</option>
<option value="standard">standard</option>
<option value="support">support</option>
</select>
</input-group>
<info-text>Saved-host collection wiring is handled in the job implementation layer.</info-text>
<input
id="known-host-button"
class="button"
type="button"
value="Collect"
data-on:click="{% if can_use_keystore %}if ($keystore.locked && (($_collect_secure_hosts.includes($job.collect.known_host)) || ($job.send.mode === 'remote' && $_send_secure_hosts.includes($job.send.remote_target)) || ($job.send.mode === 'local' && $_send_secure_hosts.includes($job.send.local_target)))) { $_pending_job_action = 'known-host'; $message = 'Unlock keystore to continue...'; @get('/keystore/modal/process', {filterSignals: {exclude: /.*/}}); } else { $loading = true; $message = 'Collecting from known host...'; @post('/known_host', {filterSignals: {include: /^(metadata|archive|job)(\.|$)/}}); }{% else %}$loading = true; $message = 'Collecting from known host...'; @post('/known_host', {filterSignals: {include: /^(metadata|archive|job)(\.|$)/}});{% endif %}"
data-attr:disabled="$loading || !$job.collect.known_host"
style="display: none"
/>
</form>
{% endif %}
<form
id="api-key-form"
data-show="$job.collect.source === 'api-key'"
style="{% if runtime_mode != "user" %}display: block{% else %}display: none{% endif %}"
>
<info-text class="margin-after">Collect directly from a remote Elasticsearch 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-group>
<label for="collect-api-type" class="form-label">Diagnostic Type</label>
<select
id="collect-api-type"
class="form-input"
data-bind:job.collect.diagnostic_type
>
<option value="minimal">minimal</option>
<option value="light">light</option>
<option value="standard">standard</option>
<option value="support">support</option>
</select>
</input-group>
<input
id="es-api-key-button"
class="button"
type="button"
value="Collect"
data-on:click="{% if can_use_keystore %}if ($keystore.locked && (($job.send.mode === 'remote' && $_send_secure_hosts.includes($job.send.remote_target)) || ($job.send.mode === 'local' && $_send_secure_hosts.includes($job.send.local_target)))) { $_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"
style="display: none"
/>
</form>
</div>
<div data-show="$job.collect.mode === 'upload'" style="display: none">
<button-group
class="stage-options nested"
role="group"
aria-label="Choose an existing diagnostic source"
>
<button
type="button"
data-on:click="{ $job.collect.source = 'upload-file', $job.collect.save = false, $archive.download_token = '' }"
data-attr:aria-pressed="$job.collect.source === 'upload-file' ? 'true' : 'false'"
>
Local File
</button>
<button
type="button"
data-on:click="$job.collect.source = 'service-link'"
data-attr:aria-pressed="$job.collect.source === 'service-link' ? 'true' : 'false'"
>
Service Link
</button>
</button-group>
<form
id="upload-form"
data-show="$job.collect.source === 'upload-file'"
data-signals:file_upload.job_id="0"
enctype="multipart/form-data"
style="display: none"
>
<info-text class="margin-after">Upload an existing diagnostic archive from your local machine.</info-text>
<input
type="file"
id="file-input"
name="file"
accept=".zip"
required
value=""
style="opacity: 0"
data-on:change="$_file_selected = evt.target.files && evt.target.files.length > 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="Upload"
disabled
style="display: none"
/>
<button
id="job-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>
</form>
<form
id="service-link-form"
enctype="multipart/form-data"
data-show="$job.collect.source === 'service-link'"
style="display: none"
>
<info-text class="margin-after">
Load an existing diagnostic 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="Load"
data-on:click="{% if can_use_keystore %}if ($keystore.locked && (($job.send.mode === 'remote' && $_send_secure_hosts.includes($job.send.remote_target)) || ($job.send.mode === 'local' && $_send_secure_hosts.includes($job.send.local_target)))) { $_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"
style="display: none"
/>
</form>
<script>
const dropzone = document.getElementById("dropzone");
const fileInput = document.getElementById("file-input");
const fileName = document.getElementById("file-name");
const uploadInstructions = document.getElementById("upload-instructions");
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
dropzone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
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;
fileInput.dispatchEvent(new Event("change", { bubbles: true }));
updateFileInfo(files[0]);
}
}
function updateFileInfo(file) {
const uploadButton = document.getElementById("upload-button");
const fileIcon = document.querySelector(".file-icon");
const fileInfo = document.querySelector(".file-info");
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(--primary)";
fileIcon.innerHTML = "<icon-check></icon-check>";
} else {
fileInput.value = "";
fileInput.dispatchEvent(new Event("change", { bubbles: true }));
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>";
}
}
dropzone.addEventListener("click", () => {
fileInput.click();
});
fileInput.addEventListener("change", function () {
if (this.files && this.files[0]) {
updateFileInfo(this.files[0]);
}
});
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);
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");
progressContainer.classList.add("active");
progressText.classList.add("active");
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);
});
{% include "components/service_link_parser.js" %}
</script>
</div>
</card-body>
<footer class="job-card-footer">
<choice-row class="footer-choice">
<input
id="collect-save-toggle"
type="checkbox"
class="switch"
data-bind:job.collect.save
data-on:change="if (evt.target.checked) { $archive.download_token = $archive.download_token || crypto.randomUUID(); } else { $archive.download_token = ''; }"
data-attr:disabled="$job.collect.mode === 'upload' && $job.collect.source === 'upload-file'"
/>
<switch-clip>
<switch-glyphs data-class="{'switch-on': $job.collect.save, 'switch-off': !$job.collect.save}">
<svg viewBox="0 0 50 20" aria-hidden="true" focusable="false">
<path class="glyph" d="M2 9 L6 13 L13 6"></path>
<circle class="glyph filled" cx="26" cy="10" r="7"></circle>
<path class="glyph" d="M41 6 L49 14 M49 6 L41 14"></path>
</svg>
</switch-glyphs>
</switch-clip>
<label for="collect-save-toggle">Download Archive</label>
</choice-row>
</footer>
</card>
<card class="job-card" data-class:disabled="!$job.process.enabled">
<card-badge>Process</card-badge>
<card-body>
<div data-show="$job.process.enabled">
<input-group>
<label for="process-product" class="form-label">Product</label>
<select id="process-product" class="form-input" data-bind:job.process.product>
<option value="elasticsearch">Elasticsearch</option>
<option value="logstash">Logstash</option>
</select>
</input-group>
<input-group>
<label for="process-type" class="form-label">Diagnostic Processors</label>
<select id="process-type" class="form-input" data-bind:job.process.diagnostic_type>
<option value="minimal">minimal</option>
<option value="light">light</option>
<option value="standard">standard</option>
<option value="support">support</option>
<option value="custom">custom</option>
</select>
</input-group>
<input-group>
<details
id="process-custom-processors"
class="processor-accordion"
data-show="$job.process.diagnostic_type === 'custom'"
data-preserve-attr="open"
style="display: none"
open
>
<summary>API Selection</summary>
<advanced-panel>
<info-text>Only fully implemented processors are shown. Required processors stay locked on.</info-text>
<processor-list id="process-option-list"></processor-list>
</advanced-panel>
</details>
</input-group>
<input type="hidden" id="process-selected-options" data-bind:job.process.selected />
</div>
<div data-show="!$job.process.enabled" style="display: none">
<info-text>Forward preserves the raw diagnostic archive without processing it into documents.</info-text>
</div>
</card-body>
<footer class="job-card-footer">
<choice-row class="footer-choice">
<input
id="process-enabled-toggle"
type="checkbox"
class="switch"
data-bind:job.process.enabled
data-on:change="{
$job.process.mode = $job.process.enabled ? 'process' : 'forward',
!$job.process.enabled && $job.send.mode === 'local' && $job.collect.source !== 'upload-file'
? $job.collect.save = true
: null
}"
/>
<switch-clip>
<switch-glyphs data-class="{'switch-on': $job.process.enabled, 'switch-off': !$job.process.enabled}">
<svg viewBox="0 0 50 20" aria-hidden="true" focusable="false">
<path class="glyph" d="M2 9 L6 13 L13 6"></path>
<circle class="glyph filled" cx="26" cy="10" r="7"></circle>
<path class="glyph" d="M41 6 L49 14 M49 6 L41 14"></path>
</svg>
</switch-glyphs>
</switch-clip>
<label for="process-enabled-toggle">Enabled</label>
</choice-row>
</footer>
</card>
<card class="job-card">
<card-badge>Send</card-badge>
<card-body>
<tab-links class="stage-options">
<button
class="tab active"
data-on:click="$job.send.mode = 'remote'"
data-class:active="$job.send.mode === 'remote'"
>
Remote
</button>
<button
class="tab"
data-on:click="{
$job.send.mode = 'local',
!$job.process.enabled && $job.collect.source !== 'upload-file'
? $job.collect.save = true
: null
}"
data-class:active="$job.send.mode === 'local'"
>
Local
</button>
</tab-links>
<div data-show="$job.send.mode === 'remote'">
<input-group data-show="$job.process.enabled">
<label for="send-remote-target" class="form-label">Diagnostic Cluster</label>
<select
id="send-remote-target"
class="form-input"
data-effect="el.value = $job.send.remote_target ?? ''"
data-on:change="$job.send.remote_target = evt.target.value === '' ? null : evt.target.value"
>
<option value="">Default</option>
{% for host in send_remote_hosts %}
<option value="{{ host }}">{{ host }}</option>
{% endfor %}
</select>
</input-group>
<input-group data-show="!$job.process.enabled" style="display: none">
<label for="send-forward-upload" class="form-label">Upload ID</label>
<input
id="send-forward-upload"
type="text"
class="form-input"
data-bind:job.send.remote_target
placeholder="00000000-0000-0000-0000-000000000000"
/>
</input-group>
<info-text data-show="$job.process.enabled">
Remote send uses the configured output target for processed diagnostics.
</info-text>
<info-text data-show="!$job.process.enabled" style="display: none">
Upload the archive to the Elastic Upload Service:
</info-text>
<a href="https://upload.elastic.co" target="_blank">https://upload.elastic.co</a>
</div>
<div data-show="$job.send.mode === 'local'" style="display: none">
<input-group data-show="$job.process.enabled">
<label for="send-local-target" class="form-label">Local Target</label>
<select
id="send-local-target"
class="form-input"
data-bind:job.send.local_target
>
<option value="">Select local target</option>
{% for host in send_local_hosts %}
<option value="{{ host }}">{{ host }}</option>
{% endfor %}
<option value="directory">{% if configured_local_path != "" %}Configured local path{% else %}Local directory{% endif %}</option>
</select>
</input-group>
<info-text data-show="$job.process.enabled">
Local send supports localhost diagnostic clusters or local directory output.
</info-text>
<input-group
data-show="$job.process.enabled && $job.send.local_target === 'directory'"
style="display: none"
>
<label for="send-local-directory" class="form-label">Local Directory</label>
<input
id="send-local-directory"
type="text"
class="form-input"
data-bind:job.send.local_directory
placeholder="{% if configured_local_path != "" %}{{ configured_local_path }}{% else %}{{ default_save_dir }}{% endif %}"
/>
</input-group>
<info-text data-show="!$job.process.enabled" style="display: none">
Download the archive from <strong>Collect</strong>.
</info-text>
</div>
<info-text
data-show="!$job.process.enabled && $job.send.mode === 'local' && $job.collect.mode === 'upload' && $job.collect.source === 'upload-file'"
style="display: none"
>
Existing local files stay in place. Choose `Remote` or use a save-capable collect source to create a new saved archive.
</info-text>
<stage-divider></stage-divider>
<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>
</card-body>
<footer class="job-card-footer send-card-footer">
<button
id="job-go-button"
type="button"
data-on:click="{
const shouldDownload = $job.collect.save && !($job.collect.mode === 'upload' && $job.collect.source === 'upload-file');
const secureSendTarget =
($job.send.mode === 'remote' && $_send_secure_hosts.includes($job.send.remote_target)) ||
($job.send.mode === 'local' && $_send_secure_hosts.includes($job.send.local_target));
const secureCollectTarget =
$job.collect.mode === 'collect' &&
$job.collect.source === 'known-host' &&
$_collect_secure_hosts.includes($job.collect.known_host);
const needsUnlock = {% if can_use_keystore %}$keystore.locked && (secureCollectTarget || secureSendTarget){% else %}false{% endif %};
{% if can_use_keystore %}
if (needsUnlock) {
evt.preventDefault();
$_pending_job_action =
$job.collect.mode === 'upload' && $job.collect.source === 'upload-file'
? 'upload-process'
: $job.collect.mode === 'upload' && $job.collect.source === 'service-link'
? 'service-link'
: $job.collect.mode === 'collect' && $job.collect.source === 'api-key'
? 'api-key'
: 'known-host';
$message = 'Unlock keystore to continue...';
@get('/keystore/modal/process', {filterSignals: {exclude: /.*/}});
} else {
{% endif %}
const requestDownload = () => {
if (!shouldDownload) return;
const token = $archive.download_token || crypto.randomUUID();
$archive.download_token = token;
$archive.pending_token = token;
$archive.ready_token = '';
$archive.status = 'waiting';
$archive.error = '';
};
requestDownload();
$job.collect.mode === 'upload' && $job.collect.source === 'upload-file'
? document.getElementById('upload-form').requestSubmit()
: null;
$job.collect.mode === 'upload' && $job.collect.source === 'service-link'
? document.getElementById('service-link-button').click()
: null;
$job.collect.mode === 'collect' && $job.collect.source === 'api-key'
? document.getElementById('es-api-key-button').click()
: null;
$job.collect.mode === 'collect' && $job.collect.source === 'known-host'
? document.getElementById('known-host-button')?.click()
: null;
{% if can_use_keystore %}
}
{% endif %}
}"
data-attr:disabled="$loading || $processing || !((
(
($job.collect.mode === 'upload' && $job.collect.source === 'upload-file' && $_file_selected) ||
($job.collect.mode === 'upload' && $job.collect.source === 'service-link' && $service_link.url !== '' && $service_link.token !== '' && $service_link.filename !== '') ||
($job.collect.mode === 'collect' && $job.collect.source === 'api-key' && $es_api.url !== '' && $es_api.key !== '') ||
($job.collect.mode === 'collect' && $job.collect.source === 'known-host' && $job.collect.known_host !== '')
)
) && (
(
$job.process.enabled &&
(
$job.send.mode === 'remote' ||
($job.send.mode === 'local' && $job.send.local_target !== '' && ($job.send.local_target !== 'directory' || $job.send.local_directory !== ''))
)
) ||
(
!$job.process.enabled &&
(
($job.send.mode === 'local' && $job.collect.save && !($job.collect.mode === 'upload' && $job.collect.source === 'upload-file')) ||
($job.send.mode === 'remote' && !!$job.send.remote_target)
)
)
))"
>
Go
</button>
<span
id="job-step-label"
class="footer-status"
>
<span class="job-step-segment">collect</span>
<svg
data-show="$job.collect.save && !($job.collect.mode === 'upload' && $job.collect.source === 'upload-file')"
viewBox="0 0 16 16"
aria-hidden="true"
focusable="false"
style="display: none"
>
<path d="M6 3L11 8L6 13"></path>
</svg>
<span
class="job-step-segment"
data-show="$job.collect.save && !($job.collect.mode === 'upload' && $job.collect.source === 'upload-file')"
style="display: none"
>
download
</span>
<svg
data-show="$job.process.enabled"
viewBox="0 0 16 16"
aria-hidden="true"
focusable="false"
style="display: none"
>
<path d="M6 3L11 8L6 13"></path>
</svg>
<span
class="job-step-segment"
data-show="$job.process.enabled"
style="display: none"
>
process
</span>
<svg
data-show="$job.process.enabled || $job.send.mode === 'remote'"
viewBox="0 0 16 16"
aria-hidden="true"
focusable="false"
style="display: none"
>
<path d="M6 3L11 8L6 13"></path>
</svg>
<span
class="job-step-segment"
data-show="$job.process.enabled || $job.send.mode === 'remote'"
style="display: none"
>
send
</span>
</span>
</footer>
</card>
</job-grid>
<job-results>
<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; $job.collect.mode='collect'; $job.collect.source='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; $job.collect.mode='upload'; $job.collect.source='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; $job.collect.mode='upload'; $job.collect.source='upload-file'; $file_upload.job_id={{ upload_id|json }}; $message = 'Processing file upload {{ upload_id }}'; @post('/upload/process', {filterSignals: {include: /^(metadata|archive|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 %}
</job-results>
<a
id="job-download-anchor"
href=""
data-attr:href="$archive.download_token ? `/advanced/download/${$archive.download_token}` : null"
aria-hidden="true"
tabindex="-1"
style="position: fixed; left: -9999px; width: 1px; height: 1px; opacity: 0; pointer-events: none"
></a>
<script>
const PROCESS_OPTIONS = {{ process_options_json|safe }};
const clickRetainedDownloadAnchor = (anchorId, token) => {
const anchor = document.getElementById(anchorId);
if (!anchor || !token) return;
anchor.href = `/advanced/download/${token}`;
anchor.click();
};
window.esdiagStartRetainedDownload = window.esdiagStartRetainedDownload || (({ token, anchorId }) => {
clickRetainedDownloadAnchor(anchorId, token);
});
const getProcessOptions = () => {
const product = document.getElementById("process-product")?.value ?? "elasticsearch";
const diagnosticType = document.getElementById("process-type")?.value ?? "standard";
return PROCESS_OPTIONS?.[product]?.[diagnosticType] ?? [];
};
const parseSelectedOptions = () => {
const hidden = document.getElementById("process-selected-options");
return new Set(
(hidden?.value ?? "")
.split(",")
.map((value) => value.trim())
.filter(Boolean),
);
};
const writeSelectedOptions = (values) => {
const hidden = document.getElementById("process-selected-options");
if (!hidden) return;
hidden.value = [...values].join(",");
hidden.dispatchEvent(new Event("input", { bubbles: true }));
hidden.dispatchEvent(new Event("change", { bubbles: true }));
};
const renderProcessOptions = () => {
const list = document.getElementById("process-option-list");
if (!list) return;
const options = getProcessOptions();
const selected = parseSelectedOptions();
list.innerHTML = "";
options.forEach((option) => {
const row = document.createElement("label");
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = selected.size === 0 ? option.selected : selected.has(option.key);
checkbox.disabled = option.required;
checkbox.addEventListener("change", () => {
const current = parseSelectedOptions();
if (checkbox.checked) {
current.add(option.key);
} else {
current.delete(option.key);
}
writeSelectedOptions(current);
});
row.appendChild(checkbox);
row.append(` ${option.key}${option.required ? " (required)" : ""}`);
list.appendChild(row);
});
};
document.addEventListener("DOMContentLoaded", () => {
["process-product", "process-type"].forEach((id) => {
document.getElementById(id)?.addEventListener("change", () => {
writeSelectedOptions(new Set());
renderProcessOptions();
});
});
renderProcessOptions();
});
</script>
{% if show_keystore_bootstrap %}
<div
id="keystore-bootstrap-init"
data-init="@get('/keystore/bootstrap-modal', {filterSignals: {exclude: /.*/}})"
></div>
{% endif %}
</content>