<code><template>
<div v-if="vmData">
<h1 class="title">{{ vmData.name }}</h1>
<hr />
<progress
v-if="formDisabled"
class="progress is-small is-primary"
max="100"
>
15%
</progress>
<div class="columns is-mobile">
<div class="column">
<label class="label">Start Time</label>
<div class="field has-addons has-addons-right">
<div class="control is-expanded">
<input
v-model="vmData.startTime"
class="input"
type="time"
:disabled="vmData.stopTime == null || formDisabled"
/>
</div>
<p v-if="vmData.startTime" class="control">
<button
@click="clearStartTime()"
class="button is-primary"
:disabled="formDisabled"
>
Clear
</button>
</p>
</div>
</div>
<div class="column">
<div class="field">
<label class="label">Stop Time</label>
<div class="control">
<input
v-model="vmData.stopTime"
class="input"
type="time"
:disabled="formDisabled"
/>
</div>
</div>
</div>
</div>
<div class="field">
<label class="label">Days of Week</label>
<div class="columns has-text-centered is-mobile">
<div class="column">
<input
type="checkbox"
v-model="vmData.daysOfWeek.Mon"
:disabled="formDisabled"
/>
Mon
</div>
<div class="column">
<input
type="checkbox"
v-model="vmData.daysOfWeek.Tue"
:disabled="formDisabled"
/>
Tue
</div>
<div class="column">
<input
type="checkbox"
v-model="vmData.daysOfWeek.Wed"
:disabled="formDisabled"
/>
Wed
</div>
<div class="column">
<input
type="checkbox"
v-model="vmData.daysOfWeek.Thu"
:disabled="formDisabled"
/>
Thu
</div>
<div class="column">
<input
type="checkbox"
v-model="vmData.daysOfWeek.Fri"
:disabled="formDisabled"
/>
Fri
</div>
<div class="column">
<input
type="checkbox"
v-model="vmData.daysOfWeek.Sat"
:disabled="formDisabled"
/>
Sat
</div>
<div class="column">
<input
type="checkbox"
v-model="vmData.daysOfWeek.Sun"
:disabled="formDisabled"
/>
Sun
</div>
</div>
</div>
<div v-if="formErrors.length > 0" class="notification is-warning is-light">
<p v-for="error in formErrors" :key="error">{{ error }}</p>
</div>
<div class="field is-grouped is-grouped-right">
<div class="control">
<button
v-if="emptySchedule == false"
@click="removeSchedule()"
class="button is-primary"
:disabled="formDisabled"
>
<i class="fa-solid fa-xmark"></i> Remove Schedule
</button>
</div>
<div class="control">
<button
@click="updateSchedule()"
class="button is-link is-right"
:disabled="formDisabled || formErrors.length > 0"
>
<i class="fa-regular fa-clock"></i> Apply
</button>
</div>
</div>
</div>
</template>
<script>
import { timeToDate } from "../helper.js";
export default {
props: ["vm"],
emits: ["applied"],
watch: {
vm: function (newVal) {
this.vmData = newVal;
},
vmData: {
handler: function (newVal) {
let errors = [];
if (newVal.stopTime == null) {
errors.push("Schedule requires a stop time");
} else {
// Check if at least one day is defined
let dayCount = 0;
Object.keys(newVal.daysOfWeek).forEach(function (value) {
if (newVal.daysOfWeek[value]) {
dayCount += 1;
}
});
if (dayCount == 0) {
errors.push("Schedule requires at least 1 day set");
}
// Check if start date is before end date
if (newVal.startTime && newVal.stopTime) {
if (timeToDate(newVal.startTime) >= timeToDate(newVal.stopTime)) {
errors.push("Start time should be before stop time");
} else if (timeToDate(newVal.stopTime) - timeToDate(newVal.startTime) < 1800000) {
errors.push("Schedule should be at least 30 minutes long")
}
}
}
this.formErrors = errors;
},
deep: true,
},
},
mounted() {
// Make a deep copy of this
this.vmData = JSON.parse(JSON.stringify(this.vm));
// Work out if it's an empty schedule
if (!this.vm.stopTime) {
this.emptySchedule = true;
} else {
this.emptySchedule = false;
}
},
methods: {
clearStartTime: function () {
this.vmData.startTime = null;
},
removeSchedule: function () {
this.formDisabled = true;
let headers = new Headers({
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
});
fetch(`/api/schedule`, {
method: "DELETE",
headers: headers,
body: JSON.stringify(this.vmData),
}).then(() => {
this.formDisabled = false;
this.$emit("applied");
});
},
updateSchedule: function () {
this.formDisabled = true;
let headers = new Headers({
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
});
fetch(`/api/schedule`, {
method: "POST",
headers: headers,
body: JSON.stringify(this.vmData),
}).then(() => {
this.formDisabled = false;
this.$emit("applied");
});
},
},
data() {
return {
formDisabled: false,
vmData: null,
formErrors: [],
emptySchedule: null,
};
},
};
</script>
</code>
import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.mgmt.compute import ComputeManagementClient
import json
import logging
import utilities
from datetime import datetime, timedelta
schedule_bp = func.Blueprint()
def generateCronSchedule(vmData, timeString):
# Create stop time chunk
stopScheduleMinHour = (
f"{vmData[timeString].split(':')[1]} {vmData[timeString].split(':')[0]}"
)
# Create days chunk
daysString = ""
for i in range(1, 8):
if vmData["daysOfWeek"][utilities.daysMapping[i]]:
daysString += f"{i},"
daysString = daysString.rstrip(daysString[-1])
stopSchedule = f"{stopScheduleMinHour} * * {daysString}"
return stopSchedule
@schedule_bp.function_name(name="SetSchedule")
@schedule_bp.route(route="api/schedule", auth_level=func.AuthLevel.ANONYMOUS)
def set_schedule(req: func.HttpRequest) -> func.HttpResponse:
vmData = json.loads(req.get_body())
# Extract subscription id and resource group from vm id
subscriptionId = vmData["id"].split("/")[2]
resourceGroup = vmData["id"].split("/")[4]
vmName = vmData["id"].split("/")[8]
compute_client = ComputeManagementClient(
credential=DefaultAzureCredential(exclude_environment_credential=True), subscription_id=subscriptionId
)
vmInstance = compute_client.virtual_machines.get(
resource_group_name=resourceGroup, vm_name=vmName
)
# Check the method type to see if we're adding or deleting a schedule
if req.method == "DELETE":
logging.info("REMOVING SCHEDULE")
# Calculate updated tags
tags = {}
if vmInstance.tags:
tags = vmInstance.tags
tags.pop(utilities.STARTSCHEDULETAG, None)
tags.pop(utilities.STOPSCHEDULETAG, None)
else:
tags = {}
if vmInstance.tags:
tags = vmInstance.tags
# Turn on the VM for an 8 hour period
start_time = datetime.utcnow()
stop_time = start_time + timedelta(hours=8)
startSchedule = f"{start_time.minute} {start_time.hour} * * *"
stopSchedule = f"{stop_time.minute} {stop_time.hour} * * *"
tags[utilities.STARTSCHEDULETAG] = startSchedule
tags[utilities.STOPSCHEDULETAG] = stopSchedule
add_tags_event = compute_client.virtual_machines.begin_create_or_update(
resource_group_name=resourceGroup,
vm_name=vmName,
parameters={"location": vmInstance.location, "tags": tags},
polling_interval=1,
)
add_tags_event.wait()
return func.HttpResponse("OK")