-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provide a wrapper Rust crate around the static-built UI. (#13)
- Loading branch information
Bob McWhirter
authored
May 9, 2024
1 parent
b17ed55
commit 453a502
Showing
5 changed files
with
203 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,6 @@ npm-debug.log* | |
|
||
# Intellij IDEA | ||
.idea/ | ||
|
||
crate/target | ||
crate/Cargo.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "trustify-ui" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
static-files = "0.2.1" | ||
serde_json = "1.0.117" | ||
tera = "1.19.1" | ||
base64 = "0.22.1" | ||
serde = { version = "1.0.201", features = ["derive"] } | ||
|
||
[build-dependencies] | ||
static-files = "0.2.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use static_files::resource_dir; | ||
use std::path::Path; | ||
use std::process::{Command, ExitStatus}; | ||
use std::{fs, io}; | ||
|
||
static UI_DIR: &str = "../"; | ||
static UI_DIR_SRC: &str = "../src"; | ||
static UI_DIST_DIR: &str = "../client/dist"; | ||
static STATIC_DIR: &str = "target/generated"; | ||
|
||
#[cfg(windows)] | ||
static NPM_CMD: &str = "npm.cmd"; | ||
#[cfg(not(windows))] | ||
static NPM_CMD: &str = "npm"; | ||
|
||
fn main() { | ||
println!("Build Trustify - build.rs!"); | ||
|
||
println!("cargo:rerun-if-changed={}", UI_DIR_SRC); | ||
|
||
let build_ui_status = install_ui_deps() | ||
.and_then(|_| build_ui()) | ||
.and_then(|_| copy_dir_all(UI_DIST_DIR, STATIC_DIR)); | ||
|
||
match build_ui_status { | ||
Ok(_) => println!("UI built successfully"), | ||
Err(_) => panic!("Error while building UI"), | ||
} | ||
|
||
resource_dir("./target/generated").build().unwrap(); | ||
} | ||
|
||
fn install_ui_deps() -> io::Result<ExitStatus> { | ||
if !Path::new("../node_modules").exists() { | ||
println!("Installing node dependencies..."); | ||
Command::new(NPM_CMD) | ||
.args(["clean-install", "--ignore-scripts"]) | ||
.current_dir(UI_DIR) | ||
.status() | ||
} else { | ||
Ok(ExitStatus::default()) | ||
} | ||
} | ||
|
||
fn build_ui() -> io::Result<ExitStatus> { | ||
if !Path::new(STATIC_DIR).exists() || Path::new(STATIC_DIR).read_dir()?.next().is_none() { | ||
println!("Building UI..."); | ||
Command::new(NPM_CMD) | ||
.args(["run", "build", "--dev"]) | ||
.current_dir(UI_DIR) | ||
.status() | ||
} else { | ||
Ok(ExitStatus::default()) | ||
} | ||
} | ||
|
||
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> { | ||
fs::create_dir_all(&dst)?; | ||
for entry in fs::read_dir(src)? { | ||
let entry = entry?; | ||
let ty = entry.file_type()?; | ||
if ty.is_dir() { | ||
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; | ||
} else { | ||
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; | ||
} | ||
} | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
include!(concat!(env!("OUT_DIR"), "/generated.rs")); | ||
|
||
use base64::prelude::BASE64_STANDARD; | ||
use base64::Engine; | ||
use serde::Serialize; | ||
use serde_json::Value; | ||
use static_files::resource::new_resource; | ||
use static_files::Resource; | ||
use std::collections::HashMap; | ||
use std::str::from_utf8; | ||
use std::sync::OnceLock; | ||
|
||
#[derive(Serialize, Clone, Default)] | ||
pub struct UI { | ||
#[serde(rename(serialize = "VERSION"))] | ||
pub version: String, | ||
|
||
#[serde(rename(serialize = "AUTH_REQUIRED"))] | ||
pub auth_required: String, | ||
|
||
#[serde(rename(serialize = "OIDC_SERVER_URL"))] | ||
pub oidc_server_url: String, | ||
|
||
#[serde(rename(serialize = "OIDC_CLIENT_ID"))] | ||
pub oidc_client_id: String, | ||
|
||
#[serde(rename(serialize = "OIDC_SCOPE"))] | ||
pub oidc_scope: String, | ||
|
||
#[serde(rename(serialize = "ANALYTICS_ENABLED"))] | ||
pub analytics_enabled: String, | ||
|
||
#[serde(rename(serialize = "ANALYTICS_WRITE_KEY"))] | ||
pub analytics_write_key: String, | ||
} | ||
|
||
pub fn trustify_ui_resources() -> HashMap<&'static str, Resource> { | ||
let mut resources = generate(); | ||
if let Some(index) = resources.get("index.html.ejs") { | ||
resources.insert( | ||
"index.html", | ||
new_resource(index.data, index.modified, "text/html"), | ||
); | ||
} | ||
|
||
resources | ||
} | ||
|
||
pub fn generate_index_html( | ||
ui: &UI, | ||
template_file: String, | ||
branding_file_content: String, | ||
) -> tera::Result<String> { | ||
let template = template_file | ||
.replace("<%=", "{{") | ||
.replace("%>", "}}") | ||
.replace( | ||
"?? branding.application.title", | ||
"| default(value=branding.application.title)", | ||
) | ||
.replace( | ||
"?? branding.application.title", | ||
"| default(value=branding.application.title)", | ||
); | ||
|
||
let env_json = serde_json::to_string(&ui)?; | ||
let env_base64 = BASE64_STANDARD.encode(env_json.as_bytes()); | ||
|
||
let branding: Value = serde_json::from_str(&branding_file_content)?; | ||
|
||
let mut context = tera::Context::new(); | ||
context.insert("_env", &env_base64); | ||
context.insert("branding", &branding); | ||
|
||
tera::Tera::one_off(&template, &context, true) | ||
} | ||
|
||
pub fn trustify_ui(ui: &UI) -> HashMap<&'static str, Resource> { | ||
let mut resources = generate(); | ||
|
||
let template_file = resources.get("index.html.ejs"); | ||
let branding_file_content = resources.get("branding/strings.json"); | ||
|
||
let index_html = INDEX_HTML.get_or_init(|| { | ||
if let (Some(template_file), Some(branding_file_content)) = | ||
(template_file, branding_file_content) | ||
{ | ||
let template_file = | ||
from_utf8(template_file.data).expect("cannot interpret template as UTF-8"); | ||
let branding_file_content = | ||
from_utf8(branding_file_content.data).expect("cannot interpret branding as UTF-8"); | ||
generate_index_html( | ||
ui, | ||
template_file.to_string(), | ||
branding_file_content.to_string(), | ||
) | ||
.expect("cannot generate index.html") | ||
} else { | ||
"Something went wrong".to_string() | ||
} | ||
}); | ||
|
||
resources.insert("", new_resource(index_html.as_bytes(), 0, "text/html")); | ||
|
||
resources | ||
} | ||
|
||
static INDEX_HTML: OnceLock<String> = OnceLock::new(); |