Vue.js + Pilvio autentimine

    Selles juhendis loome Vue.js rakenduse, mis suhtleb Pilvio API-ga läbi backend proxy ja kasutab Pilvio API tokeni autentimist infrastruktuuri haldamiseks.

    Mida ehitame

    • Vue.js 3 SPA koos Pilvio infrastruktuuri haldamise dashboardiga
    • Backend proxy, mis turvaliselt vahendab Pilvio API päringuid
    • VM-ide loetelu, käivitamine ja peatamine kasutajaliidesest

    Eeldused


    1. samm: Projekti loomine

    npm create vue@latest pilvio-vue-dashboard
    cd pilvio-vue-dashboard
    npm install
    npm install axios express dotenv
    

    2. samm: Backend proxy server

    Pilvio API token ei tohi kunagi jõuda frontendi koodi. Loome lihtsa Express proxy.

    Loo fail 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
    

    Loo fail .env:

    PILVIO_API_TOKEN=sinu-pilvio-api-token
    APP_SESSION_SECRET=genereeri-tugev-saladus
    FRONTEND_URL=http://localhost:5173
    PROXY_PORT=3001
    

    3. samm: Pilvio API composable

    Loo fail 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 };
    }
    

    4. samm: Dashboard komponent

    Loo fail 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>
    

    5. samm: Käivitamine

    # Terminal 1: Backend proxy
    node server/proxy.js
    
    # Terminal 2: Vue arendusserver
    npm run dev
    

    6. samm: Deploy Pilvio VM-ile

    # 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 seadistus (/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;
        }
    }
    

    Järgmised sammud: Lisa StorageVault failihaldus dashboardi kõrvale või ühenda PostgreSQL andmebaasiga.