Skip to content

基于 SpringBoot 和 libvirtAPI 的云平台管理系统

Notifications You must be signed in to change notification settings

pepsi-wyl/libvirt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

libvirt

KVM/QEMU虚拟化环境和开发环境搭建、

构建KVM环境

打开CPU虚拟化支持

image.png

查看系统版本

cat /etc/issue

image.png

查看内核版本

uname -r

查看CPU虚拟化支持

grep -E '(vmx|svm)' /proc/cpuinfo

image.png

查看KVM模块

lsmod|grep kvm
ls -l /dev/kvm

image.png

安装QEMU

下载和解压,选定合适目录使用命令

cd /usr/local
# wget下载
wget https://download.qemu.org/qemu-6.2.0.tar.xz
# 解压
tar -Jxvf qemu-6.2.0.tar.xz

image.png image.png

直接运行代码仓库中configure 文件进行配置

cd qemu-6.2.0
./configure

配置失败的尝试安装以下几个包

apt-get install zlib1g zlib1g-dev 
apt install libglib2.0-dev
apt-get install libpixman-1-dev
apt install ninja-build

image.png image.png

qemu的编译与安装

# 多线程编译
make -j 50
# 安装
make install | tee make-install.log

image.png image.png

查看Qemu

安装成功后,查看qemu提供的工具(按两次Tab键给出以qemu-开头的命令)

image.png

which qemu-img

image.png

安装虚拟化管理工具

libvirt的安装

# 适用于18
apt-get install libvirt-bin
apt-get install libvirt-dev

# 适用于20
apt-get install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
apt-get install libvirt-dev

image.png image.png

# 查看libvirtd的版本号
libvirtd --version

image.png

virt-manager的安装

# virt-manager的安装
apt-get install virt-manager

image.png

# 查看版本号
virt-manager --version

image.png

配置IDEA集成开发环境

安装IDEA

下载

# 下载链接
https://www.jetbrains.com/idea/download/other.html

image.png

移动到安装目录并解压

tar -zxvf ideaIU-2022.1.1.tar.gz

image.png

赋权限并且运行

# 赋权限
chmod 755 -R idea-IU-221.5591.52/

# 运行 
cd /home/wyl/idea/idea-IU-221.5591.52/bin
./idea.sh

image.png image.png

配置JDK

下载

使用IDEA自带的JDK下载工具,选择合适的版本 image.png

配置

选择刚刚下载好的JDK路径即可 image.png

配置Maven

选择IDEA自带的maven配置即可

image.png

配置Git

安装Git

sudo apt-get install git

image.png

配置git

git config --global user.name  "pepsi-wyl"
git config --global user.email "2322535585@qq.com"

image.png

IDAE配置Git

选择合适的路径点击test 成功则会显示Git的版本号 image.png

需求分析

功能要求

登陆界面和web端管理
宿主机详细信息
Libvirt连接信息
虚拟机信息、创建、启动、挂起、还原、保存、恢复、关闭、强制关闭、重启、删除
快照信息、创建、删除、使用
镜像信息、添加、删除、下载
存储池信息、创建、删除
网络断开和连接

程序功能模块

1.LibvirtUtils      获取连接和连接信息
2.SigarUtils        获取宿主机信息
3.LibvirtService    管理虚拟机的一系列操作
4.LibvirtController 处理前端页面请求调用LibvirtService完成对虚拟机的业务
5.UserController    处理前端页面请求完成登陆逻辑
6.HTML页面           展示数据和方便操作

系统设计

云平台管理系统是对虚拟机进行管理和操作,
该系统采用Libvirt Java API 进行分析和设计
,使用编程语言java作为开发语言,使用IDEA作为开发平台,
通过SpringBoot+Thymeleaf框架实现功能,能够很好的实现系统的开发及测试。

虚拟机基础管理

image.png image.png

虚拟机镜像管理

image.png

虚拟机存储池管理

image.png

虚拟机网络管理

image.png

系统实现

功能实现

libvirt连接及其连接信息

image.png image.png

主机信息

image.png

通过name或者id获取Domain

image.png

通过name或者id获取虚拟机信息

image.png

获取虚拟机列表

image.png

启动虚拟机

image.png

关闭虚拟机

image.png

强制关闭虚拟机

image.png

重启虚拟机

image.png

挂起虚拟机

image.png

还原虚拟机

image.png

保存虚拟机

image.png

恢复虚拟机

image.png

创建虚拟机

image.png image.png

删除虚拟机

image.png

获取镜像列表

image.png

添加镜像文件

image.png

下载镜像文件

image.png

删除镜像文件

image.png

获取网络状态

image.png

关闭网络

image.png

开启网络

image.png

获取快照列表

image.png

快照管理

image.png

创建快照

image.png

删除快照

image.png

启动快照

image.png

获取存储池列表

image.png

创建存储池

image.png

删除存储池

image.png

功能展示

登陆界面

账号密码都是admin 输入即可完成登陆 image.png

首页界面

image.png

注销登陆

点击注销登陆即可回到登陆界面 image.png

宿主机信息

点击宿主机详细信息即可查看信息 image.png

Libvirt连接信息

点击Libvirt连接信息即可查看信息 image.png

虚拟机管理界面

点击云平台管理即可进入管理界面 image.png 点击首页可以回到首页 image.png

注销登陆

点击注销登陆即可回到登陆界面 image.png

网络打开与关闭

点击网络状态即可实现网络的打开与关闭 image.png image.png image.png image.png

虚拟机添加

点击添加虚拟机按钮即可跳转到添加界面 image.png 输入创建虚拟机的名称和选择虚拟机的镜像即可创建个默认大小的虚拟机 image.png 虚拟机创建成功 image.png

虚拟机启动

点击虚拟机的启动按钮即可启动 image.png 虚拟机变为运行状态 image.png 虚拟机管理器查看也是运行状态 image.png 虚拟机启动成功 image.png

虚拟机挂起

点击虚拟机的挂起按钮即可挂起 image.png 虚拟机变为挂起状态 image.png 虚拟机管理器查看也是挂起状态 image.png

虚拟机还原

点击虚拟机的还原按钮即可还原 image.png 虚拟机变为运行状态 image.png 虚拟机管理器查看也是运行状态 image.png

虚拟机保存

点击虚拟机的保存按钮即可保存 image.png 选择合适的路径进行保存 image.png 保存成功虚拟机状态为关闭并且生成磁盘文件 image.png image.png

虚拟机恢复

点击虚拟机的恢复按钮即可恢复 image.png 选择磁盘文件 image.png 恢复成功虚拟机状态变为运行态 image.png

虚拟机关闭与强制关闭

点击虚拟机的关闭按钮或者强制关闭按钮即可关闭虚拟机 image.png 关闭成功变为关闭状态 image.png 虚拟机管理器查看也是关闭状态 image.png

虚拟机重启

点击虚拟机的重启按钮即可重启虚拟机 image.png 虚拟机正在重启 image.png 虚拟机启动成功并且状态仍然为运行状态 image.png image.png

虚拟机删除

点击删除按钮即可删除虚拟机 image.png 虚拟机删除后将不存在 image.png 虚拟机管理器查看也是是不存在 image.png

管理快照

点击快照管理即可进入管理界面 image.png 快照界面可以查看快照拍摄的时间状态等信息 也可以对快照进行一系列操作 image.png

拍摄快照

填入快照的名称点击拍摄按钮即可拍照 image.png 拍摄成功 image.png

快照使用

点击使用按钮即可恢复到此时的快照 image.png

快照删除

点击删除按钮即可删除该快照 image.png 删除成功快照将不存在 image.png

镜像管理

点击镜像管理即可进行镜像管理,该页面展示了镜像信息和定义了一系列操作 image.png

镜像下载

点击镜像下载按钮即可下载镜像 image.png image.png

镜像删除

点击镜像删除按钮即可删除镜像 image.png 删除之后将不在列表中 image.png

镜像添加

点击镜像添加跳转到添加界面 image.png 输入添加镜像的名称和选择镜像文件点击提交按钮即可 image.png 添加成功 image.png

