Compare commits
29 Commits
xo-server/
...
xo-server/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0ac14363d | ||
|
|
5d346aba37 | ||
|
|
124cb15ebe | ||
|
|
a244ab898d | ||
|
|
3c551590eb | ||
|
|
10e30cccbc | ||
|
|
806a6b86a2 | ||
|
|
9719fdf5cc | ||
|
|
6d8764f8cb | ||
|
|
d9fd9cb408 | ||
|
|
7710ec0aba | ||
|
|
c97bd78cd0 | ||
|
|
728c5aa86e | ||
|
|
83d68ca293 | ||
|
|
47d7561db4 | ||
|
|
7d993e8319 | ||
|
|
1d1a597b22 | ||
|
|
23082f9300 | ||
|
|
ea1a7f9376 | ||
|
|
1796c7bab8 | ||
|
|
65ad76479a | ||
|
|
422db04ec8 | ||
|
|
d12f60fe37 | ||
|
|
194c1c991c | ||
|
|
3e8e2222c1 | ||
|
|
1620327a33 | ||
|
|
b1131e3667 | ||
|
|
db0250ac08 | ||
|
|
0a6b605760 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xo-server",
|
||||
"version": "5.0.3",
|
||||
"version": "5.1.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Server part of Xen-Orchestra",
|
||||
"keywords": [
|
||||
@@ -38,7 +38,7 @@
|
||||
"@marsaud/smb2-promise": "^0.2.0",
|
||||
"app-conf": "^0.4.0",
|
||||
"babel-runtime": "^6.5.0",
|
||||
"base64url": "^1.0.5",
|
||||
"base64url": "^2.0.0",
|
||||
"blocked": "^1.1.0",
|
||||
"bluebird": "^3.1.1",
|
||||
"body-parser": "^1.13.3",
|
||||
@@ -79,6 +79,7 @@
|
||||
"make-error": "^1",
|
||||
"micromatch": "^2.3.2",
|
||||
"minimist": "^1.2.0",
|
||||
"moment-timezone": "^0.5.4",
|
||||
"ms": "^0.7.1",
|
||||
"multikey-hash": "^1.0.1",
|
||||
"ndjson": "^1.4.3",
|
||||
@@ -121,7 +122,7 @@
|
||||
"leche": "^2.1.1",
|
||||
"mocha": "^2.2.1",
|
||||
"must": "^0.13.1",
|
||||
"nyc": "^6.4.2",
|
||||
"nyc": "^7.0.0",
|
||||
"rimraf": "^2.5.2",
|
||||
"sinon": "^1.14.1",
|
||||
"standard": "^7.0.0"
|
||||
|
||||
@@ -7,6 +7,7 @@ html
|
||||
title Xen Orchestra
|
||||
meta(name = 'author' content = 'Vates SAS')
|
||||
link(rel = 'stylesheet' href = 'styles/main.css')
|
||||
link(rel = 'stylesheet' href = 'v4/styles/main.css')
|
||||
body
|
||||
.container
|
||||
.row-login
|
||||
|
||||
@@ -3,6 +3,7 @@ const debug = createDebug('xo:api')
|
||||
|
||||
import getKeys from 'lodash/keys'
|
||||
import kindOf from 'kindof'
|
||||
import moment from 'moment-timezone'
|
||||
import ms from 'ms'
|
||||
import schemaInspector from 'schema-inspector'
|
||||
|
||||
@@ -188,6 +189,11 @@ methodSignature.description = 'returns the signature of an API method'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const getServerTimezone = (tz => () => tz)(moment.tz.guess())
|
||||
getServerTimezone.description = 'return the timezone server'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default class Api {
|
||||
constructor ({
|
||||
context,
|
||||
@@ -201,6 +207,7 @@ export default class Api {
|
||||
system: {
|
||||
getMethodsInfo,
|
||||
getServerVersion,
|
||||
getServerTimezone,
|
||||
getVersion,
|
||||
listMethods,
|
||||
methodSignature
|
||||
|
||||
@@ -17,8 +17,8 @@ get.params = {
|
||||
id: {type: 'string'}
|
||||
}
|
||||
|
||||
export async function create ({jobId, cron, enabled, name}) {
|
||||
return /* await */ this.createSchedule(this.session.get('user_id'), {job: jobId, cron, enabled, name})
|
||||
export async function create ({ jobId, cron, enabled, name, timezone }) {
|
||||
return /* await */ this.createSchedule(this.session.get('user_id'), { job: jobId, cron, enabled, name, timezone })
|
||||
}
|
||||
|
||||
create.permission = 'admin'
|
||||
@@ -30,8 +30,8 @@ create.params = {
|
||||
name: {type: 'string', optional: true}
|
||||
}
|
||||
|
||||
export async function set ({id, jobId, cron, enabled, name}) {
|
||||
await this.updateSchedule(id, {job: jobId, cron, enabled, name})
|
||||
export async function set ({ id, jobId, cron, enabled, name, timezone }) {
|
||||
await this.updateSchedule(id, { job: jobId, cron, enabled, name, timezone })
|
||||
}
|
||||
|
||||
set.permission = 'admin'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asInteger } from '../xapi/utils'
|
||||
import {
|
||||
ensureArray,
|
||||
forEach,
|
||||
@@ -344,7 +345,7 @@ export async function createIscsi ({
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port
|
||||
deviceConfig.port = asInteger(port)
|
||||
}
|
||||
|
||||
const srRef = await xapi.call(
|
||||
@@ -405,7 +406,7 @@ export async function probeIscsiIqns ({
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port
|
||||
deviceConfig.port = asInteger(port)
|
||||
}
|
||||
|
||||
let xml
|
||||
@@ -483,7 +484,7 @@ export async function probeIscsiLuns ({
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port
|
||||
deviceConfig.port = asInteger(port)
|
||||
}
|
||||
|
||||
let xml
|
||||
@@ -562,7 +563,7 @@ export async function probeIscsiExists ({
|
||||
|
||||
// if we give another port than default iSCSI
|
||||
if (port) {
|
||||
deviceConfig.port = port
|
||||
deviceConfig.port = asInteger(port)
|
||||
}
|
||||
|
||||
const xml = parseXml(await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmoiscsi', {}))
|
||||
|
||||
@@ -134,7 +134,7 @@ create = $coroutine (params) ->
|
||||
installation = extract(params, 'installation')
|
||||
params.installRepository = installation && installation.repository
|
||||
|
||||
resourceSet = extract(params, resourceSet)
|
||||
resourceSet = extract(params, 'resourceSet')
|
||||
|
||||
xapiVm = yield xapi.createVm(template._xapiId, params)
|
||||
vm = xapi.xo.addObject(xapiVm)
|
||||
@@ -149,7 +149,7 @@ create = $coroutine (params) ->
|
||||
if resourceSet
|
||||
yield Promise.all([
|
||||
@addAcl(user.id, vm.id, 'admin')
|
||||
xapi.xo.setData(xapiVm.$id, 'resourceSet')
|
||||
xapi.xo.setData(xapiVm.$id, 'resourceSet', resourceSet)
|
||||
])
|
||||
|
||||
return vm.id
|
||||
@@ -455,7 +455,9 @@ set.params = {
|
||||
# Kernel arguments for PV VM.
|
||||
PV_args: { type: 'string', optional: true }
|
||||
|
||||
cpuWeight: { type: 'integer', optional: true}
|
||||
cpuWeight: { type: ['integer', 'null'], optional: true }
|
||||
|
||||
cpuCap: { type: ['integer', 'null'], optional: true }
|
||||
}
|
||||
|
||||
set.resolve = {
|
||||
|
||||
@@ -224,7 +224,8 @@ async function registerPlugin (pluginPath, pluginName) {
|
||||
// Supports both “normal” CommonJS and Babel's ES2015 modules.
|
||||
const {
|
||||
default: factory = plugin,
|
||||
configurationSchema
|
||||
configurationSchema,
|
||||
configurationPresets
|
||||
} = plugin
|
||||
|
||||
// The default export can be either a factory or directly a plugin
|
||||
@@ -237,6 +238,7 @@ async function registerPlugin (pluginPath, pluginName) {
|
||||
pluginName,
|
||||
instance,
|
||||
configurationSchema,
|
||||
configurationPresets,
|
||||
version
|
||||
)
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export default class JobExecutor {
|
||||
event: 'jobCall.end',
|
||||
runJobId,
|
||||
runCallId,
|
||||
error: reason
|
||||
error: {...reason, message: reason.message}
|
||||
})
|
||||
|
||||
call.error = reason
|
||||
|
||||
@@ -15,13 +15,14 @@ export class Schedules extends Collection {
|
||||
return 'schedule:'
|
||||
}
|
||||
|
||||
create (userId, job, cron, enabled, name = undefined) {
|
||||
create (userId, job, cron, enabled, name = undefined, timezone = undefined) {
|
||||
return this.add(new Schedule({
|
||||
userId,
|
||||
job,
|
||||
cron,
|
||||
enabled,
|
||||
name
|
||||
name,
|
||||
timezone
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
50
src/schemas/user.js
Normal file
50
src/schemas/user.js
Normal file
@@ -0,0 +1,50 @@
|
||||
export default {
|
||||
$schema: 'http://json-schema.org/draft-04/schema#',
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'unique identifier for this user'
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
description: 'email address of this user'
|
||||
},
|
||||
groups: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
description: 'identifier of groups this user belong to'
|
||||
},
|
||||
permission: {
|
||||
enum: ['none', 'read', 'write', 'admin'],
|
||||
description: 'root permission for this user, none and admin are the only significant ones'
|
||||
},
|
||||
preferences: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
lang: { type: 'string' },
|
||||
sshKeys: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: { type: 'string' },
|
||||
title: { type: 'string' }
|
||||
},
|
||||
required: [
|
||||
'key',
|
||||
'title'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
description: 'various user preferences'
|
||||
}
|
||||
},
|
||||
required: [
|
||||
'id',
|
||||
'email'
|
||||
]
|
||||
}
|
||||
39
src/utils.js
39
src/utils.js
@@ -12,6 +12,12 @@ import keys from 'lodash/keys'
|
||||
import kindOf from 'kindof'
|
||||
import multiKeyHashInt from 'multikey-hash'
|
||||
import xml2js from 'xml2js'
|
||||
|
||||
// Moment timezone can be loaded only one time, it's a workaround to load
|
||||
// the latest version because cron module uses an old version of moment which
|
||||
// does not implement `guess` function for example.
|
||||
import 'moment-timezone'
|
||||
|
||||
import { CronJob } from 'cron'
|
||||
import {
|
||||
all as pAll,
|
||||
@@ -439,27 +445,30 @@ export const streamToArray = (stream, {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const scheduleFn = (cronPattern, fn) => {
|
||||
export const scheduleFn = (cronTime, fn, timeZone) => {
|
||||
let running = false
|
||||
|
||||
const job = new CronJob(cronPattern, async () => {
|
||||
if (running) {
|
||||
return
|
||||
}
|
||||
const job = new CronJob({
|
||||
cronTime,
|
||||
onTick: async () => {
|
||||
if (running) {
|
||||
return
|
||||
}
|
||||
|
||||
running = true
|
||||
running = true
|
||||
|
||||
try {
|
||||
await fn()
|
||||
} catch (error) {
|
||||
console.error('[WARN] scheduled function:', error && error.stack || error)
|
||||
} finally {
|
||||
running = false
|
||||
}
|
||||
try {
|
||||
await fn()
|
||||
} catch (error) {
|
||||
console.error('[WARN] scheduled function:', error && error.stack || error)
|
||||
} finally {
|
||||
running = false
|
||||
}
|
||||
},
|
||||
start: true,
|
||||
timeZone
|
||||
})
|
||||
|
||||
job.start()
|
||||
|
||||
return () => {
|
||||
job.stop()
|
||||
}
|
||||
|
||||
@@ -335,8 +335,10 @@ const TRANSFORMS = {
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.VCPUs_params && obj.VCPUs_params.weight) {
|
||||
vm.cpuWeight = obj.VCPUs_params.weight
|
||||
let tmp
|
||||
if ((tmp = obj.VCPUs_params)) {
|
||||
tmp.cap && (vm.cpuCap = +tmp.cap)
|
||||
tmp.weight && (vm.cpuWeight = +tmp.weight)
|
||||
}
|
||||
|
||||
if (!isHvm) {
|
||||
|
||||
@@ -455,7 +455,7 @@ export default class Xapi extends XapiBase {
|
||||
const resolveVersionPatches = function (uuids) {
|
||||
const versionPatches = createRawObject()
|
||||
|
||||
forEach(uuids, ({uuid}) => {
|
||||
forEach(ensureArray(uuids), ({uuid}) => {
|
||||
versionPatches[uuid] = patches[uuid]
|
||||
})
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
const template = this.getObject(templateId)
|
||||
|
||||
// Clones the template.
|
||||
const vm = await this._getOrWaitObject(
|
||||
let vm = await this._getOrWaitObject(
|
||||
await this[clone ? '_cloneVm' : '_copyVm'](template, nameLabel)
|
||||
)
|
||||
|
||||
@@ -137,21 +137,26 @@ export default {
|
||||
// Creates the user defined VDIs.
|
||||
//
|
||||
// TODO: set vm.suspend_SR
|
||||
vdis && await Promise.all(mapToArray(vdis, (vdiDescription, i) => {
|
||||
return this._createVdi(
|
||||
vdiDescription.size, // FIXME: Should not be done in Xapi.
|
||||
{
|
||||
name_label: vdiDescription.name_label,
|
||||
name_description: vdiDescription.name_description,
|
||||
sr: vdiDescription.sr || vdiDescription.SR
|
||||
}
|
||||
)
|
||||
.then(ref => this._getOrWaitObject(ref))
|
||||
.then(vdi => this._createVbd(vm, vdi, {
|
||||
// Only the first VBD if installMethod is not cd is bootable.
|
||||
bootable: installMethod !== 'cd' && !i
|
||||
}))
|
||||
}))
|
||||
if (vdis) {
|
||||
const devices = await this.call('VM.get_allowed_VBD_devices', vm.$ref)
|
||||
await Promise.all(mapToArray(vdis, (vdiDescription, i) => {
|
||||
return this._createVdi(
|
||||
vdiDescription.size, // FIXME: Should not be done in Xapi.
|
||||
{
|
||||
name_label: vdiDescription.name_label,
|
||||
name_description: vdiDescription.name_description,
|
||||
sr: vdiDescription.sr || vdiDescription.SR
|
||||
}
|
||||
)
|
||||
.then(ref => this._getOrWaitObject(ref))
|
||||
.then(vdi => this._createVbd(vm, vdi, {
|
||||
// Only the first VBD if installMethod is not cd is bootable.
|
||||
bootable: installMethod !== 'cd' && !i,
|
||||
|
||||
userdevice: devices[i]
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
// Destroys the VIFs cloned from the template.
|
||||
await Promise.all(mapToArray(vm.$VIFs, vif => this._deleteVif(vif)))
|
||||
@@ -173,6 +178,9 @@ export default {
|
||||
// TODO: Assign VGPUs.
|
||||
|
||||
if (cloudConfig != null) {
|
||||
// Refresh the record.
|
||||
vm = this.getObject(vm.$id)
|
||||
|
||||
// Find the SR of the first VDI.
|
||||
let srRef
|
||||
forEach(vm.$VBDs, vbd => {
|
||||
@@ -234,6 +242,14 @@ export default {
|
||||
]
|
||||
},
|
||||
|
||||
cpuCap: {
|
||||
addToLimits: true,
|
||||
get: vm => vm.VCPUs_params.cap && +vm.VCPUs_params.cap,
|
||||
set (cap, vm) {
|
||||
return this._updateObjectMapProperty(vm, 'VCPUs_params', { cap })
|
||||
}
|
||||
},
|
||||
|
||||
cpusMax: 'cpusStaticMax',
|
||||
cpusStaticMax: {
|
||||
constraints: {
|
||||
@@ -245,7 +261,7 @@ export default {
|
||||
|
||||
cpuWeight: {
|
||||
addToLimits: true,
|
||||
get: vm => +(vm.VCPUs_params.weight || 0),
|
||||
get: vm => vm.VCPUs_params.weight && +vm.VCPUs_params.weight,
|
||||
set (weight, vm) {
|
||||
return this._updateObjectMapProperty(vm, 'VCPUs_params', { weight })
|
||||
}
|
||||
@@ -268,6 +284,7 @@ export default {
|
||||
|
||||
memory: 'memoryMax',
|
||||
memoryMax: {
|
||||
addToLimits: true,
|
||||
constraints: {
|
||||
memoryMin: lte,
|
||||
memoryStaticMax: gte
|
||||
|
||||
@@ -268,6 +268,10 @@ export const makeEditObject = specs => {
|
||||
}
|
||||
|
||||
const set = (value, name) => {
|
||||
if (value === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const spec = specs[name]
|
||||
if (!spec) {
|
||||
return
|
||||
|
||||
@@ -50,11 +50,13 @@ export default class {
|
||||
name,
|
||||
instance,
|
||||
configurationSchema,
|
||||
configurationPresets,
|
||||
version
|
||||
) {
|
||||
const id = name
|
||||
const plugin = this._plugins[id] = {
|
||||
configured: !configurationSchema,
|
||||
configurationPresets,
|
||||
configurationSchema,
|
||||
id,
|
||||
instance,
|
||||
@@ -101,6 +103,7 @@ export default class {
|
||||
|
||||
async _getPlugin (id) {
|
||||
const {
|
||||
configurationPresets,
|
||||
configurationSchema,
|
||||
loaded,
|
||||
name,
|
||||
@@ -120,6 +123,7 @@ export default class {
|
||||
unloadable,
|
||||
version,
|
||||
configuration,
|
||||
configurationPresets,
|
||||
configurationSchema
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import every from 'lodash/every'
|
||||
import keyBy from 'lodash/keyBy'
|
||||
import remove from 'lodash/remove'
|
||||
import some from 'lodash/some'
|
||||
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
isObject,
|
||||
lightSet,
|
||||
map,
|
||||
mapToArray,
|
||||
streamToArray
|
||||
} from '../utils'
|
||||
|
||||
@@ -23,6 +25,33 @@ class NoSuchResourceSet extends NoSuchObject {
|
||||
}
|
||||
}
|
||||
|
||||
const computeVmResourcesUsage = vm => {
|
||||
const processed = {}
|
||||
let disks = 0
|
||||
let disk = 0
|
||||
|
||||
forEach(vm.$VBDs, vbd => {
|
||||
let vdi, vdiId
|
||||
if (
|
||||
vbd.type === 'Disk' &&
|
||||
!processed[vdiId = vbd.VDI] &&
|
||||
(vdi = vbd.$VDI)
|
||||
) {
|
||||
processed[vdiId] = true
|
||||
++disks
|
||||
disk += +vdi.virtual_size
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
cpus: vm.VCPUs_at_startup,
|
||||
disk,
|
||||
disks,
|
||||
memory: vm.memory_dynamic_max,
|
||||
vms: 1
|
||||
}
|
||||
}
|
||||
|
||||
const normalize = set => ({
|
||||
id: set.id,
|
||||
limits: set.limits
|
||||
@@ -84,29 +113,9 @@ export default class {
|
||||
}
|
||||
|
||||
computeVmResourcesUsage (vm) {
|
||||
const processed = {}
|
||||
let disks = 0
|
||||
let disk = 0
|
||||
forEach(this._xo.getXapi(vm).getObject(vm._xapiId).$VBDs, (vbd) => {
|
||||
let vdi, vdiId
|
||||
if (
|
||||
vbd.type === 'Disk' &&
|
||||
!processed[vdiId = vbd.VDI] &&
|
||||
(vdi = vbd.$VDI)
|
||||
) {
|
||||
processed[vdiId] = true
|
||||
++disks
|
||||
disk += +vdi.virtual_size
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
cpus: vm.CPUs.number,
|
||||
disk,
|
||||
disks,
|
||||
memory: vm.memory.size,
|
||||
vms: 1
|
||||
}
|
||||
return computeVmResourcesUsage(
|
||||
this._xo.getXapi(vm).getObject(vm._xapiId)
|
||||
)
|
||||
}
|
||||
|
||||
async createResourceSet (name, subjects = undefined, objects = undefined, limits = undefined) {
|
||||
@@ -268,7 +277,7 @@ export default class {
|
||||
}
|
||||
|
||||
async recomputeResourceSetsLimits () {
|
||||
const sets = await this.getAllResourceSets()
|
||||
const sets = keyBy(await this.getAllResourceSets(), 'id')
|
||||
forEach(sets, ({ limits }) => {
|
||||
forEach(limits, (limit, id) => {
|
||||
limit.available = limit.total
|
||||
@@ -292,13 +301,15 @@ export default class {
|
||||
}
|
||||
|
||||
const { limits } = set
|
||||
forEach(this.computeVmResourcesUsage(object), (usage, resource) => {
|
||||
forEach(computeVmResourcesUsage(object), (usage, resource) => {
|
||||
const limit = limits[resource]
|
||||
limit.available -= usage
|
||||
if (limit) {
|
||||
limit.available -= usage
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
await Promise.all(map(sets, (set) => this._save(set)))
|
||||
await Promise.all(mapToArray(sets, set => this._save(set)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,10 @@ export default class {
|
||||
_enable (schedule) {
|
||||
const { id } = schedule
|
||||
|
||||
const stopSchedule = scheduleFn(schedule.cron, () =>
|
||||
this.xo.runJobSequence([ schedule.job ])
|
||||
const stopSchedule = scheduleFn(
|
||||
schedule.cron,
|
||||
() => this.xo.runJobSequence([ schedule.job ]),
|
||||
schedule.timezone
|
||||
)
|
||||
|
||||
this._cronJobs[id] = stopSchedule
|
||||
@@ -137,8 +139,8 @@ export default class {
|
||||
return /* await */ this._redisSchedules.get()
|
||||
}
|
||||
|
||||
async createSchedule (userId, {job, cron, enabled, name}) {
|
||||
const schedule_ = await this._redisSchedules.create(userId, job, cron, enabled, name)
|
||||
async createSchedule (userId, { job, cron, enabled, name, timezone }) {
|
||||
const schedule_ = await this._redisSchedules.create(userId, job, cron, enabled, name, timezone)
|
||||
const schedule = schedule_.properties
|
||||
|
||||
this._add(schedule)
|
||||
@@ -146,13 +148,18 @@ export default class {
|
||||
return schedule
|
||||
}
|
||||
|
||||
async updateSchedule (id, {job, cron, enabled, name}) {
|
||||
async updateSchedule (id, { job, cron, enabled, name, timezone }) {
|
||||
const schedule = await this._getSchedule(id)
|
||||
|
||||
if (job) schedule.set('job', job)
|
||||
if (cron) schedule.set('cron', cron)
|
||||
if (enabled !== undefined) schedule.set('enabled', enabled)
|
||||
if (name !== undefined) schedule.set('name', name)
|
||||
if (timezone === null) {
|
||||
schedule.set('timezone', undefined) // Remove current timezone
|
||||
} else if (timezone !== undefined) {
|
||||
schedule.set('timezone', timezone)
|
||||
}
|
||||
|
||||
await this._redisSchedules.save(schedule)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user