Compare commits
7 Commits
master
...
14-integra
Author | SHA1 | Date |
---|---|---|
plazmoid | 4bfcdd2b23 | 4 years ago |
plazmoid | 4e88f49f96 | 4 years ago |
plazmoid | 3e0c9ecd77 | 4 years ago |
plazmoid | 7735596bf0 | 4 years ago |
plazmoid | 5248ae7ac4 | 4 years ago |
plazmoid | 8cccc82fc2 | 4 years ago |
plazmoid | 5734145e8f | 4 years ago |
191 changed files with 2952 additions and 12941 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,3 @@ |
|||||||
POSTGRES_HOST=u_db |
ADMIN_AUTH_TOKEN=464af63dbd241969baa1e94b2461d94d |
||||||
POSTGRES_DATABASE=u_db |
POSTGRES_PASSWORD=12348756 |
||||||
POSTGRES_USER=u_ser |
DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@u_db/u_db |
||||||
POSTGRES_PORT=5432 |
|
||||||
RUST_BACKTRACE=1 |
|
||||||
U_SERVER=u_server |
|
||||||
|
@ -1,14 +1,6 @@ |
|||||||
target/ |
target/ |
||||||
|
**/*.rs.bk |
||||||
.idea/ |
.idea/ |
||||||
data/ |
data/ |
||||||
certs/ |
|
||||||
static/ |
static/ |
||||||
.vscode/ |
**/*.pyc |
||||||
release/ |
|
||||||
**/node_modules/ |
|
||||||
|
|
||||||
**/*.rs.bk |
|
||||||
**/*.pyc |
|
||||||
*.log |
|
||||||
echoer |
|
||||||
.env.private |
|
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,18 @@ |
|||||||
|
.PHONY: _pre_build debug release run clean |
||||||
|
|
||||||
|
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
|
@ -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,8 +0,0 @@ |
|||||||
use std::path::PathBuf; |
|
||||||
|
|
||||||
fn main() { |
|
||||||
let server_cert = PathBuf::from("../../certs/ca.crt"); |
|
||||||
if !server_cert.exists() { |
|
||||||
panic!("CA certificate doesn't exist. Create it first with scripts/gen_certs.sh"); |
|
||||||
} |
|
||||||
} |
|
@ -1,167 +1,88 @@ |
|||||||
|
// 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: \n{}", |
||||||
|
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("\n") |
||||||
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_agent_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,148 @@ |
|||||||
mod argparse; |
use serde::Serialize; |
||||||
mod gui; |
use std::env; |
||||||
|
use std::fmt; |
||||||
|
use structopt::StructOpt; |
||||||
|
use u_lib::{ |
||||||
|
api::ClientHandler, messaging::AsMsg, models::JobMeta, utils::init_env, UError, 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) { |
||||||
|
fn printer<Msg: AsMsg + fmt::Display>(data: UResult<Msg>, json: bool) { |
||||||
|
if json { |
||||||
|
#[derive(Serialize)] |
||||||
|
#[serde(rename_all = "lowercase")] |
||||||
|
#[serde(tag = "status", content = "data")] |
||||||
|
enum DataResult<M> { |
||||||
|
Ok(M), |
||||||
|
Err(UError), |
||||||
|
} |
||||||
|
|
||||||
|
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 json = args.json; |
||||||
|
match args.cmd { |
||||||
|
Cmd::Agents(action) => match action { |
||||||
|
LD::List { uid } => printer(cli_handler.get_agents(uid).await, json), |
||||||
|
LD::Delete { uid } => printer(cli_handler.del(Some(uid)).await, json), |
||||||
|
}, |
||||||
|
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(cli_handler.upload_jobs(&[job]).await, json); |
||||||
|
} |
||||||
|
JobALD::LD(LD::List { uid }) => printer(cli_handler.get_jobs(uid).await, json), |
||||||
|
JobALD::LD(LD::Delete { uid }) => printer(cli_handler.del(Some(uid)).await, json), |
||||||
|
}, |
||||||
|
Cmd::Jobmap(action) => match action { |
||||||
|
JobMapALD::Add { |
||||||
|
agent_uid, |
||||||
|
job_idents, |
||||||
|
} => printer( |
||||||
|
cli_handler.set_jobs(Some(agent_uid), &job_idents).await, |
||||||
|
json, |
||||||
|
), |
||||||
|
JobMapALD::List { uid } => printer(cli_handler.get_agent_jobs(uid).await, json), |
||||||
|
JobMapALD::Delete { uid } => printer(cli_handler.del(Some(uid)).await, json), |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
println!("{result}"); |
#[tokio::main] |
||||||
Ok(()) |
async fn main() { |
||||||
|
init_env(); |
||||||
|
let args: Args = Args::from_args(); |
||||||
|
process_cmd(args).await; |
||||||
} |
} |
||||||
|
@ -1,34 +1,44 @@ |
|||||||
[package] |
[package] |
||||||
authors = ["plazmoid <kronos44@mail.ru>"] |
authors = ["plazmoid <kronos44@mail.ru>"] |
||||||
edition = "2021" |
edition = "2018" |
||||||
name = "u_server" |
name = "u_server" |
||||||
version = "0.1.0" |
version = "0.1.0" |
||||||
|
|
||||||
[dependencies] |
[dependencies] |
||||||
anyhow = { workspace = true } |
log = "0.4.11" |
||||||
diesel = { workspace = true } |
simplelog = "0.10" |
||||||
deadpool-diesel = { workspace = true } |
thiserror = "*" |
||||||
hyper = "0.14" |
warp = "0.2.4" |
||||||
mime_guess = { workspace = true } |
uuid = { version = "0.6.5", features = ["serde", "v4"] } |
||||||
once_cell = "1.7.2" |
once_cell = "1.7.2" |
||||||
openssl = { workspace = true } |
hyper = "0.13.10" |
||||||
serde = { workspace = true } |
mockall = "0.9.1" |
||||||
serde_json = { workspace = true } |
mockall_double = "0.2" |
||||||
thiserror = { workspace = true } |
openssl = "*" |
||||||
tracing = { workspace = true } |
|
||||||
tokio = { workspace = true, features = ["macros"] } |
[dependencies.diesel] |
||||||
uuid = { workspace = true, features = ["serde", "v4"] } |
features = ["postgres", "uuid"] |
||||||
u_lib = { path = "../../lib/u_lib", features = ["server"] } |
version = "1.4.5" |
||||||
warp = { version = "0.3.1", features = ["tls"] } |
|
||||||
serde_qs = { version = "0.12.0", features = ["warp"] } |
[dependencies.serde] |
||||||
|
features = ["derive"] |
||||||
|
version = "1.0.114" |
||||||
|
|
||||||
|
[dependencies.tokio] |
||||||
|
features = ["macros"] |
||||||
|
version = "0.2.22" |
||||||
|
|
||||||
|
[dependencies.u_lib] |
||||||
|
path = "../../lib/u_lib" |
||||||
|
version = "*" |
||||||
|
|
||||||
[dev-dependencies] |
[dev-dependencies] |
||||||
rstest = "0.12" |
test-case = "1.1.0" |
||||||
|
|
||||||
[lib] |
[lib] |
||||||
name = "u_server_lib" |
name = "u_server_lib" |
||||||
path = "src/u_server.rs" |
path = "src/lib.rs" |
||||||
|
|
||||||
[[bin]] |
[[bin]] |
||||||
name = "u_server" |
name = "u_server" |
||||||
path = "src/main.rs" |
path = "src/main.rs" |
@ -1,377 +1,223 @@ |
|||||||
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 } |
|
||||||
} |
|
||||||
|
|
||||||
pub async fn interact<F, R>(&self, f: F) -> Result<R> |
|
||||||
where |
|
||||||
F: for<'c> FnOnce(UDB<'c>) -> Result<R>, |
|
||||||
F: Send + 'static, |
|
||||||
R: Send + 'static, |
|
||||||
{ |
|
||||||
let connection = self.pool.get().await?; |
|
||||||
connection |
|
||||||
.interact(|conn| f(UDB { conn })) |
|
||||||
.await |
|
||||||
.expect("deadpool interaction failed") |
|
||||||
} |
|
||||||
|
|
||||||
pub async fn transaction<F, R>(&self, f: F) -> Result<R> |
#[cfg_attr(test, automock)] |
||||||
where |
impl UDB { |
||||||
F: for<'c> FnOnce(UDB<'c>) -> Result<R>, |
pub fn lock_db() -> MutexGuard<'static, UDB> { |
||||||
F: Send + 'static, |
DB.get_or_init(|| { |
||||||
R: Send + 'static, |
let db_path = env::var("DATABASE_URL").unwrap(); |
||||||
{ |
let conn = PgConnection::establish(&db_path).unwrap(); |
||||||
let conn = self.pool.get().await?; |
let instance = UDB { conn }; |
||||||
conn.interact(|c| c.transaction(|conn| f(UDB { conn }))) |
Arc::new(Mutex::new(instance)) |
||||||
.await |
}) |
||||||
.expect("deadpool interaction failed") |
.lock() |
||||||
|
.unwrap() |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
pub struct UDB<'c> { |
pub fn insert_jobs(&self, job_metas: &[JobMeta]) -> ULocalResult<()> { |
||||||
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 assigned_job_ids = HashSet::<Uuid>::from_iter(assigned_jobs.iter().map(|a| a.job_id)); |
|
||||||
|
|
||||||
let jobs_meta = HashMap::<Id, JobBriefMeta>::from_iter( |
|
||||||
jobs::table |
|
||||||
.select((jobs::id, jobs::alias, jobs::target_platforms)) |
|
||||||
.filter(jobs::id.eq_any(&assigned_job_ids)) |
|
||||||
.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), |
|
||||||
))); |
|
||||||
} |
} |
||||||
|
let not_found_jobs = job_uids |
||||||
for ajob in assigned_jobs { |
.iter() |
||||||
let meta = &jobs_meta[&ajob.job_id]; |
.filter_map(|job_uid| { |
||||||
let agent_platform = match self.get_agent(ajob.agent_id)? { |
if let Err(DslError::NotFound) = jobs.find(job_uid).first::<JobMeta>(&self.conn) { |
||||||
Some(agent) => Platform::new(&agent.platform), |
Some(job_uid.to_string()) |
||||||
None => { |
} else { |
||||||
return Err(Error::ProcessingError(format!( |
None |
||||||
"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| AssignedJob { |
||||||
.map(|a| AssignedJob { |
job_id: *job_uid, |
||||||
job_id: a.job_id, |
agent_id: *agent_uid, |
||||||
agent_id: a.agent_id, |
|
||||||
alias: jobs_meta[&a.job_id].alias.clone(), |
|
||||||
..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,88 @@ |
|||||||
|
use crate::handlers::Endpoints; |
||||||
|
use serde::de::DeserializeOwned; |
||||||
|
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>,), std::convert::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, false)); |
||||||
|
|
||||||
|
let get_personal_jobs = warp::get() |
||||||
|
.and(warp::path("get_agent_jobs")) |
||||||
|
.and(warp::path::param::<Uuid>().map(Some)) |
||||||
|
.and_then(|uid| Endpoints::get_agent_jobs(uid, true)); |
||||||
|
|
||||||
|
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 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 agent_zone = get_jobs.clone().or(get_personal_jobs).or(report); |
||||||
|
|
||||||
|
let auth_zone = auth_header.and( |
||||||
|
get_agents |
||||||
|
.or(get_jobs) |
||||||
|
.or(upload_jobs) |
||||||
|
.or(del) |
||||||
|
.or(set_jobs) |
||||||
|
.or(get_agent_jobs), |
||||||
|
); |
||||||
|
|
||||||
|
agent_zone.or(auth_zone) |
||||||
|
} |
@ -1,343 +1,168 @@ |
|||||||
use std::sync::Arc; |
use crate::db::UDB; |
||||||
|
use diesel::SaveChangesDsl; |
||||||
|
use hyper::Body; |
||||||
|
use serde::Serialize; |
||||||
|
use u_lib::{ |
||||||
|
messaging::{AsMsg, BaseMessage}, |
||||||
|
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()) |
||||||
|
} |
||||||
|
|
||||||
#[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>, |
||||||
Ok(match params.map(|p| p.brief) { |
personal: bool, |
||||||
Some(Brief::Yes) => job, |
) -> Result<Response<Body>, Rejection> { |
||||||
Some(Brief::Auto) | None => { |
info!("hnd: get_agent_jobs {}", personal); |
||||||
if let Some(payload) = &mut job.payload { |
if personal { |
||||||
payload.maybe_join_payload().map_err(Error::from)?; |
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)); |
||||||
} |
} |
||||||
job |
|
||||||
} |
|
||||||
Some(Brief::No) => { |
|
||||||
if let Some(payload) = &mut job.payload { |
|
||||||
payload.join_payload().map_err(Error::from)?; |
|
||||||
} |
|
||||||
job |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
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)?; |
let result = UDB::lock_db().get_exact_jobs(uid, personal); |
||||||
payload |
match result { |
||||||
} |
Ok(r) => { |
||||||
_ => { |
if personal { |
||||||
payload.join_payload().map_err(Error::from)?; |
let db = UDB::lock_db(); |
||||||
payload |
for j in r.iter() { |
||||||
} |
db.update_job_status(j.id, JobState::Running).ok(); |
||||||
}) |
} |
||||||
} |
|
||||||
|
|
||||||
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_ok(affected.to_string())); |
||||||
} |
} |
||||||
checked_jobs.push(job) |
|
||||||
} |
} |
||||||
|
Ok(build_err("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<()> { |
pub async fn set_jobs( |
||||||
repo.transaction(move |mut db| { |
agent_uid: Uuid, |
||||||
[ |
msg: BaseMessage<'static, Vec<String>>, |
||||||
UDB::del_agents, |
) -> Result<Response<Body>, Rejection> { |
||||||
UDB::del_jobs, |
info!("hnd: set_jobs_by_alias"); |
||||||
UDB::del_results, |
let jobs: Result<Vec<Uuid>, ULocalError> = msg |
||||||
UDB::del_payloads, |
.into_inner() |
||||||
] |
.into_iter() |
||||||
.iter() |
.map(|ident| { |
||||||
.map(|f| f(&mut db, &[id])) |
Uuid::parse_str(&ident) |
||||||
.collect::<Result<(), Error>>() |
.or_else(|_| UDB::lock_db().find_job_by_alias(&ident).map(|j| j.id)) |
||||||
}) |
}) |
||||||
.await |
.collect(); |
||||||
.map_err(From::from) |
match jobs { |
||||||
} |
Ok(j) => UDB::lock_db() |
||||||
|
.set_jobs_for_agent(&agent_uid, &j) |
||||||
pub async fn assign_jobs( |
.map(|assigned_uids| build_message(assigned_uids)) |
||||||
repo: Arc<PgRepo>, |
.or_else(|e| Ok(build_err(e))), |
||||||
assigned_jobs: Vec<AssignedJobById>, |
Err(e) => Ok(build_err(e)), |
||||||
) -> EndpResult<()> { |
} |
||||||
repo.transaction(move |mut db| { |
|
||||||
db.assign_jobs(&assigned_jobs)?; |
|
||||||
Ok(()) |
|
||||||
}) |
|
||||||
.await |
|
||||||
.map_err(From::from) |
|
||||||
} |
} |
||||||
|
|
||||||
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?; |
||||||
} |
} |
||||||
} |
} |
||||||
Ok(()) |
|
||||||
}) |
|
||||||
.await |
|
||||||
.map_err(From::from) |
|
||||||
} |
|
||||||
|
|
||||||
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( |
|
||||||
repo: Arc<PgRepo>, |
|
||||||
assigned: AssignedJob, |
|
||||||
) -> EndpResult<api_types::UpdateResult> { |
|
||||||
repo.interact(move |mut db| db.update_result(&assigned)) |
|
||||||
.await |
|
||||||
.map_err(From::from) |
|
||||||
} |
|
||||||
|
|
||||||
pub async fn update_payload( |
|
||||||
repo: Arc<PgRepo>, |
|
||||||
payload: Payload, |
|
||||||
) -> EndpResult<api_types::UpdatePayload> { |
|
||||||
debug!("update payload: {payload:?}"); |
|
||||||
match payload.data { |
|
||||||
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 |
|
||||||
.interact(move |mut db| db.update_payload(&payload)) |
|
||||||
.await |
|
||||||
.map_err(From::from), |
|
||||||
} |
} |
||||||
|
if failed.len() > 0 { |
||||||
|
let err_msg = ULocalError::ProcessingError(failed.join(", ")); |
||||||
|
return Ok(build_err(err_msg)); |
||||||
|
} |
||||||
|
Ok(build_ok("")) |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,102 @@ |
|||||||
|
#[macro_use] |
||||||
|
extern crate log; |
||||||
|
|
||||||
|
#[macro_use] |
||||||
|
extern crate mockall; |
||||||
|
#[macro_use] |
||||||
|
extern crate mockall_double; |
||||||
|
|
||||||
|
// because of linking errors
|
||||||
|
extern crate openssl; |
||||||
|
#[macro_use] |
||||||
|
extern crate diesel; |
||||||
|
//
|
||||||
|
mod db; |
||||||
|
mod filters; |
||||||
|
mod handlers; |
||||||
|
|
||||||
|
use db::UDB; |
||||||
|
use filters::make_filters; |
||||||
|
use u_lib::{config::MASTER_PORT, models::*, utils::init_env}; |
||||||
|
use warp::Filter; |
||||||
|
|
||||||
|
const LOGFILE: &str = "u_server.log"; |
||||||
|
|
||||||
|
fn prefill_jobs() { |
||||||
|
let agent_hello = JobMeta::builder() |
||||||
|
.with_type(misc::JobType::Manage) |
||||||
|
.with_alias("agent_hello") |
||||||
|
.build() |
||||||
|
.unwrap(); |
||||||
|
UDB::lock_db().insert_jobs(&[agent_hello]).ok(); |
||||||
|
} |
||||||
|
|
||||||
|
fn init_logger() { |
||||||
|
use simplelog::*; |
||||||
|
use std::fs::OpenOptions; |
||||||
|
let log_cfg = ConfigBuilder::new() |
||||||
|
.set_time_format_str("%x %X") |
||||||
|
.set_time_to_local(true) |
||||||
|
.build(); |
||||||
|
let logfile = OpenOptions::new() |
||||||
|
.append(true) |
||||||
|
.create(true) |
||||||
|
.open(LOGFILE) |
||||||
|
.unwrap(); |
||||||
|
let loggers = vec![ |
||||||
|
WriteLogger::new(LevelFilter::Info, log_cfg.clone(), logfile) as Box<dyn SharedLogger>, |
||||||
|
TermLogger::new( |
||||||
|
LevelFilter::Info, |
||||||
|
log_cfg, |
||||||
|
TerminalMode::Stderr, |
||||||
|
ColorChoice::Auto, |
||||||
|
), |
||||||
|
]; |
||||||
|
CombinedLogger::init(loggers).unwrap(); |
||||||
|
} |
||||||
|
|
||||||
|
fn init_all() { |
||||||
|
init_logger(); |
||||||
|
init_env(); |
||||||
|
prefill_jobs(); |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn serve() { |
||||||
|
init_all(); |
||||||
|
let routes = make_filters(); |
||||||
|
warp::serve(routes.with(warp::log("warp"))) |
||||||
|
.run(([0, 0, 0, 0], MASTER_PORT)) |
||||||
|
.await; |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
#[double] |
||||||
|
use crate::handlers::Endpoints; |
||||||
|
use handlers::build_ok; |
||||||
|
use mockall::predicate::*; |
||||||
|
use test_case::test_case; |
||||||
|
use uuid::Uuid; |
||||||
|
use warp::test::request; |
||||||
|
|
||||||
|
#[test_case(Some(Uuid::new_v4()))] |
||||||
|
#[test_case(None => panics)] |
||||||
|
#[tokio::test] |
||||||
|
async fn test_get_agent_jobs_unauthorized(uid: Option<Uuid>) { |
||||||
|
let mock = Endpoints::get_agent_jobs_context(); |
||||||
|
mock.expect() |
||||||
|
.with(eq(uid), eq(uid.is_some())) |
||||||
|
.returning(|_, _| Ok(build_ok(""))); |
||||||
|
request() |
||||||
|
.path(&format!( |
||||||
|
"/get_agent_jobs/{}", |
||||||
|
uid.map(|u| u.simple().to_string()).unwrap_or(String::new()) |
||||||
|
)) |
||||||
|
.method("GET") |
||||||
|
.filter(&make_filters()) |
||||||
|
.await |
||||||
|
.unwrap(); |
||||||
|
mock.checkpoint(); |
||||||
|
} |
||||||
|
} |
@ -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); |
|
||||||
} |
|
||||||
} |
} |
||||||
|
@ -1,300 +0,0 @@ |
|||||||
#[macro_use] |
|
||||||
extern crate tracing; |
|
||||||
|
|
||||||
mod db; |
|
||||||
mod error; |
|
||||||
mod handlers; |
|
||||||
|
|
||||||
use crate::handlers::{Endpoints, PayloadFlags}; |
|
||||||
use db::PgRepo; |
|
||||||
use error::{Error as ServerError, RejResponse}; |
|
||||||
use std::{convert::Infallible, sync::Arc}; |
|
||||||
use u_lib::{ |
|
||||||
config, |
|
||||||
db::async_pool, |
|
||||||
messaging::{AsMsg, Reportable}, |
|
||||||
models::*, |
|
||||||
types::Id, |
|
||||||
}; |
|
||||||
use warp::{ |
|
||||||
body, |
|
||||||
log::{custom, Info}, |
|
||||||
reply::{json, Json, Response}, |
|
||||||
Filter, Rejection, Reply, |
|
||||||
}; |
|
||||||
|
|
||||||
const DEFAULT_RESPONSE: &str = "null"; |
|
||||||
|
|
||||||
fn into_message<M: AsMsg>(msg: M) -> Json { |
|
||||||
json(&msg) |
|
||||||
} |
|
||||||
|
|
||||||
pub fn init_endpoints( |
|
||||||
auth_token: &str, |
|
||||||
db: PgRepo, |
|
||||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone { |
|
||||||
fn make_optional<T>( |
|
||||||
f: impl Filter<Extract = (T,), Error = Rejection> + Clone, |
|
||||||
) -> impl Filter<Extract = (Option<T>,), Error = Infallible> + Clone { |
|
||||||
f.map(Some) |
|
||||||
.or_else(|_| async { Ok::<(Option<T>,), Infallible>((None,)) }) |
|
||||||
} |
|
||||||
|
|
||||||
let path = |p: &'static str| warp::post().and(warp::path(p)); |
|
||||||
let create_qs_cfg = || serde_qs::Config::new(1, true); |
|
||||||
|
|
||||||
let with_db = { |
|
||||||
let adb = Arc::new(db); |
|
||||||
warp::any().map(move || adb.clone()) |
|
||||||
}; |
|
||||||
|
|
||||||
let get_agents = path("get_agents") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(make_optional(warp::path::param::<Id>())) |
|
||||||
.and_then(Endpoints::get_agents) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let upload_jobs = path("upload_jobs") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<Vec<Job>>()) |
|
||||||
.and_then(Endpoints::upload_jobs) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let get_job = path("get_job") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(warp::path::param::<Id>()) |
|
||||||
.and(make_optional(serde_qs::warp::query::<PayloadFlags>( |
|
||||||
create_qs_cfg(), |
|
||||||
))) |
|
||||||
.and_then(Endpoints::get_job) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let get_jobs = path("get_jobs") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and_then(Endpoints::get_jobs) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let get_assigned_jobs = path("get_assigned_jobs") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(make_optional(warp::path::param::<Id>())) |
|
||||||
.and_then(Endpoints::get_assigned_jobs) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let get_personal_jobs = path("get_personal_jobs") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(warp::path::param::<Id>()) |
|
||||||
.and_then(Endpoints::get_personal_jobs) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let del = path("del") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(warp::path::param::<Id>()) |
|
||||||
.and_then(Endpoints::del) |
|
||||||
.map(ok); |
|
||||||
|
|
||||||
let assign_jobs = path("assign_jobs") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<Vec<AssignedJobById>>()) |
|
||||||
.and_then(Endpoints::assign_jobs) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let report = path("report") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<Vec<Reportable>>()) |
|
||||||
.and(warp::header("User-Agent")) |
|
||||||
.and_then(Endpoints::report) |
|
||||||
.map(ok); |
|
||||||
|
|
||||||
let update_agent = path("update_agent") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<Agent>()) |
|
||||||
.and_then(Endpoints::update_agent) |
|
||||||
.map(ok); |
|
||||||
|
|
||||||
let update_job = path("update_job") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<JobMeta>()) |
|
||||||
.and_then(Endpoints::update_job) |
|
||||||
.map(ok); |
|
||||||
|
|
||||||
let update_assigned_job = path("update_result") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<AssignedJob>()) |
|
||||||
.and_then(Endpoints::update_assigned_job) |
|
||||||
.map(ok); |
|
||||||
|
|
||||||
let get_payloads = path("get_payloads") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and_then(Endpoints::get_payloads) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let get_payload = path("get_payload") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(warp::path::param::<String>()) |
|
||||||
.and(make_optional(serde_qs::warp::query::<PayloadFlags>( |
|
||||||
create_qs_cfg(), |
|
||||||
))) |
|
||||||
.and_then(Endpoints::get_payload) |
|
||||||
.map(into_message); |
|
||||||
|
|
||||||
let upload_payload = path("upload_payload") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<RawPayload>()) |
|
||||||
.and_then(Endpoints::upload_payload) |
|
||||||
.map(ok); |
|
||||||
|
|
||||||
let update_payload = path("update_payload") |
|
||||||
.and(with_db.clone()) |
|
||||||
.and(body::json::<Payload>()) |
|
||||||
.and_then(Endpoints::update_payload) |
|
||||||
.map(ok); |
|
||||||
|
|
||||||
let ping = path("ping").map(|| DEFAULT_RESPONSE); |
|
||||||
|
|
||||||
let auth_token = format!("Bearer {auth_token}",).into_boxed_str(); |
|
||||||
let auth_header = warp::header::exact("authorization", Box::leak(auth_token)); |
|
||||||
|
|
||||||
let auth_zone = (get_agents |
|
||||||
.or(get_job.clone()) |
|
||||||
.or(get_jobs.clone()) |
|
||||||
.or(get_payloads) |
|
||||||
.or(get_payload) |
|
||||||
.or(upload_jobs) |
|
||||||
.or(upload_payload) |
|
||||||
.or(del) |
|
||||||
.or(assign_jobs) |
|
||||||
.or(get_assigned_jobs) |
|
||||||
.or(update_agent) |
|
||||||
.or(update_job) |
|
||||||
.or(update_assigned_job) |
|
||||||
.or(update_payload) |
|
||||||
.or(ping)) |
|
||||||
.and(auth_header); |
|
||||||
|
|
||||||
let agent_zone = get_job.or(get_jobs).or(get_personal_jobs).or(report); |
|
||||||
|
|
||||||
auth_zone.or(agent_zone) |
|
||||||
} |
|
||||||
|
|
||||||
pub async fn preload_jobs(repo: &PgRepo) -> Result<(), ServerError> { |
|
||||||
repo.interact(|mut db| { |
|
||||||
let job_alias = "agent_hello"; |
|
||||||
let if_job_exists = db.get_job_by_alias(job_alias)?; |
|
||||||
if if_job_exists.is_none() { |
|
||||||
let agent_hello = RawJob::default() |
|
||||||
.with_type(JobType::Init) |
|
||||||
.with_alias(job_alias) |
|
||||||
.try_into_job() |
|
||||||
.unwrap(); |
|
||||||
db.insert_jobs(&[agent_hello.meta])?; |
|
||||||
} |
|
||||||
Ok(()) |
|
||||||
}) |
|
||||||
.await |
|
||||||
} |
|
||||||
|
|
||||||
pub async fn serve() -> Result<(), ServerError> { |
|
||||||
let env = config::DBEnv::load()?; |
|
||||||
let pool = async_pool(&env); |
|
||||||
let db = PgRepo::new(pool); |
|
||||||
|
|
||||||
preload_jobs(&db).await?; |
|
||||||
|
|
||||||
let env = config::AccessEnv::load()?; |
|
||||||
let routes = init_endpoints(&env.admin_auth_token, db) |
|
||||||
.recover(handle_rejection) |
|
||||||
.with(custom(logger)); |
|
||||||
|
|
||||||
let server_cert = include_bytes!("../../../certs/server.crt"); |
|
||||||
let server_key = include_bytes!("../../../certs/server.key"); |
|
||||||
let ca = include_bytes!("../../../certs/ca.crt"); |
|
||||||
|
|
||||||
warp::serve(routes) |
|
||||||
.tls() |
|
||||||
.cert(server_cert) |
|
||||||
.key(server_key) |
|
||||||
.client_auth_required(ca) |
|
||||||
.run(([0, 0, 0, 0], config::MASTER_PORT)) |
|
||||||
.await; |
|
||||||
Ok(()) |
|
||||||
} |
|
||||||
|
|
||||||
async fn handle_rejection(rej: Rejection) -> Result<Response, Infallible> { |
|
||||||
let resp = if let Some(err) = rej.find::<ServerError>() { |
|
||||||
error!("{:?}", err); |
|
||||||
RejResponse::bad_request(err.to_string()) |
|
||||||
} else if rej.is_not_found() { |
|
||||||
RejResponse::not_found("not found placeholder") |
|
||||||
} else { |
|
||||||
error!("{:?}", rej); |
|
||||||
RejResponse::internal() |
|
||||||
}; |
|
||||||
Ok(resp.into_response()) |
|
||||||
} |
|
||||||
|
|
||||||
fn logger(info: Info<'_>) { |
|
||||||
info!(target: "warp", |
|
||||||
"{raddr} {agent_id} \"{path}\" {status}", |
|
||||||
raddr = info.remote_addr().unwrap_or(([0, 0, 0, 0], 0).into()), |
|
||||||
path = info.path(), |
|
||||||
agent_id = info.user_agent() |
|
||||||
.map(|id: &str| id.splitn(3, '-') |
|
||||||
.take(2) |
|
||||||
.collect::<String>() |
|
||||||
) |
|
||||||
.unwrap_or_else(|| "NO_AGENT_UID".to_string()), |
|
||||||
status = info.status() |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
fn ok<T>(_: T) -> impl Reply { |
|
||||||
DEFAULT_RESPONSE |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
#[cfg(test)] |
|
||||||
mod tests { |
|
||||||
use super::*; |
|
||||||
use crate::handlers::Endpoints; |
|
||||||
use handlers::build_ok; |
|
||||||
use u_lib::messaging::{AsMsg, BaseMessage, Reportable}; |
|
||||||
use uuid::Uuid; |
|
||||||
use warp::test; |
|
||||||
|
|
||||||
#[rstest] |
|
||||||
#[case(Some(Uuid::new_v4()))] |
|
||||||
#[should_panic] |
|
||||||
#[case(None)] |
|
||||||
#[tokio::test] |
|
||||||
async fn test_get_agent_jobs_unauthorized(#[case] uid: Option<Uuid>) { |
|
||||||
let mock = Endpoints::faux(); |
|
||||||
when!(mock.get_agent_jobs).then_return(Ok(build_ok(""))); |
|
||||||
//mock.expect().with(eq(uid)).returning(|_| Ok(build_ok("")));
|
|
||||||
test::request() |
|
||||||
.path(&format!( |
|
||||||
"/get_agent_jobs/{}", |
|
||||||
uid.map(|u| u.simple().to_string()).unwrap_or(String::new()) |
|
||||||
)) |
|
||||||
.method("GET") |
|
||||||
.filter(&init_filters("")) |
|
||||||
.await |
|
||||||
.unwrap(); |
|
||||||
} |
|
||||||
|
|
||||||
#[tokio::test] |
|
||||||
async fn test_report_unauth_successful() { |
|
||||||
let mock = Endpoints::report(); |
|
||||||
mock.expect() |
|
||||||
.withf(|msg: &BaseMessage<'_, Vec<Reportable>>| msg.inner_ref()[0] == Reportable::Dummy) |
|
||||||
.returning(|_| Ok(build_ok(""))); |
|
||||||
test::request() |
|
||||||
.path("/report/") |
|
||||||
.method("POST") |
|
||||||
.json(&vec![Reportable::Dummy].as_message()) |
|
||||||
.filter(&init_filters("")) |
|
||||||
.await |
|
||||||
.unwrap(); |
|
||||||
mock.checkpoint(); |
|
||||||
} |
|
||||||
} |
|
||||||
*/ |
|
@ -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 |
|
@ -1,9 +0,0 @@ |
|||||||
#!/bin/bash |
|
||||||
|
|
||||||
export DOCKER_UID=$(id -u) |
|
||||||
export DOCKER_GID=$(id -g) |
|
||||||
|
|
||||||
docker build -t localhost/unki/u_db -f u_db.Dockerfile . |
|
||||||
docker build -t localhost/unki/u_server -f u_server.Dockerfile . |
|
||||||
podman-compose down -v |
|
||||||
podman-compose up -d |
|
@ -1,95 +0,0 @@ |
|||||||
FROM ubuntu:xenial |
|
||||||
LABEL maintainer="Eirik Albrigtsen <sszynrae@gmail.com>" |
|
||||||
|
|
||||||
# Required packages: |
|
||||||
# - musl-dev, musl-tools - the musl toolchain |
|
||||||
# - curl, g++, make, pkgconf, cmake - for fetching and building third party libs |
|
||||||
# - ca-certificates - openssl + curl + peer verification of downloads |
|
||||||
# - xutils-dev - for openssl makedepend |
|
||||||
# - libssl-dev and libpq-dev - for dynamic linking during diesel_codegen build process |
|
||||||
# - git - cargo builds in user projects |
|
||||||
# - linux-headers-amd64 - needed for building openssl 1.1 (stretch only) |
|
||||||
# - file - needed by rustup.sh install |
|
||||||
# - automake autoconf libtool - support crates building C deps as part cargo build |
|
||||||
# recently removed: |
|
||||||
# cmake (not used), nano, zlib1g-dev |
|
||||||
RUN apt-get update && apt-get install -y \ |
|
||||||
musl-dev \ |
|
||||||
musl-tools \ |
|
||||||
git \ |
|
||||||
file \ |
|
||||||
openssh-client \ |
|
||||||
make \ |
|
||||||
g++ \ |
|
||||||
curl \ |
|
||||||
pkgconf \ |
|
||||||
ca-certificates \ |
|
||||||
xutils-dev \ |
|
||||||
libssl-dev \ |
|
||||||
libpq-dev \ |
|
||||||
automake \ |
|
||||||
autoconf \ |
|
||||||
libtool \ |
|
||||||
python3 \ |
|
||||||
--no-install-recommends && \ |
|
||||||
rm -rf /var/lib/apt/lists/* |
|
||||||
|
|
||||||
# Convenience list of versions and variables for compilation later on |
|
||||||
# This helps continuing manually if anything breaks. |
|
||||||
ENV SSL_VER="1.0.2u" \ |
|
||||||
CURL_VER="7.77.0" \ |
|
||||||
ZLIB_VER="1.2.13" \ |
|
||||||
PQ_VER="11.12" \ |
|
||||||
SQLITE_VER="3350500" \ |
|
||||||
CC=musl-gcc \ |
|
||||||
PREFIX=/musl \ |
|
||||||
PATH=/usr/local/bin:/root/.cargo/bin:$PATH \ |
|
||||||
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig \ |
|
||||||
LD_LIBRARY_PATH=$PREFIX |
|
||||||
|
|
||||||
# Set up a prefix for musl build libraries, make the linker's job of finding them easier |
|
||||||
# Primarily for the benefit of postgres. |
|
||||||
# Lastly, link some linux-headers for openssl 1.1 (not used herein) |
|
||||||
RUN mkdir $PREFIX && \ |
|
||||||
echo "$PREFIX/lib" >> /etc/ld-musl-x86_64.path && \ |
|
||||||
ln -s /usr/include/x86_64-linux-gnu/asm /usr/include/x86_64-linux-musl/asm && \ |
|
||||||
ln -s /usr/include/asm-generic /usr/include/x86_64-linux-musl/asm-generic && \ |
|
||||||
ln -s /usr/include/linux /usr/include/x86_64-linux-musl/linux |
|
||||||
|
|
||||||
# Build zlib (used in openssl and pq) |
|
||||||
RUN curl -sSL https://zlib.net/zlib-$ZLIB_VER.tar.gz | tar xz && \ |
|
||||||
cd zlib-$ZLIB_VER && \ |
|
||||||
CC="musl-gcc -fPIC -pie" LDFLAGS="-L$PREFIX/lib" CFLAGS="-I$PREFIX/include" ./configure --static --prefix=$PREFIX && \ |
|
||||||
make -j$(nproc) && make install && \ |
|
||||||
cd .. && rm -rf zlib-$ZLIB_VER |
|
||||||
|
|
||||||
# Build openssl (used in curl and pq) |
|
||||||
# Would like to use zlib here, but can't seem to get it to work properly |
|
||||||
RUN curl -sSL https://www.openssl.org/source/old/1.0.2/openssl-$SSL_VER.tar.gz | tar xz && \ |
|
||||||
cd openssl-$SSL_VER && \ |
|
||||||
./Configure no-zlib no-shared -fPIC --prefix=$PREFIX --openssldir=$PREFIX/ssl linux-x86_64 && \ |
|
||||||
env C_INCLUDE_PATH=$PREFIX/include make depend 2> /dev/null && \ |
|
||||||
make -j$(nproc) && make install && \ |
|
||||||
cd .. && rm -rf openssl-$SSL_VER |
|
||||||
|
|
||||||
# Build curl (needs with-zlib and all this stuff to allow https) |
|
||||||
# curl_LDFLAGS needed on stretch to avoid fPIC errors - though not sure from what |
|
||||||
RUN curl -sSL https://curl.se/download/curl-$CURL_VER.tar.gz | tar xz && \ |
|
||||||
cd curl-$CURL_VER && \ |
|
||||||
CC="musl-gcc -fPIC -pie" LDFLAGS="-L$PREFIX/lib" CFLAGS="-I$PREFIX/include" ./configure \ |
|
||||||
--enable-shared=no --with-zlib --enable-static=ssl --enable-optimize --prefix=$PREFIX \ |
|
||||||
--with-ca-path=/etc/ssl/certs/ --with-ca-bundle=/etc/ssl/certs/ca-certificates.crt --without-ca-fallback \ |
|
||||||
--with-openssl && \ |
|
||||||
make -j$(nproc) curl_LDFLAGS="-all-static" && make install && \ |
|
||||||
cd .. && rm -rf curl-$CURL_VER |
|
||||||
|
|
||||||
# Build libpq |
|
||||||
RUN curl -sSL https://ftp.postgresql.org/pub/source/v$PQ_VER/postgresql-$PQ_VER.tar.gz | tar xz && \ |
|
||||||
cd postgresql-$PQ_VER && \ |
|
||||||
CC="musl-gcc -fPIE -pie" LDFLAGS="-L$PREFIX/lib" CFLAGS="-I$PREFIX/include" ./configure \ |
|
||||||
--without-readline \ |
|
||||||
--with-openssl \ |
|
||||||
--prefix=$PREFIX --host=x86_64-unknown-linux-musl && \ |
|
||||||
cd src/interfaces/libpq make -s -j$(nproc) all-static-lib && make -s install install-lib-static && \ |
|
||||||
cd ../../bin/pg_config && make -j $(nproc) && make install && \ |
|
||||||
cd .. && rm -rf postgresql-$PQ_VER |
|
@ -1,5 +0,0 @@ |
|||||||
FROM rust:1.72 |
|
||||||
|
|
||||||
RUN rustup target add x86_64-unknown-linux-musl |
|
||||||
RUN mkdir -p /tests && chmod 777 /tests |
|
||||||
ENTRYPOINT ["sleep", "3600"] |
|
@ -1,5 +0,0 @@ |
|||||||
FROM ubuntu:xenial |
|
||||||
|
|
||||||
RUN apt update && apt upgrade -y |
|
||||||
#todo: without this, request to 1.1.1.1 fails due to invalid cert (?), research more |
|
||||||
RUN apt install curl -y |
|
@ -1,8 +0,0 @@ |
|||||||
FROM postgres:14.5 |
|
||||||
|
|
||||||
RUN apt update && apt upgrade -y |
|
||||||
|
|
||||||
ENV LC_ALL en_US.UTF-8 |
|
||||||
ENV LANG en_US.UTF-8 |
|
||||||
ENV LANGUAGE en_US.UTF-8 |
|
||||||
RUN apt install -y locales locales-all iproute2 |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue