Compare commits
	
		
			1 Commits 
		
	
	
		
			master
			...
			4-update-c
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								 | 
						1124495540 | 4 years ago | 
				 199 changed files with 3256 additions and 12864 deletions
			
			
		@ -1,15 +0,0 @@ | 
				
			|||||||
[build] | 
					 | 
				
			||||||
rustflags = [ | 
					 | 
				
			||||||
    "-L/usr/lib/musl/lib", | 
					 | 
				
			||||||
    "-L/home/ortem/src/rust/unki/static/lib", | 
					 | 
				
			||||||
    "--remap-path-prefix=/home/ortem/src/rust/unki=src", | 
					 | 
				
			||||||
    "--remap-path-prefix=/home/ortem/.cargo=cargo" | 
					 | 
				
			||||||
] | 
					 | 
				
			||||||
target = "x86_64-unknown-linux-musl" | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[env] | 
					 | 
				
			||||||
STATIC_PREFIX = "static" | 
					 | 
				
			||||||
PQ_LIB_STATIC_X86_64_UNKNOWN_LINUX_MUSL = "true" | 
					 | 
				
			||||||
PG_CONFIG_X86_64_UNKNOWN_LINUX_GNU = { value = "static/bin/pg_config", relative = true } | 
					 | 
				
			||||||
OPENSSL_STATIC = "true" | 
					 | 
				
			||||||
OPENSSL_DIR = { value = "static", relative = true } | 
					 | 
				
			||||||
@ -1,6 +1,4 @@ | 
				
			|||||||
POSTGRES_HOST=u_db | 
					DB_HOST=u_db | 
				
			||||||
POSTGRES_DATABASE=u_db | 
					DB_NAME=u_db | 
				
			||||||
POSTGRES_USER=u_ser | 
					DB_USER=postgres | 
				
			||||||
POSTGRES_PORT=5432 | 
					RUST_BACKTRACE=1 | 
				
			||||||
RUST_BACKTRACE=1 | 
					 | 
				
			||||||
U_SERVER=u_server | 
					 | 
				
			||||||
@ -1,3 +1,4 @@ | 
				
			|||||||
# remove '.sample' to activate | 
					# remove '.sample' to activate | 
				
			||||||
ADMIN_AUTH_TOKEN= | 
					ADMIN_AUTH_TOKEN= | 
				
			||||||
POSTGRES_PASSWORD= | 
					DB_PASSWORD= | 
				
			||||||
 | 
					POSTGRES_PASSWORD=${DB_PASSWORD} | 
				
			||||||
@ -1,14 +1,10 @@ | 
				
			|||||||
