The information allows the user to understand which VMs are used in the pool
447 lines
21 KiB
HTML
447 lines
21 KiB
HTML
<!-- Templates for the Vue.js components -->
|
|
|
|
<!-- template for the 'header' Vue.js component -->
|
|
<script type='text/x-template' id='template_header'>
|
|
<header class="d-flex align-items-center p-1 bg-darker text-small fixed-top">
|
|
<img class="img-icon mx-2" src='/static/flamenco.png' alt='Flamenco logo'>
|
|
<span>
|
|
{{ serverinfo.manager_name }}
|
|
</span>
|
|
<span class="text-warning pl-2" v-if="serverinfo.manager_mode != 'production'">
|
|
({{ serverinfo.manager_mode }} mode)
|
|
</span>
|
|
<span class="ml-auto px-2 text-secondary">
|
|
<button id='downloadkick' class='btn btn-sm btn-link py-0 text-secondary'>
|
|
Kick Task Downloader
|
|
</button>
|
|
<span class="text-muted">|</span>
|
|
{{ serverinfo.nr_of_workers }} Workers
|
|
<span class="text-muted">|</span>
|
|
{{ serverinfo.nr_of_tasks }} Tasks
|
|
<span class="text-muted">|</span>
|
|
<span title="Number of task updates queued for sending to Flamenco Server.">
|
|
{{ serverinfo.upstream_queue_size }} Upstream Queue
|
|
</span>
|
|
<span class="text-muted">|</span>
|
|
<a title="Server" :href="serverinfo.server.url" class="text-secondary d-md-none d-lg-inline-block">
|
|
{{ serverinfo.server.name }}
|
|
</a>
|
|
<span class="text-muted">|</span>
|
|
<a href="/restart-to-websetup" class='btn btn-sm btn-link py-0 text-secondary'>Setup</a>
|
|
<span class="text-muted">
|
|
| {{ serverinfo.version }}
|
|
</span>
|
|
</span>
|
|
</header>
|
|
</script>
|
|
|
|
|
|
<!-- template for the 'status' Vue.js component -->
|
|
<script type='text/x-template' id='template_status'>
|
|
<div id='status' class="text-secondary">
|
|
<p v-if='errormsg' class='error'>{{ errormsg }}</p>
|
|
<div v-else class="row text-nowrap">
|
|
<section v-if="dynamic_pools" class="col-md-12">
|
|
<strong>DYNAMIC POOLS</strong>
|
|
<div v-if="dynamic_pools.is_refreshing" class="spinner-border spinner-border-sm float-right" role="status"
|
|
title="Refreshing dynamic pool information">
|
|
<span class="sr-only">Refreshing</span>
|
|
</div>
|
|
<dynamic-pool-platform
|
|
v-if="dynamic_pools.platforms.length"
|
|
v-for="platform of dynamic_pools.platforms"
|
|
:key="platform.name"
|
|
:name="platform.name"
|
|
:pools="platform.pools"
|
|
></dynamic-pool-platform>
|
|
<hr/>
|
|
</section>
|
|
<section class="col-md-12" v-if="idle_workers.length">
|
|
<strong title="Workers not seen in a long time">OLD WORKERS</strong>
|
|
<ul class="list-unstyled pt-2">
|
|
<idle-worker
|
|
v-for="worker in idle_workers"
|
|
:key="worker._id"
|
|
:worker="worker"
|
|
@forget-worker="forgetWorker(worker)">
|
|
</idle-worker>
|
|
</ul>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</script>
|
|
|
|
<!-- template for the 'dynamic-pool-platform' Vue.js component -->
|
|
<script type='text/x-template' id='template_dynamic_pool_platform'>
|
|
<dl class="row mt-2">
|
|
<dt class="col-xl-2 col-lg-1">{{ name }}</dt>
|
|
<dd class="col-xl-10 col-lg-11 m-0">
|
|
<ul class="list-unstyled m-0">
|
|
<li v-for="pool in pools" :key="pool.ID" :title="pool.allocationState">
|
|
<dl class="row m-0">
|
|
<dt class="col-xl-4 col-sm-3 col-xs-3 text-wrap font-weight-normal">{{ pool.ID }} [{{ pool.vmSize }}]</dt>
|
|
|
|
<dd class="col-xl-8 col-sm-9 col-xs-9">
|
|
<span v-if="pool.allocationState == 'steady'" :title="nodeCountExplicit(pool.currentSize)">({{ currentNodeCount(pool) }})</span>
|
|
|
|
<span v-else-if="pool.allocationState == 'resizing'"
|
|
:title="'Resizing from ' + nodeCountExplicit(pool.currentSize) + ' to ' + nodeCountExplicit(pool.desiredSize)"
|
|
>({{ currentNodeCount(pool) }} → {{ desiredNodeCount(pool) }})</span>
|
|
<span v-else>{{ pool.allocationState }}</span>
|
|
|
|
<button type="button" class="btn btn-link btn-sm text-white py-0 px-3" data-toggle="modal"
|
|
v-if="pool.allocationState == 'steady'"
|
|
@click="onResizeButtonClick($event, pool)"
|
|
data-target="#modal_dynamic_pool_resize">
|
|
Resize
|
|
</button>
|
|
|
|
<div v-if="pool.resizeError" class="text-danger text-wrap py-2">{{ pool.resizeError }}</div>
|
|
</dd>
|
|
</dl>
|
|
</li>
|
|
</ul>
|
|
</dd>
|
|
</dl>
|
|
</script>
|
|
|
|
<!-- template for the 'dynamic-pool-resize' Vue.js component -->
|
|
<script type='text/x-template' id='template_dynamic_pool_resize'>
|
|
<bootstrap-modal
|
|
ref="modal"
|
|
title="Resize Dynamic Pool"
|
|
label-ok="Resize"
|
|
label-cancel="Close"
|
|
id='modal_dynamic_pool_resize'
|
|
:isProcessing="isProcessing"
|
|
@show-bs-modal='onShowModal'
|
|
@button-ok='onButtonOK'
|
|
>
|
|
<dl class="row">
|
|
<dt class="col-md-6">Platform</dt>
|
|
<dd class="col-md-6">{{ platformName }}</dd>
|
|
<dt class="col-md-6">Pool ID</dt>
|
|
<dd class="col-md-6">{{ poolID }}</dd>
|
|
<dt class="col-md-6">Allocation State</dt>
|
|
<dd class="col-md-6">{{ allocationState }}</dd>
|
|
</dl>
|
|
<dl class="row">
|
|
<dt class="col-md-6">Dedicated Nodes</dt>
|
|
<dd class="col-md-6">
|
|
<input class="form-control form-control-sm w-25" type="number" min="0" name="dedicatedNodes" v-model="dedicatedNodes">
|
|
</dd>
|
|
<dt class="col-md-6">Low-priority Nodes</dt>
|
|
<dd class="col-md-6">
|
|
<input class="form-control form-control-sm w-25" type="number" min="0" name="lowPriorityNodes" v-model="lowPriorityNodes">
|
|
</dd>
|
|
</dl>
|
|
</bootstrap-modal>
|
|
</script>
|
|
|
|
|
|
<!-- template for the 'idle-worker' Vue.js component -->
|
|
<script type='text/x-template' id='template_idle_worker'>
|
|
<li class="d-inline-block mr-4">
|
|
<span class="idle-worker-name" :title="worker._id">{{ worker.nickname }}</span>
|
|
<span @click="$emit('forget-worker', worker)" class="text-danger pr-1" title="click to forget worker">x</span>
|
|
</li>
|
|
</script>
|
|
|
|
<!-- template for the 'action-button' Vue.js component -->
|
|
<script type='text/x-template' id='template_action_button'>
|
|
<a class='worker-action'
|
|
:title="action.title"
|
|
@click="$emit('worker-action', worker_id, action_key)"
|
|
><i :class="'icon i-' + action_key"></i></a>
|
|
</script>
|
|
|
|
<!-- template for the 'worker-table' Vue.js component -->
|
|
<script type='text/x-template' id='template_worker_table'>
|
|
<div v-if="has_workers">
|
|
<action-bar
|
|
:workers="workers"
|
|
:selected_worker_ids="selected_worker_ids"
|
|
:show_schedule="show_schedule"
|
|
@toggle-schedule='show_schedule = !show_schedule'
|
|
></action-bar>
|
|
<table class="table table-condensed worker">
|
|
<thead>
|
|
<tr>
|
|
<td class='p-2 d-flex justify-content-center align-items-center'>
|
|
<input
|
|
title="Select All"
|
|
type="checkbox"
|
|
value="all"
|
|
:checked="all_workers_selected"
|
|
@input="$emit('toggle-all-workers-selected')"
|
|
>
|
|
</td>
|
|
<th></th>
|
|
<th>Name</th>
|
|
<th>Status</th>
|
|
<th class="text-center d-none d-xl-table-cell">Scheduled</th>
|
|
<template v-if="show_schedule">
|
|
<th>Sleep Days</th>
|
|
<th>Sleep Start</th>
|
|
<th>Sleep End</th>
|
|
<th></th>
|
|
</template>
|
|
<template v-else>
|
|
<th title="Current/Last Task">Task</th>
|
|
<th>Task Log</th>
|
|
<th>Seen</th>
|
|
<th class="d-none d-xl-table-cell">Software</th>
|
|
<th></th>
|
|
</template>
|
|
</tr>
|
|
</thead>
|
|
<worker-tbody
|
|
v-for="worker in workers"
|
|
:key="worker._id"
|
|
:worker="worker"
|
|
:selected_worker_ids="selected_worker_ids"
|
|
:show_schedule="show_schedule"
|
|
:server="server"
|
|
@worker-selected="$emit('worker-selected', $event, worker._id)"
|
|
@copy-schedule="copySchedule($event)"
|
|
></worker-tbody>
|
|
</table>
|
|
</div>
|
|
</script>
|
|
|
|
<!-- template for the 'worker-tbody' Vue.js component -->
|
|
<script type='text/x-template' id='template_worker_tbody'>
|
|
<tbody :class="'worker status-' + worker.status">
|
|
<worker-row
|
|
:worker="worker"
|
|
:selected_worker_ids="selected_worker_ids"
|
|
:show_schedule="show_schedule"
|
|
:show_details="show_details && !show_schedule"
|
|
@worker-selected="$emit('worker-selected', $event)"
|
|
@copy-schedule="$emit('copy-schedule', $event)"
|
|
@toggle-details="show_details = !show_details"
|
|
></worker-row>
|
|
<template v-if="show_details && !show_schedule">
|
|
<tr class='details'>
|
|
<th colspan='3'></th>
|
|
<th colspan='2'>Worker ID</th>
|
|
<th colspan='5'>Address</th>
|
|
</tr>
|
|
<tr class='details'>
|
|
<td colspan='3'></td>
|
|
<td colspan='2'
|
|
class="click-to-copy worker-id"
|
|
onclick="toastr.success('Copied to clipboard.')"
|
|
:data-clipboard-text="worker._id">
|
|
{{ worker._id }}
|
|
</td>
|
|
<td colspan='5'
|
|
class="click-to-copy worker-address"
|
|
onclick="toastr.success('Copied to clipboard.')"
|
|
:data-clipboard-text="worker.address">
|
|
{{ worker.address }}
|
|
</td>
|
|
</tr>
|
|
<tr class='blacklist border-top' v-if="worker.blacklist">
|
|
<td colspan='3'></td>
|
|
<th class="pt-2">Blacklist:</th>
|
|
<th class="pt-2">Job</th>
|
|
<th class="pt-2">Task Type</th>
|
|
<th colspan='4' class="pt-2">Blacklisted On</th>
|
|
</tr>
|
|
<blacklist-row
|
|
v-for="listitem in worker.blacklist"
|
|
:worker="worker"
|
|
:listitem="listitem"
|
|
:key="listitem.job_id + '-' + listitem.task_type"
|
|
:server="server"
|
|
class='blacklist'
|
|
></blacklist-row>
|
|
</template>
|
|
</tbody>
|
|
</script>
|
|
|
|
<!-- template for the 'worker-row' Vue.js component -->
|
|
<script type='text/x-template' id='template_worker_row'>
|
|
<tr :id="worker._id"
|
|
:class="{'worker-row': true, 'is-selected': is_checked, 'is-status-requested': worker.status_requested}">
|
|
<td class='p-2 d-flex justify-content-center align-items-center border-right'>
|
|
<input
|
|
type="checkbox"
|
|
:id="checkbox_id"
|
|
:value="worker._id"
|
|
:checked="is_checked"
|
|
@input="$emit('worker-selected', $event.target.checked)"
|
|
>
|
|
</td>
|
|
<td class='table-cell-nickname px-0 text-center'>
|
|
<action-button v-for="action_key in actions_for_worker"
|
|
:key="action_key"
|
|
:action_key="action_key"
|
|
:worker_id="worker._id"
|
|
@worker-action="performWorkerAction"
|
|
></action-button>
|
|
</td>
|
|
<td class='table-cell-nickname'>
|
|
<label :for="checkbox_id" class="cursor-pointer text-truncate">
|
|
<span :class='"status-indicator status-" + worker.status'></span>
|
|
{{ worker.nickname }}
|
|
</label>
|
|
</td>
|
|
<td class='table-cell-status'>
|
|
{{ worker.status || '[none]' }}
|
|
<span v-if="worker.status_requested">
|
|
<span v-if="worker.lazy_status_request" title="Status change queued for after current task is finished." class="font-weight-bold text-secondary arrow">⇢</span>
|
|
<span v-else title="Immediate status change is requested." class="font-weight-bold text-white arrow immediate">➔</span>
|
|
<span :class="'status-requested status-' + worker.status_requested">{{ worker.status_requested }}</span>
|
|
</span>
|
|
</td>
|
|
|
|
<template v-if="mode != 'edit_schedule'">
|
|
<td class='col-sched-edit-active text-center d-none d-xl-table-cell'>
|
|
<button v-if="worker.sleep_schedule.schedule_active" class='btn btn-sm btn-link' @click="scheduleSetActive(false)">Yes</button>
|
|
<button v-else class='btn btn-sm btn-link text-secondary' @click="scheduleSetActive(true)">No</button>
|
|
</td>
|
|
</template>
|
|
<template v-if="mode == 'show_schedule'">
|
|
<!-- Worker Schedule viewing template -->
|
|
<td :class="worker.sleep_schedule.days_of_week ? '' : 'implied'">{{ worker.sleep_schedule.days_of_week || 'every day'}}</td>
|
|
<td :class="worker.sleep_schedule.time_start ? '' : 'implied'">{{ worker.sleep_schedule.time_start || '00:00'}}</td>
|
|
<td :class="worker.sleep_schedule.time_end ? '' : 'implied'">{{ worker.sleep_schedule.time_end || '24:00'}}</td>
|
|
<td class='col-sched-edit-actions'>
|
|
<button class='btn btn-sm btn-link' @click="$emit('copy-schedule', worker.sleep_schedule)"
|
|
:disabled="selected_worker_ids.length == 0 || is_checked"
|
|
title='Copy this schedule to all selected workers.'>Copy</button>
|
|
<button class='btn btn-sm btn-link px-3' @click="scheduleEditMode()" title='Edit schedule'>Edit</button>
|
|
</td>
|
|
</template>
|
|
<template v-else-if="mode == 'edit_schedule'">
|
|
<!-- Worker Schedule editing template -->
|
|
<td class='col-sched-edit-active text-center'>
|
|
<input type='checkbox' v-model="edit_schedule.schedule_active" class="mt-1">
|
|
</td>
|
|
<td>
|
|
<input class='form-control form-control-sm' type='text' @keyup.enter="scheduleSave()" v-model="edit_schedule.days_of_week"
|
|
title="Space-separated list of day names. The first two letters of each day are enough." placeholder='e.g. "mo tu we th fr"'></td>
|
|
<td>
|
|
<input class='form-control form-control-sm' type='text' @keyup.enter="scheduleSave()" v-model="edit_schedule.time_start" placeholder='e.g. "00:00"'>
|
|
</td>
|
|
<td>
|
|
<input class='form-control form-control-sm' type='text' @keyup.enter="scheduleSave()" v-model="edit_schedule.time_end" placeholder='e.g. "24:00"'>
|
|
</td>
|
|
<td class='col-sched-edit-actions'>
|
|
<button class='btn btn-sm btn-link text-success' @click="scheduleSave()" title='Save Schedule'>Save</button>
|
|
<button class='btn btn-sm btn-link' @click="scheduleEditCancel()" title='Cancel Editing'>Cancel</button>
|
|
</td>
|
|
</template>
|
|
<template v-else>
|
|
<!-- Worker Info template -->
|
|
<td>
|
|
<span v-if="worker.current_task" class="text-truncate worker-current-task">
|
|
<a :href="task_server_url">{{ task_id_text }}</a>
|
|
<template v-if="worker.current_task_status">
|
|
({{ worker.current_task_status }}
|
|
<template v-if="worker.current_task_updated">{{ current_task_updated() }}</template>)
|
|
</template>
|
|
</span>
|
|
<span v-else class="no-task">[none]</span>
|
|
</td>
|
|
<td>
|
|
<template v-if="worker.current_task && worker.current_job">
|
|
<a :href="task_log_url" target="_blank" class="btn btn-link btn-sm px-0"
|
|
title="View the head & tail of this log file">view</a>
|
|
<button class="btn btn-link btn-sm click-to-copy px-0"
|
|
:data-clipboard-text="task_log_curl_command"
|
|
onclick="toastr.success('Copied Curl command to your clipboard')"
|
|
title="Click to copy a Curl command that downloads this entire task log file">curl</button>
|
|
</template>
|
|
</td>
|
|
<td :title="last_activity_abs">
|
|
<span class="text-truncate">{{ last_activity_rel() }}</span>
|
|
</td>
|
|
<td class="d-none d-xl-table-cell">
|
|
{{ worker_software }}
|
|
</td>
|
|
<td>
|
|
<button
|
|
class='btn btn-link btn-sm' style='float: right'
|
|
title="Toggle Worker Details"
|
|
@click.prevent="$emit('toggle-details')">
|
|
<span class="chevron top" v-if="show_details"></span>
|
|
<span class="chevron bottom" v-else></span>
|
|
</button>
|
|
</td>
|
|
</template>
|
|
</tr>
|
|
</script>
|
|
|
|
<!-- template for the 'blacklist-row' Vue.js component -->
|
|
<script type='text/x-template' id='template_blacklist_row'>
|
|
<tr>
|
|
<td colspan='4' class="pb-2"></td>
|
|
<td class="pb-2"><a :href="job_id_url" :title="'Open on ' + server.name" target='_blank'>{{ job_id_text }}</a></td>
|
|
<td class="pb-2">{{ listitem.task_type }}</td>
|
|
<td colspan='4' class="pb-2">
|
|
<span
|
|
@click="forget_blacklist_entry()"
|
|
class="float-right worker-action text-danger font-weight-bold px-2"
|
|
title="click to forget this blacklist entry">forget</span>
|
|
{{ created() }}
|
|
</td>
|
|
</tr>
|
|
</script>
|
|
|
|
<!-- template for the 'action-bar' Vue.js component -->
|
|
<script type='text/x-template' id='template_action_bar'>
|
|
<form>
|
|
<div class="action-bar pt-3 pb-2 mb-1 d-flex align-items-center">
|
|
<select
|
|
class="form-control form-control-sm w-25 ml-1"
|
|
:disabled="selected_worker_ids.length == 0"
|
|
v-model="selected_action"
|
|
>
|
|
<option value="" selected><template v-if="selected_worker_ids.length == 0">Select workers</template><template v-else>Choose an action</template></option>
|
|
<option v-for="(action, key) in actions" :value="key">{{ action.label }}</option>
|
|
</select>
|
|
<button
|
|
type="button"
|
|
:disabled="selected_worker_ids.length == 0 || !selected_action"
|
|
:class="[(selected_worker_ids.length == 0 || !selected_action) ? 'btn-outline-secondary btn-disabled' : 'btn-primary']"
|
|
class="btn btn-sm ml-3 px-3 font-weight-bold"
|
|
@click.prevent="performWorkerAction"
|
|
>Apply</button>
|
|
<span v-if="selected_worker_ids.length > 0" class="text-muted ml-3">{{ selected_worker_ids.length }} worker<template v-if="selected_worker_ids.length != 1">s</template> selected</span>
|
|
<button
|
|
class='btn btn-outline-secondary btn-sm ml-auto px-4 font-weight-bold'
|
|
@click.prevent="$emit('toggle-schedule')"><template v-if="show_schedule">Status</template><template v-else>Schedule</template></button>
|
|
|
|
</div>
|
|
</form>
|
|
</script>
|
|
|
|
<!-- template for the 'bootstrap-modal' Vue.js component -->
|
|
<script type='text/x-template' id='template_bootstrap_modal'>
|
|
<div class="modal" tabindex="-1" role="dialog">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{{ title }}</h5>
|
|
<div v-if="isProcessing" class="spinner-border spinner-border-sm float-right" role="status">
|
|
<span class="sr-only">Processing</span>
|
|
</div>
|
|
<button v-else type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<slot></slot>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary px-3" data-dismiss="modal">{{ labelCancel }}</button>
|
|
<button type="button" class="btn btn-primary px-5 ml-auto" @click="$emit('button-ok')" :disabled="isProcessing">{{ labelOk }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</script>
|