mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
308a78de37 | ||
|
|
d8ea87bbae | ||
|
|
db275601d4 | ||
|
|
039f776ead | ||
|
|
14127daa41 | ||
|
|
da6ee94605 | ||
|
|
abec910378 | ||
|
|
ba452587fc | ||
|
|
4de0c97bb5 | ||
|
|
5b3c692b7a | ||
|
|
2c97294196 | ||
|
|
0f43e5282f | ||
|
|
7ce276c267 | ||
|
|
7a3811235e | ||
|
|
7b0f99881b | ||
|
|
431ff86647 | ||
|
|
47e3361c4f | ||
|
|
968b94e81f | ||
|
|
c4fe73398e | ||
|
|
95a0bfdd1e | ||
|
|
37c772ff8c | ||
|
|
c6347c3ff7 | ||
|
|
adea594e28 | ||
|
|
a9c51e24f1 | ||
|
|
c3e96ef5df | ||
|
|
bd046f7e9f | ||
|
|
ab6aadc3b4 | ||
|
|
2b4c456c5d | ||
|
|
e4cbc38bfd | ||
|
|
bcfe7c0d21 | ||
|
|
7d493e12d3 | ||
|
|
414d8ae54a | ||
|
|
1da2da7e43 | ||
|
|
1a66a5d240 | ||
|
|
7939dcc4e2 | ||
|
|
48f6543cb9 | ||
|
|
3fce90b7a8 | ||
|
|
71a3728fbb | ||
|
|
0dee648078 | ||
|
|
26cf4ea418 | ||
|
|
f6ac172ef3 | ||
|
|
3ababacd91 | ||
|
|
5a121945d2 | ||
|
|
30f8b8e6cd | ||
|
|
f68c13f57f | ||
|
|
3a082c108b | ||
|
|
3efb38a742 |
10
Makefile
10
Makefile
@@ -19,8 +19,12 @@ product_key:
|
||||
cat /dev/random | base32 | head -c11 | tr '[:upper:]' '[:lower:]' >> product_key
|
||||
|
||||
appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr: $(APPMGR_SRC)
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)"/appmgr:/home/rust/src start9/rust-arm-cross:latest cargo build --release --features=production
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)"/appmgr:/home/rust/src start9/rust-arm-cross:latest arm-linux-gnueabi-strip target/armv7-unknown-linux-gnueabihf/release/appmgr
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest sh -c "(cd appmgr && cargo build --release --features=production)"
|
||||
docker run --rm -it -v ~/.cargo/registry:/root/.cargo/registry -v "$(shell pwd)":/home/rust/src start9/rust-arm-cross:latest arm-linux-gnueabi-strip appmgr/target/armv7-unknown-linux-gnueabihf/release/appmgr
|
||||
|
||||
agent: $(AGENT_SRC)
|
||||
appmgr: appmgr/target/armv7-unknown-linux-musleabihf/release/appmgr
|
||||
|
||||
agent/dist/agent: $(AGENT_SRC)
|
||||
(cd agent; ./build.sh)
|
||||
|
||||
agent: agent/dist/agent
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
cat config/settings.yml | grep app-mgr-version-spec
|
||||
cat package.yaml | grep version
|
||||
|
||||
stack --local-bin-path ./executables build --copy-bins #--flag start9-agent:disable-auth
|
||||
stack --local-bin-path ./dist build --copy-bins #--flag start9-agent:disable-auth
|
||||
|
||||
7
agent/config/restarter.service
Normal file
7
agent/config/restarter.service
Normal file
@@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=restarts dead containers
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/appmgr repair-app-status
|
||||
9
agent/config/restarter.timer
Normal file
9
agent/config/restarter.timer
Normal file
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=restarter
|
||||
|
||||
[Timer]
|
||||
OnUnitActiveSec=60s
|
||||
OnBootSec=60s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -28,12 +28,11 @@ detailed-logging: "_env:DETAILED_LOGGING:false"
|
||||
|
||||
# NB: If you need a numeric value (e.g. 123) to parse as a String, wrap it in single quotes (e.g. "_env:YESOD_PGPASS:'123'")
|
||||
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
|
||||
cors-override-star: "_env:CORS_OVERRIDE_STAR:"
|
||||
filesystem-base: "_env:FILESYSTEM_BASE:/"
|
||||
database:
|
||||
database: "start9_agent.sqlite3"
|
||||
poolsize: "_env:YESOD_SQLITE_POOLSIZE:10"
|
||||
|
||||
app-mgr-version-spec: "=0.2.6"
|
||||
app-mgr-version-spec: "=0.2.7"
|
||||
|
||||
#analytics: UA-YOURCODE
|
||||
|
||||
1
agent/migrations/0.2.6::0.2.7
Normal file
1
agent/migrations/0.2.6::0.2.7
Normal file
@@ -0,0 +1 @@
|
||||
SELECT TRUE;
|
||||
@@ -1,5 +1,5 @@
|
||||
name: ambassador-agent
|
||||
version: 0.2.6
|
||||
version: 0.2.7
|
||||
|
||||
default-extensions:
|
||||
- NoImplicitPrelude
|
||||
|
||||
@@ -710,7 +710,6 @@ remapAppMgrInfo jobCache serverApps = flip
|
||||
$ ((, infoResVersion) <$> HM.lookup appId tmpStatuses)
|
||||
<|> (guard (not infoResIsConfigured || infoResIsRecoverable) $> (NeedsConfig, infoResVersion))
|
||||
<|> (guard realViolations $> (BrokenDependencies, infoResVersion))
|
||||
<|> (guard (infoResStatus == Restarting) $> (Crashed, infoResVersion))
|
||||
in ( status
|
||||
, version
|
||||
, infoRes
|
||||
|
||||
@@ -5,26 +5,24 @@ module Handler.Hosts where
|
||||
import Startlude hiding ( ask )
|
||||
|
||||
import Control.Carrier.Lift ( runM )
|
||||
import Control.Carrier.Error.Church
|
||||
import Data.Conduit
|
||||
import qualified Data.Conduit.Binary as CB
|
||||
import Data.Time.ISO8601
|
||||
import Yesod.Core hiding ( expiresAt )
|
||||
|
||||
import Foundation
|
||||
import Daemon.ZeroConf
|
||||
import Handler.Register ( produceProofOfKey
|
||||
, checkExistingPasswordRegistration
|
||||
import Handler.Register ( checkExistingPasswordRegistration
|
||||
, getRegistration
|
||||
)
|
||||
import Handler.Types.Hosts
|
||||
import Handler.Types.Register
|
||||
import Lib.Crypto
|
||||
import Lib.Error
|
||||
import Lib.Password ( rootAccountName )
|
||||
import Lib.ProductKey
|
||||
import Lib.Ssl
|
||||
import Lib.SystemPaths
|
||||
import Lib.Tor
|
||||
import Lib.SystemPaths ( injectFilesystemBaseFromContext
|
||||
, rootCaCertPath
|
||||
, SystemPath(relativeTo)
|
||||
)
|
||||
import Settings
|
||||
|
||||
getHostsR :: Handler HostsRes
|
||||
@@ -59,23 +57,7 @@ verifyTimestampNotExpired expirationTimestamp = do
|
||||
Nothing -> throwE $ TTLExpirationE "invalid timestamp"
|
||||
Just expiration -> when (expiration < now) (throwE $ TTLExpirationE "expired")
|
||||
|
||||
getRegistration :: (MonadIO m, HasFilesystemBase sig m, Has (Error S9Error) sig m) => Text -> UTCTime -> m RegisterRes
|
||||
getRegistration productKey registerResClaimedAt = do
|
||||
torAddress <- getAgentHiddenServiceUrlMaybe >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "torAddress"
|
||||
Just t -> pure $ t
|
||||
caCert <- readSystemPath rootCaCertPath >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "cert"
|
||||
Just t -> pure t
|
||||
|
||||
-- create an hmac of the torAddress + caCert for front end
|
||||
registerResTorAddressSig <- produceProofOfKey productKey torAddress
|
||||
registerResCertSig <- produceProofOfKey productKey caCert
|
||||
|
||||
let registerResCertName = root_CA_CERT_NAME
|
||||
registerResLanAddress <- getStart9AgentHostnameLocal
|
||||
|
||||
pure RegisterRes { .. }
|
||||
|
||||
getCertificateR :: Handler TypedContent
|
||||
getCertificateR = do
|
||||
|
||||
@@ -5,7 +5,10 @@ module Handler.Register where
|
||||
|
||||
import Startlude hiding ( ask )
|
||||
|
||||
import Control.Carrier.Error.Either ( runError )
|
||||
import Control.Carrier.Error.Either ( runError
|
||||
, Error
|
||||
, throwError
|
||||
)
|
||||
import Control.Carrier.Lift
|
||||
import Control.Effect.Throw ( liftEither )
|
||||
import Crypto.Cipher.Types
|
||||
@@ -29,6 +32,7 @@ import Lib.Password
|
||||
import Lib.ProductKey
|
||||
import Lib.Ssl
|
||||
import Lib.SystemPaths
|
||||
import Lib.Tor
|
||||
import Model
|
||||
import Settings
|
||||
|
||||
@@ -46,8 +50,11 @@ postRegisterR = handleS9ErrT $ do
|
||||
|
||||
-- Check for existing registration.
|
||||
checkExistingPasswordRegistration rootAccountName >>= \case
|
||||
Nothing -> pure ()
|
||||
Just _ -> sendResponseStatus (Status 209 "Preexisting") ()
|
||||
Nothing -> pure ()
|
||||
Just claimedAt -> do
|
||||
res <- mapExceptT (liftIO . runM . injectFilesystemBaseFromContext settings)
|
||||
$ getRegistration productKey claimedAt
|
||||
sendResponseStatus (Status 209 "Preexisting") res
|
||||
|
||||
-- install new tor hidden service key and restart tor
|
||||
registerResTorAddress <- runM (injectFilesystemBaseFromContext settings $ bootupTor torKeyFileContents) >>= \case
|
||||
@@ -139,3 +146,21 @@ produceProofOfKey key message = do
|
||||
salt <- random16
|
||||
let hmac = computeHmac key message salt
|
||||
pure $ HmacSig hmac message salt
|
||||
|
||||
getRegistration :: (MonadIO m, HasFilesystemBase sig m, Has (Error S9Error) sig m) => Text -> UTCTime -> m RegisterRes
|
||||
getRegistration productKey registerResClaimedAt = do
|
||||
torAddress <- getAgentHiddenServiceUrlMaybe >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "torAddress"
|
||||
Just t -> pure $ t
|
||||
caCert <- readSystemPath rootCaCertPath >>= \case
|
||||
Nothing -> throwError $ NotFoundE "prior registration" "cert"
|
||||
Just t -> pure t
|
||||
|
||||
-- create an hmac of the torAddress + caCert for front end
|
||||
registerResTorAddressSig <- produceProofOfKey productKey torAddress
|
||||
registerResCertSig <- produceProofOfKey productKey caCert
|
||||
|
||||
let registerResCertName = root_CA_CERT_NAME
|
||||
registerResLanAddress <- getStart9AgentHostnameLocal
|
||||
|
||||
pure RegisterRes { .. }
|
||||
|
||||
@@ -41,6 +41,7 @@ import System.FilePath.Posix ( takeDirectory )
|
||||
import System.Directory
|
||||
import System.IO.Error
|
||||
import System.Posix.Files
|
||||
import System.Process ( callCommand )
|
||||
import qualified Streaming.Prelude as Stream
|
||||
import qualified Streaming.Conduit as Conduit
|
||||
import qualified Streaming.Zip as Stream
|
||||
@@ -95,12 +96,12 @@ parseKernelVersion = do
|
||||
pure $ KernelVersion (Version (major', minor', patch', 0)) arch
|
||||
|
||||
synchronizer :: Synchronizer
|
||||
synchronizer = sync_0_2_6
|
||||
synchronizer = sync_0_2_7
|
||||
{-# INLINE synchronizer #-}
|
||||
|
||||
sync_0_2_6 :: Synchronizer
|
||||
sync_0_2_6 = Synchronizer
|
||||
"0.2.6"
|
||||
sync_0_2_7 :: Synchronizer
|
||||
sync_0_2_7 = Synchronizer
|
||||
"0.2.7"
|
||||
[ syncCreateAgentTmp
|
||||
, syncCreateSshDir
|
||||
, syncRemoveAvahiSystemdDependency
|
||||
@@ -119,6 +120,7 @@ sync_0_2_6 = Synchronizer
|
||||
, syncPrepSslIntermediateCaDir
|
||||
, syncPersistLogs
|
||||
, syncConvertEcdsaCerts
|
||||
, syncRestarterService
|
||||
]
|
||||
|
||||
syncCreateAgentTmp :: SyncOp
|
||||
@@ -536,6 +538,22 @@ replaceDerivativeCerts = do
|
||||
liftIO $ renameDirectory sslDirTmp sslDir
|
||||
liftIO $ systemCtl RestartService "nginx" $> ()
|
||||
|
||||
syncRestarterService :: SyncOp
|
||||
syncRestarterService = SyncOp "Install Restarter Service" check migrate True
|
||||
where
|
||||
wantedService = $(embedFile "config/restarter.service")
|
||||
wantedTimer = $(embedFile "config/restarter.timer")
|
||||
check = do
|
||||
base <- asks $ appFilesystemBase . appSettings
|
||||
liftIO $ not <$> doesPathExist
|
||||
(toS $ "/etc/systemd/system/timers.target.wants/restarter.timer" `relativeTo` base)
|
||||
migrate = do
|
||||
base <- asks $ appFilesystemBase . appSettings
|
||||
liftIO $ BS.writeFile (toS $ "/etc/systemd/system/restarter.service" `relativeTo` base) wantedService
|
||||
liftIO $ BS.writeFile (toS $ "/etc/systemd/system/restarter.timer" `relativeTo` base) wantedTimer
|
||||
liftIO $ callCommand "systemctl enable restarter.service"
|
||||
liftIO $ callCommand "systemctl enable restarter.timer"
|
||||
|
||||
failUpdate :: S9Error -> ExceptT Void (ReaderT AgentCtx IO) ()
|
||||
failUpdate e = do
|
||||
ref <- asks appIsUpdateFailed
|
||||
|
||||
@@ -55,6 +55,8 @@ import Handler.Status
|
||||
import Handler.Wifi
|
||||
import Handler.V0
|
||||
import Settings
|
||||
import Network.HTTP.Types.Header ( hOrigin )
|
||||
import Data.List (lookup)
|
||||
|
||||
-- This line actually creates our YesodDispatch instance. It is the second half
|
||||
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
||||
@@ -64,20 +66,11 @@ mkYesodDispatch "AgentCtx" resourcesAgentCtx
|
||||
instance YesodSubDispatch Auth AgentCtx where
|
||||
yesodSubDispatch = $(mkYesodSubDispatch resourcesAuth)
|
||||
|
||||
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||||
-- applying some additional middlewares.
|
||||
makeApplication :: AgentCtx -> IO Application
|
||||
makeApplication foundation = do
|
||||
logWare <- makeLogWare foundation
|
||||
-- Create the WAI application and apply middlewares
|
||||
appPlain <- toWaiAppPlain foundation
|
||||
let origin = case appCorsOverrideStar $ appSettings foundation of
|
||||
Nothing -> Nothing
|
||||
Just override -> Just ([encodeUtf8 override], True)
|
||||
pure . logWare . cors (const . Just $ policy origin) . defaultMiddlewaresNoLogging $ appPlain
|
||||
dynamicCorsResourcePolicy :: Request -> Maybe CorsResourcePolicy
|
||||
dynamicCorsResourcePolicy req = Just . policy . lookup hOrigin $ requestHeaders req
|
||||
where
|
||||
policy o = simpleCorsResourcePolicy
|
||||
{ corsOrigins = o
|
||||
{ corsOrigins = (\o' -> ([o'], True)) <$> o
|
||||
, corsMethods = ["GET", "POST", "HEAD", "PUT", "DELETE", "TRACE", "CONNECT", "OPTIONS", "PATCH"]
|
||||
, corsRequestHeaders = [ "app-version"
|
||||
, "Accept"
|
||||
@@ -138,6 +131,15 @@ makeApplication foundation = do
|
||||
, corsIgnoreFailures = True
|
||||
}
|
||||
|
||||
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||||
-- applying some additional middlewares.
|
||||
makeApplication :: AgentCtx -> IO Application
|
||||
makeApplication foundation = do
|
||||
logWare <- makeLogWare foundation
|
||||
-- Create the WAI application and apply middlewares
|
||||
appPlain <- toWaiAppPlain foundation
|
||||
pure . logWare . cors dynamicCorsResourcePolicy . defaultMiddlewaresNoLogging $ appPlain
|
||||
|
||||
startWeb :: AgentCtx -> IO ()
|
||||
startWeb foundation = do
|
||||
app <- makeApplication foundation
|
||||
|
||||
@@ -41,7 +41,6 @@ data AppSettings = AppSettings
|
||||
-- ^ Should all log messages be displayed?
|
||||
, appMgrVersionSpec :: VersionRange
|
||||
, appFilesystemBase :: Text
|
||||
, appCorsOverrideStar :: Maybe Text
|
||||
}
|
||||
deriving Show
|
||||
|
||||
@@ -64,7 +63,6 @@ instance FromJSON AppSettings where
|
||||
|
||||
appMgrVersionSpec <- o .: "app-mgr-version-spec"
|
||||
appFilesystemBase <- o .: "filesystem-base"
|
||||
appCorsOverrideStar <- o .:? "cors-override-star"
|
||||
return AppSettings { .. }
|
||||
|
||||
-- | Raw bytes at compile time of @config/settings.yml@
|
||||
|
||||
30
appmgr/.github/workflows/rust.yml
vendored
30
appmgr/.github/workflows/rust.yml
vendored
@@ -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
357
appmgr/Cargo.lock
generated
@@ -35,7 +35,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "appmgr"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
dependencies = [
|
||||
"argonautica",
|
||||
"async-trait",
|
||||
@@ -45,7 +45,7 @@ dependencies = [
|
||||
"emver",
|
||||
"failure",
|
||||
"file-lock",
|
||||
"futures 0.3.7",
|
||||
"futures 0.3.8",
|
||||
"git-version",
|
||||
"itertools 0.9.0",
|
||||
"lazy_static",
|
||||
@@ -64,7 +64,8 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"simple-logging",
|
||||
"tokio",
|
||||
"tokio 0.3.5",
|
||||
"tokio-compat-02",
|
||||
"tokio-tar",
|
||||
]
|
||||
|
||||
@@ -105,9 +106,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.41"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b246867b8b3b6ae56035f1eb1ed557c1d8eae97f0d53696138a50fa0e3a3b8c0"
|
||||
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
@@ -172,6 +173,12 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.48.1"
|
||||
@@ -289,6 +296,12 @@ version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0dcbc35f504eb6fc275a6d20e4ebcda18cf50d40ba6fabff8c711fa16cb3b16"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.61"
|
||||
@@ -354,6 +367,25 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudabi"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
@@ -495,7 +527,8 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
[[package]]
|
||||
name = "emver"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Start9Labs/emver-rs.git#9007920a8e361669fb83b29dd8506b32eeb20180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7621f4df0519152a2ceb5f8cc0685e9fa6c8b7aec289e1afb37c66849cb71ffe"
|
||||
dependencies = [
|
||||
"either",
|
||||
"fp-core",
|
||||
@@ -579,7 +612,7 @@ checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -604,6 +637,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fp-core"
|
||||
version = "0.1.9"
|
||||
@@ -649,9 +692,9 @@ checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"
|
||||
checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -664,9 +707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"
|
||||
checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -674,9 +717,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"
|
||||
checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748"
|
||||
|
||||
[[package]]
|
||||
name = "futures-cpupool"
|
||||
@@ -690,9 +733,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"
|
||||
checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -701,15 +744,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"
|
||||
checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"
|
||||
checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"proc-macro2 1.0.24",
|
||||
@@ -719,24 +762,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"
|
||||
checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"
|
||||
checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"
|
||||
checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -828,7 +871,7 @@ version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -836,7 +879,7 @@ dependencies = [
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
@@ -879,7 +922,7 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
@@ -890,7 +933,7 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"http",
|
||||
]
|
||||
|
||||
@@ -921,7 +964,7 @@ version = "0.13.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f3afcfae8af5ad0576a31e768415edb627824129e8e5a29b8bfccb2f234e835"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -933,7 +976,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"pin-project 0.4.27",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
@@ -945,10 +988,10 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d979acc56dcb5b8dddba3917601745e877576475aa046df3226eabdecef78eed"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tokio-tls",
|
||||
]
|
||||
|
||||
@@ -973,6 +1016,15 @@ dependencies = [
|
||||
"hashbrown 0.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
@@ -1091,6 +1143,15 @@ version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
|
||||
dependencies = [
|
||||
"scopeguard 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
@@ -1164,26 +1225,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-named-pipes"
|
||||
version = "0.1.7"
|
||||
name = "mio"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
|
||||
checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mio",
|
||||
"miow 0.3.5",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-uds"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
|
||||
dependencies = [
|
||||
"iovec",
|
||||
"libc",
|
||||
"mio",
|
||||
"log",
|
||||
"miow 0.3.6",
|
||||
"ntapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1200,9 +1251,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e"
|
||||
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"winapi 0.3.9",
|
||||
@@ -1281,6 +1332,15 @@ dependencies = [
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
@@ -1348,6 +1408,32 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cloudabi 0.1.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.1.57",
|
||||
"smallvec",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peeking_take_while"
|
||||
version = "0.1.2"
|
||||
@@ -1449,6 +1535,12 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
@@ -1684,7 +1776,7 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||
dependencies = [
|
||||
"cloudabi",
|
||||
"cloudabi 0.0.3",
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.4.2",
|
||||
@@ -1726,6 +1818,15 @@ version = "0.1.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48b82c2a1e8eb6e1bfde608de2bcbebd4072aa32d056ea48a986990cd5ca0f5a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.3.5"
|
||||
@@ -1733,7 +1834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"rust-argon2",
|
||||
]
|
||||
|
||||
@@ -1775,12 +1876,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.10.8"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9eaa17ac5d7b838b7503d118fa16ad88f440498bf9ffe5424e621f93190d61e"
|
||||
checksum = "fb15d6255c792356a0f578d8a645c677904dc02e862bebe2ecc18e0c01b9a0ce"
|
||||
dependencies = [
|
||||
"base64 0.12.3",
|
||||
"bytes",
|
||||
"base64 0.13.0",
|
||||
"bytes 0.5.6",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
@@ -1796,15 +1897,16 @@ dependencies = [
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"pin-project-lite 0.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
"tokio-tls",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
@@ -1859,6 +1961,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "0.3.3"
|
||||
@@ -1896,9 +2004,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.117"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
|
||||
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1915,9 +2023,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.117"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"
|
||||
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
@@ -1946,14 +2054,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.6.1"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
|
||||
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2026,14 +2134,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.15"
|
||||
name = "smallvec"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
|
||||
checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -2103,7 +2217,7 @@ dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"rand 0.7.3",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"remove_dir_all",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
@@ -2144,7 +2258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.1.57",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -2169,18 +2283,34 @@ version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"iovec",
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"mio 0.6.22",
|
||||
"num_cpus",
|
||||
"pin-project-lite 0.1.11",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a12a3eb39ee2c231be64487f1fcbe726c8f2514876a55480a5ab8559fc374252"
|
||||
dependencies = [
|
||||
"autocfg 1.0.1",
|
||||
"bytes 0.6.0",
|
||||
"futures-core",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"mio-named-pipes",
|
||||
"mio-uds",
|
||||
"mio 0.7.6",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"parking_lot",
|
||||
"pin-project-lite 0.2.0",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"tokio-macros",
|
||||
@@ -2188,10 +2318,23 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "0.2.5"
|
||||
name = "tokio-compat-02"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
|
||||
checksum = "bb4cec419b8b6f06c32e74aae6d8c5e79646d038a38e5ea2b36045f2c3296e22"
|
||||
dependencies = [
|
||||
"bytes 0.5.6",
|
||||
"once_cell",
|
||||
"pin-project-lite 0.1.11",
|
||||
"tokio 0.2.22",
|
||||
"tokio 0.3.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21d30fdbb5dc2d8f91049691aa1a9d4d4ae422a21c334ce8936e5886d30c5c45"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
@@ -2200,15 +2343,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tar"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a9e415c93375be93253134543229563114a2be8d46440d6d8f25b2ec62a7fb"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/dr-bonez/tokio-tar.git#1ba710f344ddb2a5b4d98bb96c905195c3cd9d43"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"tokio",
|
||||
"redox_syscall 0.2.1",
|
||||
"tokio 0.3.5",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
@@ -2219,7 +2361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio 0.2.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2228,12 +2370,12 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"bytes 0.5.6",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"pin-project-lite 0.1.11",
|
||||
"tokio 0.2.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2250,7 +2392,7 @@ checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"pin-project-lite 0.1.11",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
@@ -2338,10 +2480,11 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.1.1"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
|
||||
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
@@ -2471,6 +2614,30 @@ version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34d1cdc8b98a557f24733d50a1199c4b0635e465eecba9c45b214544da197f64"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"scoped-tls",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-bindgen-test-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8fb9c67be7439ee8ab1b7db502a49c05e51e2835b66796c705134d9b8e1a585"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.45"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "appmgr"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
authors = ["Aiden McClelland <me@drbonez.dev>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -18,15 +18,15 @@ portable = []
|
||||
production = []
|
||||
|
||||
[dependencies]
|
||||
emver = { git = "https://github.com/Start9Labs/emver-rs.git", version = "0.1.0", features = ["serde"] }
|
||||
emver = { version = "0.1.0", features = ["serde"] }
|
||||
argonautica = "0.2.0"
|
||||
async-trait = "0.1.41"
|
||||
async-trait = "0.1.42"
|
||||
base32 = "0.4.0"
|
||||
clap = "2.33"
|
||||
ed25519-dalek = "1.0.1"
|
||||
failure = "0.1.8"
|
||||
file-lock = "1.1"
|
||||
futures = "0.3.7"
|
||||
futures = "0.3.8"
|
||||
git-version = "0.3.4"
|
||||
itertools = "0.9.0"
|
||||
lazy_static = "1.4"
|
||||
@@ -38,12 +38,13 @@ pest_derive = "2.1"
|
||||
prettytable-rs = "0.8.0"
|
||||
rand = "0.7.3"
|
||||
regex = "1.4.2"
|
||||
reqwest = { version = "0.10.8", features = ["stream", "json"] }
|
||||
reqwest = { version = "0.10.9", features = ["stream", "json"] }
|
||||
rpassword = "5.0.0"
|
||||
serde = { version = "1.0.117", features = ["derive", "rc"] }
|
||||
serde = { version = "1.0.118", features = ["derive", "rc"] }
|
||||
serde_yaml = "0.8.14"
|
||||
serde_cbor = "0.11.1"
|
||||
serde_json = "1.0.59"
|
||||
simple-logging = "2.0"
|
||||
tokio = { version = "0.2.22", features = ["full"] }
|
||||
tokio-tar = "0.2.0"
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
tokio-compat-02 = "0.1.2"
|
||||
tokio-tar = { version = "0.3.0", git = "https://github.com/dr-bonez/tokio-tar.git" }
|
||||
|
||||
@@ -131,7 +131,7 @@ pub async fn remove(id: &str) -> Result<(), failure::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn status(id: &str) -> Result<AppStatus, Error> {
|
||||
pub async fn status(id: &str, remap_crashed: bool) -> Result<AppStatus, Error> {
|
||||
let output = std::process::Command::new("docker")
|
||||
.args(&["inspect", id, "--format", "{{.State.Status}}"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
@@ -155,6 +155,19 @@ pub async fn status(id: &str) -> Result<AppStatus, Error> {
|
||||
"restarting" => DockerStatus::Restarting,
|
||||
"removing" => DockerStatus::Removing,
|
||||
"dead" => DockerStatus::Dead,
|
||||
"exited"
|
||||
if remap_crashed && {
|
||||
let path = PersistencePath::from_ref("running.yaml");
|
||||
if let Some(mut f) = path.maybe_read(false).await.transpose()? {
|
||||
let running: Vec<String> = from_yaml_async_reader(&mut *f).await?;
|
||||
running.iter().filter(|a| a.as_str() == id).next().is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} =>
|
||||
{
|
||||
DockerStatus::Restarting
|
||||
}
|
||||
"created" | "exited" => DockerStatus::Stopped,
|
||||
"paused" => DockerStatus::Paused,
|
||||
_ => Err(format_err!("unknown status: {}", status))?,
|
||||
@@ -275,7 +288,7 @@ pub async fn info_full(
|
||||
Ok(AppInfoFull {
|
||||
info: info(id).await?,
|
||||
status: if with_status {
|
||||
Some(status(id).await?)
|
||||
Some(status(id, true).await?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
@@ -379,8 +392,12 @@ pub async fn list(
|
||||
let info = list_info().await?;
|
||||
futures::future::join_all(info.into_iter().map(move |(id, info)| async move {
|
||||
let (status, manifest, config, dependencies) = futures::try_join!(
|
||||
OptionFuture::from(if with_status { Some(status(&id)) } else { None })
|
||||
.map(Option::transpose),
|
||||
OptionFuture::from(if with_status {
|
||||
Some(status(&id, true))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.map(Option::transpose),
|
||||
OptionFuture::from(if with_manifest {
|
||||
Some(manifest(&id))
|
||||
} else {
|
||||
|
||||
@@ -56,7 +56,7 @@ pub async fn create_backup<P: AsRef<Path>>(
|
||||
f.flush().await?;
|
||||
}
|
||||
|
||||
let status = crate::apps::status(app_id).await?;
|
||||
let status = crate::apps::status(app_id, false).await?;
|
||||
let exclude = if volume_path.is_dir() {
|
||||
let ignore_path = volume_path.join(".backupignore");
|
||||
if ignore_path.is_file() {
|
||||
@@ -148,7 +148,7 @@ pub async fn restore_backup<P: AsRef<Path>>(
|
||||
);
|
||||
}
|
||||
|
||||
let status = crate::apps::status(app_id).await?;
|
||||
let status = crate::apps::status(app_id, false).await?;
|
||||
let running = status.status == crate::apps::DockerStatus::Running;
|
||||
if running {
|
||||
crate::control::stop_app(app_id, true, false).await?;
|
||||
|
||||
@@ -137,7 +137,9 @@ pub async fn configure(
|
||||
&mut res.stopped,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(&dependent).await?.status != crate::apps::DockerStatus::Stopped {
|
||||
if crate::apps::status(&dependent, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::control::stop_app(&dependent, false, dry_run).await?;
|
||||
res.stopped.insert(
|
||||
// TODO: maybe don't do this if its not running
|
||||
@@ -283,7 +285,8 @@ pub async fn configure(
|
||||
crate::apps::set_configured(name, true).await?;
|
||||
crate::apps::set_recoverable(name, false).await?;
|
||||
}
|
||||
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped {
|
||||
if crate::apps::status(name, false).await?.status != crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
if !dry_run {
|
||||
crate::apps::set_needs_restart(name, true).await?;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::path::Path;
|
||||
|
||||
use futures::future::{BoxFuture, FutureExt};
|
||||
use linear_map::LinearMap;
|
||||
use linear_map::{set::LinearSet, LinearMap};
|
||||
|
||||
use crate::dependencies::{DependencyError, TaggedDependencyError};
|
||||
use crate::util::{from_yaml_async_reader, PersistencePath, YamlUpdateHandle};
|
||||
use crate::Error;
|
||||
|
||||
pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
@@ -19,7 +20,7 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
let status = crate::apps::status(name).await?.status;
|
||||
let status = crate::apps::status(name, false).await?.status;
|
||||
if status == crate::apps::DockerStatus::Stopped {
|
||||
if update_metadata {
|
||||
crate::config::configure(name, None, None, false).await?;
|
||||
@@ -27,6 +28,10 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
crate::dependencies::update_binds(name).await?;
|
||||
}
|
||||
crate::apps::set_needs_restart(name, false).await?;
|
||||
let mut running = YamlUpdateHandle::<LinearSet<String>>::new_or_default(
|
||||
PersistencePath::from_ref("running.yaml"),
|
||||
)
|
||||
.await?;
|
||||
let output = tokio::process::Command::new("docker")
|
||||
.args(&["start", name])
|
||||
.stdout(std::process::Stdio::null())
|
||||
@@ -38,6 +43,8 @@ pub async fn start_app(name: &str, update_metadata: bool) -> Result<(), Error> {
|
||||
"Failed to Start Application: {}",
|
||||
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error")
|
||||
);
|
||||
running.insert(name.to_owned());
|
||||
running.commit().await?;
|
||||
} else if status == crate::apps::DockerStatus::Paused {
|
||||
resume_app(name).await?;
|
||||
}
|
||||
@@ -67,6 +74,10 @@ pub async fn stop_app(
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
let mut running = YamlUpdateHandle::<LinearSet<String>>::new_or_default(
|
||||
PersistencePath::from_ref("running.yaml"),
|
||||
)
|
||||
.await?;
|
||||
log::info!("Stopping {}", name);
|
||||
let output = tokio::process::Command::new("docker")
|
||||
.args(&["stop", "-t", "25", name])
|
||||
@@ -79,6 +90,8 @@ pub async fn stop_app(
|
||||
"Failed to Stop Application: {}",
|
||||
std::str::from_utf8(&output.stderr).unwrap_or("Unknown Error")
|
||||
);
|
||||
running.remove(name);
|
||||
running.commit().await?;
|
||||
crate::util::unlock(lock).await?;
|
||||
}
|
||||
Ok(res)
|
||||
@@ -98,7 +111,7 @@ pub async fn stop_dependents(
|
||||
) -> BoxFuture<'a, Result<(), Error>> {
|
||||
async move {
|
||||
for dependent in crate::apps::dependents(name, false).await? {
|
||||
if crate::apps::status(&dependent).await?.status
|
||||
if crate::apps::status(&dependent, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
stop_dependents_rec(&dependent, dry_run, DependencyError::NotRunning, res)
|
||||
@@ -192,3 +205,34 @@ pub async fn resume_app(name: &str) -> Result<(), Error> {
|
||||
crate::util::unlock(lock).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn repair_app_status() -> Result<(), Error> {
|
||||
let mut running_file = PersistencePath::from_ref("running.yaml")
|
||||
.maybe_read(false)
|
||||
.await
|
||||
.transpose()?;
|
||||
let running: Vec<String> = if let Some(f) = running_file.as_mut() {
|
||||
from_yaml_async_reader::<_, &mut tokio::fs::File>(f).await?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
for name in running {
|
||||
let lock = crate::util::lock_file(
|
||||
format!(
|
||||
"{}",
|
||||
Path::new(crate::PERSISTENCE_DIR)
|
||||
.join("apps")
|
||||
.join(&name)
|
||||
.join("control.lock")
|
||||
.display()
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(&name, false).await?.status == crate::apps::DockerStatus::Stopped {
|
||||
start_app(&name, true).await?;
|
||||
}
|
||||
crate::util::unlock(lock).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -115,7 +115,9 @@ impl DepInfo {
|
||||
if !errors.is_empty() {
|
||||
return Ok(Err(DependencyError::ConfigUnsatisfied(errors)));
|
||||
}
|
||||
if crate::apps::status(dependency_id).await?.status != crate::apps::DockerStatus::Running {
|
||||
if crate::apps::status(dependency_id, false).await?.status
|
||||
!= crate::apps::DockerStatus::Running
|
||||
{
|
||||
return Ok(Err(DependencyError::NotRunning));
|
||||
}
|
||||
Ok(Ok(()))
|
||||
|
||||
@@ -14,8 +14,9 @@ use std::time::Duration;
|
||||
use failure::ResultExt as _;
|
||||
use futures::stream::StreamExt;
|
||||
use futures::stream::TryStreamExt;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncRead, ReadBuf};
|
||||
use tokio_compat_02::FutureExt;
|
||||
use tokio_tar as tar;
|
||||
|
||||
use crate::config::{ConfigRuleEntry, ConfigSpec};
|
||||
@@ -62,13 +63,13 @@ where
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> Poll<std::io::Result<usize>> {
|
||||
buf: &mut ReadBuf,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
let atomic = self.as_ref().1.clone(); // TODO: not efficient
|
||||
match unsafe { self.map_unchecked_mut(|a| &mut a.0) }.poll_read(cx, buf) {
|
||||
Poll::Ready(Ok(res)) => {
|
||||
atomic.fetch_add(res as u64, atomic::Ordering::SeqCst);
|
||||
Poll::Ready(Ok(res))
|
||||
Poll::Ready(Ok(())) => {
|
||||
atomic.fetch_add(buf.filled().len() as u64, atomic::Ordering::SeqCst);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
a => a,
|
||||
}
|
||||
@@ -98,6 +99,7 @@ pub async fn download(url: &str, name: Option<&str>) -> Result<PathBuf, crate::E
|
||||
let url = reqwest::Url::parse(url).no_code()?;
|
||||
log::info!("Downloading {}.", url.as_str());
|
||||
let response = reqwest::get(url)
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -141,7 +143,7 @@ pub async fn download(url: &str, name: Option<&str>) -> Result<PathBuf, crate::E
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
tokio::time::delay_for(Duration::from_millis(10)).await;
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
if !*crate::QUIET.read().await {
|
||||
println!("\rDownloading... 100%");
|
||||
@@ -191,7 +193,7 @@ pub async fn install_path<P: AsRef<Path>>(p: P, name: Option<&str>) -> Result<()
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
tokio::time::delay_for(Duration::from_millis(10)).await;
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
if !*crate::QUIET.read().await {
|
||||
println!("\rInstalling... 100%");
|
||||
@@ -407,11 +409,13 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
|
||||
.arg("stop")
|
||||
.arg(&manifest.id)
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
tokio::process::Command::new("docker")
|
||||
.arg("rm")
|
||||
.arg(&manifest.id)
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
crate::ensure_code!(
|
||||
tokio::process::Command::new("docker")
|
||||
@@ -457,7 +461,7 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
|
||||
child_in.shutdown().await?;
|
||||
drop(child_in);
|
||||
crate::ensure_code!(
|
||||
child.await?.success(),
|
||||
child.wait().await?.success(),
|
||||
crate::error::DOCKER_ERROR,
|
||||
"Failed to Load Docker Image From Tar"
|
||||
);
|
||||
@@ -554,7 +558,8 @@ pub async fn install_v0<R: AsyncRead + Unpin + Send + Sync>(
|
||||
if dep_info.mount_shared
|
||||
&& crate::apps::list_info().await?.get(&dep_id).is_some()
|
||||
&& crate::apps::manifest(&dep_id).await?.shared.is_some()
|
||||
&& crate::apps::status(&dep_id).await?.status != crate::apps::DockerStatus::Stopped
|
||||
&& crate::apps::status(&dep_id, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::apps::set_needs_restart(&dep_id, true).await?;
|
||||
}
|
||||
|
||||
@@ -817,6 +817,9 @@ async fn inner_main() -> Result<(), Error> {
|
||||
.help("Password to use for encryption of backup file"),
|
||||
),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("repair-app-status").about("Restarts crashed apps"), // TODO: remove
|
||||
);
|
||||
|
||||
let matches = app.clone().get_matches();
|
||||
@@ -1540,6 +1543,10 @@ async fn inner_main() -> Result<(), Error> {
|
||||
std::process::exit(1);
|
||||
}
|
||||
},
|
||||
#[cfg(not(feature = "portable"))]
|
||||
("repair-app-status", _) => {
|
||||
control::repair_app_status().await?;
|
||||
}
|
||||
("pack", Some(sub_m)) => {
|
||||
pack(
|
||||
sub_m.value_of("PATH").unwrap(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use emver::VersionRange;
|
||||
use tokio_compat_02::FutureExt;
|
||||
|
||||
use crate::apps::AppConfig;
|
||||
use crate::manifest::ManifestLatest;
|
||||
@@ -12,6 +13,7 @@ pub async fn manifest(id: &str, version: &VersionRange) -> Result<ManifestLatest
|
||||
id,
|
||||
version
|
||||
))
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -34,6 +36,7 @@ pub async fn version(id: &str, version: &VersionRange) -> Result<emver::Version,
|
||||
id,
|
||||
version
|
||||
))
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -51,6 +54,7 @@ pub async fn config(id: &str, version: &VersionRange) -> Result<AppConfig, Error
|
||||
id,
|
||||
version
|
||||
))
|
||||
.compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
|
||||
@@ -198,7 +198,7 @@ pub async fn read_tor_address(name: &str, timeout: Option<Duration>) -> Result<S
|
||||
}
|
||||
}
|
||||
} {
|
||||
tokio::time::delay_for(Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
let tor_addr = match tokio::fs::read_to_string(&addr_path).await {
|
||||
@@ -238,7 +238,7 @@ pub async fn read_tor_key(
|
||||
}
|
||||
}
|
||||
} {
|
||||
tokio::time::delay_for(Duration::from_millis(100)).await;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
let tor_key = match version {
|
||||
|
||||
@@ -19,7 +19,9 @@ pub async fn update(
|
||||
let version = crate::registry::version(name, &version_req).await?;
|
||||
let mut res = LinearMap::new();
|
||||
for dependent in crate::apps::dependents(name, false).await? {
|
||||
if crate::apps::status(&dependent).await?.status != crate::apps::DockerStatus::Stopped {
|
||||
if crate::apps::status(&dependent, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
let manifest = crate::apps::manifest(&dependent).await?;
|
||||
match manifest.dependencies.0.get(name) {
|
||||
Some(dep) if !version.satisfies(&dep.version) => {
|
||||
@@ -30,7 +32,8 @@ pub async fn update(
|
||||
&mut res,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped
|
||||
if crate::apps::status(name, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::control::stop_app(&dependent, false, dry_run).await?;
|
||||
res.insert(
|
||||
@@ -53,7 +56,8 @@ pub async fn update(
|
||||
&mut res,
|
||||
)
|
||||
.await?;
|
||||
if crate::apps::status(name).await?.status != crate::apps::DockerStatus::Stopped
|
||||
if crate::apps::status(name, false).await?.status
|
||||
!= crate::apps::DockerStatus::Stopped
|
||||
{
|
||||
crate::control::stop_app(&dependent, false, dry_run).await?;
|
||||
res.insert(
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
use failure::ResultExt as _;
|
||||
use file_lock::FileLock;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
|
||||
|
||||
use crate::Error;
|
||||
use crate::ResultExt as _;
|
||||
@@ -244,8 +244,8 @@ impl tokio::io::AsyncRead for UpdateHandle<ForRead> {
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
buf: &mut ReadBuf,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
unsafe { self.map_unchecked_mut(|a| a.file.file.as_mut().unwrap()) }.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
@@ -371,7 +371,13 @@ where
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
tokio::io::AsyncRead::poll_read(unsafe { self.map_unchecked_mut(|a| &mut a.0) }, cx, buf)
|
||||
let mut read_buf = ReadBuf::new(buf);
|
||||
tokio::io::AsyncRead::poll_read(
|
||||
unsafe { self.map_unchecked_mut(|a| &mut a.0) },
|
||||
cx,
|
||||
&mut read_buf,
|
||||
)
|
||||
.map(|res| res.map(|_| read_buf.filled().len()))
|
||||
}
|
||||
}
|
||||
impl<T> tokio::io::AsyncRead for AsyncCompat<T>
|
||||
@@ -381,9 +387,14 @@ where
|
||||
fn poll_read(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut [u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
futures::io::AsyncRead::poll_read(unsafe { self.map_unchecked_mut(|a| &mut a.0) }, cx, buf)
|
||||
buf: &mut ReadBuf,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
futures::io::AsyncRead::poll_read(
|
||||
unsafe { self.map_unchecked_mut(|a| &mut a.0) },
|
||||
cx,
|
||||
buf.initialize_unfilled(),
|
||||
)
|
||||
.map(|res| res.map(|len| buf.set_filled(len)))
|
||||
}
|
||||
}
|
||||
impl<T> futures::io::AsyncWrite for AsyncCompat<T>
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::cmp::Ordering;
|
||||
use async_trait::async_trait;
|
||||
use failure::ResultExt as _;
|
||||
use futures::stream::TryStreamExt;
|
||||
use tokio_compat_02::FutureExt;
|
||||
|
||||
use crate::util::{to_yaml_async_writer, AsyncCompat, PersistencePath};
|
||||
use crate::Error;
|
||||
@@ -21,8 +22,9 @@ mod v0_2_3;
|
||||
mod v0_2_4;
|
||||
mod v0_2_5;
|
||||
mod v0_2_6;
|
||||
mod v0_2_7;
|
||||
|
||||
pub use v0_2_6::Version as Current;
|
||||
pub use v0_2_7::Version as Current;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
@@ -41,6 +43,7 @@ enum Version {
|
||||
V0_2_4(Wrapper<v0_2_4::Version>),
|
||||
V0_2_5(Wrapper<v0_2_5::Version>),
|
||||
V0_2_6(Wrapper<v0_2_6::Version>),
|
||||
V0_2_7(Wrapper<v0_2_7::Version>),
|
||||
Other(emver::Version),
|
||||
}
|
||||
|
||||
@@ -149,6 +152,7 @@ pub async fn init() -> Result<(), failure::Error> {
|
||||
Version::V0_2_4(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_5(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_6(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::V0_2_7(v) => v.0.migrate_to(&Current::new()).await?,
|
||||
Version::Other(_) => (),
|
||||
// TODO find some way to automate this?
|
||||
}
|
||||
@@ -165,7 +169,7 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
|
||||
.collect();
|
||||
let url = format!("{}/appmgr?spec={}", &*crate::SYS_REGISTRY_URL, req_str);
|
||||
log::info!("Fetching new version from {}", url);
|
||||
let response = reqwest::get(&url)
|
||||
let response = reqwest::get(&url).compat()
|
||||
.await
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
.error_for_status()
|
||||
@@ -235,6 +239,7 @@ pub async fn self_update(requirement: emver::VersionRange) -> Result<(), Error>
|
||||
Version::V0_2_4(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_5(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_6(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::V0_2_7(v) => Current::new().migrate_to(&v.0).await?,
|
||||
Version::Other(_) => (),
|
||||
// TODO find some way to automate this?
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ impl VersionT for Version {
|
||||
tokio::io::copy(
|
||||
&mut AsyncCompat(
|
||||
reqwest::get(&format!("{}/torrc?spec==0.0.0", &*crate::SYS_REGISTRY_URL))
|
||||
.compat()
|
||||
.await
|
||||
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
|
||||
@@ -22,6 +22,7 @@ impl VersionT for Version {
|
||||
tokio::io::copy(
|
||||
&mut AsyncCompat(
|
||||
reqwest::get(&format!("{}/torrc?spec==0.1.1", &*crate::SYS_REGISTRY_URL))
|
||||
.compat()
|
||||
.await
|
||||
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
|
||||
.with_code(crate::error::NETWORK_ERROR)?
|
||||
@@ -76,6 +77,7 @@ impl VersionT for Version {
|
||||
tokio::io::copy(
|
||||
&mut AsyncCompat(
|
||||
reqwest::get(&format!("{}/torrc?spec==0.1.0", &*crate::SYS_REGISTRY_URL))
|
||||
.compat()
|
||||
.await
|
||||
.with_context(|e| format!("GET {}/torrc: {}", &*crate::SYS_REGISTRY_URL, e))
|
||||
.no_code()?
|
||||
|
||||
36
appmgr/src/version/v0_2_7.rs
Normal file
36
appmgr/src/version/v0_2_7.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,10 @@
|
||||
|
||||
`npm i -g @ionic/cli`
|
||||
|
||||
`git clone https://github.com/Start9Labs/embassy-ui.git`
|
||||
`git clone https://github.com/Start9Labs/embassy-os.git`
|
||||
|
||||
`cd embassy-ui`
|
||||
`cd embassy-os/ui`
|
||||
|
||||
`npm i`
|
||||
|
||||
`ionic serve`
|
||||
|
||||
## Production Deployment
|
||||
|
||||
`ionic build --prod`
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
manifest-version: 0
|
||||
app-id: start9-ambassador
|
||||
app-version: 0.2.6
|
||||
app-version: 0.2.7
|
||||
uri-rewrites:
|
||||
- =/api -> http://{{start9-ambassador}}:5959/authenticate
|
||||
- /api/ -> http://{{start9-ambassador}}:5959/
|
||||
|
||||
1955
ui/package-lock.json
generated
1955
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "embassy-ui",
|
||||
"version": "0.2.6",
|
||||
"version": "0.2.7",
|
||||
"description": "GUI for EmbassyOS",
|
||||
"author": "Start9 Labs",
|
||||
"homepage": "https://github.com/Start9Labs/embassy-ui",
|
||||
@@ -15,12 +15,12 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^10.1.6",
|
||||
"@angular/core": "^10.1.6",
|
||||
"@angular/forms": "^10.1.6",
|
||||
"@angular/platform-browser": "^10.1.6",
|
||||
"@angular/platform-browser-dynamic": "^10.1.6",
|
||||
"@angular/router": "^10.1.6",
|
||||
"@angular/common": "^11.0.0",
|
||||
"@angular/core": "^11.0.0",
|
||||
"@angular/forms": "^11.0.0",
|
||||
"@angular/platform-browser": "^11.0.0",
|
||||
"@angular/platform-browser-dynamic": "^11.0.0",
|
||||
"@angular/router": "^11.0.0",
|
||||
"@ionic/angular": "^5.4.0",
|
||||
"@ionic/storage": "2.2.0",
|
||||
"@start9labs/emver": "^0.1.1",
|
||||
@@ -42,19 +42,19 @@
|
||||
"zone.js": "^0.11.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.1002.0",
|
||||
"@angular/cli": "^10.1.7",
|
||||
"@angular/compiler": "^10.1.6",
|
||||
"@angular/compiler-cli": "^10.1.6",
|
||||
"@angular/language-service": "^10.1.6",
|
||||
"@ionic/angular-toolkit": "^2.3.3",
|
||||
"@angular-devkit/build-angular": "^0.1100.0",
|
||||
"@angular/cli": "^11.0.0",
|
||||
"@angular/compiler": "^11.0.0",
|
||||
"@angular/compiler-cli": "^11.0.0",
|
||||
"@angular/language-service": "^11.0.0",
|
||||
"@ionic/angular-toolkit": "^3.0.0",
|
||||
"@ionic/lab": "^3.2.9",
|
||||
"@types/json-pointer": "^1.0.30",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/node": "^14.11.10",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"node-html-parser": "^1.3.1",
|
||||
"ts-node": "^9.0.0",
|
||||
"node-html-parser": "^2.0.0",
|
||||
"ts-node": "^9.1.0",
|
||||
"tslint": "^6.1.0",
|
||||
"typescript": "4.0.5"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType
|
||||
ValueSpec, ConfigSpec, UniqueBy, ValueSpecOf, ValueType, ValueSpecObject, ValueSpecUnion
|
||||
} from './config-types'
|
||||
import * as pointer from 'json-pointer'
|
||||
import * as handlebars from 'handlebars'
|
||||
@@ -207,7 +207,8 @@ export class ConfigCursor<T extends ValueType> {
|
||||
}
|
||||
case 'enum':
|
||||
if (typeof cfg === 'string') {
|
||||
return spec.values.includes(cfg) ? null : `${cfg} is not a valid selection.`
|
||||
spec.valuesSet = spec.valuesSet || new Set(spec.values)
|
||||
return spec.valuesSet.has(cfg) ? null : `${cfg} is not a valid selection.`
|
||||
} else {
|
||||
throw new TypeError(`${this.ptr}: expected string, got ${Array.isArray(cfg) ? 'array' : typeof cfg}`)
|
||||
}
|
||||
@@ -223,14 +224,22 @@ export class ConfigCursor<T extends ValueType> {
|
||||
if (max && length > max) {
|
||||
return spec.subtype === 'enum' ? 'Too many options selected.' : 'List is too long.'
|
||||
}
|
||||
for (let idx in cfg) {
|
||||
for (let idx = 0; idx < cfg.length; idx++) {
|
||||
let cursor = this.seekNext(idx)
|
||||
if (cursor.checkInvalid()) {
|
||||
return `Item #${idx + 1} is invalid. ${cursor.checkInvalid()}`
|
||||
const invalid = cursor.checkInvalid()
|
||||
if (invalid) {
|
||||
return `Item #${idx + 1} is invalid. ${invalid}.`
|
||||
}
|
||||
for (let idx2 in cfg) {
|
||||
if (idx !== idx2 && cursor.equals(this.seekNext(idx2))) {
|
||||
return `Item #${idx + 1} is not unique.`
|
||||
if (spec.subtype === 'enum') continue
|
||||
for (let idx2 = idx + 1; idx2 < cfg.length; idx2++) {
|
||||
if (cursor.equals(this.seekNext(idx2))) {
|
||||
return `Item #${idx + 1} is not unique.` + ('uniqueBy' in cursor.spec()) ? `${
|
||||
displayUniqueBy(
|
||||
(cursor.spec() as ValueSpecObject | ValueSpecUnion).uniqueBy,
|
||||
(cursor.spec() as ValueSpecObject | ValueSpecUnion),
|
||||
cursor.config()
|
||||
)
|
||||
} must be unique.` : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,4 +452,34 @@ function isEqual (uniqueBy: UniqueBy, lhs: ConfigCursor<'object'>, rhs: ConfigCu
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export function displayUniqueBy(uniqueBy: UniqueBy, spec: ValueSpecObject | ValueSpecUnion, value: object): string {
|
||||
if (typeof uniqueBy === 'string') {
|
||||
if (spec.type === 'object') {
|
||||
return spec.spec[uniqueBy].name
|
||||
} else if (spec.type === 'union') {
|
||||
if (uniqueBy === spec.tag.id) {
|
||||
return spec.tag.name
|
||||
} else {
|
||||
return spec.variants[value[spec.tag.id]][uniqueBy].name
|
||||
}
|
||||
}
|
||||
} else if ('any' in uniqueBy) {
|
||||
return uniqueBy.any.map(uq => {
|
||||
if (typeof uq === 'object' && 'all' in uq) {
|
||||
return `(${displayUniqueBy(uq, spec, value)})`
|
||||
} else {
|
||||
return displayUniqueBy(uq, spec, value)
|
||||
}
|
||||
}).join(' and ')
|
||||
} else if ('all' in uniqueBy) {
|
||||
return uniqueBy.all.map(uq => {
|
||||
if (typeof uq === 'object' && 'any' in uq) {
|
||||
return `(${displayUniqueBy(uq, spec, value)})`
|
||||
} else {
|
||||
return displayUniqueBy(uq, spec, value)
|
||||
}
|
||||
}).join(' or ')
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,7 @@ export interface ListValueSpecNumber {
|
||||
|
||||
export interface ListValueSpecEnum {
|
||||
values: string[]
|
||||
valuesSet?: Set<string>
|
||||
valueNames: { [value: string]: string }
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ const routes: Routes = [
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
preloadingStrategy: PreloadAllModules,
|
||||
initialNavigation: false,
|
||||
initialNavigation: 'disabled',
|
||||
useHash: true,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -128,12 +128,10 @@
|
||||
<ion-infinite-scroll-content loadingSpinner="lines"></ion-infinite-scroll-content>
|
||||
</ion-content>
|
||||
<ion-input></ion-input>
|
||||
<ion-input type="password">getdots</ion-input>
|
||||
<ion-input type="password" value="getdots"></ion-input>
|
||||
<ion-item></ion-item>
|
||||
<ion-item-divider></ion-item-divider>
|
||||
<ion-item-group></ion-item-group>
|
||||
<ion-item-options></ion-item-options>
|
||||
<ion-item-sliding></ion-item-sliding>
|
||||
<ion-label></ion-label>
|
||||
<ion-list></ion-list>
|
||||
<ion-loading></ion-loading>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, Type } from '@angular/core'
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { RouteReuseStrategy } from '@angular/router'
|
||||
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'
|
||||
|
||||
@@ -46,21 +46,18 @@
|
||||
</ion-item-divider>
|
||||
|
||||
<div *ngFor="let v of value; index as i;">
|
||||
<ion-item-sliding>
|
||||
<ion-item button detail="false" (click)="presentModalValueEdit(i)">
|
||||
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'NoChange')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
|
||||
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'Added')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
|
||||
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Edited')" style="margin-right: 15px" color="primary" name="ellipse"></ion-icon>
|
||||
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Invalid')" style="margin-right: 15px" color="danger" name="warning-outline"></ion-icon>
|
||||
<ion-item button detail="false" (click)="presentModalValueEdit(i)">
|
||||
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'NoChange')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
|
||||
<ion-icon size="small" slot="start" *ngIf="!annotations.members[i] || (annotations.members[i] | annotationStatus: 'Added')" style="margin-right: 15px; color: rgba(0,0,0,0); background: radial-gradient(#2a4e8970, #2a4e8970 35%, transparent 35%, transparent);" name="ellipse"></ion-icon>
|
||||
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Edited')" style="margin-right: 15px" color="primary" name="ellipse"></ion-icon>
|
||||
<ion-icon size="small" slot="start" *ngIf="annotations.members[i] && (annotations.members[i] | annotationStatus: 'Invalid')" style="margin-right: 15px" color="danger" name="warning-outline"></ion-icon>
|
||||
|
||||
<ion-label>{{ valueString[i] }}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item-options side="end">
|
||||
<ion-item-option color="danger" (click)="presentAlertDeleteEntry(i)">
|
||||
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||
</ion-item-option>
|
||||
</ion-item-options>
|
||||
</ion-item-sliding>
|
||||
<ion-label>{{ valueString[i] }}</ion-label>
|
||||
|
||||
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(i, $event)">
|
||||
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-item-group>
|
||||
</div>
|
||||
|
||||
@@ -104,7 +104,9 @@ export class AppConfigListPage extends ModalPresentable {
|
||||
return this.presentModal(nextCursor, () => this.updateCaches())
|
||||
}
|
||||
|
||||
async presentAlertDeleteEntry (key: number) {
|
||||
async presentAlertDelete (key: number, e: Event) {
|
||||
e.stopPropagation()
|
||||
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Caution',
|
||||
|
||||
@@ -50,8 +50,9 @@ export class ModelPreload {
|
||||
}
|
||||
|
||||
loadInstalledApp (appId: string): Promise<PropertySubject<AppInstalledFull>> {
|
||||
const now = new Date()
|
||||
return this.api.getInstalledApp(appId).then(res => {
|
||||
this.appModel.update({ id: appId, ...res, hasFetchedFull: true })
|
||||
this.appModel.update({ id: appId, ...res, hasFetchedFull: true }, now)
|
||||
return this.appModel.watch(appId)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<ion-buttons slot="start">
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Marketplace Details</ion-title>
|
||||
<ion-title>Listing</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<ng-container *ngIf="!($loading$ | async)">
|
||||
<ion-item *ngIf="error" class="notifier-item">
|
||||
<ion-label style="margin: 7px 5px;" class="ion-text-wrap">
|
||||
<p style="color: var(--ion-color-danger)">{{error.text}}.</p>
|
||||
<p style="color: var(--ion-color-danger)">{{error.text}}</p>
|
||||
<p><a style="color: var(--ion-color-danger); text-decoration: underline; font-weight: bold;" *ngIf="error.moreInfo && !openErrorMoreInfo" (click)="openErrorMoreInfo = true">{{error.moreInfo.buttonText}}</a></p>
|
||||
|
||||
<!-- presentPopover(error.moreInfo.title, error.moreInfo.description, $event) -->
|
||||
@@ -94,21 +94,22 @@
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- save button, always show -->
|
||||
<ion-button
|
||||
[disabled]="invalid || (!edited && !added && !(['NEEDS_CONFIG'] | includes: (app.status | async)))"
|
||||
fill="outline"
|
||||
expand="block"
|
||||
style="margin: 10px"
|
||||
color="primary"
|
||||
(click)="save()"
|
||||
>
|
||||
<ion-text color="primary" style="font-weight: bold">
|
||||
Save
|
||||
</ion-text>
|
||||
</ion-button>
|
||||
|
||||
<!-- has config -->
|
||||
<ng-container *ngIf="hasConfig">
|
||||
<ion-button
|
||||
[disabled]="invalid || (!edited && !added && !(['NEEDS_CONFIG'] | includes: (app.status | async)))"
|
||||
fill="outline"
|
||||
expand="block"
|
||||
style="margin: 10px"
|
||||
color="primary"
|
||||
(click)="save()"
|
||||
>
|
||||
<ion-text color="primary" style="font-weight: bold">
|
||||
Save
|
||||
</ion-text>
|
||||
</ion-button>
|
||||
|
||||
<ion-item-group class="ion-text-wrap ion-padding-bottom">
|
||||
<ion-item-divider>Config Options</ion-item-divider>
|
||||
<object-config [cursor]="rootCursor" (onEdit)="handleObjectEdit()"></object-config>
|
||||
|
||||
@@ -205,7 +205,7 @@ export class AppConfigPage extends Cleanup {
|
||||
}
|
||||
|
||||
return this.apiService.patchAppConfig(app, config).then(
|
||||
() => this.preload.loadInstalledApp(this.appId).then(() => ({ skip: false})),
|
||||
() => this.preload.loadInstalledApp(this.appId).then(() => ({ skip: false })),
|
||||
)
|
||||
})
|
||||
.then(({ skip }) => {
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
<!-- marketplace -->
|
||||
<ion-item lines="none" [routerLink]="['/services', 'marketplace', appId]">
|
||||
<ion-icon slot="start" name="cart-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">Marketplace Details</ion-text></ion-label>
|
||||
<ion-label><ion-text color="primary">Marketplace Listing</ion-text></ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- dependencies -->
|
||||
|
||||
@@ -22,6 +22,13 @@
|
||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<!-- not running -->
|
||||
<ion-item *ngIf="app.status !== 'RUNNING'" class="ion-margin-bottom">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p><ion-text color="warning">{{ app.title }} is not running. Information on this page could be innacurate.</ion-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- no metrics -->
|
||||
<ion-item *ngIf="($hasMetrics$ | async) === false">
|
||||
<ion-label class="ion-text-wrap">
|
||||
@@ -29,7 +36,8 @@
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-group>
|
||||
<!-- metrics -->
|
||||
<ion-item-group *ngIf="($hasMetrics$ | async) === true">
|
||||
<div *ngFor="let keyval of $metrics$ | async | keyvalue: asIsOrder">
|
||||
<!-- object -->
|
||||
<ion-item button detail="false" *ngIf="keyval.value.type === 'object'" (click)="goToNested(keyval.key)">
|
||||
|
||||
@@ -33,28 +33,24 @@
|
||||
</ion-item-group>
|
||||
|
||||
<ion-item-group style="margin-bottom: 16px;">
|
||||
<ion-item-sliding *ngFor="let not of notifications; let i = index">
|
||||
<ion-item-options side="end">
|
||||
<ion-item-option color="danger" (click)="remove(not.id, i)">
|
||||
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||
</ion-item-option>
|
||||
</ion-item-options>
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>
|
||||
<ion-text [color]="getColor(not)"><b>{{ not.title }}</b></ion-text>
|
||||
</h2>
|
||||
<h2 class="notification-message">{{ not.message }}</h2>
|
||||
<p>{{ not.createdAt | date: 'short' }}</p>
|
||||
<p>
|
||||
<a style="text-decoration: none;"
|
||||
[routerLink]="['/services', 'installed', not.appId]">{{ not.appId }}</a>
|
||||
<span> - </span>
|
||||
Code: {{ not.code }}
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-sliding>
|
||||
<ion-item *ngFor="let not of notifications; let i = index">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2>
|
||||
<ion-text [color]="getColor(not)"><b>{{ not.title }}</b></ion-text>
|
||||
</h2>
|
||||
<h2 class="notification-message">{{ not.message }}</h2>
|
||||
<p>{{ not.createdAt | date: 'short' }}</p>
|
||||
<p>
|
||||
<a style="text-decoration: none;"
|
||||
[routerLink]="['/services', 'installed', not.appId]">{{ not.appId }}</a>
|
||||
<span> - </span>
|
||||
Code: {{ not.code }}
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="remove(not.id, i)">
|
||||
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
<ion-infinite-scroll [disabled]="!needInfinite" (ionInfinite)="doInfinite($event)">
|
||||
|
||||
@@ -29,17 +29,14 @@
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>Saved Keys</ion-item-divider>
|
||||
<ion-item-sliding *ngFor="let fingerprint of server.ssh | async">
|
||||
<ion-item-options side="end">
|
||||
<ion-item-option color="danger" (click)="delete(fingerprint)">
|
||||
<ion-icon slot="icon-only" name="trash-outline"></ion-icon>
|
||||
</ion-item-option>
|
||||
</ion-item-options>
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">{{ fingerprint.alg }} {{ fingerprint.hash }} {{ fingerprint.hostname }}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-sliding>
|
||||
<ion-item *ngFor="let fingerprint of server.ssh | async">
|
||||
<ion-label class="ion-text-wrap">
|
||||
{{ fingerprint.alg }} {{ fingerprint.hash }} {{ fingerprint.hostname }}
|
||||
</ion-label>
|
||||
<ion-button slot="end" fill="clear" (click)="presentAlertDelete(fingerprint)">
|
||||
<ion-icon slot="icon-only" name="close-outline" color="medium"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
||||
|
||||
@@ -6,6 +6,7 @@ import { PropertySubject } from 'src/app/util/property-subject.util'
|
||||
import { ServerConfigService } from 'src/app/services/server-config.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { ModelPreload } from 'src/app/models/model-preload'
|
||||
import { AlertController } from '@ionic/angular'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-ssh-keys',
|
||||
@@ -21,6 +22,7 @@ export class DevSSHKeysPage {
|
||||
private readonly loader: LoaderService,
|
||||
private readonly preload: ModelPreload,
|
||||
private readonly serverConfigService: ServerConfigService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
@@ -41,6 +43,28 @@ export class DevSSHKeysPage {
|
||||
await this.serverConfigService.presentModalValueEdit('ssh', true)
|
||||
}
|
||||
|
||||
async presentAlertDelete (fingerprint: SSHFingerprint) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Caution',
|
||||
message: `Are you sure you want to delete this SSH key?`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Delete',
|
||||
cssClass: 'alert-danger',
|
||||
handler: () => {
|
||||
this.delete(fingerprint)
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
async delete (fingerprint: SSHFingerprint) {
|
||||
this.loader.of({
|
||||
message: 'Deleting...',
|
||||
|
||||
@@ -30,6 +30,13 @@
|
||||
<ion-item-group>
|
||||
<ion-item-divider></ion-item-divider>
|
||||
|
||||
<ion-item lines="none" button (click)="checkForUpdates()">
|
||||
<ion-icon slot="start" name="refresh-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text style="font-weight: bold;" color="primary">Check for Updates</ion-text></ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider></ion-item-divider>
|
||||
|
||||
<ion-item [routerLink]="['specs']">
|
||||
<ion-icon slot="start" name="information-circle-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">About</ion-text></ion-label>
|
||||
@@ -45,15 +52,6 @@
|
||||
<ion-label><ion-text color="primary">Config</ion-text></ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider></ion-item-divider>
|
||||
|
||||
<ion-item lines="none" button (click)="checkForUpdates()">
|
||||
<ion-icon slot="start" name="refresh-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text style="font-weight: bold;" color="primary">Check for Updates</ion-text></ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider></ion-item-divider>
|
||||
|
||||
<ion-item [routerLink]="['lan']">
|
||||
<ion-icon slot="start" name="home-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">Secure LAN Setup</ion-text></ion-label>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<ion-header>
|
||||
<ion-buttons slot="start">
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Add Network</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding-top">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NavController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { WifiService } from '../wifi.service'
|
||||
import { LoaderService } from 'src/app/services/loader.service'
|
||||
import { ServerModel } from 'src/app/models/server-model'
|
||||
|
||||
@Component({
|
||||
selector: 'wifi-add',
|
||||
@@ -21,9 +22,11 @@ export class WifiAddPage {
|
||||
private readonly apiService: ApiService,
|
||||
private readonly loader: LoaderService,
|
||||
private readonly wifiService: WifiService,
|
||||
private readonly serverModel: ServerModel,
|
||||
) { }
|
||||
|
||||
async add (): Promise<void> {
|
||||
this.error = ''
|
||||
this.loader.of({
|
||||
message: 'Saving...',
|
||||
spinner: 'lines',
|
||||
@@ -31,9 +34,6 @@ export class WifiAddPage {
|
||||
}).displayDuringAsync( async () => {
|
||||
await this.apiService.addWifi(this.ssid, this.password, this.countryCode, false)
|
||||
this.wifiService.addWifi(this.ssid)
|
||||
this.ssid = ''
|
||||
this.password = ''
|
||||
this.error = ''
|
||||
this.navCtrl.back()
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
@@ -42,19 +42,21 @@ export class WifiAddPage {
|
||||
}
|
||||
|
||||
async addAndConnect (): Promise<void> {
|
||||
this.error = ''
|
||||
this.loader.of({
|
||||
message: 'Connecting. This could take while...',
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync( async () => {
|
||||
const current = this.serverModel.peek().wifi.current
|
||||
await this.apiService.addWifi(this.ssid, this.password, this.countryCode, true)
|
||||
const success = await this.wifiService.confirmWifi(this.ssid)
|
||||
if (!success) { return }
|
||||
this.wifiService.addWifi(this.ssid)
|
||||
this.ssid = ''
|
||||
this.password = ''
|
||||
this.error = ''
|
||||
this.navCtrl.back()
|
||||
if (success) {
|
||||
this.navCtrl.back()
|
||||
this.wifiService.presentAlertSuccess(this.ssid, current)
|
||||
} else {
|
||||
this.wifiService.presentToastFail()
|
||||
}
|
||||
}).catch (e => {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
|
||||
@@ -23,14 +23,13 @@
|
||||
<ion-item-group>
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p>
|
||||
Add WiFi credentials to your Embassy so it can connect to the Internet without an ethernet cable.
|
||||
</p>
|
||||
<ion-text color="warning">Warning!</ion-text>
|
||||
<br />
|
||||
<br />
|
||||
<ion-text color="dark">WiFi is a beta feature with known issues. If you make changes to WiFi, your Embassy and its Services may become unreachable for upward of a few hours over Tor. Please use with caution and patience.</ion-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider class="borderless"></ion-item-divider>
|
||||
|
||||
<ion-item-divider>Saved Networks</ion-item-divider>
|
||||
<ion-item button detail="false" *ngFor="let ssid of (server.wifi | async)?.ssids" (click)="presentAction(ssid)">
|
||||
<ion-label>{{ ssid }}</ion-label>
|
||||
|
||||
@@ -71,14 +71,20 @@ export class WifiListPage {
|
||||
|
||||
// Let's add country code here.
|
||||
async connect (ssid: string): Promise<void> {
|
||||
this.error = ''
|
||||
this.loader.of({
|
||||
message: 'Connecting. This could take while...',
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
}).displayDuringAsync(async () => {
|
||||
const current = this.server.wifi.getValue().current
|
||||
await this.apiService.connectWifi(ssid)
|
||||
await this.wifiService.confirmWifi(ssid)
|
||||
this.error = ''
|
||||
const success = await this.wifiService.confirmWifi(ssid)
|
||||
if (success) {
|
||||
this.wifiService.presentAlertSuccess(ssid, current)
|
||||
} else {
|
||||
this.wifiService.presentToastFail()
|
||||
}
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.error = e.message
|
||||
@@ -86,6 +92,7 @@ export class WifiListPage {
|
||||
}
|
||||
|
||||
async delete (ssid: string): Promise<void> {
|
||||
this.error = ''
|
||||
this.loader.of({
|
||||
message: 'Deleting...',
|
||||
spinner: 'lines',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ToastController } from '@ionic/angular'
|
||||
import { AlertController, ToastController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { pauseFor } from 'src/app/util/misc.util'
|
||||
import { ServerModel } from 'src/app/models/server-model'
|
||||
@@ -12,6 +12,7 @@ export class WifiService {
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly serverModel: ServerModel,
|
||||
) { }
|
||||
|
||||
@@ -41,7 +42,7 @@ export class WifiService {
|
||||
} else {
|
||||
attempts++
|
||||
const diff = end - start
|
||||
await pauseFor(Math.max(0, timeout - diff))
|
||||
await pauseFor(Math.max(2000, timeout - diff))
|
||||
if (attempts === maxAttempts) {
|
||||
this.serverModel.update({ wifi: { current, ssids } })
|
||||
}
|
||||
@@ -52,29 +53,38 @@ export class WifiService {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.serverModel.peek().wifi.current === ssid) {
|
||||
return true
|
||||
} else {
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: 'Failed to connect:',
|
||||
message: `Check credentials and try again`,
|
||||
position: 'bottom',
|
||||
duration: 4000,
|
||||
buttons: [
|
||||
{
|
||||
side: 'start',
|
||||
icon: 'close',
|
||||
handler: () => {
|
||||
return true
|
||||
},
|
||||
return this.serverModel.peek().wifi.current === ssid
|
||||
}
|
||||
|
||||
async presentToastFail (): Promise<void> {
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: 'Failed to connect:',
|
||||
message: `Check credentials and try again`,
|
||||
position: 'bottom',
|
||||
duration: 4000,
|
||||
buttons: [
|
||||
{
|
||||
side: 'start',
|
||||
icon: 'close',
|
||||
handler: () => {
|
||||
return true
|
||||
},
|
||||
],
|
||||
cssClass: 'notification-toast',
|
||||
})
|
||||
},
|
||||
],
|
||||
cssClass: 'notification-toast',
|
||||
})
|
||||
|
||||
setTimeout(() => toast.present(), 300)
|
||||
await toast.present()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
async presentAlertSuccess (current: string, old?: string): Promise<void> {
|
||||
let message = 'Note. It may take a while for your Embassy to reconnect over Tor, upward of a few hours. Unplugging the device and plugging it back in may help, but it may also just need time. You may also need to hard refresh your browser cache.'
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: `Connected to "${current}"`,
|
||||
message: old ? message : 'You may now unplug your Embassy from Ethernet.<br /></br />' + message,
|
||||
buttons: ['OK'],
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ export interface ApiServer {
|
||||
versionInstalled: string
|
||||
alternativeRegistryUrl: string | null
|
||||
specs: ServerSpecs
|
||||
wifi: { ssids: string[]; current: string; }
|
||||
wifi: {
|
||||
ssids: string[]
|
||||
current: string | null
|
||||
}
|
||||
ssh: SSHFingerprint[]
|
||||
serverId: string
|
||||
}
|
||||
|
||||
@@ -255,4 +255,5 @@ type HttpError = HttpErrorResponse & { error: { code: string, message: string }
|
||||
const dryRunParam = (dryRun: boolean, first: boolean) => {
|
||||
if (!dryRun) return ''
|
||||
return first ? `?dryrun` : `&dryrun`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ const mockApiNotifications: ReqRes.GetNotificationsRes = [
|
||||
const mockApiServer: () => ReqRes.GetServerRes = () => ({
|
||||
serverId: 'start9-mockxyzab',
|
||||
name: 'Embassy:12345678',
|
||||
versionInstalled: '0.2.6',
|
||||
versionInstalled: '0.2.7',
|
||||
status: ServerStatus.RUNNING,
|
||||
alternativeRegistryUrl: 'beta-registry.start9labs.com',
|
||||
specs: {
|
||||
@@ -420,7 +420,7 @@ const mockApiServer: () => ReqRes.GetServerRes = () => ({
|
||||
})
|
||||
|
||||
const mockVersionLatest: ReqRes.GetVersionLatestRes = {
|
||||
versionLatest: '0.2.6',
|
||||
versionLatest: '0.2.7',
|
||||
canUpdate: true,
|
||||
}
|
||||
|
||||
@@ -664,7 +664,7 @@ const mockApiAppConfig: ReqRes.GetAppConfigRes = {
|
||||
},
|
||||
{
|
||||
'firstName': 'Admin2',
|
||||
'lastName': 'User2',
|
||||
'lastName': 'User',
|
||||
'age': 40,
|
||||
},
|
||||
],
|
||||
@@ -1038,26 +1038,26 @@ const mockApiAppConfig: ReqRes.GetAppConfigRes = {
|
||||
},
|
||||
// actual config
|
||||
config: {
|
||||
testnet: undefined,
|
||||
objectList: undefined,
|
||||
unionList: undefined,
|
||||
randomEnum: 'option1',
|
||||
favoriteNumber: 8,
|
||||
secondaryNumbers: undefined,
|
||||
rpcsettings: {
|
||||
laws: null,
|
||||
rpcpass: null,
|
||||
rpcuser: '123',
|
||||
rulemakers: [],
|
||||
},
|
||||
advanced: {
|
||||
notifications: ['call'],
|
||||
},
|
||||
bitcoinNode: undefined,
|
||||
port: 5959,
|
||||
maxconnections: null,
|
||||
rpcallowip: undefined,
|
||||
rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'],
|
||||
// testnet: undefined,
|
||||
// objectList: undefined,
|
||||
// unionList: undefined,
|
||||
// randomEnum: 'option1',
|
||||
// favoriteNumber: 8,
|
||||
// secondaryNumbers: undefined,
|
||||
// rpcsettings: {
|
||||
// laws: null,
|
||||
// rpcpass: null,
|
||||
// rpcuser: '123',
|
||||
// rulemakers: [],
|
||||
// },
|
||||
// advanced: {
|
||||
// notifications: ['call'],
|
||||
// },
|
||||
// bitcoinNode: undefined,
|
||||
// port: 5959,
|
||||
// maxconnections: null,
|
||||
// rpcallowip: undefined,
|
||||
// rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'],
|
||||
},
|
||||
rules: [],
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export const AppStatusRendering: {
|
||||
} = {
|
||||
[AppStatus.UNKNOWN]: { display: 'Connecting', color: 'dark', showDots: true },
|
||||
[AppStatus.REMOVING]: { display: 'Removing', color: 'dark', showDots: true },
|
||||
[AppStatus.CRASHED]: { display: 'Crashing', color: 'danger', showDots: true },
|
||||
[AppStatus.CRASHED]: { display: 'Crashed', color: 'danger', showDots: false },
|
||||
[AppStatus.NEEDS_CONFIG]: { display: 'Needs Config', color: 'warning', showDots: false },
|
||||
[AppStatus.RUNNING]: { display: 'Running', color: 'success', showDots: false },
|
||||
[AppStatus.UNREACHABLE]: { display: 'Unreachable', color: 'danger', showDots: false },
|
||||
|
||||
@@ -263,14 +263,6 @@ ion-avatar {
|
||||
--padding-start: 10px;
|
||||
}
|
||||
|
||||
// .divider {
|
||||
// margin-top: 15px;
|
||||
// color: var(--ion-color-medium);
|
||||
// font-size: medium;
|
||||
// padding-left: 10px;
|
||||
// font-weight: unset;
|
||||
// }
|
||||
|
||||
ion-item-divider {
|
||||
margin-top: 15px;
|
||||
color: var(--ion-color-medium);
|
||||
|
||||
263
ui/test/config.test.ts
Normal file
263
ui/test/config.test.ts
Normal 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'
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user