target/ | 
					target/ | 
				
			||||||
 | 
					**/*.rs.bk | 
				
			||||||
.idea/ | 
					.idea/ | 
				
			||||||
data/ | 
					data/ | 
				
			||||||
certs/ | 
					 | 
				
			||||||
static/ | 
					 | 
				
			||||||
.vscode/ | 
					 | 
				
			||||||
release/ | 
					 | 
				
			||||||
**/node_modules/ | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**/*.rs.bk | 
					 | 
				
			||||||
**/*.pyc | 
					**/*.pyc | 
				
			||||||
 | 
					certs/* | 
				
			||||||
*.log | 
					*.log | 
				
			||||||
echoer | 
					echoer | 
				
			||||||
.env.private | 
					.env.private | 
				
			||||||
 | 
					*.lock | 
				
			||||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						@ -1,35 +1,17 @@ | 
				
			|||||||
[workspace] | 
					[workspace] | 
				
			||||||
members = [ | 
					members = [ | 
				
			||||||
    "bin/migrator", | 
					 | 
				
			||||||
    "bin/u_agent", | 
					    "bin/u_agent", | 
				
			||||||
    "bin/u_panel", | 
					    "bin/u_panel", | 
				
			||||||
    "bin/u_run", | 
					    "bin/u_run", | 
				
			||||||
    "bin/u_server", | 
					    "bin/u_server", | 
				
			||||||
    "lib/u_lib", | 
					    "lib/u_lib", | 
				
			||||||
    "integration-tests", | 
					    "lib/u_api_proc_macro", | 
				
			||||||
 | 
					    "integration" | 
				
			||||||
] | 
					] | 
				
			||||||
resolver = "2" | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[workspace.dependencies] | 
					 | 
				
			||||||
anyhow = "=1.0.63" | 
					 | 
				
			||||||
deadpool-diesel = "0.4.0" | 
					 | 
				
			||||||
diesel = { version = "2", features = ["postgres", "uuid"] } | 
					 | 
				
			||||||
mime_guess = "2.0" | 
					 | 
				
			||||||
openssl = "0.10" | 
					 | 
				
			||||||
reqwest = { version = "0.11", features = ["json"] } | 
					 | 
				
			||||||
serde = { version = "1.0", features = ["derive"] } | 
					 | 
				
			||||||
serde_json = "1.0" | 
					 | 
				
			||||||
thiserror = "=1.0.31" | 
					 | 
				
			||||||
tokio = { version = "1.11", features = ["macros"] } | 
					 | 
				
			||||||
tracing = "0.1.35" | 
					 | 
				
			||||||
tracing-appender = "0.2.0" | 
					 | 
				
			||||||
tracing-subscriber = { version = "0.3.0", features = ["env-filter"]} | 
					 | 
				
			||||||
uuid = "1.2.1" | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.release] | 
					[profile.release] | 
				
			||||||
panic = "abort" | 
					panic = "abort" | 
				
			||||||
strip = "symbols" | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.dev] | 
					[profile.dev] | 
				
			||||||
debug = true  # Добавляет флаг `-g` для компилятора; | 
					debug = true  # Добавляет флаг `-g` для компилятора; | 
				
			||||||
opt-level = 0 | 
					opt-level = 0 | 
				
			||||||
@ -0,0 +1,26 @@ | 
				
			|||||||
 | 
					.PHONY: _pre_build debug release run clean unit integration test | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CARGO=./scripts/cargo_musl.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clean: | 
				
			||||||
 | 
						${CARGO} clean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_pre_build: | 
				
			||||||
 | 
						docker build -t unki/musllibs ./muslrust
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					debug: _pre_build | 
				
			||||||
 | 
						${CARGO} build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					release: _pre_build | 
				
			||||||
 | 
						${CARGO} build --release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					run: build | 
				
			||||||
 | 
						${CARGO} run
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unit: | 
				
			||||||
 | 
						${CARGO} test --lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					integration: | 
				
			||||||
 | 
						cd ./integration && ./integration_tests.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test: unit integration | 
				
			||||||
@ -1,96 +0,0 @@ | 
				
			|||||||
# i need to preserve --release in args, not to pass cargo make -p release
 | 
					 | 
				
			||||||
# due to cargo failing to parse "" argument
 | 
					 | 
				
			||||||
env_scripts = ['''
 | 
					 | 
				
			||||||
#!@duckscript
 | 
					 | 
				
			||||||
args = array ${1} ${2} ${3} ${4} ${5} ${6} ${7}
 | 
					 | 
				
			||||||
set_env PROFILE_OVERRIDE debug | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
for arg in ${args} | 
					 | 
				
			||||||
    e = eq ${arg} "--release"
 | 
					 | 
				
			||||||
    if ${e}
 | 
					 | 
				
			||||||
        set_env PROFILE_OVERRIDE release
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
end | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
profile = get_env PROFILE_OVERRIDE
 | 
					 | 
				
			||||||
echo PROFILE_OVERRIDE=${profile}
 | 
					 | 
				
			||||||
'''] | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[config] | 
					 | 
				
			||||||
default_to_workspace = false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[env] | 
					 | 
				
			||||||
TARGET = "x86_64-unknown-linux-musl"
 | 
					 | 
				
			||||||
CARGO = "cargo"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.build_static_libs] | 
					 | 
				
			||||||
script = "./scripts/build_musl_libs.sh"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.build_frontend] | 
					 | 
				
			||||||
script = '''
 | 
					 | 
				
			||||||
cd ./bin/u_panel/src/gui/fe | 
					 | 
				
			||||||
ng build | 
					 | 
				
			||||||
''' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.clean] | 
					 | 
				
			||||||
command = "${CARGO}"
 | 
					 | 
				
			||||||
args = ["clean"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.cargo_build] | 
					 | 
				
			||||||
dependencies = ["build_static_libs", "build_frontend"]
 | 
					 | 
				
			||||||
command = "${CARGO}"
 | 
					 | 
				
			||||||
args = ["build", "--target", "${TARGET}", "${@}"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.cargo_update] | 
					 | 
				
			||||||
command = "${CARGO}"
 | 
					 | 
				
			||||||
args = ["update"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.release_tasks] | 
					 | 
				
			||||||
condition = { env = { PROFILE_OVERRIDE = "release"} }
 | 
					 | 
				
			||||||
script = '''
 | 
					 | 
				
			||||||
BINS=$(ls ./target/${TARGET}/${PROFILE_OVERRIDE}/u_* -1 | grep -v ".d")
 | 
					 | 
				
			||||||
echo "Stripping..." | 
					 | 
				
			||||||
strip $BINS | 
					 | 
				
			||||||
echo "Packing..." | 
					 | 
				
			||||||
upx -9 $BINS | 
					 | 
				
			||||||
''' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.build] | 
					 | 
				
			||||||
dependencies = ["cargo_build", "release_tasks"]
 | 
					 | 
				
			||||||
clear = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.run] | 
					 | 
				
			||||||
disabled = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.run_front] | 
					 | 
				
			||||||
script = '''
 | 
					 | 
				
			||||||
cd ./bin/u_panel/src/gui/fe | 
					 | 
				
			||||||
ng serve | 
					 | 
				
			||||||
''' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.unit-tests] | 
					 | 
				
			||||||
command = "${CARGO}"
 | 
					 | 
				
			||||||
args = ["test", "--target", "${TARGET}", "--lib", "--", "${@}"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.ut] | 
					 | 
				
			||||||
alias = "unit-tests"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.integration-tests] | 
					 | 
				
			||||||
dependencies = ["cargo_update"]
 | 
					 | 
				
			||||||
script = '''
 | 
					 | 
				
			||||||
[[ ! -d "./target/${TARGET}/${PROFILE_OVERRIDE}" ]] && echo 'No target folder. Build project first' && exit 1 | 
					 | 
				
			||||||
cd ./integration-tests | 
					 | 
				
			||||||
bash integration_tests.sh ${@} | 
					 | 
				
			||||||
''' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.it] | 
					 | 
				
			||||||
alias = "integration-tests"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.test] | 
					 | 
				
			||||||
dependencies = ["unit", "integration-tests"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.gen_schema] | 
					 | 
				
			||||||
script = './scripts/gen_schema.sh'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[tasks.deploy] | 
					 | 
				
			||||||
script = './scripts/deploy.sh'
 | 
					 | 
				
			||||||
@ -1,13 +0,0 @@ | 
				
			|||||||
[package] | 
					 | 
				
			||||||
name = "migrator" | 
					 | 
				
			||||||
version = "0.1.0" | 
					 | 
				
			||||||
edition = "2021" | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[dependencies] | 
					 | 
				
			||||||
diesel = { workspace = true, features = ["postgres", "serde_json"] } | 
					 | 
				
			||||||
diesel_migrations = { version = "2.0.0", features = ["postgres"] } | 
					 | 
				
			||||||
openssl = { workspace = true } | 
					 | 
				
			||||||
u_lib = { path = "../../lib/u_lib" } | 
					 | 
				
			||||||
url = "2.3.1" | 
					 | 
				
			||||||
@ -1,140 +0,0 @@ | 
				
			|||||||
use super::query_helper; | 
					 | 
				
			||||||
use diesel::dsl::sql; | 
					 | 
				
			||||||
use diesel::sql_types::Bool; | 
					 | 
				
			||||||
use diesel::*; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::env; | 
					 | 
				
			||||||
use std::error::Error; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type DatabaseResult<T> = Result<T, Box<dyn Error>>; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub enum Backend { | 
					 | 
				
			||||||
    Pg, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Backend { | 
					 | 
				
			||||||
    pub fn for_url(database_url: &str) -> Self { | 
					 | 
				
			||||||
        match database_url { | 
					 | 
				
			||||||
            _ if database_url.starts_with("postgres://") | 
					 | 
				
			||||||
                || database_url.starts_with("postgresql://") => | 
					 | 
				
			||||||
            { | 
					 | 
				
			||||||
                    Backend::Pg | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            _ => panic!( | 
					 | 
				
			||||||
                "At least one backend must be specified for use with this crate. \ | 
					 | 
				
			||||||
                 You may omit the unneeded dependencies in the following command. \n\n \ | 
					 | 
				
			||||||
                 ex. `cargo install diesel_cli --no-default-features --features mysql postgres sqlite` \n" | 
					 | 
				
			||||||
            ), | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub enum InferConnection { | 
					 | 
				
			||||||
    Pg(PgConnection), | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl InferConnection { | 
					 | 
				
			||||||
    pub fn establish(database_url: &str) -> DatabaseResult<Self> { | 
					 | 
				
			||||||
        match Backend::for_url(database_url) { | 
					 | 
				
			||||||
            Backend::Pg => PgConnection::establish(database_url).map(InferConnection::Pg), | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        .map_err(Into::into) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn reset_database() -> DatabaseResult<()> { | 
					 | 
				
			||||||
    drop_database(&database_url())?; | 
					 | 
				
			||||||
    setup_database() | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn setup_database() -> DatabaseResult<()> { | 
					 | 
				
			||||||
    let database_url = database_url(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    create_database_if_needed(&database_url)?; | 
					 | 
				
			||||||
    Ok(()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn drop_database_command() -> DatabaseResult<()> { | 
					 | 
				
			||||||
    drop_database(&database_url()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Creates the database specified in the connection url. It returns an error
 | 
					 | 
				
			||||||
/// it was unable to create the database.
 | 
					 | 
				
			||||||
fn create_database_if_needed(database_url: &str) -> DatabaseResult<()> { | 
					 | 
				
			||||||
    match Backend::for_url(database_url) { | 
					 | 
				
			||||||
        Backend::Pg => { | 
					 | 
				
			||||||
            if PgConnection::establish(database_url).is_err() { | 
					 | 
				
			||||||
                let (database, postgres_url) = change_database_of_url(database_url, "postgres"); | 
					 | 
				
			||||||
                println!("Creating database: {}", database); | 
					 | 
				
			||||||
                let mut conn = PgConnection::establish(&postgres_url)?; | 
					 | 
				
			||||||
                query_helper::create_database(&database).execute(&mut conn)?; | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Drops the database specified in the connection url. It returns an error
 | 
					 | 
				
			||||||
/// if it was unable to drop the database.
 | 
					 | 
				
			||||||
fn drop_database(database_url: &str) -> DatabaseResult<()> { | 
					 | 
				
			||||||
    match Backend::for_url(database_url) { | 
					 | 
				
			||||||
        Backend::Pg => { | 
					 | 
				
			||||||
            let (database, postgres_url) = change_database_of_url(database_url, "postgres"); | 
					 | 
				
			||||||
            let mut conn = PgConnection::establish(&postgres_url)?; | 
					 | 
				
			||||||
            if pg_database_exists(&mut conn, &database)? { | 
					 | 
				
			||||||
                println!("Dropping database: {}", database); | 
					 | 
				
			||||||
                query_helper::drop_database(&database) | 
					 | 
				
			||||||
                    .if_exists() | 
					 | 
				
			||||||
                    .execute(&mut conn)?; | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    Ok(()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table! { | 
					 | 
				
			||||||
    pg_database (datname) { | 
					 | 
				
			||||||
        datname -> Text, | 
					 | 
				
			||||||
        datistemplate -> Bool, | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn pg_database_exists(conn: &mut PgConnection, database_name: &str) -> QueryResult<bool> { | 
					 | 
				
			||||||
    use self::pg_database::dsl::*; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pg_database | 
					 | 
				
			||||||
        .select(datname) | 
					 | 
				
			||||||
        .filter(datname.eq(database_name)) | 
					 | 
				
			||||||
        .filter(datistemplate.eq(false)) | 
					 | 
				
			||||||
        .get_result::<String>(conn) | 
					 | 
				
			||||||
        .optional() | 
					 | 
				
			||||||
        .map(|x| x.is_some()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Returns true if the `__diesel_schema_migrations` table exists in the
 | 
					 | 
				
			||||||
/// database we connect to, returns false if it does not.
 | 
					 | 
				
			||||||
pub fn schema_table_exists(database_url: &str) -> DatabaseResult<bool> { | 
					 | 
				
			||||||
    match InferConnection::establish(database_url).unwrap() { | 
					 | 
				
			||||||
        InferConnection::Pg(mut conn) => select(sql::<Bool>( | 
					 | 
				
			||||||
            "EXISTS \ | 
					 | 
				
			||||||
             (SELECT 1 \ | 
					 | 
				
			||||||
             FROM information_schema.tables \ | 
					 | 
				
			||||||
             WHERE table_name = '__diesel_schema_migrations')", | 
					 | 
				
			||||||
        )) | 
					 | 
				
			||||||
        .get_result(&mut conn), | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    .map_err(Into::into) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn database_url() -> String { | 
					 | 
				
			||||||
    env::var("DATABASE_URL").unwrap() | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn change_database_of_url(database_url: &str, default_database: &str) -> (String, String) { | 
					 | 
				
			||||||
    let base = ::url::Url::parse(database_url).unwrap(); | 
					 | 
				
			||||||
    let database = base.path_segments().unwrap().last().unwrap().to_owned(); | 
					 | 
				
			||||||
    let mut new_url = base.join(default_database).unwrap(); | 
					 | 
				
			||||||
    new_url.set_query(base.query()); | 
					 | 
				
			||||||
    (database, new_url.into()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,88 +0,0 @@ | 
				
			|||||||
// due to linking errors
 | 
					 | 
				
			||||||
extern crate openssl; | 
					 | 
				
			||||||
// don't touch anything
 | 
					 | 
				
			||||||
extern crate diesel; | 
					 | 
				
			||||||
// in this block
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub mod database; | 
					 | 
				
			||||||
pub mod query_helper; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use diesel::migration::Migration; | 
					 | 
				
			||||||
use diesel::{migration, pg::PgConnection, Connection}; | 
					 | 
				
			||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; | 
					 | 
				
			||||||
use std::error::Error; | 
					 | 
				
			||||||
use u_lib::config::DBEnv; | 
					 | 
				
			||||||
use u_lib::db::generate_postgres_url; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn main() -> Result<(), Box<dyn Error + Send + Sync>> { | 
					 | 
				
			||||||
    let action = action::parse_command_line()?; | 
					 | 
				
			||||||
    let dbconfig = DBEnv::load()?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    database::setup_database().unwrap(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let conn = PgConnection::establish(&generate_postgres_url(&dbconfig))?; | 
					 | 
				
			||||||
    run(action, conn) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn run(action: action::Action, mut conn: PgConnection) -> migration::Result<()> { | 
					 | 
				
			||||||
    use action::Action::*; | 
					 | 
				
			||||||
    match action { | 
					 | 
				
			||||||
        ListPending => { | 
					 | 
				
			||||||
            let list = conn.pending_migrations(MIGRATIONS)?; | 
					 | 
				
			||||||
            if list.is_empty() { | 
					 | 
				
			||||||
                println!("No pending migrations."); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            for mig in list { | 
					 | 
				
			||||||
                println!("Pending migration: {}", mig.name()); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        MigrateUp => { | 
					 | 
				
			||||||
            let list = conn.run_pending_migrations(MIGRATIONS)?; | 
					 | 
				
			||||||
            if list.is_empty() { | 
					 | 
				
			||||||
                println!("No pending migrations."); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            for mig in list { | 
					 | 
				
			||||||
                println!("Applied migration: {}", mig); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        MigrateDown => { | 
					 | 
				
			||||||
            let mig = conn.revert_last_migration(MIGRATIONS)?; | 
					 | 
				
			||||||
            println!("Reverted migration: {}", mig); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    Ok(()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mod action { | 
					 | 
				
			||||||
    pub enum Action { | 
					 | 
				
			||||||
        ListPending, | 
					 | 
				
			||||||
        MigrateUp, | 
					 | 
				
			||||||
        MigrateDown, | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    impl TryFrom<&str> for Action { | 
					 | 
				
			||||||
        type Error = (); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        fn try_from(value: &str) -> Result<Self, Self::Error> { | 
					 | 
				
			||||||
            match value { | 
					 | 
				
			||||||
                "" | "list" => Ok(Action::ListPending), | 
					 | 
				
			||||||
                "up" => Ok(Action::MigrateUp), | 
					 | 
				
			||||||
                "down" => Ok(Action::MigrateDown), | 
					 | 
				
			||||||
                _ => Err(()), | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn parse_command_line() -> Result<Action, String> { | 
					 | 
				
			||||||
        let action_str = std::env::args().nth(1).unwrap_or_default(); | 
					 | 
				
			||||||
        let action = action_str.as_str().try_into().map_err(|_| { | 
					 | 
				
			||||||
            format!( | 
					 | 
				
			||||||
                "unrecognized command line argument: {} (expected 'up', 'down', 'list')", | 
					 | 
				
			||||||
                action_str | 
					 | 
				
			||||||
            ) | 
					 | 
				
			||||||
        })?; | 
					 | 
				
			||||||
        Ok(action) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,82 +0,0 @@ | 
				
			|||||||
use diesel::backend::Backend; | 
					 | 
				
			||||||
use diesel::query_builder::*; | 
					 | 
				
			||||||
use diesel::result::QueryResult; | 
					 | 
				
			||||||
use diesel::RunQueryDsl; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone)] | 
					 | 
				
			||||||
pub struct DropDatabaseStatement { | 
					 | 
				
			||||||
    db_name: String, | 
					 | 
				
			||||||
    if_exists: bool, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl DropDatabaseStatement { | 
					 | 
				
			||||||
    pub fn new(db_name: &str) -> Self { | 
					 | 
				
			||||||
        DropDatabaseStatement { | 
					 | 
				
			||||||
            db_name: db_name.to_owned(), | 
					 | 
				
			||||||
            if_exists: false, | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn if_exists(self) -> Self { | 
					 | 
				
			||||||
        DropDatabaseStatement { | 
					 | 
				
			||||||
            if_exists: true, | 
					 | 
				
			||||||
            ..self | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<DB: Backend> QueryFragment<DB> for DropDatabaseStatement { | 
					 | 
				
			||||||
    fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> { | 
					 | 
				
			||||||
        out.push_sql("DROP DATABASE "); | 
					 | 
				
			||||||
        if self.if_exists { | 
					 | 
				
			||||||
            out.push_sql("IF EXISTS "); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        out.push_identifier(&self.db_name)?; | 
					 | 
				
			||||||
        Ok(()) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<Conn> RunQueryDsl<Conn> for DropDatabaseStatement {} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl QueryId for DropDatabaseStatement { | 
					 | 
				
			||||||
    type QueryId = (); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const HAS_STATIC_QUERY_ID: bool = false; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone)] | 
					 | 
				
			||||||
pub struct CreateDatabaseStatement { | 
					 | 
				
			||||||
    db_name: String, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl CreateDatabaseStatement { | 
					 | 
				
			||||||
    pub fn new(db_name: &str) -> Self { | 
					 | 
				
			||||||
        CreateDatabaseStatement { | 
					 | 
				
			||||||
            db_name: db_name.to_owned(), | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<DB: Backend> QueryFragment<DB> for CreateDatabaseStatement { | 
					 | 
				
			||||||
    fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> { | 
					 | 
				
			||||||
        out.push_sql("CREATE DATABASE "); | 
					 | 
				
			||||||
        out.push_identifier(&self.db_name)?; | 
					 | 
				
			||||||
        Ok(()) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl<Conn> RunQueryDsl<Conn> for CreateDatabaseStatement {} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl QueryId for CreateDatabaseStatement { | 
					 | 
				
			||||||
    type QueryId = (); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const HAS_STATIC_QUERY_ID: bool = false; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn drop_database(db_name: &str) -> DropDatabaseStatement { | 
					 | 
				
			||||||
    DropDatabaseStatement::new(db_name) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn create_database(db_name: &str) -> CreateDatabaseStatement { | 
					 | 
				
			||||||
    CreateDatabaseStatement::new(db_name) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,167 +1,86 @@ | 
				
			|||||||
 | 
					// TODO:
 | 
				
			||||||
 | 
					// поддержка питона
 | 
				
			||||||
 | 
					// резолв адреса управляющего сервера через DoT
 | 
				
			||||||
 | 
					// кроссплатформенность (реализовать интерфейс для винды и никсов)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[macro_use] | 
					#[macro_use] | 
				
			||||||
extern crate log; | 
					extern crate log; | 
				
			||||||
 | 
					extern crate env_logger; | 
				
			||||||
 | 
					
 | 
				
			||||||
use tokio::runtime::Builder; | 
					use std::env; | 
				
			||||||
use tokio::time::{sleep, Duration}; | 
					use tokio::time::{sleep, Duration}; | 
				
			||||||
use u_lib::models::PreparedJob; | 
					 | 
				
			||||||
use u_lib::scheduler::SCHEDULER; | 
					 | 
				
			||||||
use u_lib::u_runner::{IdentifiableFuture, URunner}; | 
					 | 
				
			||||||
use u_lib::{ | 
					use u_lib::{ | 
				
			||||||
    api::HttpClient, | 
					    api::ClientHandler, | 
				
			||||||
 | 
					    builder::JobBuilder, | 
				
			||||||
    cache::JobCache, | 
					    cache::JobCache, | 
				
			||||||
    config::{get_self_id, EndpointsEnv, AGENT_ITERATION_INTERVAL}, | 
					    executor::pop_completed, | 
				
			||||||
    error::ErrChan, | 
					    models::{AssignedJob, ExecResult}, | 
				
			||||||
    logging::init_logger, | 
					    UID, | 
				
			||||||
    messaging::Reportable, | 
					    //daemonize
 | 
				
			||||||
    models::AssignedJobById, | 
					 | 
				
			||||||
}; | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn process_request(assigned_jobs: Vec<AssignedJobById>, client: &HttpClient) { | 
					#[macro_export] | 
				
			||||||
    for asgn_job in assigned_jobs { | 
					macro_rules! retry_until_ok { | 
				
			||||||
        if !JobCache::contains(asgn_job.job_id) { | 
					    ( $body:expr ) => { | 
				
			||||||
            info!("Fetching job: {}", &asgn_job.job_id); | 
					        loop { | 
				
			||||||
            let mut fetched_job = loop { | 
					            match $body { | 
				
			||||||
                //todo: use payload cache
 | 
					                Ok(r) => break r, | 
				
			||||||
                match client.get_full_job(asgn_job.job_id).await { | 
					                Err(e) => error!("{:?}", e), | 
				
			||||||
                    Ok(result) => break result, | 
					 | 
				
			||||||
                    Err(err) => { | 
					 | 
				
			||||||
                        debug!("{:?} \nretrying...", err); | 
					 | 
				
			||||||
                        sleep(AGENT_ITERATION_INTERVAL).await; | 
					 | 
				
			||||||
                    } | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
            }; | 
					            }; | 
				
			||||||
            if let Some(payload) = &mut fetched_job.payload { | 
					            sleep(Duration::from_secs(5)).await; | 
				
			||||||
                if let Err(e) = payload.maybe_split_payload() { | 
					 | 
				
			||||||
                    ErrChan::send(e, "pay").await; | 
					 | 
				
			||||||
                    continue; | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            JobCache::insert(fetched_job); | 
					 | 
				
			||||||
        } | 
					        } | 
				
			||||||
 | 
					    }; | 
				
			||||||
        let job = match JobCache::get(asgn_job.job_id).as_deref() { | 
					 | 
				
			||||||
            Some(job) => job.clone(), | 
					 | 
				
			||||||
            None => continue, | 
					 | 
				
			||||||
        }; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        info!("Scheduling job {}", job.meta.id.to_string()); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let schedule = match job.meta.schedule.clone() { | 
					 | 
				
			||||||
            Some(sched) => { | 
					 | 
				
			||||||
                if sched.is_empty() { | 
					 | 
				
			||||||
                    None | 
					 | 
				
			||||||
                } else { | 
					 | 
				
			||||||
                    match sched.as_str().try_into() { | 
					 | 
				
			||||||
                        Ok(s) => Some(s), | 
					 | 
				
			||||||
                        Err(err) => { | 
					 | 
				
			||||||
                            ErrChan::send(err, "sch").await; | 
					 | 
				
			||||||
                            continue; | 
					 | 
				
			||||||
                        } | 
					 | 
				
			||||||
                    } | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            None => None, | 
					 | 
				
			||||||
        }; | 
					 | 
				
			||||||
        SCHEDULER | 
					 | 
				
			||||||
            .add_job(schedule, PreparedJob { job, ids: asgn_job }) | 
					 | 
				
			||||||
            .await; | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn error_reporting(client: HttpClient) { | 
					pub async fn process_request(job_requests: Vec<AssignedJob>, client: &ClientHandler) { | 
				
			||||||
    while let Some(err) = ErrChan::recv().await { | 
					    if job_requests.len() > 0 { | 
				
			||||||
        let _ = client.report([Reportable::Error(err.clone())]).await; | 
					        for jr in &job_requests { | 
				
			||||||
    } | 
					            if !JobCache::contains(&jr.job_id) { | 
				
			||||||
} | 
					                info!("Fetching job: {}", &jr.job_id); | 
				
			||||||
 | 
					                let fetched_job = retry_until_ok!(client.get_jobs(Some(jr.job_id)).await) | 
				
			||||||
async fn agent_loop(client: HttpClient) { | 
					                    .pop() | 
				
			||||||
    let self_id = get_self_id(); | 
					                    .unwrap(); | 
				
			||||||
 | 
					                JobCache::insert(fetched_job); | 
				
			||||||
    match client.get_personal_jobs(self_id).await { | 
					            } | 
				
			||||||
        Ok(jobs) => { | 
					 | 
				
			||||||
            process_request(jobs, &client).await; | 
					 | 
				
			||||||
        } | 
					        } | 
				
			||||||
        Err(err) => ErrChan::send(err, "pro").await, | 
					        info!( | 
				
			||||||
    } | 
					            "Scheduling jobs: {}", | 
				
			||||||
 | 
					            job_requests | 
				
			||||||
    let result: Vec<Reportable> = URunner::pop_completed() | 
					                .iter() | 
				
			||||||
        .await | 
					                .map(|j| j.job_id.to_string()) | 
				
			||||||
        .into_iter() | 
					                .collect::<Vec<String>>() | 
				
			||||||
        .map(|result| match result { | 
					                .join(", ") | 
				
			||||||
            Ok(r) => Reportable::Assigned(r), | 
					        ); | 
				
			||||||
            Err(e) => Reportable::Error(e), | 
					        let mut builder = JobBuilder::from_request(job_requests); | 
				
			||||||
        }) | 
					        let errors = builder.pop_errors(); | 
				
			||||||
        .collect(); | 
					        if errors.len() > 0 { | 
				
			||||||
 | 
					            error!( | 
				
			||||||
    if !result.is_empty() { | 
					                "Some errors encountered: \n{}", | 
				
			||||||
        if let Err(err) = client.report(result).await { | 
					                errors | 
				
			||||||
            ErrChan::send(err, "rep").await; | 
					                    .iter() | 
				
			||||||
 | 
					                    .map(|j| j.to_string()) | 
				
			||||||
 | 
					                    .collect::<Vec<String>>() | 
				
			||||||
 | 
					                    .join("\n") | 
				
			||||||
 | 
					            ); | 
				
			||||||
        } | 
					        } | 
				
			||||||
 | 
					        builder.unwrap_one().spawn().await; | 
				
			||||||
    } | 
					    } | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn run_forever() -> ! { | 
					pub async fn run_forever() { | 
				
			||||||
    let env = EndpointsEnv::load(); | 
					    //daemonize();
 | 
				
			||||||
 | 
					    env_logger::init(); | 
				
			||||||
    if cfg!(debug_assertions) { | 
					    let arg_ip = env::args().nth(1); | 
				
			||||||
        let logfile_uid = format!( | 
					    let instance = ClientHandler::new(arg_ip.as_deref()); | 
				
			||||||
            "u_agent-{}", | 
					    info!("Connecting to the server"); | 
				
			||||||
            get_self_id() | 
					    loop { | 
				
			||||||
                .hyphenated() | 
					        let job_requests: Vec<AssignedJob> = | 
				
			||||||
                .to_string() | 
					            retry_until_ok!(instance.get_personal_jobs(Some(*UID)).await).into_builtin_vec(); | 
				
			||||||
                .split("-") | 
					        process_request(job_requests, &instance).await; | 
				
			||||||
                .next() | 
					        let result: Vec<ExecResult> = pop_completed().await.into_iter().collect(); | 
				
			||||||
                .unwrap() | 
					        if result.len() > 0 { | 
				
			||||||
        ); | 
					            retry_until_ok!(instance.report(&result).await); | 
				
			||||||
        init_logger(Some(&logfile_uid)); | 
					        } | 
				
			||||||
    } else { | 
					        sleep(Duration::from_secs(5)).await; | 
				
			||||||
        #[cfg(unix)] | 
					 | 
				
			||||||
        u_lib::unix::daemonize() | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					 | 
				
			||||||
    info!("Starting agent {}", get_self_id()); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Builder::new_multi_thread() | 
					 | 
				
			||||||
        .enable_all() | 
					 | 
				
			||||||
        .build() | 
					 | 
				
			||||||
        .unwrap() | 
					 | 
				
			||||||
        .block_on(async { | 
					 | 
				
			||||||
            let client = loop { | 
					 | 
				
			||||||
                match HttpClient::new(&env.u_server, None).await { | 
					 | 
				
			||||||
                    Ok(client) => break client, | 
					 | 
				
			||||||
                    Err(e) => { | 
					 | 
				
			||||||
                        error!("client init failed: {}", e); | 
					 | 
				
			||||||
                        sleep(Duration::from_secs(5)).await; | 
					 | 
				
			||||||
                        continue; | 
					 | 
				
			||||||
                    } | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
            }; | 
					 | 
				
			||||||
            { | 
					 | 
				
			||||||
                let client = client.clone(); | 
					 | 
				
			||||||
                SCHEDULER | 
					 | 
				
			||||||
                    .add_job(Some("*/3 * * * * * *".try_into().unwrap()), move || { | 
					 | 
				
			||||||
                        let client = client.clone(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        IdentifiableFuture::from_fut_with_ident("error_reporting", async move { | 
					 | 
				
			||||||
                            error_reporting(client.clone()).await | 
					 | 
				
			||||||
                        }) | 
					 | 
				
			||||||
                    }) | 
					 | 
				
			||||||
                    .await; | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            { | 
					 | 
				
			||||||
                let client = client.clone(); | 
					 | 
				
			||||||
                SCHEDULER | 
					 | 
				
			||||||
                    .add_job(Some("*/3 * * * * * *".try_into().unwrap()), move || { | 
					 | 
				
			||||||
                        let client = client.clone(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        IdentifiableFuture::from_fut_with_ident("agent_loop", async move { | 
					 | 
				
			||||||
                            agent_loop(client).await | 
					 | 
				
			||||||
                        }) | 
					 | 
				
			||||||
                    }) | 
					 | 
				
			||||||
                    .await; | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            SCHEDULER.start_blocking().await | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,7 @@ | 
				
			|||||||
fn main() { | 
					use tokio; | 
				
			||||||
    u_agent::run_forever(); | 
					use u_agent::run_forever; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[tokio::main] | 
				
			||||||
 | 
					async fn main() { | 
				
			||||||
 | 
					    run_forever().await; | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -1,176 +0,0 @@ | 
				
			|||||||
use serde_json::{from_str, to_value, Value}; | 
					 | 
				
			||||||
use structopt::StructOpt; | 
					 | 
				
			||||||
use u_lib::{ | 
					 | 
				
			||||||
    api::HttpClient, messaging::AsMsg, models::*, types::Id, types::PanelResult, UError, UResult, | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(StructOpt, Debug)] | 
					 | 
				
			||||||
pub struct Args { | 
					 | 
				
			||||||
    #[structopt(subcommand)] | 
					 | 
				
			||||||
    cmd: Cmd, | 
					 | 
				
			||||||
    #[structopt(short, long, default_value)] | 
					 | 
				
			||||||
    brief: Brief, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(StructOpt, Debug)] | 
					 | 
				
			||||||
enum Cmd { | 
					 | 
				
			||||||
    Agents(RUD), | 
					 | 
				
			||||||
    Jobs(CRUD), | 
					 | 
				
			||||||
    Map(AssignedCRUD), | 
					 | 
				
			||||||
    Payloads(PayloadCRUD), | 
					 | 
				
			||||||
    Ping, | 
					 | 
				
			||||||
    Serve, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(StructOpt, Debug)] | 
					 | 
				
			||||||
enum CRUD { | 
					 | 
				
			||||||
    Create { | 
					 | 
				
			||||||
        item: String, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    #[structopt(flatten)] | 
					 | 
				
			||||||
    RUD(RUD), | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(StructOpt, Debug)] | 
					 | 
				
			||||||
enum AssignedCRUD { | 
					 | 
				
			||||||
    Create { | 
					 | 
				
			||||||
        item: String, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    #[structopt(flatten)] | 
					 | 
				
			||||||
    RUD(RUD), | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(StructOpt, Debug)] | 
					 | 
				
			||||||
enum PayloadCRUD { | 
					 | 
				
			||||||
    Create { | 
					 | 
				
			||||||
        item: String, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    Read { | 
					 | 
				
			||||||
        id: Option<String>, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    Update { | 
					 | 
				
			||||||
        item: String, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    Delete { | 
					 | 
				
			||||||
        #[structopt(parse(try_from_str = parse::uuid))] | 
					 | 
				
			||||||
        id: Id, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(StructOpt, Debug)] | 
					 | 
				
			||||||
enum RUD { | 
					 | 
				
			||||||
    Read { | 
					 | 
				
			||||||
        #[structopt(parse(try_from_str = parse::uuid))] | 
					 | 
				
			||||||
        id: Option<Id>, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    Update { | 
					 | 
				
			||||||
        item: String, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    Delete { | 
					 | 
				
			||||||
        #[structopt(parse(try_from_str = parse::uuid))] | 
					 | 
				
			||||||
        id: Id, | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mod parse { | 
					 | 
				
			||||||
    use super::*; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn uuid(src: &str) -> Result<Id, String> { | 
					 | 
				
			||||||
        Id::parse_str(src).map_err(|e| e.to_string()) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn into_value<M: AsMsg>(data: M) -> Value { | 
					 | 
				
			||||||
    to_value(data).unwrap() | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub async fn process_cmd(client: HttpClient, args: Args) -> PanelResult<Value> { | 
					 | 
				
			||||||
    let catcher: UResult<Value> = (|| async { | 
					 | 
				
			||||||
        Ok(match args.cmd { | 
					 | 
				
			||||||
            Cmd::Agents(action) => match action { | 
					 | 
				
			||||||
                RUD::Read { id } => into_value(client.get_agents(id).await?), | 
					 | 
				
			||||||
                RUD::Update { item } => { | 
					 | 
				
			||||||
                    let agent = from_str::<Agent>(&item) | 
					 | 
				
			||||||
                        .map_err(|e| UError::DeserializeError(e.to_string(), item))?; | 
					 | 
				
			||||||
                    into_value(client.update_agent(&agent).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                RUD::Delete { id } => into_value(client.del(id).await?), | 
					 | 
				
			||||||
            }, | 
					 | 
				
			||||||
            Cmd::Jobs(action) => match action { | 
					 | 
				
			||||||
                CRUD::Create { item: job } => { | 
					 | 
				
			||||||
                    let raw_job = from_str::<RawJob>(&job) | 
					 | 
				
			||||||
                        .map_err(|e| UError::DeserializeError(e.to_string(), job))?; | 
					 | 
				
			||||||
                    let mut job = raw_job.try_into_job()?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if let Some(payload) = &mut job.payload { | 
					 | 
				
			||||||
                        payload.join_payload()?; | 
					 | 
				
			||||||
                    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    into_value(client.upload_jobs([&job]).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                CRUD::RUD(RUD::Read { id }) => match id { | 
					 | 
				
			||||||
                    Some(id) => into_value(vec![client.get_job(id, args.brief).await?]), | 
					 | 
				
			||||||
                    None => into_value(client.get_jobs().await?), | 
					 | 
				
			||||||
                }, | 
					 | 
				
			||||||
                CRUD::RUD(RUD::Update { item }) => { | 
					 | 
				
			||||||
                    let raw_job = from_str::<JobMeta>(&item) | 
					 | 
				
			||||||
                        .map_err(|e| UError::DeserializeError(e.to_string(), item))?; | 
					 | 
				
			||||||
                    let job = raw_job.validate()?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // if let Some(payload) = &mut job.payload {
 | 
					 | 
				
			||||||
                    //     payload.join_payload()?;
 | 
					 | 
				
			||||||
                    // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    into_value(client.update_job(&job).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                CRUD::RUD(RUD::Delete { id }) => into_value(client.del(id).await?), | 
					 | 
				
			||||||
            }, | 
					 | 
				
			||||||
            Cmd::Map(action) => match action { | 
					 | 
				
			||||||
                AssignedCRUD::Create { item } => { | 
					 | 
				
			||||||
                    let payload = serde_json::from_str::<Vec<AssignedJobById>>(&item) | 
					 | 
				
			||||||
                        .map_err(|e| UError::DeserializeError(e.to_string(), item))?; | 
					 | 
				
			||||||
                    into_value(client.assign_jobs(&payload).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                AssignedCRUD::RUD(RUD::Read { id }) => { | 
					 | 
				
			||||||
                    into_value(client.get_assigned_jobs(id).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                AssignedCRUD::RUD(RUD::Update { item }) => { | 
					 | 
				
			||||||
                    let assigned = from_str::<AssignedJob>(&item) | 
					 | 
				
			||||||
                        .map_err(|e| UError::DeserializeError(e.to_string(), item))?; | 
					 | 
				
			||||||
                    into_value(client.update_result(&assigned).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                AssignedCRUD::RUD(RUD::Delete { id }) => into_value(client.del(id).await?), | 
					 | 
				
			||||||
            }, | 
					 | 
				
			||||||
            Cmd::Payloads(action) => match action { | 
					 | 
				
			||||||
                PayloadCRUD::Create { item } => { | 
					 | 
				
			||||||
                    let payload = from_str::<RawPayload>(&item) | 
					 | 
				
			||||||
                        .map_err(|e| UError::DeserializeError(e.to_string(), item))?; | 
					 | 
				
			||||||
                    into_value(client.upload_payload(&payload).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                PayloadCRUD::Read { id } => match id { | 
					 | 
				
			||||||
                    None => into_value(client.get_payloads().await?), | 
					 | 
				
			||||||
                    Some(id) => into_value(vec![client.get_payload(id, args.brief).await?]), | 
					 | 
				
			||||||
                }, | 
					 | 
				
			||||||
                PayloadCRUD::Update { item } => { | 
					 | 
				
			||||||
                    let payload = from_str::<Payload>(&item) | 
					 | 
				
			||||||
                        .map_err(|e| UError::DeserializeError(e.to_string(), item))?; | 
					 | 
				
			||||||
                    into_value(client.update_payload(&payload).await?) | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                PayloadCRUD::Delete { id } => into_value(client.del(id).await?), | 
					 | 
				
			||||||
            }, | 
					 | 
				
			||||||
            Cmd::Ping => into_value(client.ping().await?), | 
					 | 
				
			||||||
            Cmd::Serve => { | 
					 | 
				
			||||||
                crate::gui::serve(client) | 
					 | 
				
			||||||
                    .await | 
					 | 
				
			||||||
                    .map_err(|e| UError::PanelError(format!("{e:?}")))?; | 
					 | 
				
			||||||
                Value::Null | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
    })() | 
					 | 
				
			||||||
    .await; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match catcher { | 
					 | 
				
			||||||
        Ok(r) => PanelResult::Ok(r), | 
					 | 
				
			||||||
        Err(e) => PanelResult::Err(e), | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,17 +0,0 @@ | 
				
			|||||||
use actix_web::http::StatusCode; | 
					 | 
				
			||||||
use actix_web::ResponseError; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(thiserror::Error, Debug)] | 
					 | 
				
			||||||
pub enum Error { | 
					 | 
				
			||||||
    #[error("Arg parse error: {0}")] | 
					 | 
				
			||||||
    ArgparseError(#[from] structopt::clap::Error), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error("Just an error: {0}")] | 
					 | 
				
			||||||
    JustError(String), | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ResponseError for Error { | 
					 | 
				
			||||||
    fn status_code(&self) -> actix_web::http::StatusCode { | 
					 | 
				
			||||||
        StatusCode::BAD_REQUEST | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,16 +0,0 @@ | 
				
			|||||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. | 
					 | 
				
			||||||
# For additional information regarding the format and rule options, please see: | 
					 | 
				
			||||||
# https://github.com/browserslist/browserslist#queries | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# For the full list of supported browsers by the Angular framework, please see: | 
					 | 
				
			||||||
# https://angular.io/guide/browser-support | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# You can see what browsers were selected by your queries by running: | 
					 | 
				
			||||||
#   npx browserslist | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
last 1 Chrome version | 
					 | 
				
			||||||
last 1 Firefox version | 
					 | 
				
			||||||
last 2 Edge major versions | 
					 | 
				
			||||||
last 2 Safari major versions | 
					 | 
				
			||||||
last 2 iOS major versions | 
					 | 
				
			||||||
Firefox ESR | 
					 | 
				
			||||||
@ -1,16 +0,0 @@ | 
				
			|||||||
# Editor configuration, see https://editorconfig.org | 
					 | 
				
			||||||
root = true | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*] | 
					 | 
				
			||||||
charset = utf-8 | 
					 | 
				
			||||||
indent_style = space | 
					 | 
				
			||||||
indent_size = 2 | 
					 | 
				
			||||||
insert_final_newline = true | 
					 | 
				
			||||||
trim_trailing_whitespace = true | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.ts] | 
					 | 
				
			||||||
quote_type = single | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[*.md] | 
					 | 
				
			||||||
max_line_length = off | 
					 | 
				
			||||||
trim_trailing_whitespace = false | 
					 | 
				
			||||||
@ -1,48 +0,0 @@ | 
				
			|||||||
# See http://help.github.com/ignore-files/ for more about ignoring files. | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# compiled output | 
					 | 
				
			||||||
/dist | 
					 | 
				
			||||||
/tmp | 
					 | 
				
			||||||
/out-tsc | 
					 | 
				
			||||||
# Only exists if Bazel was run | 
					 | 
				
			||||||
/bazel-out | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# dependencies | 
					 | 
				
			||||||
/node_modules | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# profiling files | 
					 | 
				
			||||||
chrome-profiler-events*.json | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# IDEs and editors | 
					 | 
				
			||||||
/.idea | 
					 | 
				
			||||||
.project | 
					 | 
				
			||||||
.classpath | 
					 | 
				
			||||||
.c9/ | 
					 | 
				
			||||||
*.launch | 
					 | 
				
			||||||
.settings/ | 
					 | 
				
			||||||
*.sublime-workspace | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# IDE - VSCode | 
					 | 
				
			||||||
.vscode/* | 
					 | 
				
			||||||
!.vscode/settings.json | 
					 | 
				
			||||||
!.vscode/tasks.json | 
					 | 
				
			||||||
!.vscode/launch.json | 
					 | 
				
			||||||
!.vscode/extensions.json | 
					 | 
				
			||||||
.history/* | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# misc | 
					 | 
				
			||||||
/.angular/cache | 
					 | 
				
			||||||
/.sass-cache | 
					 | 
				
			||||||
/connect.lock | 
					 | 
				
			||||||
/coverage | 
					 | 
				
			||||||
/libpeerconnection.log | 
					 | 
				
			||||||
npm-debug.log | 
					 | 
				
			||||||
yarn-error.log | 
					 | 
				
			||||||
testem.log | 
					 | 
				
			||||||
/typings | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# System Files | 
					 | 
				
			||||||
.DS_Store | 
					 | 
				
			||||||
Thumbs.db | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package-lock.json | 
					 | 
				
			||||||
@ -1,27 +0,0 @@ | 
				
			|||||||
# Fe | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 13.1.2. | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Development server | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Code scaffolding | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Build | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Running unit tests | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Running end-to-end tests | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Further help | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. | 
					 | 
				
			||||||
@ -1,114 +0,0 @@ | 
				
			|||||||
{ | 
					 | 
				
			||||||
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json", | 
					 | 
				
			||||||
  "version": 1, | 
					 | 
				
			||||||
  "newProjectRoot": "projects", | 
					 | 
				
			||||||
  "projects": { | 
					 | 
				
			||||||
    "fe": { | 
					 | 
				
			||||||
      "projectType": "application", | 
					 | 
				
			||||||
      "schematics": { | 
					 | 
				
			||||||
        "@schematics/angular:component": { | 
					 | 
				
			||||||
          "style": "less" | 
					 | 
				
			||||||
        }, | 
					 | 
				
			||||||
        "@schematics/angular:application": { | 
					 | 
				
			||||||
          "strict": true | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
      }, | 
					 | 
				
			||||||
      "root": "", | 
					 | 
				
			||||||
      "sourceRoot": "src", | 
					 | 
				
			||||||
      "prefix": "app", | 
					 | 
				
			||||||
      "architect": { | 
					 | 
				
			||||||
        "build": { | 
					 | 
				
			||||||
          "builder": "@angular-devkit/build-angular:browser", | 
					 | 
				
			||||||
          "options": { | 
					 | 
				
			||||||
            "outputPath": "dist/fe", | 
					 | 
				
			||||||
            "index": "src/index.html", | 
					 | 
				
			||||||
            "main": "src/main.ts", | 
					 | 
				
			||||||
            "polyfills": "src/polyfills.ts", | 
					 | 
				
			||||||
            "tsConfig": "tsconfig.app.json", | 
					 | 
				
			||||||
            "inlineStyleLanguage": "less", | 
					 | 
				
			||||||
            "assets": [ | 
					 | 
				
			||||||
              "src/favicon.ico", | 
					 | 
				
			||||||
              "src/assets" | 
					 | 
				
			||||||
            ], | 
					 | 
				
			||||||
            "styles": [ | 
					 | 
				
			||||||
              "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", | 
					 | 
				
			||||||
              "src/styles.less" | 
					 | 
				
			||||||
            ], | 
					 | 
				
			||||||
            "scripts": [] | 
					 | 
				
			||||||
          }, | 
					 | 
				
			||||||
          "configurations": { | 
					 | 
				
			||||||
            "production": { | 
					 | 
				
			||||||
              "baseHref": "/core/", | 
					 | 
				
			||||||
              "budgets": [ | 
					 | 
				
			||||||
                { | 
					 | 
				
			||||||
                  "type": "initial", | 
					 | 
				
			||||||
                  "maximumWarning": "500kb", | 
					 | 
				
			||||||
                  "maximumError": "1mb" | 
					 | 
				
			||||||
                }, | 
					 | 
				
			||||||
                { | 
					 | 
				
			||||||
                  "type": "anyComponentStyle", | 
					 | 
				
			||||||
                  "maximumWarning": "2kb", | 
					 | 
				
			||||||
                  "maximumError": "4kb" | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
              ], | 
					 | 
				
			||||||
              "fileReplacements": [ | 
					 | 
				
			||||||
                { | 
					 | 
				
			||||||
                  "replace": "src/environments/environment.ts", | 
					 | 
				
			||||||
                  "with": "src/environments/environment.prod.ts" | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
              ], | 
					 | 
				
			||||||
              "outputHashing": "all" | 
					 | 
				
			||||||
            }, | 
					 | 
				
			||||||
            "development": { | 
					 | 
				
			||||||
              "buildOptimizer": false, | 
					 | 
				
			||||||
              "optimization": false, | 
					 | 
				
			||||||
              "vendorChunk": true, | 
					 | 
				
			||||||
              "extractLicenses": false, | 
					 | 
				
			||||||
              "sourceMap": true, | 
					 | 
				
			||||||
              "namedChunks": true | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
          }, | 
					 | 
				
			||||||
          "defaultConfiguration": "production" | 
					 | 
				
			||||||
        }, | 
					 | 
				
			||||||
        "serve": { | 
					 | 
				
			||||||
          "builder": "@angular-devkit/build-angular:dev-server", | 
					 | 
				
			||||||
          "configurations": { | 
					 | 
				
			||||||
            "production": { | 
					 | 
				
			||||||
              "browserTarget": "fe:build:production" | 
					 | 
				
			||||||
            }, | 
					 | 
				
			||||||
            "development": { | 
					 | 
				
			||||||
              "browserTarget": "fe:build:development" | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
          }, | 
					 | 
				
			||||||
          "defaultConfiguration": "development" | 
					 | 
				
			||||||
        }, | 
					 | 
				
			||||||
        "extract-i18n": { | 
					 | 
				
			||||||
          "builder": "@angular-devkit/build-angular:extract-i18n", | 
					 | 
				
			||||||
          "options": { | 
					 | 
				
			||||||
            "browserTarget": "fe:build" | 
					 | 
				
			||||||
          } | 
					 | 
				
			||||||
        }, | 
					 | 
				
			||||||
        "test": { | 
					 | 
				
			||||||
          "builder": "@angular-devkit/build-angular:karma", | 
					 | 
				
			||||||
          "options": { | 
					 | 
				
			||||||
            "main": "src/test.ts", | 
					 | 
				
			||||||
            "polyfills": "src/polyfills.ts", | 
					 | 
				
			||||||
            "tsConfig": "tsconfig.spec.json", | 
					 | 
				
			||||||
            "karmaConfig": "karma.conf.js", | 
					 | 
				
			||||||
            "inlineStyleLanguage": "less", | 
					 | 
				
			||||||
            "assets": [ | 
					 | 
				
			||||||
              "src/favicon.ico", | 
					 | 
				
			||||||
              "src/assets" | 
					 | 
				
			||||||
            ], | 
					 | 
				
			||||||
            "styles": [ | 
					 | 
				
			||||||
              "./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css", | 
					 | 
				
			||||||
              "src/styles.less" | 
					 | 
				
			||||||
            ], | 
					 | 
				
			||||||
            "scripts": [] | 
					 | 
				
			||||||
          } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
  }, | 
					 | 
				
			||||||
  "defaultProject": "fe" | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,44 +0,0 @@ | 
				
			|||||||
// Karma configuration file, see link for more information
 | 
					 | 
				
			||||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = function (config) { | 
					 | 
				
			||||||
  config.set({ | 
					 | 
				
			||||||
    basePath: '', | 
					 | 
				
			||||||
    frameworks: ['jasmine', '@angular-devkit/build-angular'], | 
					 | 
				
			||||||
    plugins: [ | 
					 | 
				
			||||||
      require('karma-jasmine'), | 
					 | 
				
			||||||
      require('karma-chrome-launcher'), | 
					 | 
				
			||||||
      require('karma-jasmine-html-reporter'), | 
					 | 
				
			||||||
      require('karma-coverage'), | 
					 | 
				
			||||||
      require('@angular-devkit/build-angular/plugins/karma') | 
					 | 
				
			||||||
    ], | 
					 | 
				
			||||||
    client: { | 
					 | 
				
			||||||
      jasmine: { | 
					 | 
				
			||||||
        // you can add configuration options for Jasmine here
 | 
					 | 
				
			||||||
        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
 | 
					 | 
				
			||||||
        // for example, you can disable the random execution with `random: false`
 | 
					 | 
				
			||||||
        // or set a specific seed with `seed: 4321`
 | 
					 | 
				
			||||||
      }, | 
					 | 
				
			||||||
      clearContext: false // leave Jasmine Spec Runner output visible in browser
 | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    jasmineHtmlReporter: { | 
					 | 
				
			||||||
      suppressAll: true // removes the duplicated traces
 | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    coverageReporter: { | 
					 | 
				
			||||||
      dir: require('path').join(__dirname, './coverage/fe'), | 
					 | 
				
			||||||
      subdir: '.', | 
					 | 
				
			||||||
      reporters: [ | 
					 | 
				
			||||||
        { type: 'html' }, | 
					 | 
				
			||||||
        { type: 'text-summary' } | 
					 | 
				
			||||||
      ] | 
					 | 
				
			||||||
    }, | 
					 | 
				
			||||||
    reporters: ['progress', 'kjhtml'], | 
					 | 
				
			||||||
    port: 9876, | 
					 | 
				
			||||||
    colors: true, | 
					 | 
				
			||||||
    logLevel: config.LOG_INFO, | 
					 | 
				
			||||||
    autoWatch: true, | 
					 | 
				
			||||||
    browsers: ['Chrome'], | 
					 | 
				
			||||||
    singleRun: false, | 
					 | 
				
			||||||
    restartOnFileChange: true | 
					 | 
				
			||||||
  }); | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
@ -1,43 +0,0 @@ | 
				
			|||||||
{ | 
					 | 
				
			||||||
  "name": "fe", | 
					 | 
				
			||||||
  "version": "0.0.0", | 
					 | 
				
			||||||
  "scripts": { | 
					 | 
				
			||||||
    "ng": "ng", | 
					 | 
				
			||||||
    "start": "ng serve", | 
					 | 
				
			||||||
    "build": "ng build", | 
					 | 
				
			||||||
    "watch": "ng build --watch --configuration development", | 
					 | 
				
			||||||
    "test": "ng test" | 
					 | 
				
			||||||
  }, | 
					 | 
				
			||||||
  "private": true, | 
					 | 
				
			||||||
  "dependencies": { | 
					 | 
				
			||||||
    "@angular/animations": "~13.1.0", | 
					 | 
				
			||||||
    "@angular/cdk": "^13.3.9", | 
					 | 
				
			||||||
    "@angular/common": "~13.1.0", | 
					 | 
				
			||||||
    "@angular/compiler": "~13.1.0", | 
					 | 
				
			||||||
    "@angular/core": "~13.1.0", | 
					 | 
				
			||||||
    "@angular/forms": "~13.1.0", | 
					 | 
				
			||||||
    "@angular/material": "^13.3.9", | 
					 | 
				
			||||||
    "@angular/platform-browser": "~13.1.0", | 
					 | 
				
			||||||
    "@angular/platform-browser-dynamic": "~13.1.0", | 
					 | 
				
			||||||
    "@angular/router": "~13.1.0", | 
					 | 
				
			||||||
    "@types/uuid": "^8.3.4", | 
					 | 
				
			||||||
    "rxjs": "~7.4.0", | 
					 | 
				
			||||||
    "tslib": "^2.3.0", | 
					 | 
				
			||||||
    "uuid": "^8.3.2", | 
					 | 
				
			||||||
    "zone.js": "~0.11.4" | 
					 | 
				
			||||||
  }, | 
					 | 
				
			||||||
  "devDependencies": { | 
					 | 
				
			||||||
    "@angular-devkit/build-angular": "^13.3.9", | 
					 | 
				
			||||||
    "@angular/cli": "~13.1.2", | 
					 | 
				
			||||||
    "@angular/compiler-cli": "~13.1.0", | 
					 | 
				
			||||||
    "@types/jasmine": "~3.10.0", | 
					 | 
				
			||||||
    "@types/node": "^12.11.1", | 
					 | 
				
			||||||
    "jasmine-core": "~3.10.0", | 
					 | 
				
			||||||
    "karma": "~6.3.0", | 
					 | 
				
			||||||
    "karma-chrome-launcher": "~3.1.0", | 
					 | 
				
			||||||
    "karma-coverage": "~2.1.0", | 
					 | 
				
			||||||
    "karma-jasmine": "~4.0.0", | 
					 | 
				
			||||||
    "karma-jasmine-html-reporter": "~1.7.0", | 
					 | 
				
			||||||
    "typescript": "~4.5.2" | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,18 +0,0 @@ | 
				
			|||||||
import { NgModule } from '@angular/core'; | 
					 | 
				
			||||||
import { RouterModule, Routes } from '@angular/router'; | 
					 | 
				
			||||||
import { JobComponent, ResultComponent, AgentComponent, PayloadComponent } from './components/tables'; | 
					 | 
				
			||||||
//import { AgentInfoDialogComponent } from './core/tables/dialogs/agent-info-dialog.component';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const routes: Routes = [ | 
					 | 
				
			||||||
  { path: '', redirectTo: 'agents', pathMatch: 'full' }, | 
					 | 
				
			||||||
  { path: 'agents', component: AgentComponent }, | 
					 | 
				
			||||||
  { path: 'jobs', component: JobComponent }, | 
					 | 
				
			||||||
  { path: 'payloads', component: PayloadComponent }, | 
					 | 
				
			||||||
  { path: 'results', component: ResultComponent }, | 
					 | 
				
			||||||
]; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@NgModule({ | 
					 | 
				
			||||||
  imports: [RouterModule.forRoot(routes)], | 
					 | 
				
			||||||
  exports: [RouterModule] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class AppRoutingModule { } | 
					 | 
				
			||||||
@ -1,6 +0,0 @@ | 
				
			|||||||
<nav mat-tab-nav-bar animationDuration="0ms" mat-align-tabs="center"> | 
					 | 
				
			||||||
  <a mat-tab-link *ngFor="let tab of tabs" routerLink={{tab.link}} routerLinkActive #rla="routerLinkActive" | 
					 | 
				
			||||||
    [active]="rla.isActive" [routerLinkActiveOptions]="{ exact: true }">{{tab.name}}</a> | 
					 | 
				
			||||||
</nav> | 
					 | 
				
			||||||
<router-outlet></router-outlet> | 
					 | 
				
			||||||
<global-error></global-error> | 
					 | 
				
			||||||
@ -1,35 +0,0 @@ | 
				
			|||||||
import { TestBed } from '@angular/core/testing'; | 
					 | 
				
			||||||
import { RouterTestingModule } from '@angular/router/testing'; | 
					 | 
				
			||||||
import { AppComponent } from './app.component'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('AppComponent', () => { | 
					 | 
				
			||||||
  beforeEach(async () => { | 
					 | 
				
			||||||
    await TestBed.configureTestingModule({ | 
					 | 
				
			||||||
      imports: [ | 
					 | 
				
			||||||
        RouterTestingModule | 
					 | 
				
			||||||
      ], | 
					 | 
				
			||||||
      declarations: [ | 
					 | 
				
			||||||
        AppComponent | 
					 | 
				
			||||||
      ], | 
					 | 
				
			||||||
    }).compileComponents(); | 
					 | 
				
			||||||
  }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should create the app', () => { | 
					 | 
				
			||||||
    const fixture = TestBed.createComponent(AppComponent); | 
					 | 
				
			||||||
    const app = fixture.componentInstance; | 
					 | 
				
			||||||
    expect(app).toBeTruthy(); | 
					 | 
				
			||||||
  }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it(`should have as title 'fe'`, () => { | 
					 | 
				
			||||||
    const fixture = TestBed.createComponent(AppComponent); | 
					 | 
				
			||||||
    const app = fixture.componentInstance; | 
					 | 
				
			||||||
    expect(app.title).toEqual('fe'); | 
					 | 
				
			||||||
  }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should render title', () => { | 
					 | 
				
			||||||
    const fixture = TestBed.createComponent(AppComponent); | 
					 | 
				
			||||||
    fixture.detectChanges(); | 
					 | 
				
			||||||
    const compiled = fixture.nativeElement as HTMLElement; | 
					 | 
				
			||||||
    expect(compiled.querySelector('.content span')?.textContent).toContain('fe app is running!'); | 
					 | 
				
			||||||
  }); | 
					 | 
				
			||||||
}); | 
					 | 
				
			||||||
@ -1,15 +0,0 @@ | 
				
			|||||||
import { Component } from '@angular/core'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'app-root', | 
					 | 
				
			||||||
  templateUrl: './app.component.html', | 
					 | 
				
			||||||
  styleUrls: ['./app.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class AppComponent { | 
					 | 
				
			||||||
  tabs = [ | 
					 | 
				
			||||||
    { name: 'Agents', link: '/agents' }, | 
					 | 
				
			||||||
    { name: 'Jobs', link: '/jobs' }, | 
					 | 
				
			||||||
    { name: 'Results', link: '/results' }, | 
					 | 
				
			||||||
    { name: 'Payloads', link: '/payloads' } | 
					 | 
				
			||||||
  ]; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,73 +0,0 @@ | 
				
			|||||||
import { NgModule } from '@angular/core'; | 
					 | 
				
			||||||
import { BrowserModule } from '@angular/platform-browser'; | 
					 | 
				
			||||||
import { AppRoutingModule } from './app-routing.module'; | 
					 | 
				
			||||||
import { AppComponent } from './app.component'; | 
					 | 
				
			||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; | 
					 | 
				
			||||||
import { MatTabsModule } from '@angular/material/tabs'; | 
					 | 
				
			||||||
import { MatTableModule } from '@angular/material/table'; | 
					 | 
				
			||||||
import { MatFormFieldModule } from '@angular/material/form-field'; | 
					 | 
				
			||||||
import { MatButtonModule } from '@angular/material/button' | 
					 | 
				
			||||||
import { MatInputModule } from '@angular/material/input'; | 
					 | 
				
			||||||
import { MatSelectModule } from '@angular/material/select'; | 
					 | 
				
			||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; | 
					 | 
				
			||||||
import { HttpClientModule } from '@angular/common/http'; | 
					 | 
				
			||||||
import { MatDialogModule } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { MatGridListModule } from '@angular/material/grid-list'; | 
					 | 
				
			||||||
import { MatIconModule } from '@angular/material/icon'; | 
					 | 
				
			||||||
import { FormsModule } from '@angular/forms'; | 
					 | 
				
			||||||
import { AgentComponent, JobComponent, ResultComponent, PayloadComponent } from './components/tables'; | 
					 | 
				
			||||||
import { | 
					 | 
				
			||||||
  AgentInfoDialogComponent, | 
					 | 
				
			||||||
  AssignJobDialogComponent, | 
					 | 
				
			||||||
  JobInfoDialogComponent, | 
					 | 
				
			||||||
  ResultInfoDialogComponent, | 
					 | 
				
			||||||
  PayloadInfoDialogComponent | 
					 | 
				
			||||||
} from './components/dialogs'; | 
					 | 
				
			||||||
import { APP_BASE_HREF } from '@angular/common'; | 
					 | 
				
			||||||
import { MatTooltipModule } from '@angular/material/tooltip'; | 
					 | 
				
			||||||
import { MatSnackBarModule } from '@angular/material/snack-bar'; | 
					 | 
				
			||||||
import { MatListModule } from '@angular/material/list'; | 
					 | 
				
			||||||
import { GlobalErrorComponent } from './components/global-error/global-error.component'; | 
					 | 
				
			||||||
import { PayloadOverviewComponent } from './components/payload-overview/payload-overview.component'; | 
					 | 
				
			||||||
import { NewPayloadDialogComponent } from './components/dialogs/new-payload-dialog/new-payload-dialog.component'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@NgModule({ | 
					 | 
				
			||||||
  declarations: [ | 
					 | 
				
			||||||
    AppComponent, | 
					 | 
				
			||||||
    AgentComponent, | 
					 | 
				
			||||||
    JobComponent, | 
					 | 
				
			||||||
    ResultComponent, | 
					 | 
				
			||||||
    AgentInfoDialogComponent, | 
					 | 
				
			||||||
    JobInfoDialogComponent, | 
					 | 
				
			||||||
    ResultInfoDialogComponent, | 
					 | 
				
			||||||
    AssignJobDialogComponent, | 
					 | 
				
			||||||
    PayloadComponent, | 
					 | 
				
			||||||
    PayloadInfoDialogComponent, | 
					 | 
				
			||||||
    GlobalErrorComponent, | 
					 | 
				
			||||||
    PayloadOverviewComponent, | 
					 | 
				
			||||||
    NewPayloadDialogComponent | 
					 | 
				
			||||||
  ], | 
					 | 
				
			||||||
  imports: [ | 
					 | 
				
			||||||
    BrowserModule, | 
					 | 
				
			||||||
    HttpClientModule, | 
					 | 
				
			||||||
    AppRoutingModule, | 
					 | 
				
			||||||
    MatTabsModule, | 
					 | 
				
			||||||
    MatTableModule, | 
					 | 
				
			||||||
    MatButtonModule, | 
					 | 
				
			||||||
    MatFormFieldModule, | 
					 | 
				
			||||||
    MatInputModule, | 
					 | 
				
			||||||
    MatDialogModule, | 
					 | 
				
			||||||
    MatProgressSpinnerModule, | 
					 | 
				
			||||||
    MatIconModule, | 
					 | 
				
			||||||
    MatTooltipModule, | 
					 | 
				
			||||||
    MatSnackBarModule, | 
					 | 
				
			||||||
    MatSelectModule, | 
					 | 
				
			||||||
    MatListModule, | 
					 | 
				
			||||||
    MatGridListModule, | 
					 | 
				
			||||||
    FormsModule, | 
					 | 
				
			||||||
    BrowserAnimationsModule | 
					 | 
				
			||||||
  ], | 
					 | 
				
			||||||
  providers: [{ provide: APP_BASE_HREF, useValue: '/' }], | 
					 | 
				
			||||||
  bootstrap: [AppComponent] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class AppModule { } | 
					 | 
				
			||||||
@ -1,64 +0,0 @@ | 
				
			|||||||
<h2 mat-dialog-title *ngIf="is_preview">Agent info</h2> | 
					 | 
				
			||||||
<h2 mat-dialog-title *ngIf="!is_preview">Editing agent info</h2> | 
					 | 
				
			||||||
<mat-dialog-content> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field" cdkFocusInitial> | 
					 | 
				
			||||||
            <mat-label>ID</mat-label> | 
					 | 
				
			||||||
            <input matInput disabled value="{{data.id}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Alias</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="is_preview" [(ngModel)]="data.alias"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Username</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="is_preview" [(ngModel)]="data.username"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Hostname</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="is_preview" [(ngModel)]="data.hostname"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Host info</mat-label> | 
					 | 
				
			||||||
            <textarea matInput cdkTextareaAutosize [readonly]="is_preview" [(ngModel)]="data.host_info"> | 
					 | 
				
			||||||
            </textarea> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Platform</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="is_preview" [(ngModel)]="data.platform"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Is root</mat-label> | 
					 | 
				
			||||||
            <input matInput disabled value="{{data.is_root}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Registration time</mat-label> | 
					 | 
				
			||||||
            <input matInput disabled value="{{data.regtime.secs_since_epoch * 1000 | date:'long'}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
    <p> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Last active time</mat-label> | 
					 | 
				
			||||||
            <input matInput disabled value="{{data.last_active.secs_since_epoch * 1000 | date:'long'}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </p> | 
					 | 
				
			||||||
</mat-dialog-content> | 
					 | 
				
			||||||
<mat-dialog-actions align="end"> | 
					 | 
				
			||||||
    <button mat-raised-button *ngIf="is_preview" (click)="is_preview = false">Edit</button> | 
					 | 
				
			||||||
    <button mat-raised-button *ngIf="!is_preview" (click)="updateAgent()">Save</button> | 
					 | 
				
			||||||
    <button mat-button mat-dialog-close>Cancel</button> | 
					 | 
				
			||||||
</mat-dialog-actions> | 
					 | 
				
			||||||
@ -1,21 +0,0 @@ | 
				
			|||||||
import { Component, Inject } from '@angular/core'; | 
					 | 
				
			||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { AgentModel } from '../../../models/agent.model'; | 
					 | 
				
			||||||
import { EventEmitter } from '@angular/core'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
    selector: 'agent-info-dialog', | 
					 | 
				
			||||||
    templateUrl: 'agent-info-dialog.component.html', | 
					 | 
				
			||||||
    styleUrls: ['../base-info-dialog.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class AgentInfoDialogComponent { | 
					 | 
				
			||||||
    is_preview = true; | 
					 | 
				
			||||||
    onSave = new EventEmitter(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor(@Inject(MAT_DIALOG_DATA) public data: AgentModel) { } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updateAgent() { | 
					 | 
				
			||||||
        console.log(this.data); | 
					 | 
				
			||||||
        this.onSave.emit(this.data); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,12 +0,0 @@ | 
				
			|||||||
<h2 mat-dialog-title>Assign job</h2> | 
					 | 
				
			||||||
<mat-dialog-content> | 
					 | 
				
			||||||
    <mat-selection-list #jobsList [(ngModel)]="selected_rows"> | 
					 | 
				
			||||||
        <mat-list-option *ngFor="let row of rows" [value]="row"> | 
					 | 
				
			||||||
            {{row}} | 
					 | 
				
			||||||
        </mat-list-option> | 
					 | 
				
			||||||
    </mat-selection-list> | 
					 | 
				
			||||||
</mat-dialog-content> | 
					 | 
				
			||||||
<mat-dialog-actions align="end"> | 
					 | 
				
			||||||
    <button mat-raised-button mat-dialog-close (click)="assignSelectedJobs()">Assign</button> | 
					 | 
				
			||||||
    <button mat-button mat-dialog-close>Cancel</button> | 
					 | 
				
			||||||
</mat-dialog-actions> | 
					 | 
				
			||||||
@ -1,36 +0,0 @@ | 
				
			|||||||
import { Component, Inject } from '@angular/core'; | 
					 | 
				
			||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { AssignedJobByIdModel } from 'src/app/models'; | 
					 | 
				
			||||||
import { ApiTableService } from '../../../services'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
    selector: 'assign-job-dialog', | 
					 | 
				
			||||||
    templateUrl: 'assign-job-dialog.component.html', | 
					 | 
				
			||||||
    styleUrls: [] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class AssignJobDialogComponent { | 
					 | 
				
			||||||
    rows: string[] = []; | 
					 | 
				
			||||||
    selected_rows: string[] = []; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor( | 
					 | 
				
			||||||
        @Inject(MAT_DIALOG_DATA) public agent_id: string, | 
					 | 
				
			||||||
        private dataSource: ApiTableService, | 
					 | 
				
			||||||
    ) { | 
					 | 
				
			||||||
        dataSource.getJobs().subscribe(resp => { | 
					 | 
				
			||||||
            this.rows = resp.map(j => `${j.id} ${j.alias}`) | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    assignSelectedJobs() { | 
					 | 
				
			||||||
        const assigned_jobs: AssignedJobByIdModel[] = this.selected_rows.map(row => { | 
					 | 
				
			||||||
            const job_id = row.split(' ', 1)[0]; | 
					 | 
				
			||||||
            return { | 
					 | 
				
			||||||
                job_id: job_id, | 
					 | 
				
			||||||
                agent_id: this.agent_id | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        }); | 
					 | 
				
			||||||
        this.dataSource.createResult(assigned_jobs).subscribe(_ => { | 
					 | 
				
			||||||
            alert("Created") | 
					 | 
				
			||||||
        }); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,18 +0,0 @@ | 
				
			|||||||
.info-dlg-field { | 
					 | 
				
			||||||
    width: 100%; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.info-dialog-forms-box { | 
					 | 
				
			||||||
    width: 100%; | 
					 | 
				
			||||||
    margin-right: 10px; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.info-dialog-forms-box-smol { | 
					 | 
				
			||||||
    width: 30%; | 
					 | 
				
			||||||
    float: left; | 
					 | 
				
			||||||
    margin-right: 10px; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.code { | 
					 | 
				
			||||||
    font-family: "Roboto Mono", monospace; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,5 +0,0 @@ | 
				
			|||||||
export * from './agent-info-dialog/agent-info-dialog.component'; | 
					 | 
				
			||||||
export * from './result-info-dialog/result-info-dialog.component'; | 
					 | 
				
			||||||
export * from './job-info-dialog/job-info-dialog.component'; | 
					 | 
				
			||||||
export * from './assign-job-dialog/assign-job-dialog.component'; | 
					 | 
				
			||||||
export * from './payload-info-dialog/payload-info-dialog.component'; | 
					 | 
				
			||||||
@ -1,48 +0,0 @@ | 
				
			|||||||
<h2 mat-dialog-title *ngIf="isPreview">Job info</h2> | 
					 | 
				
			||||||
<h2 mat-dialog-title *ngIf="!isPreview">Editing job info</h2> | 
					 | 
				
			||||||
<mat-dialog-content> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>ID</mat-label> | 
					 | 
				
			||||||
            <input matInput disabled value="{{data.meta.id}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Alias</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="isPreview" [(ngModel)]="data.meta.alias"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Args</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="isPreview" [(ngModel)]="data.meta.argv"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Type</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="isPreview" [(ngModel)]="data.meta.exec_type"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Platform</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="isPreview" [(ngModel)]="data.meta.target_platforms"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Schedule</mat-label> | 
					 | 
				
			||||||
            <input matInput [readonly]="isPreview" [(ngModel)]="data.meta.schedule"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Payload</mat-label> | 
					 | 
				
			||||||
            <mat-select [disabled]="isPreview" [(value)]="data.meta.payload_id"> | 
					 | 
				
			||||||
                <mat-option *ngFor="let pld of allPayloads" [value]="pld[0]">{{ pld[1] }}</mat-option> | 
					 | 
				
			||||||
            </mat-select> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box"> | 
					 | 
				
			||||||
        <payload-overview *ngIf="data.payload" [preview]="true" [payload]="data.payload.data"></payload-overview> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
</mat-dialog-content> | 
					 | 
				
			||||||
<mat-dialog-actions align="end"> | 
					 | 
				
			||||||
    <button mat-raised-button *ngIf="isPreview" (click)="isPreview = false">Edit</button> | 
					 | 
				
			||||||
    <button mat-raised-button *ngIf="!isPreview" (click)="updateJob()">Save</button> | 
					 | 
				
			||||||
    <button mat-button mat-dialog-close>Cancel</button> | 
					 | 
				
			||||||
</mat-dialog-actions> | 
					 | 
				
			||||||
@ -1,28 +0,0 @@ | 
				
			|||||||
import { Component, Inject } from '@angular/core'; | 
					 | 
				
			||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { EventEmitter } from '@angular/core'; | 
					 | 
				
			||||||
import { Job, JobModel } from '../../../models/job.model'; | 
					 | 
				
			||||||
import { ApiTableService } from 'src/app/services'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
    selector: 'job-info-dialog', | 
					 | 
				
			||||||
    templateUrl: 'job-info-dialog.component.html', | 
					 | 
				
			||||||
    styleUrls: ['../base-info-dialog.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class JobInfoDialogComponent { | 
					 | 
				
			||||||
    //[id, name]
 | 
					 | 
				
			||||||
    isPreview = true; | 
					 | 
				
			||||||
    allPayloads: [string | null, string][] = [[null, "none"]]; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onSave = new EventEmitter<JobModel>(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor(@Inject(MAT_DIALOG_DATA) public data: Job, dataSource: ApiTableService) { | 
					 | 
				
			||||||
        dataSource.getPayloads().subscribe(resp => { | 
					 | 
				
			||||||
            this.allPayloads = this.allPayloads.concat(resp.map(r => [r.id, r.name])) | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    updateJob() { | 
					 | 
				
			||||||
        this.onSave.emit(this.data.meta); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,20 +0,0 @@ | 
				
			|||||||
<h2 mat-dialog-title>New payload</h2> | 
					 | 
				
			||||||
<mat-dialog-content> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field" cdkFocusInitial> | 
					 | 
				
			||||||
            <mat-label>Name</mat-label> | 
					 | 
				
			||||||
            <input matInput [(ngModel)]="payload.name"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <input type="file" class="file-input" (change)="onFileSelected($event)" #fileUpload> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field" *ngIf="!uploadMode"> | 
					 | 
				
			||||||
            <mat-label>Data</mat-label> | 
					 | 
				
			||||||
            <textarea matInput [(ngModel)]="decodedPayload"></textarea> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
</mat-dialog-content> | 
					 | 
				
			||||||
<mat-dialog-actions align="end"> | 
					 | 
				
			||||||
    <button mat-raised-button (click)="save()">Save</button> | 
					 | 
				
			||||||
    <button mat-button mat-dialog-close>Close</button> | 
					 | 
				
			||||||
</mat-dialog-actions> | 
					 | 
				
			||||||
@ -1,43 +0,0 @@ | 
				
			|||||||
import { Component, EventEmitter, Inject } from '@angular/core'; | 
					 | 
				
			||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { NewPayloadModel } from 'src/app/models/payload.model'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'new-payload-dialog', | 
					 | 
				
			||||||
  templateUrl: 'new-payload-dialog.component.html', | 
					 | 
				
			||||||
  styleUrls: ['../base-info-dialog.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class NewPayloadDialogComponent { | 
					 | 
				
			||||||
  decodedPayload = ""; | 
					 | 
				
			||||||
  uploadMode = false; | 
					 | 
				
			||||||
  onSave = new EventEmitter<NewPayloadModel>(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(@Inject(MAT_DIALOG_DATA) public payload: NewPayloadModel) { } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  save() { | 
					 | 
				
			||||||
    if (this.payload.data.length == 0) { | 
					 | 
				
			||||||
      this.payload.data = Array.from(new TextEncoder().encode(this.decodedPayload)); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    this.onSave.emit(this.payload); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onFileSelected(event: any) { | 
					 | 
				
			||||||
    const file: File = event.target.files[0]; | 
					 | 
				
			||||||
    if (file) { | 
					 | 
				
			||||||
      this.uploadMode = true | 
					 | 
				
			||||||
      const reader = new FileReader(); | 
					 | 
				
			||||||
      reader.onload = e => { | 
					 | 
				
			||||||
        this.payload.name = file.name; | 
					 | 
				
			||||||
        const result = e.target?.result; | 
					 | 
				
			||||||
        if (result instanceof ArrayBuffer) { | 
					 | 
				
			||||||
          const d = Array.from(new Uint8Array(result)); | 
					 | 
				
			||||||
          this.payload.data = d; | 
					 | 
				
			||||||
          console.log(this.payload.data) | 
					 | 
				
			||||||
        } else { | 
					 | 
				
			||||||
          alert!("no file") | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
      reader.readAsArrayBuffer(file) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,34 +0,0 @@ | 
				
			|||||||
<h2 mat-dialog-title *ngIf="isPreview">Payload</h2> | 
					 | 
				
			||||||
<h2 mat-dialog-title *ngIf="!isPreview">Editing payload</h2> | 
					 | 
				
			||||||
<mat-dialog-content> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box"> | 
					 | 
				
			||||||
        <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
            <mat-form-field class="info-dlg-field" cdkFocusInitial> | 
					 | 
				
			||||||
                <mat-label>ID</mat-label> | 
					 | 
				
			||||||
                <input matInput disabled value="{{payload.id}}"> | 
					 | 
				
			||||||
            </mat-form-field> | 
					 | 
				
			||||||
            <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
                <mat-label>Name</mat-label> | 
					 | 
				
			||||||
                <input matInput [readonly]="isPreview" [(ngModel)]="payload.name"> | 
					 | 
				
			||||||
            </mat-form-field> | 
					 | 
				
			||||||
        </div> | 
					 | 
				
			||||||
        <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
            <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
                <mat-label>MIME-type</mat-label> | 
					 | 
				
			||||||
                <input matInput disabled value="{{payload.mime_type}}"> | 
					 | 
				
			||||||
            </mat-form-field> | 
					 | 
				
			||||||
            <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
                <mat-label>Size</mat-label> | 
					 | 
				
			||||||
                <input matInput disabled value="{{payload.size}}"> | 
					 | 
				
			||||||
            </mat-form-field> | 
					 | 
				
			||||||
        </div> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box"> | 
					 | 
				
			||||||
        <payload-overview [preview]="isPreview" [payload]="payload.data"></payload-overview> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
</mat-dialog-content> | 
					 | 
				
			||||||
<mat-dialog-actions align="end"> | 
					 | 
				
			||||||
    <button mat-raised-button *ngIf="isPreview" (click)="isPreview = false">Edit</button> | 
					 | 
				
			||||||
    <button mat-raised-button *ngIf="!isPreview" (click)="updatePayload()">Save</button> | 
					 | 
				
			||||||
    <button mat-button mat-dialog-close>Close</button> | 
					 | 
				
			||||||
</mat-dialog-actions> | 
					 | 
				
			||||||
@ -1,20 +0,0 @@ | 
				
			|||||||
import { Component, EventEmitter, Inject } from '@angular/core'; | 
					 | 
				
			||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { PayloadModel } from 'src/app/models/payload.model'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'payload-info-dialog', | 
					 | 
				
			||||||
  templateUrl: 'payload-info-dialog.component.html', | 
					 | 
				
			||||||
  styleUrls: ['../base-info-dialog.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class PayloadInfoDialogComponent { | 
					 | 
				
			||||||
  isPreview = true; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  onSave = new EventEmitter<PayloadModel>(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(@Inject(MAT_DIALOG_DATA) public payload: PayloadModel) { } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updatePayload() { | 
					 | 
				
			||||||
    this.onSave.emit(this.payload); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,47 +0,0 @@ | 
				
			|||||||
<h2 mat-dialog-title>Result</h2> | 
					 | 
				
			||||||
<mat-dialog-content> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field" cdkFocusInitial> | 
					 | 
				
			||||||
            <mat-label>ID</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.id}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Job ID</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.job_id}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Agent ID</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.agent_id}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Alias</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.alias}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>State</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.state}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Return code</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.retcode}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box-smol"> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Created</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.created.secs_since_epoch * 1000 | date:'long'}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <mat-form-field class="info-dlg-field"> | 
					 | 
				
			||||||
            <mat-label>Updated</mat-label> | 
					 | 
				
			||||||
            <input matInput readonly value="{{data.updated.secs_since_epoch * 1000 | date:'long'}}"> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
    <div class="info-dialog-forms-box"> | 
					 | 
				
			||||||
        <payload-overview [preview]="true" [payload]="data.result"></payload-overview> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
</mat-dialog-content> | 
					 | 
				
			||||||
<mat-dialog-actions align="end"> | 
					 | 
				
			||||||
    <button mat-button mat-dialog-close>Close</button> | 
					 | 
				
			||||||
</mat-dialog-actions> | 
					 | 
				
			||||||
@ -1,20 +0,0 @@ | 
				
			|||||||
import { Component, Inject } from '@angular/core'; | 
					 | 
				
			||||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { ResultModel } from '../../../models/result.model'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
    selector: 'result-info-dialog', | 
					 | 
				
			||||||
    templateUrl: 'result-info-dialog.component.html', | 
					 | 
				
			||||||
    styleUrls: ['../base-info-dialog.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class ResultInfoDialogComponent { | 
					 | 
				
			||||||
    decodedResult: string; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor(@Inject(MAT_DIALOG_DATA) public data: ResultModel) { | 
					 | 
				
			||||||
        if (data.result !== null) { | 
					 | 
				
			||||||
            this.decodedResult = new TextDecoder().decode(new Uint8Array(data.result)) | 
					 | 
				
			||||||
        } else { | 
					 | 
				
			||||||
            this.decodedResult = "" | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,34 +0,0 @@ | 
				
			|||||||
import { Component, OnInit } from '@angular/core'; | 
					 | 
				
			||||||
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; | 
					 | 
				
			||||||
import { ErrorService } from 'src/app/services/error.service'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'global-error', | 
					 | 
				
			||||||
  templateUrl: './global-error.component.html', | 
					 | 
				
			||||||
  styleUrls: ['./global-error.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class GlobalErrorComponent implements OnInit { | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor( | 
					 | 
				
			||||||
    private snackBar: MatSnackBar, | 
					 | 
				
			||||||
    private errorService: ErrorService | 
					 | 
				
			||||||
  ) { } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ngOnInit() { | 
					 | 
				
			||||||
    this.errorService.error$.subscribe(err => { | 
					 | 
				
			||||||
      const _config = (duration: number): MatSnackBarConfig => { | 
					 | 
				
			||||||
        return { | 
					 | 
				
			||||||
          horizontalPosition: 'right', | 
					 | 
				
			||||||
          verticalPosition: 'bottom', | 
					 | 
				
			||||||
          duration | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
      const error = true; | 
					 | 
				
			||||||
      const cfg = error ? _config(0) : _config(2000) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (err != '') { | 
					 | 
				
			||||||
        this.snackBar.open(err, 'Ok', cfg) | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
    }) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,7 +0,0 @@ | 
				
			|||||||
<mat-form-field style="box-sizing:border-box; width:100%" class="info-dlg-field" floatLabel="always"> | 
					 | 
				
			||||||
    <mat-label>Payload data</mat-label> | 
					 | 
				
			||||||
    <textarea class="code" matInput cdkTextareaAutosize="true" *ngIf="!isTooBigPayload" [readonly]="isPreview" | 
					 | 
				
			||||||
        [(ngModel)]="decodedPayload"> | 
					 | 
				
			||||||
    </textarea> | 
					 | 
				
			||||||
    <input matInput *ngIf="isTooBigPayload" disabled placeholder="Payload is too big to display"> | 
					 | 
				
			||||||
</mat-form-field> | 
					 | 
				
			||||||
@ -1,21 +0,0 @@ | 
				
			|||||||
import { Component, Input, OnInit } from '@angular/core'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'payload-overview', | 
					 | 
				
			||||||
  templateUrl: './payload-overview.component.html', | 
					 | 
				
			||||||
  styleUrls: ['./payload-overview.component.less'] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class PayloadOverviewComponent implements OnInit { | 
					 | 
				
			||||||
  @Input() payload: number[] | null = null; | 
					 | 
				
			||||||
  @Input("preview") isPreview = true; | 
					 | 
				
			||||||
  isTooBigPayload = false; | 
					 | 
				
			||||||
  decodedPayload = ""; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ngOnInit() { | 
					 | 
				
			||||||
    if (this.payload !== null) { | 
					 | 
				
			||||||
      this.decodedPayload = new TextDecoder().decode(new Uint8Array(this.payload)) | 
					 | 
				
			||||||
    } else { | 
					 | 
				
			||||||
      this.isTooBigPayload = true | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,78 +0,0 @@ | 
				
			|||||||
<div class="mat-elevation-z8"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div class="table-container"> | 
					 | 
				
			||||||
        <div class="loading-shade" *ngIf="isLoadingResults"> | 
					 | 
				
			||||||
            <mat-spinner *ngIf="isLoadingResults"></mat-spinner> | 
					 | 
				
			||||||
        </div> | 
					 | 
				
			||||||
        <mat-form-field appearance="standard"> | 
					 | 
				
			||||||
            <mat-label>Filter</mat-label> | 
					 | 
				
			||||||
            <input matInput (keyup)="applyFilter($event)" #input> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <button id="refresh_btn" mat-raised-button color="primary" (click)="loadTableData()">Refresh</button> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <table mat-table fixedLayout="true" [dataSource]="table_data" class="data-table" matSort matSortActive="id" | 
					 | 
				
			||||||
            matSortDisableClear matSortDirection="desc"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="id"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>ID</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.id}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="alias"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Alias</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.alias}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="username"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>User</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.username}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="hostname"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Hostname</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.hostname}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="last_active"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Last active</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.last_active.secs_since_epoch * 1000 | date:'long'}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="actions"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef></th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    <button mat-icon-button (click)="assignJobs(row.id)"> | 
					 | 
				
			||||||
                        <mat-icon>add_task</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                    | | 
					 | 
				
			||||||
                    <button mat-icon-button routerLink='.' [queryParams]="{id: row.id}"> | 
					 | 
				
			||||||
                        <mat-icon>more_horiz</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                    | | 
					 | 
				
			||||||
                    <button mat-icon-button (click)="deleteItem(row.id)"> | 
					 | 
				
			||||||
                        <mat-icon>delete</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | 
					 | 
				
			||||||
            <tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;"></tr> | 
					 | 
				
			||||||
            <tr class="mat-row" *matNoDataRow> | 
					 | 
				
			||||||
                <td class="mat-cell">No data</td> | 
					 | 
				
			||||||
            </tr> | 
					 | 
				
			||||||
        </table> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> | 
					 | 
				
			||||||
    </mat-paginator> --> | 
					 | 
				
			||||||
</div> | 
					 | 
				
			||||||
@ -1,42 +0,0 @@ | 
				
			|||||||
import { Component, OnInit } from '@angular/core'; | 
					 | 
				
			||||||
import { TableComponent } from '../base-table/base-table.component'; | 
					 | 
				
			||||||
import { AgentModel, Area } from '../../../models'; | 
					 | 
				
			||||||
import { AssignJobDialogComponent, AgentInfoDialogComponent } from '../../dialogs'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'agent-table', | 
					 | 
				
			||||||
  templateUrl: './agent-table.component.html', | 
					 | 
				
			||||||
  styleUrls: ['../base-table/base-table.component.less'], | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class AgentComponent extends TableComponent<AgentModel> implements OnInit { | 
					 | 
				
			||||||
  area = 'agents' as Area | 
					 | 
				
			||||||
  displayedColumns = ['id', 'alias', 'username', 'hostname', 'last_active', 'actions'] | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  showItemDialog(id: string) { | 
					 | 
				
			||||||
    this.dataSource.getAgent(id).subscribe(resp => { | 
					 | 
				
			||||||
      const dialog = this.infoDialog.open(AgentInfoDialogComponent, { | 
					 | 
				
			||||||
        data: resp, | 
					 | 
				
			||||||
        width: '1000px', | 
					 | 
				
			||||||
      }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const saveSub = dialog.componentInstance.onSave.subscribe(result => { | 
					 | 
				
			||||||
        this.dataSource.updateAgent(result).subscribe(_ => { | 
					 | 
				
			||||||
          alert('Saved') | 
					 | 
				
			||||||
          this.loadTableData() | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      dialog.afterClosed().subscribe(result => { | 
					 | 
				
			||||||
        saveSub.unsubscribe() | 
					 | 
				
			||||||
        this.router.navigate(['.'], { relativeTo: this.route }) | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
    }) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  assignJobs(id: string) { | 
					 | 
				
			||||||
    const dialog = this.infoDialog.open(AssignJobDialogComponent, { | 
					 | 
				
			||||||
      data: id, | 
					 | 
				
			||||||
      width: '1000px', | 
					 | 
				
			||||||
    }); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,32 +0,0 @@ | 
				
			|||||||
.data-table { | 
					 | 
				
			||||||
    width: 100%; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.table-container { | 
					 | 
				
			||||||
    margin: 50px; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.loading-shade { | 
					 | 
				
			||||||
    position: absolute; | 
					 | 
				
			||||||
    top: 0; | 
					 | 
				
			||||||
    left: 0; | 
					 | 
				
			||||||
    bottom: 56px; | 
					 | 
				
			||||||
    right: 0; | 
					 | 
				
			||||||
    //background: rgba(0, 0, 0, 0.15); | 
					 | 
				
			||||||
    z-index: 1; | 
					 | 
				
			||||||
    display: flex; | 
					 | 
				
			||||||
    align-items: center; | 
					 | 
				
			||||||
    justify-content: center; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#refresh_btn { | 
					 | 
				
			||||||
    margin-left: 10px; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.data-table-row { | 
					 | 
				
			||||||
    height: 30px; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.data-table-row:hover { | 
					 | 
				
			||||||
    background: whitesmoke; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,59 +0,0 @@ | 
				
			|||||||
import { OnInit, Directive, Component } from '@angular/core'; | 
					 | 
				
			||||||
import { ApiTableService } from '../../..'; | 
					 | 
				
			||||||
import { MatTableDataSource } from '@angular/material/table'; | 
					 | 
				
			||||||
import { MatDialog } from '@angular/material/dialog'; | 
					 | 
				
			||||||
import { ApiModel, Area } from '../../../models'; | 
					 | 
				
			||||||
import { ActivatedRoute, Router } from '@angular/router'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Directive() | 
					 | 
				
			||||||
export abstract class TableComponent<T extends ApiModel> implements OnInit { | 
					 | 
				
			||||||
  abstract area: Area; | 
					 | 
				
			||||||
  table_data: MatTableDataSource<T> = new MatTableDataSource; | 
					 | 
				
			||||||
  isLoadingResults = true; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor( | 
					 | 
				
			||||||
    public dataSource: ApiTableService, | 
					 | 
				
			||||||
    public infoDialog: MatDialog, | 
					 | 
				
			||||||
    public route: ActivatedRoute, | 
					 | 
				
			||||||
    public router: Router, | 
					 | 
				
			||||||
  ) { } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ngOnInit() { | 
					 | 
				
			||||||
    this.loadTableData(); | 
					 | 
				
			||||||
    this.route.queryParams.subscribe(params => { | 
					 | 
				
			||||||
      const id = params['id'] | 
					 | 
				
			||||||
      const new_item = params['new'] | 
					 | 
				
			||||||
      if (id) { | 
					 | 
				
			||||||
        this.showItemDialog(id); | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
      if (new_item) { | 
					 | 
				
			||||||
        this.showItemDialog(null); | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
    }) | 
					 | 
				
			||||||
    //interval(10000).subscribe(_ => this.loadTableData());
 | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  loadTableData() { | 
					 | 
				
			||||||
    this.isLoadingResults = true; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.dataSource.getMany(this.area).subscribe(resp => { | 
					 | 
				
			||||||
      this.isLoadingResults = false; | 
					 | 
				
			||||||
      this.table_data.data = resp; | 
					 | 
				
			||||||
    }) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  applyFilter(event: Event) { | 
					 | 
				
			||||||
    const filterValue = (event.target as HTMLInputElement).value; | 
					 | 
				
			||||||
    this.table_data.filter = filterValue.trim().toLowerCase(); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  deleteItem(id: string) { | 
					 | 
				
			||||||
    if (confirm(`Delete ${id}?`)) { | 
					 | 
				
			||||||
      this.dataSource.delete(id, this.area).subscribe(_ => { }) | 
					 | 
				
			||||||
      this.loadTableData() | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  abstract displayedColumns: string[]; | 
					 | 
				
			||||||
  abstract showItemDialog(id: string | null): void; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,5 +0,0 @@ | 
				
			|||||||
export * from './agent-table/agent-table.component'; | 
					 | 
				
			||||||
export * from './base-table/base-table.component'; | 
					 | 
				
			||||||
export * from './job-table/job-table.component'; | 
					 | 
				
			||||||
export * from './payload-table/payload-table.component'; | 
					 | 
				
			||||||
export * from './result-table/result-table.component'; | 
					 | 
				
			||||||
@ -1,83 +0,0 @@ | 
				
			|||||||
<div class="mat-elevation-z8"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div class="table-container"> | 
					 | 
				
			||||||
        <div class="loading-shade" *ngIf="isLoadingResults"> | 
					 | 
				
			||||||
            <mat-spinner *ngIf="isLoadingResults"></mat-spinner> | 
					 | 
				
			||||||
        </div> | 
					 | 
				
			||||||
        <mat-form-field appearance="standard"> | 
					 | 
				
			||||||
            <mat-label>Filter</mat-label> | 
					 | 
				
			||||||
            <input matInput (keyup)="applyFilter($event)" #input> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <button id="refresh_btn" mat-raised-button color="basic" (click)="loadTableData()">Refresh</button> | 
					 | 
				
			||||||
        <button id="new_btn" mat-raised-button color="primary" routerLink='.' [queryParams]="{new: true}">Add | 
					 | 
				
			||||||
            job</button> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <table mat-table fixedLayout="true" [dataSource]="table_data" class="data-table" matSort matSortActive="id" | 
					 | 
				
			||||||
            matSortDisableClear matSortDirection="desc"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="id"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>ID</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.id}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="alias"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Alias</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.alias}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="argv"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Cmd-line args</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.argv}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="platform"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Platform</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.target_platforms}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="schedule"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Schedule</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.schedule}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="exec_type"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Type</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.exec_type}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="actions"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef></th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    <button mat-icon-button routerLink='.' [queryParams]="{id: row.id}"> | 
					 | 
				
			||||||
                        <mat-icon>more_horiz</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                    | | 
					 | 
				
			||||||
                    <button mat-icon-button (click)="deleteItem(row.id)"> | 
					 | 
				
			||||||
                        <mat-icon>delete</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | 
					 | 
				
			||||||
            <tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;"></tr> | 
					 | 
				
			||||||
            <tr class="mat-row" *matNoDataRow> | 
					 | 
				
			||||||
                <td class="mat-cell">No data</td> | 
					 | 
				
			||||||
            </tr> | 
					 | 
				
			||||||
        </table> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> | 
					 | 
				
			||||||
    </mat-paginator> --> | 
					 | 
				
			||||||
</div> | 
					 | 
				
			||||||
@ -1,72 +0,0 @@ | 
				
			|||||||
import { Component, OnInit } from '@angular/core'; | 
					 | 
				
			||||||
import { TableComponent } from '../base-table/base-table.component'; | 
					 | 
				
			||||||
import { Area, JobModel, Job } from '../../../models'; | 
					 | 
				
			||||||
import { JobInfoDialogComponent } from '../../dialogs'; | 
					 | 
				
			||||||
import { Observable } from 'rxjs'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'job-table', | 
					 | 
				
			||||||
  templateUrl: './job-table.component.html', | 
					 | 
				
			||||||
  styleUrls: ['../base-table/base-table.component.less'], | 
					 | 
				
			||||||
  providers: [{ provide: 'area', useValue: 'jobs' }] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class JobComponent extends TableComponent<JobModel> { | 
					 | 
				
			||||||
  area = 'jobs' as Area; | 
					 | 
				
			||||||
  displayedColumns = ['id', 'alias', 'platform', 'schedule', 'exec_type', 'actions'] | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  showItemDialog(id: string | null) { | 
					 | 
				
			||||||
    const is_new_job = id === null; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var dialogData$: Observable<Job>; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (is_new_job) { | 
					 | 
				
			||||||
      dialogData$ = new Observable(subscriber => { | 
					 | 
				
			||||||
        var defaultJob: Job = { | 
					 | 
				
			||||||
          meta: { | 
					 | 
				
			||||||
            alias: null, | 
					 | 
				
			||||||
            argv: '', | 
					 | 
				
			||||||
            exec_type: 'shell', | 
					 | 
				
			||||||
            target_platforms: '*', | 
					 | 
				
			||||||
            payload_id: null, | 
					 | 
				
			||||||
            schedule: null | 
					 | 
				
			||||||
          }, | 
					 | 
				
			||||||
          payload: null | 
					 | 
				
			||||||
        }; | 
					 | 
				
			||||||
        subscriber.next(defaultJob) | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
    } else { | 
					 | 
				
			||||||
      dialogData$ = this.dataSource.getJob(id) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dialogData$.subscribe(dialogData => { | 
					 | 
				
			||||||
      const dialog = this.infoDialog.open(JobInfoDialogComponent, { | 
					 | 
				
			||||||
        data: dialogData, | 
					 | 
				
			||||||
        width: '1000px', | 
					 | 
				
			||||||
      }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      dialog.componentInstance.isPreview = !is_new_job; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      const saveSub = dialog.componentInstance.onSave.subscribe(result => { | 
					 | 
				
			||||||
        if (is_new_job) { | 
					 | 
				
			||||||
          this.dataSource.create(dialogData.meta, this.area) | 
					 | 
				
			||||||
            .subscribe(_ => { | 
					 | 
				
			||||||
              alert("Created") | 
					 | 
				
			||||||
              this.loadTableData() | 
					 | 
				
			||||||
            }) | 
					 | 
				
			||||||
        } else { | 
					 | 
				
			||||||
          this.dataSource.updateJob(result) | 
					 | 
				
			||||||
            .subscribe(_ => { | 
					 | 
				
			||||||
              alert("Updated") | 
					 | 
				
			||||||
              this.loadTableData() | 
					 | 
				
			||||||
            }) | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        dialog.close() | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      dialog.afterClosed().subscribe(result => { | 
					 | 
				
			||||||
        saveSub.unsubscribe() | 
					 | 
				
			||||||
        this.router.navigate(['.'], { relativeTo: this.route }) | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
    }) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,63 +0,0 @@ | 
				
			|||||||
<div class="mat-elevation-z8"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div class="table-container"> | 
					 | 
				
			||||||
        <div class="loading-shade" *ngIf="isLoadingResults"> | 
					 | 
				
			||||||
            <mat-spinner *ngIf="isLoadingResults"></mat-spinner> | 
					 | 
				
			||||||
        </div> | 
					 | 
				
			||||||
        <mat-form-field appearance="standard"> | 
					 | 
				
			||||||
            <mat-label>Filter</mat-label> | 
					 | 
				
			||||||
            <input matInput (keyup)="applyFilter($event)" #input> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <button id="refresh_btn" mat-raised-button color="primary" (click)="loadTableData()">Refresh</button> | 
					 | 
				
			||||||
        <button id="new_btn" mat-raised-button color="primary" routerLink='.' [queryParams]="{new: true}">Add | 
					 | 
				
			||||||
            payload</button> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <table mat-table fixedLayout="true" [dataSource]="table_data" class="data-table" matSort matSortActive="id" | 
					 | 
				
			||||||
            matSortDisableClear matSortDirection="desc"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="name"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Name</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.name}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="mime_type"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>MIME-type</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.mime_type}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="size"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Size</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.size}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="actions"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef></th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    <button mat-icon-button routerLink='.' [queryParams]="{id: row.id}"> | 
					 | 
				
			||||||
                        <mat-icon>more_horiz</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                    | | 
					 | 
				
			||||||
                    <button mat-icon-button (click)="deleteItem(row.id)"> | 
					 | 
				
			||||||
                        <mat-icon>delete</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | 
					 | 
				
			||||||
            <tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;"></tr> | 
					 | 
				
			||||||
            <tr class="mat-row" *matNoDataRow> | 
					 | 
				
			||||||
                <td class="mat-cell">No data</td> | 
					 | 
				
			||||||
            </tr> | 
					 | 
				
			||||||
        </table> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> | 
					 | 
				
			||||||
    </mat-paginator> --> | 
					 | 
				
			||||||
</div> | 
					 | 
				
			||||||
@ -1,67 +0,0 @@ | 
				
			|||||||
import { Component } from '@angular/core'; | 
					 | 
				
			||||||
import { Area } from 'src/app/models'; | 
					 | 
				
			||||||
import { NewPayloadModel, PayloadModel } from 'src/app/models/payload.model'; | 
					 | 
				
			||||||
import { PayloadInfoDialogComponent } from '../../dialogs'; | 
					 | 
				
			||||||
import { NewPayloadDialogComponent } from '../../dialogs/new-payload-dialog/new-payload-dialog.component'; | 
					 | 
				
			||||||
import { TableComponent } from '../base-table/base-table.component'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'payload-table', | 
					 | 
				
			||||||
  templateUrl: './payload-table.component.html', | 
					 | 
				
			||||||
  styleUrls: ['../base-table/base-table.component.less'], | 
					 | 
				
			||||||
  providers: [{ provide: 'area', useValue: 'payloads' }] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class PayloadComponent extends TableComponent<PayloadModel> { | 
					 | 
				
			||||||
  area = 'payloads' as Area | 
					 | 
				
			||||||
  displayedColumns = ["name", "mime_type", "size", 'actions']; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  showItemDialog(id: string | null) { | 
					 | 
				
			||||||
    if (id === null) { | 
					 | 
				
			||||||
      const payload: NewPayloadModel = { | 
					 | 
				
			||||||
        name: "", | 
					 | 
				
			||||||
        data: [] | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
      const dialog = this.infoDialog.open(NewPayloadDialogComponent, { | 
					 | 
				
			||||||
        data: payload, | 
					 | 
				
			||||||
        width: '1000px', | 
					 | 
				
			||||||
      }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      dialog.componentInstance.onSave.subscribe(result => { | 
					 | 
				
			||||||
        this.dataSource.createPayload(result) | 
					 | 
				
			||||||
          .subscribe(_ => { | 
					 | 
				
			||||||
            alert("Created") | 
					 | 
				
			||||||
            this.loadTableData() | 
					 | 
				
			||||||
          }) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        dialog.close() | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      dialog.afterClosed().subscribe(_ => { | 
					 | 
				
			||||||
        this.router.navigate(['.'], { relativeTo: this.route }) | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    } else { | 
					 | 
				
			||||||
      this.dataSource.getPayload(id as string).subscribe(resp => { | 
					 | 
				
			||||||
        const dialog = this.infoDialog.open(PayloadInfoDialogComponent, { | 
					 | 
				
			||||||
          data: resp, | 
					 | 
				
			||||||
          width: '1000px', | 
					 | 
				
			||||||
        }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const saveSub = dialog.componentInstance.onSave.subscribe(result => { | 
					 | 
				
			||||||
          this.dataSource.updatePayload(result) | 
					 | 
				
			||||||
            .subscribe(_ => { | 
					 | 
				
			||||||
              alert("Updated") | 
					 | 
				
			||||||
              this.loadTableData() | 
					 | 
				
			||||||
            }) | 
					 | 
				
			||||||
          dialog.close() | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        dialog.afterClosed().subscribe(_ => { | 
					 | 
				
			||||||
          saveSub.unsubscribe() | 
					 | 
				
			||||||
          this.router.navigate(['.'], { relativeTo: this.route }) | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,81 +0,0 @@ | 
				
			|||||||
<div class="mat-elevation-z8"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <div class="table-container"> | 
					 | 
				
			||||||
        <div class="loading-shade" *ngIf="isLoadingResults"> | 
					 | 
				
			||||||
            <mat-spinner *ngIf="isLoadingResults"></mat-spinner> | 
					 | 
				
			||||||
        </div> | 
					 | 
				
			||||||
        <mat-form-field appearance="standard"> | 
					 | 
				
			||||||
            <mat-label>Filter</mat-label> | 
					 | 
				
			||||||
            <input matInput (keyup)="applyFilter($event)" #input> | 
					 | 
				
			||||||
        </mat-form-field> | 
					 | 
				
			||||||
        <button id="refresh_btn" mat-raised-button color="primary" (click)="loadTableData()">Refresh</button> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <table mat-table fixedLayout="true" [dataSource]="table_data" class="data-table" matSort matSortActive="id" | 
					 | 
				
			||||||
            matSortDisableClear matSortDirection="desc"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="id"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>ID</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.id}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="alias"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Alias</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.alias}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="agent_id"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Agent</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    <a routerLink='/agents' [queryParams]="{id: row.agent_id}">{{row.agent_id}}</a> | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="job_id"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Job</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    <a routerLink='/jobs' [queryParams]="{id: row.job_id}">{{row.job_id}}</a> | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="state"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>State</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.state}} {{(row.state === "Finished") ? '(' + row.retcode + ')' : ''}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="last_updated"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef>Last updated</th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    {{row.updated.secs_since_epoch * 1000| date:'long'}} | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <ng-container matColumnDef="actions"> | 
					 | 
				
			||||||
                <th mat-header-cell *matHeaderCellDef></th> | 
					 | 
				
			||||||
                <td mat-cell *matCellDef="let row"> | 
					 | 
				
			||||||
                    <button mat-icon-button routerLink='.' [queryParams]="{id: row.id}"> | 
					 | 
				
			||||||
                        <mat-icon>more_horiz</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                    | | 
					 | 
				
			||||||
                    <button mat-icon-button (click)="deleteItem(row.id)"> | 
					 | 
				
			||||||
                        <mat-icon>delete</mat-icon> | 
					 | 
				
			||||||
                    </button> | 
					 | 
				
			||||||
                </td> | 
					 | 
				
			||||||
            </ng-container> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> | 
					 | 
				
			||||||
            <tr mat-row class="data-table-row" *matRowDef="let row; columns: displayedColumns;"></tr> | 
					 | 
				
			||||||
            <tr class="mat-row" *matNoDataRow> | 
					 | 
				
			||||||
                <td class="mat-cell">No data</td> | 
					 | 
				
			||||||
            </tr> | 
					 | 
				
			||||||
        </table> | 
					 | 
				
			||||||
    </div> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <!-- <mat-paginator [length]="resultsLength" [pageSize]="30" aria-label="Select page of GitHub search results"> | 
					 | 
				
			||||||
    </mat-paginator> --> | 
					 | 
				
			||||||
</div> | 
					 | 
				
			||||||
@ -1,36 +0,0 @@ | 
				
			|||||||
import { Component, OnInit } from '@angular/core'; | 
					 | 
				
			||||||
import { TableComponent } from '../base-table/base-table.component'; | 
					 | 
				
			||||||
import { Area, ResultModel } from '../../../models'; | 
					 | 
				
			||||||
import { ResultInfoDialogComponent } from '../../dialogs'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component({ | 
					 | 
				
			||||||
  selector: 'results-table', | 
					 | 
				
			||||||
  templateUrl: './result-table.component.html', | 
					 | 
				
			||||||
  styleUrls: ['../base-table/base-table.component.less'], | 
					 | 
				
			||||||
  providers: [{ provide: 'area', useValue: 'map' }] | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class ResultComponent extends TableComponent<ResultModel> { | 
					 | 
				
			||||||
  area = 'map' as Area | 
					 | 
				
			||||||
  displayedColumns = [ | 
					 | 
				
			||||||
    'id', | 
					 | 
				
			||||||
    'alias', | 
					 | 
				
			||||||
    'agent_id', | 
					 | 
				
			||||||
    'job_id', | 
					 | 
				
			||||||
    'state', | 
					 | 
				
			||||||
    'last_updated', | 
					 | 
				
			||||||
    'actions' | 
					 | 
				
			||||||
  ]; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  showItemDialog(id: string) { | 
					 | 
				
			||||||
    this.dataSource.getResult(id).subscribe(resp => { | 
					 | 
				
			||||||
      const dialog = this.infoDialog.open(ResultInfoDialogComponent, { | 
					 | 
				
			||||||
        data: resp, | 
					 | 
				
			||||||
        width: '1000px', | 
					 | 
				
			||||||
      }); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      dialog.afterClosed().subscribe(_ => { | 
					 | 
				
			||||||
        this.router.navigate(['.'], { relativeTo: this.route }) | 
					 | 
				
			||||||
      }) | 
					 | 
				
			||||||
    }) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1 +0,0 @@ | 
				
			|||||||
export * from './services'; 
 | 
					 | 
				
			||||||
@ -1,16 +0,0 @@ | 
				
			|||||||
import { UTCDate } from "."; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface AgentModel { | 
					 | 
				
			||||||
    alias: string | null, | 
					 | 
				
			||||||
    hostname: string, | 
					 | 
				
			||||||
    host_info: string, | 
					 | 
				
			||||||
    id: string, | 
					 | 
				
			||||||
    is_root: boolean, | 
					 | 
				
			||||||
    is_root_allowed: boolean, | 
					 | 
				
			||||||
    last_active: UTCDate, | 
					 | 
				
			||||||
    platform: string, | 
					 | 
				
			||||||
    regtime: UTCDate, | 
					 | 
				
			||||||
    state: "new" | "active" | "banned", | 
					 | 
				
			||||||
    token: string | null, | 
					 | 
				
			||||||
    username: string, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,20 +0,0 @@ | 
				
			|||||||
import { AgentModel } from './agent.model'; | 
					 | 
				
			||||||
import { JobModel } from './job.model'; | 
					 | 
				
			||||||
import { PayloadModel } from './payload.model'; | 
					 | 
				
			||||||
import { ResultModel } from './result.model'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export * from './agent.model'; | 
					 | 
				
			||||||
export * from './result.model'; | 
					 | 
				
			||||||
export * from './job.model'; | 
					 | 
				
			||||||
export * from './payload.model'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface UTCDate { | 
					 | 
				
			||||||
    secs_since_epoch: number, | 
					 | 
				
			||||||
    nanos_since_epoch: number | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type Area = "agents" | "jobs" | "map" | "payloads"; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type ApiModel = AgentModel | JobModel | ResultModel | PayloadModel | Empty; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface Empty { } | 
					 | 
				
			||||||
@ -1,16 +0,0 @@ | 
				
			|||||||
import { PayloadModel } from './' | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface JobModel { | 
					 | 
				
			||||||
    alias: string | null, | 
					 | 
				
			||||||
    argv: string, | 
					 | 
				
			||||||
    id?: string, | 
					 | 
				
			||||||
    exec_type: string, | 
					 | 
				
			||||||
    target_platforms: string, | 
					 | 
				
			||||||
    payload_id: string | null, | 
					 | 
				
			||||||
    schedule: string | null, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface Job { | 
					 | 
				
			||||||
    meta: JobModel, | 
					 | 
				
			||||||
    payload: PayloadModel | null, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,12 +0,0 @@ | 
				
			|||||||
export interface PayloadModel { | 
					 | 
				
			||||||
    id: string, | 
					 | 
				
			||||||
    mime_type: string, | 
					 | 
				
			||||||
    name: string, | 
					 | 
				
			||||||
    size: number, | 
					 | 
				
			||||||
    data: number[] | null | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface NewPayloadModel { | 
					 | 
				
			||||||
    name: string, | 
					 | 
				
			||||||
    data: number[] | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,18 +0,0 @@ | 
				
			|||||||
import { UTCDate } from "."; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface ResultModel { | 
					 | 
				
			||||||
    agent_id: string, | 
					 | 
				
			||||||
    alias: string, | 
					 | 
				
			||||||
    created: UTCDate, | 
					 | 
				
			||||||
    id: string, | 
					 | 
				
			||||||
    job_id: string, | 
					 | 
				
			||||||
    result: number[] | null, | 
					 | 
				
			||||||
    state: "Queued" | "Running" | "Finished", | 
					 | 
				
			||||||
    retcode: number | null, | 
					 | 
				
			||||||
    updated: UTCDate, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface AssignedJobByIdModel { | 
					 | 
				
			||||||
    job_id: string, | 
					 | 
				
			||||||
    agent_id: string | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,142 +0,0 @@ | 
				
			|||||||
import { environment } from 'src/environments/environment'; | 
					 | 
				
			||||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http'; | 
					 | 
				
			||||||
import { Observable, map, catchError, throwError } from 'rxjs'; | 
					 | 
				
			||||||
import { ApiModel, PayloadModel, Empty, Area, AgentModel, JobModel, ResultModel, Job, NewPayloadModel, AssignedJobByIdModel } from '../models'; | 
					 | 
				
			||||||
import { Injectable, Inject } from '@angular/core'; | 
					 | 
				
			||||||
import { ErrorService } from './error.service'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Status = "ok" | "err"; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
interface ServerResponse<T extends ApiModel> { | 
					 | 
				
			||||||
  status: Status, | 
					 | 
				
			||||||
  data: T | string | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable({ | 
					 | 
				
			||||||
  providedIn: 'root' | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class ApiTableService { | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor( | 
					 | 
				
			||||||
    private http: HttpClient, | 
					 | 
				
			||||||
    private errorService: ErrorService | 
					 | 
				
			||||||
  ) { | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  requestUrl = `${environment.server}/cmd/`; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  req<R extends ApiModel>(cmd: string): Observable<ServerResponse<R>> { | 
					 | 
				
			||||||
    return this.http.post<ServerResponse<R>>(this.requestUrl, cmd) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getOne<T extends ApiModel>(id: string, area: Area, brief: 'yes' | 'no' | 'auto' | null = null): Observable<T> { | 
					 | 
				
			||||||
    const request = `${area} read ${id}` + (brief !== null ? `-b=${brief}` : '') | 
					 | 
				
			||||||
    const resp = this.req<T[]>(request).pipe( | 
					 | 
				
			||||||
      map(resp => { | 
					 | 
				
			||||||
        if (resp.data.length === 0) { | 
					 | 
				
			||||||
          return { | 
					 | 
				
			||||||
            status: 'err' as Status, | 
					 | 
				
			||||||
            data: `${id} not found in ${area}` | 
					 | 
				
			||||||
          } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        return { | 
					 | 
				
			||||||
          status: resp.status, | 
					 | 
				
			||||||
          data: resp.data[0] | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
      })); | 
					 | 
				
			||||||
    return this.filterErrStatus(resp) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getAgent(id: string): Observable<AgentModel> { | 
					 | 
				
			||||||
    return this.getOne(id, 'agents') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getJob(id: string): Observable<Job> { | 
					 | 
				
			||||||
    return this.getOne(id, 'jobs') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getResult(id: string): Observable<ResultModel> { | 
					 | 
				
			||||||
    return this.getOne(id, 'map') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getPayload(id: string): Observable<PayloadModel> { | 
					 | 
				
			||||||
    return this.getOne(id, 'payloads') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getMany(area: Area): Observable<any[]> { | 
					 | 
				
			||||||
    return this.filterErrStatus(this.req(`${area} read`)) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getAgents(): Observable<AgentModel[]> { | 
					 | 
				
			||||||
    return this.getMany('agents') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getJobs(): Observable<JobModel[]> { | 
					 | 
				
			||||||
    return this.getMany('jobs') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getResults(): Observable<ResultModel[]> { | 
					 | 
				
			||||||
    return this.getMany('map') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getPayloads(): Observable<PayloadModel[]> { | 
					 | 
				
			||||||
    return this.getMany('payloads') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  update<T extends ApiModel>(item: T, area: Area): Observable<Empty> { | 
					 | 
				
			||||||
    return this.filterErrStatus(this.req(`${area} update '${JSON.stringify(item)}'`)) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updateAgent(item: AgentModel): Observable<Empty> { | 
					 | 
				
			||||||
    return this.update(item, 'agents') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updateJob(item: JobModel): Observable<Empty> { | 
					 | 
				
			||||||
    return this.update(item, 'jobs') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updateResult(item: ResultModel): Observable<Empty> { | 
					 | 
				
			||||||
    return this.update(item, 'map') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  updatePayload(item: PayloadModel): Observable<Empty> { | 
					 | 
				
			||||||
    return this.update(item, 'payloads') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  delete(id: string, area: Area): Observable<Empty> { | 
					 | 
				
			||||||
    return this.filterErrStatus(this.req(`${area} delete ${id}`)) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  create<T extends ApiModel>(item: T | null, area: Area): Observable<string[]> { | 
					 | 
				
			||||||
    var serialized = '"{}"' | 
					 | 
				
			||||||
    if (item) { | 
					 | 
				
			||||||
      serialized = JSON.stringify(item); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    return this.filterErrStatus(this.req(`${area} create '${serialized}'`)) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  createResult(item: AssignedJobByIdModel[]): Observable<string[]> { | 
					 | 
				
			||||||
    return this.create(item, 'map') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  createPayload(item: NewPayloadModel): Observable<string[]> { | 
					 | 
				
			||||||
    return this.create(item, 'payloads') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  filterErrStatus<R extends ApiModel>(obs: Observable<ServerResponse<R>>): Observable<R> { | 
					 | 
				
			||||||
    return obs.pipe( | 
					 | 
				
			||||||
      map(r => { | 
					 | 
				
			||||||
        if (r.status == 'err') { | 
					 | 
				
			||||||
          throw new Error(r.data as string) | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        return r.data as R | 
					 | 
				
			||||||
      }), | 
					 | 
				
			||||||
      catchError(this.errorHandler.bind(this))) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  errorHandler(err: HttpErrorResponse, caught: any) { | 
					 | 
				
			||||||
    var error = err.error.data !== undefined ? JSON.stringify(err.error.data) : err.message; | 
					 | 
				
			||||||
    this.errorService.handle(error); | 
					 | 
				
			||||||
    return throwError(() => new Error()); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,17 +0,0 @@ | 
				
			|||||||
import { Injectable } from '@angular/core'; | 
					 | 
				
			||||||
import { Subject } from 'rxjs'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Injectable({ | 
					 | 
				
			||||||
  providedIn: 'root' | 
					 | 
				
			||||||
}) | 
					 | 
				
			||||||
export class ErrorService { | 
					 | 
				
			||||||
  error$ = new Subject<string>(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handle(msg: string) { | 
					 | 
				
			||||||
    this.error$.next(msg) | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  clear() { | 
					 | 
				
			||||||
    this.handle('') | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1 +0,0 @@ | 
				
			|||||||
export * from './api.service' | 
					 | 
				
			||||||
@ -1,3 +0,0 @@ | 
				
			|||||||
export function epochToStr(epoch: number): string { | 
					 | 
				
			||||||
    return new Date(epoch * 1000).toLocaleString('en-GB') | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,4 +0,0 @@ | 
				
			|||||||
export const environment = { | 
					 | 
				
			||||||
  production: true, | 
					 | 
				
			||||||
  server: "", | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
@ -1,17 +0,0 @@ | 
				
			|||||||
// This file can be replaced during build by using the `fileReplacements` array.
 | 
					 | 
				
			||||||
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
 | 
					 | 
				
			||||||
// The list of file replacements can be found in `angular.json`.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const environment = { | 
					 | 
				
			||||||
  production: false, | 
					 | 
				
			||||||
  server: "http://127.0.0.1:7799", | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* | 
					 | 
				
			||||||
 * For easier debugging in development mode, you can import the following file | 
					 | 
				
			||||||
 * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * This import should be commented out in production mode because it will have a negative impact | 
					 | 
				
			||||||
 * on performance if an error is thrown. | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
// import 'zone.js/plugins/zone-error';  // Included with Angular CLI.
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 948 B  | 
@ -1,18 +0,0 @@ | 
				
			|||||||
<!doctype html> | 
					 | 
				
			||||||
<html lang="en"> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<head> | 
					 | 
				
			||||||
  <meta charset="utf-8"> | 
					 | 
				
			||||||
  <title>Fe</title> | 
					 | 
				
			||||||
  <meta name="viewport" content="width=device-width, initial-scale=1"> | 
					 | 
				
			||||||
  <link rel="icon" type="image/x-icon" href="favicon.ico"> | 
					 | 
				
			||||||
  <link rel="preconnect" href="https://fonts.gstatic.com"> | 
					 | 
				
			||||||
  <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@300;400;500&display=swap" rel="stylesheet"> | 
					 | 
				
			||||||
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | 
					 | 
				
			||||||
</head> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<body class="mat-typography"> | 
					 | 
				
			||||||
  <app-root></app-root> | 
					 | 
				
			||||||
</body> | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</html> | 
					 | 
				
			||||||
@ -1,12 +0,0 @@ | 
				
			|||||||
import { enableProdMode } from '@angular/core'; | 
					 | 
				
			||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { AppModule } from './app/app.module'; | 
					 | 
				
			||||||
import { environment } from './environments/environment'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if (environment.production) { | 
					 | 
				
			||||||
  enableProdMode(); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
platformBrowserDynamic().bootstrapModule(AppModule) | 
					 | 
				
			||||||
  .catch(err => console.error(err)); | 
					 | 
				
			||||||
@ -1,53 +0,0 @@ | 
				
			|||||||
/** | 
					 | 
				
			||||||
 * This file includes polyfills needed by Angular and is loaded before the app. | 
					 | 
				
			||||||
 * You can add your own extra polyfills to this file. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * This file is divided into 2 sections: | 
					 | 
				
			||||||
 *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. | 
					 | 
				
			||||||
 *   2. Application imports. Files imported after ZoneJS that should be loaded before your main | 
					 | 
				
			||||||
 *      file. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * The current setup is for so-called "evergreen" browsers; the last versions of browsers that | 
					 | 
				
			||||||
 * automatically update themselves. This includes recent versions of Safari, Chrome (including | 
					 | 
				
			||||||
 * Opera), Edge on the desktop, and iOS and Chrome on mobile. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * Learn more in https://angular.io/guide/browser-support
 | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*************************************************************************************************** | 
					 | 
				
			||||||
 * BROWSER POLYFILLS | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** | 
					 | 
				
			||||||
 * By default, zone.js will patch all possible macroTask and DomEvents | 
					 | 
				
			||||||
 * user can disable parts of macroTask/DomEvents patch by setting following flags | 
					 | 
				
			||||||
 * because those flags need to be set before `zone.js` being loaded, and webpack | 
					 | 
				
			||||||
 * will put import in the top of bundle, so user need to create a separate file | 
					 | 
				
			||||||
 * in this directory (for example: zone-flags.ts), and put the following flags | 
					 | 
				
			||||||
 * into that file, and then add the following code before importing zone.js. | 
					 | 
				
			||||||
 * import './zone-flags'; | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * The flags allowed in zone-flags.ts are listed here. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * The following flags will work for all browsers. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
 | 
					 | 
				
			||||||
 * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
 | 
					 | 
				
			||||||
 * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
 | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js | 
					 | 
				
			||||||
 *  with the following flag, it will bypass `zone.js` patch for IE/Edge | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 *  (window as any).__Zone_enable_cross_context_check = true; | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*************************************************************************************************** | 
					 | 
				
			||||||
 * Zone JS is required by default for Angular itself. | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
import 'zone.js';  // Included with Angular CLI.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*************************************************************************************************** | 
					 | 
				
			||||||
 * APPLICATION IMPORTS | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
@ -1,4 +0,0 @@ | 
				
			|||||||
/* You can add global styles to this file, and also import other style files */ | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
html, body { height: 100%; } | 
					 | 
				
			||||||
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } | 
					 | 
				
			||||||
@ -1,26 +0,0 @@ | 
				
			|||||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import 'zone.js/testing'; | 
					 | 
				
			||||||
import { getTestBed } from '@angular/core/testing'; | 
					 | 
				
			||||||
import { | 
					 | 
				
			||||||
  BrowserDynamicTestingModule, | 
					 | 
				
			||||||
  platformBrowserDynamicTesting | 
					 | 
				
			||||||
} from '@angular/platform-browser-dynamic/testing'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
declare const require: { | 
					 | 
				
			||||||
  context(path: string, deep?: boolean, filter?: RegExp): { | 
					 | 
				
			||||||
    <T>(id: string): T; | 
					 | 
				
			||||||
    keys(): string[]; | 
					 | 
				
			||||||
  }; | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// First, initialize the Angular testing environment.
 | 
					 | 
				
			||||||
getTestBed().initTestEnvironment( | 
					 | 
				
			||||||
  BrowserDynamicTestingModule, | 
					 | 
				
			||||||
  platformBrowserDynamicTesting(), | 
					 | 
				
			||||||
); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Then we find all the tests.
 | 
					 | 
				
			||||||
const context = require.context('./', true, /\.spec\.ts$/); | 
					 | 
				
			||||||
// And load the modules.
 | 
					 | 
				
			||||||
context.keys().map(context); | 
					 | 
				
			||||||
@ -1,15 +0,0 @@ | 
				
			|||||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
  "extends": "./tsconfig.json", | 
					 | 
				
			||||||
  "compilerOptions": { | 
					 | 
				
			||||||
    "outDir": "./out-tsc/app", | 
					 | 
				
			||||||
    "types": [] | 
					 | 
				
			||||||
  }, | 
					 | 
				
			||||||
  "files": [ | 
					 | 
				
			||||||
    "src/main.ts", | 
					 | 
				
			||||||
    "src/polyfills.ts" | 
					 | 
				
			||||||
  ], | 
					 | 
				
			||||||
  "include": [ | 
					 | 
				
			||||||
    "src/**/*.d.ts" | 
					 | 
				
			||||||
  ] | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,32 +0,0 @@ | 
				
			|||||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
  "compileOnSave": false, | 
					 | 
				
			||||||
  "compilerOptions": { | 
					 | 
				
			||||||
    "baseUrl": "./", | 
					 | 
				
			||||||
    "outDir": "./dist/out-tsc", | 
					 | 
				
			||||||
    "forceConsistentCasingInFileNames": true, | 
					 | 
				
			||||||
    "strict": true, | 
					 | 
				
			||||||
    "noImplicitOverride": true, | 
					 | 
				
			||||||
    "noPropertyAccessFromIndexSignature": true, | 
					 | 
				
			||||||
    "noImplicitReturns": true, | 
					 | 
				
			||||||
    "noFallthroughCasesInSwitch": true, | 
					 | 
				
			||||||
    "sourceMap": true, | 
					 | 
				
			||||||
    "declaration": false, | 
					 | 
				
			||||||
    "downlevelIteration": true, | 
					 | 
				
			||||||
    "experimentalDecorators": true, | 
					 | 
				
			||||||
    "moduleResolution": "node", | 
					 | 
				
			||||||
    "importHelpers": true, | 
					 | 
				
			||||||
    "target": "es2017", | 
					 | 
				
			||||||
    "module": "es2020", | 
					 | 
				
			||||||
    "lib": [ | 
					 | 
				
			||||||
      "es2020", | 
					 | 
				
			||||||
      "dom" | 
					 | 
				
			||||||
    ] | 
					 | 
				
			||||||
  }, | 
					 | 
				
			||||||
  "angularCompilerOptions": { | 
					 | 
				
			||||||
    "enableI18nLegacyMessageIdFormat": false, | 
					 | 
				
			||||||
    "strictInjectionParameters": true, | 
					 | 
				
			||||||
    "strictInputAccessModifiers": true, | 
					 | 
				
			||||||
    "strictTemplates": true | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,18 +0,0 @@ | 
				
			|||||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */ | 
					 | 
				
			||||||
{ | 
					 | 
				
			||||||
  "extends": "./tsconfig.json", | 
					 | 
				
			||||||
  "compilerOptions": { | 
					 | 
				
			||||||
    "outDir": "./out-tsc/spec", | 
					 | 
				
			||||||
    "types": [ | 
					 | 
				
			||||||
      "jasmine" | 
					 | 
				
			||||||
    ] | 
					 | 
				
			||||||
  }, | 
					 | 
				
			||||||
  "files": [ | 
					 | 
				
			||||||
    "src/test.ts", | 
					 | 
				
			||||||
    "src/polyfills.ts" | 
					 | 
				
			||||||
  ], | 
					 | 
				
			||||||
  "include": [ | 
					 | 
				
			||||||
    "src/**/*.spec.ts", | 
					 | 
				
			||||||
    "src/**/*.d.ts" | 
					 | 
				
			||||||
  ] | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,96 +0,0 @@ | 
				
			|||||||
mod error; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::{process_cmd, Args}; | 
					 | 
				
			||||||
use actix_cors::Cors; | 
					 | 
				
			||||||
use actix_web::{get, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder}; | 
					 | 
				
			||||||
use error::Error; | 
					 | 
				
			||||||
use futures_util::StreamExt; | 
					 | 
				
			||||||
use rust_embed::RustEmbed; | 
					 | 
				
			||||||
use std::borrow::Cow; | 
					 | 
				
			||||||
use structopt::StructOpt; | 
					 | 
				
			||||||
use u_lib::{api::HttpClient, unwrap_enum}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(RustEmbed)] | 
					 | 
				
			||||||
#[folder = "./src/gui/fe/dist/fe/"] | 
					 | 
				
			||||||
struct Files; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Files { | 
					 | 
				
			||||||
    pub fn get_static(path: impl AsRef<str>) -> Option<&'static [u8]> { | 
					 | 
				
			||||||
        let file = Self::get(path.as_ref())?.data; | 
					 | 
				
			||||||
        Some(unwrap_enum!(file, Cow::Borrowed)) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async fn spa_main() -> impl Responder { | 
					 | 
				
			||||||
    let index = Files::get_static("index.html").unwrap(); | 
					 | 
				
			||||||
    HttpResponse::Ok().body(index) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[get("/core/{path}")] | 
					 | 
				
			||||||
async fn resources_adapter(path: web::Path<(String,)>) -> impl Responder { | 
					 | 
				
			||||||
    let path = path.into_inner().0; | 
					 | 
				
			||||||
    let mimetype = mime_guess::from_path(&path).first_or_octet_stream(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    match Files::get_static(path) { | 
					 | 
				
			||||||
        Some(data) => HttpResponse::Ok() | 
					 | 
				
			||||||
            .content_type(mimetype.to_string()) | 
					 | 
				
			||||||
            .body(data), | 
					 | 
				
			||||||
        None => HttpResponse::NotFound().finish(), | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[post("/cmd/")] | 
					 | 
				
			||||||
async fn send_cmd( | 
					 | 
				
			||||||
    mut body: web::Payload, | 
					 | 
				
			||||||
    client: web::Data<HttpClient>, | 
					 | 
				
			||||||
) -> Result<impl Responder, Error> { | 
					 | 
				
			||||||
    let mut bytes = web::BytesMut::new(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while let Some(item) = body.next().await { | 
					 | 
				
			||||||
        bytes.extend_from_slice( | 
					 | 
				
			||||||
            &item.map_err(|e| Error::JustError(format!("payload loading failure: {e}")))?, | 
					 | 
				
			||||||
        ); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let cmd = String::from_utf8(bytes.to_vec()) | 
					 | 
				
			||||||
        .map_err(|_| Error::JustError("cmd contains non-utf8 data".to_string()))?; | 
					 | 
				
			||||||
    let mut cmd = shlex::split(&cmd).ok_or(Error::JustError("argparse failed".to_string()))?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    info!("cmd: {:?}", cmd); | 
					 | 
				
			||||||
    cmd.insert(0, String::from("u_panel")); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let parsed_cmd = Args::from_iter_safe(cmd)?; | 
					 | 
				
			||||||
    let result = process_cmd(client.as_ref().clone(), parsed_cmd).await; | 
					 | 
				
			||||||
    let result_string = result.to_string(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let response = if result.is_ok() { | 
					 | 
				
			||||||
        HttpResponse::Ok().body(result_string) | 
					 | 
				
			||||||
    } else { | 
					 | 
				
			||||||
        HttpResponse::BadRequest().body(result_string) | 
					 | 
				
			||||||
    }; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ok(response) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub async fn serve(client: HttpClient) -> anyhow::Result<()> { | 
					 | 
				
			||||||
    info!("Connecting to u_server..."); | 
					 | 
				
			||||||
    client.ping().await?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let addr = "127.0.0.1:7799"; | 
					 | 
				
			||||||
    info!("Connected, instanciating u_panel at http://{}", addr); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    HttpServer::new(move || { | 
					 | 
				
			||||||
        App::new() | 
					 | 
				
			||||||
            .wrap(Logger::default()) | 
					 | 
				
			||||||
            .wrap(Cors::permissive()) | 
					 | 
				
			||||||
            .app_data(web::Data::new(client.clone())) | 
					 | 
				
			||||||
            .service(send_cmd) | 
					 | 
				
			||||||
            .service(resources_adapter) | 
					 | 
				
			||||||
            .service(web::resource("/").to(spa_main)) | 
					 | 
				
			||||||
            .service(web::resource("/{_}").to(spa_main)) | 
					 | 
				
			||||||
    }) | 
					 | 
				
			||||||
    .bind(addr)? | 
					 | 
				
			||||||
    .run() | 
					 | 
				
			||||||
    .await?; | 
					 | 
				
			||||||
    Ok(()) | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,25 +1,143 @@ | 
				
			|||||||
mod argparse; | 
					use std::env; | 
				
			||||||
mod gui; | 
					use std::fmt; | 
				
			||||||
 | 
					use structopt::StructOpt; | 
				
			||||||
 | 
					use u_lib::{ | 
				
			||||||
 | 
					    api::ClientHandler, datatypes::DataResult, messaging::AsMsg, models::JobMeta, utils::init_env, | 
				
			||||||
 | 
					    UResult, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use uuid::Uuid; | 
				
			||||||
 | 
					
 | 
				
			||||||
#[macro_use] | 
					#[derive(StructOpt, Debug)] | 
				
			||||||
extern crate tracing; | 
					struct Args { | 
				
			||||||
 | 
					    #[structopt(subcommand)] | 
				
			||||||
 | 
					    cmd: Cmd, | 
				
			||||||
 | 
					    #[structopt(long)] | 
				
			||||||
 | 
					    json: bool, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::Result as AnyResult; | 
					#[derive(StructOpt, Debug)] | 
				
			||||||
use argparse::{process_cmd, Args}; | 
					enum Cmd { | 
				
			||||||
use structopt::StructOpt; | 
					    Agents(LD), | 
				
			||||||
use u_lib::api::HttpClient; | 
					    Jobs(JobALD), | 
				
			||||||
use u_lib::config::AccessEnv; | 
					    Jobmap(JobMapALD), | 
				
			||||||
use u_lib::logging::init_logger; | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(StructOpt, Debug)] | 
				
			||||||
 | 
					enum JobALD { | 
				
			||||||
 | 
					    Add { | 
				
			||||||
 | 
					        #[structopt(long, parse(try_from_str = parse_uuid))] | 
				
			||||||
 | 
					        agent: Option<Uuid>, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #[structopt(long)] | 
				
			||||||
 | 
					        alias: String, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #[structopt(subcommand)] | 
				
			||||||
 | 
					        cmd: JobCmd, | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    #[structopt(flatten)] | 
				
			||||||
 | 
					    LD(LD), | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(StructOpt, Debug)] | 
				
			||||||
 | 
					enum JobCmd { | 
				
			||||||
 | 
					    #[structopt(external_subcommand)] | 
				
			||||||
 | 
					    Cmd(Vec<String>), | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_web::main] | 
					#[derive(StructOpt, Debug)] | 
				
			||||||
async fn main() -> AnyResult<()> { | 
					enum JobMapALD { | 
				
			||||||
    init_logger(None); | 
					    Add { | 
				
			||||||
 | 
					        #[structopt(parse(try_from_str = parse_uuid))] | 
				
			||||||
 | 
					        agent_uid: Uuid, | 
				
			||||||
 | 
					
 | 
				
			||||||
    let env = AccessEnv::load()?; | 
					        job_idents: Vec<String>, | 
				
			||||||
    let client = HttpClient::new(&env.u_server, Some(env.admin_auth_token)).await?; | 
					    }, | 
				
			||||||
    let args = Args::from_args(); | 
					    List { | 
				
			||||||
    let result = process_cmd(client, args).await.to_string(); | 
					        #[structopt(parse(try_from_str = parse_uuid))] | 
				
			||||||
 | 
					        uid: Option<Uuid>, | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    Delete { | 
				
			||||||
 | 
					        #[structopt(parse(try_from_str = parse_uuid))] | 
				
			||||||
 | 
					        uid: Uuid, | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(StructOpt, Debug)] | 
				
			||||||
 | 
					enum LD { | 
				
			||||||
 | 
					    List { | 
				
			||||||
 | 
					        #[structopt(parse(try_from_str = parse_uuid))] | 
				
			||||||
 | 
					        uid: Option<Uuid>, | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    Delete { | 
				
			||||||
 | 
					        #[structopt(parse(try_from_str = parse_uuid))] | 
				
			||||||
 | 
					        uid: Uuid, | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parse_uuid(src: &str) -> Result<Uuid, String> { | 
				
			||||||
 | 
					    Uuid::parse_str(src).map_err(|e| e.to_string()) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn process_cmd(args: Args) { | 
				
			||||||
 | 
					    struct Printer { | 
				
			||||||
 | 
					        json: bool, | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Printer { | 
				
			||||||
 | 
					        pub fn print<Msg: AsMsg + fmt::Display>(&self, data: UResult<Msg>) { | 
				
			||||||
 | 
					            if self.json { | 
				
			||||||
 | 
					                let data = match data { | 
				
			||||||
 | 
					                    Ok(r) => DataResult::Ok(r), | 
				
			||||||
 | 
					                    Err(e) => DataResult::Err(e), | 
				
			||||||
 | 
					                }; | 
				
			||||||
 | 
					                println!("{}", serde_json::to_string_pretty(&data).unwrap()); | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                match data { | 
				
			||||||
 | 
					                    Ok(r) => println!("{}", r), | 
				
			||||||
 | 
					                    Err(e) => eprintln!("Error: {}", e), | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let token = env::var("ADMIN_AUTH_TOKEN").expect("Authentication token is not set"); | 
				
			||||||
 | 
					    let cli_handler = ClientHandler::new(None).password(token); | 
				
			||||||
 | 
					    let printer = Printer { json: args.json }; | 
				
			||||||
 | 
					    match args.cmd { | 
				
			||||||
 | 
					        Cmd::Agents(action) => match action { | 
				
			||||||
 | 
					            LD::List { uid } => printer.print(cli_handler.get_agents(uid).await), | 
				
			||||||
 | 
					            LD::Delete { uid } => printer.print(cli_handler.del(Some(uid)).await), | 
				
			||||||
 | 
					        }, | 
				
			||||||
 | 
					        Cmd::Jobs(action) => match action { | 
				
			||||||
 | 
					            JobALD::Add { | 
				
			||||||
 | 
					                cmd: JobCmd::Cmd(cmd), | 
				
			||||||
 | 
					                alias, | 
				
			||||||
 | 
					                agent: _agent, | 
				
			||||||
 | 
					            } => { | 
				
			||||||
 | 
					                let job = JobMeta::builder() | 
				
			||||||
 | 
					                    .with_shell(cmd.join(" ")) | 
				
			||||||
 | 
					                    .with_alias(alias) | 
				
			||||||
 | 
					                    .build() | 
				
			||||||
 | 
					                    .unwrap(); | 
				
			||||||
 | 
					                printer.print(cli_handler.upload_jobs(&[job]).await); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            JobALD::LD(LD::List { uid }) => printer.print(cli_handler.get_jobs(uid).await), | 
				
			||||||
 | 
					            JobALD::LD(LD::Delete { uid }) => printer.print(cli_handler.del(Some(uid)).await), | 
				
			||||||
 | 
					        }, | 
				
			||||||
 | 
					        Cmd::Jobmap(action) => match action { | 
				
			||||||
 | 
					            JobMapALD::Add { | 
				
			||||||
 | 
					                agent_uid, | 
				
			||||||
 | 
					                job_idents, | 
				
			||||||
 | 
					            } => printer.print(cli_handler.set_jobs(Some(agent_uid), &job_idents).await), | 
				
			||||||
 | 
					            JobMapALD::List { uid } => printer.print(cli_handler.get_agent_jobs(uid).await), | 
				
			||||||
 | 
					            JobMapALD::Delete { uid } => printer.print(cli_handler.del(Some(uid)).await), | 
				
			||||||
 | 
					        }, | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
    println!("{result}"); | 
					#[tokio::main] | 
				
			||||||
    Ok(()) | 
					async fn main() { | 
				
			||||||
 | 
					    init_env(); | 
				
			||||||
 | 
					    let args: Args = Args::from_args(); | 
				
			||||||
 | 
					    process_cmd(args).await; | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -1,377 +1,234 @@ | 
				
			|||||||
use crate::error::Error; | 
					use diesel::{pg::PgConnection, prelude::*, result::Error as DslError}; | 
				
			||||||
use diesel::{pg::PgConnection, prelude::*, result::Error as DslError, Connection}; | 
					use once_cell::sync::OnceCell; | 
				
			||||||
use std::collections::{HashMap, HashSet}; | 
					use std::{ | 
				
			||||||
use std::mem::drop; | 
					    env, | 
				
			||||||
 | 
					    sync::{Arc, Mutex, MutexGuard}, | 
				
			||||||
 | 
					}; | 
				
			||||||
use u_lib::{ | 
					use u_lib::{ | 
				
			||||||
    db::PgAsyncPool, | 
					    models::{schema, Agent, AssignedJob, JobMeta, JobState}, | 
				
			||||||
    models::{schema, Agent, AssignedJob, AssignedJobById, Job, JobMeta, JobState, Payload}, | 
					    ULocalError, ULocalResult, | 
				
			||||||
    platform::Platform, | 
					 | 
				
			||||||
    types::Id, | 
					 | 
				
			||||||
}; | 
					}; | 
				
			||||||
use uuid::Uuid; | 
					use uuid::Uuid; | 
				
			||||||
 | 
					
 | 
				
			||||||
type Result<T> = std::result::Result<T, Error>; | 
					pub struct UDB { | 
				
			||||||
 | 
					    pub conn: PgConnection, | 
				
			||||||
pub struct PgRepo { | 
					 | 
				
			||||||
    pool: PgAsyncPool, | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PgRepo { | 
					static DB: OnceCell<Arc<Mutex<UDB>>> = OnceCell::new(); | 
				
			||||||
    pub fn new(pool: PgAsyncPool) -> PgRepo { | 
					
 | 
				
			||||||
        PgRepo { pool } | 
					#[cfg_attr(test, automock)] | 
				
			||||||
    } | 
					impl UDB { | 
				
			||||||
 | 
					    pub fn lock_db() -> MutexGuard<'static, UDB> { | 
				
			||||||
    pub async fn interact<F, R>(&self, f: F) -> Result<R> | 
					        DB.get_or_init(|| { | 
				
			||||||
    where | 
					            let _getenv = |v| env::var(v).unwrap(); | 
				
			||||||
        F: for<'c> FnOnce(UDB<'c>) -> Result<R>, | 
					            let db_host = _getenv("DB_HOST"); | 
				
			||||||
        F: Send + 'static, | 
					            let db_name = _getenv("DB_NAME"); | 
				
			||||||
        R: Send + 'static, | 
					            let db_user = _getenv("DB_USER"); | 
				
			||||||
    { | 
					            let db_password = _getenv("DB_PASSWORD"); | 
				
			||||||
        let connection = self.pool.get().await?; | 
					            let db_url = format!( | 
				
			||||||
        connection | 
					                "postgres://{}:{}@{}/{}", | 
				
			||||||
            .interact(|conn| f(UDB { conn })) | 
					                db_user, db_password, db_host, db_name | 
				
			||||||
            .await | 
					            ); | 
				
			||||||
            .expect("deadpool interaction failed") | 
					            let conn = PgConnection::establish(&db_url).unwrap(); | 
				
			||||||
    } | 
					            let instance = UDB { conn }; | 
				
			||||||
 | 
					            Arc::new(Mutex::new(instance)) | 
				
			||||||
    pub async fn transaction<F, R>(&self, f: F) -> Result<R> | 
					        }) | 
				
			||||||
    where | 
					        .lock() | 
				
			||||||
        F: for<'c> FnOnce(UDB<'c>) -> Result<R>, | 
					        .unwrap() | 
				
			||||||
        F: Send + 'static, | 
					    } | 
				
			||||||
        R: Send + 'static, | 
					
 | 
				
			||||||
    { | 
					    pub fn insert_jobs(&self, job_metas: &[JobMeta]) -> ULocalResult<()> { | 
				
			||||||
        let conn = self.pool.get().await?; | 
					 | 
				
			||||||
        conn.interact(|c| c.transaction(|conn| f(UDB { conn }))) | 
					 | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .expect("deadpool interaction failed") | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct UDB<'c> { | 
					 | 
				
			||||||
    conn: &'c mut PgConnection, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl UDB<'_> { | 
					 | 
				
			||||||
    pub fn insert_jobs(&mut self, jobs: &[JobMeta]) -> Result<()> { | 
					 | 
				
			||||||
        use schema::jobs; | 
					        use schema::jobs; | 
				
			||||||
 | 
					 | 
				
			||||||
        diesel::insert_into(jobs::table) | 
					        diesel::insert_into(jobs::table) | 
				
			||||||
            .values(jobs) | 
					            .values(job_metas) | 
				
			||||||
            .execute(self.conn) | 
					            .execute(&self.conn)?; | 
				
			||||||
            .map(drop) | 
					        Ok(()) | 
				
			||||||
            .map_err(with_err_ctx("Can't insert jobs")) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn insert_payload(&mut self, payload: &Payload) -> Result<()> { | 
					 | 
				
			||||||
        use schema::payloads; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        diesel::insert_into(payloads::table) | 
					 | 
				
			||||||
            .values(payload) | 
					 | 
				
			||||||
            .execute(self.conn) | 
					 | 
				
			||||||
            .map(drop) | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't insert payload {payload:?}"))) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_job(&mut self, id: Id) -> Result<Option<Job>> { | 
					 | 
				
			||||||
        use schema::{jobs, payloads}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let maybe_job_with_payload = jobs::table | 
					 | 
				
			||||||
            .left_join(payloads::table) | 
					 | 
				
			||||||
            .filter(jobs::id.eq(id)) | 
					 | 
				
			||||||
            .first::<(JobMeta, Option<Payload>)>(self.conn) | 
					 | 
				
			||||||
            .optional() | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't get job {id}")))?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(maybe_job_with_payload.map(|(job, payload)| Job { meta: job, payload })) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn get_jobs(&mut self) -> Result<Vec<JobMeta>> { | 
					    pub fn get_jobs(&self, uid: Option<Uuid>) -> ULocalResult<Vec<JobMeta>> { | 
				
			||||||
        use schema::jobs; | 
					        use schema::jobs; | 
				
			||||||
 | 
					        let result = if uid.is_some() { | 
				
			||||||
        jobs::table | 
					            jobs::table | 
				
			||||||
            .load(self.conn) | 
					                .filter(jobs::id.eq(uid.unwrap())) | 
				
			||||||
            .map_err(with_err_ctx("Can't get jobs")) | 
					                .get_results::<JobMeta>(&self.conn)? | 
				
			||||||
    } | 
					        } else { | 
				
			||||||
 | 
					            jobs::table.load::<JobMeta>(&self.conn)? | 
				
			||||||
    pub fn get_payload(&mut self, id: Id) -> Result<Option<Payload>> { | 
					        }; | 
				
			||||||
        use schema::payloads; | 
					        Ok(result) | 
				
			||||||
 | 
					 | 
				
			||||||
        payloads::table | 
					 | 
				
			||||||
            .filter(payloads::id.eq(id)) | 
					 | 
				
			||||||
            .first(self.conn) | 
					 | 
				
			||||||
            .optional() | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't get payload {id}"))) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_payload_by_name(&mut self, name: String) -> Result<Option<Payload>> { | 
					 | 
				
			||||||
        use schema::payloads; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        payloads::table | 
					 | 
				
			||||||
            .filter(payloads::name.eq(&name)) | 
					 | 
				
			||||||
            .first(self.conn) | 
					 | 
				
			||||||
            .optional() | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't get payload by name {name}"))) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn get_payloads(&mut self) -> Result<Vec<Payload>> { | 
					 | 
				
			||||||
        use schema::payloads; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        payloads::table | 
					 | 
				
			||||||
            .load(self.conn) | 
					 | 
				
			||||||
            .map_err(with_err_ctx("Can't get payloads")) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn payload_exists(&mut self, payload_id: Id) -> Result<bool> { | 
					 | 
				
			||||||
        use schema::payloads; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        payloads::table | 
					 | 
				
			||||||
            .find(payload_id) | 
					 | 
				
			||||||
            .first::<Payload>(self.conn) | 
					 | 
				
			||||||
            .optional() | 
					 | 
				
			||||||
            .map(|r| r.is_some()) | 
					 | 
				
			||||||
            .map_err(with_err_ctx("Can't check payload {payload_id}")) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn get_job_by_alias(&mut self, alias: &str) -> Result<Option<Job>> { | 
					    pub fn find_job_by_alias(&self, alias: &str) -> ULocalResult<JobMeta> { | 
				
			||||||
        use schema::{jobs, payloads}; | 
					        use schema::jobs; | 
				
			||||||
 | 
					        let result = jobs::table | 
				
			||||||
        let maybe_job_with_payload = jobs::table | 
					 | 
				
			||||||
            .left_join(payloads::table) | 
					 | 
				
			||||||
            .filter(jobs::alias.eq(alias)) | 
					            .filter(jobs::alias.eq(alias)) | 
				
			||||||
            .first::<(JobMeta, Option<Payload>)>(self.conn) | 
					            .first::<JobMeta>(&self.conn)?; | 
				
			||||||
            .optional() | 
					        Ok(result) | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't get job by alias {alias}")))?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(maybe_job_with_payload.map(|(job, payload_meta)| Job { | 
					 | 
				
			||||||
            meta: job, | 
					 | 
				
			||||||
            payload: payload_meta, | 
					 | 
				
			||||||
        })) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn insert_result(&mut self, result: &AssignedJob) -> Result<()> { | 
					 | 
				
			||||||
        use schema::results; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        diesel::insert_into(results::table) | 
					 | 
				
			||||||
            .values(result) | 
					 | 
				
			||||||
            .execute(self.conn) | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't insert result {result:?}")))?; | 
					 | 
				
			||||||
        Ok(()) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn get_agent(&mut self, id: Id) -> Result<Option<Agent>> { | 
					    pub fn insert_agent(&self, agent: &Agent) -> ULocalResult<()> { | 
				
			||||||
        use schema::agents; | 
					        use schema::agents; | 
				
			||||||
 | 
					        diesel::insert_into(agents::table) | 
				
			||||||
        agents::table | 
					            .values(agent) | 
				
			||||||
            .filter(agents::id.eq(id)) | 
					            .on_conflict(agents::id) | 
				
			||||||
            .first(self.conn) | 
					            .do_update() | 
				
			||||||
            .optional() | 
					            .set(agent) | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't get agent {id:?}"))) | 
					            .execute(&self.conn)?; | 
				
			||||||
 | 
					        Ok(()) | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn get_agents(&mut self) -> Result<Vec<Agent>> { | 
					    pub fn get_agents(&self, uid: Option<Uuid>) -> ULocalResult<Vec<Agent>> { | 
				
			||||||
        use schema::agents; | 
					        use schema::agents; | 
				
			||||||
 | 
					        let result = if uid.is_some() { | 
				
			||||||
        agents::table | 
					            agents::table | 
				
			||||||
            .load::<Agent>(self.conn) | 
					                .filter(agents::id.eq(uid.unwrap())) | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't get agents"))) | 
					                .load::<Agent>(&self.conn)? | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            agents::table.load::<Agent>(&self.conn)? | 
				
			||||||
 | 
					        }; | 
				
			||||||
 | 
					        Ok(result) | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn update_job_status(&mut self, id: Id, status: JobState) -> Result<()> { | 
					    pub fn update_job_status(&self, uid: Uuid, status: JobState) -> ULocalResult<()> { | 
				
			||||||
        use schema::results; | 
					        use schema::results; | 
				
			||||||
 | 
					 | 
				
			||||||
        diesel::update(results::table) | 
					        diesel::update(results::table) | 
				
			||||||
            .filter(results::id.eq(id)) | 
					            .filter(results::id.eq(uid)) | 
				
			||||||
            .set(results::state.eq(status)) | 
					            .set(results::state.eq(status)) | 
				
			||||||
            .execute(self.conn) | 
					            .execute(&self.conn)?; | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't update status of job {id}")))?; | 
					 | 
				
			||||||
        Ok(()) | 
					        Ok(()) | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    //TODO: filters possibly could work in a wrong way, check
 | 
					    //TODO: filters possibly could work in a wrong way, check
 | 
				
			||||||
    pub fn get_assigned_jobs( | 
					    pub fn get_exact_jobs( | 
				
			||||||
        &mut self, | 
					        &self, | 
				
			||||||
        id: Option<Id>, | 
					        uid: Option<Uuid>, | 
				
			||||||
        personal: bool, | 
					        personal: bool, | 
				
			||||||
    ) -> Result<Vec<AssignedJob>> { | 
					    ) -> ULocalResult<Vec<AssignedJob>> { | 
				
			||||||
        use schema::results; | 
					        use schema::results; | 
				
			||||||
 | 
					 | 
				
			||||||
        let mut q = results::table.into_boxed(); | 
					        let mut q = results::table.into_boxed(); | 
				
			||||||
        /*if id.is_some() {
 | 
					        /*if uid.is_some() {
 | 
				
			||||||
            q = q.filter(results::agent_id.eq(id.unwrap())) | 
					            q = q.filter(results::agent_id.eq(uid.unwrap())) | 
				
			||||||
        }*/ | 
					        }*/ | 
				
			||||||
        if personal { | 
					        if personal { | 
				
			||||||
            q = q.filter( | 
					            q = q.filter( | 
				
			||||||
                results::state | 
					                results::state | 
				
			||||||
                    .eq(JobState::Queued) | 
					                    .eq(JobState::Queued) | 
				
			||||||
                    .and(results::agent_id.eq(id.unwrap())), | 
					                    .and(results::agent_id.eq(uid.unwrap())), | 
				
			||||||
            ) | 
					            ) | 
				
			||||||
        } else if id.is_some() { | 
					        } else if uid.is_some() { | 
				
			||||||
            q = q | 
					            q = q | 
				
			||||||
                .filter(results::agent_id.eq(id.unwrap())) | 
					                .filter(results::agent_id.eq(uid.unwrap())) | 
				
			||||||
                .or_filter(results::job_id.eq(id.unwrap())) | 
					                .or_filter(results::job_id.eq(uid.unwrap())) | 
				
			||||||
                .or_filter(results::id.eq(id.unwrap())) | 
					                .or_filter(results::id.eq(uid.unwrap())) | 
				
			||||||
        } | 
					        } | 
				
			||||||
        let result = q | 
					        let result = q.load::<AssignedJob>(&self.conn)?; | 
				
			||||||
            .load::<AssignedJob>(self.conn) | 
					 | 
				
			||||||
            .map_err(with_err_ctx("Can't get exact jobs"))?; | 
					 | 
				
			||||||
        Ok(result) | 
					        Ok(result) | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    // todo: move to handlers
 | 
					    pub fn set_jobs_for_agent( | 
				
			||||||
    pub fn assign_jobs(&mut self, assigned_jobs: &[AssignedJobById]) -> Result<()> { | 
					        &self, | 
				
			||||||
        use schema::{jobs, results}; | 
					        agent_uid: &Uuid, | 
				
			||||||
 | 
					        job_uids: &[Uuid], | 
				
			||||||
        struct JobBriefMeta { | 
					    ) -> ULocalResult<Vec<Uuid>> { | 
				
			||||||
            alias: Option<String>, | 
					        use schema::{agents::dsl::agents, jobs::dsl::jobs, results}; | 
				
			||||||
            target_platform: String, | 
					        if let Err(DslError::NotFound) = agents.find(agent_uid).first::<Agent>(&self.conn) { | 
				
			||||||
 | 
					            return Err(ULocalError::NotFound(agent_uid.to_string())); | 
				
			||||||
        } | 
					        } | 
				
			||||||
 | 
					        let not_found_jobs = job_uids | 
				
			||||||
        let assigned_job_ids = HashSet::<Uuid>::from_iter(assigned_jobs.iter().map(|a| a.job_id)); | 
					            .iter() | 
				
			||||||
 | 
					            .filter_map(|job_uid| { | 
				
			||||||
        let jobs_meta = HashMap::<Id, JobBriefMeta>::from_iter( | 
					                if let Err(DslError::NotFound) = jobs.find(job_uid).first::<JobMeta>(&self.conn) { | 
				
			||||||
            jobs::table | 
					                    Some(job_uid.to_string()) | 
				
			||||||
                .select((jobs::id, jobs::alias, jobs::target_platforms)) | 
					                } else { | 
				
			||||||
                .filter(jobs::id.eq_any(&assigned_job_ids)) | 
					                    None | 
				
			||||||
                .load::<(Id, Option<String>, String)>(self.conn) | 
					 | 
				
			||||||
                .map_err(with_err_ctx(format!( | 
					 | 
				
			||||||
                    "Can't find jobs {:?}", | 
					 | 
				
			||||||
                    assigned_job_ids | 
					 | 
				
			||||||
                )))? | 
					 | 
				
			||||||
                .into_iter() | 
					 | 
				
			||||||
                .map(|(id, alias, target_platform)| { | 
					 | 
				
			||||||
                    ( | 
					 | 
				
			||||||
                        id, | 
					 | 
				
			||||||
                        JobBriefMeta { | 
					 | 
				
			||||||
                            alias, | 
					 | 
				
			||||||
                            target_platform, | 
					 | 
				
			||||||
                        }, | 
					 | 
				
			||||||
                    ) | 
					 | 
				
			||||||
                }), | 
					 | 
				
			||||||
        ); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let existing_job_ids = HashSet::from_iter(jobs_meta.keys().copied()); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if assigned_job_ids != existing_job_ids { | 
					 | 
				
			||||||
            return Err(Error::ProcessingError(format!( | 
					 | 
				
			||||||
                "Jobs not found: {:?}", | 
					 | 
				
			||||||
                assigned_job_ids.difference(&existing_job_ids), | 
					 | 
				
			||||||
            ))); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for ajob in assigned_jobs { | 
					 | 
				
			||||||
            let meta = &jobs_meta[&ajob.job_id]; | 
					 | 
				
			||||||
            let agent_platform = match self.get_agent(ajob.agent_id)? { | 
					 | 
				
			||||||
                Some(agent) => Platform::new(&agent.platform), | 
					 | 
				
			||||||
                None => { | 
					 | 
				
			||||||
                    return Err(Error::ProcessingError(format!( | 
					 | 
				
			||||||
                        "Agent {} not found", | 
					 | 
				
			||||||
                        ajob.agent_id | 
					 | 
				
			||||||
                    ))) | 
					 | 
				
			||||||
                } | 
					                } | 
				
			||||||
            }; | 
					            }) | 
				
			||||||
            if !agent_platform.matches(&meta.target_platform) { | 
					            .collect::<Vec<String>>(); | 
				
			||||||
                return Err(Error::InsuitablePlatform( | 
					        if not_found_jobs.len() > 0 { | 
				
			||||||
                    agent_platform.into_string(), | 
					            return Err(ULocalError::NotFound(not_found_jobs.join(", "))); | 
				
			||||||
                    meta.target_platform.clone(), | 
					 | 
				
			||||||
                )); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					        } | 
				
			||||||
 | 
					        let job_requests = job_uids | 
				
			||||||
        let job_requests = assigned_jobs | 
					            .iter() | 
				
			||||||
            .into_iter() | 
					            .map(|job_uid| { | 
				
			||||||
            .map(|a| AssignedJob { | 
					                info!("set_jobs_for_agent: set {} for {}", job_uid, agent_uid); | 
				
			||||||
                job_id: a.job_id, | 
					                AssignedJob { | 
				
			||||||
                agent_id: a.agent_id, | 
					                    job_id: *job_uid, | 
				
			||||||
                alias: jobs_meta[&a.job_id].alias.clone(), | 
					                    agent_id: *agent_uid, | 
				
			||||||
                ..Default::default() | 
					                    ..Default::default() | 
				
			||||||
 | 
					                } | 
				
			||||||
            }) | 
					            }) | 
				
			||||||
            .collect::<Vec<AssignedJob>>(); | 
					            .collect::<Vec<AssignedJob>>(); | 
				
			||||||
 | 
					 | 
				
			||||||
        diesel::insert_into(results::table) | 
					        diesel::insert_into(results::table) | 
				
			||||||
            .values(&job_requests) | 
					            .values(&job_requests) | 
				
			||||||
            .execute(self.conn) | 
					            .execute(&self.conn)?; | 
				
			||||||
            .map(drop) | 
					        let assigned_uids = job_requests.iter().map(|aj| aj.id).collect(); | 
				
			||||||
            .map_err(with_err_ctx("Can't assign jobs")) | 
					        Ok(assigned_uids) | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn del_jobs(&mut self, ids: &[Id]) -> Result<()> { | 
					    pub fn del_jobs(&self, uids: &Vec<Uuid>) -> ULocalResult<usize> { | 
				
			||||||
        use schema::jobs; | 
					        use schema::jobs; | 
				
			||||||
 | 
					        let mut affected = 0; | 
				
			||||||
        diesel::delete(jobs::table) | 
					        for &uid in uids { | 
				
			||||||
            .filter(jobs::id.eq_any(ids)) | 
					            let deleted = diesel::delete(jobs::table) | 
				
			||||||
            .execute(self.conn) | 
					                .filter(jobs::id.eq(uid)) | 
				
			||||||
            .map(drop) | 
					                .execute(&self.conn)?; | 
				
			||||||
            .map_err(with_err_ctx("Can't delete jobs")) | 
					            affected += deleted; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        Ok(affected) | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn del_results(&mut self, ids: &[Id]) -> Result<()> { | 
					    pub fn del_results(&self, uids: &Vec<Uuid>) -> ULocalResult<usize> { | 
				
			||||||
        use schema::results; | 
					        use schema::results; | 
				
			||||||
 | 
					        let mut affected = 0; | 
				
			||||||
        diesel::delete(results::table) | 
					        for &uid in uids { | 
				
			||||||
            .filter(results::id.eq_any(ids)) | 
					            let deleted = diesel::delete(results::table) | 
				
			||||||
            .execute(self.conn) | 
					                .filter(results::id.eq(uid)) | 
				
			||||||
            .map(drop) | 
					                .execute(&self.conn)?; | 
				
			||||||
            .map_err(with_err_ctx("Can't delete results")) | 
					            affected += deleted; | 
				
			||||||
    } | 
					        } | 
				
			||||||
 | 
					        Ok(affected) | 
				
			||||||
    pub fn del_agents(&mut self, ids: &[Id]) -> Result<()> { | 
					 | 
				
			||||||
        use schema::agents; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        diesel::delete(agents::table) | 
					 | 
				
			||||||
            .filter(agents::id.eq_any(ids)) | 
					 | 
				
			||||||
            .execute(self.conn) | 
					 | 
				
			||||||
            .map(drop) | 
					 | 
				
			||||||
            .map_err(with_err_ctx("Can't delete agents")) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn del_payloads(&mut self, ids: &[Id]) -> Result<()> { | 
					 | 
				
			||||||
        use schema::payloads; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        diesel::delete(payloads::table) | 
					 | 
				
			||||||
            .filter(payloads::id.eq_any(ids)) | 
					 | 
				
			||||||
            .execute(self.conn) | 
					 | 
				
			||||||
            .map(drop) | 
					 | 
				
			||||||
            .map_err(with_err_ctx("Can't delete payloads")) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn upsert_agent(&mut self, agent: &Agent) -> Result<()> { | 
					    pub fn del_agents(&self, uids: &Vec<Uuid>) -> ULocalResult<usize> { | 
				
			||||||
        use schema::agents; | 
					        use schema::agents; | 
				
			||||||
 | 
					        let mut affected = 0; | 
				
			||||||
        diesel::insert_into(agents::table) | 
					        for &uid in uids { | 
				
			||||||
            .values(agent) | 
					            let deleted = diesel::delete(agents::table) | 
				
			||||||
            .on_conflict(agents::id) | 
					                .filter(agents::id.eq(uid)) | 
				
			||||||
            .do_update() | 
					                .execute(&self.conn)?; | 
				
			||||||
            .set(agent) | 
					            affected += deleted; | 
				
			||||||
            .execute(self.conn) | 
					        } | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't insert agent {agent:?}")))?; | 
					        Ok(affected) | 
				
			||||||
        Ok(()) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn update_job(&mut self, job: &JobMeta) -> Result<()> { | 
					 | 
				
			||||||
        job.save_changes::<JobMeta>(self.conn) | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't update job {job:?}")))?; | 
					 | 
				
			||||||
        Ok(()) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn update_payload(&mut self, payload: &Payload) -> Result<()> { | 
					 | 
				
			||||||
        payload | 
					 | 
				
			||||||
            .save_changes::<Payload>(self.conn) | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't update payload {payload:?}")))?; | 
					 | 
				
			||||||
        Ok(()) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn update_result(&mut self, result: &AssignedJob) -> Result<()> { | 
					 | 
				
			||||||
        debug!( | 
					 | 
				
			||||||
            "updating result: id = {}, job_id = {}, agent_id = {}", | 
					 | 
				
			||||||
            result.id, result.job_id, result.agent_id | 
					 | 
				
			||||||
        ); | 
					 | 
				
			||||||
        result | 
					 | 
				
			||||||
            .save_changes::<AssignedJob>(self.conn) | 
					 | 
				
			||||||
            .map_err(with_err_ctx(format!("Can't update result {result:?}")))?; | 
					 | 
				
			||||||
        Ok(()) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
					/* | 
				
			||||||
fn with_err_ctx(msg: impl AsRef<str>) -> impl Fn(DslError) -> Error { | 
					#[cfg(test)] | 
				
			||||||
    move |err| Error::DBErrorCtx(format!("{}, reason: {err}", msg.as_ref())) | 
					mod tests { | 
				
			||||||
 | 
					    use super::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn setup_db() -> Storage { | 
				
			||||||
 | 
					        return UDB::new().unwrap(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[tokio::test] | 
				
			||||||
 | 
					    async fn test_add_agent() { | 
				
			||||||
 | 
					        let db = setup_db(); | 
				
			||||||
 | 
					        let agent = IAgent { | 
				
			||||||
 | 
					            alias: None, | 
				
			||||||
 | 
					            id: "000-000".to_string(), | 
				
			||||||
 | 
					            hostname: "test".to_string(), | 
				
			||||||
 | 
					            is_root: false, | 
				
			||||||
 | 
					            is_root_allowed: false, | 
				
			||||||
 | 
					            platform: "linux".to_string(), | 
				
			||||||
 | 
					            status: None, | 
				
			||||||
 | 
					            token: None, | 
				
			||||||
 | 
					            username: "test".to_string() | 
				
			||||||
 | 
					        }; | 
				
			||||||
 | 
					        db.lock().unwrap().new_agent(agent).unwrap(); | 
				
			||||||
 | 
					        let result = db.lock().unwrap().get_agents().unwrap(); | 
				
			||||||
 | 
					        assert_eq!( | 
				
			||||||
 | 
					            result[0].username, | 
				
			||||||
 | 
					            "test".to_string() | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					    } | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
					*/ | 
				
			||||||
 | 
				
			|||||||
@ -1,98 +0,0 @@ | 
				
			|||||||
use diesel::result::Error as DslError; | 
					 | 
				
			||||||
use thiserror::Error; | 
					 | 
				
			||||||
use u_lib::{ufs, UError}; | 
					 | 
				
			||||||
use warp::{ | 
					 | 
				
			||||||
    http::StatusCode, | 
					 | 
				
			||||||
    reject::Reject, | 
					 | 
				
			||||||
    reply::{with_status, Response}, | 
					 | 
				
			||||||
    Reply, | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Error, Debug)] | 
					 | 
				
			||||||
pub enum Error { | 
					 | 
				
			||||||
    #[error("Configs error: {0}")] | 
					 | 
				
			||||||
    ConfigError(#[from] u_lib::config::Error), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error("Processing error: {0}")] | 
					 | 
				
			||||||
    ProcessingError(String), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error(transparent)] | 
					 | 
				
			||||||
    DBError(#[from] DslError), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error("DB error: {0}")] | 
					 | 
				
			||||||
    DBErrorCtx(String), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error("Deadpool error: {0}")] | 
					 | 
				
			||||||
    DeadpoolError(#[from] deadpool_diesel::PoolError), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error(transparent)] | 
					 | 
				
			||||||
    FSError(#[from] ufs::Error), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error("Job cannot be ran on this platform. Expected: {0}, got: {1}")] | 
					 | 
				
			||||||
    InsuitablePlatform(String, String), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error("{0}\nContext: {1}")] | 
					 | 
				
			||||||
    Contexted(Box<Error>, String), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error(transparent)] | 
					 | 
				
			||||||
    UError(#[from] UError), | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[error("Runtime error: {0}")] | 
					 | 
				
			||||||
    Runtime(String), | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Reject for Error {} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct RejResponse { | 
					 | 
				
			||||||
    message: String, | 
					 | 
				
			||||||
    status: StatusCode, | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl RejResponse { | 
					 | 
				
			||||||
    pub fn not_found(msg: impl Into<String>) -> Self { | 
					 | 
				
			||||||
        Self { | 
					 | 
				
			||||||
            message: msg.into(), | 
					 | 
				
			||||||
            status: StatusCode::NOT_FOUND, | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn bad_request(msg: impl Into<String>) -> Self { | 
					 | 
				
			||||||
        Self { | 
					 | 
				
			||||||
            message: msg.into(), | 
					 | 
				
			||||||
            status: StatusCode::BAD_REQUEST, | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn internal() -> Self { | 
					 | 
				
			||||||
        Self { | 
					 | 
				
			||||||
            message: "INTERNAL SERVER ERROR".to_string(), | 
					 | 
				
			||||||
            status: StatusCode::INTERNAL_SERVER_ERROR, | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Reply for RejResponse { | 
					 | 
				
			||||||
    fn into_response(self) -> Response { | 
					 | 
				
			||||||
        with_status(self.message, self.status).into_response() | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<anyhow::Error> for Error { | 
					 | 
				
			||||||
    fn from(e: anyhow::Error) -> Self { | 
					 | 
				
			||||||
        let ctx = e | 
					 | 
				
			||||||
            .chain() | 
					 | 
				
			||||||
            .rev() | 
					 | 
				
			||||||
            .skip(1) | 
					 | 
				
			||||||
            .map(|cause| format!("ctx: {}", cause)) | 
					 | 
				
			||||||
            .collect::<Vec<_>>() | 
					 | 
				
			||||||
            .join("\n"); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        match e.downcast::<Error>() { | 
					 | 
				
			||||||
            Ok(err) => Error::Contexted(Box::new(err), ctx), | 
					 | 
				
			||||||
            Err(err) => match err.downcast::<ufs::Error>() { | 
					 | 
				
			||||||
                Ok(err) => Error::Contexted(Box::new(Error::FSError(err)), ctx), | 
					 | 
				
			||||||
                Err(err) => Error::Runtime(err.to_string()), | 
					 | 
				
			||||||
            }, | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -0,0 +1,103 @@ | 
				
			|||||||
 | 
					use crate::handlers::Endpoints; | 
				
			||||||
 | 
					use serde::de::DeserializeOwned; | 
				
			||||||
 | 
					use std::convert::Infallible; | 
				
			||||||
 | 
					use std::env; | 
				
			||||||
 | 
					use u_lib::{ | 
				
			||||||
 | 
					    messaging::{AsMsg, BaseMessage}, | 
				
			||||||
 | 
					    models::*, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use uuid::Uuid; | 
				
			||||||
 | 
					use warp::{body, Filter, Rejection, Reply}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_content<M>() -> impl Filter<Extract = (BaseMessage<'static, M>,), Error = Rejection> + Clone | 
				
			||||||
 | 
					where | 
				
			||||||
 | 
					    M: AsMsg + Sync + Send + DeserializeOwned + 'static, | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    body::content_length_limit(1024 * 64).and(body::json::<BaseMessage<M>>()) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn make_filters() -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone { | 
				
			||||||
 | 
					    let infallible_none = |_| async { Ok::<(Option<Uuid>,), Infallible>((None,)) }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let get_agents = warp::get() | 
				
			||||||
 | 
					        .and(warp::path("get_agents")) | 
				
			||||||
 | 
					        .and( | 
				
			||||||
 | 
					            warp::path::param::<Uuid>() | 
				
			||||||
 | 
					                .map(Some) | 
				
			||||||
 | 
					                .or_else(infallible_none), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					        .and_then(Endpoints::get_agents); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let upload_jobs = warp::post() | 
				
			||||||
 | 
					        .and(warp::path("upload_jobs")) | 
				
			||||||
 | 
					        .and(get_content::<Vec<JobMeta>>()) | 
				
			||||||
 | 
					        .and_then(Endpoints::upload_jobs); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let get_jobs = warp::get() | 
				
			||||||
 | 
					        .and(warp::path("get_jobs")) | 
				
			||||||
 | 
					        .and( | 
				
			||||||
 | 
					            warp::path::param::<Uuid>() | 
				
			||||||
 | 
					                .map(Some) | 
				
			||||||
 | 
					                .or_else(infallible_none), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					        .and_then(Endpoints::get_jobs); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let get_agent_jobs = warp::get() | 
				
			||||||
 | 
					        .and(warp::path("get_agent_jobs")) | 
				
			||||||
 | 
					        .and( | 
				
			||||||
 | 
					            warp::path::param::<Uuid>() | 
				
			||||||
 | 
					                .map(Some) | 
				
			||||||
 | 
					                .or_else(infallible_none), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					        .and_then(|uid| Endpoints::get_agent_jobs(uid)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let get_personal_jobs = warp::get() | 
				
			||||||
 | 
					        .and(warp::path("get_personal_jobs")) | 
				
			||||||
 | 
					        .and(warp::path::param::<Uuid>().map(Some)) | 
				
			||||||
 | 
					        .and_then(|uid| Endpoints::get_personal_jobs(uid)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let del = warp::get() | 
				
			||||||
 | 
					        .and(warp::path("del")) | 
				
			||||||
 | 
					        .and(warp::path::param::<Uuid>()) | 
				
			||||||
 | 
					        .and_then(Endpoints::del); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let set_jobs = warp::post() | 
				
			||||||
 | 
					        .and(warp::path("set_jobs")) | 
				
			||||||
 | 
					        .and(warp::path::param::<Uuid>()) | 
				
			||||||
 | 
					        .and(get_content::<Vec<String>>()) | 
				
			||||||
 | 
					        .and_then(Endpoints::set_jobs); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let report = warp::post() | 
				
			||||||
 | 
					        .and(warp::path("report")) | 
				
			||||||
 | 
					        .and(get_content::<Vec<ExecResult>>()) | 
				
			||||||
 | 
					        .and_then(Endpoints::report); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let download = warp::get() | 
				
			||||||
 | 
					        .and(warp::path("dl")) | 
				
			||||||
 | 
					        .and(warp::path::param::<Uuid>()) | 
				
			||||||
 | 
					        .and_then(Endpoints::download); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let download_request = warp::post() | 
				
			||||||
 | 
					        .and(warp::path("dlr")) | 
				
			||||||
 | 
					        .and(get_content::<String>()) | 
				
			||||||
 | 
					        .and_then(Endpoints::download_request); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let auth_token = format!("Bearer {}", env::var("ADMIN_AUTH_TOKEN").unwrap()).into_boxed_str(); | 
				
			||||||
 | 
					    let auth_header = warp::header::exact("authorization", Box::leak(auth_token)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let auth_zone = (get_agents | 
				
			||||||
 | 
					        .or(get_jobs) | 
				
			||||||
 | 
					        .or(upload_jobs) | 
				
			||||||
 | 
					        .or(del) | 
				
			||||||
 | 
					        .or(set_jobs) | 
				
			||||||
 | 
					        .or(get_agent_jobs)) | 
				
			||||||
 | 
					    .and(auth_header); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let agent_zone = get_jobs | 
				
			||||||
 | 
					        .or(get_personal_jobs) | 
				
			||||||
 | 
					        .or(report) | 
				
			||||||
 | 
					        .or(download) | 
				
			||||||
 | 
					        .or(download_request); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auth_zone.or(agent_zone) | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,343 +1,197 @@ | 
				
			|||||||
use std::sync::Arc; | 
					use crate::{ | 
				
			||||||
 | 
					    db::UDB, | 
				
			||||||
 | 
					    storages::{FileIndex, Mapper}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use diesel::SaveChangesDsl; | 
				
			||||||
 | 
					use hyper::Body; | 
				
			||||||
 | 
					use serde::Serialize; | 
				
			||||||
 | 
					use u_lib::{ | 
				
			||||||
 | 
					    messaging::{AsMsg, BaseMessage, DownloadInfo}, | 
				
			||||||
 | 
					    models::{Agent, AgentState, AssignedJob, ExecResult, JobMeta, JobState}, | 
				
			||||||
 | 
					    ULocalError, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use uuid::Uuid; | 
				
			||||||
 | 
					use warp::{ | 
				
			||||||
 | 
					    http::{Response, StatusCode}, | 
				
			||||||
 | 
					    Rejection, Reply, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_response<S: Into<Body>>(code: StatusCode, body: S) -> Response<Body> { | 
				
			||||||
 | 
					    Response::builder().status(code).body(body.into()).unwrap() | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::db::{PgRepo, UDB}; | 
					pub fn build_ok<S: Into<Body>>(body: S) -> Response<Body> { | 
				
			||||||
use crate::error::Error; | 
					    build_response(StatusCode::OK, body) | 
				
			||||||
use serde::Deserialize; | 
					} | 
				
			||||||
use u_lib::{api::api_types, messaging::Reportable, models::*, types::Id}; | 
					 | 
				
			||||||
use warp::reject::not_found; | 
					 | 
				
			||||||
use warp::Rejection; | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type EndpResult<T> = Result<T, Rejection>; | 
					pub fn build_err<S: ToString>(body: S) -> Response<Body> { | 
				
			||||||
 | 
					    build_response(StatusCode::BAD_REQUEST, body.to_string()) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_not_found() -> Response<Body> { | 
				
			||||||
 | 
					    build_response(StatusCode::NOT_FOUND, "") | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Deserialize)] | 
					pub fn build_message<M: AsMsg + Serialize>(m: M) -> Response<Body> { | 
				
			||||||
pub struct PayloadFlags { | 
					    warp::reply::json(&m.as_message()).into_response() | 
				
			||||||
    brief: Brief, | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Endpoints; | 
					pub struct Endpoints; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg_attr(test, automock)] | 
				
			||||||
impl Endpoints { | 
					impl Endpoints { | 
				
			||||||
    pub async fn get_agents(repo: Arc<PgRepo>, id: Option<Id>) -> EndpResult<api_types::GetAgents> { | 
					    pub async fn add_agent(msg: Agent) -> Result<Response<Body>, Rejection> { | 
				
			||||||
        repo.interact(move |mut db| { | 
					        info!("hnd: add_agent"); | 
				
			||||||
            Ok(match id { | 
					        UDB::lock_db() | 
				
			||||||
                Some(id) => { | 
					            .insert_agent(&msg) | 
				
			||||||
                    if let Some(agent) = db.get_agent(id)? { | 
					            .map(|_| build_ok("")) | 
				
			||||||
                        vec![agent] | 
					            .or_else(|e| Ok(build_err(e))) | 
				
			||||||
                    } else { | 
					    } | 
				
			||||||
                        vec![] | 
					
 | 
				
			||||||
                    } | 
					    pub async fn get_agents(uid: Option<Uuid>) -> Result<Response<Body>, Rejection> { | 
				
			||||||
                } | 
					        info!("hnd: get_agents"); | 
				
			||||||
                None => db.get_agents()?, | 
					        UDB::lock_db() | 
				
			||||||
            }) | 
					            .get_agents(uid) | 
				
			||||||
        }) | 
					            .map(|m| build_message(m)) | 
				
			||||||
        .await | 
					            .or_else(|e| Ok(build_err(e))) | 
				
			||||||
        .map_err(From::from) | 
					    } | 
				
			||||||
    } | 
					
 | 
				
			||||||
 | 
					    pub async fn get_jobs(uid: Option<Uuid>) -> Result<Response<Body>, Rejection> { | 
				
			||||||
    pub async fn get_job( | 
					        info!("hnd: get_jobs"); | 
				
			||||||
        repo: Arc<PgRepo>, | 
					        UDB::lock_db() | 
				
			||||||
        id: Id, | 
					            .get_jobs(uid) | 
				
			||||||
        params: Option<PayloadFlags>, | 
					            .map(|m| build_message(m)) | 
				
			||||||
    ) -> EndpResult<api_types::GetJob> { | 
					            .or_else(|e| Ok(build_err(e))) | 
				
			||||||
        let Some(mut job) = repo.interact(move |mut db| db.get_job(id)).await? else { | 
					    } | 
				
			||||||
            return Err(not_found()); | 
					
 | 
				
			||||||
        }; | 
					    pub async fn get_agent_jobs(uid: Option<Uuid>) -> Result<Response<Body>, Rejection> { | 
				
			||||||
 | 
					        info!("hnd: get_agent_jobs"); | 
				
			||||||
        Ok(match params.map(|p| p.brief) { | 
					        UDB::lock_db() | 
				
			||||||
            Some(Brief::Yes) => job, | 
					            .get_exact_jobs(uid, false) | 
				
			||||||
            Some(Brief::Auto) | None => { | 
					            .map(|m| build_message(m)) | 
				
			||||||
                if let Some(payload) = &mut job.payload { | 
					            .or_else(|e| Ok(build_err(e))) | 
				
			||||||
                    payload.maybe_join_payload().map_err(Error::from)?; | 
					    } | 
				
			||||||
                } | 
					
 | 
				
			||||||
                job | 
					    pub async fn get_personal_jobs(uid: Option<Uuid>) -> Result<Response<Body>, Rejection> { | 
				
			||||||
 | 
					        info!("hnd: get_personal_jobs"); | 
				
			||||||
 | 
					        let agents = UDB::lock_db().get_agents(uid).unwrap(); | 
				
			||||||
 | 
					        if agents.len() == 0 { | 
				
			||||||
 | 
					            let db = UDB::lock_db(); | 
				
			||||||
 | 
					            db.insert_agent(&Agent::with_id(uid.unwrap())).unwrap(); | 
				
			||||||
 | 
					            let job = db.find_job_by_alias("agent_hello").unwrap(); | 
				
			||||||
 | 
					            if let Err(e) = db.set_jobs_for_agent(&uid.unwrap(), &[job.id]) { | 
				
			||||||
 | 
					                return Ok(build_err(e)); | 
				
			||||||
            } | 
					            } | 
				
			||||||
            Some(Brief::No) => { | 
					        } | 
				
			||||||
                if let Some(payload) = &mut job.payload { | 
					        let result = UDB::lock_db().get_exact_jobs(uid, true); | 
				
			||||||
                    payload.join_payload().map_err(Error::from)?; | 
					        match result { | 
				
			||||||
                } | 
					            Ok(r) => { | 
				
			||||||
                job | 
					                let db = UDB::lock_db(); | 
				
			||||||
            } | 
					                for j in r.iter() { | 
				
			||||||
        }) | 
					                    db.update_job_status(j.id, JobState::Running).unwrap(); | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn get_jobs(repo: Arc<PgRepo>) -> EndpResult<api_types::GetJobs> { | 
					 | 
				
			||||||
        repo.interact(move |mut db| db.get_jobs()) | 
					 | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn get_assigned_jobs( | 
					 | 
				
			||||||
        repo: Arc<PgRepo>, | 
					 | 
				
			||||||
        id: Option<Id>, | 
					 | 
				
			||||||
    ) -> EndpResult<api_types::GetAgentJobs> { | 
					 | 
				
			||||||
        repo.interact(move |mut db| db.get_assigned_jobs(id, false)) | 
					 | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn get_payloads(repo: Arc<PgRepo>) -> EndpResult<api_types::GetPayloads> { | 
					 | 
				
			||||||
        repo.interact(move |mut db| db.get_payloads()) | 
					 | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn get_payload( | 
					 | 
				
			||||||
        repo: Arc<PgRepo>, | 
					 | 
				
			||||||
        name_or_id: String, | 
					 | 
				
			||||||
        params: Option<PayloadFlags>, | 
					 | 
				
			||||||
    ) -> EndpResult<api_types::GetPayload> { | 
					 | 
				
			||||||
        let mut payload = match repo | 
					 | 
				
			||||||
            .interact(move |mut db| match Id::parse_str(&name_or_id) { | 
					 | 
				
			||||||
                Ok(id) => db.get_payload(id), | 
					 | 
				
			||||||
                Err(_) => db.get_payload_by_name(name_or_id), | 
					 | 
				
			||||||
            }) | 
					 | 
				
			||||||
            .await? | 
					 | 
				
			||||||
        { | 
					 | 
				
			||||||
            Some(p) => p, | 
					 | 
				
			||||||
            None => return Err(not_found()), | 
					 | 
				
			||||||
        }; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(match params.map(|p| p.brief) { | 
					 | 
				
			||||||
            Some(Brief::Yes) => { | 
					 | 
				
			||||||
                payload.data = None; | 
					 | 
				
			||||||
                payload | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            None | Some(Brief::Auto) => { | 
					 | 
				
			||||||
                payload.maybe_join_payload().map_err(Error::from)?; | 
					 | 
				
			||||||
                payload | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            _ => { | 
					 | 
				
			||||||
                payload.join_payload().map_err(Error::from)?; | 
					 | 
				
			||||||
                payload | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn get_personal_jobs( | 
					 | 
				
			||||||
        repo: Arc<PgRepo>, | 
					 | 
				
			||||||
        agent_id: Id, | 
					 | 
				
			||||||
    ) -> EndpResult<api_types::GetPersonalJobs> { | 
					 | 
				
			||||||
        repo.transaction(move |mut db| { | 
					 | 
				
			||||||
            let agent = db.get_agent(agent_id)?; | 
					 | 
				
			||||||
            match agent { | 
					 | 
				
			||||||
                Some(mut agent) => { | 
					 | 
				
			||||||
                    agent.touch(); | 
					 | 
				
			||||||
                    db.upsert_agent(&agent)?; | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
                None => { | 
					 | 
				
			||||||
                    let mut new_agent = Agent::empty(); | 
					 | 
				
			||||||
                    new_agent.id = agent_id; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    db.upsert_agent(&new_agent)?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let job = db | 
					 | 
				
			||||||
                        .get_job_by_alias("agent_hello")? | 
					 | 
				
			||||||
                        .expect("agent_hello job not found"); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let assigned_job = AssignedJobById { | 
					 | 
				
			||||||
                        agent_id, | 
					 | 
				
			||||||
                        job_id: job.meta.id, | 
					 | 
				
			||||||
                        ..Default::default() | 
					 | 
				
			||||||
                    }; | 
					 | 
				
			||||||
                    db.assign_jobs(&[assigned_job])?; | 
					 | 
				
			||||||
                } | 
					                } | 
				
			||||||
 | 
					                Ok(build_message(r)) | 
				
			||||||
            } | 
					            } | 
				
			||||||
 | 
					            Err(e) => Ok(build_err(e)), | 
				
			||||||
            let assigned_jobs = db.get_assigned_jobs(Some(agent_id), true)?; | 
					        } | 
				
			||||||
 | 
					 | 
				
			||||||
            for job in &assigned_jobs { | 
					 | 
				
			||||||
                db.update_job_status(job.id, JobState::Running)?; | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Ok(assigned_jobs | 
					 | 
				
			||||||
                .into_iter() | 
					 | 
				
			||||||
                .map(|j| AssignedJobById::from(&j)) | 
					 | 
				
			||||||
                .collect()) | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
        .await | 
					 | 
				
			||||||
        .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn upload_jobs( | 
					    pub async fn upload_jobs( | 
				
			||||||
        repo: Arc<PgRepo>, | 
					        msg: BaseMessage<'static, Vec<JobMeta>>, | 
				
			||||||
        jobs: Vec<Job>, | 
					    ) -> Result<Response<Body>, Rejection> { | 
				
			||||||
    ) -> EndpResult<api_types::UploadJobs> { | 
					        info!("hnd: upload_jobs"); | 
				
			||||||
        let mut checked_jobs = vec![]; | 
					        UDB::lock_db() | 
				
			||||||
        for mut job in jobs { | 
					            .insert_jobs(&msg.into_inner()) | 
				
			||||||
            if let Some(payload) = &mut job.payload { | 
					            .map(|_| build_ok("")) | 
				
			||||||
                payload.maybe_split_payload().map_err(Error::from)?; | 
					            .or_else(|e| Ok(build_err(e))) | 
				
			||||||
            } else if let Some(pld_id) = job.meta.payload_id { | 
					    } | 
				
			||||||
                if !repo | 
					
 | 
				
			||||||
                    .interact(move |mut db| db.payload_exists(pld_id)) | 
					    pub async fn del(uid: Uuid) -> Result<Response<Body>, Rejection> { | 
				
			||||||
                    .await? | 
					        info!("hnd: del"); | 
				
			||||||
                { | 
					        let db = UDB::lock_db(); | 
				
			||||||
                    Err(Error::ProcessingError(format!( | 
					        let del_fns = &[UDB::del_agents, UDB::del_jobs, UDB::del_results]; | 
				
			||||||
                        "Payload {pld_id} not found" | 
					        for del_fn in del_fns { | 
				
			||||||
                    )))? | 
					            let affected = del_fn(&db, &vec![uid]).unwrap(); | 
				
			||||||
                } | 
					            if affected > 0 { | 
				
			||||||
 | 
					                return Ok(build_message(affected as i32)); | 
				
			||||||
            } | 
					            } | 
				
			||||||
            checked_jobs.push(job) | 
					 | 
				
			||||||
        } | 
					        } | 
				
			||||||
 | 
					        Ok(build_message(0)) | 
				
			||||||
        let (jobs, payloads_opt): (Vec<_>, Vec<_>) = checked_jobs | 
					 | 
				
			||||||
            .into_iter() | 
					 | 
				
			||||||
            .map(|j| (j.meta, j.payload)) | 
					 | 
				
			||||||
            .unzip(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let payloads = payloads_opt | 
					 | 
				
			||||||
            .into_iter() | 
					 | 
				
			||||||
            .filter_map(|p| p) | 
					 | 
				
			||||||
            .collect::<Vec<_>>(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        repo.transaction(move |mut db| { | 
					 | 
				
			||||||
            for payload in payloads { | 
					 | 
				
			||||||
                db.insert_payload(&payload)?; | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
            db.insert_jobs(&jobs) | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
        .await | 
					 | 
				
			||||||
        .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn upload_payload( | 
					 | 
				
			||||||
        repo: Arc<PgRepo>, | 
					 | 
				
			||||||
        raw_payload: RawPayload, | 
					 | 
				
			||||||
    ) -> EndpResult<api_types::UploadPayloads> { | 
					 | 
				
			||||||
        let payloads = raw_payload.into_payload().map_err(Error::from)?; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        repo.interact(move |mut db| db.insert_payload(&payloads)) | 
					 | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn del(repo: Arc<PgRepo>, id: Id) -> EndpResult<()> { | 
					 | 
				
			||||||
        repo.transaction(move |mut db| { | 
					 | 
				
			||||||
            [ | 
					 | 
				
			||||||
                UDB::del_agents, | 
					 | 
				
			||||||
                UDB::del_jobs, | 
					 | 
				
			||||||
                UDB::del_results, | 
					 | 
				
			||||||
                UDB::del_payloads, | 
					 | 
				
			||||||
            ] | 
					 | 
				
			||||||
            .iter() | 
					 | 
				
			||||||
            .map(|f| f(&mut db, &[id])) | 
					 | 
				
			||||||
            .collect::<Result<(), Error>>() | 
					 | 
				
			||||||
        }) | 
					 | 
				
			||||||
        .await | 
					 | 
				
			||||||
        .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn assign_jobs( | 
					    pub async fn set_jobs( | 
				
			||||||
        repo: Arc<PgRepo>, | 
					        agent_uid: Uuid, | 
				
			||||||
        assigned_jobs: Vec<AssignedJobById>, | 
					        msg: BaseMessage<'static, Vec<String>>, | 
				
			||||||
    ) -> EndpResult<()> { | 
					    ) -> Result<Response<Body>, Rejection> { | 
				
			||||||
        repo.transaction(move |mut db| { | 
					        info!("hnd: set_jobs_by_alias, agent: {}", agent_uid); | 
				
			||||||
            db.assign_jobs(&assigned_jobs)?; | 
					        let jobs: Result<Vec<Uuid>, ULocalError> = msg | 
				
			||||||
            Ok(()) | 
					            .into_inner() | 
				
			||||||
        }) | 
					            .into_iter() | 
				
			||||||
        .await | 
					            .map(|ident| { | 
				
			||||||
        .map_err(From::from) | 
					                info!("hnd: set_jobs_by_alias, job: {}", ident); | 
				
			||||||
 | 
					                Uuid::parse_str(&ident) | 
				
			||||||
 | 
					                    .or_else(|_| UDB::lock_db().find_job_by_alias(&ident).map(|j| j.id)) | 
				
			||||||
 | 
					            }) | 
				
			||||||
 | 
					            .collect(); | 
				
			||||||
 | 
					        match jobs { | 
				
			||||||
 | 
					            Ok(j) => UDB::lock_db() | 
				
			||||||
 | 
					                .set_jobs_for_agent(&agent_uid, &j) | 
				
			||||||
 | 
					                .map(|assigned_uids| build_message(assigned_uids)) | 
				
			||||||
 | 
					                .or_else(|e| Ok(build_err(e))), | 
				
			||||||
 | 
					            Err(e) => Ok(build_err(e)), | 
				
			||||||
 | 
					        } | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn report( | 
					    pub async fn report( | 
				
			||||||
        repo: Arc<PgRepo>, | 
					        msg: BaseMessage<'static, Vec<ExecResult>>, | 
				
			||||||
        msg: Vec<Reportable>, | 
					    ) -> Result<Response<Body>, Rejection> { | 
				
			||||||
        agent_id: Id, | 
					        info!("hnd: report"); | 
				
			||||||
    ) -> EndpResult<api_types::Report> { | 
					        let id = msg.id; | 
				
			||||||
        repo.transaction(move |mut db| { | 
					        let mut failed = vec![]; | 
				
			||||||
            for entry in msg { | 
					        for entry in msg.into_inner() { | 
				
			||||||
                match entry { | 
					            match entry { | 
				
			||||||
                    Reportable::Assigned(mut result) => { | 
					                ExecResult::Assigned(res) => { | 
				
			||||||
                        let result_agent_id = &result.agent_id; | 
					                    if id != res.agent_id { | 
				
			||||||
                        if agent_id != *result_agent_id { | 
					                        continue; | 
				
			||||||
                            warn!( | 
					 | 
				
			||||||
                                "Agent ids are not equal! actual id: {agent_id}, \ | 
					 | 
				
			||||||
                                id from job: {result_agent_id}" | 
					 | 
				
			||||||
                            ); | 
					 | 
				
			||||||
                            continue; | 
					 | 
				
			||||||
                        } | 
					 | 
				
			||||||
                        result.touch(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        info!("agent {agent_id} updated job {}", result.id); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        match result.exec_type { | 
					 | 
				
			||||||
                            JobType::Init => { | 
					 | 
				
			||||||
                                result.state = JobState::Finished; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                let mut agent: Agent = match result.deserialize() { | 
					 | 
				
			||||||
                                    Ok(a) => a, | 
					 | 
				
			||||||
                                    Err(e) => { | 
					 | 
				
			||||||
                                        error!( | 
					 | 
				
			||||||
                                            "Error deserializing agent \ | 
					 | 
				
			||||||
                                                    data from {agent_id}: {e}" | 
					 | 
				
			||||||
                                        ); | 
					 | 
				
			||||||
                                        continue; | 
					 | 
				
			||||||
                                    } | 
					 | 
				
			||||||
                                }; | 
					 | 
				
			||||||
                                agent.state = AgentState::Active; | 
					 | 
				
			||||||
                                db.upsert_agent(&agent)?; | 
					 | 
				
			||||||
                            } | 
					 | 
				
			||||||
                            JobType::Shell => result.state = JobState::Finished, | 
					 | 
				
			||||||
                            JobType::Stats => result.state = JobState::Finished, | 
					 | 
				
			||||||
                            JobType::Terminate => (), | 
					 | 
				
			||||||
                            JobType::Update => (), | 
					 | 
				
			||||||
                        } | 
					 | 
				
			||||||
                        db.update_result(&result)?; | 
					 | 
				
			||||||
                    } | 
					                    } | 
				
			||||||
                    Reportable::Error(e) => { | 
					                    let db = UDB::lock_db(); | 
				
			||||||
                        error!("agent {agent_id} reported: {e}"); | 
					                    if let Err(e) = res | 
				
			||||||
 | 
					                        .save_changes::<AssignedJob>(&db.conn) | 
				
			||||||
 | 
					                        .map_err(ULocalError::from) | 
				
			||||||
 | 
					                    { | 
				
			||||||
 | 
					                        failed.push(e.to_string()) | 
				
			||||||
                    } | 
					                    } | 
				
			||||||
                    Reportable::Dummy => (), | 
					 | 
				
			||||||
                } | 
					                } | 
				
			||||||
 | 
					                ExecResult::Agent(mut a) => { | 
				
			||||||
 | 
					                    a.state = AgentState::Active; | 
				
			||||||
 | 
					                    Self::add_agent(a).await?; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                ExecResult::Dummy => (), | 
				
			||||||
            } | 
					            } | 
				
			||||||
            Ok(()) | 
					        } | 
				
			||||||
        }) | 
					        if failed.len() > 0 { | 
				
			||||||
        .await | 
					            let err_msg = ULocalError::ProcessingError(failed.join(", ")); | 
				
			||||||
        .map_err(From::from) | 
					            return Ok(build_err(err_msg)); | 
				
			||||||
    } | 
					        } | 
				
			||||||
 | 
					        Ok(build_ok("")) | 
				
			||||||
    pub async fn update_agent( | 
					 | 
				
			||||||
        repo: Arc<PgRepo>, | 
					 | 
				
			||||||
        agent: Agent, | 
					 | 
				
			||||||
    ) -> EndpResult<api_types::UpdateAgent> { | 
					 | 
				
			||||||
        repo.interact(move |mut db| db.upsert_agent(&agent)) | 
					 | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn update_job(repo: Arc<PgRepo>, job: JobMeta) -> EndpResult<api_types::UpdateJob> { | 
					 | 
				
			||||||
        repo.interact(move |mut db| db.update_job(&job.validate()?)) | 
					 | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn update_assigned_job( | 
					    pub async fn download_request( | 
				
			||||||
        repo: Arc<PgRepo>, | 
					        msg: BaseMessage<'static, String>, | 
				
			||||||
        assigned: AssignedJob, | 
					    ) -> Result<Response<Body>, Rejection> { | 
				
			||||||
    ) -> EndpResult<api_types::UpdateResult> { | 
					        let agent_id = msg.id; | 
				
			||||||
        repo.interact(move |mut db| db.update_result(&assigned)) | 
					        FileIndex:: | 
				
			||||||
            .await | 
					 | 
				
			||||||
            .map_err(From::from) | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub async fn update_payload( | 
					    pub async fn download(uid: Uuid) -> Result<Response<Body>, Rejection> { | 
				
			||||||
        repo: Arc<PgRepo>, | 
					        let data = match Mapper::get(uid) { | 
				
			||||||
        payload: Payload, | 
					            Some(mut r) => { | 
				
			||||||
    ) -> EndpResult<api_types::UpdatePayload> { | 
					                let mut buf = vec![]; | 
				
			||||||
        debug!("update payload: {payload:?}"); | 
					                r.read_to_end(&mut buf).unwrap(); | 
				
			||||||
        match payload.data { | 
					                build_ok(buf) | 
				
			||||||
            Some(data) => { | 
					 | 
				
			||||||
                let mut well_formed_payload = | 
					 | 
				
			||||||
                    Payload::from_data(data, Some(payload.name)).map_err(Error::from)?; | 
					 | 
				
			||||||
                well_formed_payload.id = payload.id; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                repo.interact(move |mut db| db.update_payload(&well_formed_payload)) | 
					 | 
				
			||||||
                    .await | 
					 | 
				
			||||||
                    .map_err(From::from) | 
					 | 
				
			||||||
            } | 
					            } | 
				
			||||||
            None => repo | 
					            None => build_not_found(), | 
				
			||||||
                .interact(move |mut db| db.update_payload(&payload)) | 
					        }; | 
				
			||||||
                .await | 
					        Ok(data) | 
				
			||||||
                .map_err(From::from), | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					    } | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,6 @@ | 
				
			|||||||
// due to linking errors
 | 
					use u_server_lib::serve; | 
				
			||||||
extern crate openssl; | 
					 | 
				
			||||||
// don't touch anything
 | 
					 | 
				
			||||||
extern crate diesel; | 
					 | 
				
			||||||
// in this block
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[macro_use] | 
					 | 
				
			||||||
extern crate tracing; | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::main] | 
					#[tokio::main] | 
				
			||||||
async fn main() { | 
					async fn main() { | 
				
			||||||
    u_lib::logging::init_logger(Some("u_server")); | 
					    serve().await; | 
				
			||||||
 | 
					 | 
				
			||||||
    if let Err(e) = u_server_lib::serve().await { | 
					 | 
				
			||||||
        error!("U_SERVER error: {}", e); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,65 @@ | 
				
			|||||||
 | 
					use crate::worker::Worker; | 
				
			||||||
 | 
					use once_cell::sync::Lazy; | 
				
			||||||
 | 
					use sha2::{Digest, Sha512}; | 
				
			||||||
 | 
					use std::fs; | 
				
			||||||
 | 
					use std::path::PathBuf; | 
				
			||||||
 | 
					use u_lib::utils::{Hexlify, SharedStorage}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct FileMeta { | 
				
			||||||
 | 
					    hashsum: String, | 
				
			||||||
 | 
					    path: PathBuf, | 
				
			||||||
 | 
					    name: String, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static FILES: Lazy<SharedStorage<String, FileMeta>> = SharedStorage::new(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct FileIndex; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FileIndex { | 
				
			||||||
 | 
					    pub fn search_by_name(pat: impl Into<String>) -> Option<String> { | 
				
			||||||
 | 
					        let name = pat.into(); | 
				
			||||||
 | 
					        let (key, _) = FILES.lock().iter().find(|(_, v)| v.name == name)?; | 
				
			||||||
 | 
					        Some(*key) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(key: impl Into<String>) -> Option<FileMeta> { | 
				
			||||||
 | 
					        let name = key.into(); | 
				
			||||||
 | 
					        FILES.get(&name).and_then(|r| Some(*r)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Worker for FileIndex { | 
				
			||||||
 | 
					    fn process(&mut self) { | 
				
			||||||
 | 
					        let hexlify = |data: &[u8]| format!("{:x}", Hexlify(data)); | 
				
			||||||
 | 
					        let mut hasher = Sha512::new(); | 
				
			||||||
 | 
					        for file in fs::read_dir("./files/hosted/").unwrap() { | 
				
			||||||
 | 
					            let file = match file { | 
				
			||||||
 | 
					                Ok(f) => f.path(), | 
				
			||||||
 | 
					                Err(e) => { | 
				
			||||||
 | 
					                    warn!("Error reading file: {}", e); | 
				
			||||||
 | 
					                    continue; | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            }; | 
				
			||||||
 | 
					            if file.is_file() { | 
				
			||||||
 | 
					                let file_data = fs::read(file).unwrap(); | 
				
			||||||
 | 
					                hasher.update(file_data); | 
				
			||||||
 | 
					                let hashsum = hasher.finalize_reset(); | 
				
			||||||
 | 
					                let shrinked_sum = hashsum | 
				
			||||||
 | 
					                    .iter() | 
				
			||||||
 | 
					                    .cloned() | 
				
			||||||
 | 
					                    .take(32) | 
				
			||||||
 | 
					                    .map(|b| b ^ 0x5a) | 
				
			||||||
 | 
					                    .collect::<Vec<u8>>(); | 
				
			||||||
 | 
					                let shrinked_sum = hexlify(&shrinked_sum); | 
				
			||||||
 | 
					                let hashsum = hexlify(&hashsum); | 
				
			||||||
 | 
					                let name = file.file_name().unwrap().to_string_lossy().into_owned(); | 
				
			||||||
 | 
					                let meta = FileMeta { | 
				
			||||||
 | 
					                    hashsum, | 
				
			||||||
 | 
					                    name, | 
				
			||||||
 | 
					                    path: file, | 
				
			||||||
 | 
					                }; | 
				
			||||||
 | 
					                FILES.lock().insert(shrinked_sum, meta); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,78 @@ | 
				
			|||||||
 | 
					use once_cell::sync::Lazy; | 
				
			||||||
 | 
					use std::collections::HashSet; | 
				
			||||||
 | 
					use std::io::Read; | 
				
			||||||
 | 
					use std::iter::Iterator; | 
				
			||||||
 | 
					use std::time::{Duration, SystemTime}; | 
				
			||||||
 | 
					use u_lib::utils::SharedStorage; | 
				
			||||||
 | 
					use uuid::Uuid; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Data = Box<dyn Read + Send + Sync>; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// This struct holds uids of various readable objects (files, streams, etc...)
 | 
				
			||||||
 | 
					/// with timeout and permissions for any uids to read data
 | 
				
			||||||
 | 
					struct MappedData { | 
				
			||||||
 | 
					    //ids that allowed to read the data
 | 
				
			||||||
 | 
					    for_ids: HashSet<Uuid>, | 
				
			||||||
 | 
					    created: SystemTime, | 
				
			||||||
 | 
					    timeout: Duration, | 
				
			||||||
 | 
					    data: Data, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl MappedData { | 
				
			||||||
 | 
					    pub fn new( | 
				
			||||||
 | 
					        ids: impl Iterator<Item = Uuid>, | 
				
			||||||
 | 
					        data: impl Read + Send + Sync + 'static, | 
				
			||||||
 | 
					        timeout: Duration, | 
				
			||||||
 | 
					    ) -> Self { | 
				
			||||||
 | 
					        MappedData { | 
				
			||||||
 | 
					            for_ids: ids.collect(), | 
				
			||||||
 | 
					            created: SystemTime::now(), | 
				
			||||||
 | 
					            timeout, | 
				
			||||||
 | 
					            data: Box::new(data), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static MAPPER: Lazy<SharedStorage<Uuid, MappedData>> = SharedStorage::new(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Mapper; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Mapper { | 
				
			||||||
 | 
					    fn remove_overdue() { | 
				
			||||||
 | 
					        MAPPER | 
				
			||||||
 | 
					            .lock() | 
				
			||||||
 | 
					            .retain(|_, v| v.created.elapsed().unwrap() < v.timeout); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(item_uid: Uuid, getter_uid: Uuid) -> Option<Data> { | 
				
			||||||
 | 
					        Self::remove_overdue(); | 
				
			||||||
 | 
					        let allowed_ids = MAPPER.lock().get(&item_uid)?.for_ids; | 
				
			||||||
 | 
					        if allowed_ids.contains(&getter_uid) { | 
				
			||||||
 | 
					            MAPPER.lock().remove(&item_uid).map(|d| d.data) | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            None | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn set( | 
				
			||||||
 | 
					        for_ids: impl Iterator<Item = Uuid>, | 
				
			||||||
 | 
					        data: impl Read + Send + Sync + 'static, | 
				
			||||||
 | 
					        timeout: Option<Duration>, | 
				
			||||||
 | 
					    ) -> Uuid { | 
				
			||||||
 | 
					        Self::remove_overdue(); | 
				
			||||||
 | 
					        let uid = Uuid::new_v4(); | 
				
			||||||
 | 
					        let timeout = timeout.unwrap_or(Duration::from_secs(60)); | 
				
			||||||
 | 
					        let mapped = MappedData::new(for_ids, data, timeout); | 
				
			||||||
 | 
					        MAPPER.lock().insert(uid, mapped); | 
				
			||||||
 | 
					        uid | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// init:
 | 
				
			||||||
 | 
					// fill index struct with files allowed to download (probably auto-refresh index on file adding)
 | 
				
			||||||
 | 
					// hashmap<stripped_hash_of_file_name, PathBuf>
 | 
				
			||||||
 | 
					// download:
 | 
				
			||||||
 | 
					// 1) agent sends download_request with hash of wanted file
 | 
				
			||||||
 | 
					// 2) handler checks if this agent is allowed to dl file
 | 
				
			||||||
 | 
					// 3) if all ok, create in entry in mapper and return its uid and hashsum to agent
 | 
				
			||||||
 | 
					// 4) agent downloads file by uid, uid destructed after that
 | 
				
			||||||
@ -0,0 +1,5 @@ | 
				
			|||||||
 | 
					pub mod files; | 
				
			||||||
 | 
					pub mod mapper; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use files::FileIndex; | 
				
			||||||
 | 
					pub use mapper::Mapper; | 
				
			||||||
@ -0,0 +1,3 @@ | 
				
			|||||||
 | 
					pub trait Worker { | 
				
			||||||
 | 
					    fn process(&mut self); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,24 +1,16 @@ | 
				
			|||||||
set -ex | 
					set -ex | 
				
			||||||
source $(dirname $0)/rootdir.sh #set ROOTDIR | 
					DIR=. | 
				
			||||||
DIR=$ROOTDIR/certs | 
					 | 
				
			||||||
V3_CFG=$DIR/v3.ext | 
					V3_CFG=$DIR/v3.ext | 
				
			||||||
 | 
					
 | 
				
			||||||
mkdir -p $DIR | 
					 | 
				
			||||||
cat > $V3_CFG << EOF | 
					cat > $V3_CFG << EOF | 
				
			||||||
authorityKeyIdentifier=keyid,issuer | 
					authorityKeyIdentifier=keyid,issuer | 
				
			||||||
basicConstraints=CA:FALSE | 
					basicConstraints=CA:FALSE | 
				
			||||||
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign | 
					keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign | 
				
			||||||
subjectAltName = @alt_names | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[alt_names] | 
					 | 
				
			||||||
DNS.1 = ortem.xyz | 
					 | 
				
			||||||
DNS.2 = u_server | 
					 | 
				
			||||||
DNS.3 = localhost | 
					 | 
				
			||||||
EOF | 
					EOF | 
				
			||||||
 | 
					
 | 
				
			||||||
openssl req -x509 -newkey rsa:4096 -keyout $DIR/ca.key -out $DIR/ca.crt -nodes -days 365 -subj "/CN=root" | 
					openssl req -x509 -newkey rsa:4096 -keyout $DIR/ca.key -out $DIR/ca.crt -nodes -days 365 -subj "/CN=root" | 
				
			||||||
openssl req -newkey rsa:4096 -keyout $DIR/alice.key -out $DIR/alice.csr -nodes -days 365 -subj "/CN=alice" | 
					openssl req -newkey rsa:4096 -keyout $DIR/alice.key -out $DIR/alice.csr -nodes -days 365 -subj "/CN=alice" | 
				
			||||||
openssl req -newkey rsa:4096 -keyout $DIR/server.key -out $DIR/server.csr -nodes -days 365 -subj "/CN=ortem.xyz" | 
					openssl req -newkey rsa:4096 -keyout $DIR/server.key -out $DIR/server.csr -nodes -days 365 -subj "/CN=u_server" | 
				
			||||||
openssl x509 -req -in $DIR/alice.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/alice.crt -set_serial 01 -days 365 -extfile $V3_CFG | 
					openssl x509 -req -in $DIR/alice.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/alice.crt -set_serial 01 -days 365 -extfile $V3_CFG | 
				
			||||||
openssl x509 -req -in $DIR/server.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/server.crt -set_serial 01 -days 365 -extfile $V3_CFG | 
					openssl x509 -req -in $DIR/server.csr -CA $DIR/ca.crt -CAkey $DIR/ca.key -out $DIR/server.crt -set_serial 01 -days 365 -extfile $V3_CFG | 
				
			||||||
openssl pkcs12 -export -out $DIR/alice.p12 -inkey $DIR/alice.key -in $DIR/alice.crt -passin pass: -passout pass: | 
					openssl pkcs12 -export -out $DIR/alice.p12 -inkey $DIR/alice.key -in $DIR/alice.crt -passin pass: -passout pass: | 
				
			||||||
@ -1,52 +0,0 @@ | 
				
			|||||||
version: "3.4" | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
networks: | 
					 | 
				
			||||||
  u_net: | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
services: | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  u_server: | 
					 | 
				
			||||||
    image: localhost/unki/u_server | 
					 | 
				
			||||||
    networks: | 
					 | 
				
			||||||
      - u_net | 
					 | 
				
			||||||
    volumes: | 
					 | 
				
			||||||
      - ./u_server:/unki/u_server | 
					 | 
				
			||||||
      - ./logs:/unki/logs:rw | 
					 | 
				
			||||||
    working_dir: /unki | 
					 | 
				
			||||||
    command: /unki/u_server | 
					 | 
				
			||||||
    depends_on: | 
					 | 
				
			||||||
      u_db: | 
					 | 
				
			||||||
        condition: service_healthy | 
					 | 
				
			||||||
    ports: | 
					 | 
				
			||||||
      - 9990:9990 | 
					 | 
				
			||||||
    env_file: | 
					 | 
				
			||||||
      - ./.env | 
					 | 
				
			||||||
      - ./.env.private | 
					 | 
				
			||||||
    environment: | 
					 | 
				
			||||||
      RUST_LOG: warp=info,u_server_lib=debug | 
					 | 
				
			||||||
    healthcheck: | 
					 | 
				
			||||||
        test: ss -tlpn | grep 9990 | 
					 | 
				
			||||||
        interval: 5s | 
					 | 
				
			||||||
        timeout: 2s | 
					 | 
				
			||||||
        retries: 2 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  u_db: | 
					 | 
				
			||||||
    image: localhost/unki/u_db | 
					 | 
				
			||||||
    networks: | 
					 | 
				
			||||||
      - u_net | 
					 | 
				
			||||||
    env_file: | 
					 | 
				
			||||||
      - ./.env | 
					 | 
				
			||||||
      - ./.env.private | 
					 | 
				
			||||||
    volumes: | 
					 | 
				
			||||||
      - ./migrator:/migrator | 
					 | 
				
			||||||
      - ./data:/var/lib/postgresql/data | 
					 | 
				
			||||||
      - type: bind | 
					 | 
				
			||||||
        source: ./u_db_entrypoint.sh | 
					 | 
				
			||||||
        target: /u_db_entrypoint.sh | 
					 | 
				
			||||||
    command: /u_db_entrypoint.sh | 
					 | 
				
			||||||
    healthcheck: | 
					 | 
				
			||||||
        # test if db's port is open and db is created | 
					 | 
				
			||||||
        test: ss -tlpn | grep 5432 && psql -lqt -U $${POSTGRES_USER} | grep -qw $${POSTGRES_DATABASE} | 
					 | 
				
			||||||
        interval: 5s | 
					 | 
				
			||||||
        timeout: 5s | 
					 | 
				
			||||||
        retries: 3 | 
					 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue