configurator: add new configurator

add new configurator

Tracked-On: #6691
Signed-off-by: Weiyi Feng <weiyix.feng@intel.com>
(cherry picked from commit 1232a7229af7ed60706cc725acf955ccbd8ca035)
This commit is contained in:
Weiyi Feng 2022-03-18 17:57:59 +08:00 committed by acrnsi-robot
parent cdb142397b
commit 3c4f14ede7
188 changed files with 17058 additions and 67 deletions

3
.gitignore vendored
View File

@ -6,5 +6,8 @@ build
*.warnings
*.pyc
/offline-tools/
.idea
venv
*.egg-info
/misc/config_tools/dist
/misc/config_tools/configurator/src-tauri/tauri.conf.json

View File

@ -80,6 +80,8 @@ HV_CFG_LOG = $(HV_OUT)/cfg.log
VM_CONFIGS_DIR = $(T)/misc/config_tools
ASL_COMPILER ?= $(shell which iasl)
DPKG_BIN ?= $(shell which dpkg)
YARN_BIN ?= $(shell which yarn)
CARGO_BIN ?= $(shell which cargo)
.PHONY: all hypervisor devicemodel tools life_mngr doc
all: hypervisor devicemodel tools
@ -113,6 +115,13 @@ board_inspector:
echo -e "The 'dpkg' utility is not available. Unable to create Debian package for board_inspector."; \
fi
configurator:
@if [ -x "$(YARN_BIN)" ] && [ -x "$(CARGO_BIN)" ]; then \
python3 misc/packaging/gen_acrn_deb.py configurator $(ROOT_OUT) ; \
else \
echo -e "'yarn' or 'cargo' utility is not available. Unable to create Debian package for configurator."; \
fi
hypervisor: hvdefconfig
$(MAKE) $(HV_MAKEOPTS)
@echo -e "ACRN Configuration Summary:" > $(HV_CFG_LOG)

View File

@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
build
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# TS temp file
tauri-plugin.js.map
tauri-plugin.js

View File

@ -0,0 +1,79 @@
# ACRN Configurator
This version based on tauri, WIP.
## Features
### Support Platforms
- [x] Linux (.deb, AppImage)
- [x] Windows 7,8,10 (.exe, .msi)
- [x] macOS (.app, .dmg)
## Setting Up
### 1. Install System Dependencies
Please follow [this guide](https://tauri.studio/docs/getting-started/prerequisites)
to install system dependencies **(including yarn)**.
### 2. Clone Project And Install Project Dependencies.
#### Linux
```bash
sudo apt install git
git clone https://github.com/projectacrn/acrn-hypervisor
cd acrn-hypervisor/misc/config_tools/configurator
python3 -m pip install -r requirements.txt
yarn
```
#### Windows && macOS
Similar to Linux.
On macOS, you may need to install git and python3 via `brew`.
In Windows environment maybe you need to install git and python3 via chocolatey or manually,
and replace the command line `python3` with `py -3`.
### 3. How To Build
#### Linux
Run this command in the acrn-hypervisor directory.
```shell
make configurator
```
#### Windows/macOS
Run follow command in the 'acrn-hypervisor' directory.
```shell
python3 misc/config_tools/scenario_config/schema_slicer.py
python3 misc/config_tools/scenario_config/xs2js.py
cd misc/config_tools/configurator
yarn build
```
### 4. How To Run
#### Linux
Run this command in the acrn-hypervisor directory.
```shell
sudo apt install ./build/acrn-configurator_*.deb
acrn-configurator
```
#### Windows/macOS
You can find msi(Windows)/dmg(macOS) folder under the
`misc/config_tools/configurator/src-tauri/target/release/bundle`
directory, the installer in the folder.

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite App</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
async function main() {
window.pyodide = await loadPyodide({
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/"
})
pyodide.loadPackage(['micropip', 'lxml', 'beautifulsoup4'])
}
main();
</script>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

@ -0,0 +1,47 @@
{
"name": "acrn-configurator",
"private": true,
"version": "0.2.1",
"author": {
"name": "Feng, Weiyi",
"email": "weiyix.feng@intel.com",
"url": "https://github.com/Weiyi-Feng"
},
"description": "ACRN Configurator",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.3.0",
"@fortawesome/free-regular-svg-icons": "^6.0.0",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"@fortawesome/react-fontawesome": "^0.1.17",
"@popperjs/core": "^2.11.2",
"@rjsf/core": "^4.0.1",
"@rollup/plugin-replace": "^4.0.0",
"@tauri-apps/api": "^1.0.0-rc.1",
"bootstrap": "^5.1.3",
"js-base64": "^3.7.2",
"lodash": "^4.17.21",
"node-fetch": "2",
"query-string": "^7.1.1",
"react": "^17.0.2",
"react-bootstrap": "^2.1.2",
"react-dom": "^17.0.2",
"react-icons": "^4.3.1",
"react-router": "^6.2.1",
"react-router-dom": "^6.2.1",
"vconsole": "^3.14.3"
},
"devDependencies": {
"@tauri-apps/cli": "^1.0.0-rc.5",
"@types/lodash": "^4.14.179",
"@types/node": "^17.0.21",
"@vitejs/plugin-react": "^1.0.7",
"sass": "^1.49.9",
"vite": "^2.8.0"
}
}

View File

@ -0,0 +1,7 @@
lxml
xmltodict
xmlschema
defusedxml
requests
bs4
sphinx

View File

