Compare commits

...

47 Commits

Author SHA1 Message Date
Keagan McClelland
308a78de37 fixes issues in appmgr makefile 2020-12-11 11:29:21 -07:00
Aaron Greenspan
d8ea87bbae copy 2020-12-11 11:09:00 -07:00
Matt Hill
db275601d4 letter 2020-12-11 11:09:00 -07:00
Matt Hill
039f776ead fix add wifi alert 2020-12-11 11:09:00 -07:00
Matt Hill
14127daa41 word change 2020-12-10 22:37:09 -07:00
Matt Hill
da6ee94605 just show wanring for wifi 2020-12-10 22:37:09 -07:00
Matt Hill
abec910378 temporarily deprecate wifi feature until tor issue is resolved 2020-12-10 22:37:09 -07:00
Matt Hill
ba452587fc show save button in config even if no config 2020-12-10 22:37:09 -07:00
Matt Hill
4de0c97bb5 replace sliding items with close buttons 2020-12-10 22:37:09 -07:00
Matt Hill
5b3c692b7a show properties message even if no properties 2020-12-10 22:37:09 -07:00
Matt Hill
2c97294196 fix alert and toast for wifi connect 2020-12-10 22:37:09 -07:00
Keagan McClelland
0f43e5282f use origin instead of host 2020-12-10 17:45:35 -07:00
Matt Hill
7ce276c267 explicit null check 2020-12-09 14:21:53 -07:00
Matt Hill
7a3811235e alert after connecting to new wifi 2020-12-09 14:21:53 -07:00
Aiden McClelland
7b0f99881b appmgr: update tokio tar to v3 2020-12-09 13:49:47 -07:00
Aiden McClelland
431ff86647 appmgr: compat unpack_in from tokio-tar 2020-12-08 14:54:32 -07:00
Aiden McClelland
47e3361c4f config test 2020-12-08 12:22:57 -07:00
Matt Hill
968b94e81f details to listing 2020-12-08 12:22:57 -07:00
Aaron Greenspan
c4fe73398e head end timing for loadInstalledApp 2020-12-08 12:22:57 -07:00
Aiden McClelland
95a0bfdd1e ui: better display for UniqueBy, hashset for enums 2020-12-08 12:22:57 -07:00
Aiden McClelland
37c772ff8c more efficient uniqueness check 2020-12-08 12:22:57 -07:00
Matt Hill
c6347c3ff7 no uniqueness check on list of enums 2020-12-08 12:22:57 -07:00
Matt Hill
adea594e28 remove extractCss 2020-12-08 12:22:57 -07:00
Matt Hill
a9c51e24f1 small fix 2020-12-08 12:22:57 -07:00
Matt Hill
c3e96ef5df crashed not crashing 2020-12-08 12:22:57 -07:00
Matt Hill
bd046f7e9f turn off mocks 2020-12-08 12:22:57 -07:00
Matt Hill
ab6aadc3b4 changes for 0.2.7 2020-12-08 12:22:57 -07:00
Aiden McClelland
2b4c456c5d appmgr: correct locking for running.yaml 2020-12-08 12:22:57 -07:00
Aiden McClelland
e4cbc38bfd appmgr: fix locking for app status repair 2020-12-08 12:22:57 -07:00
Aiden McClelland
bcfe7c0d21 dynamic cors policy 2020-12-08 12:22:57 -07:00
Aiden McClelland
7d493e12d3 return RegisterRes on 209 2020-12-08 12:22:57 -07:00
Aiden McClelland
414d8ae54a agent: stop remapping restarting 2020-12-08 12:22:57 -07:00
Aiden McClelland
1da2da7e43 run restarter on startup 2020-12-08 12:22:57 -07:00
Aiden McClelland
1a66a5d240 appmgr: remap stopped to crashed if expected to be running 2020-12-08 12:22:57 -07:00
Keagan McClelland
7939dcc4e2 embassy ui version bump 2020-12-08 12:22:57 -07:00
Aiden McClelland
48f6543cb9 appmgr: reqwest compatibility 2020-12-08 12:22:57 -07:00
Aiden McClelland
3fce90b7a8 appmgr: adjust subcommand tree 2020-12-08 12:22:57 -07:00
Aiden McClelland
71a3728fbb stop container before updating 2020-12-08 12:22:57 -07:00
Aiden McClelland
0dee648078 reboot to start up restarter daemon 2020-12-08 12:22:57 -07:00
Keagan McClelland
26cf4ea418 fix Makefile 2020-12-08 12:22:57 -07:00
Keagan McClelland
f6ac172ef3 bump version to 0.2.7 in agent 2020-12-08 12:22:57 -07:00
Aiden McClelland
3ababacd91 address pr comments 2020-12-08 12:22:57 -07:00
Aiden McClelland
5a121945d2 stop using exit codes, track app state 2020-12-08 12:22:57 -07:00
Aiden McClelland
30f8b8e6cd switch to auto-restarter daemon 2020-12-08 12:22:57 -07:00
Aiden McClelland
f68c13f57f appmgr: remove github action 2020-12-08 12:22:57 -07:00
Aiden McClelland
3a082c108b appmgr: update to tokio 0.3.4 2020-12-08 12:22:57 -07:00
Aiden McClelland
3efb38a742 appmgr: version bump -> 0.2.7 2020-12-08 12:22:57 -07:00
64 changed files with 2225 additions and 1213 deletions

View File

@@ -19,8 +19,12 @@ product_key:
cat /dev/random | base32 | head -c11 | tr '[:upper:]' '[:lower:]' >> product_key
appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr: $(APPMGR_SRC)
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)"/appmgr:/home/rust/src start9/rust-arm-cross:latest cargo build --release --features=production
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)"/appmgr:/home/rust/src start9/rust-arm-cross:latest arm-linux-gnueabi-strip target/armv7-unknown-linux-gnueabihf/release/appmgr
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest sh -c "(cd appmgr && cargo build --release --features=production)"
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest arm-linux-gnueabi-strip appmgr/target/armv7-unknown-linux-gnueabihf/release/appmgr
agent: $(AGENT_SRC)
appmgr: appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr
agent/dist/agent: $(AGENT_SRC)
(cd agent; ./build.sh)
agent: agent/dist/agent

View File

@@ -3,4 +3,4 @@
cat config/settings.yml | grep app-mgr-version-spec
cat package.yaml | grep version
stack --local-bin-path ./executables build --copy-bins #--flag start9-agent:disable-auth
stack --local-bin-path ./dist build --copy-bins #--flag start9-agent:disable-auth

View File

@@ -0,0 +1,7 @@
[Unit]
Description=restarts dead containers
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/appmgr repair-app-status

View File

@@ -0,0 +1,9 @@
[Unit]
Description=restarter
[Timer]
OnUnitActiveSec=60s
OnBootSec=60s
[Install]
WantedBy=timers.target

View File

@@ -28,12 +28,11 @@ detailed-logging: "_env:DETAILED_LOGGING:false"
# NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:YESOD_PGPASS:'123'")
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
cors-override-star: "_env:CORS_OVERRIDE_STAR:"
filesystem-base: "_env:FILESYSTEM_BASE:/"
database:
database: "start9_agent.sqlite3"
poolsize: "_env:YESOD_SQLITE_POOLSIZE:10"
app-mgr-version-spec: "=0.2.6"
app-mgr-version-spec: "=0.2.7"
#analytics: UA-YOURCODE

View File

@@ -0,0 +1 @@
SELECT TRUE;

View File

@@ -1,5 +1,5 @@
name: ambassador-agent
version: 0.2.6
version: 0.2.7
default-extensions:
- NoImplicitPrelude

View File

