Vue.js + Pilvio Authentication
In this tutorial, we will create a Vue.js application that communicates with the Pilvio API through a backend proxy and uses Pilvio API token authentication for infrastructure management.
What we will build
- A Vue.js 3 SPA with a Pilvio infrastructure management dashboard
- A backend proxy that securely relays Pilvio API requests
- Listing, starting, and stopping VMs from the user interface
Prerequisites
- Pilvio account and API token (see overview)
- Node.js 20+
Step 1: Create the project
npm create vue@latest pilvio-vue-dashboard
cd pilvio-vue-dashboard
npm install
npm install axios express dotenv
Step 2: Backend proxy server
The Pilvio API token must never reach the frontend code. We will create a simple Express proxy.
Create the file server/proxy.js:
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
require('dotenv').config();
const app = express();
const PORT = process.env.PROXY_PORT || 3001;
// CORS frontendi jaoks
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', process.env.FRONTEND_URL || 'http://localhost:5173');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE');
next();
});
// Pilvio API proxy — lisab automaatselt API tokeni
app.use('/pilvio', (req, res, next) => {
// Kontroll: kasutaja peab olema autenditud (lihtne näide)
const sessionToken = req.headers.authorization;
if (!sessionToken || sessionToken !== `Bearer ${process.env.APP_SESSION_SECRET}`) {
return res.status(401).json({ error: 'Autentimine nõutav' });
}
next();
}, createProxyMiddleware({
target: 'https://api.pilvio.com',
changeOrigin: true,
pathRewrite: { '^/pilvio': '/v1' },
onProxyReq: (proxyReq) => {
proxyReq.setHeader('apikey', process.env.PILVIO_API_TOKEN);
},
}));
app.listen(PORT, () => {
console.log(`Pilvio proxy server pordil ${PORT}`);
});
npm install http-proxy-middleware
Create the file .env:
PILVIO_API_TOKEN=sinu-pilvio-api-token
APP_SESSION_SECRET=genereeri-tugev-saladus
FRONTEND_URL=http://localhost:5173
PROXY_PORT=3001
Step 3: Pilvio API composable
Create the file src/composables/usePilvio.js:
import { ref } from 'vue';
import axios from 'axios';
const PROXY_URL = import.meta.env.VITE_PROXY_URL || 'http://localhost:3001';
const SESSION_SECRET = import.meta.env.VITE_SESSION_SECRET;
const api = axios.create({
baseURL: `${PROXY_URL}/pilvio`,
headers: {
'Authorization': `Bearer ${SESSION_SECRET}`,
},
});
export function usePilvio() {
const vms = ref([]);
const loading = ref(false);
const error = ref(null);
async function fetchVMs() {
loading.value = true;
error.value = null;
try {
const { data } = await api.get('/user-resource/vm/list');
vms.value = data;
} catch (err) {
error.value = 'VM-ide laadimine ebaõnnestus';
} finally {
loading.value = false;
}
}
async function startVM(uuid) {
try {
await api.post('/user-resource/vm/start', `uuid=${uuid}`, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
await fetchVMs();
} catch (err) {
error.value = `VM käivitamine ebaõnnestus: ${uuid}`;
}
}
async function stopVM(uuid) {
try {
await api.post('/user-resource/vm/stop', `uuid=${uuid}`, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});
await fetchVMs();
} catch (err) {
error.value = `VM peatamine ebaõnnestus: ${uuid}`;
}
}
async function fetchLocations() {
const { data } = await api.get('/config/locations');
return data;
}
return { vms, loading, error, fetchVMs, startVM, stopVM, fetchLocations };
}
Step 4: Dashboard component
Create the file src/components/VmDashboard.vue:
<script setup>
import { onMounted } from 'vue';
import { usePilvio } from '../composables/usePilvio';
const { vms, loading, error, fetchVMs, startVM, stopVM } = usePilvio();
onMounted(() => fetchVMs());
function statusColor(status) {
if (status === 'running') return '#22c55e';
if (status === 'stopped') return '#ef4444';
return '#f59e0b';
}
function formatMemory(mb) {
return mb >= 1024 ? `${(mb / 1024).toFixed(1)} GB` : `${mb} MB`;
}
</script>
<template>
<div class="dashboard">
<h1>Pilvio VM Dashboard</h1>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="loading" class="loading">Laadin...</div>
<button @click="fetchVMs" :disabled="loading" class="refresh-btn">
Värskenda
</button>
<table v-if="vms.length > 0" class="vm-table">
<thead>
<tr>
<th>Nimi</th>
<th>Staatus</th>
<th>OS</th>
<th>vCPU</th>
<th>RAM</th>
<th>IP</th>
<th>Tegevused</th>
</tr>
</thead>
<tbody>
<tr v-for="vm in vms" :key="vm.uuid">
<td>{{ vm.name }}</td>
<td>
<span class="status-dot" :style="{ backgroundColor: statusColor(vm.status) }"></span>
{{ vm.status }}
</td>
<td>{{ vm.os_name }} {{ vm.os_version }}</td>
<td>{{ vm.vcpu }}</td>
<td>{{ formatMemory(vm.memory) }}</td>
<td>{{ vm.private_ipv4 }}</td>
<td>
<button
v-if="vm.status === 'stopped'"
@click="startVM(vm.uuid)"
class="btn-start"
>
Käivita
</button>
<button
v-if="vm.status === 'running'"
@click="stopVM(vm.uuid)"
class="btn-stop"
>
Peata
</button>
</td>
</tr>
</tbody>
</table>
<p v-else-if="!loading">VM-e ei leitud.</p>
</div>
</template>
<style scoped>
.dashboard { max-width: 1000px; margin: 0 auto; padding: 20px; }
.error { color: #ef4444; padding: 10px; background: #fef2f2; border-radius: 4px; margin-bottom: 16px; }
.loading { color: #6b7280; margin-bottom: 16px; }
.refresh-btn { margin-bottom: 16px; padding: 8px 16px; cursor: pointer; }
.vm-table { width: 100%; border-collapse: collapse; }
.vm-table th, .vm-table td { padding: 10px; text-align: left; border-bottom: 1px solid #e5e7eb; }
.vm-table th { font-weight: 600; background: #f9fafb; }
.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
.btn-start { background: #22c55e; color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; }
.btn-stop { background: #ef4444; color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; }
</style>
Step 5: Running the application
# Terminal 1: Backend proxy
node server/proxy.js
# Terminal 2: Vue arendusserver
npm run dev
Step 6: Deploy to a Pilvio VM
# Ehita
npm run build
# Kopeeri serverisse
scp -r dist/* deploy@SINU_FLOATING_IP:/var/www/vue-dashboard/
# Proxy serveri käivitamine PM2-ga
scp server/proxy.js deploy@SINU_FLOATING_IP:~/proxy/
scp .env deploy@SINU_FLOATING_IP:~/proxy/
ssh deploy@SINU_FLOATING_IP "cd ~/proxy && npm install express http-proxy-middleware dotenv && pm2 start proxy.js --name pilvio-proxy"
Nginx configuration (/etc/nginx/sites-available/dashboard):
server {
listen 80;
server_name dashboard.sinu-domeen.ee;
# Vue SPA
root /var/www/vue-dashboard;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Proxy Pilvio API jaoks
location /pilvio/ {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Next steps: Add StorageVault file management alongside the dashboard or connect to a PostgreSQL database.