@ -0,0 +1,14 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools
# These are backup files generated by rustfmt
**/*.rs.bk
config.json
bundle.json
# ts temp file
types/*.js
types/*.map

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
[package]
name = "acrn-configurator"
version = "0.1.0"
description = "ACRN Configurator"
authors = ["Feng, Weiyi <weiyix.feng@intel.com>"]
license = "BSD"
repository = "https://github.com/projectacrn/acrn-hypervisor"
default-run = "acrn-configurator"
edition = "2021"
rust-version = "1.57"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.0.0-rc.3", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { git = "https://github.com/Weiyi-Feng/tauri.git", features = ["api-all", "cli"] }
log = "0.4.14"
env_logger = "0.9.0"
glob = "0.3.0"
dirs = "4.0.0"
itertools = "0.10.3"
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = ["custom-protocol"]
# this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]

View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,13 @@
max_width = 100
hard_tabs = false
tab_spaces = 2
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2021"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true

View File

@ -0,0 +1,316 @@
use std::borrow::Borrow;
use std::fs;
use std::ops::Add;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use glob::{glob_with, MatchOptions};
use itertools::Itertools;
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
#[repr(u16)]
#[non_exhaustive]
pub enum HistoryType {
WorkingFolder = 1,
Board,
Scenario,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct History {
pub working_folder: Vec<String>,
pub board_file: Vec<String>,
pub scenario_file: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigData {
pub history: History,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Configurator {
pub config_write_enable: bool,
pub config_path: PathBuf,
pub config_data: ConfigData,
pub working_folder: String,
}
pub fn write_file(path: PathBuf, content: String) -> Result<(), String> {
fs::write(path, content).map_err(|e| e.to_string())
}
impl ConfigData {
fn new() -> ConfigData {
let history = History { working_folder: vec![], board_file: vec![], scenario_file: vec![] };
ConfigData { history }
}
pub fn serialize(&self) -> String {
serde_json::to_string(&self).unwrap_or_else(|_| {
let default = ConfigData::new();
ConfigData::serialize(&default)
})
}
/// deserialize data
fn deserialize(config_json: String) -> Result<ConfigData, String> {
match serde_json::from_str(&config_json.to_string()) {
Ok(config_data) => Ok(config_data),
Err(e) => Err(e.to_string())
}
}
}
impl Configurator {
pub fn new() -> Self {
match Self::ensure_config_file() {
Ok(config_file_path) => {
// read config.json
Self::init(config_file_path)
}
Err(e) => {
log::warn!("get config file path error! error: {}", e.to_string());
log::warn!("Use blank config and disable config write to start configurator.");
Self {
config_write_enable: false,
config_path: Path::new(".").to_path_buf(),
config_data: ConfigData::new(),
working_folder: "".to_string(),
}
}
}
}
fn ensure_config_file() -> Result<PathBuf, String> {
// get config_dir or home_dir path
// Todo: 讨论fallback逻辑是否可行
let config_base = match dirs::config_dir() {
None => {
log::info!("get config_dir error! fallback to get home_dir.");
match dirs::home_dir() {
None => {
return Err("get config_dir and home_dir error!".to_string());
}
Some(path) => path
}
}
Some(path) => path
};
// get acrn-configurator dir path and check it exist
let config_dir = config_base.join(".acrn-configurator");
log::info!("current config_dir is {}.", config_dir.to_str().unwrap());
if !config_dir.is_dir() {
match fs::create_dir(&config_dir) {
Err(e) => {
//Todo: 明确无法创建 .acrn-configurator 文件夹时的处理逻辑
log::warn!("Create configurator config dir failed, {}", e.to_string());
return Err(e.to_string());
}
_ => {}
}
}
// get config.json file path and check it exist
let default_config_path = config_dir.join("config.json");
if !default_config_path.is_file() {
let empty_config = ConfigData::new();
match fs::write(&default_config_path, empty_config.serialize()) {
Ok(_) => {}
Err(e) => return Err(e.to_string())
};
}
Ok(default_config_path)
}
pub fn init(config_file_path: PathBuf) -> Configurator {
let config_json = match fs::read_to_string(&config_file_path) {
Ok(data) => { data }
Err(e) => {
log::warn!("read config error! error: {}", e.to_string());
log::warn!("Use default blank config to start due to read config failed.");
return Configurator {
config_write_enable: false,
config_path: config_file_path,
config_data: ConfigData::new(),
working_folder: "".to_string(),
};
}
};
let config_data = match ConfigData::deserialize(config_json) {
Ok(config_data) => {
log::info!("success load config: {}", config_data.serialize());
config_data
}
Err(e) => {
log::warn!("Deserialize json data error! error: {}", e);
log::warn!("Use default blank config to start due to deserialize config failed.");
ConfigData::new()
}
};
log::info!("Using config: {}", config_data.serialize());
Configurator {
config_write_enable: true,
config_path: config_file_path,
config_data,
working_folder: "".to_string(),
}
}
pub fn save_config(&self) {
if !self.config_write_enable {
return;
}
match fs::write(&self.config_path, self.config_data.serialize()) {
Ok(_) => {}
Err(e) => {
log::warn!("Write config error! error:{}",e.to_string())
}
}
}
pub fn add_history(&mut self, history_type: HistoryType, path: &Path) {
let path_string: String = path.to_string_lossy().parse().unwrap();
match history_type {
HistoryType::WorkingFolder => {
self.config_data.history.working_folder.insert(0, path_string);
self.config_data.history.working_folder = self.config_data.history.working_folder.clone().into_iter().unique().collect()
}
HistoryType::Board => {
self.config_data.history.board_file.insert(0, path_string);
self.config_data.history.board_file = self.config_data.history.board_file.clone().into_iter().unique().collect()
}
HistoryType::Scenario => {
self.config_data.history.scenario_file.insert(0, path_string);
self.config_data.history.scenario_file = self.config_data.history.scenario_file.clone().into_iter().unique().collect()
}
};
}
pub fn get_history(&self, history_type: HistoryType) -> &[String] {
match history_type {
HistoryType::WorkingFolder => {
self.config_data.history.working_folder.borrow()
}
HistoryType::Board => {
self.config_data.history.board_file.borrow()
}
HistoryType::Scenario => {
self.config_data.history.scenario_file.borrow()
}
}
}
pub fn force_reset(&mut self) {
self.config_data = ConfigData::new();
self.save_config()
}
pub fn set_working_folder(&mut self, working_folder: String) {
self.working_folder = working_folder
}
pub fn write_board(&self, board_name: String, board_xml_string: String) -> Result<(), String> {
let options = MatchOptions {
case_sensitive: false,
..Default::default()
};
let pattern = self.working_folder.clone().add("/.*\\.board\\.xml");
let files = match glob_with(&pattern, options).map_err(|e| e.to_string()) {
Ok(files) => { files }
Err(e) => return Err(e.to_string())
};
for entry in files {
match entry {
Ok(filepath) => {
match fs::remove_file(&filepath) {
Ok(_) => {}
Err(e) => {
let err_msg = format!(
"Can not delete file:{} error: {}",
filepath.to_str().unwrap_or_else(|| "").to_string(), e.to_string()
);
log::warn!("{}",err_msg);
return Err(err_msg);
}
}
}
Err(e) => {
log::error!("find old board error! error:{}", e.to_string())
}
}
}
let board_basename = board_name.add(".board.xml");
let board_xml_path = Path::new(&self.working_folder).join(board_basename);
write_file(board_xml_path, board_xml_string)
}
}
static mut WORKING_FOLDER: String = String::new();
#[tauri::command]
pub fn get_history(history_type: HistoryType) -> Result<String, ()> {
let configurator = Configurator::new();
let history = serde_json::to_string(configurator.get_history(history_type)).unwrap_or_else(|_| String::from("[]"));
Ok(history)
}
#[tauri::command]
pub fn add_history(history_type: HistoryType, path: String) -> Result<(), &'static str> {
let path_buf = Path::new(&path);
if !(path_buf.is_dir() || path_buf.is_file()) {
return Err("Not a validate dir or file path.");
}
let mut configurator = Configurator::new();
configurator.add_history(history_type, path_buf);
configurator.save_config();
Ok(())
}
#[tauri::command]
pub fn set_working_folder(working_folder: String) -> Result<(), ()> {
unsafe {
WORKING_FOLDER = working_folder;
}
Ok(())
}
#[tauri::command]
pub fn write_board(board_name: String, contents: String) -> Result<(), String> {
let mut configurator = Configurator::new();
unsafe {
configurator.set_working_folder(WORKING_FOLDER.clone());
}
configurator.write_board(board_name, contents)
}
#[tauri::command]
pub fn force_reset() -> Result<(), ()> {
let mut configurator = Configurator::new();
configurator.force_reset();
Ok(())
}
#[tauri::command]
pub fn get_home() -> Result<String, ()> {
match dirs::home_dir() {
None => {
Ok(String::new())
}
Some(path) => {
Ok(path.to_str().unwrap().to_string())
}
}
}

View File

@ -0,0 +1,102 @@
use serde::Serialize;
use std::fs::{self, File};
use std::io;
use std::io::prelude::*;
use std::path::Path;
#[tauri::command]
pub fn fs_read_text_file(path: &str) -> Result<String, String> {
let mut file = File::open(path).map_err(|e| e.to_string())?;
let mut contents = String::new();
file
.read_to_string(&mut contents)
.map_err(|e| e.to_string())?;
Ok(contents)
}
#[tauri::command]
pub fn fs_write_text_file(path: &str, contents: &str) -> Result<(), String> {
let mut file = File::create(path).map_err(|e| e.to_string())?;
file
.write_all(contents.as_bytes())
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
pub fn fs_read_binary_file(path: &str) -> Result<Vec<u8>, String> {
let mut file = File::open(path).map_err(|e| e.to_string())?;
let mut contents = Vec::new();
file.read_to_end(&mut contents).map_err(|e| e.to_string())?;
Ok(contents)
}
#[tauri::command]
pub fn fs_write_binary_file(path: &str, contents: &[u8]) -> Result<(), String> {
let mut file = File::create(path).map_err(|e| e.to_string())?;
file.write_all(contents).map_err(|e| e.to_string())?;
Ok(())
}
#[derive(Serialize)]
pub struct DirEntry {
path: String,
children: Option<Vec<DirEntry>>,
}
fn read_dir<P: AsRef<Path>>(
path: P,
recursive: bool,
) -> io::Result<Vec<DirEntry>> {
let path = path.as_ref();
let mut entries = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path().to_str().unwrap().to_string();
let children = if recursive && entry.file_type()?.is_dir() {
Some(read_dir(&path, true)?)
} else {
None
};
entries.push(DirEntry { path, children });
}
Ok(entries)
}
#[tauri::command]
pub fn fs_read_dir(
path: &str,
recursive: bool,
) -> Result<Vec<DirEntry>, String> {
read_dir(path, recursive).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_rename(from: &str, to: &str) -> Result<(), String> {
fs::rename(from, to).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_delete_file(path: &str) -> Result<(), String> {
fs::remove_file(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_delete_dir(path: &str) -> Result<(), String> {
fs::remove_dir_all(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_create_dir(path: &str) -> Result<(), String> {
fs::create_dir(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_is_file(path: &str) -> bool {
fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)
}
#[tauri::command]
pub fn fs_is_dir(path: &str) -> bool {
fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
}

View File

@ -0,0 +1,80 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod filesystem;
mod configurator;
use log::*;
use tauri::{api::cli::get_matches, utils::config::CliConfig, PackageInfo, RunEvent};
fn main() {
env_logger::init();
// Init context
let context = tauri::generate_context!();
// Build app instance and run
let app = tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
filesystem::fs_rename,
filesystem::fs_read_dir,
filesystem::fs_read_text_file,
filesystem::fs_read_binary_file,
filesystem::fs_write_text_file,
filesystem::fs_write_binary_file,
filesystem::fs_read_dir,
filesystem::fs_delete_file,
filesystem::fs_delete_dir,
filesystem::fs_create_dir,
filesystem::fs_is_file,
filesystem::fs_is_dir,
configurator::get_history,
configurator::add_history,
configurator::set_working_folder,
configurator::write_board,
configurator::force_reset,
configurator::get_home
])
.setup(|app| {
// Handle cli cmdline
let app_config = app.config();
let cli_config = app_config.tauri.cli.as_ref().unwrap();
let package_info = app.package_info();
handle_cli_arg_data(&cli_config, &package_info);
Ok(())
})
.build(context)
.expect("error while running tauri application");
app.run(|_app, event| {
if let RunEvent::Exit = event {
info!("Received Exit event");
}
});
}
fn handle_cli_arg_data(cli_config: &CliConfig, pkg_info: &PackageInfo) {
match get_matches(cli_config, pkg_info) {
Ok(matches) => {
let mut exit_flag = false;
if let Some(arg_data) = matches.args.get("help") {
println!("{}", arg_data.value.as_str().unwrap_or("No help available"));
exit_flag = true;
}
if let Some(arg_data) = matches.args.get("version") {
println!("{}", arg_data.value.as_str().unwrap_or("No version data available"));
exit_flag = true
}
if exit_flag {
std::process::exit(1);
}
}
Err(e) => {
error!("{}", e.to_string());
std::process::exit(1);
}
}
}

View File

@ -0,0 +1,78 @@
{
"$schema": "types/schema.json",
"package": {
"productName": "acrn-configurator",
"version": "../package.json"
},
"build": {
"distDir": "../build",
"devPath": "http://localhost:3000",
"beforeDevCommand": "",
"beforeBuildCommand": ""
},
"tauri": {
"cli": {
"args": [
{
"name": "debug",
"takesValue": false
}
]
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [],
"externalBin": [],
"copyright": "",
"category": "DeveloperTool",
"shortDescription": "",
"longDescription": "",
"deb": {
"depends": [],
"useBootstrapper": false
},
"macOS": {
"frameworks": [],
"minimumSystemVersion": "",
"useBootstrapper": false,
"exceptionDomain": "",
"signingIdentity": null,
"providerShortName": null,
"entitlements": null
},
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"updater": {
"active": false
},
"allowlist": {
"all": true
},
"windows": [
{
"title": "ACRN Configurator",
"width": 1366,
"height": 768,
"resizable": true,
"fullscreen": false,
"decorations": false
}
],
"security": {
"csp": null
}
}
}

View File

@ -0,0 +1,114 @@
export default interface Config {
"$schema"?: string
package?: PackageConfig
tauri: TauriConfig
build: BuildConfig
plugins?: {}
}
interface PackageConfig {
/// App name.
productName?: string,
/// App version. It is a semver version number or a path to a `package.json` file contaning the `version` field.
version?: string
}
interface TauriConfig {
pattern?: {
/**
* Brownfield pattern.
*/
Brownfield: any,
/// Isolation pattern. Recommended for security purposes.
Isolation: {
/// The dir containing the index.html file that contains the secure isolation application.
dir: string,
},
}
windows: WindowConfig[]
/**
* app's CLI definition
*/
cli?: {}
bundle?: {}
allowlist?: {}
security?: {}
updater?: {}
system_tray?: {}
macos_private_api?: {}
}
interface WindowConfig {
/// The window identifier. It must be alphanumeric.
label?: string,
/// The window webview URL.
url?: string,
/// Whether the file drop is enabled or not on the webview. By default it is enabled.
///
/// Disabling it is required to use drag and drop on the frontend on Windows.
file_drop_enabled?: boolean,
/// Whether or not the window starts centered or not.
center?: boolean,
/// The horizontal position of the window's top left corner
x?: number,
/// The vertical position of the window's top left corner
y?: number,
/// The window width.
width?: number,
/// The window height.
height?: number,
/// The min window width.
min_width?: number,
/// The min window height.
min_height?: number,
/// The max window width.
max_width?: number,
/// The max window height.
max_height?: number,
/// Whether the window is resizable or not.
resizable?: boolean,
/// The window title.
title?: string,
/// Whether the window starts as fullscreen or not.
fullscreen?: boolean,
/// Whether the window will be initially hidden or focused.
focus?: boolean,
/// Whether the window is transparent or not.
///
/// Note that on `macOS` this requires the `macos-private-api` feature flag, enabled under `tauri.conf.json > tauri > macosPrivateApi`.
/// WARNING: Using private APIs on `macOS` prevents your application from being accepted for the `App Store`.
transparent?: boolean,
/// Whether the window is maximized or not.
maximized?: boolean,
/// Whether the window is visible or not.
visible?: boolean,
/// Whether the window should have borders and bars.
decorations?: boolean,
/// Whether the window should always be on top of other windows.
always_on_top?: boolean,
/// Whether or not the window icon should be added to the taskbar.
skip_taskbar?: boolean,
}
interface BuildConfig {
/// The binary used to build and run the application.
runner?: string,
/// The path or URL to use on development.
devPath?: string,
/// The path to the app's dist dir. This path must contain your index.html file.
distDir?: string,
/// A shell command to run before `tauri dev` kicks in.
///
/// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.
beforeDevCommand?: string,
/// A shell command to run before `tauri build` kicks in.
///
/// The TAURI_PLATFORM, TAURI_ARCH, TAURI_FAMILY, TAURI_PLATFORM_VERSION, TAURI_PLATFORM_TYPE and TAURI_DEBUG environment variables are set if you perform conditional compilation.
beforeBuildCommand?: string,
/// Features passed to `cargo` commands.
features?: string[],
/// Whether we should inject the Tauri API on `window.__TAURI__` or not.
withGlobalTauri?: boolean,
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
import React, {createContext} from 'react'
import {Helper, TauriLocalFSBackend} from "./lib/helper";
import {Configurator} from "./lib/acrn";
// 1. Use React createContext API to create ACRN Context
export const ACRNContext = createContext({
helper: () => {
},
configurator: () => {
}
})
// 2. Create Context Provider
export class ACRNProvider extends React.Component {
constructor(props) {
super(props);
let fsBackend = new TauriLocalFSBackend()
let helper = new Helper(fsBackend, fsBackend)
let configurator = new Configurator(helper)
this.state = {
helper: helper,
configurator: configurator
}
}
render() {
console.log(this.state)
return (
<ACRNContext.Provider value={this.state}>
{this.props.children}
</ACRNContext.Provider>
)
}
}
// 3. export Consumer
export const ACRNConsumer = ACRNContext.Consumer

View File

@ -0,0 +1,97 @@
a {
/* Browse for folder… */
font-family: Roboto, serif;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 19px;
color: #2A958D;
text-decoration: auto;
}
@media (min-width: 576px) {
.border-end-sm {
border-right: 1px solid #dee2e6 !important;
}
}
.btn-lg {
min-height: 50px;
border-radius: 30px;
font-family: Roboto, serif;
font-style: normal;
font-weight: bold;
line-height: 23px;
text-align: center;
text-transform: capitalize;
color: #FFFFFF;
}
.btn-primary {
background-color: #007B81;
border-color: #007B81;
}
.btn-primary:hover {
background-color: #004B50;
border-color: #004B50;
}
select {
height: 49px;
background-image: url("assets/images/dropdown_arrow.svg") !important;
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 12px;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
appearance: none;
}
select:focus {
border: 1px solid #69BFAD;
box-sizing: border-box;
box-shadow: 0 0 6px rgba(105, 191, 173, 0.37);
border-radius: 5px;
}
.banner-text {
max-width: 594px;
font-family: Roboto, serif;
font-style: normal;
font-weight: normal;
font-size: 28px;
line-height: 37px;
letter-spacing: -0.175px;
color: #FFFFFF;
}
.bg-gray {
background: #F5F5F5;
}
.text-right {
text-align: right;
}
table {
width: 100%;
}
.descInfoBtn {
color: gray;
}
.descInfoBtn:hover {
color: lightgray;
}
.rst-paragraph:last-child{
margin-bottom: 0;
}

View File

@ -0,0 +1,25 @@
import {ACRNProvider} from "./ACRNContext";
import {HashRouter, Routes, Route} from "react-router-dom";
import './App.css'
import Welcome from "./pages/Welcome/Welcome";
import Error from "./pages/Error/Error";
import Config from "./pages/Config/Config";
import "bootstrap/dist/js/bootstrap"
function App() {
return (
<ACRNProvider>
<HashRouter>
<Routes>
<Route path="/" element={<Welcome/>}/>
<Route path="/config" element={<Config/>}/>
<Route path="*" element={<Error/>}/>
</Routes>
</HashRouter>
</ACRNProvider>
)
}
export default App

View File

@ -0,0 +1,9 @@
@font-face {
font-family: 'Roboto';
src: url('Roboto-Regular.woff2') format('woff2'),
url('Roboto-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}

View File

@ -0,0 +1,3 @@
<svg width="11" height="19" viewBox="0 0 11 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.7425 19C6.973 19 6.2225 18.6372 5.7665 17.9674L0 9.50154L5.757 1.03572C6.4885 -0.0341323 7.961 -0.322528 9.0535 0.39381C10.146 1.11015 10.4405 2.55213 9.709 3.62198L5.7 9.50154L9.709 15.3811C10.4405 16.451 10.146 17.8929 9.0535 18.6093C8.6545 18.8698 8.189 19 7.7425 19Z" fill="#69BFAD"/>
</svg>

After

Width:  |  Height:  |  Size: 404 B

View File

@ -0,0 +1,3 @@
<svg width="18" height="9" viewBox="0 0 18 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 8.5947L0.75205 3.09606C-0.0247838 2.57592 -0.234191 1.52888 0.28595 0.75205C0.806091 -0.0247838 1.85313 -0.234191 2.62996 0.28595L9 4.53489L15.37 0.28595C16.1469 -0.234191 17.1939 -0.0247838 17.7141 0.75205C18.2342 1.52888 18.0248 2.57592 17.248 3.09606L9 8.5947Z" fill="#69BFAD"/>
</svg>

After

Width:  |  Height:  |  Size: 399 B

View File

@ -0,0 +1,78 @@
"""
public var board_xml
public function get_enum(source, selector)
enum = {
'type': 'dynamicEnum',
'function': 'get_enum',
'source': 'board_xml',
'selector': element['xs:annotation']['@acrn:options'],
'sorted': element['xs:annotation'].get('@acrn:options-sorted-by', None)
}
params defined before this document string in js side like
```js
let params = JSON.stringify({board_xml: boardXMLText, scenario_json: JSON.stringify(scenario_json)})
params = Base64.encode(params);
return runPyCode(`
params="${params}"
${dynamicScenario}
`)
```
"""
import json
from base64 import b64decode
import elementpath
import lxml.etree as etree
# local test var set
if 'params' not in globals():
# params = b64encode(open(r"board.xml",'rb').read())
raise NotImplementedError
# Main flow
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable
params: str = b64decode(params).decode('utf-8')
params: dict = json.loads(params)
board_xml = etree.XML(params["board_xml"])
scenario_json = params['scenario_json']
def get_enum(source, options):
elements = [str(x) for x in elementpath.select(source, options) if x]
elements = list(set(elements))
if not elements:
elements = ['']
return elements
def dynamic_enum(**kwargs):
# value from env
function, source = [globals()[kwargs[key]] for key in ['function', 'source']]
# value from given
selector, sorted_func = [kwargs[key] for key in ['selector', 'sorted']]
# get enum data
enum = function(source, selector)
if sorted_func:
enum = sorted(enum, key=eval(sorted_func))
return enum
def dynamic_enum_apply(obj):
# get json schema enum obj
if 'enum' in obj and isinstance(obj['enum'], dict):
enum_setting = obj['enum']
# check enum obj type
if enum_setting['type'] == 'dynamicEnum':
del enum_setting['type']
# replace json schema obj enum field data
obj['enum'] = dynamic_enum(**enum_setting)
return obj
data = json.loads(scenario_json, object_hook=dynamic_enum_apply)
json.dumps(data)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
.banner {
min-height: 50px;
background-image: url("images/top_pattern.png"), linear-gradient(90.29deg, #242357 0.08%, #6ABFAE 85.19%, #69BFAD 99.72%);
background-repeat: no-repeat;
background-position: 50%;
}

View File

@ -0,0 +1,9 @@
import React from "react";
export class Banner extends React.Component {
render() {
return (
<div className="banner">{this.props.children}</div>
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,5 @@
import './Banner.css'
import {Banner} from "./Banner";
export default Banner;

View File

@ -0,0 +1,45 @@
import {Button, Modal} from "react-bootstrap";
import {useState} from "react";
export default function Confirm(props) {
const [show, setShow] = useState(false);
const handleClose = (choice) => {
setShow(false);
props.callback(choice)
}
const handleShow = (e) => {
setShow(true);
e.preventDefault()
}
return (
<Modal show={show} onHide={() => handleClose('cancel')} size="lg">
<Modal.Header closeButton closeVariant="white" className="bg-primary text-white">
<Modal.Title>{props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="p-4">
<b>{props.content}</b>
</div>
</Modal.Body>
<Modal.Footer className="px-5 py-4 my-2">
<Button className="me-sm-2" variant="outline-primary" onClick={() => handleShow('cancel')}
size="lg" style={{width: '137px'}}>
Cancel
</Button>
<Button className="me-sm-2" variant="outline-primary" onClick={() => handleShow('no')} size="lg"
style={{width: '137px'}}>
{props.no ? props.no : "No"}
</Button>
<Button variant="primary" onClick={() => {
props.callback('yes')
}} size="lg" style={{width: '137px'}}>
{props.yes ? props.yes : "Yes"}
</Button>
</Modal.Footer>
</Modal>
);
}

View File

@ -0,0 +1,3 @@
import Confirm from "./Confirm";
export default Confirm

View File

@ -0,0 +1,25 @@
import {Component} from "react";
import {getVersion} from "@tauri-apps/api/app";
export default class Footer extends Component {
constructor(props) {
super(props);
this.state = {
version: "0.1.0"
}
}
componentDidMount = () => {
getVersion().then((version) => {
this.setState({version: version})
})
}
render = () => {
return <div className="pt-3">
<p className="text-center text-secondary">© Copyright Project ACRN, a Series of LF Projects, LLC.
- Version {this.state.version}</p>
</div>
}
}

View File

@ -0,0 +1,3 @@
import Footer from "./footer";
export default Footer

View File

@ -0,0 +1,44 @@
body {
-webkit-app-region: no-drag
}
.bg-navbar {
height: 80px;
background: #007B81;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
-webkit-user-select: none;
-webkit-app-region: drag;
}
.logo-text {
cursor: default;
font-family: Roboto, serif;
font-style: normal;
font-weight: bold;
font-size: 26px;
line-height: 30px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #9ADFD1;
}
.controlButtons {
margin-right: 30px;
width: 130px;
filter: drop-shadow(3px 3px 2px rgb(0 0 0 / 0.4));
}
.wmb {
cursor: pointer;
-webkit-app-region: no-drag;
}
.wmb:hover {
opacity: 0.7;
}
.btn-close {
-webkit-app-region: no-drag;
}

View File

@ -0,0 +1,39 @@
import React from "react";
import {Container} from "react-bootstrap";
import {Navbar as BootstrapNavbar} from "react-bootstrap";
import logo from "./images/ACRN_Logo.svg";
import {faWindowMaximize} from "@fortawesome/free-regular-svg-icons";
import {faClose, faMinus} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {windowHelper} from "../../lib/platform/tauri/tauri";
export class Navbar extends React.Component {
render() {
return (
<BootstrapNavbar bg="navbar" variant="dark" height="80">
<Container fluid data-tauri-drag-region>
<BootstrapNavbar.Brand>
<img
alt="ACRN" src={logo} height="38" data-tauri-drag-region="true"
className="d-inline-block align-self-center mx-3"
/>
<div className="d-inline align-bottom logo-text" data-tauri-drag-region="true">
Configurator
</div>
</BootstrapNavbar.Brand>
<div className="controlButtons d-flex justify-content-between align-items-center"
data-tauri-drag-region={true}>
<FontAwesomeIcon className="wmb" icon={faMinus} color="white" size="lg"
onClick={windowHelper.minimal}/>
<FontAwesomeIcon className="wmb" icon={faWindowMaximize} color="white" size="lg"
onClick={windowHelper.maxmal}/>
<FontAwesomeIcon className="wmb" icon={faClose} color="white" size="lg"
onClick={windowHelper.close}/>
</div>
</Container>
</BootstrapNavbar>
);
}
}

View File

@ -0,0 +1,15 @@
<svg width="150" height="38" viewBox="0 0 150 38" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_153_432)">
<path d="M49.4131 6.45779L36.9832 31.2792H39.7601L42.9943 24.8355C44.5379 22.3463 47.6728 20.8086 50.6057 20.8086C53.6503 20.8086 56.9164 22.4522 58.4068 25.0985H58.4085L61.5116 31.2809H64.2885L51.8604 6.45956H49.4131V6.45779ZM50.6057 18.3247C48.8655 18.3247 47.1766 18.7449 45.6685 19.5076L50.6359 9.61078L55.6209 19.5464C54.0951 18.7573 52.3814 18.3247 50.6057 18.3247Z" fill="white"/>
<path d="M117.389 16.773C117.389 10.8907 112.938 6.45602 107.033 6.45602H96.6782V31.2774H99.1699V8.93816H107.033C111.369 8.93816 114.898 12.4531 114.898 16.773C114.898 19.5058 113.503 21.9985 111.166 23.4391L110.583 23.7992L118.019 31.2792H121.517L114.421 24.1505C115.286 23.1354 117.389 20.3126 117.389 16.773Z" fill="white"/>
<path d="M147.133 6.45779V26.9751L130.367 6.45779H127.512V31.2792H130.004V9.92502L147.419 31.2792H149.625V6.45779H147.133Z" fill="white"/>
<path d="M87.1884 25.8012C85.294 27.8896 82.5347 29.0866 79.616 29.0866C74.0355 29.0866 69.497 24.5018 69.497 18.8685C69.497 13.2351 74.0372 8.65041 79.616 8.65041C82.5276 8.65041 85.2833 9.84382 87.1778 11.9234L87.5269 12.3065L89.4231 10.7548L89.0385 10.3328C86.5522 7.60177 83.3623 6.15768 79.8145 6.15768C72.779 6.15768 67.0549 11.8599 67.0549 18.8685C67.0549 25.8771 72.779 31.5793 79.8145 31.5793C83.3712 31.5793 86.5646 30.1299 89.0527 27.3883L89.4373 26.9663L87.5393 25.4181L87.1884 25.8012Z" fill="white"/>
<path d="M15.2955 15.3836C9.30207 15.3836 5.05067 17.0501 4.87345 17.1207L4.4375 17.2938V30.7884L15.2955 38L26.1535 30.7884V17.2955L25.7176 17.1207C25.5386 17.0501 21.2889 15.3836 15.2955 15.3836ZM9.14258 28.827L12.9474 34.7817L6.42586 30.4512L9.14258 28.827ZM8.49574 27.6036L5.82333 29.2013V21.2147L8.49574 27.6036ZM11.8239 21.7037L19.1393 27.1976H9.82663L8.47802 23.974L11.8239 21.7037ZM7.93574 22.6729L6.05194 18.1712C7.50865 17.698 10.9325 16.7641 15.2955 16.7641C15.7226 16.7641 16.1568 16.773 16.5945 16.7924L7.93574 22.6729ZM13.0325 20.8828L18.2249 17.3573L20.1565 26.2337L13.0325 20.8828ZM19.9616 28.5781L15.2937 35.8833L10.6259 28.5781H19.9616ZM21.4343 25.5911L19.5788 17.0625C21.8596 17.3785 23.591 17.864 24.5373 18.1712L21.4343 25.5911ZM24.1634 30.4512L17.6418 34.7817L21.4467 28.827L24.1634 30.4512ZM22.0935 27.6036L24.7659 21.2147V29.2013L22.0935 27.6036Z" fill="white"/>
<path d="M26.3343 3.56079L19.7117 2.73635L18.7973 0H11.7937L10.8775 2.73635L4.25495 3.56079L0 6.74026V11.3356L4.42862 15.7473L5.15698 15.4577C5.19951 15.4419 9.45269 13.7859 15.2955 13.7859C21.1436 13.7859 25.3915 15.4419 25.4322 15.4577L26.1606 15.7473L30.5892 11.3356V6.74026L26.3343 3.56079ZM28.7178 11.2296L25.8221 14.1143C25.5687 14.0207 24.9573 13.8053 24.0464 13.5547L26.8996 10.0203L28.7178 11.2296ZM29.191 7.43582V9.86853L27.3851 8.66983L26.7755 5.63159L29.191 7.43582ZM25.4056 5.91758L26.0064 8.90462L23.1922 12.3895L20.9451 9.25064L25.4056 5.91758ZM19.8251 10.0874L21.9535 13.0603C20.2256 12.7108 18.4623 12.4972 16.699 12.423L19.8251 10.0874ZM16.5838 6.08353L19.6373 4.13101L24.6348 4.75243L19.9545 8.24966L16.5838 6.08353ZM15.2902 5.25203L12.1783 3.25361L12.8003 1.39289H17.7871L18.4092 3.25361L15.2902 5.25203ZM15.2884 6.91326L18.7601 9.14118L15.2955 11.731L11.8185 9.13236L15.2884 6.91326ZM13.8902 12.423C12.1233 12.4972 10.36 12.7126 8.62863 13.0621L10.7481 10.0768L13.8902 12.423ZM5.95622 4.75419L10.9572 4.13101L13.9947 6.08177L10.6241 8.24084L5.95622 4.75419ZM9.6299 9.24005L7.39521 12.3877L4.58457 8.90639L5.18356 5.91935L9.6299 9.24005ZM3.68963 10.0221L6.5428 13.5564C5.63014 13.8071 5.01875 14.0225 4.7671 14.1161L1.8714 11.2296L3.68963 10.0221ZM3.81545 5.63159L3.20583 8.66983L1.39823 9.86853V7.43582L3.81545 5.63159Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_153_432">
<rect width="149.625" height="38" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,5 @@
import './Navbar.css'
import {Navbar} from "./Navbar";
export default Navbar;

View File

@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,36 @@
// global
$primary: #007B81;
// grid padding
$grid-gutter-width: 0;
// a
$link-decoration: auto;
$link-shade-percentage: 39%;
// text box fontsize
$input-font-size-lg: 1rem;
@import "../node_modules/bootstrap/scss/bootstrap";
.text-pre-line {
white-space: pre-line;
}
.btn.btn-outline-primary {
color: #007B81;
border-color: #007B81;
background-color: white;
}
.btn-outline-primary:hover {
color: #004B50;
border-color: #004B50;
background-color: white;
}
.btn-close-white {
opacity: 1;
filter: none;
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11.5195 8.99331L17.4644 3.04835C18.1607 2.3521 18.1607 1.21845 17.4644 0.522192C16.7682 -0.174064 15.6345 -0.174064 14.9383 0.522192L8.99331 6.46715L3.04835 0.531118C2.3521 -0.165138 1.21845 -0.165138 0.522192 0.531118C-0.174064 1.22737 -0.174064 2.36102 0.522192 3.05728L6.46715 9.00223L0.522192 14.9472C-0.174064 15.6434 -0.174064 16.7771 0.522192 17.4733C0.87032 17.8215 1.32556 18 1.78081 18C2.23605 18 2.6913 17.8215 3.03942 17.4733L8.98438 11.5284L14.9293 17.4733C15.2775 17.8215 15.7327 18 16.1879 18C16.6432 18 17.0984 17.8215 17.4466 17.4733C18.1428 16.7771 18.1428 15.6434 17.4466 14.9472L11.5195 8.99331Z' fill='white'/%3E%3C/svg%3E%0A");
}

View File

@ -0,0 +1,2 @@
pyodide
/pyodide.tar.bz2

View File

@ -0,0 +1,476 @@
import _ from "lodash";
import React, {Component} from "react";
import {path} from "@tauri-apps/api";
import scenario from '../assets/schema/scenario.json'
import {createSearchParams} from "react-router-dom";
import queryString from 'query-string'
import {invoke} from "@tauri-apps/api/tauri";
import {getNewSchema} from "./runpy";
function ThrowError(errMsg) {
alert(errMsg)
throw new Error(errMsg)
}
function NameTrans(name) {
return {
"recentlyWorkingFolders": "WorkingFolder",
"board": "Board",
"scenario": "Scenario",
}[name.replace("History", "")]
}
class EventBase extends Component {
constructor(props) {
super(props)
this.funRegister = {}
this.funRegisterID = 0
this.eventName = {
scenarioDataUpdate: 'scenarioDataUpdate'
}
}
register = (eventName, fun) => {
if (this.funRegister.hasOwnProperty(eventName)) {
this.funRegisterID++
this.funRegister[eventName][this.funRegisterID] = fun
return this.funRegisterID
}
}
unregister = (eventName, funRegisterID) => {
if (this.funRegister.hasOwnProperty(eventName)) {
if (this.funRegister[eventName].hasOwnProperty(funRegisterID)) {
delete this.funRegister[eventName][funRegisterID]
}
}
}
}
export class XMLLayer extends EventBase {
constructor(helper) {
super();
this.helper = helper
this.funRegister = {
onScenarioLoad: []
}
}
#parseXML = (XMLText) => {
return (new DOMParser()).parseFromString(XMLText, "text/xml")
}
#validateBoardXMLText = boardXMLText => {
// call inside
try {
let boardXML = this.#parseXML(boardXMLText, "text/xml");
return !!boardXML.querySelector("BIOS_INFO")
} catch (e) {
return false
}
}
#validateScenarioXMLText = (scenarioXMLText) => {
// call inside
try {
let scenarioXML = this.#parseXML(scenarioXMLText);
return !!scenarioXML.querySelector("acrn-config")
} catch (e) {
return false
}
}
loadBoard = async (boardXMLPath) => {
// call by program
let boardXMLText = await this.helper.read(await this.helper.resolveHome(boardXMLPath))
if (this.#validateBoardXMLText(boardXMLText)) {
let PCIDevices = this.getPCIDevice(boardXMLText)
return {boardXMLText, PCIDevices}
} else {
alert('Board XML Error!')
return false
}
}
loadScenario = async (scenarioXMLPath) => {
// call by program
// load scenario data from xml file
let scenarioXMLText = await this.helper.read(await this.helper.resolveHome(scenarioXMLPath))
if (!this.#validateScenarioXMLText(scenarioXMLText)) {
ThrowError('Scenario XML Error!')
}
return this.helper.convertXMLTextToObj(scenarioXMLText)['acrn-config']
}
getPCIDevice = (boardXMLText) => {
let pci_devices = this.#parseXML(boardXMLText).querySelector("PCI_DEVICE").textContent
// Remove Region line
pci_devices = pci_devices.replace(/Region.+\s+/g, '\n')
// Remove Space
pci_devices = pci_devices.replace(/[\n\t]+/g, '\n')
// Split by \n
pci_devices = pci_devices.split('\n')
return _.uniq(pci_devices)
}
saveBoard = (boardFileWritePath, boardData) => {
this.helper.save(boardFileWritePath, boardData)
}
saveScenario = (scenarioWritePath, scenarioData) => {
// call by program
console.log(scenarioData)
const scenarioXML = this.helper.convertObjToXML({'acrn-config': scenarioData})
console.log(scenarioXML)
// debugger
this.helper.save(scenarioWritePath, scenarioXML)
}
}
export class ProgramLayer extends EventBase {
constructor(helper, instanceOfXMLLayer) {
super()
this.helper = helper
this.vmID = 0
this.scenarioData = {}
this.initScenario()
this.xmlLayer = instanceOfXMLLayer
this.funRegister = {
scenarioDataUpdate: []
}
}
initScenario = () => {
this.vmID = 0
this.scenarioData = {
hv: {},
vm: {
PRE_LAUNCHED_VM: [],
SERVICE_VM: [],
POST_LAUNCHED_VM: [],
}
}
}
onScenarioDataUpdateEvent() {
this.funRegister.scenarioDataUpdate.map((f) => f())
}
newScenario = (preLaunchedVM, serviceVM, postLaunchedVM) => {
// call by view
this.initScenario()
for (let i = 0; i < preLaunchedVM; i++) {
this.addVM('PRE_LAUNCHED_VM')
}
for (let i = 0; i < serviceVM; i++) {
this.addVM('SERVICE_VM')
}
for (let i = 0; i < postLaunchedVM; i++) {
this.addVM('POST_LAUNCHED_VM')
}
this.onScenarioDataUpdateEvent()
}
addVM = (VMType, vmData = {}) => {
// call by inside and view
// if provide VMType and vmData at same time
// function will set VMType to vmData
let vm = vmData
let usedVMID = false;
// check VMType
let vmType = VMType;
if (!_.isString(vmType) || _.isEmpty(vmType)) {
// vmType is empty or not correct
if (vm.hasOwnProperty('load_order')) {
vmType = vm.load_order;
} else {
ThrowError('No VMType Set')
}
} else {
if (vm.hasOwnProperty('load_order') && vm.load_order !== vmType) {
console.warn("vmType and vmData provide load_order at same time, set vmData's load_order to vmType now")
}
vm.load_order = vmType
}
// check and validate vm name
if (!vm.hasOwnProperty('name') || !_.isString(vm.name) || _.isEmpty(vm.name)) {
vm.name = 'VM' + this.vmID
usedVMID = true
}
vm['@id'] = this.vmID
this.vmID++
// add to config
this.scenarioData.vm[vmType].push(vm)
this.onScenarioDataUpdateEvent()
}
deleteVM = (vmID) => {
// call by view
for (let vmType in this.scenarioData.vm) {
this.scenarioData.vm[vmType].map((vmConfig, vmIndex) => {
if (vmConfig['@id'] === vmID) {
this.scenarioData.vm[vmType].splice(vmIndex, 1)
}
})
}
this.onScenarioDataUpdateEvent()
}
loadBoard = async (WorkingFolder, boardXMLPath) => {
// call by view
let boardData = await this.xmlLayer.loadBoard(boardXMLPath)
if (boardData === false) {
return false
}
let {boardXMLText, PCIDevices} = boardData
// get new board file name
let newBoardFileName = await path.basename(boardXMLPath)
let cut = 0
if (_.endsWith(newBoardFileName.toLowerCase(), '.xml')) {
cut = '.xml'.length
}
if (_.endsWith(newBoardFileName.toLowerCase(), '.board.xml')) {
cut = '.board.xml'.length
}
newBoardFileName = newBoardFileName.slice(0, newBoardFileName.length - cut)
newBoardFileName = newBoardFileName + '.board.xml'
// new board file save path
const boardFileWritePath = await path.join(await this.helper.resolveHome(WorkingFolder), newBoardFileName)
// remove current working folder old Board File first
await this.removeOldBoardFile(WorkingFolder)
// save board file to working director
this.xmlLayer.saveBoard(boardFileWritePath, boardXMLText)
// get shownName
let shownName = await path.join(WorkingFolder, newBoardFileName)
console.log({shownName, boardXMLText, PCIDevices})
return {shownName, boardXMLText, PCIDevices}
}
loadScenario = async (scenarioXMLPath) => {
// call by view
let scenarioConfig = await this.xmlLayer.loadScenario(scenarioXMLPath)
this.initScenario()
this.scenarioData.hv = scenarioConfig.hv;
scenarioConfig.vm.map((vmConfig, index) => {
let vmType = vmConfig.load_order
if (!this.scenarioData.vm.hasOwnProperty(vmType)) {
try {
ThrowError('VM @id=' + index + ' VMType Does Not Exist')
} catch (e) {
}
} else {
//fix pci_devs is object issue
if (vmConfig.hasOwnProperty("pci_devs") && vmConfig.pci_devs.hasOwnProperty("pci_dev") && _.isString(vmConfig.pci_devs.pci_dev)) {
vmConfig.pci_devs.pci_dev = [vmConfig.pci_devs.pci_dev]
}
this.addVM(vmType, vmConfig)
}
})
this.onScenarioDataUpdateEvent()
}
getOriginScenarioData = () => {
// call by inside
let originScenario = _.cloneDeep(this.scenarioData)
originScenario.vm = originScenario.vm.PRE_LAUNCHED_VM.concat(
originScenario.vm.SERVICE_VM,
originScenario.vm.POST_LAUNCHED_VM
)
return originScenario
}
async removeOldBoardFile(WorkingFolder) {
let files = await this.helper.list(await this.helper.resolveHome(WorkingFolder))
files.map((filename) => {
if (_.endsWith(filename, '.board.xml')) {
this.helper.remove(filename)
}
})
}
saveScenario = async (WorkingFolder) => {
// call by view
let originScenarioData = this.getOriginScenarioData()
let filename = 'scenario.xml'
let scenarioWritePath = await path.join(await this.helper.resolveHome(WorkingFolder), filename)
this.xmlLayer.saveScenario(scenarioWritePath, originScenarioData)
// noinspection UnnecessaryLocalVariableJS
let shownPath = await path.join(WorkingFolder, filename)
return shownPath
}
}
export class Configurator extends EventBase {
// get data from Program
// convert it to view data
constructor(helper) {
super()
this.WorkingFolder = this.#getURLParam('WorkingFolder')
this.helper = helper
this.XMLLayer = new XMLLayer(this.helper)
this.programLayer = new ProgramLayer(this.helper, this.XMLLayer)
this.vmSchemas = this.Schemas()
this.hvSchema = this.vmSchemas.HV
delete this.vmSchemas.HV
this.updateSchema()
}
#getURLParam(key) {
let hash = location.hash
let params = hash.substring(hash.indexOf('?'))
params = queryString.parse(params)
return params[key]
}
#buildPageParams(url, queryParams = {}) {
let data = {pathname: url}
if (queryParams) {
data.search = createSearchParams(queryParams).toString()
}
return data;
}
settingWorkingFolder = (WorkingFolder) => {
this.WorkingFolder = WorkingFolder
return this.#buildPageParams('./config', {WorkingFolder})
}
updateSchema = () => {
let listingFunctions = [this.ivshmemEnum]
listingFunctions.forEach((func) => {
func()
this.programLayer.register("scenarioDataUpdate", func)
})
}
ivshmemEnum = () => {
let odata = this.programLayer.getOriginScenarioData()
let vmNames = odata.vm.map((vmData) => {
return vmData.name
})
if (vmNames.length === 0) {
vmNames = ['']
}
this.hvSchema.basic.definitions.VMNameType.enum = vmNames
}
loadBoard = async (boardXMLPath, callback) => {
let boardData = await this.programLayer.loadBoard(this.WorkingFolder, boardXMLPath)
if (boardData === false) {
await this.removeHistory('board', boardXMLPath)
return
}
let {shownName, boardXMLText, PCIDevices} = boardData
scenario.definitions.PCIDevsConfiguration.properties.pci_dev.items.enum = PCIDevices
Object.keys(this.vmSchemas).map((VMTypeKey) => {
Object.keys(this.vmSchemas[VMTypeKey]).map((configLevelKey) => {
this.vmSchemas[VMTypeKey][configLevelKey].definitions.PCIDevsConfiguration.properties.pci_dev.items.enum = PCIDevices
})
})
try {
let new_scenario = getNewSchema(boardXMLText)
scenario.definitions = new_scenario.definitions;
Object.keys(this.vmSchemas).map((VMTypeKey) => {
Object.keys(this.vmSchemas[VMTypeKey]).map((configLevelKey) => {
this.vmSchemas[VMTypeKey][configLevelKey].definitions = new_scenario.definitions
})
})
Object.keys(this.hvSchema).map((configLevelKey) => {
this.hvSchema[configLevelKey].definitions = new_scenario.definitions;
})
} catch (e) {
}
callback(shownName, boardXMLText)
}
async getHistory(key) {
let p = await invoke("get_history", {historyType: NameTrans(key)})
console.log("p", p);
return JSON.parse(p)
}
async addHistory(key, historyPath) {
await invoke("add_history", {historyType: NameTrans(key), path: historyPath})
return Promise.resolve()
}
async setHistory(key, history) {
return Promise.resolve()
}
async removeHistory(key, historyPath) {
let history = await this.getHistory(key)
let index = history.indexOf(historyPath);
if (index > -1) {
history.splice(index, 1);
}
return this.setHistory(key, history)
}
saveScenario = async () => {
let shownPath = await this.programLayer.saveScenario(this.WorkingFolder)
return this.addHistory('scenario', shownPath)
}
static #getSchema(prefix) {
let bo = scenario.definitions[prefix + "BasicConfigType"]
let basic = _.cloneDeep(scenario);
basic.type = bo.type;
basic.required = bo.required;
basic.properties = bo.properties;
let ao = scenario.definitions[prefix + "AdvancedConfigType"]
let advanced = _.cloneDeep(scenario);
advanced.type = ao.type;
advanced.required = ao.required;
advanced.properties = ao.properties;
return {basic, advanced}
}
Schemas() {
let prefixData = {
HV: 'HV',
PRE_LAUNCHED_VM: 'PreLaunchedVM',
SERVICE_VM: 'ServiceVM',
POST_LAUNCHED_VM: 'PostLaunchedVM'
}
for (let key in prefixData) {
prefixData[key] = Configurator.#getSchema(prefixData[key])
}
return prefixData
}
log() {
this.helper.log(...arguments)
}
}

View File

@ -0,0 +1,2 @@
*.js
*.js.map

View File

@ -0,0 +1,13 @@
import React from "react";
import {AddButtonProps} from "@rjsf/core";
import Button from "react-bootstrap/Button";
import {BsPlus} from "react-icons/bs";
const AddButton: React.FC<AddButtonProps> = props => (
<Button {...props} style={{width: "100%"}} className={`ml-1 ${props.className}`}>
<BsPlus/>
</Button>
);
export default AddButton;

View File

@ -0,0 +1,2 @@
export {default} from "./AddButton";
export * from "./AddButton";

View File

@ -0,0 +1,210 @@
import React from "react";
import {utils} from "@rjsf/core";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import {ArrayFieldTemplateProps, IdSchema} from "@rjsf/core";
import AddButton from "../AddButton/AddButton";
import IconButton from "../IconButton/IconButton";
const {isMultiSelect, getDefaultRegistry} = utils;
const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
const {schema, registry = getDefaultRegistry()} = props;
if (isMultiSelect(schema, registry.rootSchema)) {
return <DefaultFixedArrayFieldTemplate {...props} />;
} else {
return <DefaultNormalArrayFieldTemplate {...props} />;
}
};
type ArrayFieldTitleProps = {
TitleField: any;
idSchema: IdSchema;
title: string;
required: boolean;
};
const ArrayFieldTitle = ({
TitleField,
idSchema,
title,
required,
}: ArrayFieldTitleProps) => {
if (!title) {
return null;
}
const id = `${idSchema.$id}__title`;
return <TitleField id={id} title={title} required={required}/>;
};
type ArrayFieldDescriptionProps = {
DescriptionField: any;
idSchema: IdSchema;
description: string;
};
const ArrayFieldDescription = ({
DescriptionField,
idSchema,
description,
}: ArrayFieldDescriptionProps) => {
if (!description) {
return null;
}
const id = `${idSchema.$id}__description`;
return <DescriptionField id={id} description={description}/>;
};
// Used in the two templates
const DefaultArrayItem = (props: any) => {
const btnStyle = {
flex: 1,
paddingLeft: 6,
paddingRight: 6,
fontWeight: "bold",
};
return (
<div key={props.key}>
<Row className="mb-2 d-flex align-items-baseline">
<Col xs="9" lg="9">{props.children}</Col>
<Col xs="3" lg="3" className="px-3">
{props.hasToolbar && (
<div className="d-flex flex-row">
{(props.hasMoveUp || props.hasMoveDown) && (
<div className="m-0 p-0">
<IconButton
icon="arrow-up"
className="array-item-move-up"
tabIndex={-1}
style={btnStyle as any}
disabled={
props.disabled || props.readonly || !props.hasMoveUp
}
onClick={props.onReorderClick(props.index, props.index - 1)}
/>
</div>
)}
{(props.hasMoveUp || props.hasMoveDown) && (
<div className="m-0 p-0">
<IconButton
icon="arrow-down"
tabIndex={-1}
style={btnStyle as any}
disabled={
props.disabled || props.readonly || !props.hasMoveDown
}
onClick={props.onReorderClick(props.index, props.index + 1)}
/>
</div>
)}
{props.hasRemove && (
<div className="m-0 p-0">
<IconButton
icon="remove"
tabIndex={-1}
style={btnStyle as any}
disabled={props.disabled || props.readonly}
onClick={props.onDropIndexClick(props.index)}
/>
</div>
)}
</div>
)}
</Col>
</Row>
</div>
);
};
const DefaultFixedArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
return (
<fieldset className={props.className}>
<ArrayFieldTitle
key={`array-field-title-${props.idSchema.$id}`}
TitleField={props.TitleField}
idSchema={props.idSchema}
title={props.uiSchema["ui:title"] || props.title}
required={props.required}
/>
{(props.uiSchema["ui:description"] || props.schema.description) && (
<div
className="field-description"
key={`field-description-${props.idSchema.$id}`}>
{props.uiSchema["ui:description"] || props.schema.description}
</div>
)}
<div
className="row array-item-list"
key={`array-item-list-${props.idSchema.$id}`}>
{props.items && props.items.map(DefaultArrayItem)}
</div>
{props.canAdd && (
<AddButton
className="array-item-add"
onClick={props.onAddClick}
disabled={props.disabled || props.readonly}
/>
)}
</fieldset>
);
};
const DefaultNormalArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
return (
<div>
<Row className="p-0 m-0">
<Col className="p-0 m-0">
<ArrayFieldTitle
key={`array-field-title-${props.idSchema.$id}`}
TitleField={props.TitleField}
idSchema={props.idSchema}
title={props.uiSchema["ui:title"] || props.title}
required={props.required}
/>
{(props.uiSchema["ui:description"] || props.schema.description) && (
<ArrayFieldDescription
key={`array-field-description-${props.idSchema.$id}`}
DescriptionField={props.DescriptionField}
idSchema={props.idSchema}
description={
props.uiSchema["ui:description"] || props.schema.description
}
/>
)}
<Container fluid key={`array-item-list-${props.idSchema.$id}`} className="p-0 m-0">
{props.items && props.items.map(p => DefaultArrayItem(p))}
{props.canAdd && (
<Container className="">
<Row className="mt-2">
<Col xs={9}/>
<Col xs={3} className="py-4 col-lg-3 col-3"> <AddButton
className="array-item-add"
onClick={props.onAddClick}
disabled={props.disabled || props.readonly}
/></Col>
</Row>
</Container>
)}
</Container></Col>
</Row>
</div>
);
};
export default ArrayFieldTemplate;

