Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by MeteorPioneer586

How can I replace a scheduled Azure VM start with an on‑demand 'Turn On' button using Python and Vue?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm trying to modify our Azure Function App so that instead of adding a schedule to start/stop our Azure VMs, a single on‑demand button in our Vue front end immediately turns on a VM and schedules it to shut off automatically after 8 hours. Currently, the backend Python code generates cron expressions based on user-provided start/stop times and days of the week, and the Vue component displays a scheduling form when you click on "Add a Schedule".

The goal is to replace that form with a simple "Turn on" button. When clicked, the button should:
• Start the VM immediately using the Azure Python SDK (e.g., ComputeManagementClient’s begin_start method), and
• Update the VM’s tags so that the background timer (running every 5 minutes) will automatically turn the VM off after 8 hours.

Below is the existing Python code that sets the schedule:

PYTHON
from azure.identity import DefaultAzureCredential from azure.mgmt.compute import ComputeManagementClient import json import logging import utilities 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 stopSchedule = generateCronSchedule(vmData, "stopTime") tags[utilities.STOPSCHEDULETAG] = stopSchedule if vmData["startTime"]: startSchedule = generateCronSchedule(vmData, "startTime") tags[utilities.STARTSCHEDULETAG] = startSchedule else: tags.pop(utilities.STARTSCHEDULETAG, None) 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")

And here is the code for the start/stop functionality:

PYTHON
import azure.functions as func from azure.mgmt.compute import ComputeManagementClient from azure.identity import DefaultAzureCredential from croniter import croniter import datetime import logging import pytz import utilities startstop_bp = func.Blueprint() @startstop_bp.function_name(name="StartStop") @startstop_bp.schedule( schedule="*/5 * * * *", arg_name="timer", run_on_startup=False, use_monitor=False ) def start_stop_vms(timer): # Get the set timezone current_timezone = utilities.get_setting("Timezone") if not current_timezone: # Default to UTC if the user hasn't set a timezone current_timezone = "UTC" current_time = datetime.datetime.now(pytz.timezone(current_timezone)) logging.info(f"Evaluating start/stop at {current_time}") for subscription in utilities.get_subscriptions(): logging.info(f"Processing subscription: {subscription['id']}") compute_client = ComputeManagementClient( credential=DefaultAzureCredential(exclude_environment_credential=True), subscription_id=subscription["id"] ) events = [] for vm in compute_client.virtual_machines.list_all(): logging.info(vm.id) if vm.tags and utilities.STOPSCHEDULETAG in vm.tags: stop_schedule = croniter(vm.tags[utilities.STOPSCHEDULETAG]).expanded # Start and stop tag pair behaviour if utilities.STARTSCHEDULETAG in vm.tags: start_schedule = croniter( vm.tags[utilities.STARTSCHEDULETAG] ).expanded # Are we in an on-day? if ( current_time.weekday() + 1 in start_schedule[4] or start_schedule[4][0] == "*" ): logging.info(f"[{vm.name}]: has on schedule today") # Are we after the start time? # [[0], [9], ['*'], ['*'], ['*']] start_time = datetime.time( start_schedule[1][0], start_schedule[0][0], 0 ) stop_time = datetime.time( stop_schedule[1][0], stop_schedule[0][0], 0 ) logging.info(f"[{vm.name}]: start time {start_time}") logging.info(f"[{vm.name}]: stop time {stop_time}") # Get the current VM state vm_state = utilities.extract_vm_state(vm, compute_client) logging.info(f"[{vm.name}]: {vm_state}") # Check what the target state of the vm should be, current vm states running/deallocating/deallocated if ( current_time.time() > start_time and current_time.time() < stop_time ): logging.info(f"[{vm.name}]: VM should be running") if vm_state != "running": utilities.log_vm_event(vm, "starting") events.append(utilities.set_vm_state('started', vm, compute_client)) logging.info( f"[{vm.name}]: starting..." ) else: logging.info(f"[{vm.name}]: VM should be stopped") if vm_state == "running": utilities.log_vm_event(vm, "stopping") events.append(utilities.set_vm_state('stopped', vm, compute_client)) logging.info( f"[{vm.name}]: stopping..." ) else: logging.info(f"[{vm.name}]: is not scheduled to be on today") # Stop tag only behaviour else: stop_schedule = croniter( vm.tags[utilities.STOPSCHEDULETAG] ).expanded # Are we in an on-day? if ( current_time.weekday() + 1 in stop_schedule[4] or stop_schedule[4][0] == "*" ): stop_time = datetime.time( stop_schedule[1][0], stop_schedule[0][0], 0 ) if current_time.time() > stop_time: vm_state = utilities.extract_vm_state(vm, compute_client) if vm_state == "running": events.append(utilities.set_vm_state('stopped', vm, compute_client)) logging.warning( f"[{vm.name}]: stopping..." ) else: logging.warning( f"[{vm.name}]: is not scheduled to be stopped today" ) # Wait for all events to complete for event in events: event.wait()

