mirror of
https://github.com/linuxserver/davos.git
synced 2026-04-20 00:01:51 +08:00
Implemented UI into API. Can now create/update schedules
This commit is contained in:
parent
c15d8fca38
commit
e7eba69e96
@ -0,0 +1,6 @@
|
||||
package io.linuxserver.davos.dto;
|
||||
|
||||
public class ScheduleProcessResponse {
|
||||
|
||||
public String message = "OK";
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package io.linuxserver.davos.exception;
|
||||
|
||||
public class ScheduleAlreadyRunningException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ScheduleAlreadyRunningException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package io.linuxserver.davos.exception;
|
||||
|
||||
public class ScheduleNotRunningException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ScheduleNotRunningException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,10 @@
|
||||
package io.linuxserver.davos.schedule;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
@ -11,6 +14,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.linuxserver.davos.exception.ScheduleAlreadyRunningException;
|
||||
import io.linuxserver.davos.exception.ScheduleNotRunningException;
|
||||
import io.linuxserver.davos.persistence.dao.ScheduleConfigurationDAO;
|
||||
import io.linuxserver.davos.persistence.model.ScheduleConfigurationModel;
|
||||
|
||||
@ -18,7 +23,9 @@ import io.linuxserver.davos.persistence.model.ScheduleConfigurationModel;
|
||||
public class ScheduleExecutor {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleExecutor.class);
|
||||
|
||||
|
||||
private Map<Long, ScheduledFuture<?>> runningSchedules = new HashMap<>();
|
||||
|
||||
@Resource
|
||||
private ScheduleConfigurationDAO scheduleConfigurationDAO;
|
||||
|
||||
@ -32,23 +39,55 @@ public class ScheduleExecutor {
|
||||
public void runAutomaticStartupSchedules() {
|
||||
|
||||
LOGGER.info("Initialising automatic startup schedules");
|
||||
|
||||
|
||||
for (ScheduleConfigurationModel model : scheduleConfigurationDAO.getAll()) {
|
||||
|
||||
if (model.startAutomatically) {
|
||||
|
||||
RunnableSchedule runnable = new RunnableSchedule(model.id, scheduleConfigurationDAO);
|
||||
scheduledExecutorService.scheduleAtFixedRate(runnable, 0, model.interval, TimeUnit.MINUTES);
|
||||
ScheduledFuture<?> runningSchedule = scheduledExecutorService.scheduleAtFixedRate(runnable, 0, model.interval,
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
runningSchedules.put(model.id, runningSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LOGGER.info("Automatic startup schedules should now be running");
|
||||
}
|
||||
|
||||
public void startSchedule(Long id) {
|
||||
|
||||
ScheduleConfigurationModel model = scheduleConfigurationDAO.getConfig(id);
|
||||
RunnableSchedule runnable = new RunnableSchedule(model.id, scheduleConfigurationDAO);
|
||||
scheduledExecutorService.scheduleAtFixedRate(runnable, 0, model.interval, TimeUnit.MINUTES);
|
||||
|
||||
public void startSchedule(Long id) throws ScheduleAlreadyRunningException {
|
||||
|
||||
if (!runningSchedules.containsKey(id)) {
|
||||
|
||||
ScheduleConfigurationModel model = scheduleConfigurationDAO.getConfig(id);
|
||||
RunnableSchedule runnable = new RunnableSchedule(model.id, scheduleConfigurationDAO);
|
||||
|
||||
LOGGER.info("Starting schedule {}", id);
|
||||
ScheduledFuture<?> runningSchedule = scheduledExecutorService.scheduleAtFixedRate(runnable, 0, model.interval,
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
runningSchedules.put(model.id, runningSchedule);
|
||||
|
||||
} else {
|
||||
throw new ScheduleAlreadyRunningException();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopSchedule(Long id) throws ScheduleNotRunningException {
|
||||
|
||||
if (runningSchedules.containsKey(id)) {
|
||||
|
||||
LOGGER.info("Stopping schedule {}", id);
|
||||
|
||||
if (!runningSchedules.get(id).isCancelled()) {
|
||||
|
||||
runningSchedules.get(id).cancel(true);
|
||||
runningSchedules.remove(id);
|
||||
LOGGER.info("Schedule should now be stopped");
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new ScheduleNotRunningException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,36 +7,52 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import io.linuxserver.davos.dto.ScheduleConfigurationDTO;
|
||||
import io.linuxserver.davos.dto.converters.ScheduleConfigurationDTOConverter;
|
||||
import io.linuxserver.davos.persistence.dao.ScheduleConfigurationDAO;
|
||||
import io.linuxserver.davos.persistence.model.ScheduleConfigurationModel;
|
||||
import io.linuxserver.davos.persistence.repository.ScheduleConfigurationRepository;
|
||||
import io.linuxserver.davos.transfer.ftp.FileTransferType;
|
||||
import io.linuxserver.davos.transfer.ftp.TransferProtocol;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/")
|
||||
public class MainController {
|
||||
|
||||
@Resource
|
||||
private ScheduleConfigurationDAO scheduleConfigurationDAO;
|
||||
|
||||
@RequestMapping("/")
|
||||
|
||||
@RequestMapping
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping("/scheduling")
|
||||
public String scheduling(Model model) {
|
||||
|
||||
|
||||
model.addAttribute("schedules", scheduleConfigurationDAO.getAll());
|
||||
|
||||
|
||||
return "scheduling";
|
||||
}
|
||||
|
||||
|
||||
@RequestMapping("/scheduling/{id}")
|
||||
public String schedulingEdit(@PathVariable Long id, Model model) {
|
||||
|
||||
|
||||
ScheduleConfigurationModel config = scheduleConfigurationDAO.getConfig(id);
|
||||
model.addAttribute("schedule", new ScheduleConfigurationDTOConverter().convert(config));
|
||||
|
||||
return "schedulingedit";
|
||||
}
|
||||
|
||||
@RequestMapping("/scheduling/new")
|
||||
public String schedulingNew(Model model) {
|
||||
|
||||
ScheduleConfigurationDTO schedule = new ScheduleConfigurationDTO();
|
||||
schedule.interval = 60;
|
||||
schedule.transferType = FileTransferType.FILE;
|
||||
schedule.connectionType = TransferProtocol.FTP;
|
||||
|
||||
model.addAttribute("schedule", schedule);
|
||||
|
||||
return "schedulingedit";
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@ -12,23 +14,64 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.linuxserver.davos.dto.ScheduleConfigurationDTO;
|
||||
import io.linuxserver.davos.dto.ScheduleProcessResponse;
|
||||
import io.linuxserver.davos.dto.converters.ScheduleConfigurationDTOConverter;
|
||||
import io.linuxserver.davos.dto.converters.ScheduleConfigurationModelConverter;
|
||||
import io.linuxserver.davos.exception.ScheduleAlreadyRunningException;
|
||||
import io.linuxserver.davos.exception.ScheduleNotRunningException;
|
||||
import io.linuxserver.davos.persistence.dao.ScheduleConfigurationDAO;
|
||||
import io.linuxserver.davos.persistence.model.ScheduleConfigurationModel;
|
||||
import io.linuxserver.davos.schedule.ScheduleExecutor;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1")
|
||||
public class RestAPIController {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RestAPIController.class);
|
||||
|
||||
@Resource
|
||||
private ScheduleConfigurationDAO scheduleConfigurationDAO;
|
||||
|
||||
@Resource
|
||||
private ScheduleExecutor scheduleExecutor;
|
||||
|
||||
@RequestMapping(value = "/schedule/{id}")
|
||||
public ScheduleConfigurationDTO getScheduleConfig(@PathVariable("id") Long id) {
|
||||
return toDTO(scheduleConfigurationDAO.getConfig(id));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/schedule/{id}/stop")
|
||||
public ScheduleProcessResponse stopScheduleConfig(@PathVariable("id") Long id) {
|
||||
|
||||
ScheduleProcessResponse scheduleProcessResponse = new ScheduleProcessResponse();
|
||||
|
||||
try {
|
||||
scheduleExecutor.stopSchedule(id);
|
||||
scheduleProcessResponse.message = "Schedule stopped";
|
||||
} catch (ScheduleNotRunningException e) {
|
||||
LOGGER.info("Unable to stop schedule {} as it was not initially running", id);
|
||||
scheduleProcessResponse.message = "Unable to stop schedule. Wasn't running.";
|
||||
}
|
||||
|
||||
return scheduleProcessResponse;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/schedule/{id}/start")
|
||||
public ScheduleProcessResponse startScheduleConfig(@PathVariable("id") Long id) {
|
||||
|
||||
ScheduleProcessResponse scheduleProcessResponse = new ScheduleProcessResponse();
|
||||
|
||||
try {
|
||||
scheduleExecutor.startSchedule(id);
|
||||
scheduleProcessResponse.message = "Schedule started";
|
||||
} catch (ScheduleAlreadyRunningException e) {
|
||||
LOGGER.info("Not starting schedule {} as it is already running", id);
|
||||
scheduleProcessResponse.message = "Unable to start schedule. May already be running";
|
||||
}
|
||||
|
||||
return scheduleProcessResponse;
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/schedule", method = RequestMethod.POST)
|
||||
public ScheduleConfigurationDTO createScheduleConfig(@RequestBody ScheduleConfigurationDTO dto) {
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#main-nav {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 200px;
|
||||
width: 250px;
|
||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
#logo {
|
||||
@ -58,7 +58,7 @@ html {
|
||||
bottom: 0;
|
||||
}
|
||||
main {
|
||||
padding-left: 200px;
|
||||
padding-left: 250px;
|
||||
}
|
||||
h1.header {
|
||||
color: #fff;
|
||||
@ -108,3 +108,7 @@ a.collection-item.schedule {
|
||||
font-size: 12px;
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.chip {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
157
src/main/resources/static/js/davos.js
Normal file
157
src/main/resources/static/js/davos.js
Normal file
@ -0,0 +1,157 @@
|
||||
/*global $, jQuery, base, Materialize */
|
||||
var filter = (function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var initialise;
|
||||
|
||||
initialise = function () {
|
||||
|
||||
$('#add_new_filter').on('click', function () {
|
||||
|
||||
var filter = $('#new_filter').val();
|
||||
|
||||
if ($.trim(filter.length) > 0) {
|
||||
|
||||
$('#filter_chips').append('<div class="chip"><input type="hidden" value="" class="filter_id" /><span class="filter_value">' +
|
||||
filter + '</span><i class="material-icons">close</i></div> ');
|
||||
|
||||
$('#new_filter').val('');
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
init: initialise
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
|
||||
var action = (function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var initialise;
|
||||
|
||||
initialise = function () {
|
||||
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
init: initialise
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
|
||||
var schedule = (function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var initialise, save, onSuccess, onError, cleanId, startSchedule, stopSchedule, startResponse, stopResponse;
|
||||
|
||||
initialise = function () {
|
||||
|
||||
$('#start_schedule').on('click', startSchedule);
|
||||
$('#stop_schedule').on('click', stopSchedule);
|
||||
|
||||
$('#save_schedule').on('click', function () {
|
||||
|
||||
var postData = {
|
||||
|
||||
id: cleanId($('#schedule_id').val()),
|
||||
name: $('#schedule_name').val(),
|
||||
startAutomatically: $('#start_automatically').prop('checked'),
|
||||
interval: parseInt($('#interval option:checked').attr('value'), 10),
|
||||
connectionType: $('input[name="protocol_type"]:checked').val(),
|
||||
hostName: $('#server_name').val(),
|
||||
port: parseInt($('#server_port').val(), 10),
|
||||
username: $('#username').val(),
|
||||
password: $('#password').val(),
|
||||
remoteFilePath: $('#remote_path').val(),
|
||||
localFilePath: $('#local_path').val(),
|
||||
transferType: $('input[name="scan_type"]:checked').val(),
|
||||
filters: [],
|
||||
actions: []
|
||||
};
|
||||
|
||||
$('#filter_chips .chip').each(function () {
|
||||
|
||||
var filter = {
|
||||
|
||||
id: cleanId($(this).find('.filter_id').val()),
|
||||
value: $(this).find('.filter_value').text()
|
||||
};
|
||||
|
||||
postData.filters.push(filter);
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
|
||||
method: "POST",
|
||||
url: "/api/v1/schedule",
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(postData)
|
||||
|
||||
}).done(onSuccess);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
cleanId = function (id) {
|
||||
|
||||
if ($.trim(id).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parseInt(id, 10);
|
||||
};
|
||||
|
||||
startSchedule = function () {
|
||||
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: "/api/v1/schedule/" + cleanId($('#schedule_id').val()) + "/start"
|
||||
}).done(startResponse).fail(startResponse);
|
||||
};
|
||||
|
||||
stopSchedule = function () {
|
||||
|
||||
$.ajax({
|
||||
method: "GET",
|
||||
url: "/api/v1/schedule/" + cleanId($('#schedule_id').val()) + "/stop"
|
||||
}).done(stopResponse).fail(stopResponse);
|
||||
};
|
||||
|
||||
startResponse = function (msg) {
|
||||
Materialize.toast(msg.message, 3000, 'rounded');
|
||||
};
|
||||
|
||||
stopResponse = function (msg) {
|
||||
Materialize.toast(msg.message, 3000, 'rounded');
|
||||
};
|
||||
|
||||
onSuccess = function (msg) {
|
||||
|
||||
if (msg.id === cleanId($('#schedule_id').val())) {
|
||||
Materialize.toast('Schedule Saved', 3000, 'rounded');
|
||||
} else {
|
||||
window.location.replace('/scheduling/' + msg.id);
|
||||
}
|
||||
};
|
||||
|
||||
onError = function (msg) {
|
||||
Materialize.toast('Unable to save schedule config', 3000, 'rounded');
|
||||
};
|
||||
|
||||
return {
|
||||
init: initialise
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
|
||||
jQuery(document).ready(filter.init);
|
||||
jQuery(document).ready(action.init);
|
||||
jQuery(document).ready(schedule.init);
|
||||
@ -55,12 +55,12 @@
|
||||
<h4 class="light">Current Schedules</h4>
|
||||
|
||||
<div class="collection">
|
||||
<a th:each="schedule : ${schedules}" th:href="@{/scheduling/edit/} + ${schedule.id}" class="collection-item schedule" th:text="${schedule.name}"></a>
|
||||
<a th:each="schedule : ${schedules}" th:href="@{/scheduling/} + ${schedule.id}" class="collection-item schedule" th:text="${schedule.name}"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fixed-action-btn" style="bottom: 45px; right: 24px;">
|
||||
<a class="btn-floating btn-large teal waves-effect waves-teal">
|
||||
<a th:href="@{/scheduling/new}" class="btn-floating btn-large teal waves-effect waves-teal">
|
||||
<i class="large material-icons">add</i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
<link type="text/css" rel="stylesheet" th:href="@{/css/davos.css}" media="screen" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<title th:inline="text">Edit [[${schedule.name}]]</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -44,223 +46,250 @@
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<form class="col s12">
|
||||
|
||||
<div class="row">
|
||||
<div class="section">
|
||||
<h4 class="light" th:text="${schedule.name}"></h4>
|
||||
<p class="caption light">Click on the list items below to edit the schedule</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="section">
|
||||
<h4 class="light" th:text="${schedule.name}"></h4>
|
||||
<input type="hidden" id="schedule_id" th:attr="value=${schedule.id}" />
|
||||
<p class="caption light">Click on the list items below to edit the schedule</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row">
|
||||
|
||||
<ul class="collapsible" data-collapsible="expandable">
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">settings_applications</i>General</div>
|
||||
<div class="collapsible-body">
|
||||
<ul class="collapsible" data-collapsible="expandable">
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">settings_applications</i>General</div>
|
||||
<div class="collapsible-body">
|
||||
|
||||
<div class="row valign-wrapper">
|
||||
<div class="row valign-wrapper">
|
||||
|
||||
<div class="input-field col s4">
|
||||
<input id="schedule_name" placeholder="Please choose a name for this schedule" th:attr="value=${schedule.name}" type="text" class="validate" />
|
||||
<label for="schedule_name">Schedule name</label>
|
||||
</div>
|
||||
<div class="col s8">
|
||||
<p class="subtext">
|
||||
Give this schedule a meaningful name so you know what its purpose is.
|
||||
</p>
|
||||
</div>
|
||||
<div class="input-field col s4">
|
||||
<input id="schedule_name" placeholder="Please choose a name for this schedule" th:attr="value=${schedule.name}" type="text" class="validate" />
|
||||
<label for="schedule_name">Schedule name</label>
|
||||
</div>
|
||||
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s4 ">
|
||||
<label>
|
||||
<input id="start-automatically" type="checkbox" th:checked="${schedule.startAutomatically}" />
|
||||
<span class="lever"></span>
|
||||
</label>
|
||||
Start Automatically
|
||||
</div>
|
||||
<div class="col s8">
|
||||
<p class="subtext">
|
||||
Select this if you want this schedule to automatically start when davos starts. This is a useful option if davos needs to restart and you don't want to have to manually kick off this process.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="input-field col s4">
|
||||
<select class="browser-default">
|
||||
<option value="" disabled="disabled" selected="selected">Please select...</option>
|
||||
<option th:selected="${schedule.interval == 15}" value="15">Every 15 minutes</option>
|
||||
<option th:selected="${schedule.interval == 30}" value="30">Every 30 minutes</option>
|
||||
<option th:selected="${schedule.interval == 60}" value="60">Every hour</option>
|
||||
<option th:selected="${schedule.interval == 120}" value="120">Every 2 hours</option>
|
||||
<option th:selected="${schedule.interval == 720}" value="720">Twice a day</option>
|
||||
<option th:selected="${schedule.interval == 1440}" value="1440">Once a day</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col s8">
|
||||
<p class="subtext">
|
||||
The interval that defines how often this schedule will poll the server to check for new files.
|
||||
</p>
|
||||
</div>
|
||||
<div class="col s8">
|
||||
<p class="subtext">
|
||||
Give this schedule a meaningful name so you know what its purpose is.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">business</i>Server</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s2">
|
||||
<input name="protocol_type" type="radio" id="protocol_ftp" th:checked="${schedule.connectionType == 'FTP'}" />
|
||||
<label for="protocol_ftp">FTP</label>
|
||||
</div>
|
||||
<div class="switch col s2">
|
||||
<input name="protocol_type" type="radio" id="protocol_ftps" th:checked="${schedule.connectionType == 'FTPS'}" />
|
||||
<label for="protocol_ftps">FTPS</label>
|
||||
</div>
|
||||
<div class="switch col s2">
|
||||
<input name="protocol_type" type="radio" id="protocol_sftp" th:checked="${schedule.connectionType == 'SFTP'}" />
|
||||
<label for="protocol_sftp">SFTP</label>
|
||||
</div>
|
||||
<div class="switch col s6"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">business</i>
|
||||
<input id="server_name" type="text" th:attr="value=${schedule.hostName}" />
|
||||
<label for="server_name">Server Name (or IP address)</label>
|
||||
</div>
|
||||
<div class="input-field col s2">
|
||||
<i class="material-icons prefix">settings_input_svideo</i>
|
||||
<input id="server_port" type="number" th:attr="value=${schedule.port}" />
|
||||
<label for="server_port">Port</label>
|
||||
</div>
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s4 ">
|
||||
<label>
|
||||
<input id="start_automatically" type="checkbox" th:checked="${schedule.startAutomatically}" />
|
||||
<span class="lever"></span>
|
||||
</label>
|
||||
Start Automatically
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">perm_identity</i>
|
||||
<input id="username" type="text" th:attr="value=${schedule.username}" />
|
||||
<label for="username">Username</label>
|
||||
</div>
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">vpn_key</i>
|
||||
<input id="password" type="password" th:attr="value=${schedule.password}" />
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">track_changes</i>
|
||||
<input id="remote-path" type="text" th:attr="value=${schedule.remoteFilePath}" />
|
||||
<label for="remote-path">Remote folder to scan</label>
|
||||
</div>
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">play_for_work</i>
|
||||
<input id="local-path" type="text" th:attr="value=${schedule.localFilePath}" />
|
||||
<label for="local-path">Local destination folder</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">swap_vert</i>File Transfers</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s2">
|
||||
<input name="scan_type" type="radio" id="scan_files_only" th:checked="${schedule.transferType == 'FILE'}" />
|
||||
<label for="scan_files_only">Files only</label>
|
||||
</div>
|
||||
<div class="col s10">
|
||||
<p class="subtext">
|
||||
Only download files in the remote folder. Ignore sub-folders.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s2">
|
||||
<input name="scan_type" type="radio" id="scan_recursive" th:checked="${schedule.transferType == 'RECURSIVE'}" />
|
||||
<label for="scan_recursive">Recursive</label>
|
||||
</div>
|
||||
<div class="col s10">
|
||||
<p class="subtext">
|
||||
Look inside any sub-folders in the remote folder. If any files are found, download them.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">sort_by_alpha</i>Filters</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row">
|
||||
<form class="col s12">
|
||||
<div class="col s12">
|
||||
<p class="subtext">
|
||||
If you do not want davos to pull down all recent files from the server during the next scheduled run, please define a filter (or more). A filter works by telling davos that a file must adhere to a particular naming structure (file name or extension) before it decides to download it. For example, if you only want the schedule to download <i>.doc</i> files, you should enter a filter of <strong>*.doc</strong>. You may use a question mark (<strong>?</strong>) to denote a single wildcard character, or an asterisk (<strong>*</strong>) to denote any number of wildcard characters.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<textarea id="filter-keywords" class="materialize-textarea"></textarea>
|
||||
<label for="filter-keywords">Filter keywords (one per line)</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="col s8">
|
||||
<p class="subtext">
|
||||
Select this if you want this schedule to automatically start when davos starts. This is a useful option if davos needs to restart and you don't want to have to manually kick off this process.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">code</i>Post-download Actions</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="input-field col s4">
|
||||
<select class="browser-default" id="interval">
|
||||
<option th:selected="${schedule.interval == 15}" th:value="15">Every 15 minutes</option>
|
||||
<option th:selected="${schedule.interval == 30}" th:value="30">Every 30 minutes</option>
|
||||
<option th:selected="${schedule.interval == 60}" selected="selected" th:value="60">Every hour</option>
|
||||
<option th:selected="${schedule.interval == 120}" th:value="120">Every 2 hours</option>
|
||||
<option th:selected="${schedule.interval == 720}" th:value="720">Twice a day</option>
|
||||
<option th:selected="${schedule.interval == 1440}" th:value="1440">Once a day</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col s8">
|
||||
<p class="subtext">
|
||||
The interval that defines how often this schedule will poll the server to check for new files.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">business</i>Server</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s2">
|
||||
<input name="protocol_type" value="FTP" type="radio" id="protocol_ftp" th:checked="${schedule.connectionType != null and schedule.connectionType.name() == 'FTP'}" />
|
||||
<label for="protocol_ftp">FTP</label>
|
||||
</div>
|
||||
<div class="switch col s2">
|
||||
<input name="protocol_type" value="FTPS" type="radio" id="protocol_ftps" th:checked="${schedule.connectionType != null and schedule.connectionType.name() == 'FTPS'}" />
|
||||
<label for="protocol_ftps">FTPS</label>
|
||||
</div>
|
||||
<div class="switch col s2">
|
||||
<input name="protocol_type" value="SFTP" type="radio" id="protocol_sftp" th:checked="${schedule.connectionType != null and schedule.connectionType.name() == 'SFTP'}" />
|
||||
<label for="protocol_sftp">SFTP</label>
|
||||
</div>
|
||||
<div class="switch col s6"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">business</i>
|
||||
<input id="server_name" type="text" th:attr="value=${schedule.hostName}" />
|
||||
<label for="server_name">Server Name (or IP address)</label>
|
||||
</div>
|
||||
<div class="input-field col s2">
|
||||
<i class="material-icons prefix">settings_input_svideo</i>
|
||||
<input id="server_port" type="number" th:attr="value=${schedule.port}" />
|
||||
<label for="server_port">Port</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">perm_identity</i>
|
||||
<input id="username" type="text" th:attr="value=${schedule.username}" />
|
||||
<label for="username">Username</label>
|
||||
</div>
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">vpn_key</i>
|
||||
<input id="password" type="password" th:attr="value=${schedule.password}" />
|
||||
<label for="password">Password</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">track_changes</i>
|
||||
<input id="remote_path" type="text" th:attr="value=${schedule.remoteFilePath}" />
|
||||
<label for="remote-path">Remote folder to scan</label>
|
||||
</div>
|
||||
<div class="input-field col s6">
|
||||
<i class="material-icons prefix">play_for_work</i>
|
||||
<input id="local_path" type="text" th:attr="value=${schedule.localFilePath}" />
|
||||
<label for="local-path">Local destination folder</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">swap_vert</i>File Transfers</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s2">
|
||||
<input name="scan_type" value="FILE" type="radio" id="scan_files_only" th:checked="${schedule.transferType != null and schedule.transferType.name() == 'FILE'}" />
|
||||
<label for="scan_files_only">Files only</label>
|
||||
</div>
|
||||
<div class="col s10">
|
||||
<p class="subtext">
|
||||
Only download files in the remote folder. Ignore sub-folders.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row valign-wrapper">
|
||||
<div class="switch col s2">
|
||||
<input name="scan_type" value="RECURSIVE" type="radio" id="scan_recursive" th:checked="${schedule.transferType != null and schedule.transferType.name() == 'RECURSIVE'}" />
|
||||
<label for="scan_recursive">Recursive</label>
|
||||
</div>
|
||||
<div class="col s10">
|
||||
<p class="subtext">
|
||||
Look inside any sub-folders in the remote folder. If any files are found, download them.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">sort_by_alpha</i>Filters</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row">
|
||||
<form class="col s12">
|
||||
<div class="col s12">
|
||||
<p class="subtext">
|
||||
Add actions for davos to perform after each file has been successfully downloaded.
|
||||
If you do not want davos to pull down all recent files from the server during the next scheduled run, please define a filter (or more). A filter works by telling davos that a file must adhere to a particular naming structure (file name or extension) before it decides to download it. For example, if you only want the schedule to download <i>.doc</i> files, you should enter a filter of <strong>*.doc</strong>. You may use a question mark (<strong>?</strong>) to denote a single wildcard character, or an asterisk (<strong>*</strong>) to denote any number of wildcard characters.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="input-field col s6">
|
||||
<input id="new_filter" type="text" />
|
||||
<label for="new_filter">Add filter</label>
|
||||
</div>
|
||||
<div class="input-field col s6">
|
||||
<button class="btn waves-effect waves-light" type="button" id="add_new_filter">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="input-field col s12" id="filter_chips">
|
||||
|
||||
<div class="chip" th:each="filter : ${schedule.filters}">
|
||||
<input type="hidden" class="filter_id" th:attr="value=${filter.id}" />
|
||||
<span class="filter_value" th:text="${filter.value}"></span>
|
||||
<i class="material-icons">close</i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header active"><i class="material-icons">code</i>Post-download Actions</div>
|
||||
<div class="collapsible-body">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<p class="subtext">
|
||||
Add actions for davos to perform after each file has been successfully downloaded.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="input-field col s4">
|
||||
<select class="browser-default">
|
||||
<option value="" disabled="disabled" selected="selected">Add action...</option>
|
||||
<option value="pushbullet">Pushbullet Notification</option>
|
||||
<option value="move">Move downloaded file</option>
|
||||
</select>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="download_actions">
|
||||
|
||||
<div class="row download_action">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn waves-effect waves-light" type="button" id="save_schedule">Save changes</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="section">
|
||||
<h5 class="light">Schedule Options</h5>
|
||||
<button class="btn waves-effect waves-light green lighten-2" id="start_schedule" type="button">Start</button>
|
||||
<button class="btn waves-effect waves-light red lighten-2" id="stop_schedule" type="button">Stop</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="btn waves-effect waves-light" type="submit" name="action">Save changes</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="section">
|
||||
<h5 class="light">Schedule Options</h5>
|
||||
<button class="btn waves-effect waves-light green lighten-2" type="submit" name="action">Start</button>
|
||||
<button class="btn waves-effect waves-light orange lighten-2" type="submit" name="action">Restart</button>
|
||||
<button class="btn waves-effect waves-light red lighten-2" type="submit" name="action">Stop</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
|
||||
<script type="text/javascript" th:src="@{/js/jquery-2.1.4.min.js}"></script>
|
||||
<script type="text/javascript" th:src="@{/js/materialize.min.js}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
<script type="text/javascript" th:src="@{/js/davos.js}"></script>
|
||||
|
||||
<script type="text/javascript" th:inline="text">
|
||||
var base = "[[@{/}]]"
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@ -16,6 +16,7 @@ import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import io.linuxserver.davos.exception.ScheduleAlreadyRunningException;
|
||||
import io.linuxserver.davos.persistence.dao.ScheduleConfigurationDAO;
|
||||
import io.linuxserver.davos.persistence.model.ScheduleConfigurationModel;
|
||||
|
||||
@ -62,11 +63,24 @@ public class ScheduleExecutorTest {
|
||||
|
||||
ScheduleConfigurationModel config = new ScheduleConfigurationModel();
|
||||
config.interval = 86;
|
||||
|
||||
|
||||
when(mockConfigurationDAO.getConfig(1337L)).thenReturn(config);
|
||||
|
||||
|
||||
scheduleExecutor.startSchedule(1337L);
|
||||
|
||||
verify(mockExecutorService).scheduleAtFixedRate(any(RunnableSchedule.class), eq(0l), eq(86l), eq(TimeUnit.MINUTES));
|
||||
}
|
||||
|
||||
@Test(expected = ScheduleAlreadyRunningException.class)
|
||||
public void startScheduleShouldNotRunScheduleIfAlreadyRunning() {
|
||||
|
||||
ScheduleConfigurationModel config = new ScheduleConfigurationModel();
|
||||
config.interval = 86;
|
||||
config.id = 1337L;
|
||||
|
||||
when(mockConfigurationDAO.getConfig(1337L)).thenReturn(config);
|
||||
|
||||
scheduleExecutor.startSchedule(1337L);
|
||||
scheduleExecutor.startSchedule(1337L);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user