View File

@ -0,0 +1,2 @@
export {default} from "./ArrayFieldTemplate";
export * from "./ArrayFieldTemplate";

View File

@ -0,0 +1,50 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
import Form from "react-bootstrap/Form";
const CheckboxWidget = (props: WidgetProps) => {
const {
id,
value,
required,
disabled,
readonly,
label,
schema,
autofocus,
onChange,
onBlur,
onFocus,
} = props;
const _onChange = ({
target: {checked},
}: React.FocusEvent<HTMLInputElement>) => onChange(checked);
const _onBlur = ({
target: {checked},
}: React.FocusEvent<HTMLInputElement>) => onBlur(id, checked);
const _onFocus = ({
target: {checked},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, checked);
const desc = label || schema.description;
return (
<Form.Group className={`checkbox ${disabled || readonly ? "disabled" : ""}`}>
<Form.Check
id={id}
label={desc}
checked={typeof value === "undefined" ? false : value}
required={required}
disabled={disabled || readonly}
autoFocus={autofocus}
onChange={_onChange}
type="checkbox"
onBlur={_onBlur}
onFocus={_onFocus}
/>
</Form.Group>
);
};
export default CheckboxWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./CheckboxWidget";
export * from "./CheckboxWidget";

View File

@ -0,0 +1,103 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
const selectValue = (value: any, selected: any, all: any) => {
const at = all.indexOf(value);
const updated = selected.slice(0, at).concat(value, selected.slice(at));
// As inserting values at predefined index positions doesn't work with empty
// arrays, we need to reorder the updated selection to match the initial order
return updated.sort((a: any, b: any) => all.indexOf(a) > all.indexOf(b));
};
const deselectValue = (value: any, selected: any) => {
return selected.filter((v: any) => v !== value);
};
const CheckboxesWidget = ({
schema,
label,
id,
disabled,
options,
value,
autofocus,
readonly,
required,
onChange,
onBlur,
onFocus,
}: WidgetProps) => {
const {enumOptions, enumDisabled, inline} = options;
const _onChange = (option: any) => ({
target: {checked},
}: React.ChangeEvent<HTMLInputElement>) => {
const all = (enumOptions as any).map(({value}: any) => value);
if (checked) {
onChange(selectValue(option.value, value, all));
} else {
onChange(deselectValue(option.value, value));
}
};
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: {value},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
return (
<>
<Form.Label htmlFor={id}>{label || schema.title}</Form.Label>
<Form.Group>
{(enumOptions as any).map((option: any, index: number) => {
const checked = value.indexOf(option.value) !== -1;
const itemDisabled =
enumDisabled && (enumDisabled as any).indexOf(option.value) != -1;
return inline ? (
<Form key={index}>
<Form.Check
required={required}
inline
className="bg-transparent border-0"
custom
checked={checked}
type={"checkbox"}
id={`${id}_${index}`}
label={option.label}
autoFocus={autofocus && index === 0}
onChange={_onChange(option)}
onBlur={_onBlur}
onFocus={_onFocus}
disabled={disabled || itemDisabled || readonly}
/>
</Form>
) : (
<Form key={index}>
<Form.Check
custom
required={required}
checked={checked}
className="bg-transparent border-0"
type={"checkbox"}
id={`${id}_${index}`}
label={option.label}
autoFocus={autofocus && index === 0}
onChange={_onChange(option)}
onBlur={_onBlur}
onFocus={_onFocus}
disabled={disabled || itemDisabled || readonly}
/>
</Form>
);
})}
</Form.Group>
</>
);
};
export default CheckboxesWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./CheckboxesWidget";
export * from "./CheckboxesWidget";

View File

@ -0,0 +1,10 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const ColorWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return <TextWidget {...props} type="color"/>;
};
export default ColorWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./ColorWidget";
export * from "./ColorWidget";