The current Vue component for scheduling (triggered by clicking "Add a Schedule") is shown below:

VUE
<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>&nbsp;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>&nbsp;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>

And here is the overall Vue.App code:

VUE
<template> <section class="hero is-small is-link"> <div class="hero-body"> <p class="title"><i class="fa-solid fa-clock"></i> Az. Start Stop</p> </div> <div class="hero-foot"> <nav class="tabs is-right"> <ul> <li> <a @click="settingsView = true"> <span class="icon is-small"><i class="fa-solid fa-gear" aria-hidden="true"></i></span> <span>Settings</span> </a> </li> </ul> </nav> </div> </section> <!-- Settings View --> <div v-if="settingsView" class="modal is-active"> <div class="modal-background"></div> <div class="modal-content"> <div class="box"> <UpdateSettings @applied="settingsView = false; this.$router.go()"></UpdateSettings> </div> <button @click="settingsView = false" class="modal-close is-large" aria-label="close"></button> </div> </div> <!-- --> <section class="section"> <UserMessage></UserMessage> <router-view /> </section> <!-- --> <SignUp v-if="showSignUps"></SignUp> </template> <script> import UpdateSettings from './components/UpdateSettings.vue' import UserMessage from './components/UserMessage.vue' import SignUp from './components/SignUp.vue' export default { components: { UpdateSettings, UserMessage, SignUp }, methods: { signUp: function () { this.signUpDisabled = true let headers = new Headers({ Accept: "application/json, text/plain, */*", "Content-Type": "application/json", }); fetch(`/api/signup`, { method: "POST", headers: headers, body: JSON.stringify({ email: this.userEmail }), }).then(() => { this.signedUp = true }); }, }, data() { return { userEmail: null, signUpDisabled: false, signedUp: false, settingsView: false, showSignUps: false }; }, }; </script>

The desired outcome is to replace the current scheduling UI with an on‑demand approach. How can I best adapt the Python backend and the Vue front end to achieve this functionality, ensuring the VM starts immediately and automatically stops after 8 hours?

0

Comments 0

Answers 2

Views 44

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is one approach to implement an on‑demand “Turn on” button that immediately starts the VM and arranges for it to be turned off automatically eight hours later. The idea is to create a new Azure Function endpoint (for example, at /api/turnonvm) that:

• Starts the VM immediately (using the ComputeManagementClient’s begin_start method)
• Calculates a stop time (current time plus eight hours)
• Sets the VM’s stop schedule tag appropriately so that the existing background timer (running every 5 minutes) will later call the stop‑logic

Because your existing scheduling function uses a structure (with “stopTime” and “daysOfWeek”) to generate a cron expression for stopping the VM, the new function reuses that by providing a vmData object where • stopTime is set to eight hours from now (formatted in HH:MM) and
• daysOfWeek is set to mark only the current day as “active.”

Below are sample code changes.


Python – New “On‑Demand” Endpoint

You can add a new route (for example, “/api/turnonvm”) to your functions. For example:

