From 0b583dfabe9e2838631aaa61c64ae1b023dd0f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sun, 22 Sep 2024 23:09:52 +0800 Subject: [PATCH 01/14] a commit when I finished the program on Windows. --- .gitignore | 2 +- docs/win-user-manual.md | 293 +++++--- eulerlauncher/eulerlauncherGUI.py | 1043 +++++++++++++++++++++++++++++ requirements-win.txt | 3 +- requirements.txt | 3 +- 5 files changed, 1230 insertions(+), 114 deletions(-) create mode 100644 eulerlauncher/eulerlauncherGUI.py diff --git a/.gitignore b/.gitignore index 7c9d0f2..7bf9f33 100644 --- a/.gitignore +++ b/.gitignore @@ -158,4 +158,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ diff --git a/docs/win-user-manual.md b/docs/win-user-manual.md index 7fafc27..65eb65a 100644 --- a/docs/win-user-manual.md +++ b/docs/win-user-manual.md @@ -12,6 +12,7 @@ - eulerlauncherd.exe:EulerLauncher的主进程,是运行在后台的守护进程,负责与各类虚拟化后端交互,管理虚拟机、容器以及镜像的生命周期,eulerlauncherd.exe是运行在后台的守护进程。 - eulerlauncher.exe:EulerLauncher的CLI客户端,用户通过该客户端与eulerlauncherd守护进程交互,对虚拟机、镜像等进行相关操作。 +- eulerlauncherGUI.exe:EulerLauncher的GUI客户端,用户也可以通过该图形化界面客户端与eulerlauncherd守护进程交互,对虚拟机、镜像等进行相关操作。 - eulerlauncher.conf:EulerLauncher配置文件,需与eulerlauncherd.exe放置于同一目录下,参考下面配置进行相应配置: ```Conf @@ -31,7 +32,7 @@ memory = 8G 配置完成后请右键点击eulerlauncherd.exe,选择以管理员身份运行,点击后eulerlauncherd.exe将以守护进程的形式在后台运行。 -打开 `PowerShell` 或 `Terminal` ,准备进行对应的操作。 +打开 `PowerShell` 或 `Terminal` (对应后文的***CLI客户端***),或者打开`eulerlauncherGUI.exe`(对应后文的***GUI客户端***),准备进行对应的操作。 ### Windows下退出EulerLauncherd后台进程 @@ -43,161 +44,231 @@ memory = 8G ### 镜像操作 1. 获取可用镜像列表: -```PowerShell -eulerlauncher images - -+-----------+----------+--------------+ -| Images | Location | Status | -+-----------+----------+--------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 2203-load | Local | Ready | -+-----------+----------+--------------+ -``` +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher images +> +-----------+----------+--------------+ +> | Images | Location | Status | +> +-----------+----------+--------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 2203-load | Local | Ready | +> +-----------+----------+--------------+ +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边下方点击`刷新`,即可获取可用镜像列表,内容包括镜像名称、位置、状态等信息。 + **EulerLauncher**镜像有两种位置属性:1)远端镜像 2)本地镜像,只有处于本地且状态为 `Ready` 的镜像可以直接用来创建虚拟机,位于远端的镜像需要下载后才能够使用;你也可以加载已经预先下载好的本地镜像到**EulerLauncher**中,具体操作方法可以参考接下来的操作指导。 2. 下载远端镜像 -```PowerShell -eulerlauncher download-image 22.03-LTS - -Downloading: 22.03-LTS, this might take a while, please check image status with "images" command. -``` - -镜像下载请求是一个异步请求,具体的下载动作将在后台完成,具体耗时与你的网络情况相关,整体的镜像下载流程包括下载、解压缩、格式转换等相关子流程,在下载过程中可以通过 `image` 命令随时查看下载进展与镜像状态,进度条格式为`[downloaded_bytes]/[total_bytes] (percentage)`: - -```PowerShell -eulerlauncher images - -+-----------+----------+------------------------------------+ -| Images | Location | Status | -+-----------+----------+------------------------------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 22.03-LTS | Local | Downloading: 97.76/ 386.74MB (25%) | -+-----------+----------+------------------------------------+ -``` - +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher download-image 22.03-LTS +> +> Downloading: 22.03-LTS, this might take a while, please check image status with "images" command. +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边列表中选中要下载的镜像,然后在右边下方点击`下载`,即可开始下载镜像,下载过程中点击`刷新`按钮可以查看当前状态。 + +镜像下载请求是一个异步请求,具体的下载动作将在后台完成,具体耗时与你的网络情况相关,整体的镜像下载流程包括下载、解压缩、格式转换等相关子流程,在下载过程中可以通过 `image` 命令(CLI客户端)或者点击`刷新`(GUI客户端)随时查看下载进展与镜像状态,进度条格式为`[downloaded_bytes]/[total_bytes] (percentage)`: +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher images +> +> +-----------+----------+------------------------------------+ +> | Images | Location | Status | +> +-----------+----------+------------------------------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 22.03-LTS | Local | Downloading: 97.76/ 386.74MB (25%) | +> +-----------+----------+------------------------------------+ +> ``` + +> ***GUI客户端*** +> +> 点击`刷新`按钮查看当前状态,列表中也会显示上面的三列。 当镜像状态转变为 `Ready` 时,表示镜像下载完成,处于 `Ready` 状态的镜像可被用来创建虚拟机: -```PowerShell -eulerlauncher images +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher images +> +> +-----------+----------+--------------+ +> | Images | Location | Status | +> +-----------+----------+--------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 22.03-LTS | Local | Ready | +> +-----------+----------+--------------+ +> ``` + +> ***GUI客户端*** +> +> 点击`刷新`按钮查看当前状态,列表中也会显示上面的三列,最后一个值为`Ready`时说明下载完成。 -+-----------+----------+--------------+ -| Images | Location | Status | -+-----------+----------+--------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 22.03-LTS | Local | Ready | -+-----------+----------+--------------+ -``` 3. 加载本地镜像 用户也可以加载自定义镜像或预先下载到本地的镜像到EulerLauncher中用于创建自定义虚拟机: -```PowerShell -eulerlauncher load-image --path {image_file_path} IMAGE_NAME -``` +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher load-image --path {image_file_path} IMAGE_NAME +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边下方点击`加载本地镜像`,会弹出文件选择窗口,按照弹出窗口的引导选择本地镜像文件,输入镜像名称,即可完成加载。 当前支持加载的镜像格式有 `xxx.{qcow2, raw, vmdk, vhd, vhdx, qcow, vdi}.[xz]` 例如: -```PowerShell -eulerlauncher load-image --path D:\openEuler-22.03-LTS-x86_64.qcow2.xz 2203-load - -Loading: 2203-load, this might take a while, please check image status with "images" command. -``` - -将位于 `D:\` 目录下的 `openEuler-22.03-LTS-x86_64.qcow2.xz` 加载到OmniVirt系统中,并命名为 `2203-load`,与下载命令一样,加载命令也是一个异步命令,用户需要用镜像列表命令查询镜像状态直到显示为 `Ready`, 但相对于直接下载镜像,加载镜像的速度会快很多: - -```PowerShell -eulerlauncher images - -+-----------+----------+----------------------------+ -| Images | Location | Status | -+-----------+----------+----------------------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 2203-load | Local | Loading: (24.00/100%) | -+-----------+----------+----------------------------+ - -eulerlauncher images - -+-----------+----------+--------------+ -| Images | Location | Status | -+-----------+----------+--------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 2203-load | Local | Ready | -+-----------+----------+--------------+ -``` +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher load-image --path D:\openEuler-22.03-LTS-x86_64.qcow2.xz 2203-load +> +> Loading: 2203-load, this might take a while, please check image status with "images" command. +> ``` +> +> 将位于 `D:\` 目录下的 `openEuler-22.03-LTS-x86_64.qcow2.xz` 加载到OmniVirt系统中,并命名为 `2203-load`,与下载命令一样,加载命令也是一个异步命令,用户需要用镜像列表命令查询镜像状态直到显示为 `Ready`, 但相对于直接下载镜像,加载镜像的速度会快很多: +> +> ```PowerShell +> eulerlauncher images +> +> +-----------+----------+----------------------------+ +> | Images | Location | Status | +> +-----------+----------+----------------------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 2203-load | Local | Loading: (24.00/100%) | +> +-----------+----------+----------------------------+ +> +> eulerlauncher images +> +> +-----------+----------+--------------+ +> | Images | Location | Status | +> +-----------+----------+--------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 2203-load | Local | Ready | +> +-----------+----------+--------------+ +> ``` + +> ***GUI客户端*** +> +> 同样以上面的例子为例,点击`加载本地镜像`,在文件选择窗口内选择`D:\openEuler-22.03-LTS-x86_64.qcow2.xz`,镜像名称输入`2203-load`,点击`确定`,即可完成加载。获取加载情况请点击`刷新`按钮查看。 4. 删除镜像: -通过下面的命令将镜像从EulerLauncher系统中删除: - -```PowerShell -eulerlauncher delete-image 2203-load +> ***CLI客户端*** +> +> 通过下面的命令将镜像从EulerLauncher系统中删除: +> +> ```PowerShell +> eulerlauncher delete-image 2203-load +> +> Image: 2203-load has been successfully deleted. +> ``` -Image: 2203-load has been successfully deleted. -``` +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边列表中选中要删除的镜像,然后在右边下方点击`删除镜像`,即可删除镜像。 ### 虚拟机操作 1. 获取虚拟机列表: -```Powershell -eulerlauncher list - -+----------+-----------+---------+---------------+ -| Name | Image | State | IP | -+----------+-----------+---------+---------------+ -| test1 | 2203-load | Running | 172.22.57.220 | -+----------+-----------+---------+---------------+ -| test2 | 2203-load | Running | N/A | -+----------+-----------+---------+---------------+ -``` +> ***CLI客户端*** +> ```Powershell +> eulerlauncher list +> +> +----------+-----------+---------+---------------+ +> | Name | Image | State | IP | +> +----------+-----------+---------+---------------+ +> | test1 | 2203-load | Running | 172.22.57.220 | +> +----------+-----------+---------+---------------+ +> | test2 | 2203-load | Running | N/A | +> +----------+-----------+---------+---------------+ +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边上方点击`刷新`,即可获取虚拟机列表,内容包括虚拟机名称、镜像、状态、IP等信息。 若虚拟机IP地址显示为 `N/A` ,若这台虚拟机的状态为 `Running` 则表示这台虚拟机为新创建的虚拟机,网络还未配置完成,网络配置过程大概需要若干秒,请稍后重新尝试获取相关虚拟机信息。 2. 登录虚拟机: -若虚拟机已成功分配到IP地址,可以直接使用 `SSH` 命令进行登录: +> ***CLI客户端*** +> +>若虚拟机已成功分配到IP地址,可以直接使用 `SSH` 命令进行登录: +> +> +> ```PowerShell +> ssh root@{instance_ip} +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要登录的虚拟机,然后在右边下方点击`连接`,界面会自动跳转到`虚拟机连接`。`虚拟机连接`界面上方左侧会显示虚拟机的各种信息,上方右侧有三个按钮,提供两种SSH连接方式。 +> - 选择`调用终端进行SSH连接`,则会调用系统终端进行SSH连接,会自动弹出终端窗口,在里面操作即可,关闭终端窗口即可断开连接(最好先输入exit指令)。 +> - 选择`在GUI中进行SSH连接`,如果使用导入的镜像则需要在弹出窗口中输入密码,连接成功则会在界面下方的文本框内显示信息,在输入栏中输入命令后按输入条右侧的`Send`或者按键盘上的`Enter`键即可发送命令。要断开连接,可以点击上方右侧的`断开连接`按钮,或者在输入栏中输入`exit`命令,还可以在左边栏直接切换到其他页面(不推荐),这三种方式均可断开连接。 -```PowerShell -ssh root@{instance_ip} -``` 若使用的是openEuler社区提供的官方镜像,则默认用户为 `root` 默认密码为 `openEuler12#$` 3. 创建虚拟机 -```PowerShell -eulerlauncher launch --image {image_name} {instance_name} -``` +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher launch --image {image_name} {instance_name} +> ``` +> +> 通过 `--image` 指定镜像,同时指定虚拟机名称,EulerLauncher会根据所指定的镜像默认创建一个规格为`2U4G`的openEuler虚拟机。 -通过 `--image` 指定镜像,同时指定虚拟机名称,EulerLauncher会根据所指定的镜像默认创建一个规格为`2U4G`的openEuler虚拟机。 +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边下方点击`创建虚拟机`,弹出窗口中选择镜像,输入虚拟机名称,点击`确定`,即可创建虚拟机,创建过程中请耐心等待`请等待`弹窗消失。 4. 删除虚拟机 -```PowerShell -eulerlauncher delete-instance {instance_name} -``` -根据虚拟机名称删除指定的虚拟机。 +> ***CLI客户端*** +> ```PowerShell +> eulerlauncher delete-instance {instance_name} +> ``` +> 根据虚拟机名称删除指定的虚拟机。 + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要删除的虚拟机,然后在右边下方点击`删除虚拟机`,即可删除虚拟机。 5. 为虚拟机打快照,并导出为镜像 -```Shell -eulerlauncher take-snapshot --snapshot_name snap --export_path path vm_name -``` -通过`--snapshot_name`指定快照名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名。 +> ***CLI客户端*** +> ```Shell +> eulerlauncher take-snapshot --snapshot_name snap --export_path path vm_name +> ``` +> 通过`--snapshot_name`指定快照名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名。 + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要打快照的虚拟机,然后在右边下方点击`生成快照`,弹出窗口中选择文件存放位置,输入快照名称,点击`确定`,即可完成快照,其会存放在选择的路径下。 6. 将虚拟机导出为主流编程框架开发镜像 -```Shell -eulerlauncher export-development-image --image_name image --export_path path vm_name -``` -通过`--image_name`指定导出镜像名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名,默认导出为Python/Go/Java主流编程框架开发镜像。 +> ***CLI客户端*** +> ```Shell +> eulerlauncher export-development-image --image_name image --export_path path vm_name +> ``` +> 通过`--image_name`指定导出镜像名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名,默认导出为Python/Go/Java主流编程框架开发镜像。 + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要导出的虚拟机,然后在右边下方点击`导出为开发镜像`,弹出窗口中选择文件存放位置,输入镜像名称,点击`确定`,即可完成导出,其会存放在选择的路径下。 [1]: https://gitee.com/openeuler/omnivirt/releases [2]: https://learn.microsoft.com/zh-cn/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v \ No newline at end of file diff --git a/eulerlauncher/eulerlauncherGUI.py b/eulerlauncher/eulerlauncherGUI.py new file mode 100644 index 0000000..fd33d7b --- /dev/null +++ b/eulerlauncher/eulerlauncherGUI.py @@ -0,0 +1,1043 @@ +""" +Author: XiangChenyu +Description: EulerLauncher前端界面 +Tips: +- take-snapshot与export-development-image功能由于主程序尚未实现,该前端界面在这两部分留空,实现后请补齐 + 具体需要修改的位置为button_vmsnapshot_clicked和button_vmout_clicked两个函数 + Line 619 未实现的提示,主程序实现后请删除此行代码 + Line 636 实现后请在实际情况下修改该判断条件,就是按照正确执行后命令行的返回值特点来进行判断 + Line 671 未实现的提示,主程序实现后请删除此行代码 + Line 689 实现后请在实际情况下修改该判断条件,就是按照正确执行后命令行的返回值特点来进行判断 +- 增删改功能请在主界面init_ui或四个界面生成函数(以widget_of开头)中增删改按钮等控件,然后增删改对应的槽函数即可。 +- 本代码中有许多通用方法,如do_cmd、table_process、create_massage_box、create_line等,可以复用 + 它们在Line 373、414、954、963 +- 请注意,本代码仅为前端界面,后端功能由EulerLauncherd提供,因此请确保整个EulerLauncher已经正确安装并配置好conf文件 +""" + +import sys +from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QStackedWidget, QFrame, \ + QPushButton, QLabel, QListWidget, QFileDialog, QInputDialog, QMessageBox, QTextEdit, \ + QLineEdit, QCheckBox, QDialog +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QIcon +from paramiko import SSHClient, AutoAddPolicy +import subprocess +import time +import re + + +class EulerLauncherGUI(QMainWindow): + def __init__(self): + super().__init__() + + # 创建一个堆叠布局 + self.stack = QStackedWidget() + + # 创建边栏 + self.sidebar = QFrame() + self.sidebar.setObjectName("sidebar") + self.sidebar_layout = QVBoxLayout(self.sidebar) + + # 创建内容区域 + self.content = QFrame() + self.content.setObjectName("content") + self.content_layout = QVBoxLayout(self.content) + + # 设置刷新提示与刷新时间记录文本 + self.refresh_time = QLabel('请点击刷新按钮获取镜像列表') + self.refresh_vm_time = QLabel('请点击刷新按钮获取虚拟机列表') + + # 边栏选中的按钮 + self.current_button = None + + # 虚拟机连接界面的信息 + self.vm_tip = QLabel('请点击虚拟机管理界面选择虚拟机进行连接', self) + self.vm_tip.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.vm_name = QLabel('', self) + self.vm_name.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.vm_image = QLabel('', self) + self.vm_image.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.vm_state = QLabel('', self) + self.vm_state.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + self.vm_ip = QLabel('', self) + self.vm_ip.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + # ssh连接 + self.ssh = None + self.shell = None + + # 创建主窗口 + self.init_ui() + + def init_ui(self): + # 创建主窗口 + main_widget = QWidget() + + # 创建布局 + main_layout = QHBoxLayout(self) + + # 将堆叠布局添加到内容区域 + self.content_layout.addWidget(self.stack) + + # 将各个页面添加到堆叠布局 + self.stack.addWidget(self.widget_of_iso()) + self.stack.addWidget(self.widget_of_management()) + self.stack.addWidget(self.widget_of_connecting()) + self.stack.addWidget(self.widget_of_setting()) + + # 切换显示用的按钮, 它们的name属性为menu + self.button1 = QPushButton('镜像管理', self) + self.button1.setProperty('name', 'menu') + self.button1.clicked.connect(self.switch_page) + self.sidebar_layout.addWidget(self.button1) + + self.button2 = QPushButton('虚拟机管理', self) + self.button2.setProperty('name', 'menu') + self.button2.clicked.connect(self.switch_page) + self.sidebar_layout.addWidget(self.button2) + + self.button3 = QPushButton('虚拟机连接', self) + self.button3.setProperty('name', 'menu') + self.button3.clicked.connect(self.switch_page) + self.sidebar_layout.addWidget(self.button3) + + self.button4 = QPushButton('设置', self) + self.button4.setProperty('name', 'menu') + self.button4.clicked.connect(self.switch_page) + + # 设置按钮的位置 + self.sidebar_layout.addStretch(1) + self.sidebar_layout.addWidget(self.create_line(True)) + self.sidebar_layout.addWidget(self.button4) + + # 设置按钮的可选择状态 + self.button1.setCheckable(True) + self.button2.setCheckable(True) + self.button3.setCheckable(True) + self.button4.setCheckable(True) + + # 设置按钮的默认状态 + self.current_button = self.button1 + self.button1.setChecked(True) + + # 将按钮布局和堆叠布局添加到主布局中 + main_layout.addWidget(self.sidebar) + main_layout.addWidget(self.stack) + + # 设置主窗口的布局 + main_widget.setLayout(main_layout) + + # 设置主窗口的中央部件 + self.setCentralWidget(main_widget) + + # 设置窗口的标题 + self.setWindowTitle('EulerLauncherGUI') + + # 设置窗口的图标 + self.setWindowIcon(QIcon('etc/favicon.ico')) + + # 加载样式表 + self.load_qss_style() + + # 设置窗口的大小 + self.resize(800, 450) + + def widget_of_iso(self): # 创建镜像管理页面 + wid = QWidget() + + # 定义布局 + main_layout = QVBoxLayout(wid) + button_layout = QHBoxLayout(wid) + + # 镜像列表的标题 + label1 = QLabel(f'镜像名称 | 位置 | 状态', wid) + label1.setAlignment(Qt.AlignLeft) + main_layout.addWidget(label1) + + # 镜像列表 使用QListWidget + self.list_of_iso = QListWidget(wid) + + # 四个按钮 + button_fresh = QPushButton('刷新', wid) + button_load = QPushButton('加载本地镜像', wid) + button_del = QPushButton('删除镜像', wid) + button_download = QPushButton('下载镜像', wid) + + # 各个button的位置布局 放入HBox button_layout 中 + button_layout.addStretch(1) + button_layout.addWidget(button_fresh) + button_layout.addStretch(1) + button_layout.addWidget(button_load) + button_layout.addStretch(1) + button_layout.addWidget(button_del) + button_layout.addStretch(1) + button_layout.addWidget(button_download) + button_layout.addStretch(1) + + # 设置按钮的信号与槽 + button_fresh.clicked.connect(self.button_fresh_clicked) + button_load.clicked.connect(self.button_load_clicked) + button_del.clicked.connect(self.button_del_clicked) + button_download.clicked.connect(self.button_download_clicked) + + # 将文本、镜像列表和按钮布局放入VBox main_layout 中 + main_layout.addWidget(self.list_of_iso) + main_layout.addWidget(self.refresh_time) + main_layout.addLayout(button_layout) + + # 设置窗口的布局 + wid.setLayout(main_layout) + + return wid + + def widget_of_management(self): # 创建虚拟机管理页面 + wid = QWidget() + + # 定义布局 + main_layout = QVBoxLayout(wid) + button_layout = QHBoxLayout(wid) + head_layout = QHBoxLayout(wid) + + # 虚拟机列表的标题 + label1 = QLabel(f'虚拟机名称 | 使用的镜像 | 状态 | IP', wid) + label1.setAlignment(Qt.AlignLeft | Qt.AlignBottom) + head_layout.addWidget(label1) + + # 镜像列表 使用QListWidget + self.list_of_vm = QListWidget(wid) + + # 六个按钮 + button_vmfresh = QPushButton('刷新', wid) + button_vmcreate = QPushButton('创建虚拟机', wid) + button_vmsnapshot = QPushButton('生成快照', wid) + button_vmdel = QPushButton('删除虚拟机', wid) + button_vmout = QPushButton('导出为开发镜像', wid) + button_vmlink = QPushButton('连接', wid) + + # 刷新按钮与Label一行 + head_layout.addWidget(button_vmfresh) + main_layout.addLayout(head_layout) + + # 其他各个button的位置布局 放入HBox button_layout 中 + button_layout.addStretch(1) + button_layout.addWidget(button_vmcreate) + button_layout.addStretch(1) + button_layout.addWidget(button_vmdel) + button_layout.addStretch(1) + button_layout.addWidget(button_vmsnapshot) + button_layout.addStretch(1) + button_layout.addWidget(button_vmout) + button_layout.addStretch(1) + button_layout.addWidget(button_vmlink) + button_layout.addStretch(1) + + # 设置按钮的信号与槽 + button_vmfresh.clicked.connect(self.button_vmfresh_clicked) + button_vmcreate.clicked.connect(self.button_vmcreate_clicked) + button_vmsnapshot.clicked.connect(self.button_vmsnapshot_clicked) + button_vmdel.clicked.connect(self.button_vmdel_clicked) + button_vmout.clicked.connect(self.button_vmout_clicked) + button_vmlink.clicked.connect(self.button_vmlink_clicked) + + # 将文本、镜像列表和按钮布局放入VBox main_layout 中 + main_layout.addWidget(self.list_of_vm) + main_layout.addWidget(self.refresh_vm_time) + main_layout.addLayout(button_layout) + + # 设置窗口的布局 + wid.setLayout(main_layout) + + return wid + + def widget_of_connecting(self): # 创建虚拟机连接页面 + wid = QWidget() + + # 定义按钮 + btn = QPushButton('调用终端进行SSH连接', wid) + btn.clicked.connect(self.button_connection_clicked) + btn2 = QPushButton('在GUI中进行SSH连接', wid) + btn2.clicked.connect(self.connect_ssh) + btn3 = QPushButton('断开连接', wid) + btn3.clicked.connect(self.close_ssh) + self.ck = QCheckBox('保留上一句命令', wid) + + # 定义内容输出框 + self.output_text = QTextEdit(wid) + self.output_text.setReadOnly(True) + + # 定义输入框与发送按钮 + self.command_input = QLineEdit(wid) + self.command_input.returnPressed.connect(self.execute_command) + self.command_input.setPlaceholderText("Command Press Enter to send") + send_button = QPushButton('Send', wid) + send_button.clicked.connect(self.execute_command) + + # 定义布局 + main_layout = QVBoxLayout(wid) + upper_layout = QHBoxLayout(wid) + bottom_layout = QHBoxLayout(wid) + + button_layout = QVBoxLayout(wid) + button_layout.addWidget(btn) + button_layout.addWidget(btn2) + button_layout.addWidget(btn3) + + layout = QVBoxLayout(wid) + layout.addWidget(self.vm_tip) + layout.addWidget(self.vm_name) + layout.addWidget(self.vm_image) + layout.addWidget(self.vm_state) + layout.addWidget(self.vm_ip) + + upper = QWidget() + upper_layout.addLayout(layout) + upper_layout.addLayout(button_layout) + upper.setLayout(upper_layout) + upper.setMaximumHeight(200) + + bottom_layout.addWidget(self.command_input) + bottom_layout.addWidget(send_button) + + main_layout.addWidget(upper) + main_layout.addWidget(self.output_text) + main_layout.addLayout(bottom_layout) + main_layout.addWidget(self.ck) + + wid.setLayout(main_layout) + + return wid + + def widget_of_setting(self): # 创建设置页面 + wid = QWidget() + main_layout = QVBoxLayout(wid) + + # conf文件选择区域 + conf_layout = QHBoxLayout(wid) + + conf_button = QPushButton('选择conf文件', wid) + conf_button.clicked.connect(self.fetch_conf_path) + + self.conf_label = QLabel('conf文件未选中', wid) + + conf_layout.addWidget(self.conf_label) + conf_layout.addWidget(conf_button) + + # conf文件显示内容修改区域 + bottom_layout = QHBoxLayout(wid) + btn_layout = QVBoxLayout(wid) + + self.conf_text = QTextEdit(wid) + self.conf_text.setReadOnly(True) + + edit_button = QPushButton('修改', wid) + edit_button.clicked.connect(self.write_conf) + re_button = QPushButton('恢复默认', wid) + re_button.clicked.connect(self.initialize_conf) + + btn_layout.addStretch(1) + btn_layout.addWidget(edit_button) + btn_layout.addWidget(re_button) + btn_layout.addStretch(1) + + bottom_layout.addWidget(self.conf_text) + bottom_layout.addLayout(btn_layout) + + # 设置布局 + main_layout.addLayout(conf_layout) + main_layout.addWidget(self.create_line()) + main_layout.addLayout(bottom_layout) + + wid.setLayout(main_layout) + + return wid + + def switch_page(self): # 切换页面 + button = self.sender() + if self.current_button: # 取消之前选中的按钮 + self.current_button.setChecked(False) + button.setChecked(True) # 设置当前选中的按钮 + self.current_button = button + index = 0 + if button.text() == '镜像管理': + index = 0 + elif button.text() == '虚拟机管理': + index = 1 + elif button.text() == '虚拟机连接': + index = 2 + elif button.text() == '设置': + index = 3 + self.stack.setCurrentIndex(index) # 切换页面 + if self.ssh: + self.close_ssh() # 关闭ssh连接, 防止在连接界面切换到其他界面时出现问题 + + def do_cmd(self, cmd): + # 执行命令行语句, 并将输出以列表形式返回, check为True表示正常执行, False表示出现错误 + outputs = [] + check = True + try: # 捕获异常 + if sys.platform == 'win32': + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + creationflags=subprocess.CREATE_NO_WINDOW) + else: + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + while True: + output = p.stdout.readline() + if output == '' and p.poll() is not None: + break + elif output: + outputs.append(output.strip()) + + # 等待进程结束 + p.wait() + + # 错误输出 + stderr = p.stderr.read() + if stderr: + self.create_message_box('错误', stderr) + check = False + + # 异常处理 + except FileNotFoundError: + self.create_message_box('错误', '命令行未知命令: eulerlauncher\n请检查是否安装了EulerLauncher\n' + '\n需要以管理员身份运行' + '\nWindows:是否将EulerLauncher添加到环境变量PATH中' + '\nLinux & MacOS:EulerLauncher是否按说明将conf文件配置正确') + check = False + except Exception as e: + self.create_message_box('错误', str(e)) + check = False + finally: + return outputs, check + + @staticmethod + def table_process(column_num, table_list, target_q_list_widget): # 该函数处理返回的表格 + for item in table_list: + if item[0] == '+': # 去除表格线 + continue + else: + pattern1 = r'.+Images.+Location.+Status.*' + pattern2 = r'.+Name.+Image.+State.+IP.*' + if re.match(pattern1, item) or re.match(pattern2, item): # 去除表头 + continue + else: # 处理表格内容 + item = item.split() + flag = 0 + item_process = '' + for i in item: + if not i == '|': + flag += 1 + item_process += i + if flag < column_num: + item_process += ' | ' + target_q_list_widget.addItem(item_process) + + def button_fresh_clicked(self): # 镜像刷新按钮的槽函数 + self.list_of_iso.clear() + + # 构建命令行语句 + cmd = ['eulerlauncher', 'images'] + + # 执行命令行语句, 并将输出添加到列表中 + iso_list, check = self.do_cmd(cmd) + if check: + if not iso_list[0][0] == '+': # 返回的不是一个表格, 说明出现了错误 + if iso_list[0] == 'Calling to EulerLauncherd daemon failed, please check ' \ + 'EulerLauncherd daemon status ...': + self.create_message_box('错误', '{}\n请检查是否运行了EulerLauncherd后台程序'.format(iso_list[0])) + else: + self.create_message_box('错误', iso_list[0]) + else: + # 处理表格 + self.table_process(3, iso_list, self.list_of_iso) + self.list_of_iso.update() + + # 刷新时间 + self.refresh_time.setText('刷新时间: ' + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())) + + def button_load_clicked(self): # 加载本地镜像按钮的槽函数 + # 打开文件选择对话框 + filepath, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "镜像文件 (*.qcow2 *.raw *.vmdk *.vhd *.vhdx " + "*.qcow *.vdi *.qcow2.xz *.raw.xz *.vmdk.xz *.vhd.xz *.vhdx.xz " + "*.qcow.xz *.vdi.xz)") + if filepath: + # 选中镜像后给镜像命名 + image_name, ok = QInputDialog.getText(self, '镜像命名', '请输入镜像名称:') + if ok: + # 构建命令行语句 + cmd = ['eulerlauncher', 'load-image', '--path', filepath, image_name] + + # 执行命令行语句, 并将输出添加到列表中 + outputs, check = self.do_cmd(cmd) + if check: + self.refresh_time.setText( + 'Loading: {}, 可能需要一段时间, 请通过刷新按键查看加载状态'.format(image_name)) + self.create_message_box('信息', 'Loading: {}, 可能需要一段时间\n' + '请通过刷新按键查看加载状态, Ready时加载完毕'.format(image_name)) + self.button_fresh_clicked() + + def button_del_clicked(self): # 删除镜像按钮的槽函数 + item = self.list_of_iso.currentItem() + if item: + # 获取选中的镜像名称已经其状态 + iso_info = item.text().split(' | ') + iso_name = iso_info[0] + + # 构建命令行语句 + cmd = ['eulerlauncher', 'delete-image', iso_name] + + # 执行命令行语句, 并将输出添加到列表中 + outputs, check = self.do_cmd(cmd) + pattern = r'.+has been successfully deleted' + if check and re.match(pattern, outputs[0]): + self.refresh_time.setText('镜像{}被成功删除'.format(iso_name)) + self.create_message_box('信息', '镜像{}被成功删除'.format(iso_name)) + self.button_fresh_clicked() + elif check: + self.create_message_box('错误', outputs[0]) + else: + self.create_message_box('错误', '删除失败') + + else: + self.create_message_box('错误', '未选中镜像') + + def button_download_clicked(self): # 下载镜像按钮的槽函数 + item = self.list_of_iso.currentItem() + if item: + # 获取选中的镜像名称已经其状态 + iso_info = item.text().split(' | ') + iso_name = iso_info[0] + iso_status = iso_info[2] + + if iso_status == 'Downloadable': + # 构建命令行语句 + cmd = ['eulerlauncher', 'download-image', iso_name] + + # 执行命令行语句, 并将输出添加到列表中 + outputs, check = self.do_cmd(cmd) + if check: + self.refresh_time.setText( + 'Downloading: {}, 可能需要一段时间, 请通过刷新按键查看下载状态'.format(iso_name)) + self.create_message_box('信息', 'Downloading: {}, 可能需要一段时间\n' + '请通过刷新按键查看下载状态, Ready时为下载完毕'.format(iso_name)) + self.button_fresh_clicked() + else: + self.create_message_box('错误', '未选中镜像') + + def button_vmfresh_clicked(self): # 虚拟机刷新按钮的槽函数 + self.list_of_vm.clear() + + # 构建命令行语句 + cmd = ['eulerlauncher', 'list'] + + # 执行命令行语句, 并将输出添加到列表中 + vm_list, check = self.do_cmd(cmd) + if check: + if not vm_list[0][0] == '+': + if vm_list[0] == 'Calling to EulerLauncherd daemon failed, please check ' \ + 'EulerLauncherd daemon status ...': + self.create_message_box('错误', '{}\n请检查是否运行了EulerLauncherd后台程序\n'.format(vm_list[0])) + else: + self.create_message_box('错误', vm_list[0]) + else: + # 处理表格 + self.table_process(4, vm_list, self.list_of_vm) + self.list_of_vm.update() + + # 如果要连接的虚拟机不存在了, 则将连接界面的信息清空 + if self.vm_ip.text() != '': + vm_ip = self.vm_ip.text() + for i in range(self.list_of_vm.count()): + now = self.list_of_vm.item(i).text() + ip = now.split(' | ')[3] + if ip == vm_ip: + break + else: + self.vm_tip.setText('请点击虚拟机管理界面选择虚拟机进行连接') + self.vm_name.setText('') + self.vm_image.setText('') + self.vm_state.setText('') + self.vm_ip.setText('') + + # 刷新时间 + self.refresh_vm_time.setText('刷新时间: ' + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())) + + def button_vmcreate_clicked(self): # 创建虚拟机按钮的槽函数 + self.button_fresh_clicked() + + # 获取可用镜像列表 + list_of_iso_copy = [] + for i in range(self.list_of_iso.count()): + now = self.list_of_iso.item(i).text() + name = now.split(' | ')[0] + status = now.split(' | ')[2] + if status == 'Ready': + list_of_iso_copy.append(name) + + # 如果没有可用镜像, 则'无可用镜像' + if len(list_of_iso_copy) == 0: + list_of_iso_copy.append('无可用镜像') + + # 弹出对话框, 选择镜像 + text, ok = QInputDialog.getItem(self, '选择一个镜像', '镜像:', list_of_iso_copy, 0, False) + if ok and text: + if text == '无可用镜像': + self.create_message_box('错误', '无可用镜像\n请先加载镜像或下载镜像') + else: + # 弹出对话框, 输入虚拟机名称 + vm_name, ok = QInputDialog.getText(self, '虚拟机命名', '请输入虚拟机名称:') + + if ok: + # 等待提示 + dialog = QDialog(self) + dialog.setWindowTitle('请等待...') + dialog.setWindowModality(Qt.WindowModal) + dialog.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint) + dialog_layout = QVBoxLayout(dialog) + dialog.setLayout(dialog_layout) + dialog_label = QLabel('创建虚拟机{}中, 需要一段时间响应, 请稍后'.format(vm_name)) + dialog_layout.addWidget(dialog_label) + dialog.show() + + # 构建命令行语句 + cmd = ['eulerlauncher', 'launch', '--image', text, vm_name] + + # 执行命令行语句, 并将输出添加到列表中 + outputs, check = self.do_cmd(cmd) + if check: + if outputs[0][0] == '+': # 返回的是一个表格, 说明创建成功 + self.create_message_box('信息', '创建虚拟机{}成功\n' + '需要一定时间进行网络配置,请等待\n'.format(vm_name)) + else: + self.create_message_box('错误', outputs[0]) + else: + self.create_message_box('错误', '创建失败') + dialog.close() + self.button_vmfresh_clicked() + + def button_vmsnapshot_clicked(self): # 生成快照按钮的槽函数 + self.create_message_box('提示', '该功能在主程序尚未实现,请自行取消后续操作') # 该功能在主程序尚未实现,实现后请删除此行代码 + + item = self.list_of_vm.currentItem() + if item: + snapshot_name, ok = QInputDialog.getText(self, '快照命名', '请输入快照名称:') + if ok: + folder_path = QFileDialog.getExistingDirectory(None, '选择保存路径', './', QFileDialog.ShowDirsOnly) + if folder_path: + # 获取选中的虚拟机名称 + vm_name = item.text().split(' | ')[0] + + # 构建命令行语句 + cmd = ['eulerlauncher', 'take-snapshot', snapshot_name, 'snap', folder_path, 'path', vm_name] + + # 执行命令行语句, 并将输出添加到列表中 + outputs, check = self.do_cmd(cmd) + if check: + if re.match(r'Unknown', outputs[0]): # 注:此处无对应的正确输出,因为指令不存在,请在实际情况下修改该判断条件 + self.create_message_box('信息', '创建快照{}成功'.format(snapshot_name)) + else: + self.create_message_box('错误', outputs[0]) + else: + self.create_message_box('错误', '快照生成失败') + self.button_vmfresh_clicked() + else: + self.create_message_box('错误', '未选中虚拟机') + + def button_vmdel_clicked(self): # 删除虚拟机按钮的槽函数 + item = self.list_of_vm.currentItem() + if item: + # 获取选中的虚拟机名称 + vm_name = item.text().split(' | ')[0] + + # 构建命令行语句 + cmd = ['eulerlauncher', 'delete-instance', vm_name] + + # 执行命令行语句, 并将输出添加到列表中 + outputs, check = self.do_cmd(cmd) + if check: + if re.match(r'Successfully deleted instance.+', outputs[0]): + self.create_message_box('信息', '删除虚拟机{}成功'.format(vm_name)) + self.button_vmfresh_clicked() + else: + self.create_message_box('错误', outputs[0]) + else: + self.create_message_box('错误', '删除失败') + self.button_vmfresh_clicked() + + else: + self.create_message_box('错误', '未选中虚拟机') + + def button_vmout_clicked(self): # 导出为开发镜像按钮的槽函数 + self.create_message_box('提示', '该功能在主程序尚未实现,请自行取消后续操作') # 该功能在主程序尚未实现,实现后请删除此行代码 + + item = self.list_of_vm.currentItem() + if item: + image_name, ok = QInputDialog.getText(self, '导出镜像命名', '请输入导出镜像名称:') + if ok: + folder_path = QFileDialog.getExistingDirectory(None, '选择保存路径', './', QFileDialog.ShowDirsOnly) + if folder_path: + # 获取选中的虚拟机名称 + vm_name = item.text().split(' | ')[0] + + # 构建命令行语句 + cmd = ['eulerlauncher', 'export-development-image', image_name, 'image', folder_path, 'path', + vm_name] + + # 执行命令行语句, 并将输出添加到列表中 + outputs, check = self.do_cmd(cmd) + if check: + if re.match(r'Unknown', outputs[0]): # 注:此处无对应的正确输出,因为指令不存在,请在实际情况下修改修改该判断条件 + self.create_message_box('信息', '导出为开发镜像{}成功'.format(image_name)) + else: + self.create_message_box('错误', outputs[0]) + else: + self.create_message_box('错误', '导出失败') + self.button_vmfresh_clicked() + else: + self.create_message_box('错误', '未选中虚拟机') + + def button_vmlink_clicked(self): # 连接按钮的槽函数 + # 获取选中的虚拟机信息 + vm = self.list_of_vm.currentItem() + if vm: + vm_info = vm.text().split(' | ') + # 如果虚拟机IP正在配置中, 则无法连接 + if vm_info[3] == 'N/A': + self.create_message_box('错误', '虚拟机IP正在配置中,请稍后') + else: + # 更新虚拟机连接界面的信息 + self.vm_tip.setText('更换需要连接的虚拟机\n请在虚拟机管理中重新选择\n\n' + '将要连接的虚拟机信息: ') + self.vm_name.setText('虚拟机名称: ' + vm_info[0]) + self.vm_image.setText('使用的镜像: ' + vm_info[1]) + self.vm_state.setText('状态: ' + vm_info[2]) + self.vm_ip.setText(vm_info[3]) + + # 自动切到虚拟机连接界面 + self.button3.click() + else: + self.create_message_box('错误', '未选中虚拟机') + + def button_connection_clicked(self): # 虚拟机连接界面按钮的槽函数 + now_ip = self.vm_ip.text() + if now_ip == '': # 未选中虚拟机 + self.create_message_box('错误', '请点击虚拟机管理界面选择虚拟机进行连接') + else: # 打开终端, 并连接虚拟机 + self.open_terminal(self, 'ssh root@{}'.format(now_ip)) + + @staticmethod + def open_terminal(self, command): # 打开终端并执行命令,用于执行终端ssh连接的第一种方式 + if sys.platform == "win32": + # Windows系统 + subprocess.run(f'start cmd /k {command}', shell=True) + elif sys.platform == "darwin": + # macOS系统 + subprocess.run(['open', '-a', 'Terminal', '--args', 'bash', '-c', f'{command}; exec bash']) + elif sys.platform == "linux" or sys.platform == "linux2": + # Linux系统 + terminals = ['terminal', 'gnome-terminal', 'xterm', 'konsole', 'terminator', 'tilix'] + for terminal in terminals: + try: + subprocess.run([terminal, '--', 'bash', '-c', f'{command}; exec bash']) + break + except FileNotFoundError: + continue + else: + self.create_message_box('错误', '未找到合适的终端') + + def connect_ssh(self): # GUI连接SSH + host = self.vm_ip.text() + user = 'root' + password = 'openEuler12#$' + + if host == '': # 未选中虚拟机 + self.create_message_box('错误', '请点击虚拟机管理界面选择虚拟机进行连接') + return + + # 检查是否为官方镜像,如果是则使用默认密码 + self.button_fresh_clicked() + for i in range(self.list_of_iso.count()): + now = self.list_of_iso.item(i).text() + name = now.split(' | ')[0] + location = now.split(' | ')[1] + tar = self.vm_image.text().split(': ')[1] + if name == tar and location == 'Remote': + break + else: + self.create_message_box('信息', '检测到该虚拟机为非官方镜像\n请手动输入密码连接') + password, ok = QInputDialog.getText(self, '输入密码', '请输入密码:') + if not ok: + return + + # 清空输出框 + self.output_text.clear() + + try: + # 创建SSH连接 + self.ssh = SSHClient() + self.ssh.set_missing_host_key_policy(AutoAddPolicy()) + self.ssh.connect(host, username=user, password=password) + + # 创建伪shell + self.shell = self.ssh.invoke_shell() + + # 读取输出 + if self.shell and self.shell.recv_ready(): + self.output_text.append(f"Connected to {host}\n") + self.read_output() + + except Exception as e: + self.output_text.append(f"Error: {str(e)}\n") + + def read_output(self): # 输出读取函数 + if self.shell: + try: + while True: + if self.shell.recv_ready(): + # 去除输出中ANSI转义字符 + data = re.sub(r'\x1b\[\??([0-9a-z]+)(;[0-9]+)*([A-Za-z])?', '', + self.shell.recv(1024).decode('utf-8')) + # 输出到文本框 + self.output_text.append(data) + if '$' in data or '# ' in data: # 检查命令提示符,结束读取 + break + except Exception as e: + self.output_text.append(f"Error reading output: {str(e)}\n") + + def execute_command(self): # 执行命令,即发送命令 + if self.shell: + command = self.command_input.text() + if command == 'exit': + self.close_ssh() + return + self.shell.send(command + '\n') + self.read_output() + if not self.ck.isChecked(): + self.command_input.clear() + else: + self.output_text.clear() + self.output_text.append("Not connected\n") + + def close_ssh(self): + if self.ssh: + self.ssh.close() + self.ssh = None + if self.shell: + self.shell.close() + self.shell = None + self.output_text.clear() + self.command_input.clear() + self.output_text.append("Disconnected\n") + + def read_conf(self): + try: + with open(self.conf_label.text(), 'r') as f: + self.conf_text.setText(f.read()) + except Exception as e: + self.create_message_box('错误', '读取conf文件失败,{}'.format(e)) + self.conf_text.setText(str(e)) + + def write_conf(self): + if self.conf_label.text() == 'conf文件未选中': + self.create_message_box('错误', '请先选择conf文件') + return + + # 确认弹窗 + reply = QMessageBox.question(self, '确认', '确认修改conf文件?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.No: + return + + # 修改收集弹窗 + dialog = QDialog(self) + dialog.setWindowTitle('修改conf文件') + dialog.resize(400, 200) + + # 创建布局 + layout = QVBoxLayout(dialog) + self.write_text = QTextEdit(dialog) + self.write_text.setText(self.conf_text.toPlainText()) + layout.addWidget(self.write_text) + button = QPushButton('确认', dialog) + button.clicked.connect(self.write_conf_to_file) + layout.addWidget(button) + + # 设置布局 + dialog.setLayout(layout) + + # 显示弹窗 + dialog.exec() + + # 完成后再次读取conf文件 + self.read_conf() + + def write_conf_to_file(self): + try: + with open(self.conf_label.text(), 'w') as f: + f.write(self.write_text.toPlainText()) + self.create_message_box('信息', '修改成功') + except Exception as e: + self.create_message_box('错误', '写入conf文件失败,{}'.format(e)) + finally: + dialog = self.sender().parent() + dialog.close() + + def initialize_conf(self): + if self.conf_label.text() == 'conf文件未选中': + self.create_message_box('错误', '请先选择conf文件') + return + + # 确认弹窗 + reply = QMessageBox.question(self, '确认', '确认将conf文件恢复为初始状态?', QMessageBox.Yes | QMessageBox.No, + QMessageBox.No) + if reply == QMessageBox.No: + return + + # 按系统选择恢复内容 + if sys.platform == "win32": + # Windows系统 + index = r'''[default] +log_dir = C:\workdir\logs +debug = True +work_dir = C:\workdir +image_dir = images +instance_dir = instances +qemu_dir = +pattern = hyper-v + +[vm] +cpu_num = 2 +memory = 8G''' + else: + # Linux & MacOS系统 + index = '''[default] +log_dir = +work_dir = +wget_dir = +qemu_dir = +qemu_img_dir = +debug = True + +[vm] +cpu_num = 1 +memory = 1024''' + + # 确认弹窗 + reply = QMessageBox.question(self, '确认', '确认将conf文件恢复为以下内容?\n{}\n注意:该内容不完整,' + '请稍后自行根据情况填入和更改各参数'.format(index), + QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.No: + return + + # 恢复默认 + try: + with open(self.conf_label.text(), 'w') as f: + f.write(index) + self.create_message_box('信息', '恢复初始状态成功') + except Exception as e: + self.create_message_box('错误', '写入conf文件失败,{}'.format(e)) + + # 完成后读取conf文件内容 + self.read_conf() + + def fetch_conf_path(self): + # 打开文件选择对话框 + filepath, _ = QFileDialog.getOpenFileName(self, "选择对应conf文件", "", + "eulerlauncher.conf文件 (eulerlauncher.conf)") + if filepath: + # 选中conf文件后显示conf文件路径 + self.conf_label.setText(filepath) + + # 读取conf文件内容 + self.read_conf() + + # 创建该程序中标准的消息框 + def create_message_box(self, title, text): + message_box = QMessageBox(self) + message_box.setIcon(QMessageBox.Information) + message_box.setText(text) + message_box.setWindowTitle(title) + message_box.setStandardButtons(QMessageBox.Ok) + message_box.exec() + + @staticmethod # 创建该程序中标准的分割线 True为水平 False为垂直 + def create_line(horizon=True): + line = QFrame() + if horizon: + line.setFrameShape(QFrame.HLine) + else: + line.setFrameShape(QFrame.VLine) + line.setFrameShadow(QFrame.Sunken) + return line + + def load_qss_style(self): # 读取 QSS 文件 + # 本字符串为GUI的样式表,用于设置GUI的样式,其为qss格式 + # 进行修改请在style字符串中进行 + style = """ + QWidget + { + font-family: "Arial", "SimHei", sans-serif; + font-size: 14px; + } + QPushButton[name = 'menu'] + { + color : black; + padding-top : 10px; + padding-bottom : 10px; + padding-left : 10px; + padding-right : 50px; + min-width : 100px; + border-left : 0; + font-size : 18px; + background-color: white; + border-radius: 10px; + text-align: left; + } + QPushButton[name = 'menu']:checked + { + background-color: #f0f0f0; + border-left: 5px solid #87CEEB; + padding-left : 5px; + } + QPushButton + { + color : black; + padding : 10px; + min-width : 80px; + } + QListWidget + { + color: black; + border-radius: 10px; + } + QListWidget::item + { + padding: 5px; + } + #sidebar + { + background-color: white; + width: 300px; + color: black; + border-radius: 10px; + } + QLineEdit + { + border-radius: 10px; + border: 1px solid #87CEEB; + padding: 10px; + } + QTextEdit + { + border-radius: 10px; + border: 1px solid #87CEEB; + padding: 3px; + } + """ + self.setStyleSheet(style) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + ex = EulerLauncherGUI() + ex.show() + sys.exit(app.exec()) diff --git a/requirements-win.txt b/requirements-win.txt index 7ed4143..cb83a50 100644 --- a/requirements-win.txt +++ b/requirements-win.txt @@ -23,4 +23,5 @@ pystray PyYAML six urllib3 -wget \ No newline at end of file +wget +PyQt5 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 93e8873..54bd753 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,4 +27,5 @@ urllib3 wget pywebview sqlalchemy -httpx \ No newline at end of file +httpx +PyQt5 \ No newline at end of file -- Gitee From 5732ebd557b15b5f6b1b9d6e90dc2cd74ce898fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sun, 22 Sep 2024 23:12:49 +0800 Subject: [PATCH 02/14] change gitignore and commit a spec file. --- .gitignore | 2 +- specs/eulerlauncherGUI.spec | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 specs/eulerlauncherGUI.spec diff --git a/.gitignore b/.gitignore index 7bf9f33..8cf4c65 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ MANIFEST # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec +# *.spec # Installer logs pip-log.txt diff --git a/specs/eulerlauncherGUI.spec b/specs/eulerlauncherGUI.spec new file mode 100644 index 0000000..1279092 --- /dev/null +++ b/specs/eulerlauncherGUI.spec @@ -0,0 +1,41 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +a = Analysis( + ['..\\eulerlauncher\\eulerlauncherGUI.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='eulerlauncherGUI', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=['..\\logos\\favicon.ico'], +) -- Gitee From 099b8d42b1320ab7cfa5226bdffb0c00fa5ab091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sun, 22 Sep 2024 23:21:51 +0800 Subject: [PATCH 03/14] add Tips in GUI py file. --- eulerlauncher/eulerlauncherGUI.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/eulerlauncher/eulerlauncherGUI.py b/eulerlauncher/eulerlauncherGUI.py index fd33d7b..7f1a275 100644 --- a/eulerlauncher/eulerlauncherGUI.py +++ b/eulerlauncher/eulerlauncherGUI.py @@ -4,13 +4,14 @@ Description: EulerLauncher前端界面 Tips: - take-snapshot与export-development-image功能由于主程序尚未实现,该前端界面在这两部分留空,实现后请补齐 具体需要修改的位置为button_vmsnapshot_clicked和button_vmout_clicked两个函数 - Line 619 未实现的提示,主程序实现后请删除此行代码 - Line 636 实现后请在实际情况下修改该判断条件,就是按照正确执行后命令行的返回值特点来进行判断 - Line 671 未实现的提示,主程序实现后请删除此行代码 - Line 689 实现后请在实际情况下修改该判断条件,就是按照正确执行后命令行的返回值特点来进行判断 + Line 620 未实现的提示,主程序实现该功能后请删除此行代码 + Line 637 实现后请在实际情况下修改该判断条件,就是按照正确执行后命令行的返回值特点来进行判断 + Line 672 未实现的提示,主程序实现该功能后请删除此行代码 + Line 690 实现后请在实际情况下修改该判断条件,就是按照正确执行后命令行的返回值特点来进行判断 +- 由于主程序暂未对Linux提供支持,因此Linux系统下的终端SSH连接功能还未进行验证,修改请在Line 739处进行 - 增删改功能请在主界面init_ui或四个界面生成函数(以widget_of开头)中增删改按钮等控件,然后增删改对应的槽函数即可。 - 本代码中有许多通用方法,如do_cmd、table_process、create_massage_box、create_line等,可以复用 - 它们在Line 373、414、954、963 + 它们在Line 374、415、955、964 - 请注意,本代码仅为前端界面,后端功能由EulerLauncherd提供,因此请确保整个EulerLauncher已经正确安装并配置好conf文件 """ -- Gitee From 3b660adc70a92dda758d9adbc9a634dd4e1a7913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Tue, 24 Sep 2024 19:16:34 +0800 Subject: [PATCH 04/14] change manual files. --- docs/developer-manual.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/developer-manual.md b/docs/developer-manual.md index 45a60a4..a579838 100644 --- a/docs/developer-manual.md +++ b/docs/developer-manual.md @@ -122,15 +122,16 @@ EulerLauncher可执行文件包括以下几个部分: - eulerlauncherd.exe:EulerLauncher的主进程,是运行在后台的守护进程,负责与各类虚拟化后端交互,管理虚拟机、容器以及镜像的生命周期,eulerlauncherd.exe是运行在后台的守护进程。 - eulerlauncher.exe:EulerLauncher的CLI客户端,用户通过该客户端与eulerlauncherd守护进程交互,对虚拟机、镜像等进行相关操作。 +- eulerlauncherGUI.exe:EulerLauncher的GUI客户端,用户也可以通过该图形化界面客户端与eulerlauncherd守护进程交互,对虚拟机、镜像等进行相关操作。 - config-env.bat: 帮助用户快速配置环境变量 1. 构建`eulerlauncherd.exe`: -项目源码中已包含用于构建EulerLauncherd的Spec脚本`EulerLauncherd-win.spec`, 若非必要,请勿修改该文件,使用一下命令开始构建: +项目源码中已包含用于构建EulerLauncherd的Spec脚本`EulerLauncherd-win.spec`, 若非必要,请勿修改该文件,使用以下命令开始构建: - ``` Shell - pyinstaller --clean --noconfirm specs\\EulerLauncherd-win.spec - ``` +``` Shell +pyinstaller --clean --noconfirm specs\\EulerLauncherd-win.spec +``` 2. 构建`eulerlauncher.exe`: @@ -138,9 +139,15 @@ EulerLauncher可执行文件包括以下几个部分: pyinstaller --clean --noconfirm specs\\cli-win.spec ``` -3. 将`etc\bin`目录下的`config-env.bat`,`qemu`及`qemu-img`文件夹拷贝到制品目录。 +3. 构建`eulerlauncherGUI.exe`: + +``` Shell +pyinstaller --clean --noconfirm specs\\eulerlauncherGUI.spec +``` + +4. 将`etc\bin`目录下的`config-env.bat`,`qemu`及`qemu-img`文件夹拷贝到制品目录。 -4. 在制品目录创建`etc`文件夹,将`logos\favicon.png`复制到该目录下,并在该目录下创建`eulerlauncher.conf`文件,最后将制品目录压缩打包 +5. 在制品目录创建`etc`文件夹,将`logos\favicon.png`与`logos\favicon.ico`复制到该目录下,并在该目录下创建`eulerlauncher.conf`文件,最后将制品目录压缩打包 [1]: https://www.python.org/ [2]: https://brew.sh/ \ No newline at end of file -- Gitee From 91e28f5e36aa33f3aee0afc1ebbf22c1b121ba2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Tue, 24 Sep 2024 19:24:06 +0800 Subject: [PATCH 05/14] first push --- .idea/.gitignore | 8 +++++ .idea/eulerlauncher.iml | 14 +++++++++ .idea/inspectionProfiles/Project_Default.xml | 30 +++++++++++++++++++ .../inspectionProfiles/profiles_settings.xml | 6 ++++ .idea/modules.xml | 8 +++++ .idea/vcs.xml | 6 ++++ 6 files changed, 72 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/eulerlauncher.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/eulerlauncher.iml b/.idea/eulerlauncher.iml new file mode 100644 index 0000000..8e5446a --- /dev/null +++ b/.idea/eulerlauncher.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..24b292c --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,30 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..2ecf9ba --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file -- Gitee From 6a48f32830e683ab11be2eebff494ee7e8ba0635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Wed, 25 Sep 2024 16:23:35 +0800 Subject: [PATCH 06/14] add an icns logo which will be used on macOS. --- logos/favicon.icns | Bin 0 -> 27781 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 logos/favicon.icns diff --git a/logos/favicon.icns b/logos/favicon.icns new file mode 100644 index 0000000000000000000000000000000000000000..83fad6db5ab6f082d2b6af88e32c58fcbeb9f386 GIT binary patch literal 27781 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 Date: Wed, 25 Sep 2024 16:24:59 +0800 Subject: [PATCH 07/14] Change the name of Windows GUI spec file. --- specs/{eulerlauncherGUI.spec => eulerlauncherGUI-win.spec} | 1 - 1 file changed, 1 deletion(-) rename specs/{eulerlauncherGUI.spec => eulerlauncherGUI-win.spec} (98%) diff --git a/specs/eulerlauncherGUI.spec b/specs/eulerlauncherGUI-win.spec similarity index 98% rename from specs/eulerlauncherGUI.spec rename to specs/eulerlauncherGUI-win.spec index 1279092..88ce5ff 100644 --- a/specs/eulerlauncherGUI.spec +++ b/specs/eulerlauncherGUI-win.spec @@ -13,7 +13,6 @@ a = Analysis( runtime_hooks=[], excludes=[], noarchive=False, - optimize=0, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) -- Gitee From 2f6af8f86c8a9fed21c97d9bc98bc2454e278fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sat, 28 Sep 2024 19:20:43 +0800 Subject: [PATCH 08/14] Add spec file for macOS and fixed some bugs on macOS. --- eulerlauncher/eulerlauncherGUI.py | 14 ++++++--- specs/eulerlauncherGUI-Mac.spec | 48 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 specs/eulerlauncherGUI-Mac.spec diff --git a/eulerlauncher/eulerlauncherGUI.py b/eulerlauncher/eulerlauncherGUI.py index 7f1a275..00efe2c 100644 --- a/eulerlauncher/eulerlauncherGUI.py +++ b/eulerlauncher/eulerlauncherGUI.py @@ -1,5 +1,5 @@ """ -Author: XiangChenyu +Author: Xiang Chenyu Description: EulerLauncher前端界面 Tips: - take-snapshot与export-development-image功能由于主程序尚未实现,该前端界面在这两部分留空,实现后请补齐 @@ -11,7 +11,7 @@ Tips: - 由于主程序暂未对Linux提供支持,因此Linux系统下的终端SSH连接功能还未进行验证,修改请在Line 739处进行 - 增删改功能请在主界面init_ui或四个界面生成函数(以widget_of开头)中增删改按钮等控件,然后增删改对应的槽函数即可。 - 本代码中有许多通用方法,如do_cmd、table_process、create_massage_box、create_line等,可以复用 - 它们在Line 374、415、955、964 + 它们在Line 374、415、961、970 - 请注意,本代码仅为前端界面,后端功能由EulerLauncherd提供,因此请确保整个EulerLauncher已经正确安装并配置好conf文件 """ @@ -733,13 +733,19 @@ class EulerLauncherGUI(QMainWindow): subprocess.run(f'start cmd /k {command}', shell=True) elif sys.platform == "darwin": # macOS系统 - subprocess.run(['open', '-a', 'Terminal', '--args', 'bash', '-c', f'{command}; exec bash']) + applescript = ''' + tell application "Terminal" + activate + do script "{}" + end tell + '''.format(command) + subprocess.run(['osascript'], text=True, input=applescript) elif sys.platform == "linux" or sys.platform == "linux2": # Linux系统 terminals = ['terminal', 'gnome-terminal', 'xterm', 'konsole', 'terminator', 'tilix'] for terminal in terminals: try: - subprocess.run([terminal, '--', 'bash', '-c', f'{command}; exec bash']) + subprocess.run([terminal, '--', 'bash', '-c', f'{command}']) break except FileNotFoundError: continue diff --git a/specs/eulerlauncherGUI-Mac.spec b/specs/eulerlauncherGUI-Mac.spec new file mode 100644 index 0000000..e11aba0 --- /dev/null +++ b/specs/eulerlauncherGUI-Mac.spec @@ -0,0 +1,48 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +a = Analysis( + ['../eulerlauncher/eulerlauncherGUI.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + cipher=block_cipher, + noarchive=False, +) +pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='eulerlauncherGUI', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + uac_admin=True, + icon=['../logos/favicon.icns'], +) +app = BUNDLE( + exe, + name='eulerlauncherGUI.app', + icon='../logos/favicon.icns', + bundle_identifier=None, +) -- Gitee From 05ddcf95b71c5cc5a5ffa293beba8e62a7988f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sat, 28 Sep 2024 19:33:55 +0800 Subject: [PATCH 09/14] Modify mac-user-manual.md --- docs/mac-user-manual.md | 293 +++++++++++++++++++++++++--------------- 1 file changed, 185 insertions(+), 108 deletions(-) diff --git a/docs/mac-user-manual.md b/docs/mac-user-manual.md index 2e7b82e..c864497 100644 --- a/docs/mac-user-manual.md +++ b/docs/mac-user-manual.md @@ -54,6 +54,8 @@ brew install wget 其中`install`可执行文件为安装文件,用于将**EulerLauncher**所需支持文件安装到指定位置,`EulerLauncher.dmg`为主程序的磁盘映象。 +如果要安装GUI的话,还需要用到`eulerlauncherGUI.dmg`。 + 1. 安装支持文件(本操作需要sudo权限,请先完成前面的步骤):双击`install`可执行文件,等待程序完成执行。 2. 配置**EulerLauncher**: @@ -98,6 +100,10 @@ brew install wget +4. 安装**eulerlauncherGUI.app**(选装): + + - 参考上一步,双击`eulerlauncherGUI.dmg`,在弹出的窗口中用鼠标将`eulerlauncherGUI.app`拖动到`Applications`中,即可完成安装,并可在应用程序中找到`eulerlauncherGUI.app` + ## 使用EulerLauncher 1. 在应用程序中找到`EulerLauncher.app`,单击启动程序。 @@ -106,166 +112,237 @@ brew install wget -3. EulerLauncher当前仅支持命令行方式进行访问,请打开`终端.app`,使用命令行进行操作。 +3. EulerLauncher当前支持命令行方式和使用GUI进行访问,请打开`终端.app`或者`eulerlauncherGUI.app`,进行操作。 ### 镜像操作 1. 获取可用镜像列表: -```Shell -eulerlauncher images - -+-----------+----------+--------------+ -| Images | Location | Status | -+-----------+----------+--------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 2203-load | Local | Ready | -+-----------+----------+--------------+ -``` +> ***CLI客户端*** +> ```Shell +> eulerlauncher images +> +-----------+----------+--------------+ +> | Images | Location | Status | +> +-----------+----------+--------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 2203-load | Local | Ready | +> +-----------+----------+--------------+ +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边下方点击`刷新`,即可获取可用镜像列表,内容包括镜像名称、位置、状态等信息。 **EulerLauncher**镜像有两种位置属性:1)远端镜像 2)本地镜像,只有处于本地且状态为 `Ready` 的镜像可以直接用来创建虚拟机,位于远端的镜像需要下载后才能够使用;你也可以加载已经预先下载好的本地镜像到**EulerLauncher**中,具体操作方法可以参考接下来的操作指导。 2. 下载远端镜像 -```Shell -eulerlauncher download-image 22.03-LTS +> ***CLI客户端*** +> ```Shell +> eulerlauncher download-image 22.03-LTS +> +> Downloading: 22.03-LTS, this might take a while, please check image status with "images" command. +> ``` -Downloading: 22.03-LTS, this might take a while, please check image status with "images" command. -``` +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边列表中选中要下载的镜像,然后在右边下方点击`下载`,即可开始下载镜像,下载过程中点击`刷新`按钮可以查看当前状态。 -镜像下载请求是一个异步请求,具体的下载动作将在后台完成,具体耗时与你的网络情况相关,整体的镜像下载流程包括下载、解压缩、格式转换等相关子流程,在下载过程中可以通过 `image` 命令随时查看下载进展与镜像状态,进度条格式为`([downloaded_bytes] [percentage] [download_speed] [remaining_download_time])`: - -```Shell -eulerlauncher images -+-----------+----------+------------------------------------+ -| Images | Location | Status | -+-----------+----------+------------------------------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 22.03-LTS | Local | Downloading: 33792K 8% 4.88M 55s | -+-----------+----------+------------------------------------+ -``` +镜像下载请求是一个异步请求,具体的下载动作将在后台完成,具体耗时与你的网络情况相关,整体的镜像下载流程包括下载、解压缩、格式转换等相关子流程,在下载过程中可以通过 `image` 命令随时查看下载进展与镜像状态,进度条格式为`([downloaded_bytes] [percentage] [download_speed] [remaining_download_time])`: +> ***CLI客户端*** +> ```Shell +> eulerlauncher images +> +> +-----------+----------+------------------------------------+ +> | Images | Location | Status | +> +-----------+----------+------------------------------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 22.03-LTS | Local | Downloading: 33792K 8% 4.88M 55s | +> +-----------+----------+------------------------------------+ +> ``` + +> ***GUI客户端*** +> +> 点击`刷新`按钮查看当前状态,列表中也会显示上面的三列。 当镜像状态转变为 `Ready` 时,表示镜像下载完成,处于 `Ready` 状态的镜像可被用来创建虚拟机: -```Shell -eulerlauncher images - -+-----------+----------+--------------+ -| Images | Location | Status | -+-----------+----------+--------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 22.03-LTS | Local | Ready | -+-----------+----------+--------------+ -``` +> ***CLI客户端*** +> ```Shell +> eulerlauncher images +> +> +-----------+----------+--------------+ +> | Images | Location | Status | +> +-----------+----------+--------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 22.03-LTS | Local | Ready | +> +-----------+----------+--------------+ +> ``` + +> ***GUI客户端*** +> +> 点击`刷新`按钮查看当前状态,列表中也会显示上面的三列,最后一个值为`Ready`时说明下载完成。 3. 加载本地镜像 用户也可以加载自定义镜像或预先下载到本地的镜像到EulerLauncher中用于创建自定义虚拟机: -```Shell -eulerlauncher load-image --path {image_file_path} IMAGE_NAME -``` - -当前支持加载的镜像格式有 `xxx.{qcow2, raw, vmdk, vhd, vhdx, qcow, vdi}.[xz]` - -例如: +> ***CLI客户端*** +> ```Shell +> eulerlauncher load-image --path {image_file_path} IMAGE_NAME +> ``` -```Shell -eulerlauncher load-image --path /opt/openEuler-22.03-LTS-x86_64.qcow2.xz 2203-load +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边下方点击`加载本地镜像`,会弹出文件选择窗口,按照弹出窗口的引导选择本地镜像文件,输入镜像名称,即可完成加载。 -Loading: 2203-load, this might take a while, please check image status with "images" command. -``` -将位于 `/opt` 目录下的 `openEuler-22.03-LTS-x86_64.qcow2.xz` 加载到EulerLauncher系统中,并命名为 `2203-load`,与下载命令一样,加载命令也是一个异步命令,用户需要用镜像列表命令查询镜像状态直到显示为 `Ready`, 但相对于直接下载镜像,加载镜像的速度会快很多: - -```Shell -eulerlauncher images - -+-----------+----------+----------------------------+ -| Images | Location | Status | -+-----------+----------+----------------------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 2203-load | Local | Loading: (24.00/100%) | -+-----------+----------+----------------------------+ +当前支持加载的镜像格式有 `xxx.{qcow2, raw, vmdk, vhd, vhdx, qcow, vdi}.[xz]` -eulerlauncher images +例如: +> ***CLI客户端*** +> ```Shell +> eulerlauncher load-image --path /opt/openEuler-22.03-LTS-x86_64.qcow2.xz 2203-load +> +> Loading: 2203-load, this might take a while, please check image status with "images" command. +> ``` +> +> 将位于 `/opt` 目录下的 `openEuler-22.03-LTS-x86_64.qcow2.xz` 加载到EulerLauncher系统中,并命名为 `2203-load`,与下载命令一样,加载命令也是一个异步命令,用户需要用镜像列表命令查询镜像状态直到显示为 `Ready`, 但相对于直接下载镜像,加载镜像的速度会快很多: +> +> ```Shell +> eulerlauncher images +> +> +-----------+----------+----------------------------+ +> | Images | Location | Status | +> +-----------+----------+----------------------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 2203-load | Local | Loading: (24.00/100%) | +> +-----------+----------+----------------------------+ +> +> eulerlauncher images +> +> +-----------+----------+--------------+ +> | Images | Location | Status | +> +-----------+----------+--------------+ +> | 22.03-LTS | Remote | Downloadable | +> | 21.09 | Remote | Downloadable | +> | 2203-load | Local | Ready | +> +-----------+----------+--------------+ +> ``` + +> ***GUI客户端*** +> +> 同样以上面的例子为例,点击`加载本地镜像`,在文件选择窗口内选择`/opt/openEuler-22.03-LTS-x86_64.qcow2.xz`,镜像名称输入`2203-load`,点击`确定`,即可完成加载。获取加载情况请点击`刷新`按钮查看。 -+-----------+----------+--------------+ -| Images | Location | Status | -+-----------+----------+--------------+ -| 22.03-LTS | Remote | Downloadable | -| 21.09 | Remote | Downloadable | -| 2203-load | Local | Ready | -+-----------+----------+--------------+ -``` 4. 删除镜像: -通过下面的命令将镜像从EulerLauncher系统中删除: - -```Shell -eulerlauncher delete-image 2203-load +> ***CLI客户端*** +> +> 通过下面的命令将镜像从EulerLauncher系统中删除: +> +> ```Shell +> eulerlauncher delete-image 2203-load +> +> Image: 2203-load has been successfully deleted. +> ``` -Image: 2203-load has been successfully deleted. -``` +> ***GUI客户端*** +> +> 左边栏选择`镜像管理`,右边列表中选中要删除的镜像,然后在右边下方点击`删除镜像`,即可删除镜像。 ### 虚拟机操作 1. 获取虚拟机列表: -```shell -eulerlauncher list - -+----------+-----------+---------+---------------+ -| Name | Image | State | IP | -+----------+-----------+---------+---------------+ -| test1 | 2203-load | Running | 172.22.57.220 | -+----------+-----------+---------+---------------+ -| test2 | 2203-load | Running | N/A | -+----------+-----------+---------+---------------+ -``` +> ***CLI客户端*** +> ```Shell +> eulerlauncher list +> +> +----------+-----------+---------+---------------+ +> | Name | Image | State | IP | +> +----------+-----------+---------+---------------+ +> | test1 | 2203-load | Running | 172.22.57.220 | +> +----------+-----------+---------+---------------+ +> | test2 | 2203-load | Running | N/A | +> +----------+-----------+---------+---------------+ +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边上方点击`刷新`,即可获取虚拟机列表,内容包括虚拟机名称、镜像、状态、IP等信息。 若虚拟机IP地址显示为 `N/A` ,若这台虚拟机的状态为 `Running` 则表示这台虚拟机为新创建的虚拟机,网络还未配置完成,网络配置过程大概需要若干秒,请稍后重新尝试获取相关虚拟机信息。 2. 登录虚拟机: -若虚拟机已成功分配到IP地址,可以直接使用 `SSH` 命令进行登录: +> ***CLI客户端*** +> +>若虚拟机已成功分配到IP地址,可以直接使用 `SSH` 命令进行登录: +> +> +> ```Shell +> ssh root@{instance_ip} +> ``` + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要登录的虚拟机,然后在右边下方点击`连接`,界面会自动跳转到`虚拟机连接`。`虚拟机连接`界面上方左侧会显示虚拟机的各种信息,上方右侧有三个按钮,提供两种SSH连接方式。 +> - 选择`调用终端进行SSH连接`,则会调用系统终端进行SSH连接,会自动弹出终端窗口,在里面操作即可,关闭终端窗口即可断开连接(最好先输入exit指令)。 +> - 选择`在GUI中进行SSH连接`,如果使用导入的镜像则需要在弹出窗口中输入密码,连接成功则会在界面下方的文本框内显示信息,在输入栏中输入命令后按输入条右侧的`Send`或者按键盘上的`Enter`键即可发送命令。要断开连接,可以点击上方右侧的`断开连接`按钮,或者在输入栏中输入`exit`命令,还可以在左边栏直接切换到其他页面(不推荐),这三种方式均可断开连接。 -```Shell -ssh root@{instance_ip} -``` 若使用的是openEuler社区提供的官方镜像,则默认用户为 `root` 默认密码为 `openEuler12#$` 3. 创建虚拟机 -```Shell -eulerlauncher launch --image {image_name} {instance_name} -``` +> ***CLI客户端*** +> ```Shell +> eulerlauncher launch --image {image_name} {instance_name} +> ``` +> +> 通过 `--image` 指定镜像,同时指定虚拟机名称,EulerLauncher会根据所指定的镜像默认创建一个规格为`2U4G`的openEuler虚拟机。 -通过 `--image` 指定镜像,同时指定虚拟机名称。 +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边下方点击`创建虚拟机`,弹出窗口中选择镜像,输入虚拟机名称,点击`确定`,即可创建虚拟机,创建过程中请耐心等待`请等待`弹窗消失。 4. 删除虚拟机 -```Shell -eulerlauncher delete-instance {instance_name} -``` -根据虚拟机名称删除指定的虚拟机。 +> ***CLI客户端*** +> ```Shell +> eulerlauncher delete-instance {instance_name} +> ``` +> 根据虚拟机名称删除指定的虚拟机。 + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要删除的虚拟机,然后在右边下方点击`删除虚拟机`,即可删除虚拟机。 5. 为虚拟机打快照,并导出为镜像 -```Shell -eulerlauncher take-snapshot --snapshot_name snap --export_path path vm_name -``` -通过`--snapshot_name`指定快照名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名。 +> ***CLI客户端*** +> ```Shell +> eulerlauncher take-snapshot --snapshot_name snap --export_path path vm_name +> ``` +> 通过`--snapshot_name`指定快照名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名。 + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要打快照的虚拟机,然后在右边下方点击`生成快照`,弹出窗口中选择文件存放位置,输入快照名称,点击`确定`,即可完成快照,其会存放在选择的路径下。 6. 将虚拟机导出为主流编程框架开发镜像 -```Shell -eulerlauncher export-development-image --image_name image --export_path path vm_name -``` -通过`--image_name`指定导出镜像名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名,默认导出为Python/Go/Java主流编程框架开发镜像。 +> ***CLI客户端*** +> ```Shell +> eulerlauncher export-development-image --image_name image --export_path path vm_name +> ``` +> 通过`--image_name`指定导出镜像名称,`--export_path`指定导出镜像存放位置,`vm_name`为虚拟机名,默认导出为Python/Go/Java主流编程框架开发镜像。 + +> ***GUI客户端*** +> +> 左边栏选择`虚拟机管理`,右边列表中选中要导出的虚拟机,然后在右边下方点击`导出为开发镜像`,弹出窗口中选择文件存放位置,输入镜像名称,点击`确定`,即可完成导出,其会存放在选择的路径下。 [1]: https://developer.apple.com/documentation/vmnet [2]: https://gitee.com/openeuler/eulerlauncher/releases \ No newline at end of file -- Gitee From 5cc5366ef1437752bb3ed27d38efecb1e3f17894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sat, 28 Sep 2024 19:34:23 +0800 Subject: [PATCH 10/14] Modify developer-user-manual.md --- docs/developer-manual.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/developer-manual.md b/docs/developer-manual.md index a579838..cc39801 100644 --- a/docs/developer-manual.md +++ b/docs/developer-manual.md @@ -49,7 +49,8 @@ EulerLauncher可执行文件包括以下几个部分: 1. EulerLauncherd: EulerLauncher守护进程,以root权限运行在后台,与调用虚拟化组件(Qemu、HyperV、KVM等)及镜像组件进行相关操作; 2. EulerLauncher.app: EulerLauncher服务端主程序,将EulerLauncher及其他相关程序、数据、文件等打包为MacOS APP软件包,便于分发和使用。 3. EulerLauncher: MacOS可执行文件,EulerLauncher客户端CLI工具,用于与服务端交互。 -4. install: MacOS可执行文件,将EulerLauncher运行所需配置文件及相关数据文件安装至`Application Support`文件夹。 +4. eulerlauncherGUI.app: EulerLauncher的客户端GUI程序,用于与服务端交互。 +5. install: MacOS可执行文件,将EulerLauncher运行所需配置文件及相关数据文件安装至`Application Support`文件夹。 由于`EulerLauncher.app`对`EulerLauncherd`有依赖关系,请严格按照以下顺序构建`EulerLauncherd`及`EulerLauncher.app`: @@ -74,6 +75,11 @@ pyinstaller --clean --noconfirm specs/cli-mac.spec pyinstaller --clean --noconfirm specs/install.spec ``` +`eulerlauncherGUI.app`与其他文件之间无依赖关系,可以单独构建: +``` Shell +pyinstaller --clean --noconfirm specs/eulerlauncherGUI-Mac.spec +``` + ### 制作`.dmg`: 首先,我们创建一个新目录并将文件移动到其中。 @@ -82,12 +88,19 @@ mkdir -p dist/dmg cp -R dist/EulerLauncher.app dist/dmg ``` -然后,我们可以使用下面的命令来制作磁盘镜像文件: +然后,我们可以使用下面的命令来制作主程序`EulerLauncher.app`的磁盘镜像文件: ``` Shell create-dmg --volname "EulerLauncher" --volicon "etc/images/favicon.png" --window-pos 200 120 --window-size 600 300 --icon-size 100 --icon "EulerLauncher.app" 175 120 --hide-extension "EulerLauncher.app" --app-drop-link 425 120 "dist/EulerLauncher.dmg" "dist/dmg/" ``` -`EulerLauncher.dmg`中将只包含`EulerLauncher.app`主程序,需要将`install`脚本及`EulerLauncher` CLI工具一并压缩后再进行分发。 +类似的,也可以制作出GUI`eulerlauncherGUI.app`的`.dmg`文件: +``` Shell +mkdir -p dist/GUIdmg +cp -R dist/eulerlauncherGUI.app dist/GUIdmg +create-dmg --volname "eulerlauncherGUI" --volicon "logos/favicon.png" --window-pos 200 120 --window-size 600 300 --icon-size 100 --icon "eulerlauncherGUI.app" 175 120 --hide-extension "eulerlauncherGUI.app" --app-drop-link 425 120 "dist/eulerlauncherGUI.dmg" "dist/GUIdmg/" +``` + +`EulerLauncher.dmg`中将只包含`EulerLauncher.app`主程序,`eulerlauncherGUI.dmg`中将只含`eulerlauncherGUI.app`前端界面程序,需要将`install`脚本及`EulerLauncher` CLI工具一并压缩后再进行分发。 ## 在Windows上构建EulerLauncher -- Gitee From 9c1855976913128d623d1726c391ad3206b09825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sat, 28 Sep 2024 19:38:18 +0800 Subject: [PATCH 11/14] Add some installation screenshots of macOS. --- docs/images/mac-GUIcontent.png | Bin 0 -> 5201 bytes docs/images/mac-GUIinstall.png | Bin 0 -> 36979 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/images/mac-GUIcontent.png create mode 100644 docs/images/mac-GUIinstall.png diff --git a/docs/images/mac-GUIcontent.png b/docs/images/mac-GUIcontent.png new file mode 100644 index 0000000000000000000000000000000000000000..dc5a234549e9dbf9b4c1cadb9dadf9e29d738e5b GIT binary patch literal 5201 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 RyhS;o literal 0 HcmV?d00001 diff --git a/docs/images/mac-GUIinstall.png b/docs/images/mac-GUIinstall.png new file mode 100644 index 0000000000000000000000000000000000000000..a0644bd99b9faea5232f13910d18211164994198 GIT binary patch literal 36979 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 Date: Sat, 28 Sep 2024 19:41:54 +0800 Subject: [PATCH 12/14] Add some screenshots to mac-user-manual.md --- docs/mac-user-manual.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/mac-user-manual.md b/docs/mac-user-manual.md index c864497..352ebfb 100644 --- a/docs/mac-user-manual.md +++ b/docs/mac-user-manual.md @@ -50,7 +50,7 @@ brew install wget 解压后的目录包含以下文件: - + 其中`install`可执行文件为安装文件,用于将**EulerLauncher**所需支持文件安装到指定位置,`EulerLauncher.dmg`为主程序的磁盘映象。 @@ -102,7 +102,9 @@ brew install wget 4. 安装**eulerlauncherGUI.app**(选装): - - 参考上一步,双击`eulerlauncherGUI.dmg`,在弹出的窗口中用鼠标将`eulerlauncherGUI.app`拖动到`Applications`中,即可完成安装,并可在应用程序中找到`eulerlauncherGUI.app` + - 参考上一步,双击`eulerlauncherGUI.dmg`,在弹出的窗口中用鼠标将`eulerlauncherGUI.app`拖动到`Applications`中,即可完成安装,并可在应用程序中找到`eulerlauncherGUI.app` + + ## 使用EulerLauncher -- Gitee From adbd8e317a1390a9857916c2323bf994bd50a6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sat, 28 Sep 2024 19:49:53 +0800 Subject: [PATCH 13/14] Check all manual files and fix some bugs in them. --- docs/win-user-manual.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/win-user-manual.md b/docs/win-user-manual.md index 65eb65a..0385da3 100644 --- a/docs/win-user-manual.md +++ b/docs/win-user-manual.md @@ -38,7 +38,7 @@ memory = 8G 当eulerlauncherd.exe运行后,会在操作系统右下角托盘区域生成eulerlauncherd托盘图标: - + 鼠标右键点击托盘图标,并选择 `Exit EulerLauncher` 即可退出EulerLauncherd后台进程。 ### 镜像操作 -- Gitee From f3a415820822b61b6f1fe1e99efddaa1a81e9f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=91=E5=AE=B8=E5=AE=87?= Date: Sat, 28 Sep 2024 11:51:03 +0000 Subject: [PATCH 14/14] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20.i?= =?UTF-8?q?dea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 ----- .idea/eulerlauncher.iml | 14 --------- .idea/inspectionProfiles/Project_Default.xml | 30 ------------------- .../inspectionProfiles/profiles_settings.xml | 6 ---- .idea/modules.xml | 8 ----- .idea/vcs.xml | 6 ---- 6 files changed, 72 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/eulerlauncher.iml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 35410ca..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/eulerlauncher.iml b/.idea/eulerlauncher.iml deleted file mode 100644 index 8e5446a..0000000 --- a/.idea/eulerlauncher.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 24b292c..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 2ecf9ba..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file -- Gitee