View File

@ -0,0 +1,24 @@
import React from "react";
import {utils, WidgetProps} from "@rjsf/core";
const {localToUTC, utcToLocal} = utils;
const DateTimeWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
const value = utcToLocal(props.value);
const onChange = (value: any) => {
props.onChange(localToUTC(value));
};
return (
<TextWidget
{...props}
type="datetime-local"
value={value}
onChange={onChange}
/>
);
};
export default DateTimeWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./DateTimeWidget";
export * from "./DateTimeWidget";

View File

@ -0,0 +1,15 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const DateWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return (
<TextWidget
{...props}
type="date"
/>
);
};
export default DateWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./DateWidget";
export * from "./DateWidget";

View File

@ -0,0 +1,18 @@
import React from "react";
import {FieldProps} from "@rjsf/core";
export interface DescriptionFieldProps extends Partial<FieldProps> {
description?: string;
}
const DescriptionField = ({description}: Partial<FieldProps>) => {
if (description) {
return <div>
<div className="mb-3" dangerouslySetInnerHTML={{__html: description}}/>
</div>;
}
return null;
};
export default DescriptionField;

View File

@ -0,0 +1,2 @@
export {default} from "./DescriptionField";
export * from "./DescriptionField";

View File

@ -0,0 +1,10 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const EmailWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return <TextWidget {...props} type="email"/>;
};
export default EmailWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./EmailWidget";
export * from "./EmailWidget";

View File

@ -0,0 +1,25 @@
import React from "react";
import Card from "react-bootstrap/Card";
import ListGroup from "react-bootstrap/ListGroup";
import {ErrorListProps} from "@rjsf/core";
const ErrorList = ({errors}: ErrorListProps) => (
<Card border="danger" className="mb-4">
<Card.Header className="alert-danger">Errors</Card.Header>
<Card.Body className="p-0">
<ListGroup>
{errors.map((error, i: number) => {
return (
<ListGroup.Item key={i} className="border-0">
<span>{error.stack}</span>
</ListGroup.Item>
);
})}
</ListGroup>
</Card.Body>
</Card>
);
export default ErrorList;

View File

@ -0,0 +1,2 @@
export {default} from "./ErrorList";
export * from "./ErrorList";

View File

@ -0,0 +1,171 @@
import React from "react";
import {FieldTemplateProps} from "@rjsf/core";
import Form from "react-bootstrap/Form";
import ListGroup from "react-bootstrap/ListGroup";
import WrapIfAdditional from "./WrapIfAdditional";
import {OverlayTrigger, Popover} from "react-bootstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faCircleInfo, faCircleExclamation} from "@fortawesome/free-solid-svg-icons";
import _ from "lodash";
const FieldTemplate = (
{
id,
children,
displayLabel,
rawErrors = [],
rawHelp,
rawDescription,
classNames,
disabled,
label,
onDropPropertyClick,
onKeyChange,
readonly,
required,
schema,
uiSchema
}: FieldTemplateProps) => {
let descLabel = (uiSchema.hasOwnProperty("ui:descLabel") && uiSchema["ui:descLabel"] === true)
let descWithChildren
let showLabel = _.endsWith(id, 'IVSHMEM_VM_0_VBDF') || _.endsWith(id, 'IVSHMEM_VM_0_VM_NAME') || (
id.indexOf('vuart_connection') > 0 && (_.endsWith(id, 'vm_name') || _.endsWith(id, 'io_port'))
)
let dlva = uiSchema.hasOwnProperty("ui:descLabelAli") && uiSchema["ui:descLabelAli"] === 'V'
if (displayLabel && rawDescription) {
let desc
const icon = rawErrors.length > 0 ? faCircleExclamation : faCircleInfo;
if (descLabel) {
if (dlva) {
desc = <OverlayTrigger
trigger={["hover", "focus"]}
key="top"
placement="top"
overlay={
<Popover id={`popover-positioned-top`}>
<Popover.Body>
<Form.Text className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
dangerouslySetInnerHTML={{__html: rawDescription}}/>
</Popover.Body>
</Popover>
}>
<div className="mx-2 py-2 row">
<Form.Label
className={(showLabel ? 'col-12 ps-4' : 'd-none') + " col-form-label " + (rawErrors.length > 0 ? "text-danger" : "")}>
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
</div>
</OverlayTrigger>
descWithChildren = <div className="row">
{desc}
<div className="col-12">
{children}
</div>
</div>
} else {
desc = <OverlayTrigger
trigger={["hover", "focus"]}
key="top"
placement="top"
overlay={
<Popover id={`popover-positioned-top`}>
<Popover.Body>
<Form.Text className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
dangerouslySetInnerHTML={{__html: rawDescription}}/>
</Popover.Body>
</Popover>
}>
<div className="col-4"
style={{marginTop: (uiSchema.hasOwnProperty("ui:descLabelMT") && uiSchema["ui:descLabelMT"] ? '54px' : 'auto')}}>
<Form.Label
className={(showLabel ? 'col-12 ps-4' : 'd-none') + " col-form-label " + (rawErrors.length > 0 ? "text-danger" : "")}>
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
</div>
</OverlayTrigger>
descWithChildren = <div className="row">
{desc}
<div className="col-8">
{children}
</div>
</div>
}
} else {
desc = <OverlayTrigger
trigger={["hover", "focus"]}
key="top"
placement="top"
overlay={
<Popover id={`popover-positioned-top`}>
<Popover.Body>
<Form.Text className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
dangerouslySetInnerHTML={{__html: rawDescription}}/>
</Popover.Body>
</Popover>
}>
<div className="mx-2 py-2 descInfoBtn">
<FontAwesomeIcon
icon={icon}
color={rawErrors.length > 0 ? "red" : ""}
/>
</div>
</OverlayTrigger>
descWithChildren = <div className="d-flex">
{desc}
<div className="w-100">
{children}
</div>
</div>
}
} else {
descWithChildren = children
}
return (
<WrapIfAdditional
classNames={classNames}
disabled={disabled}
id={id}
label={label}
onDropPropertyClick={onDropPropertyClick}
onKeyChange={onKeyChange}
readonly={readonly}
required={required}
schema={schema}
>
<Form.Group style={uiSchema.hasOwnProperty('ui:style') ? uiSchema['ui:style'] : {}}>
{descWithChildren}
{rawErrors.length > 0 && (
<ListGroup as="ul" className={descLabel ? (dlva ? 'ps-4' : ' col-8 offset-4') : ''}>
{rawErrors.map((error: string) => {
return (
<ListGroup.Item as="li" key={error} className="border-0 m-0 p-0">
<small className="m-0 text-danger">
{error}
</small>
</ListGroup.Item>
);
})}
</ListGroup>
)}
{rawHelp && (
<Form.Text
className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
id={id}>
{rawHelp}
</Form.Text>
)}
</Form.Group>
</WrapIfAdditional>
);
};
export default FieldTemplate;