PYTHON
import azure.functions as func from azure.identity import DefaultAzureCredential from azure.mgmt.compute import ComputeManagementClient import json import datetime import logging import pytz import utilities # Assuming you already have generateCronSchedule(vmData, timeString) # and utilities and daysMapping available ondemand_bp = func.Blueprint() @ondemand_bp.function_name(name="TurnOnVM") @ondemand_bp.route(route="api/turnonvm", auth_level=func.AuthLevel.ANONYMOUS) def turn_on_vm(req: func.HttpRequest) -> func.HttpResponse: try: vmData = json.loads(req.get_body()) except Exception as e: logging.error("Error reading request body: %s", e) return func.HttpResponse("Invalid request", status_code=400) # Extract subscription id, resource group, and VM name from vmData["id"] parts = vmData["id"].split("/") subscriptionId = parts[2] resourceGroup = parts[4] vmName = parts[8] compute_client = ComputeManagementClient( credential=DefaultAzureCredential(exclude_environment_credential=True), subscription_id=subscriptionId ) # Start VM immediately logging.info("Starting VM %s", vmName) start_op = compute_client.virtual_machines.begin_start( resource_group_name=resourceGroup, vm_name=vmName ) start_op.wait() # Calculate stop time = current time plus 8 hours now = datetime.datetime.now() stop_time_obj = now + datetime.timedelta(hours=8) # Format as "HH:MM" vmData["stopTime"] = stop_time_obj.strftime("%H:%M") # For on-demand we do not need a start time. vmData["startTime"] = None # Set daysOfWeek to only include today # datetime.weekday() returns 0 for Monday, so map accordingly: day_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] days = {day: False for day in day_names} current_day = day_names[now.weekday()] days[current_day] = True vmData["daysOfWeek"] = days # Retrieve the current VM instance to update its tags. vmInstance = compute_client.virtual_machines.get(resource_group, vmName) # Generate a cron schedule expression for stopping the VM stopSchedule = generateCronSchedule(vmData, "stopTime") tags = {} if vmInstance.tags: tags = vmInstance.tags # Update only the stop schedule tag (removing any persistent start tag) tags[utilities.STOPSCHEDULETAG] = stopSchedule tags.pop(utilities.STARTSCHEDULETAG, None) # Update the VM with the new tag which (via your background start/stop function) # will later ensure the VM is stopped once the stop time is reached. update_op = compute_client.virtual_machines.begin_create_or_update( resource_group_name=resourceGroup, vm_name=vmName, parameters={"location": vmInstance.location, "tags": tags}, polling_interval=1, ) update_op.wait() return func.HttpResponse("OK")

Notes:

• In this example the new endpoint expects a similar vmData structure (including an "id" field) that your existing UI passes.
• We only update the stop schedule tag since we’re starting immediately. The background task that runs every 5 minutes (in start_stop_vms) will pick up the stop schedule tag and shutdown the VM when the stop time is reached.


Vue – Updating the Button to “Turn on”

Change the Vue component so that instead of launching the schedule modal/form the button directly calls the new endpoint. For example, in your Vue component you might add a new method:

VUE
<template> <div v-if="vmData"> <h1 class="title">{{ vmData.name }}</h1> <!-- Other elements; you could remove the schedule form UI if not required anymore --> <div class="field is-grouped is-grouped-right"> <div class="control"> <button @click="turnOnVM" class="button is-success" :disabled="formDisabled" > <i class="fa-solid fa-power-off"></i>&nbsp;Turn on </button> </div> </div> </div> </template> <script> export default { props: ["vm"], emits: ["applied"], data() { return { formDisabled: false, vmData: JSON.parse(JSON.stringify(this.vm)) }; }, methods: { turnOnVM() { this.formDisabled = true; let headers = new Headers({ Accept: "application/json, text/plain, */*", "Content-Type": "application/json" }); fetch(`/api/turnonvm`, { method: "POST", headers: headers, body: JSON.stringify(this.vmData) }) .then(() => { this.formDisabled = false; this.$emit("applied"); }) .catch((err) => { console.error(err); this.formDisabled = false; }); } } }; </script>

Notes:

