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 cat /dev/random | base32 | head -c11 | tr '[:upper:]' '[:lower:]' >> product_key
appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr: $(APPMGR_SRC) 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)":/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)"/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 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) (cd agent; ./build.sh)
agent: agent/dist/agent

View File

@@ -3,4 +3,4 @@
cat config/settings.yml | grep app-mgr-version-spec cat config/settings.yml | grep app-mgr-version-spec
cat package.yaml | grep version 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'") # 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 # 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:/" filesystem-base: "_env:FILESYSTEM_BASE:/"
database: database:
database: "start9_agent.sqlite3" database: "start9_agent.sqlite3"
poolsize: "_env:YESOD_SQLITE_POOLSIZE:10" poolsize: "_env:YESOD_SQLITE_POOLSIZE:10"
app-mgr-version-spec: "=0.2.6" app-mgr-version-spec: "=0.2.7"
#analytics: UA-YOURCODE #analytics: UA-YOURCODE

View File

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

View File

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

View File

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

View File

@@ -5,26 +5,24 @@ module Handler.Hosts where
import Startlude hiding ( ask ) import Startlude hiding ( ask )
import Control.Carrier.Lift ( runM ) import Control.Carrier.Lift ( runM )
import Control.Carrier.Error.Church
import Data.Conduit import Data.Conduit
import qualified Data.Conduit.Binary as CB import qualified Data.Conduit.Binary as CB
import Data.Time.ISO8601 import Data.Time.ISO8601
import Yesod.Core hiding ( expiresAt ) import Yesod.Core hiding ( expiresAt )
import Foundation import Foundation
import Daemon.ZeroConf import Handler.Register ( checkExistingPasswordRegistration
import Handler.Register ( produceProofOfKey , getRegistration
, checkExistingPasswordRegistration
) )
import Handler.Types.Hosts import Handler.Types.Hosts
import Handler.Types.Register
import Lib.Crypto import Lib.Crypto
import Lib.Error import Lib.Error
import Lib.Password ( rootAccountName ) import Lib.Password ( rootAccountName )
import Lib.ProductKey import Lib.ProductKey
import Lib.Ssl import Lib.SystemPaths ( injectFilesystemBaseFromContext
import Lib.SystemPaths , rootCaCertPath
import Lib.Tor , SystemPath(relativeTo)
)
import Settings import Settings
getHostsR :: Handler HostsRes getHostsR :: Handler HostsRes
@@ -59,23 +57,7 @@ verifyTimestampNotExpired expirationTimestamp = do
Nothing -> throwE $ TTLExpirationE "invalid timestamp" Nothing -> throwE $ TTLExpirationE "invalid timestamp"
Just expiration -> when (expiration < now) (throwE $ TTLExpirationE "expired") 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 :: Handler TypedContent
getCertificateR = do getCertificateR = do

View File