View File

@ -0,0 +1,83 @@
import React from "react";
import {utils} from "@rjsf/core";
import {JSONSchema7} from "json-schema";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Form from "react-bootstrap/Form";
import IconButton from "../IconButton/IconButton";
const {ADDITIONAL_PROPERTY_FLAG} = utils;
type WrapIfAdditionalProps = {
children: React.ReactElement;
classNames: string;
disabled: boolean;
id: string;
label: string;
onDropPropertyClick: (index: string) => (event?: any) => void;
onKeyChange: (index: string) => (event?: any) => void;
readonly: boolean;
required: boolean;
schema: JSONSchema7;
};
const WrapIfAdditional = ({
children,
disabled,
id,
label,
onDropPropertyClick,
onKeyChange,
readonly,
required,
schema,
}: WrapIfAdditionalProps) => {
const keyLabel = `${label} Key`; // i18n ?
const additional = schema.hasOwnProperty(ADDITIONAL_PROPERTY_FLAG);
if (!additional) {
return children;
}
const handleBlur = ({target}: React.FocusEvent<HTMLInputElement>) =>
onKeyChange(target.value);
return (
<Row key={`${id}-key`}>
<Col xs={5}>
<Form.Group>
<Form.Label>{keyLabel}</Form.Label>
<Form.Control
required={required}
defaultValue={label}
disabled={disabled || readonly}
id={`${id}-key`}
name={`${id}-key`}
onBlur={!readonly ? handleBlur : undefined}
type="text"
/>
</Form.Group>
</Col>
<Col xs={5}>
{children}
</Col>
<Col xs={2} className="py-4">
<IconButton
// @ts-ignore
block={"true"}
className="w-100"
variant="danger"
icon="remove"
tabIndex={-1}
disabled={disabled || readonly}
onClick={onDropPropertyClick(label)}
/>
</Col>
</Row>
);
};
export default WrapIfAdditional;