@@ -710,7 +710,6 @@ remapAppMgrInfo jobCache serverApps = flip
$ ((, infoResVersion) <$> HM.lookup appId tmpStatuses)
<|> (guard (not infoResIsConfigured || infoResIsRecoverable) $> (NeedsConfig, infoResVersion))
<|> (guard realViolations $> (BrokenDependencies, infoResVersion))
<|> (guard (infoResStatus == Restarting) $> (Crashed, infoResVersion))
in ( status
, version
, infoRes

View File

@@ -5,26 +5,24 @@ module Handler.Hosts where
import Startlude hiding ( ask )
import Control.Carrier.Lift ( runM )
import Control.Carrier.Error.Church
import Data.Conduit
import qualified Data.Conduit.Binary as CB
import Data.Time.ISO8601
import Yesod.Core hiding ( expiresAt )
import Foundation
import Daemon.ZeroConf
import Handler.Register ( produceProofOfKey
, checkExistingPasswordRegistration
import Handler.Register ( checkExistingPasswordRegistration
, getRegistration
)
import Handler.Types.Hosts
import Handler.Types.Register
import Lib.Crypto
import Lib.Error
import Lib.Password ( rootAccountName )
import Lib.ProductKey
import Lib.Ssl
import Lib.SystemPaths
import Lib.Tor
import Lib.SystemPaths ( injectFilesystemBaseFromContext
, rootCaCertPath
, SystemPath(relativeTo)
)
import Settings
getHostsR :: Handler HostsRes
@@ -59,23 +57,7 @@ verifyTimestampNotExpired expirationTimestamp = do
Nothing -> throwE $ TTLExpirationE "invalid timestamp"
Just expiration -> when (expiration < now) (throwE $ TTLExpirationE "expired")
getRegistration :: (MonadIO m, HasFilesystemBase sig m, Has (Error S9Error) sig m) => Text -> UTCTime -> m RegisterRes
getRegistration productKey registerResClaimedAt = do
torAddress <- getAgentHiddenServiceUrlMaybe >>= \case
Nothing -> throwError $ NotFoundE "prior registration" "torAddress"
Just t -> pure $ t
caCert <- readSystemPath rootCaCertPath >>= \case
Nothing -> throwError $ NotFoundE "prior registration" "cert"
Just t -> pure t
-- create an hmac of the torAddress + caCert for front end
registerResTorAddressSig <- produceProofOfKey productKey torAddress
registerResCertSig <- produceProofOfKey productKey caCert
let registerResCertName = root_CA_CERT_NAME
registerResLanAddress <- getStart9AgentHostnameLocal
pure RegisterRes { .. }
getCertificateR :: Handler TypedContent
getCertificateR = do

View File

@@ -5,7 +5,10 @@ module Handler.Register where
import Startlude hiding ( ask )
import Control.Carrier.Error.Either ( runError )
import Control.Carrier.Error.Either ( runError
, Error
, throwError
)
import Control.Carrier.Lift
import Control.Effect.Throw ( liftEither )
import Crypto.Cipher.Types
@@ -29,6 +32,7 @@ import Lib.Password
import Lib.ProductKey
import Lib.Ssl
import Lib.SystemPaths
import Lib.Tor
import Model
import Settings
@@ -46,8 +50,11 @@ postRegisterR = handleS9ErrT $ do
-- Check for existing registration.
checkExistingPasswordRegistration rootAccountName >>= \case
Nothing -> pure ()
Just _ -> sendResponseStatus (Status 209 "Preexisting") ()
Nothing -> pure ()
Just claimedAt -> do
res <- mapExceptT (liftIO . runM . injectFilesystemBaseFromContext settings)
$ getRegistration productKey claimedAt
sendResponseStatus (Status 209 "Preexisting") res
-- install new tor hidden service key and restart tor
registerResTorAddress <- runM (injectFilesystemBaseFromContext settings $ bootupTor torKeyFileContents) >>= \case
@@ -139,3 +146,21 @@ produceProofOfKey key message = do
salt <- random16
let hmac = computeHmac key message salt
pure $ HmacSig hmac message salt
getRegistration :: (MonadIO m, HasFilesystemBase sig m, Has (Error S9Error) sig m) => Text -> UTCTime -> m RegisterRes
getRegistration productKey registerResClaimedAt = do
torAddress <- getAgentHiddenServiceUrlMaybe >>= \case
Nothing -> throwError $ NotFoundE "prior registration" "torAddress"
Just t -> pure $ t
caCert <- readSystemPath rootCaCertPath >>= \case
Nothing -> throwError $ NotFoundE "prior registration" "cert"
Just t -> pure t
-- create an hmac of the torAddress + caCert for front end
registerResTorAddressSig <- produceProofOfKey productKey torAddress
registerResCertSig <- produceProofOfKey productKey caCert
let registerResCertName = root_CA_CERT_NAME
registerResLanAddress <- getStart9AgentHostnameLocal
pure RegisterRes { .. }

View File

@@ -41,6 +41,7 @@ import System.FilePath.Posix ( takeDirectory )
import System.Directory
import System.IO.Error
import System.Posix.Files
import System.Process ( callCommand )
import qualified Streaming.Prelude as Stream
import qualified Streaming.Conduit as Conduit
import qualified Streaming.Zip as Stream
@@ -95,12 +96,12 @@ parseKernelVersion = do
pure $ KernelVersion (Version (major', minor', patch', 0)) arch
synchronizer :: Synchronizer
synchronizer = sync_0_2_6
synchronizer = sync_0_2_7
{-# INLINE synchronizer #-}
sync_0_2_6 :: Synchronizer
sync_0_2_6 = Synchronizer
"0.2.6"
sync_0_2_7 :: Synchronizer
sync_0_2_7 = Synchronizer
"0.2.7"
[ syncCreateAgentTmp
, syncCreateSshDir
, syncRemoveAvahiSystemdDependency
@@ -119,6 +120,7 @@ sync_0_2_6 = Synchronizer
, syncPrepSslIntermediateCaDir
, syncPersistLogs
, syncConvertEcdsaCerts
, syncRestarterService
]
syncCreateAgentTmp :: SyncOp
@@ -536,6 +538,22 @@ replaceDerivativeCerts = do
liftIO $ renameDirectory sslDirTmp sslDir
liftIO $ systemCtl RestartService "nginx" $> ()
syncRestarterService :: SyncOp
syncRestarterService = SyncOp "Install Restarter Service" check migrate True
where
wantedService = $(embedFile "config/restarter.service")
wantedTimer = $(embedFile "config/restarter.timer")
check = do
base <- asks $ appFilesystemBase . appSettings
liftIO $ not <$> doesPathExist
(toS $ "/etc/systemd/system/timers.target.wants/restarter.timer" `relativeTo` base)
migrate = do
base <- asks $ appFilesystemBase . appSettings
liftIO $ BS.writeFile (toS $ "/etc/systemd/system/restarter.service" `relativeTo` base) wantedService
liftIO $ BS.writeFile (toS $ "/etc/systemd/system/restarter.timer" `relativeTo` base) wantedTimer
liftIO $ callCommand "systemctl enable restarter.service"
liftIO $ callCommand "systemctl enable restarter.timer"
failUpdate :: S9Error -> ExceptT Void (ReaderT AgentCtx IO) ()
failUpdate e = do
ref <- asks appIsUpdateFailed

View File

@@ -55,6 +55,8 @@ import Handler.Status
import Handler.Wifi
import Handler.V0
import Settings
import Network.HTTP.Types.Header ( hOrigin )
import Data.List (lookup)
-- This line actually creates our YesodDispatch instance. It is the second half
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
@@ -64,20 +66,11 @@ mkYesodDispatch "AgentCtx" resourcesAgentCtx
instance YesodSubDispatch Auth AgentCtx where
yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth)
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
-- applying some additional middlewares.
makeApplication :: AgentCtx -> IO Application
makeApplication foundation = do
logWare <- makeLogWare foundation
-- Create the WAI application and apply middlewares
appPlain <- toWaiAppPlain foundation
let origin = case appCorsOverrideStar $ appSettings foundation of
Nothing -> Nothing
Just override -> Just ([encodeUtf8 override], True)
pure . logWare . cors (const . Just $ policy origin) . defaultMiddlewaresNoLogging $ appPlain
dynamicCorsResourcePolicy :: Request -> Maybe CorsResourcePolicy
dynamicCorsResourcePolicy req = Just . policy . lookup hOrigin $ requestHeaders req
where
policy o = simpleCorsResourcePolicy
{ corsOrigins = o
{ corsOrigins = (\o' -> ([o'], True)) <$> o
, corsMethods = ["GET", "POST", "HEAD", "PUT", "DELETE", "TRACE", "CONNECT", "OPTIONS", "PATCH"]
, corsRequestHeaders = [ "app-version"
, "Accept"
@@ -138,6 +131,15 @@ makeApplication foundation = do
, corsIgnoreFailures = True
}
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
-- applying some additional middlewares.
makeApplication :: AgentCtx -> IO Application
makeApplication foundation = do
logWare <- makeLogWare foundation
-- Create the WAI application and apply middlewares
appPlain <- toWaiAppPlain foundation
pure . logWare . cors dynamicCorsResourcePolicy . defaultMiddlewaresNoLogging $ appPlain
startWeb :: AgentCtx -> IO ()
startWeb foundation = do
app <- makeApplication foundation

View File

@@ -41,7 +41,6 @@ data AppSettings = AppSettings
-- ^ Should all log messages be displayed?
, appMgrVersionSpec :: VersionRange
, appFilesystemBase :: Text
, appCorsOverrideStar :: Maybe Text
}
deriving Show
@@ -64,7 +63,6 @@ instance FromJSON AppSettings where
appMgrVersionSpec <- o .: "app-mgr-version-spec"
appFilesystemBase <- o .: "filesystem-base"
appCorsOverrideStar <- o .:? "cors-override-star"
return AppSettings { .. }
-- | Raw bytes at compile time of @config/settings.yml@

View File

@@ -1,30 +0,0 @@
name: Rust
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Cache .cargo
uses: actions/cache@v1
with:
path: ~/.cargo
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
${{ runner.os }}-cargo-
- name: Cache target release directory
uses: actions/cache@v1
with:
path: target/release
key: ${{ runner.os }}-target-release-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-target-release-${{ hashFiles('**/Cargo.lock') }}
${{ runner.os }}-target-release-
- name: Check
run: cargo check
- name: Test
run: cargo test --release

357
appmgr/Cargo.lock generated
View File

@@ -35,7 +35,7 @@ dependencies = [
[[package]]
name = "appmgr"
version = "0.2.6"
version = "0.2.7"
dependencies = [
"argonautica",
"async-trait",
@@ -45,7 +45,7 @@ dependencies = [
"emver",
"failure",
"file-lock",
"futures 0.3.7",
"futures 0.3.8",
"git-version",
"itertools 0.9.0",
"lazy_static",
@@ -64,7 +64,8 @@ dependencies = [
"serde_json",
"serde_yaml",
"simple-logging",
"tokio",
"tokio 0.3.5",
"tokio-compat-02",
"tokio-tar",
]
@@ -105,9 +106,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-trait"
version = "0.1.41"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0"
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.7",
@@ -172,6 +173,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bindgen"
version = "0.48.1"
@@ -289,6 +296,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]]
name = "bytes"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
[[package]]
name = "cc"
version = "1.0.61"
@@ -354,6 +367,25 @@ dependencies = [
"bitflags",
]
[[package]]
name = "cloudabi"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
dependencies = [
"bitflags",
]
[[package]]
name = "console_error_panic_hook"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
dependencies = [
"cfg-if 0.1.10",
"wasm-bindgen",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@@ -495,7 +527,8 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "emver"
version = "0.1.0"
source = "git+https://github.com/Start9Labs/emver-rs.git#9007920a8e361669fb83b29dd8506b32eeb20180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7621f4df0519152a2ceb5f8cc0685e9fa6c8b7aec289e1afb37c66849cb71ffe"
dependencies = [
"either",
"fp-core",
@@ -579,7 +612,7 @@ checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
dependencies = [
"cfg-if 0.1.10",
"libc",
"redox_syscall",
"redox_syscall 0.1.57",
"winapi 0.3.9",
]
@@ -604,6 +637,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "fp-core"
version = "0.1.9"
@@ -649,9 +692,9 @@ checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed"
[[package]]
name = "futures"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
dependencies = [
"futures-channel",
"futures-core",
@@ -664,9 +707,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
dependencies = [
"futures-core",
"futures-sink",
@@ -674,9 +717,9 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
[[package]]
name = "futures-cpupool"
@@ -690,9 +733,9 @@ dependencies = [
[[package]]
name = "futures-executor"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65"
dependencies = [
"futures-core",
"futures-task",
@@ -701,15 +744,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
[[package]]
name = "futures-macro"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.24",
@@ -719,24 +762,24 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
[[package]]
name = "futures-task"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
dependencies = [
"once_cell",
]
[[package]]
name = "futures-util"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
dependencies = [
"futures-channel",
"futures-core",
@@ -828,7 +871,7 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
dependencies = [
"bytes",
"bytes 0.5.6",
"fnv",
"futures-core",
"futures-sink",
@@ -836,7 +879,7 @@ dependencies = [
"http",
"indexmap",
"slab",
"tokio",
"tokio 0.2.22",
"tokio-util",
"tracing",
"tracing-futures",
@@ -879,7 +922,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
dependencies = [
"bytes",
"bytes 0.5.6",
"fnv",
"itoa",
]
@@ -890,7 +933,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
dependencies = [
"bytes",
"bytes 0.5.6",
"http",
]
@@ -921,7 +964,7 @@ version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3afcfae8af5ad0576a31e768415edb627824129e8e5a29b8bfccb2f234e835"
dependencies = [
"bytes",
"bytes 0.5.6",
"futures-channel",
"futures-core",
"futures-util",
@@ -933,7 +976,7 @@ dependencies = [
"itoa",
"pin-project 0.4.27",
"socket2",
"tokio",
"tokio 0.2.22",
"tower-service",
"tracing",
"want",
@@ -945,10 +988,10 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed"
dependencies = [
"bytes",
"bytes 0.5.6",
"hyper",
"native-tls",
"tokio",
"tokio 0.2.22",
"tokio-tls",
]
@@ -973,6 +1016,15 @@ dependencies = [
"hashbrown 0.9.1",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "iovec"
version = "0.1.4"
@@ -1091,6 +1143,15 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard 1.1.0",
]
[[package]]
name = "log"
version = "0.4.11"
@@ -1164,26 +1225,16 @@ dependencies = [
]
[[package]]
name = "mio-named-pipes"
version = "0.1.7"
name = "mio"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b"
dependencies = [
"log",
"mio",
"miow 0.3.5",
"winapi 0.3.9",
]
[[package]]
name = "mio-uds"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
dependencies = [
"iovec",
"libc",
"mio",
"log",
"miow 0.3.6",
"ntapi",
"winapi 0.3.9",
]
[[package]]
@@ -1200,9 +1251,9 @@ dependencies = [
[[package]]
name = "miow"
version = "0.3.5"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
dependencies = [
"socket2",
"winapi 0.3.9",
@@ -1281,6 +1332,15 @@ dependencies = [
"version_check 0.9.2",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
@@ -1348,6 +1408,32 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
dependencies = [
"cfg-if 0.1.10",
"cloudabi 0.1.0",
"instant",
"libc",
"redox_syscall 0.1.57",
"smallvec",
"winapi 0.3.9",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@@ -1449,6 +1535,12 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
[[package]]
name = "pin-project-lite"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
[[package]]
name = "pin-utils"
version = "0.1.0"
@@ -1684,7 +1776,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"cloudabi 0.0.3",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
@@ -1726,6 +1818,15 @@ version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48b82c2a1e8eb6e1bfde608de2bcbebd4072aa32d056ea48a986990cd5ca0f5a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.3.5"
@@ -1733,7 +1834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [
"getrandom",
"redox_syscall",
"redox_syscall 0.1.57",
"rust-argon2",
]
@@ -1775,12 +1876,12 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.10.8"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e"
checksum = "fb15d6255c792356a0f578d8a645c677904dc02e862bebe2ecc18e0c01b9a0ce"
dependencies = [
"base64 0.12.3",
"bytes",
"base64 0.13.0",
"bytes 0.5.6",
"encoding_rs",
"futures-core",
"futures-util",
@@ -1796,15 +1897,16 @@ dependencies = [
"mime_guess",
"native-tls",
"percent-encoding",
"pin-project-lite",
"pin-project-lite 0.2.0",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio 0.2.22",
"tokio-tls",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"web-sys",
"winreg",
]
@@ -1859,6 +1961,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "scopeguard"
version = "0.3.3"
@@ -1896,9 +2004,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.117"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
dependencies = [
"serde_derive",
]
@@ -1915,9 +2023,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.117"
version = "1.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.7",
@@ -1946,14 +2054,14 @@ dependencies = [
[[package]]
name = "serde_urlencoded"
version = "0.6.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
dependencies = [
"dtoa",
"form_urlencoded",
"itoa",
"ryu",
"serde",
"url",
]
[[package]]
@@ -2026,14 +2134,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "socket2"
version = "0.3.15"
name = "smallvec"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
[[package]]
name = "socket2"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
dependencies = [
"cfg-if 0.1.10",
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"redox_syscall 0.1.57",
"winapi 0.3.9",
]
@@ -2103,7 +2217,7 @@ dependencies = [
"cfg-if 0.1.10",
"libc",
"rand 0.7.3",
"redox_syscall",
"redox_syscall 0.1.57",
"remove_dir_all",
"winapi 0.3.9",
]
@@ -2144,7 +2258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
dependencies = [
"libc",
"redox_syscall",
"redox_syscall 0.1.57",
"winapi 0.3.9",
]
@@ -2169,18 +2283,34 @@ version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"
dependencies = [
"bytes",
"bytes 0.5.6",
"fnv",
"futures-core",
"iovec",
"lazy_static",
"memchr",
"mio 0.6.22",
"num_cpus",
"pin-project-lite 0.1.11",
"slab",
]
[[package]]
name = "tokio"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12a3eb39ee2c231be64487f1fcbe726c8f2514876a55480a5ab8559fc374252"
dependencies = [
"autocfg 1.0.1",
"bytes 0.6.0",
"futures-core",
"lazy_static",
"libc",
"memchr",
"mio",
"mio-named-pipes",
"mio-uds",
"mio 0.7.6",
"num_cpus",
"pin-project-lite",
"parking_lot",
"pin-project-lite 0.2.0",
"signal-hook-registry",
"slab",
"tokio-macros",
@@ -2188,10 +2318,23 @@ dependencies = [
]
[[package]]
name = "tokio-macros"
version = "0.2.5"
name = "tokio-compat-02"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
checksum = "bb4cec419b8b6f06c32e74aae6d8c5e79646d038a38e5ea2b36045f2c3296e22"
dependencies = [
"bytes 0.5.6",
"once_cell",
"pin-project-lite 0.1.11",
"tokio 0.2.22",
"tokio 0.3.5",
]
[[package]]
name = "tokio-macros"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21d30fdbb5dc2d8f91049691aa1a9d4d4ae422a21c334ce8936e5886d30c5c45"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.7",
@@ -2200,15 +2343,14 @@ dependencies = [
[[package]]
name = "tokio-tar"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a9e415c93375be93253134543229563114a2be8d46440d6d8f25b2ec62a7fb"
version = "0.3.0"
source = "git+https://github.com/dr-bonez/tokio-tar.git#1ba710f344ddb2a5b4d98bb96c905195c3cd9d43"
dependencies = [
"filetime",
"futures-core",
"libc",
"redox_syscall",
"tokio",
"redox_syscall 0.2.1",
"tokio 0.3.5",
"xattr",
]
@@ -2219,7 +2361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343"
dependencies = [
"native-tls",
"tokio",
"tokio 0.2.22",
]
[[package]]
@@ -2228,12 +2370,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
dependencies = [
"bytes",
"bytes 0.5.6",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
"pin-project-lite 0.1.11",
"tokio 0.2.22",
]
[[package]]
@@ -2250,7 +2392,7 @@ checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
dependencies = [
"cfg-if 0.1.10",
"log",
"pin-project-lite",
"pin-project-lite 0.1.11",
"tracing-core",
]
@@ -2338,10 +2480,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "url"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
@@ -2471,6 +2614,30 @@ version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34d1cdc8b98a557f24733d50a1199c4b0635e465eecba9c45b214544da197f64"
dependencies = [
"console_error_panic_hook",
"js-sys",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fb9c67be7439ee8ab1b7db502a49c05e51e2835b66796c705134d9b8e1a585"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.7",
]
[[package]]
name = "web-sys"
version = "0.3.45"

View File

@@ -1,6 +1,6 @@
[package]
name = "appmgr"
version = "0.2.6"
version = "0.2.7"
authors = ["Aiden McClelland <me@drbonez.dev>"]
edition = "2018"
@@ -18,15 +18,15 @@ portable = []
production = []
[dependencies]
emver = { git = "https://github.com/Start9Labs/emver-rs.git", version = "0.1.0", features = ["serde"] }
emver = { version = "0.1.0", features = ["serde"] }
argonautica = "0.2.0"
async-trait = "0.1.41"
async-trait = "0.1.42"
base32 = "0.4.0"
clap = "2.33"
ed25519-dalek = "1.0.1"
failure = "0.1.8"
file-lock = "1.1"
futures = "0.3.7"
futures = "0.3.8"
git-version = "0.3.4"
itertools = "0.9.0"
lazy_static = "1.4"
@@ -38,12 +38,13 @@ pest_derive = "2.1"
prettytable-rs = "0.8.0"
rand = "0.7.3"
regex = "1.4.2"
reqwest = { version = "0.10.8", features = ["stream", "json"] }
reqwest = { version = "0.10.9", features = ["stream", "json"] }
rpassword = "5.0.0"
serde = { version = "1.0.117", features = ["derive", "rc"] }
serde = { version = "1.0.118", features = ["derive", "rc"] }
serde_yaml = "0.8.14"
serde_cbor = "0.11.1"
serde_json = "1.0.59"
simple-logging = "2.0"
tokio = { version = "0.2.22", features = ["full"] }
tokio-tar = "0.2.0"
tokio = { version = "0.3.5", features = ["full"] }
tokio-compat-02 = "0.1.2"
tokio-tar = { version = "0.3.0", git = "https://github.com/dr-bonez/tokio-tar.git" }

View File

@@ -131,7 +131,7 @@ pub async fn remove(id: &str) -> Result<(), failure::Error> {
Ok(())
}
pub async fn status(id: &str) -> Result<AppStatus, Error> {
pub async fn status(id: &str, remap_crashed: bool) -> Result<AppStatus, Error> {
let output = std::process::Command::new("docker")
.args(&["inspect", id, "--format", "{{.State.Status}}"])
.stdout(std::process::Stdio::piped())
@@ -155,6 +155,19 @@ pub async fn status(id: &str) -> Result<AppStatus, Error> {
"restarting" => DockerStatus::Restarting,
"removing" => DockerStatus::Removing,
"dead" => DockerStatus::Dead,
"exited"
if remap_crashed && {
let path = PersistencePath::from_ref("running.yaml");
if let Some(mut f) = path.maybe_read(false).await.transpose()? {
let running: Vec<String> = from_yaml_async_reader(&mut *f).await?;
running.iter().filter(|a| a.as_str() == id).next().is_some()
} else {
false
}
} =>
{
DockerStatus::Restarting
}
"created" | "exited" => DockerStatus::Stopped,
"paused" => DockerStatus::Paused,
_ => Err(format_err!("unknown status: {}", status))?,
@@ -275,7 +288,7 @@ pub async fn info_full(
Ok(AppInfoFull {
info: info(id).await?,
status: if with_status {
Some(status(id).await?)
Some(status(id, true).await?)
} else {
None
},
@@ -379,8 +392,12 @@ pub async fn list(
let info = list_info().await?;
futures::future::join_all(info.into_iter().map(move |(id, info)| async move {
let (status, manifest, config, dependencies) = futures::try_join!(
OptionFuture::from(if with_status { Some(status(&id)) } else { None })
.map(Option::transpose),
OptionFuture::from(if with_status {
Some(status(&id, true))
} else {
None
})
.map(Option::transpose),
OptionFuture::from(if with_manifest {
Some(manifest(&id))
} else {

View File

@@ -56,7 +56,7 @@ pub async fn create_backup<P: AsRef<Path>>(
f.flush().await?;
}
let status = crate::apps::status(app_id).await?;
let status = crate::apps::status(app_id, false).await?;
let exclude = if volume_path.is_dir() {
let ignore_path = volume_path.join(".backupignore");
if ignore_path.is_file() {
@@ -148,7 +148,7 @@ pub async fn restore_backup<P: AsRef<Path>>(
);
}
let status = crate::apps::status(app_id).await?;
let status = crate::apps::status(app_id, false).await?;
let running = status.status == crate::apps::DockerStatus::Running;
if running {
crate::control::stop_app(app_id, true, false).await?;

View File

@@ -137,7 +137,9 @@ pub async fn configure(
&mut res.stopped,
)
.await?;
if crate::apps::status(&dependent).await?.status != crate::apps::DockerStatus::Stopped {
if crate::apps::status(&dependent, false).await?.status
!= crate::apps::DockerStatus::Stopped
{
crate::control::stop_app(&dependent, false, dry_run).await?;
res.stopped.insert(
// TODO: maybe don't do this if its not running
@@ -283,7 +285,8 @@ pub async fn configure(
crate::apps::set_configured(name, true).await?;
crate::apps::set_recoverable(name, false).await?;
}
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped {
if crate::apps::status(name, false).await?.status != crate::apps::DockerStatus::Stopped
{
if !dry_run {
crate::apps::set_needs_restart(name, true).await?;
}

View File

@@ -1,9 +1,10 @@
use std::path::Path;
use futures::future::{BoxFuture, FutureExt};
use linear_map::LinearMap;
use linear_map::{set::LinearSet, LinearMap};
use crate::dependencies::{DependencyError, TaggedDependencyError};
use crate::util::{from_yaml_async_reader, PersistencePath, YamlUpdateHandle};
use crate::Error;
pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
@@ -19,7 +20,7 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
true,
)
.await?;
let status = crate::apps::status(name).await?.status;
let status = crate::apps::status(name, false).await?.status;
if status == crate::apps::DockerStatus::Stopped {
if update_metadata {
crate::config::configure(name, None, None, false).await?;
@@ -27,6 +28,10 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
crate::dependencies::update_binds(name).await?;
}
crate::apps::set_needs_restart(name, false).await?;
let mut running = YamlUpdateHandle::<LinearSet<String>>::new_or_default(
PersistencePath::from_ref("running.yaml"),
)
.await?;
let output = tokio::process::Command::new("docker")
.args(&["start", name])
.stdout(std::process::Stdio::null())
@@ -38,6 +43,8 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
"Failed to Start Application: {}",
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error")
);
running.insert(name.to_owned());
running.commit().await?;
} else if status == crate::apps::DockerStatus::Paused {
resume_app(name).await?;
}
@@ -67,6 +74,10 @@ pub async fn stop_app(
true,
)
.await?;
let mut running = YamlUpdateHandle::<LinearSet<String>>::new_or_default(
PersistencePath::from_ref("running.yaml"),
)
.await?;
log::info!("Stopping {}", name);
let output = tokio::process::Command::new("docker")
.args(&["stop", "-t", "25", name])
@@ -79,6 +90,8 @@ pub async fn stop_app(
"Failed to Stop Application: {}",
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error")
);
running.remove(name);
running.commit().await?;
crate::util::unlock(lock).await?;
}
Ok(res)
@@ -98,7 +111,7 @@ pub async fn stop_dependents(
) -> BoxFuture<'a, Result<(), Error>> {
async move {
for dependent in crate::apps::dependents(name, false).await? {
if crate::apps::status(&dependent).await?.status
if crate::apps::status(&dependent, false).await?.status
!= crate::apps::DockerStatus::Stopped
{
stop_dependents_rec(&dependent, dry_run, DependencyError::NotRunning, res)
@@ -192,3 +205,34 @@ pub async fn resume_app(name: &str) -> Result<(), Error> {
crate::util::unlock(lock).await?;
Ok(())
}
pub async fn repair_app_status() -> Result<(), Error> {
let mut running_file = PersistencePath::from_ref("running.yaml")
.maybe_read(false)
.await
.transpose()?;
let running: Vec<String> = if let Some(f) = running_file.as_mut() {
from_yaml_async_reader::<_, &mut tokio::fs::File>(f).await?
} else {
Vec::new()
};
for name in running {
let lock = crate::util::lock_file(
format!(
"{}",
Path::new(crate::PERSISTENCE_DIR)
.join("apps")
.join(&name)
.join("control.lock")
.display()
),
true,
)
.await?;
if crate::apps::status(&name, false).await?.status == crate::apps::DockerStatus::Stopped {
start_app(&name, true).await?;
}
crate::util::unlock(lock).await?;
}
Ok(())
}

View File

@@ -115,7 +115,9 @@ impl DepInfo {
if !errors.is_empty() {
return Ok(Err(DependencyError::ConfigUnsatisfied(errors)));
}
if crate::apps::status(dependency_id).await?.status != crate::apps::DockerStatus::Running {
if crate::apps::status(dependency_id, false).await?.status
!= crate::apps::DockerStatus::Running
{
return Ok(Err(DependencyError::NotRunning));
}
Ok(Ok(()))

View File

@@ -14,8 +14,9 @@ use std::time::Duration;
use failure::ResultExt as _;
use futures::stream::StreamExt;
use futures::stream::TryStreamExt;
use tokio::io::AsyncRead;
use tokio::io::AsyncWriteExt;
use tokio::io::{AsyncRead, ReadBuf};
use tokio_compat_02::FutureExt;
use tokio_tar as tar;
use crate::config::{ConfigRuleEntry, ConfigSpec};
@@ -62,13 +63,13 @@ where
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
buf: &mut ReadBuf,
) -> Poll<std::io::Result<()>> {
let atomic = self.as_ref().1.clone(); // TODO: not efficient
match unsafe { self.map_unchecked_mut(|a| &mut a.0) }.poll_read(cx, buf) {
Poll::Ready(Ok(res)) => {
atomic.fetch_add(res as u64, atomic::Ordering::SeqCst);
Poll::Ready(Ok(res))
Poll::Ready(Ok(())) => {
atomic.fetch_add(buf.filled().len() as u64, atomic::Ordering::SeqCst);
Poll::Ready(Ok(()))
}
a => a,
}
@@ -98,6 +99,7 @@ pub async fn download(url: &str, name: Option<&str>) -> Result<PathBuf, crate::E
let url = reqwest::Url::parse(url).no_code()?;
log::info!("Downloading {}.", url.as_str());
let response = reqwest::get(url)
.compat()
.await
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()
@@ -141,7 +143,7 @@ pub async fn download(url: &str, name: Option<&str>) -> Result<PathBuf, crate::E
if is_done {
break;
}
tokio::time::delay_for(Duration::from_millis(10)).await;
tokio::time::sleep(Duration::from_millis(10)).await;
}
if !*crate::QUIET.read().await {
println!("\rDownloading... 100%");
@@ -191,7 +193,7 @@ pub async fn install_path<P: AsRef<Path>>(p: P, name: Option<&str>) -> Result<()
if is_done {
break;
}
tokio::time::delay_for(Duration::from_millis(10)).await;
tokio::time::sleep(Duration::from_millis(10)).await;
}
if !*crate::QUIET.read().await {
println!("\rInstalling... 100%");
@@ -407,11 +409,13 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
.arg("stop")
.arg(&manifest.id)
.spawn()?
.wait()
.await?;
tokio::process::Command::new("docker")
.arg("rm")
.arg(&manifest.id)
.spawn()?
.wait()
.await?;
crate::ensure_code!(
tokio::process::Command::new("docker")
@@ -457,7 +461,7 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
child_in.shutdown().await?;
drop(child_in);
crate::ensure_code!(
child.await?.success(),
child.wait().await?.success(),
crate::error::DOCKER_ERROR,
"Failed to Load Docker Image From Tar"
);
@@ -554,7 +558,8 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
if dep_info.mount_shared
&& crate::apps::list_info().await?.get(&dep_id).is_some()
&& crate::apps::manifest(&dep_id).await?.shared.is_some()
&& crate::apps::status(&dep_id).await?.status != crate::apps::DockerStatus::Stopped
&& crate::apps::status(&dep_id, false).await?.status
!= crate::apps::DockerStatus::Stopped
{
crate::apps::set_needs_restart(&dep_id, true).await?;
}

View File

@@ -817,6 +817,9 @@ async fn inner_main() -> Result<(), Error> {
.help("Password to use for encryption of backup file"),
),
),
)
.subcommand(
SubCommand::with_name("repair-app-status").about("Restarts crashed apps"), // TODO: remove
);
let matches = app.clone().get_matches();
@@ -1540,6 +1543,10 @@ async fn inner_main() -> Result<(), Error> {
std::process::exit(1);
}
},
#[cfg(not(feature = "portable"))]
("repair-app-status", _) => {
control::repair_app_status().await?;
}
("pack", Some(sub_m)) => {
pack(
sub_m.value_of("PATH").unwrap(),

View File

@@ -1,4 +1,5 @@
use emver::VersionRange;
use tokio_compat_02::FutureExt;
use crate::apps::AppConfig;
use crate::manifest::ManifestLatest;
@@ -12,6 +13,7 @@ pub async fn manifest(id: &str, version: &VersionRange) -> Result<ManifestLatest
id,
version
))
.compat()
.await
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()
@@ -34,6 +36,7 @@ pub async fn version(id: &str, version: &VersionRange) -> Result<emver::Version,
id,
version
))
.compat()
.await
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()
@@ -51,6 +54,7 @@ pub async fn config(id: &str, version: &VersionRange) -> Result<AppConfig, Error
id,
version
))
.compat()
.await
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()

View File

@@ -198,7 +198,7 @@ pub async fn read_tor_address(name: &str, timeout: Option<Duration>) -> Result<S
}
}
} {
tokio::time::delay_for(Duration::from_millis(100)).await;
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
let tor_addr = match tokio::fs::read_to_string(&addr_path).await {
@@ -238,7 +238,7 @@ pub async fn read_tor_key(
}
}
} {
tokio::time::delay_for(Duration::from_millis(100)).await;
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
let tor_key = match version {

View File

@@ -19,7 +19,9 @@ pub async fn update(
let version = crate::registry::version(name, &version_req).await?;
let mut res = LinearMap::new();
for dependent in crate::apps::dependents(name, false).await? {
if crate::apps::status(&dependent).await?.status != crate::apps::DockerStatus::Stopped {
if crate::apps::status(&dependent, false).await?.status
!= crate::apps::DockerStatus::Stopped
{
let manifest = crate::apps::manifest(&dependent).await?;
match manifest.dependencies.0.get(name) {
Some(dep) if !version.satisfies(&dep.version) => {
@@ -30,7 +32,8 @@ pub async fn update(
&mut res,
)
.await?;
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped
if crate::apps::status(name, false).await?.status
!= crate::apps::DockerStatus::Stopped
{
crate::control::stop_app(&dependent, false, dry_run).await?;
res.insert(
@@ -53,7 +56,8 @@ pub async fn update(
&mut res,
)
.await?;
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped
if crate::apps::status(name, false).await?.status
!= crate::apps::DockerStatus::Stopped
{
crate::control::stop_app(&dependent, false, dry_run).await?;
res.insert(

View File

@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use failure::ResultExt as _;
use file_lock::FileLock;
use tokio::fs::File;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
use crate::Error;
use crate::ResultExt as _;
@@ -244,8 +244,8 @@ impl tokio::io::AsyncRead for UpdateHandle<ForRead> {
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
buf: &mut ReadBuf,
) -> std::task::Poll<std::io::Result<()>> {
unsafe { self.map_unchecked_mut(|a| a.file.file.as_mut().unwrap()) }.poll_read(cx, buf)
}
}
@@ -371,7 +371,13 @@ where
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
tokio::io::AsyncRead::poll_read(unsafe { self.map_unchecked_mut(|a| &mut a.0) }, cx, buf)
let mut read_buf = ReadBuf::new(buf);
tokio::io::AsyncRead::poll_read(
unsafe { self.map_unchecked_mut(|a| &mut a.0) },
cx,
&mut read_buf,
)
.map(|res| res.map(|_| read_buf.filled().len()))
}
}
impl<T> tokio::io::AsyncRead for AsyncCompat<T>
@@ -381,9 +387,14 @@ where
fn poll_read(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<std::io::Result<usize>> {
futures::io::AsyncRead::poll_read(unsafe { self.map_unchecked_mut(|a| &mut a.0) }, cx, buf)
buf: &mut ReadBuf,
) -> std::task::Poll<std::io::Result<()>> {
futures::io::AsyncRead::poll_read(
unsafe { self.map_unchecked_mut(|a| &mut a.0) },
cx,
buf.initialize_unfilled(),
)
.map(|res| res.map(|len| buf.set_filled(len)))
}
}
impl<T> futures::io::AsyncWrite for AsyncCompat<T>

View File

@@ -3,6 +3,7 @@ use std::cmp::Ordering;
use async_trait::async_trait;
use failure::ResultExt as _;
use futures::stream::TryStreamExt;
use tokio_compat_02::FutureExt;
use crate::util::{to_yaml_async_writer, AsyncCompat, PersistencePath};
use crate::Error;
@@ -21,8 +22,9 @@ mod v0_2_3;
mod v0_2_4;
mod v0_2_5;
mod v0_2_6;
mod v0_2_7;
pub use v0_2_6::Version as Current;
pub use v0_2_7::Version as Current;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
@@ -41,6 +43,7 @@ enum Version {
V0_2_4(Wrapper<v0_2_4::Version>),
V0_2_5(Wrapper<v0_2_5::Version>),
V0_2_6(Wrapper<v0_2_6::Version>),
V0_2_7(Wrapper<v0_2_7::Version>),
Other(emver::Version),
}
@@ -149,6 +152,7 @@ pub async fn init() -> Result<(), failure::Error> {
Version::V0_2_4(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_5(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_6(v) => v.0.migrate_to(&Current::new()).await?,
Version::V0_2_7(v) => v.0.migrate_to(&Current::new()).await?,
Version::Other(_) => (),
// TODO find some way to automate this?
}
@@ -165,7 +169,7 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
.collect();
let url = format!("{}/appmgr?spec={}", &*crate::SYS_REGISTRY_URL, req_str);
log::info!("Fetching new version from {}", url);
let response = reqwest::get(&url)
let response = reqwest::get(&url).compat()
.await
.with_code(crate::error::NETWORK_ERROR)?
.error_for_status()
@@ -235,6 +239,7 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
Version::V0_2_4(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_5(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_6(v) => Current::new().migrate_to(&v.0).await?,
Version::V0_2_7(v) => Current::new().migrate_to(&v.0).await?,
Version::Other(_) => (),
// TODO find some way to automate this?
};

View File

@@ -25,6 +25,7 @@ impl VersionT for Version {
tokio::io::copy(
&mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.0.0", &*crate::SYS_REGISTRY_URL))
.compat()
.await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.with_code(crate::error::NETWORK_ERROR)?

View File

@@ -22,6 +22,7 @@ impl VersionT for Version {
tokio::io::copy(
&mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.1.1", &*crate::SYS_REGISTRY_URL))
.compat()
.await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.with_code(crate::error::NETWORK_ERROR)?
@@ -76,6 +77,7 @@ impl VersionT for Version {
tokio::io::copy(
&mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.1.0", &*crate::SYS_REGISTRY_URL))
.compat()
.await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.no_code()?

View File

@@ -0,0 +1,36 @@
use super::*;
use crate::util::Invoke;
const V0_2_7: emver::Version = emver::Version::new(0, 2, 7, 0);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_2_6::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> &'static emver::Version {
&V0_2_7
}
async fn up(&self) -> Result<(), Error> {
for (app_id, _) in crate::apps::list_info().await? {
tokio::process::Command::new("docker")
.arg("stop")
.arg(&app_id)
.invoke("Docker")
.await?;
tokio::process::Command::new("docker")
.arg("update")
.arg("--restart")
.arg("no")
.arg(&app_id)
.invoke("Docker")
.await?;
}
Ok(())
}
async fn down(&self) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -6,14 +6,10 @@
`npm i -g @ionic/cli`
`git clone https://github.com/Start9Labs/embassy-ui.git`
`git clone https://github.com/Start9Labs/embassy-os.git`
`cd embassy-ui`
`cd embassy-os/ui`
`npm i`
`ionic serve`
## Production Deployment
`ionic build --prod`

View File

@@ -52,7 +52,6 @@
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,

View File

@@ -1,6 +1,6 @@
manifest-version: 0
app-id: start9-ambassador
app-version: 0.2.6
app-version: 0.2.7
uri-rewrites:
- =/api -> http://{{start9-ambassador}}:5959/authenticate
- /api/ -> http://{{start9-ambassador}}:5959/

1955
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "embassy-ui",
"version": "0.2.6",
"version": "0.2.7",
"description": "GUI for EmbassyOS",
"author": "Start9 Labs",
"homepage": "https://github.com/Start9Labs/embassy-ui",
@@ -15,12 +15,12 @@
},
"private": true,
"dependencies": {
"@angular/common": "^10.1.6",
"@angular/core": "^10.1.6",
"@angular/forms": "^10.1.6",
"@angular/platform-browser": "^10.1.6",
"@angular/platform-browser-dynamic": "^10.1.6",
"@angular/router": "^10.1.6",
"@angular/common": "^11.0.0",
"@angular/core": "^11.0.0",
"@angular/forms": "^11.0.0",
"@angular/platform-browser": "^11.0.0",
"@angular/platform-browser-dynamic": "^11.0.0",
"@angular/router": "^11.0.0",
"@ionic/angular": "^5.4.0",
"@ionic/storage": "2.2.0",
"@start9labs/emver": "^0.1.1",
@@ -42,19 +42,19 @@
"zone.js": "^0.11.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.1002.0",
"@angular/cli": "^10.1.7",
"@angular/compiler": "^10.1.6",
"@angular/compiler-cli": "^10.1.6",
"@angular/language-service": "^10.1.6",
"@ionic/angular-toolkit": "^2.3.3",
"@angular-devkit/build-angular": "^0.1100.0",
"@angular/cli": "^11.0.0",
"@angular/compiler": "^11.0.0",
"@angular/compiler-cli": "^11.0.0",
"@angular/language-service": "^11.0.0",
"@ionic/angular-toolkit": "^3.0.0",
"@ionic/lab": "^3.2.9",
"@types/json-pointer": "^1.0.30",
"@types/marked": "^1.1.0",
"@types/node": "^14.11.10",
"@types/uuid": "^8.0.0",
"node-html-parser": "^1.3.1",
"ts-node": "^9.0.0",
"node-html-parser": "^2.0.0",
"ts-node": "^9.1.0",
"tslint": "^6.1.0",
"typescript": "4.0.5"
}

View File

@@ -1,5 +1,5 @@
import {
ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType
ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType, ValueSpecObject, ValueSpecUnion
} from './config-types'
import * as pointer from 'json-pointer'
import * as handlebars from 'handlebars'
@@ -207,7 +207,8 @@ export class ConfigCursor<T extends ValueType> {
}
case 'enum':
if (typeof cfg === 'string') {
return spec.values.includes(cfg) ? null : `${cfg} is not a valid selection.`
spec.valuesSet = spec.valuesSet || new Set(spec.values)
return spec.valuesSet.has(cfg) ? null : `${cfg} is not a valid selection.`
} else {
throw new TypeError(`${this.ptr}: expected string, got ${Array.isArray(cfg) ? 'array' : typeof cfg}`)
}
@@ -223,14 +224,22 @@ export class ConfigCursor<T extends ValueType> {
if (max && length > max) {
return spec.subtype === 'enum' ? 'Too many options selected.' : 'List is too long.'
}
for (let idx in cfg) {
for (let idx = 0; idx < cfg.length; idx++) {
let cursor = this.seekNext(idx)
if (cursor.checkInvalid()) {
return `Item #${idx + 1} is invalid. ${cursor.checkInvalid()}`
const invalid = cursor.checkInvalid()
if (invalid) {
return `Item #${idx + 1} is invalid. ${invalid}.`
}
for (let idx2 in cfg) {
if (idx !== idx2 && cursor.equals(this.seekNext(idx2))) {
return `Item #${idx + 1} is not unique.`
if (spec.subtype === 'enum') continue
for (let idx2 = idx + 1; idx2 < cfg.length; idx2++) {
if (cursor.equals(this.seekNext(idx2))) {
return `Item #${idx + 1} is not unique.` + ('uniqueBy' in cursor.spec()) ? `${
displayUniqueBy(
(cursor.spec() as ValueSpecObject | ValueSpecUnion).uniqueBy,
(cursor.spec() as ValueSpecObject | ValueSpecUnion),
cursor.config()
)
} must be unique.` : ''
}
}
}
@@ -443,4 +452,34 @@ function isEqual (uniqueBy: UniqueBy, lhs: ConfigCursor<'object'>, rhs: ConfigCu
}
return true
}
}
export function displayUniqueBy(uniqueBy: UniqueBy, spec: ValueSpecObject | ValueSpecUnion, value: object): string {
if (typeof uniqueBy === 'string') {
if (spec.type === 'object') {
return spec.spec[uniqueBy].name
} else if (spec.type === 'union') {
if (uniqueBy === spec.tag.id) {
return spec.tag.name
} else {
return spec.variants[value[spec.tag.id]][uniqueBy].name
}
}
} else if ('any' in uniqueBy) {
return uniqueBy.any.map(uq => {
if (typeof uq === 'object' && 'all' in uq) {
return `(${displayUniqueBy(uq, spec, value)})`
} else {
return displayUniqueBy(uq, spec, value)
}
}).join(' and ')
} else if ('all' in uniqueBy) {
return uniqueBy.all.map(uq => {
if (typeof uq === 'object' && 'any' in uq) {
return `(${displayUniqueBy(uq, spec, value)})`
} else {
return displayUniqueBy(uq, spec, value)
}
}).join(' or ')
}
}

View File

@@ -102,6 +102,7 @@ export interface ListValueSpecNumber {
export interface ListValueSpecEnum {
values: string[]
valuesSet?: Set<string>
valueNames: { [value: string]: string }
}

View File

@@ -39,7 +39,7 @@ const routes: Routes = [
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
initialNavigation: false,
initialNavigation: 'disabled',
useHash: true,
}),
],

View File

@@ -128,12 +128,10 @@
<ion-infinite-scroll-content loadingSpinner="lines"></ion-infinite-scroll-content>
</ion-content>
<ion-input></ion-input>
<ion-input type="password">getdots</ion-input>
<ion-input type="password" value="getdots"></ion-input>
<ion-item></ion-item>
<ion-item-divider></ion-item-divider>
<ion-item-group></ion-item-group>
<ion-item-options></ion-item-options>
<ion-item-sliding></ion-item-sliding>
<ion-label></ion-label>
<ion-list></ion-list>
<ion-loading></ion-loading>

View File

@@ -1,4 +1,4 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, Type } from '@angular/core'
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { RouteReuseStrategy } from '@angular/router'
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'

View File

@@ -46,21 +46,18 @@
</ion-item-divider>
<div *ngFor="let v of value; index as i;">
<ion-item-sliding>
<ion-item button detail="false" (click)="presentModalValueEdit(i)">
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'NoChange')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'Added')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Edited')" style="margin-right: 15px" color="primary" name="ellipse"></ion-icon>
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Invalid')" style="margin-right: 15px" color="danger" name="warning-outline"></ion-icon>
<ion-item button detail="false" (click)="presentModalValueEdit(i)">
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'NoChange')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'Added')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Edited')" style="margin-right: 15px" color="primary" name="ellipse"></ion-icon>
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Invalid')" style="margin-right: 15px" color="danger" name="warning-outline"></ion-icon>
<ion-label>{{ valueString[i] }}</ion-label>
</ion-item>
<ion-item-options side="end">
<ion-item-option color="danger" (click)="presentAlertDeleteEntry(i)">
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
</ion-item-option>
</ion-item-options>
</ion-item-sliding>
<ion-label>{{ valueString[i] }}</ion-label>
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(i, $event)">
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
</ion-button>
</ion-item>
</div>
</ion-item-group>
</div>

View File

@@ -104,7 +104,9 @@ export class AppConfigListPage extends ModalPresentable {
return this.presentModal(nextCursor, () => this.updateCaches())
}
async presentAlertDeleteEntry (key: number) {
async presentAlertDelete (key: number, e: Event) {
e.stopPropagation()
const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Caution',

View File

@@ -50,8 +50,9 @@ export class ModelPreload {
}
loadInstalledApp (appId: string): Promise<PropertySubject<AppInstalledFull>> {
const now = new Date()
return this.api.getInstalledApp(appId).then(res => {
this.appModel.update({ id: appId, ...res, hasFetchedFull: true })
this.appModel.update({ id: appId, ...res, hasFetchedFull: true }, now)
return this.appModel.watch(appId)
})
}

View File

@@ -3,7 +3,7 @@
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Marketplace Details</ion-title>
<ion-title>Listing</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>

View File

@@ -23,7 +23,7 @@
<ng-container *ngIf="!($loading$ | async)">
<ion-item *ngIf="error" class="notifier-item">
<ion-label style="margin: 7px 5px;" class="ion-text-wrap">
<p style="color: var(--ion-color-danger)">{{error.text}}.</p>
<p style="color: var(--ion-color-danger)">{{error.text}}</p>
<p><a style="color: var(--ion-color-danger); text-decoration: underline; font-weight: bold;" *ngIf="error.moreInfo && !openErrorMoreInfo" (click)="openErrorMoreInfo = true">{{error.moreInfo.buttonText}}</a></p>
<!-- presentPopover(error.moreInfo.title, error.moreInfo.description, $event) -->
@@ -94,21 +94,22 @@
</ion-label>
</ion-item>
<!-- save button, always show -->
<ion-button
[disabled]="invalid || (!edited && !added && !(['NEEDS_CONFIG'] | includes: (app.status | async)))"
fill="outline"
expand="block"
style="margin: 10px"
color="primary"
(click)="save()"
>
<ion-text color="primary" style="font-weight: bold">
Save
</ion-text>
</ion-button>
<!-- has config -->
<ng-container *ngIf="hasConfig">
<ion-button
[disabled]="invalid || (!edited && !added && !(['NEEDS_CONFIG'] | includes: (app.status | async)))"
fill="outline"
expand="block"
style="margin: 10px"
color="primary"
(click)="save()"
>
<ion-text color="primary" style="font-weight: bold">
Save
</ion-text>
</ion-button>
<ion-item-group class="ion-text-wrap ion-padding-bottom">
<ion-item-divider>Config Options</ion-item-divider>
<object-config [cursor]="rootCursor" (onEdit)="handleObjectEdit()"></object-config>

View File

@@ -205,7 +205,7 @@ export class AppConfigPage extends Cleanup {
}
return this.apiService.patchAppConfig(app, config).then(
() => this.preload.loadInstalledApp(this.appId).then(() => ({ skip: false})),
() => this.preload.loadInstalledApp(this.appId).then(() => ({ skip: false })),
)
})
.then(({ skip }) => {

View File

@@ -135,7 +135,7 @@
<!-- marketplace -->
<ion-item lines="none" [routerLink]="['/services', 'marketplace', appId]">
<ion-icon slot="start" name="cart-outline" color="primary"></ion-icon>
<ion-label><ion-text color="primary">Marketplace Details</ion-text></ion-label>
<ion-label><ion-text color="primary">Marketplace Listing</ion-text></ion-label>
</ion-item>
<!-- dependencies -->

View File

@@ -22,6 +22,13 @@
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
</ion-item>
<!-- not running -->
<ion-item *ngIf="app.status !== 'RUNNING'" class="ion-margin-bottom">
<ion-label class="ion-text-wrap">
<p><ion-text color="warning">{{ app.title }} is not running. Information on this page could be innacurate.</ion-text></p>
</ion-label>
</ion-item>
<!-- no metrics -->
<ion-item *ngIf="($hasMetrics$ | async) === false">
<ion-label class="ion-text-wrap">
@@ -29,7 +36,8 @@
</ion-label>
</ion-item>
<ion-item-group>
<!-- metrics -->
<ion-item-group *ngIf="($hasMetrics$ | async) === true">
<div *ngFor="let keyval of $metrics$ | async | keyvalue: asIsOrder">
<!-- object -->
<ion-item button detail="false" *ngIf="keyval.value.type === 'object'" (click)="goToNested(keyval.key)">

View File

@@ -33,28 +33,24 @@
</ion-item-group>
<ion-item-group style="margin-bottom: 16px;">
<ion-item-sliding *ngFor="let not of notifications; let i = index">
<ion-item-options side="end">
<ion-item-option color="danger" (click)="remove(not.id, i)">
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label class="ion-text-wrap">
<h2>
<ion-text [color]="getColor(not)"><b>{{ not.title }}</b></ion-text>
</h2>
<h2 class="notification-message">{{ not.message }}</h2>
<p>{{ not.createdAt | date: 'short' }}</p>
<p>
<a style="text-decoration: none;"
[routerLink]="['/services', 'installed', not.appId]">{{ not.appId }}</a>
<span> - </span>
Code: {{ not.code }}
</p>
</ion-label>
</ion-item>
</ion-item-sliding>
<ion-item *ngFor="let not of notifications; let i = index">
<ion-label class="ion-text-wrap">
<h2>
<ion-text [color]="getColor(not)"><b>{{ not.title }}</b></ion-text>
</h2>
<h2 class="notification-message">{{ not.message }}</h2>
<p>{{ not.createdAt | date: 'short' }}</p>
<p>
<a style="text-decoration: none;"
[routerLink]="['/services', 'installed', not.appId]">{{ not.appId }}</a>
<span> - </span>
Code: {{ not.code }}
</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="remove(not.id, i)">
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
</ion-button>
</ion-item>
</ion-item-group>
<ion-infinite-scroll [disabled]="!needInfinite" (ionInfinite)="doInfinite($event)">

View File

@@ -29,17 +29,14 @@
</ion-item>
<ion-item-divider>Saved Keys</ion-item-divider>
<ion-item-sliding *ngFor="let fingerprint of server.ssh | async">
<ion-item-options side="end">
<ion-item-option color="danger" (click)="delete(fingerprint)">
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
</ion-item-option>
</ion-item-options>
<ion-item>
<ion-label class="ion-text-wrap">{{ fingerprint.alg }} {{ fingerprint.hash }} {{ fingerprint.hostname }}
</ion-label>
</ion-item>
</ion-item-sliding>
<ion-item *ngFor="let fingerprint of server.ssh | async">
<ion-label class="ion-text-wrap">
{{ fingerprint.alg }} {{ fingerprint.hash }} {{ fingerprint.hostname }}
</ion-label>
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(fingerprint)">
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
</ion-button>
</ion-item>
</ion-item-group>
<ion-fab vertical="bottom" horizontal="end" slot="fixed">

View File

@@ -6,6 +6,7 @@ import { PropertySubject } from 'src/app/util/property-subject.util'
import { ServerConfigService } from 'src/app/services/server-config.service'
import { LoaderService } from 'src/app/services/loader.service'
import { ModelPreload } from 'src/app/models/model-preload'
import { AlertController } from '@ionic/angular'
@Component({
selector: 'dev-ssh-keys',
@@ -21,6 +22,7 @@ export class DevSSHKeysPage {
private readonly loader: LoaderService,
private readonly preload: ModelPreload,
private readonly serverConfigService: ServerConfigService,
private readonly alertCtrl: AlertController,
) { }
ngOnInit () {
@@ -41,6 +43,28 @@ export class DevSSHKeysPage {
await this.serverConfigService.presentModalValueEdit('ssh', true)
}
async presentAlertDelete (fingerprint: SSHFingerprint) {
const alert = await this.alertCtrl.create({
backdropDismiss: false,
header: 'Caution',
message: `Are you sure you want to delete this SSH key?`,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Delete',
cssClass: 'alert-danger',
handler: () => {
this.delete(fingerprint)
},
},
],
})
await alert.present()
}
async delete (fingerprint: SSHFingerprint) {
this.loader.of({
message: 'Deleting...',

View File

@@ -30,6 +30,13 @@
<ion-item-group>
<ion-item-divider></ion-item-divider>
<ion-item lines="none" button (click)="checkForUpdates()">
<ion-icon slot="start" name="refresh-outline" color="primary"></ion-icon>
<ion-label><ion-text style="font-weight: bold;" color="primary">Check for Updates</ion-text></ion-label>
</ion-item>
<ion-item-divider></ion-item-divider>
<ion-item [routerLink]="['specs']">
<ion-icon slot="start" name="information-circle-outline" color="primary"></ion-icon>
<ion-label><ion-text color="primary">About</ion-text></ion-label>
@@ -45,15 +52,6 @@
<ion-label><ion-text color="primary">Config</ion-text></ion-label>
</ion-item>
<ion-item-divider></ion-item-divider>
<ion-item lines="none" button (click)="checkForUpdates()">
<ion-icon slot="start" name="refresh-outline" color="primary"></ion-icon>
<ion-label><ion-text style="font-weight: bold;" color="primary">Check for Updates</ion-text></ion-label>
</ion-item>
<ion-item-divider></ion-item-divider>
<ion-item [routerLink]="['lan']">
<ion-icon slot="start" name="home-outline" color="primary"></ion-icon>
<ion-label><ion-text color="primary">Secure LAN Setup</ion-text></ion-label>

View File

@@ -1,13 +1,13 @@
<ion-header>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-toolbar>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Add Network</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>
</ion-toolbar>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>
</ion-header>
<ion-content class="ion-padding-top">

View File

@@ -3,6 +3,7 @@ import { NavController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { WifiService } from '../wifi.service'
import { LoaderService } from 'src/app/services/loader.service'
import { ServerModel } from 'src/app/models/server-model'
@Component({
selector: 'wifi-add',
@@ -21,9 +22,11 @@ export class WifiAddPage {
private readonly apiService: ApiService,
private readonly loader: LoaderService,
private readonly wifiService: WifiService,
private readonly serverModel: ServerModel,
) { }
async add (): Promise<void> {
this.error = ''
this.loader.of({
message: 'Saving...',
spinner: 'lines',
@@ -31,9 +34,6 @@ export class WifiAddPage {
}).displayDuringAsync( async () => {
await this.apiService.addWifi(this.ssid, this.password, this.countryCode, false)
this.wifiService.addWifi(this.ssid)
this.ssid = ''
this.password = ''
this.error = ''
this.navCtrl.back()
}).catch(e => {
console.error(e)
@@ -42,19 +42,21 @@ export class WifiAddPage {
}
async addAndConnect (): Promise<void> {
this.error = ''
this.loader.of({
message: 'Connecting. This could take while...',
spinner: 'lines',
cssClass: 'loader',
}).displayDuringAsync( async () => {
const current = this.serverModel.peek().wifi.current
await this.apiService.addWifi(this.ssid, this.password, this.countryCode, true)
const success = await this.wifiService.confirmWifi(this.ssid)
if (!success) { return }
this.wifiService.addWifi(this.ssid)
this.ssid = ''
this.password = ''
this.error = ''
this.navCtrl.back()
if (success) {
this.navCtrl.back()
this.wifiService.presentAlertSuccess(this.ssid, current)
} else {
this.wifiService.presentToastFail()
}
}).catch (e => {
console.error(e)
this.error = e.message

View File

@@ -23,14 +23,13 @@
<ion-item-group>
<ion-item>
<ion-label class="ion-text-wrap">
<p>
Add WiFi credentials to your Embassy so it can connect to the Internet without an ethernet cable.
</p>
<ion-text color="warning">Warning!</ion-text>
<br />
<br />
<ion-text color="dark">WiFi is a beta feature with known issues. If you make changes to WiFi, your Embassy and its Services may become unreachable for upward of a few hours over Tor. Please use with caution and patience.</ion-text>
</ion-label>
</ion-item>
<ion-item-divider class="borderless"></ion-item-divider>
<ion-item-divider>Saved Networks</ion-item-divider>
<ion-item button detail="false" *ngFor="let ssid of (server.wifi | async)?.ssids" (click)="presentAction(ssid)">
<ion-label>{{ ssid }}</ion-label>

View File

@@ -71,14 +71,20 @@ export class WifiListPage {
// Let's add country code here.
async connect (ssid: string): Promise<void> {
this.error = ''
this.loader.of({
message: 'Connecting. This could take while...',
spinner: 'lines',
cssClass: 'loader',
}).displayDuringAsync(async () => {
const current = this.server.wifi.getValue().current
await this.apiService.connectWifi(ssid)
await this.wifiService.confirmWifi(ssid)
this.error = ''
const success = await this.wifiService.confirmWifi(ssid)
if (success) {
this.wifiService.presentAlertSuccess(ssid, current)
} else {
this.wifiService.presentToastFail()
}
}).catch(e => {
console.error(e)
this.error = e.message
@@ -86,6 +92,7 @@ export class WifiListPage {
}
async delete (ssid: string): Promise<void> {
this.error = ''
this.loader.of({
message: 'Deleting...',
spinner: 'lines',

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { ToastController } from '@ionic/angular'
import { AlertController, ToastController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { pauseFor } from 'src/app/util/misc.util'
import { ServerModel } from 'src/app/models/server-model'
@@ -12,6 +12,7 @@ export class WifiService {
constructor (
private readonly apiService: ApiService,
private readonly toastCtrl: ToastController,
private readonly alertCtrl: AlertController,
private readonly serverModel: ServerModel,
) { }
@@ -41,7 +42,7 @@ export class WifiService {
} else {
attempts++
const diff = end - start
await pauseFor(Math.max(0, timeout - diff))
await pauseFor(Math.max(2000, timeout - diff))
if (attempts === maxAttempts) {
this.serverModel.update({ wifi: { current, ssids } })
}
@@ -52,29 +53,38 @@ export class WifiService {
}
}
if (this.serverModel.peek().wifi.current === ssid) {
return true
} else {
const toast = await this.toastCtrl.create({
header: 'Failed to connect:',
message: `Check credentials and try again`,
position: 'bottom',
duration: 4000,
buttons: [
{
side: 'start',
icon: 'close',
handler: () => {
return true
},
return this.serverModel.peek().wifi.current === ssid
}
async presentToastFail (): Promise<void> {
const toast = await this.toastCtrl.create({
header: 'Failed to connect:',
message: `Check credentials and try again`,
position: 'bottom',
duration: 4000,
buttons: [
{
side: 'start',
icon: 'close',
handler: () => {
return true
},
],
cssClass: 'notification-toast',
})
},
],
cssClass: 'notification-toast',
})
setTimeout(() => toast.present(), 300)
await toast.present()
}
return false
}
async presentAlertSuccess (current: string, old?: string): Promise<void> {
let message = 'Note. It may take a while for your Embassy to reconnect over Tor, upward of a few hours. Unplugging the device and plugging it back in may help, but it may also just need time. You may also need to hard refresh your browser cache.'
const alert = await this.alertCtrl.create({
header: `Connected to "${current}"`,
message: old ? message : 'You may now unplug your Embassy from Ethernet.<br /></br />' + message,
buttons: ['OK'],
})
await alert.present()
}
}

View File

@@ -11,7 +11,10 @@ export interface ApiServer {
versionInstalled: string
alternativeRegistryUrl: string | null
specs: ServerSpecs
wifi: { ssids: string[]; current: string; }
wifi: {
ssids: string[]
current: string | null
}
ssh: SSHFingerprint[]
serverId: string
}

View File

@@ -255,4 +255,5 @@ type HttpError = HttpErrorResponse & { error: { code: string, message: string }
const dryRunParam = (dryRun: boolean, first: boolean) => {
if (!dryRun) return ''
return first ? `?dryrun` : `&dryrun`
}
}

View File

@@ -389,7 +389,7 @@ const mockApiNotifications: ReqRes.GetNotificationsRes = [
const mockApiServer: () => ReqRes.GetServerRes = () => ({
serverId: 'start9-mockxyzab',
name: 'Embassy:12345678',
versionInstalled: '0.2.6',
versionInstalled: '0.2.7',
status: ServerStatus.RUNNING,
alternativeRegistryUrl: 'beta-registry.start9labs.com',
specs: {
@@ -420,7 +420,7 @@ const mockApiServer: () => ReqRes.GetServerRes = () => ({
})
const mockVersionLatest: ReqRes.GetVersionLatestRes = {
versionLatest: '0.2.6',
versionLatest: '0.2.7',
canUpdate: true,
}
@@ -664,7 +664,7 @@ const mockApiAppConfig: ReqRes.GetAppConfigRes = {
},
{
'firstName': 'Admin2',
'lastName': 'User2',
'lastName': 'User',
'age': 40,
},
],
@@ -1038,26 +1038,26 @@ const mockApiAppConfig: ReqRes.GetAppConfigRes = {
},
// actual config
config: {
testnet: undefined,
objectList: undefined,
unionList: undefined,
randomEnum: 'option1',
favoriteNumber: 8,
secondaryNumbers: undefined,
rpcsettings: {
laws: null,
rpcpass: null,
rpcuser: '123',
rulemakers: [],
},
advanced: {
notifications: ['call'],
},
bitcoinNode: undefined,
port: 5959,
maxconnections: null,
rpcallowip: undefined,
rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'],
// testnet: undefined,
// objectList: undefined,
// unionList: undefined,
// randomEnum: 'option1',
// favoriteNumber: 8,
// secondaryNumbers: undefined,
// rpcsettings: {
// laws: null,
// rpcpass: null,
// rpcuser: '123',
// rulemakers: [],
// },
// advanced: {
// notifications: ['call'],
// },
// bitcoinNode: undefined,
// port: 5959,
// maxconnections: null,
// rpcallowip: undefined,
// rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'],
},
rules: [],
}

View File

@@ -16,7 +16,7 @@ export const AppStatusRendering: {
} = {
[AppStatus.UNKNOWN]: { display: 'Connecting', color: 'dark', showDots: true },
[AppStatus.REMOVING]: { display: 'Removing', color: 'dark', showDots: true },
[AppStatus.CRASHED]: { display: 'Crashing', color: 'danger', showDots: true },
[AppStatus.CRASHED]: { display: 'Crashed', color: 'danger', showDots: false },
[AppStatus.NEEDS_CONFIG]: { display: 'Needs Config', color: 'warning', showDots: false },
[AppStatus.RUNNING]: { display: 'Running', color: 'success', showDots: false },
[AppStatus.UNREACHABLE]: { display: 'Unreachable', color: 'danger', showDots: false },

View File

@@ -263,14 +263,6 @@ ion-avatar {
--padding-start: 10px;
}
// .divider {
// margin-top: 15px;
// color: var(--ion-color-medium);
// font-size: medium;
// padding-left: 10px;
// font-weight: unset;
// }
ion-item-divider {
margin-top: 15px;
color: var(--ion-color-medium);

263
ui/test/config.test.ts Normal file
View File

@@ -0,0 +1,263 @@
import { displayUniqueBy } from '../src/app/app-config/config-cursor'
function assert(predicate: boolean, message: string) {
if (!predicate) {
throw new Error('Assertion Failed: ' + message)
}
}
function assertEq(a: any, b: any, message: string) {
assert(a === b, message)
}
function test() {
assertEq(
displayUniqueBy(
'foo',
{
type: 'object',
name: 'Object',
spec: {
'foo': {
type: 'string',
name: 'Foo',
nullable: true,
copyable: false,
masked: false,
},
},
nullByDefault: false,
nullable: false,
uniqueBy: 'foo'
},
{
foo: 'foo-val',
}
),
'Foo',
'base string uses name mapping'
)
assertEq(
displayUniqueBy(
{
any: [
'foo',
'bar'
]
},
{
type: 'object',
name: 'Object',
spec: {
'foo': {
type: 'string',
name: 'Foo',
nullable: true,
copyable: false,
masked: false,
},
'bar': {
type: 'string',
name: 'Bar',
nullable: true,
copyable: false,
masked: false,
},
},
nullByDefault: false,
nullable: false,
uniqueBy: {
any: [
'foo',
'bar'
]
}
},
{
foo: 'foo-val',
bar: 'bar-val',
}
),
'Foo and Bar',
'`any` must be joined with `and`'
)
assertEq(
displayUniqueBy(
{
all: [
'foo',
'bar'
]
},
{
type: 'object',
name: 'Object',
spec: {
'foo': {
type: 'string',
name: 'Foo',
nullable: true,
copyable: false,
masked: false,
},
'bar': {
type: 'string',
name: 'Bar',
nullable: true,
copyable: false,
masked: false,
},
},
nullByDefault: false,
nullable: false,
uniqueBy: {
all: [
'foo',
'bar'
]
}
},
{
foo: 'foo-val',
bar: 'bar-val',
}
),
'Foo or Bar',
'`all` must be joined with `or`'
)
assertEq(
displayUniqueBy(
{
any: [
'foo',
{
all: [
'bar',
'baz'
]
}
]
},
{
type: 'object',
name: 'Object',
spec: {
'foo': {
type: 'string',
name: 'Foo',
nullable: true,
copyable: false,
masked: false,
},
'bar': {
type: 'string',
name: 'Bar',
nullable: true,
copyable: false,
masked: false,
},
'baz': {
type: 'string',
name: 'Baz',
nullable: true,
copyable: false,
masked: false,
}
},
nullByDefault: false,
nullable: false,
uniqueBy: {
any: [
'foo',
{
all: [
'bar',
'baz'
]
}
]
}
},
{
foo: 'foo-val',
bar: 'bar-val',
baz: 'baz-val',
}
),
'Foo and (Bar or Baz)',
'`any` of `all` is correct'
)
assertEq(
displayUniqueBy(
{
any: [
'foo',
{
all: [
'bar',
'baz'
]
}
]
},
{
type: 'union',
name: 'Union',
tag: {
id: 'variant',
name: 'Variant',
variantNames: {
'variant-a': 'Variant A',
'variant-b': 'Variant B'
}
},
variants: {
'variant-a': {
'foo': {
type: 'string',
name: 'Foo',
nullable: true,
copyable: false,
masked: false,
},
'bar': {
type: 'string',
name: 'Bar',
nullable: true,
copyable: false,
masked: false,
},
'baz': {
type: 'string',
name: 'Baz',
nullable: true,
copyable: false,
masked: false,
}
},
'variant-b': {},
},
uniqueBy: {
any: [
'foo',
{
all: [
'bar',
'baz'
]
}
]
},
default: 'variant-a',
},
{
variant: 'variant-a',
foo: 'foo-val',
bar: 'bar-val',
baz: 'baz-val',
}
),
'Foo and (Bar or Baz)',
'union is correct'
)
}