@@ -5,7 +5,10 @@ module Handler.Register where
import Startlude hiding ( ask ) import Startlude hiding ( ask )
import Control.Carrier.Error.Either ( runError ) import Control.Carrier.Error.Either ( runError
, Error
, throwError
)
import Control.Carrier.Lift import Control.Carrier.Lift
import Control.Effect.Throw ( liftEither ) import Control.Effect.Throw ( liftEither )
import Crypto.Cipher.Types import Crypto.Cipher.Types
@@ -29,6 +32,7 @@ import Lib.Password
import Lib.ProductKey import Lib.ProductKey
import Lib.Ssl import Lib.Ssl
import Lib.SystemPaths import Lib.SystemPaths
import Lib.Tor
import Model import Model
import Settings import Settings
@@ -46,8 +50,11 @@ postRegisterR = handleS9ErrT $ do
-- Check for existing registration. -- Check for existing registration.
checkExistingPasswordRegistration rootAccountName >>= \case checkExistingPasswordRegistration rootAccountName >>= \case
Nothing -> pure () Nothing -> pure ()
Just _ -> sendResponseStatus (Status 209 "Preexisting") () 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 -- install new tor hidden service key and restart tor
registerResTorAddress <- runM (injectFilesystemBaseFromContext settings $ bootupTor torKeyFileContents) >>= \case registerResTorAddress <- runM (injectFilesystemBaseFromContext settings $ bootupTor torKeyFileContents) >>= \case
@@ -139,3 +146,21 @@ produceProofOfKey key message = do
salt <- random16 salt <- random16
let hmac = computeHmac key message salt let hmac = computeHmac key message salt
pure $ HmacSig hmac 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.Directory
import System.IO.Error import System.IO.Error
import System.Posix.Files import System.Posix.Files
import System.Process ( callCommand )
import qualified Streaming.Prelude as Stream import qualified Streaming.Prelude as Stream
import qualified Streaming.Conduit as Conduit import qualified Streaming.Conduit as Conduit
import qualified Streaming.Zip as Stream import qualified Streaming.Zip as Stream
@@ -95,12 +96,12 @@ parseKernelVersion = do
pure $ KernelVersion (Version (major', minor', patch', 0)) arch pure $ KernelVersion (Version (major', minor', patch', 0)) arch
synchronizer :: Synchronizer synchronizer :: Synchronizer
synchronizer = sync_0_2_6 synchronizer = sync_0_2_7
{-# INLINE synchronizer #-} {-# INLINE synchronizer #-}
sync_0_2_6 :: Synchronizer sync_0_2_7 :: Synchronizer
sync_0_2_6 = Synchronizer sync_0_2_7 = Synchronizer
"0.2.6" "0.2.7"
[ syncCreateAgentTmp [ syncCreateAgentTmp
, syncCreateSshDir , syncCreateSshDir
, syncRemoveAvahiSystemdDependency , syncRemoveAvahiSystemdDependency
@@ -119,6 +120,7 @@ sync_0_2_6 = Synchronizer
, syncPrepSslIntermediateCaDir , syncPrepSslIntermediateCaDir
, syncPersistLogs , syncPersistLogs
, syncConvertEcdsaCerts , syncConvertEcdsaCerts
, syncRestarterService
] ]
syncCreateAgentTmp :: SyncOp syncCreateAgentTmp :: SyncOp
@@ -536,6 +538,22 @@ replaceDerivativeCerts = do
liftIO $ renameDirectory sslDirTmp sslDir liftIO $ renameDirectory sslDirTmp sslDir
liftIO $ systemCtl RestartService "nginx" $> () 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 :: S9Error -> ExceptT Void (ReaderT AgentCtx IO) ()
failUpdate e = do failUpdate e = do
ref <- asks appIsUpdateFailed ref <- asks appIsUpdateFailed

View File

@@ -55,6 +55,8 @@ import Handler.Status
import Handler.Wifi import Handler.Wifi
import Handler.V0 import Handler.V0
import Settings import Settings
import Network.HTTP.Types.Header ( hOrigin )
import Data.List (lookup)
-- This line actually creates our YesodDispatch instance. It is the second half -- 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 -- 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 instance YesodSubDispatch Auth AgentCtx where
yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth) yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth)
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and dynamicCorsResourcePolicy :: Request -> Maybe CorsResourcePolicy
-- applying some additional middlewares. dynamicCorsResourcePolicy req = Just . policy . lookup hOrigin $ requestHeaders req
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
where where
policy o = simpleCorsResourcePolicy policy o = simpleCorsResourcePolicy
{ corsOrigins = o { corsOrigins = (\o' -> ([o'], True)) <$> o
, corsMethods = ["GET", "POST", "HEAD", "PUT", "DELETE", "TRACE", "CONNECT", "OPTIONS", "PATCH"] , corsMethods = ["GET", "POST", "HEAD", "PUT", "DELETE", "TRACE", "CONNECT", "OPTIONS", "PATCH"]
, corsRequestHeaders = [ "app-version" , corsRequestHeaders = [ "app-version"
, "Accept" , "Accept"
@@ -138,6 +131,15 @@ makeApplication foundation = do
, corsIgnoreFailures = True , 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 :: AgentCtx -> IO ()
startWeb foundation = do startWeb foundation = do
app <- makeApplication foundation app <- makeApplication foundation

View File

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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "appmgr" name = "appmgr"
version = "0.2.6" version = "0.2.7"
authors = ["Aiden McClelland <me@drbonez.dev>"] authors = ["Aiden McClelland <me@drbonez.dev>"]
edition = "2018" edition = "2018"
@@ -18,15 +18,15 @@ portable = []
production = [] production = []
[dependencies] [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" argonautica = "0.2.0"
async-trait = "0.1.41" async-trait = "0.1.42"
base32 = "0.4.0" base32 = "0.4.0"
clap = "2.33" clap = "2.33"
ed25519-dalek = "1.0.1" ed25519-dalek = "1.0.1"
failure = "0.1.8" failure = "0.1.8"
file-lock = "1.1" file-lock = "1.1"
futures = "0.3.7" futures = "0.3.8"
git-version = "0.3.4" git-version = "0.3.4"
itertools = "0.9.0" itertools = "0.9.0"
lazy_static = "1.4" lazy_static = "1.4"
@@ -38,12 +38,13 @@ pest_derive = "2.1"
prettytable-rs = "0.8.0" prettytable-rs = "0.8.0"
rand = "0.7.3" rand = "0.7.3"
regex = "1.4.2" regex = "1.4.2"
reqwest = { version = "0.10.8", features = ["stream", "json"] } reqwest = { version = "0.10.9", features = ["stream", "json"] }
rpassword = "5.0.0" 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_yaml = "0.8.14"
serde_cbor = "0.11.1" serde_cbor = "0.11.1"
serde_json = "1.0.59" serde_json = "1.0.59"
simple-logging = "2.0" simple-logging = "2.0"
tokio = { version = "0.2.22", features = ["full"] } tokio = { version = "0.3.5", features = ["full"] }
tokio-tar = "0.2.0" 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(()) 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") let output = std::process::Command::new("docker")
.args(&["inspect", id, "--format", "{{.State.Status}}"]) .args(&["inspect", id, "--format", "{{.State.Status}}"])
.stdout(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped())
@@ -155,6 +155,19 @@ pub async fn status(id: &str) -> Result<AppStatus, Error> {
"restarting" => DockerStatus::Restarting, "restarting" => DockerStatus::Restarting,
"removing" => DockerStatus::Removing, "removing" => DockerStatus::Removing,
"dead" => DockerStatus::Dead, "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, "created" | "exited" => DockerStatus::Stopped,
"paused" => DockerStatus::Paused, "paused" => DockerStatus::Paused,
_ => Err(format_err!("unknown status: {}", status))?, _ => Err(format_err!("unknown status: {}", status))?,
@@ -275,7 +288,7 @@ pub async fn info_full(
Ok(AppInfoFull { Ok(AppInfoFull {
info: info(id).await?, info: info(id).await?,
status: if with_status { status: if with_status {
Some(status(id).await?) Some(status(id, true).await?)
} else { } else {
None None
}, },
@@ -379,8 +392,12 @@ pub async fn list(
let info = list_info().await?; let info = list_info().await?;
futures::future::join_all(info.into_iter().map(move |(id, info)| async move { futures::future::join_all(info.into_iter().map(move |(id, info)| async move {
let (status, manifest, config, dependencies) = futures::try_join!( let (status, manifest, config, dependencies) = futures::try_join!(
OptionFuture::from(if with_status { Some(status(&id)) } else { None }) OptionFuture::from(if with_status {
.map(Option::transpose), Some(status(&id, true))
} else {
None
})
.map(Option::transpose),
OptionFuture::from(if with_manifest { OptionFuture::from(if with_manifest {
Some(manifest(&id)) Some(manifest(&id))
} else { } else {

View File

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

View File

@@ -137,7 +137,9 @@ pub async fn configure(
&mut res.stopped, &mut res.stopped,
) )
.await?; .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?; crate::control::stop_app(&dependent, false, dry_run).await?;
res.stopped.insert( res.stopped.insert(
// TODO: maybe don't do this if its not running // 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_configured(name, true).await?;
crate::apps::set_recoverable(name, false).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 { if !dry_run {
crate::apps::set_needs_restart(name, true).await?; crate::apps::set_needs_restart(name, true).await?;
} }

View File

@@ -1,9 +1,10 @@
use std::path::Path; use std::path::Path;
use futures::future::{BoxFuture, FutureExt}; use futures::future::{BoxFuture, FutureExt};
use linear_map::LinearMap; use linear_map::{set::LinearSet, LinearMap};
use crate::dependencies::{DependencyError, TaggedDependencyError}; use crate::dependencies::{DependencyError, TaggedDependencyError};
use crate::util::{from_yaml_async_reader, PersistencePath, YamlUpdateHandle};
use crate::Error; use crate::Error;
pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), 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, true,
) )
.await?; .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 status == crate::apps::DockerStatus::Stopped {
if update_metadata { if update_metadata {
crate::config::configure(name, None, None, false).await?; 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::dependencies::update_binds(name).await?;
} }
crate::apps::set_needs_restart(name, false).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") let output = tokio::process::Command::new("docker")
.args(&["start", name]) .args(&["start", name])
.stdout(std::process::Stdio::null()) .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: {}", "Failed to Start Application: {}",
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error") 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 { } else if status == crate::apps::DockerStatus::Paused {
resume_app(name).await?; resume_app(name).await?;
} }
@@ -67,6 +74,10 @@ pub async fn stop_app(
true, true,
) )
.await?; .await?;
let mut running = YamlUpdateHandle::<LinearSet<String>>::new_or_default(
PersistencePath::from_ref("running.yaml"),
)
.await?;
log::info!("Stopping {}", name); log::info!("Stopping {}", name);
let output = tokio::process::Command::new("docker") let output = tokio::process::Command::new("docker")
.args(&["stop", "-t", "25", name]) .args(&["stop", "-t", "25", name])
@@ -79,6 +90,8 @@ pub async fn stop_app(
"Failed to Stop Application: {}", "Failed to Stop Application: {}",
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error") std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error")
); );
running.remove(name);
running.commit().await?;
crate::util::unlock(lock).await?; crate::util::unlock(lock).await?;
} }
Ok(res) Ok(res)
@@ -98,7 +111,7 @@ pub async fn stop_dependents(
) -> BoxFuture<'a, Result<(), Error>> { ) -> BoxFuture<'a, Result<(), Error>> {
async move { async move {
for dependent in crate::apps::dependents(name, false).await? { 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 != crate::apps::DockerStatus::Stopped
{ {
stop_dependents_rec(&dependent, dry_run, DependencyError::NotRunning, res) 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?; crate::util::unlock(lock).await?;
Ok(()) 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() { if !errors.is_empty() {
return Ok(Err(DependencyError::ConfigUnsatisfied(errors))); 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)); return Ok(Err(DependencyError::NotRunning));
} }
Ok(Ok(())) Ok(Ok(()))

View File

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

View File

@@ -1,4 +1,5 @@
use emver::VersionRange; use emver::VersionRange;
use tokio_compat_02::FutureExt;
use crate::apps::AppConfig; use crate::apps::AppConfig;
use crate::manifest::ManifestLatest; use crate::manifest::ManifestLatest;
@@ -12,6 +13,7 @@ pub async fn manifest(id: &str, version: &VersionRange) -> Result<ManifestLatest
id, id,
version version
)) ))
.compat()
.await .await
.with_code(crate::error::NETWORK_ERROR)? .with_code(crate::error::NETWORK_ERROR)?
.error_for_status() .error_for_status()
@@ -34,6 +36,7 @@ pub async fn version(id: &str, version: &VersionRange) -> Result<emver::Version,
id, id,
version version
)) ))
.compat()
.await .await
.with_code(crate::error::NETWORK_ERROR)? .with_code(crate::error::NETWORK_ERROR)?
.error_for_status() .error_for_status()
@@ -51,6 +54,7 @@ pub async fn config(id: &str, version: &VersionRange) -> Result<AppConfig, Error
id, id,
version version
)) ))
.compat()
.await .await
.with_code(crate::error::NETWORK_ERROR)? .with_code(crate::error::NETWORK_ERROR)?
.error_for_status() .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 { 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 { 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 version = crate::registry::version(name, &version_req).await?;
let mut res = LinearMap::new(); let mut res = LinearMap::new();
for dependent in crate::apps::dependents(name, false).await? { 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?; let manifest = crate::apps::manifest(&dependent).await?;
match manifest.dependencies.0.get(name) { match manifest.dependencies.0.get(name) {
Some(dep) if !version.satisfies(&dep.version) => { Some(dep) if !version.satisfies(&dep.version) => {
@@ -30,7 +32,8 @@ pub async fn update(
&mut res, &mut res,
) )
.await?; .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?; crate::control::stop_app(&dependent, false, dry_run).await?;
res.insert( res.insert(
@@ -53,7 +56,8 @@ pub async fn update(
&mut res, &mut res,
) )
.await?; .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?; crate::control::stop_app(&dependent, false, dry_run).await?;
res.insert( res.insert(

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ impl VersionT for Version {
tokio::io::copy( tokio::io::copy(
&mut AsyncCompat( &mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.1.1", &*crate::SYS_REGISTRY_URL)) reqwest::get(&format!("{}/torrc?spec==0.1.1", &*crate::SYS_REGISTRY_URL))
.compat()
.await .await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e)) .with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.with_code(crate::error::NETWORK_ERROR)? .with_code(crate::error::NETWORK_ERROR)?
@@ -76,6 +77,7 @@ impl VersionT for Version {
tokio::io::copy( tokio::io::copy(
&mut AsyncCompat( &mut AsyncCompat(
reqwest::get(&format!("{}/torrc?spec==0.1.0", &*crate::SYS_REGISTRY_URL)) reqwest::get(&format!("{}/torrc?spec==0.1.0", &*crate::SYS_REGISTRY_URL))
.compat()
.await .await
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e)) .with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
.no_code()? .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` `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` `npm i`
`ionic serve` `ionic serve`
## Production Deployment
`ionic build --prod`

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { import {
ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType, ValueSpecObject, ValueSpecUnion
} from './config-types' } from './config-types'
import * as pointer from 'json-pointer' import * as pointer from 'json-pointer'
import * as handlebars from 'handlebars' import * as handlebars from 'handlebars'
@@ -207,7 +207,8 @@ export class ConfigCursor<T extends ValueType> {
} }
case 'enum': case 'enum':
if (typeof cfg === 'string') { 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 { } else {
throw new TypeError(`${this.ptr}: expected string, got ${Array.isArray(cfg) ? 'array' : typeof cfg}`) 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) { if (max && length > max) {
return spec.subtype === 'enum' ? 'Too many options selected.' : 'List is too long.' 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) let cursor = this.seekNext(idx)
if (cursor.checkInvalid()) { const invalid = cursor.checkInvalid()
return `Item #${idx + 1} is invalid. ${cursor.checkInvalid()}` if (invalid) {
return `Item #${idx + 1} is invalid. ${invalid}.`
} }
for (let idx2 in cfg) { if (spec.subtype === 'enum') continue
if (idx !== idx2 && cursor.equals(this.seekNext(idx2))) { for (let idx2 = idx + 1; idx2 < cfg.length; idx2++) {
return `Item #${idx + 1} is not unique.` 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.` : ''
} }
} }
} }
@@ -444,3 +453,33 @@ function isEqual (uniqueBy: UniqueBy, lhs: ConfigCursor<'object'>, rhs: ConfigCu
return true 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 { export interface ListValueSpecEnum {
values: string[] values: string[]
valuesSet?: Set<string>
valueNames: { [value: string]: string } valueNames: { [value: string]: string }
} }

View File

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

View File

@@ -128,12 +128,10 @@
<ion-infinite-scroll-content loadingSpinner="lines"></ion-infinite-scroll-content> <ion-infinite-scroll-content loadingSpinner="lines"></ion-infinite-scroll-content>
</ion-content> </ion-content>
<ion-input></ion-input> <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></ion-item>
<ion-item-divider></ion-item-divider> <ion-item-divider></ion-item-divider>
<ion-item-group></ion-item-group> <ion-item-group></ion-item-group>
<ion-item-options></ion-item-options>
<ion-item-sliding></ion-item-sliding>
<ion-label></ion-label> <ion-label></ion-label>
<ion-list></ion-list> <ion-list></ion-list>
<ion-loading></ion-loading> <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 { BrowserModule } from '@angular/platform-browser'
import { RouteReuseStrategy } from '@angular/router' import { RouteReuseStrategy } from '@angular/router'
import { IonicModule, IonicRouteStrategy } from '@ionic/angular' import { IonicModule, IonicRouteStrategy } from '@ionic/angular'

View File

@@ -46,21 +46,18 @@
</ion-item-divider> </ion-item-divider>
<div *ngFor="let v of value; index as i;"> <div *ngFor="let v of value; index as i;">
<ion-item-sliding> <ion-item button detail="false" (click)="presentModalValueEdit(i)">
<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: '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: '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: '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-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-label>{{ valueString[i] }}</ion-label>
</ion-item>
<ion-item-options side="end"> <ion-button slot="end" fill="clear" (click)="presentAlertDelete(i, $event)">
<ion-item-option color="danger" (click)="presentAlertDeleteEntry(i)"> <ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
<ion-icon slot="icon-only" name="trash-outline"></ion-icon> </ion-button>
</ion-item-option> </ion-item>
</ion-item-options>
</ion-item-sliding>
</div> </div>
</ion-item-group> </ion-item-group>
</div> </div>

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@
<ng-container *ngIf="!($loading$ | async)"> <ng-container *ngIf="!($loading$ | async)">
<ion-item *ngIf="error" class="notifier-item"> <ion-item *ngIf="error" class="notifier-item">
<ion-label style="margin: 7px 5px;" class="ion-text-wrap"> <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> <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) --> <!-- presentPopover(error.moreInfo.title, error.moreInfo.description, $event) -->
@@ -94,21 +94,22 @@
</ion-label> </ion-label>
</ion-item> </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 --> <!-- has config -->
<ng-container *ngIf="hasConfig"> <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-group class="ion-text-wrap ion-padding-bottom">
<ion-item-divider>Config Options</ion-item-divider> <ion-item-divider>Config Options</ion-item-divider>
<object-config [cursor]="rootCursor" (onEdit)="handleObjectEdit()"></object-config> <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( 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 }) => { .then(({ skip }) => {

View File

@@ -135,7 +135,7 @@
<!-- marketplace --> <!-- marketplace -->
<ion-item lines="none" [routerLink]="['/services', 'marketplace', appId]"> <ion-item lines="none" [routerLink]="['/services', 'marketplace', appId]">
<ion-icon slot="start" name="cart-outline" color="primary"></ion-icon> <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> </ion-item>
<!-- dependencies --> <!-- dependencies -->

View File

@@ -22,6 +22,13 @@
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text> <ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
</ion-item> </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 --> <!-- no metrics -->
<ion-item *ngIf="($hasMetrics$ | async) === false"> <ion-item *ngIf="($hasMetrics$ | async) === false">
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
@@ -29,7 +36,8 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-group> <!-- metrics -->
<ion-item-group *ngIf="($hasMetrics$ | async) === true">
<div *ngFor="let keyval of $metrics$ | async | keyvalue: asIsOrder"> <div *ngFor="let keyval of $metrics$ | async | keyvalue: asIsOrder">
<!-- object --> <!-- object -->
<ion-item button detail="false" *ngIf="keyval.value.type === 'object'" (click)="goToNested(keyval.key)"> <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>
<ion-item-group style="margin-bottom: 16px;"> <ion-item-group style="margin-bottom: 16px;">
<ion-item-sliding *ngFor="let not of notifications; let i = index"> <ion-item *ngFor="let not of notifications; let i = index">
<ion-item-options side="end"> <ion-label class="ion-text-wrap">
<ion-item-option color="danger" (click)="remove(not.id, i)"> <h2>
<ion-icon slot="icon-only" name="trash-outline"></ion-icon> <ion-text [color]="getColor(not)"><b>{{ not.title }}</b></ion-text>
</ion-item-option> </h2>
</ion-item-options> <h2 class="notification-message">{{ not.message }}</h2>
<ion-item> <p>{{ not.createdAt | date: 'short' }}</p>
<ion-label class="ion-text-wrap"> <p>
<h2> <a style="text-decoration: none;"
<ion-text [color]="getColor(not)"><b>{{ not.title }}</b></ion-text> [routerLink]="['/services', 'installed', not.appId]">{{ not.appId }}</a>
</h2> <span> - </span>
<h2 class="notification-message">{{ not.message }}</h2> Code: {{ not.code }}
<p>{{ not.createdAt | date: 'short' }}</p> </p>
<p> </ion-label>
<a style="text-decoration: none;" <ion-button slot="end" fill="clear" (click)="remove(not.id, i)">
[routerLink]="['/services', 'installed', not.appId]">{{ not.appId }}</a> <ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
<span> - </span> </ion-button>
Code: {{ not.code }} </ion-item>
</p>
</ion-label>
</ion-item>
</ion-item-sliding>
</ion-item-group> </ion-item-group>
<ion-infinite-scroll [disabled]="!needInfinite" (ionInfinite)="doInfinite($event)"> <ion-infinite-scroll [disabled]="!needInfinite" (ionInfinite)="doInfinite($event)">

View File

@@ -29,17 +29,14 @@
</ion-item> </ion-item>
<ion-item-divider>Saved Keys</ion-item-divider> <ion-item-divider>Saved Keys</ion-item-divider>
<ion-item-sliding *ngFor="let fingerprint of server.ssh | async"> <ion-item *ngFor="let fingerprint of server.ssh | async">
<ion-item-options side="end"> <ion-label class="ion-text-wrap">
<ion-item-option color="danger" (click)="delete(fingerprint)"> {{ fingerprint.alg }} {{ fingerprint.hash }} {{ fingerprint.hostname }}
<ion-icon slot="icon-only" name="trash-outline"></ion-icon> </ion-label>
</ion-item-option> <ion-button slot="end" fill="clear" (click)="presentAlertDelete(fingerprint)">
</ion-item-options> <ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
<ion-item> </ion-button>
<ion-label class="ion-text-wrap">{{ fingerprint.alg }} {{ fingerprint.hash }} {{ fingerprint.hostname }} </ion-item>
</ion-label>
</ion-item>
</ion-item-sliding>
</ion-item-group> </ion-item-group>
<ion-fab vertical="bottom" horizontal="end" slot="fixed"> <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 { ServerConfigService } from 'src/app/services/server-config.service'
import { LoaderService } from 'src/app/services/loader.service' import { LoaderService } from 'src/app/services/loader.service'
import { ModelPreload } from 'src/app/models/model-preload' import { ModelPreload } from 'src/app/models/model-preload'
import { AlertController } from '@ionic/angular'
@Component({ @Component({
selector: 'dev-ssh-keys', selector: 'dev-ssh-keys',
@@ -21,6 +22,7 @@ export class DevSSHKeysPage {
private readonly loader: LoaderService, private readonly loader: LoaderService,
private readonly preload: ModelPreload, private readonly preload: ModelPreload,
private readonly serverConfigService: ServerConfigService, private readonly serverConfigService: ServerConfigService,
private readonly alertCtrl: AlertController,
) { } ) { }
ngOnInit () { ngOnInit () {
@@ -41,6 +43,28 @@ export class DevSSHKeysPage {
await this.serverConfigService.presentModalValueEdit('ssh', true) 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) { async delete (fingerprint: SSHFingerprint) {
this.loader.of({ this.loader.of({
message: 'Deleting...', message: 'Deleting...',

View File

@@ -30,6 +30,13 @@
<ion-item-group> <ion-item-group>
<ion-item-divider></ion-item-divider> <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-item [routerLink]="['specs']">
<ion-icon slot="start" name="information-circle-outline" color="primary"></ion-icon> <ion-icon slot="start" name="information-circle-outline" color="primary"></ion-icon>
<ion-label><ion-text color="primary">About</ion-text></ion-label> <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-label><ion-text color="primary">Config</ion-text></ion-label>
</ion-item> </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-item [routerLink]="['lan']">
<ion-icon slot="start" name="home-outline" color="primary"></ion-icon> <ion-icon slot="start" name="home-outline" color="primary"></ion-icon>
<ion-label><ion-text color="primary">Secure LAN Setup</ion-text></ion-label> <ion-label><ion-text color="primary">Secure LAN Setup</ion-text></ion-label>

View File

@@ -1,13 +1,13 @@
<ion-header> <ion-header>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-toolbar> <ion-toolbar>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Add Network</ion-title> <ion-title>Add Network</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>
</ion-header> </ion-header>
<ion-content class="ion-padding-top"> <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 { ApiService } from 'src/app/services/api/api.service'
import { WifiService } from '../wifi.service' import { WifiService } from '../wifi.service'
import { LoaderService } from 'src/app/services/loader.service' import { LoaderService } from 'src/app/services/loader.service'
import { ServerModel } from 'src/app/models/server-model'
@Component({ @Component({
selector: 'wifi-add', selector: 'wifi-add',
@@ -21,9 +22,11 @@ export class WifiAddPage {
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly loader: LoaderService, private readonly loader: LoaderService,
private readonly wifiService: WifiService, private readonly wifiService: WifiService,
private readonly serverModel: ServerModel,
) { } ) { }
async add (): Promise<void> { async add (): Promise<void> {
this.error = ''
this.loader.of({ this.loader.of({
message: 'Saving...', message: 'Saving...',
spinner: 'lines', spinner: 'lines',
@@ -31,9 +34,6 @@ export class WifiAddPage {
}).displayDuringAsync( async () => { }).displayDuringAsync( async () => {
await this.apiService.addWifi(this.ssid, this.password, this.countryCode, false) await this.apiService.addWifi(this.ssid, this.password, this.countryCode, false)
this.wifiService.addWifi(this.ssid) this.wifiService.addWifi(this.ssid)
this.ssid = ''
this.password = ''
this.error = ''
this.navCtrl.back() this.navCtrl.back()
}).catch(e => { }).catch(e => {
console.error(e) console.error(e)
@@ -42,19 +42,21 @@ export class WifiAddPage {
} }
async addAndConnect (): Promise<void> { async addAndConnect (): Promise<void> {
this.error = ''
this.loader.of({ this.loader.of({
message: 'Connecting. This could take while...', message: 'Connecting. This could take while...',
spinner: 'lines', spinner: 'lines',
cssClass: 'loader', cssClass: 'loader',
}).displayDuringAsync( async () => { }).displayDuringAsync( async () => {
const current = this.serverModel.peek().wifi.current
await this.apiService.addWifi(this.ssid, this.password, this.countryCode, true) await this.apiService.addWifi(this.ssid, this.password, this.countryCode, true)
const success = await this.wifiService.confirmWifi(this.ssid) const success = await this.wifiService.confirmWifi(this.ssid)
if (!success) { return } if (success) {
this.wifiService.addWifi(this.ssid) this.navCtrl.back()
this.ssid = '' this.wifiService.presentAlertSuccess(this.ssid, current)
this.password = '' } else {
this.error = '' this.wifiService.presentToastFail()
this.navCtrl.back() }
}).catch (e => { }).catch (e => {
console.error(e) console.error(e)
this.error = e.message this.error = e.message

View File

@@ -23,14 +23,13 @@
<ion-item-group> <ion-item-group>
<ion-item> <ion-item>
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
<p> <ion-text color="warning">Warning!</ion-text>
Add WiFi credentials to your Embassy so it can connect to the Internet without an ethernet cable. <br />
</p> <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-label>
</ion-item> </ion-item>
<ion-item-divider class="borderless"></ion-item-divider>
<ion-item-divider>Saved Networks</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-item button detail="false" *ngFor="let ssid of (server.wifi | async)?.ssids" (click)="presentAction(ssid)">
<ion-label>{{ ssid }}</ion-label> <ion-label>{{ ssid }}</ion-label>

View File

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

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core' 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 { ApiService } from 'src/app/services/api/api.service'
import { pauseFor } from 'src/app/util/misc.util' import { pauseFor } from 'src/app/util/misc.util'
import { ServerModel } from 'src/app/models/server-model' import { ServerModel } from 'src/app/models/server-model'
@@ -12,6 +12,7 @@ export class WifiService {
constructor ( constructor (
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly toastCtrl: ToastController, private readonly toastCtrl: ToastController,
private readonly alertCtrl: AlertController,
private readonly serverModel: ServerModel, private readonly serverModel: ServerModel,
) { } ) { }
@@ -41,7 +42,7 @@ export class WifiService {
} else { } else {
attempts++ attempts++
const diff = end - start const diff = end - start
await pauseFor(Math.max(0, timeout - diff)) await pauseFor(Math.max(2000, timeout - diff))
if (attempts === maxAttempts) { if (attempts === maxAttempts) {
this.serverModel.update({ wifi: { current, ssids } }) this.serverModel.update({ wifi: { current, ssids } })
} }
@@ -52,29 +53,38 @@ export class WifiService {
} }
} }
if (this.serverModel.peek().wifi.current === ssid) { return this.serverModel.peek().wifi.current === ssid
return true }
} else {
const toast = await this.toastCtrl.create({ async presentToastFail (): Promise<void> {
header: 'Failed to connect:', const toast = await this.toastCtrl.create({
message: `Check credentials and try again`, header: 'Failed to connect:',
position: 'bottom', message: `Check credentials and try again`,
duration: 4000, position: 'bottom',
buttons: [ duration: 4000,
{ buttons: [
side: 'start', {
icon: 'close', side: 'start',
handler: () => { icon: 'close',
return true 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 versionInstalled: string
alternativeRegistryUrl: string | null alternativeRegistryUrl: string | null
specs: ServerSpecs specs: ServerSpecs
wifi: { ssids: string[]; current: string; } wifi: {
ssids: string[]
current: string | null
}
ssh: SSHFingerprint[] ssh: SSHFingerprint[]
serverId: string serverId: string
} }

View File

@@ -256,3 +256,4 @@ const dryRunParam = (dryRun: boolean, first: boolean) => {
if (!dryRun) return '' if (!dryRun) return ''
return first ? `?dryrun` : `&dryrun` return first ? `?dryrun` : `&dryrun`
} }

View File

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

View File

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

View File

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