View File

@ -0,0 +1,2 @@
export {default} from "./FieldTemplate";
export * from "./FieldTemplate";

View File

@ -0,0 +1,7 @@
import DescriptionField from "../DescriptionField/DescriptionField";
import TitleField from "../TitleField/TitleField";
export default {
DescriptionField,
TitleField,
};

View File

@ -0,0 +1,2 @@
export {default} from "./Fields";
export * from "./Fields";

View File

@ -0,0 +1,10 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const FileWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return <TextWidget {...props} type="file"/>;
};
export default FileWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./FileWidget";
export * from "./FileWidget";

View File

@ -0,0 +1,10 @@
import {withTheme, FormProps} from "@rjsf/core";
import Theme from "../Theme";
import {StatelessComponent} from "react";
const Form:
| React.ComponentClass<FormProps<any>>
| StatelessComponent<FormProps<any>> = withTheme(Theme);
export default Form;

View File

@ -0,0 +1,2 @@
export {default} from "./Form";
export * from "./Form";

View File

@ -0,0 +1,33 @@
import React from "react";
import Button, {ButtonProps} from "react-bootstrap/Button";
import {IoIosRemove} from "react-icons/io";
import {GrAdd} from "react-icons/gr";
import {AiOutlineArrowUp, AiOutlineArrowDown} from "react-icons/ai";
const mappings: any = {
remove: <IoIosRemove/>,
plus: <GrAdd/>,
"arrow-up": <AiOutlineArrowUp/>,
"arrow-down": <AiOutlineArrowDown/>,
};
type IconButtonProps = ButtonProps & {
icon: string;
variant?: ButtonProps["variant"];
className?: string;
tabIndex?: number;
style?: any;
disabled?: any;
onClick?: any;
};
const IconButton = (props: IconButtonProps) => {
const {icon, className, ...otherProps} = props;
return (
<Button {...otherProps} variant={props.variant || "light"} size="sm">
{mappings[icon]}
</Button>
);
};
export default IconButton;