添加存储池

点击添加存储池按钮跳转到添加界面 image.png 输入存储池名称和存储池的路径点击提交按钮即可 image.png 存储添加成功 image.png

删除存储池

点击存储池管理按钮跳转到存储池管理界面 image.png 点击删除按钮即可删除存储池 image.png 存储删除成功 image.png

附录:源程序清单

utils

LibvirtUtils

package com.example.libvirt.Utils;

import com.example.libvirt.pojo.LibvirtConnect;
import lombok.SneakyThrows;
import lombok.extern.java.Log;
import org.libvirt.Connect;

@Log
public class LibvirtUtils {
    
    private static Connect connect;
    
    // Connection
    @SneakyThrows
    public static Connect getConnection() {
        if (null == connect) {
            connect = new Connect("qemu:///system", false);
            log.info("Libvirt local connection successful" + "\n"
                     + "     连接URI: " + connect.getURI() + "\n"
                     + "     宿主机主机名: " + connect.getHostName() + "\n"
                     + "     宿主机剩余内存: " + connect.getFreeMemory() + "\n"
                     + "     宿主机最大cpu数量: " + connect.getMaxVcpus(null) + "\n"
                     + "     libvirt库版本号: " + connect.getLibVirVersion() + "\n"
                     + "     hypervisor名称: " + connect.getType()
                    );
        }
        return connect;
    }
    
    // ConnectionInfo
    @SneakyThrows
    public static LibvirtConnect getConnectionIo() {
        Connect connect = getConnection();
        return LibvirtConnect.builder()
            .url(connect.getURI())
            .hostName(connect.getHostName())
            .libVirVersion(connect.getLibVirVersion())
            .hypervisorVersion(connect.getType())
            .build();
    }    
}

SigarUtils

package com.example.libvirt.Utils;

import com.example.libvirt.pojo.Host;
import lombok.SneakyThrows;
import org.hyperic.sigar.*;

import java.util.HashMap;

public class SigarUtils {

    private static Sigar sigar;

    private static Sigar getSigarInstance() {
        if (null == sigar) sigar = new Sigar();
        return sigar;
    }

    // getHostInfo
    @SneakyThrows
    public static Host getHostInfo() {

        OperatingSystem OS = OperatingSystem.getInstance();
        Mem mem = getSigarInstance().getMem();
        Swap swap = sigar.getSwap();
        CpuInfo[] cpuInfoList = getSigarInstance().getCpuInfoList();

        HashMap<String, Object> fileInfoMap = new HashMap<>();
        for (FileSystem fs : getSigarInstance().getFileSystemList()) {
            //盘符类型:ext4   硬盘类型:本地硬盘 2
            if (fs.getType() == 2 && fs.getSysTypeName().equals("ext4")) {
                FileSystemUsage usage = getSigarInstance().getFileSystemUsage(fs.getDirName());
                fileInfoMap.put("devName", fs.getDevName());              // 盘符名称
                fileInfoMap.put("dirName", fs.getDirName());              // 盘符路径
                fileInfoMap.put("fileTotal", usage.getTotal());           // 总大小       KB
                fileInfoMap.put("fileUsed", usage.getUsed());             // 已经使用量   KB
                fileInfoMap.put("fileUsePercent", (usage.getUsePercent() * 100D)); // 资源的利用率 %
            }
        }

        HashMap<String, String> netInfoMap = new HashMap<>();
        for (String name : getSigarInstance().getNetInterfaceList()) {
            if (name.equals("ens33")) {
                NetInterfaceConfig cfg = sigar.getNetInterfaceConfig(name);
                if (NetFlags.LOOPBACK_ADDRESS.equals(cfg.getAddress()) || (cfg.getFlags() & NetFlags.IFF_LOOPBACK) != 0 || NetFlags.NULL_HWADDR.equals(cfg.getHwaddr()))
                    continue;
                netInfoMap.put("description", cfg.getDescription());    // 网卡描述
                netInfoMap.put("netType", cfg.getType());               // 网卡类型
                netInfoMap.put("IPAddress", cfg.getAddress());          // IP地址
                netInfoMap.put("MACAddress", cfg.getHwaddr());          // MAC地址
                netInfoMap.put("netmask", cfg.getNetmask());            // 子网掩码
            }
        }

        return Host.builder()

                // os
                .vendor(OS.getVendor())                 // Ubuntu
                .vendorVersion(OS.getVendorVersion())   // 20.04
                .vendorCodeName(OS.getVendorCodeName()) // focal
                .version(OS.getVersion())               // 5.13.0-44-generic

                // memory 单位:k
                .memory(mem.getTotal() / 1024L)
                .memoryUsed(mem.getUsed() / 1024L)
                .memoryFree(mem.getFree() / 1024L)
                .swap(swap.getTotal() / 1024L)
                .swapUsed(swap.getUsed() / 1024L)
                .swapFree(swap.getFree() / 1024L)

                // cpu
                .cpuNum(cpuInfoList.length)
                .cpuModel(cpuInfoList[1].getModel())
                .cpuMhz(cpuInfoList[1].getMhz())

                // file
                .devName((String) fileInfoMap.get("devName"))                    // 盘符名称
                .dirName((String) fileInfoMap.get("dirName"))                    // 盘符路径
                .fileTotal((Long) fileInfoMap.get("fileTotal"))                  // 总大小       KB
                .fileUsed((Long) fileInfoMap.get("fileUsed"))                    // 已经使用量   KB
                .fileUsePercent((Double) fileInfoMap.get("fileUsePercent"))      // 资源的利用率 %

                // net
                .netDescription(netInfoMap.get("description"))                   // 网卡描述
                .netType(netInfoMap.get("netType"))                              // 网卡类型
                .netIP(netInfoMap.get("IPAddress"))                              // IP地址
                .netMAC(netInfoMap.get("MACAddress"))                            // MAC地址
                .netMask(netInfoMap.get("netmask"))                              // 子网掩码

                .build();
    }
}

pojo

Host

package com.example.libvirt.pojo;

import lombok.*;

@Builder
@Getter
@ToString
public class Host {
    // os
    private String vendor;           // Ubuntu
    private String vendorVersion;    // 20.04
    private String vendorCodeName;   // focal
    private String version;          // 5.13.0-44-generic

    // memory 单位:k
    private long memory;
    private long memoryUsed;
    private long memoryFree;
    private long swap;
    private long swapUsed;
    private long swapFree;

    // cpu
    private int cpuNum;
    private String cpuModel;
    private int cpuMhz;

    // file
    private String devName;
    private String dirName;
    private long fileTotal;
    private long fileUsed;
    private double fileUsePercent;

    // net
    private String netDescription;
    private String netType;
    private String netIP;
    private String netMAC;
    private String netMask;
}

Virtual

package com.example.libvirt.pojo;

import lombok.*;

@Builder
@Getter
@ToString
public class Virtual {
    private int id;
    private String name;
    private String state;
}

ImgFile

package com.example.libvirt.pojo;

import lombok.*;

@Builder
@Getter
@ToString
public class ImgFile {
    private String name;
    private String size;
}

Snapshot

package com.example.libvirt.pojo;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

@Builder
@Getter
@ToString
public class Snapshot {
    private String name;
    private String creationTime;
    private String state;
}

Storagepool

package com.example.libvirt.pojo;

import lombok.*;

@Builder
@Getter
@ToString
public class Storagepool {
    private String name;    // 名称
    private String type;    // 类型

    private int capacity;   // GB 容量
    private int available;  // GB 可用容量
    private int allocation; // GB 已用容量
    private String usage;   // 使用率(%)

    private String state;   // 状态
    private String xml;     // 描述xml
 }

LibvirtConnect

package com.example.libvirt.pojo;

import lombok.*;

@Builder
@Getter
@ToString
public class LibvirtConnect {
    private String url;
    private String hostName;
    private long libVirVersion;
    private String hypervisorVersion;
}

service

LibvirtService

package com.example.libvirt.service;

import com.example.libvirt.Utils.*;
import com.example.libvirt.pojo.*;
import org.apache.commons.io.FileUtils;

import lombok.SneakyThrows;
import lombok.extern.java.Log;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.libvirt.*;

import javax.servlet.http.HttpServletResponse;
import javax.swing.*;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;

@Log
@Service(value = "libvirtService")
public class LibvirtService {

    /**
     * getHostInformation
     */
    public Host getHostInfo() {
        return SigarUtils.getHostInfo();
    }

    /**
     * getLibvirtConnectInformation
     */
    public LibvirtConnect getLibvirtConnectInformation() {
        return LibvirtUtils.getConnectionIo();
    }

    /**
     * getDomainById
     */
    @SneakyThrows
    public Domain getDomainById(int id) {
        return LibvirtUtils.getConnection().domainLookupByID(id);
    }

    /**
     * getDomainByName
     */
    @SneakyThrows
    public Domain getDomainByName(String name) {
        return LibvirtUtils.getConnection().domainLookupByName(name);
    }

    /**
     * getVirtualById
     */
    @SneakyThrows
    public Virtual getVirtualById(int id) {
        Domain domain = getDomainById(id);
        return Virtual.builder()
                .id(domain.getID())
                .name(domain.getName())
                .state(domain.getInfo().state.toString())
                .build();
    }

    /**
     * getVirtualByName
     */
    @SneakyThrows
    public Virtual getVirtualByName(String name) {
        Domain domain = getDomainByName(name);
        return Virtual.builder()
                .id(domain.getID())
                .name(domain.getName())
                .state(domain.getInfo().state.toString())
                .build();
    }

    /**
     * 虚拟机列表
     */
    @SneakyThrows
    public List<Virtual> getVirtualList() {
        ArrayList<Virtual> virtualList = new ArrayList<>();
        // live
        int[] ids = LibvirtUtils.getConnection().listDomains();
        for (int id : ids) virtualList.add(getVirtualById(id));
        // down
        String[] names = LibvirtUtils.getConnection().listDefinedDomains();
        for (String name : names) virtualList.add(getVirtualByName(name));
        return virtualList;
    }

    /**
     * 暂停/挂起 虚拟机
     */
    @SneakyThrows
    private void suspendedDomain(Domain domain) {
        if (domain.isActive() == 1) {
            domain.suspend();
            log.info(domain.getName() + "虚拟机已挂起!");
        } else log.info("虚拟机未打开");
    }

    public void suspendedDomainById(int id) {
        suspendedDomain(getDomainById(id));
    }

    public void suspendedDomainName(String name) {
        suspendedDomain(getDomainByName(name));
    }


    /**
     * 还原 暂停/挂起 虚拟机
     */
    @SneakyThrows
    private void resumeDomain(Domain domain) {
        if (domain.isActive() == 1) {
            domain.resume();
            log.info(domain.getName() + "虚拟机已唤醒!");
        } else log.info("虚拟机未打开");
    }

    public void resumeDomainById(int id) {
        resumeDomain(getDomainById(id));
    }

    public void resumeDomainByName(String name) {
        resumeDomain(getDomainByName(name));
    }

    /**
     * 保存 虚拟机 --->img文件
     */
    @SneakyThrows
    private void saveDomain(Domain domain) {
        JFileChooser jf = new JFileChooser();
        jf.setFileSelectionMode(JFileChooser.SAVE_DIALOG | JFileChooser.DIRECTORIES_ONLY);
        jf.showDialog(null, null);
        String f = jf.getSelectedFile().getAbsolutePath() + "/save.img";
        if (domain.isActive() == 1) {
            domain.save(f);
            log.info(domain.getName() + "虚拟机状态已保存!" + "save: " + f);
        } else log.info("虚拟机未打开");
    }

    public void saveDomainById(int id) {
        saveDomain(getDomainById(id));
    }

    public void saveDomainByName(String name) {
        saveDomain(getDomainByName(name));
    }

    /**
     * 恢复 虚拟机 --->img文件
     */
    @SneakyThrows
    private void restoreDomain(Domain domain) {
        JFileChooser chooser = new JFileChooser();
        chooser.showOpenDialog(null);
        if (chooser.getSelectedFile() != null) {
            String path = chooser.getSelectedFile().getPath();
            if (domain.isActive() == 0) {
                domain.getConnect().restore(path);
                log.info(domain.getName() + "虚拟机状态已恢复!!" + "path: " + path);
            } else log.info("虚拟机未关闭");
        }
    }

    public void restoreDomainById(int id) {
        restoreDomain(getDomainById(id));
    }

    public void restoreDomainByName(String name) {
        restoreDomain(getDomainByName(name));
    }

    /**
     * 启动 虚拟机
     */
    @SneakyThrows
    private void initiateDomain(Domain domain) {
        if (domain.isActive() == 0) {
            domain.create();
            log.info(domain.getName() + "虚拟机已启动!");
        } else log.info("虚拟机已经打开过!");
    }

    public void initiateDomainByName(String name) {
        initiateDomain(getDomainByName(name));
    }

    /**
     * 关闭 虚拟机
     */
    @SneakyThrows
    private void shutdownDomain(Domain domain) {
        if (domain.isActive() == 1) {
            domain.shutdown();
            log.info(domain.getName() + "虚拟机已正常关机!");
        } else log.info("虚拟机未打开");
    }

    public void shutdownDomainById(int id) {
        shutdownDomain(getDomainById(id));
    }

    public void shutdownDomainByName(String name) {
        shutdownDomain(getDomainByName(name));
    }

    /**
     * 强制关闭 虚拟机
     */
    @SneakyThrows
    private void shutdownMustDomain(Domain domain) {
        if (domain.isActive() == 1) {
            domain.destroy();
            log.info(domain.getName() + "虚拟机已强制关机!");
        } else log.info("虚拟机未打开");
    }

    public void shutdownMustDomainById(int id) {
        shutdownMustDomain(getDomainById(id));
    }

    public void shutdownMustDomainByName(String name) {
        shutdownMustDomain(getDomainByName(name));
    }

    /**
     * 重启 虚拟机
     */
    @SneakyThrows
    private void rebootDomain(Domain domain) {
        if (domain.isActive() == 1) {
            domain.reboot(0);
            log.info(domain.getName() + "虚拟机状态已重启!");
        } else log.info("虚拟机未打开");
    }

    public void rebootDomainById(int id) {
        rebootDomain(getDomainById(id));
    }

    public void rebootDomainByName(String name) {
        rebootDomain(getDomainByName(name));
    }

    /**
     * 添加 虚拟机 xml------>name   1024MB
     */
    @SneakyThrows
    public void addDomainByName(String name) {
        String xml = "<domain type='kvm'>\n" +
                "  <name>" + name + "</name>\n" +
                "  <uuid>" + UUID.randomUUID() + "</uuid>\n" +
                "  <metadata>\n" +
                "    <libosinfo:libosinfo xmlns:libosinfo=\"http://libosinfo.org/xmlns/libvirt/domain/1.0\">\n" +
                "      <libosinfo:os id=\"http://ubuntu.com/ubuntu/14.04\"/>\n" +
                "    </libosinfo:libosinfo>\n" +
                "  </metadata>\n" +
                "  <memory unit='KiB'>1048576</memory>\n" +                 // 1024 MB
                "  <currentMemory unit='KiB'>1048576</currentMemory>\n" +   // 1024 MB
                "  <vcpu placement='static'>2</vcpu>\n" +
                "  <os>\n" +
                "    <type arch='x86_64' machine='pc-i440fx-focal'>hvm</type>\n" +
                "    <boot dev='hd'/>\n" +
                "  </os>\n" +
                "  <features>\n" +
                "    <acpi/>\n" +
                "    <apic/>\n" +
                "    <vmport state='off'/>\n" +
                "  </features>\n" +
                "  <cpu mode='host-model' check='partial'/>\n" +
                "  <clock offset='utc'>\n" +
                "    <timer name='rtc' tickpolicy='catchup'/>\n" +
                "    <timer name='pit' tickpolicy='delay'/>\n" +
                "    <timer name='hpet' present='no'/>\n" +
                "  </clock>\n" +
                "  <on_poweroff>destroy</on_poweroff>\n" +
                "  <on_reboot>restart</on_reboot>\n" +
                "  <on_crash>destroy</on_crash>\n" +
                "  <pm>\n" +
                "    <suspend-to-mem enabled='no'/>\n" +
                "    <suspend-to-disk enabled='no'/>\n" +
                "  </pm>\n" +
                "  <devices>\n" +
                "    <emulator>" + "/usr/bin/qemu-system-x86_64" + "</emulator>\n" +
                "    <disk type='file' device='disk'>\n" +
                "      <driver name='qemu' type='qcow2'/>\n" +
                "      <source file='/home/wyl/KVMImg/" + name + ".img'/>\n" +   // FileSource
                "      <target dev='vda' bus='virtio'/>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>\n" +
                "    </disk>\n" +
                "    <controller type='usb' index='0' model='ich9-ehci1'>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x7'/>\n" +
                "    </controller>\n" +
                "    <controller type='usb' index='0' model='ich9-uhci1'>\n" +
                "      <master startport='0'/>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0' multifunction='on'/>\n" +
                "    </controller>\n" +
                "    <controller type='usb' index='0' model='ich9-uhci2'>\n" +
                "      <master startport='2'/>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x1'/>\n" +
                "    </controller>\n" +
                "    <controller type='usb' index='0' model='ich9-uhci3'>\n" +
                "      <master startport='4'/>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x2'/>\n" +
                "    </controller>\n" +
                "    <controller type='pci' index='0' model='pci-root'/>\n" +
                "    <controller type='virtio-serial' index='0'>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>\n" +
                "    </controller>\n" +
                "    <interface type='network'>\n" +
                "      <mac address='52:54:00:27:6d:ef'/>\n" +
                "      <source network='default'/>\n" +
                "      <model type='virtio'/>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>\n" +
                "    </interface>\n" +
                "    <serial type='pty'>\n" +
                "      <target type='isa-serial' port='0'>\n" +
                "        <model name='isa-serial'/>\n" +
                "      </target>\n" +
                "    </serial>\n" +
                "    <console type='pty'>\n" +
                "      <target type='serial' port='0'/>\n" +
                "    </console>\n" +
                "    <channel type='spicevmc'>\n" +
                "      <target type='virtio' name='com.redhat.spice.0'/>\n" +
                "      <address type='virtio-serial' controller='0' bus='0' port='1'/>\n" +
                "    </channel>\n" +
                "    <input type='tablet' bus='usb'>\n" +
                "      <address type='usb' bus='0' port='1'/>\n" +
                "    </input>\n" +
                "    <input type='mouse' bus='ps2'/>\n" +
                "    <input type='keyboard' bus='ps2'/>\n" +
                "    <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0' keymap='en-us'>\n" +
                "      <listen type='address' address='0.0.0.0'/>\n" +
                "    </graphics>\n" +
                "    <sound model='ich6'>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>\n" +
                "    </sound>\n" +
                "    <video>\n" +
                "      <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>\n" +
                "    </video>\n" +
                "    <redirdev bus='usb' type='spicevmc'>\n" +
                "      <address type='usb' bus='0' port='2'/>\n" +
                "    </redirdev>\n" +
                "    <redirdev bus='usb' type='spicevmc'>\n" +
                "      <address type='usb' bus='0' port='3'/>\n" +
                "    </redirdev>\n" +
                "    <memballoon model='virtio'>\n" +
                "      <address type='pci' domain='0x0000' bus='0x00' slot='0x08' function='0x0'/>\n" +
                "    </memballoon>\n" +
                "  </devices>\n" +
                "</domain>";
        LibvirtUtils.getConnection().domainDefineXML(xml);    // define ------> creat
        log.info(name + "虚拟机已创建!");
    }

    /**
     * 删除 虚拟机 xml
     */
    @SneakyThrows
    private void deleteDomain(Domain domain) {
        if (domain.isActive() == 1) domain.destroy();  // 强制关机
        domain.undefine();
        log.info(domain.getName() + "虚拟机已删除!");
    }

    public void deleteDomainById(int id) {
        deleteDomain(getDomainById(id));
    }

    public void deleteDomainByName(String name) {
        deleteDomain(getDomainByName(name));
    }

    /**
     * get ImgList
     */
    public List<ImgFile> getImgList() {
        List<ImgFile> list = new ArrayList<>();
        File[] files = new File("/home/wyl/KVMImg/").listFiles();
        if (files != null) {
            for (File file : files) {
                list.add(ImgFile.builder()
                        .name(file.getName())
                        .size(FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(file)))
                        .build());
            }
        }
        return list;
    }

    /**
     * 添加 img
     */
    @SneakyThrows
    public Boolean addImgFile(String name, MultipartFile file) {
        if (!file.isEmpty()) {
            file.transferTo(new File("/home/wyl/KVMImg/" + name + ".img"));
            log.info("文件" + name + ".img已经保存!");
            return true;
        }
        log.info("文件" + name + ".img保存失败!");
        return false;
    }

    /**
     * 下载 img
     */
    @SneakyThrows
    public String downImgFile(String name, HttpServletResponse response) {
        File file = new File("/home/wyl/KVMImg/" + name);
        if (!file.exists()) return "下载文件不存在";
        response.reset();
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding("utf-8");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(file.getName(), "UTF-8"));  // 设置编码格式
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        OutputStream os = response.getOutputStream();
        int i = 0;
        byte[] buff = new byte[1024];
        while ((i = bis.read(buff)) != -1) {
            os.write(buff, 0, i);
            os.flush();
        }
        bis.close();
        os.close();
        return "下载成功";
    }

    /**
     * 删除 img
     */
    public Boolean deleteImgFile(String name) {
        if (new File("/home/wyl/KVMImg/" + name).delete()) {
            log.info("文件" + name + "已经删除!");
            return true;
        }
        log.info("文件" + name + "文件不存在");
        return false;
    }

    /**
     * 关闭网络
     */
    @SneakyThrows
    public void closeNetWork() {
        Domain domain = getDomainByName(getVirtualList().get(0).getName());
        Network network = domain.getConnect().networkLookupByName("default");
        if (network.isActive() == 1) {
            network.destroy();
            log.info("网络" + network.getName() + "已经被关闭!");
        } else log.info("网络" + network.getName() + "已经处于关闭状态!");
    }

    /**
     * 启动网络
     */
    @SneakyThrows
    public void openNetWork() {
        Domain domain = getDomainByName(getVirtualList().get(0).getName());
        Network network = domain.getConnect().networkLookupByName("default");
        if (network.isActive() == 0) {
            network.create();
            log.info("网络" + network.getName() + "已经被打开!");
        } else log.info("网络" + network.getName() + "已经处于打开状态!");
    }

    /**
     * 网络 State
     */
    @SneakyThrows
    public String getNetState() {
        Domain domain = getDomainByName(getVirtualList().get(0).getName());
        if (domain.getConnect().networkLookupByName("default").isActive() == 1) return "on";
        else return "off";
    }

    /**
     * getSnapshotList
     */
    @SneakyThrows
    public List<Snapshot> getSnapshotListByName(String name) {
        // virsh snapshot-list      虚拟机名
        String cmd = "virsh snapshot" + "-list " + name;
        Process process = Runtime.getRuntime().exec(cmd);
        LineNumberReader line = new LineNumberReader(new InputStreamReader(process.getInputStream()));
        ArrayList<Snapshot> snapshots = new ArrayList<>();
        String str;
        int linCount = 0;
        int snapshotNum = getDomainByName(name).snapshotNum(); // 2
        while ((str = line.readLine()) != null && snapshotNum > 0) {
            linCount++;
            if (linCount <= 2) continue;  // -2 line
            else {
                snapshotNum--;
                String[] lineStr = str.split("   ");
                snapshots.add(Snapshot.builder().name(lineStr[0]).creationTime(lineStr[1]).state(lineStr[2]).build());
            }
        }
        return snapshots;
    }

    /**
     * snapshot管理
     */
    @SneakyThrows
    private void snapshotManger(String op, String name, String snapshotName) {
        // virsh snapshot-create-as 虚拟机名称 快照名称
        // virsh snapshot-delete    虚拟机名称 快照名称
        // virsh snapshot-revert    虚拟机名称 快照名称
        String cmd = "";
        switch (op) {
            case "creat":
                cmd = "virsh snapshot" + "-create-as " + name + " " + snapshotName;
                break;
            case "delete":
                cmd = "virsh snapshot" + "-delete " + name + " " + snapshotName;
                break;
            case "revert":
                cmd = "virsh snapshot" + "-revert " + name + " " + snapshotName;
                break;
        }
        Runtime.getRuntime().exec(cmd);
    }

    /**
     * 创建快照
     */
    public void createSnapshot(String name, String snapshotName) {
        snapshotManger("creat", name, snapshotName);
        log.info("虚拟机" + name + "成功创建快照" + snapshotName);
    }

    /**
     * 删除快照
     */
    public void deleteSnapshot(String name, String snapshotName) {
        snapshotManger("delete", name, snapshotName);
        log.info("虚拟机" + name + "成功删除快照" + snapshotName);
    }

    /**
     * 启动快照
     */
    public void revertSnapshot(String name, String snapshotName) {
        snapshotManger("revert", name, snapshotName);
        log.info("虚拟机" + name + "成功切换快照" + snapshotName);
    }

    /**
     * getStoragePoolList
     */
    @SneakyThrows
    public List<Storagepool> getStoragePoolList() {
        String[] pools = LibvirtUtils.getConnection().listStoragePools();
        String[] definedPools = LibvirtUtils.getConnection().listDefinedStoragePools();
        log.info("pools" + Arrays.toString(pools) + "definedPools" + Arrays.toString(definedPools));
        List<Storagepool> storagePoolList = new ArrayList<>();
        for (String pool : pools) storagePoolList.add(getStoragePool(pool));
        for (String pool : definedPools) storagePoolList.add(getStoragePool(pool));
        return storagePoolList;
    }

    /**
     * getStoragePool ByName
     */
    @SneakyThrows
    public Storagepool getStoragePool(String name) {
        StoragePool storagePool = LibvirtUtils.getConnection().storagePoolLookupByName(name);
        StoragePoolInfo info = storagePool.getInfo();
        return Storagepool.builder()
                .name(name)     // 名称
                .type("qcow2")  // 类型
                .capacity((int) (info.capacity / 1024.00 / 1024.00 / 1024.00))     // GB 容量
                .available((int) (info.available / 1024.00 / 1024.00 / 1024.00))   // GB 可用容量
                .allocation((int) (info.allocation / 1024.00 / 1024.00 / 1024.00)) // GB 已用容量
                .usage(((int) ((info.allocation / 1024.00 / 1024.00 / 1024.00) / (info.capacity / 1024.00 / 1024.00 / 1024.00) * 100)) + "%") // 使用率(%)
                .state(info.state.toString())                // 状态
                .xml(storagePool.getXMLDesc(0))        // 描述xml
                .build();
    }

    /**
     * 删除StoragePool ByName
     */
    @SneakyThrows
    public void deleteStoragePool(String name) {
        StoragePool storagePool = LibvirtUtils.getConnection().storagePoolLookupByName(name);
        for (String pool : LibvirtUtils.getConnection().listStoragePools())
            if (pool.equals(name)) storagePool.destroy();
        for (String pool : LibvirtUtils.getConnection().listDefinedStoragePools())
            if (pool.equals(name)) storagePool.undefine();
    }

    /**
     * 创建Storagepool  >>>>>> url必须存在
     */
    @SneakyThrows
    public boolean createStoragepool(String name, String url) {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "\n" +
                "<pool type=\"dir\">\n" +
                "    <name>" + name + "</name> \n" +       // 名称必须唯一
                "    <source>\n" +
                "    </source>\n" +
                "    <target>\n" +
                "        <path>" + url + "</path> \n" +                     // StoragePool 在宿主机的路径
                "        <permissions> \n" +                                // 权限
                "            <mode>0711</mode>\n" +
                "            <owner>0</owner>\n" +
                "            <group>0</group>\n" +
                "        </permissions>\n" +
                "    </target>\n" +
                "</pool>";
        return LibvirtUtils.getConnection().storagePoolCreateXML(xml, 0) == null ? false : true;
    }
}

controller

LibvirtController

package com.example.libvirt.controller;

import com.example.libvirt.pojo.*;
import com.example.libvirt.service.LibvirtService;
import lombok.SneakyThrows;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

@Controller
public class LibvirtController {

    @Resource(name = "libvirtService")
    private LibvirtService libvirtService;

    @RequestMapping(value = {"/index"})
    public String index(Model model) {
        Host hostInfo = libvirtService.getHostInfo();
        model.addAttribute("hostinfo", hostInfo);
        LibvirtConnect connectInformation = libvirtService.getLibvirtConnectInformation();
        model.addAttribute("connectInformation", connectInformation);
        return "index";
    }

    @RequestMapping("/main")
    public String main(Model model) {
        List<Virtual> virtualList = libvirtService.getVirtualList();
        model.addAttribute("virtualList", virtualList);
        String netState = libvirtService.getNetState();
        model.addAttribute("netState", netState);
        return "main";
    }

    @RequestMapping("/openOrCloseNetWork")
    public String openOrCloseNetWork(@RequestParam("netState") String netState) {
        if ("on".equals(netState)) libvirtService.closeNetWork();
        if ("off".equals(netState)) libvirtService.openNetWork();
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/initiate")
    public String initiateVirtual(@RequestParam("name") String name) {
        libvirtService.initiateDomainByName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/suspended")
    public String suspendedVirtual(@RequestParam("name") String name) {
        libvirtService.suspendedDomainName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/resume")
    public String resumeVirtual(@RequestParam("name") String name) {
        libvirtService.resumeDomainByName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/save")
    public String saveVirtual(@RequestParam("name") String name) {
        libvirtService.saveDomainByName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/restore")
    public String restoreVirtual(@RequestParam("name") String name) {
        libvirtService.restoreDomainByName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/shutdown")
    public String shutdownVirtual(@RequestParam("name") String name) {
        libvirtService.shutdownDomainByName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/shutdownMust")
    public String shutdownMustVirtual(@RequestParam("name") String name) {
        libvirtService.shutdownMustDomainByName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @SneakyThrows
    @RequestMapping("/reboot")
    public String rebootVirtual(@RequestParam("name") String name) {
        libvirtService.rebootDomainByName(name);
        Thread.sleep(1000);
        return "redirect:main";
    }

    @RequestMapping("/delete")
    public String deleteVirtual(@RequestParam("name") String name) {
        libvirtService.deleteDomainByName(name);
        libvirtService.deleteImgFile(name + ".img");
        return "redirect:main";
    }

    @RequestMapping("/toAddVirtual")
    public String toAddVirtual(Model model) {
        String netState = libvirtService.getNetState();
        model.addAttribute("netState", netState);
        return "addVirtual";
    }

    @RequestMapping(value = "/addVirtual", method = RequestMethod.POST)
    public String addVirtual(@RequestParam("virtualName") String name,
                             @RequestPart("file") MultipartFile file) {
        libvirtService.addDomainByName(name);
        libvirtService.addImgFile(name, file);
        return "redirect:main";
    }

    @RequestMapping("/img")
    public String imgList(Model model) {
        List<ImgFile> imgList = libvirtService.getImgList();
        model.addAttribute("imgList", imgList);
        String netState = libvirtService.getNetState();
        model.addAttribute("netState", netState);
        return "img";
    }

    @RequestMapping("/toAddImg")
    public String toAddImg(Model model) {
        String netState = libvirtService.getNetState();
        model.addAttribute("netState", netState);
        return "addImg";
    }

    @RequestMapping("/addImg")
    public String addImg(@RequestParam("imgName") String name,
                         @RequestPart("file") MultipartFile file) {
        libvirtService.addImgFile(name, file);
        return "redirect:img";
    }

    @RequestMapping("/deleteImg")
    public String deleteImg(@RequestParam("name") String name) {
        libvirtService.deleteImgFile(name);
        return "redirect:img";
    }

    @ResponseBody
    @RequestMapping("/downImg")
    public String downImg(@RequestParam("name") String name, HttpServletResponse response) {
        return libvirtService.downImgFile(name, response);
    }

    @RequestMapping("/getSnapshotList")
    public String getSnapshotList(@RequestParam("name") String name,
                                  Model model) {
        List<Snapshot> snapshotList = libvirtService.getSnapshotListByName(name);
        model.addAttribute("snapshotList", snapshotList);
        model.addAttribute("virtualName", name);
        return "snapshot";
    }

    @SneakyThrows
    @RequestMapping("/deleteSnapshot")
    public String deleteSnapshot(@RequestParam("virtualName") String virtualName,
                                 @RequestParam("snapshotName") String snapshotName) {
        libvirtService.deleteSnapshot(virtualName, snapshotName);
        Thread.sleep(1000);
        return "redirect:/getSnapshotList?name=" + virtualName;
    }

    @SneakyThrows
    @RequestMapping("/revertSnapshot")
    public String revertSnapshot(@RequestParam("virtualName") String virtualName,
                                 @RequestParam("snapshotName") String snapshotName) {
        libvirtService.revertSnapshot(virtualName, snapshotName);
        Thread.sleep(1000);
        return "redirect:/getSnapshotList?name=" + virtualName;
    }

    @SneakyThrows
    @RequestMapping("/createSnapshot")
    public String createSnapshot(@RequestParam("virtualName") String virtualName,
                                 @RequestParam("snapshotName") String snapshotName) {
        libvirtService.createSnapshot(virtualName, snapshotName);
        Thread.sleep(1000);
        return "redirect:/getSnapshotList?name=" + virtualName;
    }

    @RequestMapping("/storagePoolList")
    public String storagePoolList(Model model) {
        List<Storagepool> storagePoolList = libvirtService.getStoragePoolList();
        model.addAttribute("storagePoolList", storagePoolList);
        String netState = libvirtService.getNetState();
        model.addAttribute("netState", netState);
        return "storagepool";
    }

    @SneakyThrows
    @RequestMapping("/deleteStoragePool")
    public String deleteStoragePool(@RequestParam("name") String name) {
        libvirtService.deleteStoragePool(name);
        Thread.sleep(1000);
        return "redirect:/storagePoolList";
    }

    @RequestMapping("/toCreateStoragepool")
    public String toCreateStoragepool(Model model) {
        String netState = libvirtService.getNetState();
        model.addAttribute("netState", netState);
        return "addStoragepool";
    }

    @SneakyThrows
    @RequestMapping("/createStoragepool")
    public String createStoragepool(@RequestParam("storagepoolName") String name,
                                    @RequestParam("storagepoolPath") String url) {
        libvirtService.createStoragepool(name, url);
        Thread.sleep(1000);
        return "redirect:/storagePoolList";
    }

}

UserController

package com.example.libvirt.controller;

import com.example.libvirt.pojo.Host;
import com.example.libvirt.pojo.LibvirtConnect;
import com.example.libvirt.service.LibvirtService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;

@Controller
public class UserController {

    /**
     * 跳转登录页面
     */
    @RequestMapping(value = {"/"})
    public String toLogin() {
        return "login";
    }

    /**
     * 登录
     */
    @RequestMapping(value = {"/login"})
    public String login(@RequestParam(value = "username", required = true) String userName,
                        @RequestParam(value = "password", required = true) String password,
                        Model model,
                        HttpSession session) {
        if ("admin".equals(userName) && "admin".equals(password)) {
            session.setAttribute("loginUser", userName);//UserName存入Session
            return "redirect:index";
        }
        model.addAttribute("msg", "userName or password error!");
        return "login";
    }

    /**
     * 注销
     */
    @RequestMapping(value = "/loginOut", method = RequestMethod.GET)
    public String loginOut(HttpSession session) {
        if (session.getAttribute("loginUser") != null) {
            session.removeAttribute("loginUser");          //移除Session 转到登陆界面
        }
        return "redirect:/";
    }
}

interceptor

LoginInterceptor

package com.example.libvirt.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {


    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle拦截的请求路径是{}", request.getRequestURI());
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser != null) return true;     // 放行
        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg", "请先登录");
        //response.sendRedirect("/");
        request.getRequestDispatcher("/").forward(request, response);
        return false;
    }

    /**
     * 目标方法执行完成以后
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}", modelAndView);
    }

    /**
     * 页面渲染以后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}", ex);
    }

}

config

MyMvcConfig

package com.example.libvirt.config;

import com.example.libvirt.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

import java.util.List;

@Configuration(value = "myMvcConfig")
public class MyMvcConfig implements WebMvcConfigurer {

    /**
     * 配置拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).
                addPathPatterns("/**").
                excludePathPatterns("/", "/login").
                excludePathPatterns("/css/*", "/js/*", "/img/*");
    }

    @Override
    public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {

    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {

    }

    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {

    }

    @Override
    public void addFormatters(FormatterRegistry formatterRegistry) {

    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {

    }

    @Override
    public void addCorsMappings(CorsRegistry corsRegistry) {

    }

    @Override
    public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {

    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {

    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {

    }

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> list) {

    }

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {

    }

    @Override
    public Validator getValidator() {
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        return null;
    }

}

properties

application.properties

spring.http.multipart.max-file-size=10240MB
spring.http.multipart.max-request-size=10240MB

templates

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <meta name="description" content=""/>
    <meta name="author" content=""/>
    <title>云平台管理系统AdminLogin</title>
    <!-- Bootstrap core CSS -->
    <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"/>
    <!-- Custom styles for this template -->
    <link th:href="@{/css/signin.css}" rel="stylesheet"/>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/login}" method="post">
    <img class="mb-4" th:src="@{/img/log.jpg}" alt="" width="72" height="72"/>
    <h1 class="h3 mb-3 font-weight-normal">Admin Log In</h1>
    <input type="text" class="form-control" name="username" required="" autofocus=""/>
    <input type="password" class="form-control" name="password" required=""/>
    <button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
    <p class="mt-5 mb-3 " style="color: red" th:text="${msg}"></p>
    <p class="mt-5 mb-3 text-muted"2021 design by wyl</p>
</form>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <meta name="description" content=""/>
    <meta name="author" content=""/>
    <title>云平台管理系统</title>
    <!-- Font Awesome icons (free version)-->
    <script src="https://use.fontawesome.com/releases/v6.1.0/js/all.js" crossorigin="anonymous"></script>
    <!-- Google fonts-->
    <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css"/>
    <link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet"
          type="text/css"/>
    <!-- Core theme CSS (includes Bootstrap)-->
    <link th:href="@{css/styleindex.css}" rel="stylesheet"/>
    <!--    <link th:href="@{/css/signin.css}" rel="stylesheet"/>-->
    <!-- Bootstrap core JS-->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Core theme JS-->
    <script th:href="@{js/scripts.js}"></script>
    <script src="https://cdn.startbootstrap.com/sb-forms-latest.js"></script>
</head>
<body id="page-top">

<!-- Navigation-->
<nav class="navbar navbar-expand-lg bg-secondary text-uppercase fixed-top" id="mainNav">
    <div class="container">
        <a class="navbar-brand" href="#page-top">云平台管理系统</a>
        <div class="collapse navbar-collapse" id="navbarResponsive">
            <ul class="navbar-nav ms-auto">
                <li class="nav-item mx-0 mx-lg-1">
                    <a class="nav-link py-3 px-0 px-lg-3 rounded" th:href="@{/main}">云平台管理</a>
                </li>
                <li class="nav-item mx-0 mx-lg-1">
                    <a class="nav-link py-3 px-0 px-lg-3 rounded" th:href="@{/loginOut}">注销登录</a>
                </li>
            </ul>
        </div>
    </div>
</nav>
<!--Masthead-->
<header class="masthead bg-primary text-white text-center">
    <div class="container d-flex align-items-center flex-column">
        <!-- Masthead Heading-->
        <h1 class="masthead-heading text-uppercase mb-0">云平台管理系统</h1>
        <!-- Icon Divider-->
        <div class="divider-custom divider-light">
            <div class="divider-custom-line"></div>
            <div class="divider-custom-line"></div>
            <div class="divider-custom-line"></div>
            <div class="divider-custom-line"></div>
            <div class="divider-custom-line"></div>
        </div>
        <!-- Masthead Subheading-->
        <p class="masthead-subheading font-weight-light mb-0">SpringBoot + Thymeleaf + HTML+BootStrap + Libvirt +
            Sigar</p>
    </div>
</header>
<!-- Portfolio Section-->
<section class="page-section portfolio" id="portfolio">
    <div class="container">
        <div class="row justify-content-center">
            <!-- Portfolio Item 1-->
            <div class="col-md-6 col-lg-4 mb-5">
                <div class="portfolio-item mx-auto" data-bs-toggle="modal" data-bs-target="#portfolioModal1">
                    <div class="portfolio-item-caption d-flex align-items-center justify-content-center h-100 w-100">
                        <div class="portfolio-item-caption-content text-center text-white"><i
                                class="fas fa-plus fa-3x"></i></div>
                    </div>
                    <!--                    <p class="mb-5">宿主机详细信息</p>-->
                    <img class="img-fluid" th:src="@{img/index1.png}" alt="..."/>
                </div>
            </div>
            <!-- Portfolio Item 2-->
            <div class="col-md-6 col-lg-4 mb-5">
                <div class="portfolio-item mx-auto" data-bs-toggle="modal" data-bs-target="#portfolioModal2">
                    <div class="portfolio-item-caption d-flex align-items-center justify-content-center h-100 w-100">
                        <div class="portfolio-item-caption-content text-center text-white"><i
                                class="fas fa-plus fa-3x"></i></div>
                    </div>
                    <!--                    <p class="mb-5">Libvirt连接信息</p>-->
                    <img class="img-fluid" th:src="@{img/index.png}" alt="..."/>
                </div>
            </div>
        </div>
    </div>
</section>
<!-- Copyright Section-->
<div class="copyright py-4 text-center text-white">
    <div class="container"><small2022 design by wyl</small></div>
</div>
<!-- Portfolio Modals-->
<!-- Portfolio Modal 1-->
<div class="portfolio-modal modal fade" id="portfolioModal1" tabindex="-1" aria-labelledby="portfolioModal1"
     aria-hidden="true">
    <div class="modal-dialog modal-xl">
        <div class="modal-content">
            <div class="modal-header border-0">
                <button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body text-center pb-5">
                <div class="container">
                    <div class="row justify-content-center">
                        <div class="col-lg-8">
                            <h5>版本信息</h5>
                            <p class="mb-3" th:inline="text">型号:[[${hostinfo.vendor}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;数字版本:[[${hostinfo.vendorVersion}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;文字版本:[[${hostinfo.vendorCodeName}]]&nbsp;&nbsp;&nbsp;内核版本:[[${hostinfo.version}]]</p>
                            <h5>内存信息</h5>
                            <p class="mb-3" th:inline="text">内存:[[${hostinfo.memory}]]k&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用内存:[[${hostinfo.memoryUsed}]]k&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;可用内存:[[${hostinfo.memoryFree}]]k</p>
                            <p class="mb-3" th:inline="text">交换空间:[[${hostinfo.swap}]]k&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用交换空间:[[${hostinfo.swapUsed}]]k&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;可用交换空间:[[${hostinfo.swapFree}]]k</p>
                            <h5>CPU信息</h5>
                            <p class="mb-4" th:inline="text">宿主机CPU数量:[[${hostinfo.cpuNum}]]个&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPU型号:[[${hostinfo.cpuModel}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CPU频率:[[${hostinfo.cpuMhz}]]Mhz</p>
                            <h5>磁盘信息</h5>
                            <p class="mb-4" th:inline="text">设备名称:[[${hostinfo.devName}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;设备目录:[[${hostinfo.dirName}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;磁盘总量:[[${hostinfo.fileTotal}]]KB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                                <br/>使用:[[${hostinfo.fileUsed}]]KB&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用率:[[${hostinfo.fileUsePercent}]]%
                            </p>
                            <h5>网络信息</h5>
                            <p class="mb-4" th:inline="text">网卡描述:[[${hostinfo.netDescription}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;网卡类型:[[${hostinfo.netType}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IP地址:[[${hostinfo.netIP}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                                <br/>
                                MAC地址:[[${hostinfo.netMAC}]]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Mask掩码:[[${hostinfo.netMask}]]
                            </p>
                            <button class="btn btn-primary" data-bs-dismiss="modal">
                                <i class="fas fa-xmark fa-fw"></i>
                                关闭窗口
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- Portfolio Modal 2-->
<div class="portfolio-modal modal fade" id="portfolioModal2" tabindex="-1" aria-labelledby="portfolioModal2"
     aria-hidden="true">
    <div class="modal-dialog modal-xl">
        <div class="modal-content">
            <div class="modal-header border-0">
                <button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body text-center pb-5">
                <div class="container">
                    <div class="row justify-content-center">
                        <div class="col-lg-8">
                            <h5>Libvirt连接信息</h5>
                            <p class="mb-4" th:inline="text">主机名称:[[${connectInformation.hostName}]]</p>
                            <p class="mb-4" th:inline="text">连接URL:[[${connectInformation.url}]]</p>
                            <p class="mb-4" th:inline="text">libVir版本:[[${connectInformation.libVirVersion}]]</p>
                            <p class="mb-4" th:inline="text">
                                hypervisor版本:[[${connectInformation.hypervisorVersion}]]</p>
                            <button class="btn btn-primary" data-bs-dismiss="modal">
                                <i class="fas fa-xmark fa-fw">
                                </i>
                                关闭窗口
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

main.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8"/>
    <title>虚拟机管理</title>
    <!--    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>-->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"/>
</head>
<body>


<nav class="navbar navbar-default">
    <div class="container-fluid">

        <div class="navbar-header">
            <a class="navbar-brand" th:href="@{/index}">首页</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
                <li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
                <li><a th:href="@{/img}">镜像管理</a></li>
                <li><a th:href="@{/toAddImg}">添加镜像</a></li>
                <li><a th:href="@{/storagePoolList}">存储池管理</a></li>
                <li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
                <li><a th:href="@{/loginOut}">注销登录</a></li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container">
    <div class="row clearfix">
        <div class="col-md-12 column">
            <table class="table table-hover table-striped">
                <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Status</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr th:each="virtual:${virtualList}">
                    <td th:text="${virtual.id}"></td>
                    <td th:text="${virtual.name}"></td>
                    <td th:text="${virtual.state}"></td>
                    <td>
                        <div>
                            <a th:href="@{/initiate(name=${virtual.name})}">启动</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/suspended(name=${virtual.name})}">挂起</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/resume(name=${virtual.name})}">还原</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/save(name=${virtual.name})}">保存</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/restore(name=${virtual.name})}">恢复</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/shutdown(name=${virtual.name})}">关闭</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/shutdownMust(name=${virtual.name})}">强制关闭</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/reboot(name=${virtual.name})}">重启</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/delete(name=${virtual.name})}">删除</a>
                            &nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;
                            <a th:href="@{/getSnapshotList(name=${virtual.name})}">快照管理</a>
                        </div>
                    </td>
                </tr>
                </tbody>

            </table>
        </div>
    </div>
</div>
</body>
</html>

snapshot.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8"/>
    <title>快照管理</title>
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">

            <ul class="nav navbar-nav">
                <li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
            </ul>
            <form class="navbar-form navbar-right" th:action="@{/createSnapshot}" method="get">
                <div class="form-group">
                    <input type="text" class="from-control" id="virtualName" name="virtualName" th:value="${virtualName}"/>
                </div>
                <div class="form-group">
                    <input type="text" class="from-control" id="snapshotName" name="snapshotName" placeholder="请输入快照名称" required=""/>
                </div>
                <button type="submit" class="btn btn-default">拍摄</button>
            </form>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="container">
    <div class="row clearfix">
        <div class="col-md-12 column">
            <table class="table table-hover table-striped">
                <thead>
                <tr>
                    <th>名称</th>
                    <th>时间</th>
                    <th>状态</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr th:each="snapshot:${snapshotList}">
                    <td th:text="${snapshot.name}"></td>
                    <td th:text="${snapshot.creationTime}"></td>
                    <td th:text="${snapshot.state}"></td>
                    <td>
                        <div>
                            <a th:href="@{/deleteSnapshot(virtualName=${virtualName},snapshotName=${snapshot.name})}">删除</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/revertSnapshot(virtualName=${virtualName},snapshotName=${snapshot.name})}">使用</a>
                        </div>
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

img.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8"/>
    <title>虚拟机管理</title>
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<nav class="navbar navbar-default">
    <div class="container-fluid">
        <div class="navbar-header">
            <a class="navbar-brand" th:href="@{/index}">首页</a>
        </div>
        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
                <li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
                <li class="active"><a th:href="@{/img}">镜像管理</a></li>
                <li><a th:href="@{/toAddImg}">添加镜像</a></li>
                <li><a th:href="@{/storagePoolList}">存储池管理</a></li>
                <li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
                <li><a th:href="@{/loginOut}">注销登录</a></li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container">
    <div class="row clearfix">
        <div class="col-md-12 column">
            <table class="table table-hover table-striped">
                <thead>
                <tr>
                    <th>名称</th>
                    <th>大小</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr th:each="img:${imgList}">
                    <td th:text="${img.name}"></td>
                    <td th:text="${img.size}"></td>
                    <td>
                        <div>
                            <a th:href="@{/deleteImg(name=${img.name})}">删除</a>
                            &nbsp;|&nbsp;
                            <a th:href="@{/downImg(name=${img.name})}">下载</a>
                        </div>
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

storagepool.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:v-model="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8"/>
    <title>存储池管理</title>
    <!--    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>-->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous"/>
</head>
<body>


<nav class="navbar navbar-default">
    <div class="container-fluid">

        <div class="navbar-header">
            <a class="navbar-brand" th:href="@{/index}">首页</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
                <li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
                <li><a th:href="@{/img}">镜像管理</a></li>
                <li><a th:href="@{/toAddImg}">添加镜像</a></li>
                <li class="active"><a th:href="@{/storagePoolList}">存储池管理</a></li>
                <li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a>
                </li>
                <li><a th:href="@{/loginOut}">注销登录</a></li>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container">
    <div class="row clearfix">
        <div class="col-md-12 column">
            <table class="table table-hover table-striped">
                <thead>
                <tr>
                    <th>name</th>
                    <th>type</th>
                    <th>capacity</th>
                    <th>available</th>
                    <th>allocation</th>
                    <th>usage</th>
                    <th>state</th>
                </tr>
                </thead>
                <tbody>
                <tr th:each="storagePool:${storagePoolList}">
                    <td th:text="${storagePool.name}"></td>
                    <td th:text="${storagePool.type}"></td>

                    <td th:inline="text">[[${storagePool.capacity}]] GB</td>
                    <td th:inline="text">[[${storagePool.available}]] GB</td>
                    <td th:inline="text">[[${storagePool.allocation}]] GB</td>

                    <td th:text="${storagePool.usage}"></td>
                    <td th:text="${storagePool.state}"></td>
                    <td>
                        <div>
                            <a th:href="@{/deleteStoragePool(name=${storagePool.name})}">删除</a>
                        </div>
                    </td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

addVirtual.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>添加虚拟机</title>
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>


<nav class="navbar navbar-default">
    <div class="container-fluid">

        <div class="navbar-header">
            <a class="navbar-brand" th:href="@{/index}">首页</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
                <li class="active"><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
                <li><a th:href="@{/img}">镜像管理</a></li>
                <li><a th:href="@{/toAddImg}">添加镜像</a></li>
                <li><a th:href="@{/storagePoolList}">存储池管理</a></li>
                <li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
                <li><a th:href="@{/loginOut}">注销登录</a></li>
            </ul>
        </div><!-- /.navbar-collapse -->

    </div><!-- /.container-fluid -->
</nav>

<div align="center" class="center">
    <form class="form-inline" role="form" th:action="@{/addVirtual}" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label class="sr-only" for="virtualName">虚拟机名称</label>
            <input type="text" class="from-control" id="virtualName" name="virtualName" placeholder="请输入虚拟机名称"
                   required=""/>
        </div>
        <div class="form-group">
            <label class="sr-only" for="file">选择镜像文件</label>
            <input type="file" id="file" name="file" required=""/><br/>
        </div>
        <button type="submit" class="btn btn-default">提交</button>
    </form>
</div>

</body>
</html>

addImg.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>添加镜像</title>
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>

<nav class="navbar navbar-default">
    <div class="container-fluid">

        <div class="navbar-header">
            <a class="navbar-brand" th:href="@{/index}">首页</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
                <li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
                <li><a th:href="@{/img}">镜像管理</a></li>
                <li class="active"><a th:href="@{/toAddImg}">添加镜像</a></li>
                <li><a th:href="@{/storagePoolList}">存储池管理</a></li>
                <li><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a></li>
                <li><a th:href="@{/loginOut}">注销登录</a></li>
            </ul>
        </div><!-- /.navbar-collapse -->

    </div><!-- /.container-fluid -->
</nav>

<div align="center" class="center">
    <form class="form-inline" role="form" th:action="@{/addImg}" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label class="sr-only" for="imgName">镜像名称</label>
            <input type="text" class="from-control" id="imgName" name="imgName" placeholder="请输入镜像名称"
                   required=""/>
        </div>
        <div class="form-group">
            <label class="sr-only" for="file">选择镜像文件</label>
            <input type="file" id="file" name="file" required=""/><br/>
        </div>
        <button type="submit" class="btn btn-default">提交</button>
    </form>
</div>

</body>
</html>

addStoragepool.html

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>添加虚拟机</title>
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>


<nav class="navbar navbar-default">
    <div class="container-fluid">

        <div class="navbar-header">
            <a class="navbar-brand" th:href="@{/index}">首页</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a th:href="@{/main}">虚拟机基础管理<span class="sr-only">(current)</span></a></li>
                <li><a th:href="@{/toAddVirtual}">添加虚拟机</a></li>
                <li><a th:href="@{/img}">镜像管理</a></li>
                <li><a th:href="@{/toAddImg}">添加镜像</a></li>
                <li><a th:href="@{/storagePoolList}">存储池管理</a></li>
                <li class="active"><a th:href="@{/toCreateStoragepool}">添加存储池</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a th:href="@{/openOrCloseNetWork(netState=${netState})}" th:inline="text">网络状态:[[${netState}]]</a>
                </li>
                <li><a th:href="@{/loginOut}">注销登录</a></li>
            </ul>
        </div><!-- /.navbar-collapse -->

    </div><!-- /.container-fluid -->
</nav>

<div align="center" class="center">
    <form class="form-inline" role="form" th:action="@{/createStoragepool}" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label class="sr-only" for="storagepoolName">存储池名称</label>
            <input type="text" class="from-control" id="storagepoolName" name="storagepoolName" placeholder="请输入存储池名称"
                   required=""/>
        </div>
        <div class="form-group">
            <label class="sr-only" for="storagepoolPath">存储池路径</label>
            <input type="text" class="from-control" id="storagepoolPath" name="storagepoolPath" placeholder="请输入存储池路径"
                   required=""/>
        </div>
        <button type="submit" class="btn btn-default">提交</button>
    </form>
</div>

</body>
</html>

About

基于 SpringBoot 和 libvirtAPI 的云平台管理系统

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published