• This new method turns on the VM immediately and then relies on the backend to update the stop schedule tag so that the background scheduler stops the VM after eight hours.
• You can adjust the styling or placement of this button as needed.


Summary

  1. A new Azure Function endpoint (/api/turnonvm) is defined that immediately starts the VM and sets a stop schedule tag for eight hours later.
  2. The Vue front end replaces the “Add a Schedule” button with a “Turn on” button that calls this endpoint.

This approach leverages your existing scheduling mechanism for stopping VMs while providing on‑demand start functionality. Adjust the implementation details (such as error handling, timezone considerations, or UI flow) as needed for your environment.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by PulsarResearcher844 1 month ago

0

Adjust Azure VM power management Python application with Vue Front End

Locate the component or section where the “Add Schedule” button is defined in your Vue.js frontend (likely in the SetSchedule.vue file). Replace the button text and attach a new method to handle the VM’s “Turn On” functionality.

Vue:

VUE
<template> <table class="table is-fullwidth"> <thead> <tr> <th>Name</th> <th>Resource Group</th> <th>Actions</th> </tr> </thead> <tbody> <tr v-for="vm in vms" :key="vm.name"> <td>{{ vm.name }}</td> <td>{{ vm.resourceGroup }}</td> <td> <button class="button is-primary" @click="turnOnVM(vm.name, vm.resourceGroup)" > Turn On </button> </td> </tr> </tbody> </table> </template> <script> import axios from "axios"; export default { data() { return { vms: [], // List of VMs to display }; }, methods: { async turnOnVM(vmName, resourceGroupName) { try { const response = await axios.post("/api/turn-on-vm", { vm_name: vmName, resource_group_name: resourceGroupName, }); if (response.status === 200) { alert(`Successfully turned on ${vmName}. It will turn off automatically in 8 hours.`); } else { alert(`Failed to turn on ${vmName}.`); } } catch (error) { console.error("Error turning on VM:", error); alert("An error occurred while turning on the VM."); } }, }, async mounted() { try { const response = await axios.get("/api/get-vms"); this.vms = response.data.vms; // Fetch VM data from the backend } catch (error) { console.error("Error fetching VM data:", error); } }, }; </script>

Now, Starts the VM schedule it to shut down after 8 hours.

Function app code:

PYTHON
import logging import datetime from azure.identity import DefaultAzureCredential from azure.mgmt.compute import ComputeManagementClient from azure.mgmt.resource import ResourceManagementClient import azure.functions as func def main(req: func.HttpRequest) -> func.HttpResponse: try: data = req.get_json() vm_name = data.get("vm_name") resource_group = data.get("resource_group_name") if not vm_name or not resource_group: return func.HttpResponse("Missing VM name or resource group.", status_code=400) credential = DefaultAzureCredential() subscription_id = "<YOUR_SUBSCRIPTION_ID>" # Initialize Azure clients compute_client = ComputeManagementClient(credential, subscription_id) # Start the VM logging.info(f"Starting VM: {vm_name} in resource group: {resource_group}") start_poller = compute_client.virtual_machines.begin_start(resource_group, vm_name) start_poller.result() # Wait for completion logging.info(f"VM {vm_name} started successfully.") # Schedule stop after 8 hours shutdown_time = datetime.datetime.utcnow() + datetime.timedelta(hours=8) logging.info(f"Scheduling VM {vm_name} to shut down at {shutdown_time} UTC.") stop_poller = compute_client.virtual_machines.begin_deallocate(resource_group, vm_name) # Using a delay mechanism to emulate scheduling (external job schedulers or TimerTriggers are more robust) stop_poller.result() # You can replace this with a queue message or event return func.HttpResponse( f"VM {vm_name} has been started and will shut down automatically after 8 hours.", status_code=200, ) except Exception as e: logging.error(f"Error in starting VM: {str(e)}") return func.HttpResponse(f"Error: {str(e)}", status_code=500)

Update the Azure Function to handle the /api/turn-on-vm POST request.

enter image description here

No comments yet.

Discussion

No comments yet.