View File

@ -0,0 +1,2 @@
export {default} from "./IconButton";
export * from "./IconButton";

View File

@ -0,0 +1,81 @@
import React from "react";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import {ObjectFieldTemplateProps} from "@rjsf/core";
import {utils} from "@rjsf/core";
import AddButton from "../AddButton/AddButton";
const {canExpand} = utils;
const ObjectFieldTemplate = (
{
DescriptionField,
description,
TitleField,
title,
properties,
required,
uiSchema,
idSchema,
schema,
formData,
onAddClick,
disabled,
readonly,
}: ObjectFieldTemplateProps) => {
let content = properties.map((element: any, index: number) => {
return (
<Col
key={index}
style={{marginBottom: "10px"}}
className={element.hidden ? "d-none" : undefined}
xs={(uiSchema[element.name] && uiSchema[element.name]['ui:grid']) ?
uiSchema[element.name]['ui:grid'] :
12
}>
{element.content}
</Col>
);
})
let expand = canExpand(schema, uiSchema, formData) ? (
<Row>
<Col xs={{offset: 9, span: 3}} className="py-4">
<AddButton
onClick={onAddClick(schema)}
disabled={disabled || readonly}
className="object-property-expand"
/>
</Col>
</Row>
) : null
let container = <Container fluid className="p-0">
<Row>{content}</Row>
{expand}
</Container>
return (
<>
{(uiSchema["ui:title"] || title) && (
<TitleField
id={`${idSchema.$id}-title`}
title={uiSchema["ui:title"] || title}
required={required}
/>
)}
{description && (
<DescriptionField
id={`${idSchema.$id}-description`}
description={description}
/>
)}
{container}
</>
);
};
export default ObjectFieldTemplate;

View File

@ -0,0 +1,2 @@
export {default} from "./ObjectFieldTemplate";
export * from "./ObjectFieldTemplate";

View File

@ -0,0 +1,55 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
const PasswordWidget = ({
id,
required,
readonly,
disabled,
value,
label,
onFocus,
onBlur,
onChange,
options,
autofocus,
schema,
rawErrors = [],
}: WidgetProps) => {
const _onChange = ({
target: {value},
}: React.ChangeEvent<HTMLInputElement>) =>
onChange(value === "" ? options.emptyValue : value);
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: {value},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
return (
<Form.Group className="mb-0">
<Form.Label className={rawErrors.length > 0 ? "text-danger" : ""}>
{label || schema.title}
{(label || schema.title) && required ? "*" : null}
</Form.Label>
<Form.Control
id={id}
autoFocus={autofocus}
className={rawErrors.length > 0 ? "is-invalid" : ""}
required={required}
disabled={disabled}
readOnly={readonly}
type="password"
value={value ? value : ""}
onFocus={_onFocus}
onBlur={_onBlur}
onChange={_onChange}
/>
</Form.Group>
);
};
export default PasswordWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./PasswordWidget";
export * from "./PasswordWidget";

View File

@ -0,0 +1,70 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
const RadioWidget = (
{
id,
schema,
options,
value,
required,
disabled,
readonly,
label,
onChange,
onBlur,
onFocus,
uiSchema,
}: WidgetProps) => {
const {enumOptions, enumDisabled} = options;
const _onChange = ({target: {value},}: React.ChangeEvent<HTMLInputElement>) =>
onChange(schema.type == "boolean" ? value !== "false" : value);
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({target: {value},}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
const inline = Boolean(options && options.inline);
return (
<Form.Group className="mb-0 row">
<Form.Label className="col-sm-4 col-form-label">
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
<div className="col-sm-8">
{(enumOptions as any).map((option: any, i: number) => {
const itemDisabled =
Array.isArray(enumDisabled) &&
enumDisabled.indexOf(option.value) !== -1;
const checked = option.value == value;
// @ts-ignore
const radio = (
<Form.Check
inline={inline}
label={option.label}
id={option.label}
key={i}
name={id}
type="radio"
disabled={disabled || itemDisabled || readonly}
checked={checked}
required={required}
value={option.value}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
/>
);
return radio;
})}
</div>
</Form.Group>
);
};
export default RadioWidget;

View File

@ -0,0 +1,2 @@
export {default} from "./RadioWidget";
export * from "./RadioWidget";

View File

@ -0,0 +1,57 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {utils} from "@rjsf/core";
import {WidgetProps} from "@rjsf/core";
const {rangeSpec} = utils;
const RangeWidget = ({
value,
readonly,
disabled,
onBlur,
onFocus,
options,
schema,
onChange,
required,
label,
id,
uiSchema,
}: WidgetProps) => {
let sliderProps = {value, label, id, ...rangeSpec(schema)};
const _onChange = ({
target: {value},
}: React.ChangeEvent<HTMLInputElement>) =>
onChange(value === "" ? options.emptyValue : value);
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: {value},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
return (
<Form.Group className="mb-0">
<Form.Label>
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
<Form.Control
type="range"
required={required}
disabled={disabled}
readOnly={readonly}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
{...sliderProps}
/>
<span className="range-view">{value}</span>
</Form.Group>
);
};
export default RangeWidget;

Some files were not shown because too many files have changed in this diff Show More