diff --git a/Gruntfile.js b/Gruntfile.js index 81fed5af8..c907d1946 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -287,6 +287,7 @@ module.exports = function(grunt) { var locals = { version: version, env: require('./client/config/json/environment.json').environment, + intercomAppId: require('./client/config/json/environment.json').intercomAppId, commitHash: require('./client/config/json/commit.json').commitHash, commitTime: require('./client/config/json/commit.json').commitTime, apiHost: require('./client/config/json/api.json').host @@ -438,6 +439,7 @@ module.exports = function(grunt) { configObj.userContentDomain = process.env.USER_CONTENT_DOMAIN || 'runnableapp.com'; configObj.corporateUrl = process.env.MARKETING_URL || 'https://runnable.io'; configObj.stripeToken = process.env.STRIPE_TOKEN || 'pk_test_sHr5tQaPtgwiE2cpW6dQkzi8'; + configObj.siftApiKey = process.env.SIFT_API_KEY || 'eea9746dff'; if (configObj.host.charAt(configObj.host.length - 1) === '/') { configObj.host = configObj.host.substr(0, configObj.host.length - 1); @@ -477,7 +479,8 @@ module.exports = function(grunt) { }, function (cb) { var configObj = { - environment: environment || process.env.NODE_ENV || 'development' + environment: environment || process.env.NODE_ENV || 'development', + intercomAppId: process.env.INTERCOM_APP_ID || 'xs5g95pd' }; var configJSON = JSON.stringify(configObj); fs.writeFile(path.join(clientPath, 'config', 'json', 'environment.json'), configJSON, function () { diff --git a/client/assets/images/confetti.png b/client/assets/images/confetti.png index 3543e6e78..2d389a041 100644 Binary files a/client/assets/images/confetti.png and b/client/assets/images/confetti.png differ diff --git a/client/assets/images/favicon-gray.png b/client/assets/images/favicon-gray.png index 8b1df7bd4..c4e184be6 100644 Binary files a/client/assets/images/favicon-gray.png and b/client/assets/images/favicon-gray.png differ diff --git a/client/assets/images/favicon-green.png b/client/assets/images/favicon-green.png index f07b62d6c..b3cabfff6 100644 Binary files a/client/assets/images/favicon-green.png and b/client/assets/images/favicon-green.png differ diff --git a/client/assets/images/favicon-orange.png b/client/assets/images/favicon-orange.png index 668f4f364..4038a874d 100644 Binary files a/client/assets/images/favicon-orange.png and b/client/assets/images/favicon-orange.png differ diff --git a/client/assets/images/favicon-red.png b/client/assets/images/favicon-red.png index 0dddc9548..64fb0f5e3 100644 Binary files a/client/assets/images/favicon-red.png and b/client/assets/images/favicon-red.png differ diff --git a/client/assets/images/favicon.png b/client/assets/images/favicon.png index 38469bdbc..52ca3f0cf 100644 Binary files a/client/assets/images/favicon.png and b/client/assets/images/favicon.png differ diff --git a/client/assets/images/home/hero-bg.svg b/client/assets/images/home/hero-bg.svg deleted file mode 100644 index 74536a1ca..000000000 --- a/client/assets/images/home/hero-bg.svg +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/client/assets/images/home/preview.png b/client/assets/images/home/preview.png deleted file mode 100644 index 2b0eb794c..000000000 Binary files a/client/assets/images/home/preview.png and /dev/null differ diff --git a/client/assets/images/logos/logo-icons-wordpress.svg b/client/assets/images/logos/logo-icons-wordpress.svg new file mode 100644 index 000000000..cfbb01a9b --- /dev/null +++ b/client/assets/images/logos/logo-icons-wordpress.svg @@ -0,0 +1 @@ +icons-wordpress \ No newline at end of file diff --git a/client/assets/images/runnabear-head.png b/client/assets/images/runnabear-head.png index 6baea151d..c74420e9b 100644 Binary files a/client/assets/images/runnabear-head.png and b/client/assets/images/runnabear-head.png differ diff --git a/client/assets/images/runnabear-waving-1.png b/client/assets/images/runnabear-waving-1.png new file mode 100644 index 000000000..105906b29 Binary files /dev/null and b/client/assets/images/runnabear-waving-1.png differ diff --git a/client/assets/images/runnabear-waving-2.png b/client/assets/images/runnabear-waving-2.png index 7f650ecb9..f0d8b05c2 100644 Binary files a/client/assets/images/runnabear-waving-2.png and b/client/assets/images/runnabear-waving-2.png differ diff --git a/client/assets/images/runnabear-working.png b/client/assets/images/runnabear-working.png new file mode 100644 index 000000000..6b8329c7f Binary files /dev/null and b/client/assets/images/runnabear-working.png differ diff --git a/client/assets/images/runnabot-comment.png b/client/assets/images/runnabot-comment.png new file mode 100644 index 000000000..dec4a408b Binary files /dev/null and b/client/assets/images/runnabot-comment.png differ diff --git a/client/assets/images/runnabot-head.png b/client/assets/images/runnabot-head.png new file mode 100644 index 000000000..1e8920e96 Binary files /dev/null and b/client/assets/images/runnabot-head.png differ diff --git a/client/assets/images/slack-notification.png b/client/assets/images/slack-notification.png index 08e3e61d4..a10a95318 100644 Binary files a/client/assets/images/slack-notification.png and b/client/assets/images/slack-notification.png differ diff --git a/client/assets/styles/scss/components/aha-button.scss b/client/assets/styles/scss/components/aha-button.scss index 707479e41..7908edac5 100644 --- a/client/assets/styles/scss/components/aha-button.scss +++ b/client/assets/styles/scss/components/aha-button.scss @@ -9,6 +9,6 @@ .iconnables { height: 12px; - width: 12px; + width: 100%; } } diff --git a/client/assets/styles/scss/components/aha-guide.scss b/client/assets/styles/scss/components/aha-guide.scss index 65b278110..a8bf7a48b 100644 --- a/client/assets/styles/scss/components/aha-guide.scss +++ b/client/assets/styles/scss/components/aha-guide.scss @@ -1,3 +1,16 @@ +.environment-view-aha-guide { + > .aha-guide { + margin: 0 auto 15px; + max-width: 450px; + + .p-slide { + font-size: 15px; + position: relative; + top: 0; + } + } +} + .aha-guide { color: $gray; overflow: hidden; @@ -14,7 +27,6 @@ } // anchor image - .spinner-wrapper, .aha-meter { height: $input-md; left: 15px; @@ -96,7 +108,6 @@ .icons-alert { color: $orange; - overflow: visible; padding: 10px 11px 11px; } } @@ -179,3 +190,9 @@ .aha-tips > .iconnables { margin-left: 6px; } + +.aha-overlay-div { + position: absolute; + height: 100%; + width: 100%; +} diff --git a/client/assets/styles/scss/components/aha-modals.scss b/client/assets/styles/scss/components/aha-modals.scss index ff8112165..f7a87ef0c 100644 --- a/client/assets/styles/scss/components/aha-modals.scss +++ b/client/assets/styles/scss/components/aha-modals.scss @@ -23,15 +23,7 @@ } + .modal-dialog { - margin-top: 30px; - - @include media(lg) { - margin-top: 90px; - } - - @media (max-height: $screen-xs), (max-width: $screen-sm) { - margin-top: 105px; - } + margin-top: 120px; } .spinner-wrapper, diff --git a/client/assets/styles/scss/components/aha-popover.scss b/client/assets/styles/scss/components/aha-popover.scss deleted file mode 100644 index 19b8fb652..000000000 --- a/client/assets/styles/scss/components/aha-popover.scss +++ /dev/null @@ -1,27 +0,0 @@ -.popover.popover-aha { - left: 75px; - max-width: none; - top: 66px; - width: 450px; - - @include media(xxs) { - left: 15px; - right: 15px; - top: 60px; - width: calc(100vw - 30px); - - &.right { - margin-left: 0; - } - } - - .arrow { - @include media(xxs) { - display: none; - } - } - - .popover-content { - @extend %padding-sm; - } -} diff --git a/client/assets/styles/scss/components/aha-sidebar.scss b/client/assets/styles/scss/components/aha-sidebar.scss index e6db79d31..d038f9efb 100644 --- a/client/assets/styles/scss/components/aha-sidebar.scss +++ b/client/assets/styles/scss/components/aha-sidebar.scss @@ -33,11 +33,22 @@ transform: translate3d(100%,0,0); } + .aha-sidebar-header { + border-bottom: 1px solid $gray-lightest; + height: $input-md; + margin-bottom: 15px; + padding-bottom: 15px; + position: relative; + width: 100%; + } + .icons-close { - color: $gray; + color: $gray-light; cursor: pointer; - margin-bottom: 15px; pointer-events: auto; + position: absolute; + right: 0; + top: 0; &:hover, &:active { @@ -46,13 +57,21 @@ } .aha-overview { - animation: slide-left .45s ease-in-out forwards; border-bottom: 1px solid $gray-lightest; margin-bottom: 15px; - padding: 32px 0 42px; + + &:last-child { + border-bottom: 0; + } + + .p { + margin-top: 15px; + } .btn { - align-self: center; + margin-bottom: 30px; + margin-left: auto; + margin-right: auto; } } diff --git a/client/assets/styles/scss/components/buttons/buttons-group.scss b/client/assets/styles/scss/components/buttons/buttons-group.scss index 2ba8c2b08..3fb67ba76 100644 --- a/client/assets/styles/scss/components/buttons/buttons-group.scss +++ b/client/assets/styles/scss/components/buttons/buttons-group.scss @@ -134,7 +134,7 @@ > .btn:active { &:not(:first-child):not(:last-child) { - border-radius: $input-border-radius; + border-radius: 0; } &:first-child:not(:last-child) { diff --git a/client/assets/styles/scss/components/buttons/buttons.scss b/client/assets/styles/scss/components/buttons/buttons.scss index 01241d797..c254c8d7c 100755 --- a/client/assets/styles/scss/components/buttons/buttons.scss +++ b/client/assets/styles/scss/components/buttons/buttons.scss @@ -45,7 +45,6 @@ > .spinner-wrapper { align-items: center; display: flex; - float: left; height: 100%; &:first-child { @@ -116,7 +115,7 @@ font-size: 12px; height: $input-xxs; line-height: $input-line-height-xxs; - padding: 0 3px; + padding: 0 5px; } // .btn-icon @@ -486,12 +485,10 @@ } .btn-radio { - background: $white; border: $input-border solid transparent; border-radius: $input-border-radius; box-shadow: none; cursor: pointer; - display: inline-block; font-size: 16px; height: 72px; text-align: center; @@ -499,44 +496,26 @@ transition: none; user-select: none; white-space: nowrap; - width: 90px; - &:active, - &.active { + &:hover:not([disabled]) { + border-color: $gray-lighter; + } + + &:active:not([disabled]), + &.active.active { + background: lighten($purple-lightest,6%); + border-color: $purple-light; + box-shadow: inset 0 0 0 1px $purple-light; color: $purple-light; - cursor: default; } &.active { - font-weight: $weight-bold; + cursor: default; } &:focus { outline: 0; } - - &.btn-lg { - height: 108px; - width: 120px; - - .img, - .iconnables { - margin-bottom: 6px; - width: 36px; - } - } - - &:last-child { - margin-right: 0; - } - - .img, - .iconnables { - display: block; - height: 33px; - margin: 0 auto; - width: 18px; - } } // add button @@ -550,14 +529,10 @@ transition: none; &.btn-sm { - right: 15px; - top: 15px; width: $input-sm; } &.btn-xs { - right: 6px; - top: 6px; width: $input-xs; } diff --git a/client/assets/styles/scss/components/cards.scss b/client/assets/styles/scss/components/cards.scss index 400971183..9037447af 100644 --- a/client/assets/styles/scss/components/cards.scss +++ b/client/assets/styles/scss/components/cards.scss @@ -1,5 +1,5 @@ .card { - border: 2px solid transparent; + border: $input-border solid transparent; border-radius: $input-border-radius-lg; display: flex; flex-direction: column; @@ -148,8 +148,8 @@ } &:not([disabled]):hover { - border-color: darken($gray-lighter,6%); - box-shadow: inset 0 1px 0 darken($gray-lighter,6%); + border-color: $gray-light; + box-shadow: inset 0 0 0 $gray-light; &:first-child { box-shadow: none; @@ -159,18 +159,14 @@ &:not([disabled]):active { background: lighten($purple-lightest,6%); border-color: $purple-light; - box-shadow: inset 0 1px 0 $purple-light; + box-shadow: inset 0 0 0 $purple-light; &:first-child { box-shadow: none; } - > .small { - color: lighten($purple-light,18%); - } - .icons-arrow-down { - color: lighten($purple-light,37%); + color: lighten($purple-light,18%); } .btn-xxs { @@ -210,9 +206,9 @@ // description of tool > .small { - color: $gray; display: block; line-height: 15px; + opacity: .6; } // notifications diff --git a/client/assets/styles/scss/components/lists.scss b/client/assets/styles/scss/components/lists.scss index 381080512..8d108d6cb 100644 --- a/client/assets/styles/scss/components/lists.scss +++ b/client/assets/styles/scss/components/lists.scss @@ -81,6 +81,7 @@ &:active:not(.disabled) .btn-add { @extend %green; + color: $white; .iconnables { color: $white; @@ -121,8 +122,8 @@ } .small { - color: $gray; display: block; + opacity: .6; } .input-checkbox { @@ -150,11 +151,6 @@ opacity: 0; position: absolute; } - - // add button - .btn-add { - position: absolute; - } } // links diff --git a/client/assets/styles/scss/components/repository-details-content.scss b/client/assets/styles/scss/components/repository-details-content.scss index c00d08535..cda3d6365 100644 --- a/client/assets/styles/scss/components/repository-details-content.scss +++ b/client/assets/styles/scss/components/repository-details-content.scss @@ -2,6 +2,18 @@ display: flex; flex-direction: column; + &.modal-body { + max-height: calc(100vh - 195px); + + @media (max-height: $screen-xs), (max-width: $screen-sm) { + max-height: calc(100vh - 165px); + } + + @include media(xxxs) { + max-height: calc(100vh - 212px); + } + } + > .label { display: table; margin: 0 auto 12px; @@ -15,8 +27,9 @@ } .list-servers.list-servers { - flex: 1 1 auto; + flex: 0 0 auto; min-height: 90px; + padding: 15px 0; position: relative; > .spinner-wrapper { @@ -26,7 +39,6 @@ .list-item { @extend %text-overflow; - font-size: 15px; padding-right: 36px; &:active, @@ -69,10 +81,6 @@ } } - &:last-child { - margin-bottom: 15px; - } - .small { color: $gray; } @@ -91,7 +99,6 @@ border-bottom: 1px solid $gray-lighter; flex: 0 0 auto; font-size: 15px; - margin-bottom: 9px; padding-right: 48px; &:not(:first-child) { diff --git a/client/assets/styles/scss/deprecated/btn-auto-deploy.scss b/client/assets/styles/scss/deprecated/btn-auto-deploy.scss deleted file mode 100644 index c4deb7d21..000000000 --- a/client/assets/styles/scss/deprecated/btn-auto-deploy.scss +++ /dev/null @@ -1,53 +0,0 @@ -.btn-repository.main-btn.deprecated { - min-height: 127px; - padding-bottom: ($input-sm + 6px); - - .icons-arrow-forward { - height: calc(100% - 36px); - } -} - -.btn-auto-deploy-deprecated { - background: $gray-lightest; - border-radius: 0; - border-top: 1px solid $gray-lighter; - bottom: 0; - color: $gray; - cursor: default; - font-size: 13px; - height: $input-sm; - left: 0; - margin: 2px; - padding: 0 9px; - position: absolute; - right: 0; - z-index: 1; - - .icons-launch { - filter: grayscale(100%); - height: 100%; - margin: 0 6px 0 -3px; - opacity: .75; - text-align: center; - width: 18px; - - @include retina() { - margin-left: 0; - width: 15px; - } - } - - .underline { - line-height: 1.2; - margin-top: 8px; - } - - .toggle-wrapper { - top: 9px; - - &.disabled { - cursor: not-allowed; - opacity: .5; - } - } -} diff --git a/client/assets/styles/scss/deprecated/instances-list.scss b/client/assets/styles/scss/deprecated/instances-list.scss index 87a129b66..6170bc7ad 100644 --- a/client/assets/styles/scss/deprecated/instances-list.scss +++ b/client/assets/styles/scss/deprecated/instances-list.scss @@ -5,7 +5,7 @@ padding: 0 7px 0 29px; .icons-gear { - margin-right: 4px; + margin: 0 4px; } } diff --git a/client/assets/styles/scss/deprecated/popover-container-menu.scss b/client/assets/styles/scss/deprecated/popover-container-menu.scss index b0af5a60e..ad684589b 100644 --- a/client/assets/styles/scss/deprecated/popover-container-menu.scss +++ b/client/assets/styles/scss/deprecated/popover-container-menu.scss @@ -1,26 +1,38 @@ //- ************************ //- deprecated file, delete with $root.featureFlags.autoIsolation -.popover-container-menu .disabled { +.popover-container-menu { - .p { - color: $gray; + .well { font-size: 13px; - font-weight: $weight-bold; - margin: 9px 15px 15px; - } + margin-bottom: 6px; - .list { - margin: 0 15px; - padding: 0; + .list-item { + margin-top: 6px; + } } - .list-item + .list-item { - margin-top: 6px; - } + .disabled { + + .p { + color: $gray; + font-size: 13px; + font-weight: $weight-bold; + margin: 9px 15px 15px; + } + + .list { + margin: 0 15px; + padding: 0; + } + + .list-item + .list-item { + margin-top: 6px; + } - .btn-block { - margin: 15px auto; - position: static; + .btn-block { + margin: 15px auto; + position: static; + } } } //- end deprecated file diff --git a/client/assets/styles/scss/forms/forms-github.scss b/client/assets/styles/scss/forms/forms-github.scss new file mode 100644 index 000000000..7ed5f35d6 --- /dev/null +++ b/client/assets/styles/scss/forms/forms-github.scss @@ -0,0 +1,74 @@ +// github form +.form-github { + + > .img.img.img { + height: auto; + margin-bottom: 30px; + margin-top: 30px; + max-width: 100%; + } + + .btn .iconnables { + height: 18px; + top: 2px; + width: 18px; + + &:first-child { + margin-right: 6px; + } + + &:last-child { + margin-left: 6px; + } + } + + // the details text + .grid-content.small { + margin-top: 15px; + } + + // success state + .runnabot-success { + max-width: 215px; + overflow: visible; + position: relative; + + .well { + background: $white; + border-color: $gray-lighter; + position: relative; + } + + .arrow::after { + border-right-color: $white; + } + + .arrow, + .arrow::after { + border-color: transparent; + border-style: solid; + position: absolute; + } + + .arrow { + border-bottom-width: 10px; + border-left-width: 0; + border-right: 10px solid darken($gray-lighter,5); + border-top-width: 10px; + left: -10px; + margin-top: -10px; + top: 50%; + + &::after { + border-bottom-width: 9px; + border-left-width: 0; + border-right: 9px solid $white; + border-top-width: 9px; + bottom: -9px; + content: ''; + display: block; + left: 1px; + } + } + } +} diff --git a/client/assets/styles/scss/forms/forms-inputs.scss b/client/assets/styles/scss/forms/forms-inputs.scss index 747b37d97..ec25a087f 100644 --- a/client/assets/styles/scss/forms/forms-inputs.scss +++ b/client/assets/styles/scss/forms/forms-inputs.scss @@ -116,7 +116,7 @@ input { &.input-xs { font-size: 13px; height: $input-xs; - line-height: 1; + line-height: $input-line-height-xs; &[type="search"] { border-radius: $input-xs; @@ -127,7 +127,7 @@ input { &.input-xxs { font-size: 13px; height: $input-xxs; - line-height: 1; + line-height: $input-line-height-xxs; &[type="search"] { border-radius: $input-xxs; diff --git a/client/assets/styles/scss/forms/forms-payment.scss b/client/assets/styles/scss/forms/forms-payment.scss index c827b256e..a3f6fd819 100644 --- a/client/assets/styles/scss/forms/forms-payment.scss +++ b/client/assets/styles/scss/forms/forms-payment.scss @@ -57,6 +57,7 @@ border: 0; &:first-child { + flex: 0 0 27px; width: 27px; } } diff --git a/client/assets/styles/scss/forms/forms-plan.scss b/client/assets/styles/scss/forms/forms-plan.scss index 2c4fb70e5..85d8a2624 100644 --- a/client/assets/styles/scss/forms/forms-plan.scss +++ b/client/assets/styles/scss/forms/forms-plan.scss @@ -319,4 +319,9 @@ top: 12px; } } + + // spacing after plans + .plans-wrapper + .well-summary { + margin-top: 15px; + } } diff --git a/client/assets/styles/scss/forms/forms-toggles.scss b/client/assets/styles/scss/forms/forms-toggles.scss index 0155907f1..74bf46216 100644 --- a/client/assets/styles/scss/forms/forms-toggles.scss +++ b/client/assets/styles/scss/forms/forms-toggles.scss @@ -49,7 +49,7 @@ width: 40px; &:hover { - background: #e6e6e6; // warning: unique color + background: $gray-lighter; // warning: unique color } &:active { @@ -63,8 +63,8 @@ width: 33px; &::after { - height: 17px; - width: 17px; + height: 21px - ($input-border * 2); + width: 21px - ($input-border * 2); } } @@ -74,8 +74,8 @@ width: 24px; &::after { - height: 11px; - width: 11px; + height: 15px - ($input-border * 2); + width: 15px - ($input-border * 2); } } @@ -86,12 +86,12 @@ box-shadow: 1px 1px 3px rgba($black,.05); content: ''; display: block; - height: 22px; + height: 26px - ($input-border * 2); left: 0; position: absolute; top: 0; transition: transform .15s ease-in-out; - width: 22px; + width: 26px - ($input-border * 2); } } diff --git a/client/assets/styles/scss/globals/var.scss b/client/assets/styles/scss/globals/var.scss index 335ef36e2..c3c9d90b8 100755 --- a/client/assets/styles/scss/globals/var.scss +++ b/client/assets/styles/scss/globals/var.scss @@ -121,8 +121,7 @@ $z-show: 10000; // views $z-views: 1000; $z-views-active-panel: 1001; -$z-views-header: 1002; -$z-views-sidebar: 1003; +$z-views-sidebar: 1002; // modals $z-modal-backdrop: 2000; @@ -171,7 +170,7 @@ $screen-xl: 1920px + $layout-navigation-width; $terminal-line-height: 19px; // button/input sizes -$input-border: 2px; +$input-border: 1px; $input-border-radius: 3px; $input-border-radius-lg: 6px; @@ -188,4 +187,4 @@ $input-xs: 24px; $input-line-height-xs: $input-xs - ($input-border * 2); $input-xxs: 18px; -$input-line-height-xxs: $input-xxs - ($input-border * 2); +$input-line-height-xxs: $input-xxs - 2px; diff --git a/client/assets/styles/scss/index.scss b/client/assets/styles/scss/index.scss index 2c197ac9e..8c7c9a441 100755 --- a/client/assets/styles/scss/index.scss +++ b/client/assets/styles/scss/index.scss @@ -62,7 +62,6 @@ @import "components/aha-button"; @import "components/aha-guide"; @import "components/aha-modals"; -@import "components/aha-popover"; @import "components/aha-sidebar"; @import "components/animated-panel"; @import "components/badges"; @@ -106,6 +105,7 @@ // form specific @import "forms/forms-billing"; +@import "forms/forms-github"; @import "forms/forms-payment"; @import "forms/forms-plan"; @import "forms/forms-trial"; @@ -129,6 +129,7 @@ // modal specific @import "modals/modals-backup"; @import "modals/modals-branch-setup"; +@import "modals/modals-confirm-setup"; @import "modals/modals-edit"; @import "modals/modals-edit-ace"; @import "modals/modals-edit-dockerfile"; @@ -155,7 +156,11 @@ // popover specific @import "popover/popover-account-menu"; +@import "popover/popover-add-branches"; @import "popover/popover-add-tab"; +@import "popover/popover-aha"; +@import "popover/popover-aha-containers"; +@import "popover/popover-aha-url"; @import "popover/popover-branch-menu"; @import "popover/popover-card-status"; @import "popover/popover-container-menu"; @@ -192,7 +197,6 @@ @import "pages/server-selection"; //deprecated -@import "deprecated/btn-auto-deploy"; @import "deprecated/card-status"; @import "deprecated/connections"; @import "deprecated/deleted-commit"; diff --git a/client/assets/styles/scss/layout/environment-body.scss b/client/assets/styles/scss/layout/environment-body.scss index 51fd60bb4..437ff9d35 100644 --- a/client/assets/styles/scss/layout/environment-body.scss +++ b/client/assets/styles/scss/layout/environment-body.scss @@ -18,12 +18,12 @@ } .card { - height: 461px; + height: 466px; margin-bottom: 15px; min-width: 0; &.deprecated { - height: 439px; + height: 444px; } // sibling styles must appear first to be overridden by media queries diff --git a/client/assets/styles/scss/layout/environment-header.scss b/client/assets/styles/scss/layout/environment-header.scss index 58c3e7cde..b64897332 100644 --- a/client/assets/styles/scss/layout/environment-header.scss +++ b/client/assets/styles/scss/layout/environment-header.scss @@ -16,13 +16,8 @@ } } - .btn-aha { - right: 0; - top: 3px; - } - // new server button - .green { + > .green { display: table; margin: 0 auto; @@ -54,4 +49,9 @@ } } } + + .btn-aha { + right: 0; + top: 3px; + } } diff --git a/client/assets/styles/scss/layout/instance-header.scss b/client/assets/styles/scss/layout/instance-header.scss index f4100b12d..0c974dca2 100644 --- a/client/assets/styles/scss/layout/instance-header.scss +++ b/client/assets/styles/scss/layout/instance-header.scss @@ -3,7 +3,6 @@ background: $white; flex: 0 0 auto; position: relative; - z-index: $z-views-header; } .instance-header-title { @@ -45,7 +44,6 @@ .green { margin-left: 9px; padding: 0 3px 0 0; - top: -4px; .iconnables { height: 100%; @@ -89,6 +87,7 @@ align-items: top; display: flex; padding: 0 12px 12px; + position: relative; width: 100%; // status, dns mappings, save @@ -115,7 +114,6 @@ } .icons-link-external { - left: 1px; margin: 0 3px; top: -1px; width: 15px; diff --git a/client/assets/styles/scss/layout/instance-sidebar.scss b/client/assets/styles/scss/layout/instance-sidebar.scss index d0e99d6f1..014905540 100644 --- a/client/assets/styles/scss/layout/instance-sidebar.scss +++ b/client/assets/styles/scss/layout/instance-sidebar.scss @@ -16,7 +16,6 @@ font-size: 15px; font-weight: $weight-normal; min-height: 118px; - padding-right: 24px; text-align: left; &.grid-block { @@ -81,13 +80,6 @@ } } - .icons-arrow-forward { - height: 100%; - position: absolute; - right: 3px; - top: 0; - } - .file-tree { margin: 0; } diff --git a/client/assets/styles/scss/layout/instance.scss b/client/assets/styles/scss/layout/instance.scss index 33bc08726..1d1092c09 100644 --- a/client/assets/styles/scss/layout/instance.scss +++ b/client/assets/styles/scss/layout/instance.scss @@ -2,28 +2,8 @@ .instance-wrapper { height: 100vh; overflow: hidden; - overflow-x: auto; position: relative; - &.empty { - background-color: $gray-lightest; - background-image: url(/build/images/confetti.png); - flex-direction: row; - - > .instance { - min-width: 600px; - } - - .instance-header { - background: transparent; - flex: 0 0 60px; - } - } - - > .instance-wrapper { - min-width: 600px; - } - > .spinner-wrapper { .modal-open & { diff --git a/client/assets/styles/scss/layout/instances-list.scss b/client/assets/styles/scss/layout/instances-list.scss index 3c2ea5b99..9b2d83e54 100644 --- a/client/assets/styles/scss/layout/instances-list.scss +++ b/client/assets/styles/scss/layout/instances-list.scss @@ -25,30 +25,10 @@ > .well { background: $white; border-color: $gray-lighter; - margin-bottom: 15px; - padding-left: 30px; - padding-right: 30px; - position: relative; - - .icons-branch { - flex: 0 0 30px; - height: 30px; - } - - // 'x' button - .btn-xs { - color: $gray-light; - height: 18px; - position: absolute; - right: 12px; - top: 12px; - width: 18px; - - &:hover, - &:active { - color: $red; - } - } + border-radius: $input-border-radius-lg; + font-size: 21px; + margin-top: 3px; + padding: 15px 24px 24px; } .a-sref { @@ -88,8 +68,7 @@ .list-clusters { padding: 15px 0; - + .list-clusters, - .well + & { + + .list-clusters { border-top: 1px solid $gray-lighter; } } @@ -97,18 +76,29 @@ .list-clusters-heading { height: 27px; margin: 4px 0; - padding: 0 10px 0 31px; + padding: 0 4px 0 31px; // repo name .text-overflow { padding-right: 9px; } - // '+' button - .btn-xs { - line-height: initial; - overflow: hidden; - width: $input-xs; + // 'add…' buttons + .btn-xxs { + color: lighten($gray,15); + font-weight: $weight-normal; + height: 20px; + top: 1px; + + &:hover { + color: $gray; + } + + .icons-arrow-forward { + height: 12px; + margin-right: -3px; + width: 12px; + } } } diff --git a/client/assets/styles/scss/modals/modals-confirm-setup.scss b/client/assets/styles/scss/modals/modals-confirm-setup.scss new file mode 100644 index 000000000..0e09120d7 --- /dev/null +++ b/client/assets/styles/scss/modals/modals-confirm-setup.scss @@ -0,0 +1,14 @@ +.modal-confirm-setup { + + > .img { + height: auto; + position: relative; + top: 43px; + width: 180px; + z-index: 1; + } + + .modal-dialog { + margin-top: 30px; + } +} diff --git a/client/assets/styles/scss/modals/modals-edit.scss b/client/assets/styles/scss/modals/modals-edit.scss index a3464b567..b732c2319 100644 --- a/client/assets/styles/scss/modals/modals-edit.scss +++ b/client/assets/styles/scss/modals/modals-edit.scss @@ -354,13 +354,25 @@ } > .btn { - border-color: $gray-lighter; flex: 0 1 25%; font-size: 13px; height: 90px; line-height: normal; // reset padding: 0; + &:not(:active):not(.active) { + border-color: $gray-lighter; + + &:hover:not([disabled]) { + border-color: $gray-light; + } + } + + &:active, + &.active { + box-shadow: none; + } + &:first-child { border-radius: $input-border-radius 0 0; } @@ -389,31 +401,32 @@ } &.recommended { + border-radius: 0; &::after { background: $gray-lighter; - border-radius: $input-border-radius; + border-radius: $input-border-radius $input-border-radius 0 0; color: $gray; content: 'Recommended'; display: block; font-size: 11px; font-weight: $weight-normal; height: 21px; - left: -2px; + left: -$input-border; letter-spacing: -.25px; line-height: 20px; position: absolute; - right: -2px; + right: -$input-border; top: -21px; } - &:hover::after { + &:hover:not([disabled])::after { background: $gray-light; color: $white; } &.active, - &:active { + &:active:not([disabled]) { &::after { background: $purple-light; @@ -423,6 +436,7 @@ } .iconnables { + display: block; height: 24px; margin: 9px auto 6px; width: 24px; diff --git a/client/assets/styles/scss/modals/modals-guide.scss b/client/assets/styles/scss/modals/modals-guide.scss index 1bc1c8491..b3f151f5a 100644 --- a/client/assets/styles/scss/modals/modals-guide.scss +++ b/client/assets/styles/scss/modals/modals-guide.scss @@ -16,5 +16,16 @@ .modal-edit .aha-tips { margin-left: auto; margin-right: auto; - max-width: 360px; + + @include media(xxxs) { + flex-direction: column; + } + + .grid-content + .grid-content { + @include media(xxxs) { + margin-left: 0; + margin-top: 9px; + text-align: center; + } + } } diff --git a/client/assets/styles/scss/modals/modals-mirror-dockerfile.scss b/client/assets/styles/scss/modals/modals-mirror-dockerfile.scss index 4fbfd2e0d..353df1c8b 100644 --- a/client/assets/styles/scss/modals/modals-mirror-dockerfile.scss +++ b/client/assets/styles/scss/modals/modals-mirror-dockerfile.scss @@ -70,10 +70,7 @@ // check button .btn-icon { border-radius: 50%; - flex: 0 0 auto; pointer-events: none; // let the label parent select - position: static; - width: $input-xs; } } diff --git a/client/assets/styles/scss/modals/modals-server-select.scss b/client/assets/styles/scss/modals/modals-server-select.scss index f3b774f6b..25e530c98 100644 --- a/client/assets/styles/scss/modals/modals-server-select.scss +++ b/client/assets/styles/scss/modals/modals-server-select.scss @@ -1,10 +1,7 @@ .modal-servers, .modal-servers .animated-panel { + max-width: 100%; min-width: 100%; // fix animated panel container - - @include media(xxxs) { - max-width: 100%; - } } .modal-server-select { @@ -32,11 +29,7 @@ } .btn { - align-items: center; - border: $input-border solid transparent; border-radius: $input-border-radius-lg; - display: flex; - font-size: 15px; height: 60px; line-height: 1.2; padding: 0 21px; @@ -49,21 +42,11 @@ @include media(xxs) { flex: 1 1 50%; flex-direction: column; - height: 96px; + height: 94px; padding: 15px; white-space: normal; } - &:hover { - border-color: $gray-lighter; - } - - &:active, - &.active { - border-color: currentColor; - color: $purple; - } - + .btn { margin-left: 15px; @@ -125,15 +108,6 @@ .list-servers { flex: 0 0 auto; - // add button - .btn-icon { - border-radius: $input-border-radius; - padding: 3px; - right: 10px; - top: 10px; // to center - width: $input-sm; - } - .icons-dockerfile, .icons-server { height: 14px; @@ -150,21 +124,13 @@ } .list-item { + font-size: 18px; height: 60px; - overflow: hidden; - - // template list - &.grid-block { - align-items: center; - - .img { - margin-right: 15px; - } - } + } - .spinner-wrapper { - right: 21px; - } + .btn-add { + padding: 0 9px; + width: auto; } } @@ -175,9 +141,8 @@ border: $input-border solid transparent; border-radius: $input-border-radius-lg; cursor: pointer; - font-size: 18px; font-weight: $weight-bold; - padding: 6px 60px 6px 15px; + padding: 6px 15px; white-space: nowrap; // reset .multi-line default &:hover:not(.disabled) { @@ -186,16 +151,15 @@ } &.disabled { - color: $gray-light; cursor: default; - > .small { - color: $gray-light; + > .small, + .btn-icon { + opacity: .6; } .btn-icon { cursor: default; - opacity: .5; } } @@ -205,22 +169,6 @@ background: $white; border-color: currentColor; color: $green; - - .small { - color: currentColor; - } - } - - &.multi-line { - - // only if in modal (for templates without descriptions) - .modal-dialog & { - min-height: 60px; - } - } - - &:last-child { - margin-bottom: 15px; } + .list-item { @@ -240,18 +188,15 @@ // next arrow .icons-arrow-down { - height: 100%; - position: absolute; - right: 3px; - top: 0; + height: 36px; + padding: 6px; transform: rotate3d(0,0,1,-90deg); + width: 36px; } // spinner .spinner-wrapper { - position: absolute; - right: 9px; - top: 20px; + padding: 10px; } // tiled view for template containers diff --git a/client/assets/styles/scss/modals/modals.scss b/client/assets/styles/scss/modals/modals.scss index 5a4ca4f67..d23d49a13 100755 --- a/client/assets/styles/scss/modals/modals.scss +++ b/client/assets/styles/scss/modals/modals.scss @@ -2,26 +2,17 @@ .modal-backdrop { align-items: center; animation: fade .15s; - background: rgba($white,.97); + background: rgba($white,.98); display: flex; flex-direction: column; height: 100vh; overflow: auto; - padding: 90px 15px; + padding: 0 15px; position: fixed; top: 0; width: 100vw; z-index: $z-modal; - @include media(lg) { - padding-bottom: 30px; - padding-top: 30px; - } - - @media (max-height: $screen-xs), (max-width: $screen-sm) { - padding: 15px; - } - &.modal-blackout { background: $white; } @@ -36,17 +27,28 @@ .modal-dialog { animation: scale-in-modal .15s ease-out; background: $gray-lighterest; - border: $input-border-radius solid $gray-lighter; + border: 2px solid $gray-lighter; border-radius: $input-border-radius-lg; display: flex; flex: 0 0 auto; flex-direction: column; - margin-bottom: 15px; + margin-bottom: 90px; + margin-top: 90px; min-width: 360px; position: relative; transform-origin: 50% 10%; transition: max-width .3s ease-in-out; + @include media(lg) { + margin-bottom: 30px; + margin-top: 30px; + } + + @media (max-height: $screen-xs), (max-width: $screen-sm) { + margin-bottom: 15px; + margin-top: 15px; + } + @include media(sm) { width: 100%; } @@ -141,6 +143,11 @@ position: absolute; top: 18px; z-index: 2; // fix overlap with container-title-wrapper + + &.disabled { + color: $gray-lighter; + cursor: not-allowed; + } } .modal-header > .icons-close, @@ -169,11 +176,11 @@ > .icons-arrow-backward { left: 15px; - &:hover { + &:hover:not(.disabled) { color: $gray; } - &:active { + &:active:not(.disabled) { color: $purple-light; } } @@ -281,25 +288,21 @@ } .btn-radio { - background: none; - border: 1px solid transparent; border-radius: $input-border-radius-lg; - color: $gray; - display: flex; flex: 0 0 90px; font-weight: $weight-normal; padding: 9px 3px 6px; + width: 90px; - &:hover { - border-color: $gray-lighter; + &:not(.active):not(:active) { + color: $gray; } - &:active, - &.active { - background: lighten($purple-lightest,6%); - border-color: currentColor; - box-shadow: inset 0 0 0 1px $purple-light; - color: $purple-light; + .iconnables { + display: block; + height: 18px; + margin: 0 auto; + width: 18px; } // animation start state @@ -325,10 +328,6 @@ margin-left: $tab-margin; } - .iconnables { - height: 21px; - } - .btn-text { align-items: center; display: flex; diff --git a/client/assets/styles/scss/pages/grace-period.scss b/client/assets/styles/scss/pages/grace-period.scss index 0e6a43198..c7cf410b8 100644 --- a/client/assets/styles/scss/pages/grace-period.scss +++ b/client/assets/styles/scss/pages/grace-period.scss @@ -71,7 +71,6 @@ .btn-add { border-radius: 50%; flex: 0 0 auto; - position: static; } } diff --git a/client/assets/styles/scss/popover/popover-add-branches.scss b/client/assets/styles/scss/popover/popover-add-branches.scss new file mode 100644 index 000000000..b66aa4410 --- /dev/null +++ b/client/assets/styles/scss/popover/popover-add-branches.scss @@ -0,0 +1,7 @@ +.popover-add-branches { + max-width: 330px; + + .btn-menu { + margin-left: 15px; + } +} diff --git a/client/assets/styles/scss/popover/popover-aha-containers.scss b/client/assets/styles/scss/popover/popover-aha-containers.scss new file mode 100644 index 000000000..c60f948bf --- /dev/null +++ b/client/assets/styles/scss/popover/popover-aha-containers.scss @@ -0,0 +1,19 @@ +.popover.popover-aha-containers { + left: 75px; + top: 72px; + + @include media(xxs) { + left: 7px; + margin-left: 0; + top: 234px; + width: calc(100vw - 22px); + } + + .arrow { + @include media(xxs) { + left: 30px; + top: -6px; + transform: rotate(90deg); + } + } +} diff --git a/client/assets/styles/scss/popover/popover-aha-url.scss b/client/assets/styles/scss/popover/popover-aha-url.scss new file mode 100644 index 000000000..da26b56f7 --- /dev/null +++ b/client/assets/styles/scss/popover/popover-aha-url.scss @@ -0,0 +1,18 @@ +.popover.popover-aha-url { + left: auto; + position: fixed; + right: 12px; + top: 96px; + + @media (max-width: 810px) { + max-width: calc(100vw - 30px); + right: 15px; + top: 15px; + } + + .arrow { + @media (max-width: 810px) { + display: none; + } + } +} diff --git a/client/assets/styles/scss/popover/popover-aha.scss b/client/assets/styles/scss/popover/popover-aha.scss new file mode 100644 index 000000000..5283b3797 --- /dev/null +++ b/client/assets/styles/scss/popover/popover-aha.scss @@ -0,0 +1,25 @@ +.popover.popover-aha { + max-width: none; + width: 450px; + + &.popover-sm { + color: $gray; + line-height: $input-xs; + max-width: none; + width: auto; + + .float-left + .float-right { + margin-left: 15px; + + @include media(xxs) { + float: left; + margin-left: 0; + margin-top: 3px; + } + } + } + + .popover-content { + @extend %padding-sm; + } +} diff --git a/client/assets/styles/scss/popover/popover-arrows.scss b/client/assets/styles/scss/popover/popover-arrows.scss index fa527f837..6a1b290b0 100644 --- a/client/assets/styles/scss/popover/popover-arrows.scss +++ b/client/assets/styles/scss/popover/popover-arrows.scss @@ -31,7 +31,7 @@ .arrow { background: transparent; border-color: $gray-lighter; - border-width: 13px; + border-width: 12px; z-index: $z-popover-arrow; // reset bg @@ -75,7 +75,7 @@ border-bottom-width: 0; border-left-color: transparent; border-right-color: transparent; - bottom: -13px; + bottom: -12px; left: 50%; margin-left: -11px; @@ -83,7 +83,7 @@ border-bottom-width: 0; border-left-color: transparent; border-right-color: transparent; - bottom: 3px; + bottom: 2px; margin-left: -10px; } } @@ -98,8 +98,8 @@ border-bottom-color: transparent; border-left-width: 0; border-top-color: transparent; - left: -13px; - margin-top: -13px; + left: -12px; + margin-top: -12px; top: 50%; &::after { @@ -107,7 +107,7 @@ border-left-width: 0; border-top-color: transparent; bottom: -10px; - left: 3px; + left: 2px; } } } @@ -121,15 +121,15 @@ border-right-color: transparent; border-top-width: 0; left: 50%; - margin-left: -13px; - top: -13px; + margin-left: -12px; + top: -12px; &::after { border-left-color: transparent; border-right-color: transparent; border-top-width: 0; margin-left: -10px; - top: 3px; + top: 2px; } } } @@ -142,8 +142,8 @@ border-bottom-color: transparent; border-right-width: 0; border-top-color: transparent; - margin-top: -13px; - right: -13px; + margin-top: -12px; + right: -12px; top: 50%; &::after { diff --git a/client/assets/styles/scss/popover/popover-branch-menu.scss b/client/assets/styles/scss/popover/popover-branch-menu.scss index 4b4a8021d..1ed58dce3 100644 --- a/client/assets/styles/scss/popover/popover-branch-menu.scss +++ b/client/assets/styles/scss/popover/popover-branch-menu.scss @@ -1,16 +1,71 @@ -.popover-branch-menu { - max-width: 330px; - min-height: 150px; +.popover-branch-menu, +.popover-template-menu { + max-width: 360px; width: 100%; + .popover-content.popover-content { + max-height: 375px; + min-height: 105px; + overflow-y: auto; + } + + .list-item { + border: 1px solid transparent; + + &:hover:not(.disabled) { + background: $gray-lighterest; + border-color: $gray-lighter; + } + + &:active:not(.disabled), + &.active:not(.disabled) { + background: $white; + border-color: currentColor; + color: $green; + } + + &.active { + cursor: default; + } + + // loading state + &.disabled { + cursor: not-allowed; + opacity: .5; + } + + .spinner-wrapper { + padding: 4px; + } + } + + .btn-add { + padding: 0 6px; + width: auto; + } +} + +.popover-branch-menu { + + .aha-guide { + border-bottom: 1px solid $gray-lighter; + font-size: 15px; + margin-top: 6px; + padding-bottom: 21px; + } + .well { - margin-bottom: 6px; + margin-bottom: 15px; } - .popover-content.popover-content { - max-height: 330px; - min-height: 90px; - overflow-y: auto; + .popover-list-item { + padding-right: 9px; + + .iconnables.icons-branch { + color: $gray-light; + height: 18px; + margin-right: 0; + } } // empty state @@ -25,9 +80,43 @@ font-size: 15px; } } +} + +.popover-template-menu { + + .popover-header { + display: flex; + height: auto; + padding: 15px; + } + + .btn-radio { + border-radius: $input-border-radius-lg; + flex: 0 1 50%; + font-size: 13px; + font-weight: $weight-bold; + height: auto; + line-height: 1.4; + padding: 9px 15px; + text-align: left; + + + .btn-radio { + margin-left: 12px; + } + + .small { + font-size: 12px; + opacity: .6; + } + } + + .list-item .iconnables.icons-repository { + margin-left: 3px; + margin-right: 18px; + width: 18px; + } - // pagination - .list-pagination { - padding: 6px; + .btn-add { + margin-left: 15px; } } diff --git a/client/assets/styles/scss/popover/popover-files.scss b/client/assets/styles/scss/popover/popover-files.scss index de368daf5..d6f5bb534 100644 --- a/client/assets/styles/scss/popover/popover-files.scss +++ b/client/assets/styles/scss/popover/popover-files.scss @@ -31,8 +31,7 @@ .list-servers { .list-item { - font-size: 15px; - padding: 6px 21px 6px 9px; + padding: 6px 3px 6px 9px; &:active, &.active { @@ -40,10 +39,6 @@ color: $purple-light; } } - - .spinner-wrapper { - top: 18px; - } } // forms diff --git a/client/assets/styles/scss/popover/popover-primary-options.scss b/client/assets/styles/scss/popover/popover-primary-options.scss index 95c6bef6a..e6960d2a5 100644 --- a/client/assets/styles/scss/popover/popover-primary-options.scss +++ b/client/assets/styles/scss/popover/popover-primary-options.scss @@ -17,13 +17,9 @@ padding-bottom: 6px; padding-top: 6px; - &:active .small { - color: currentColor; - } - .small { - color: $gray; margin-left: 27px; + opacity: .6; } } } diff --git a/client/assets/styles/scss/popover/popover.scss b/client/assets/styles/scss/popover/popover.scss index 976a6c2f3..1a9a806b4 100755 --- a/client/assets/styles/scss/popover/popover.scss +++ b/client/assets/styles/scss/popover/popover.scss @@ -259,8 +259,7 @@ background: rgba($purple-lightest,.9); color: $purple-light; - .iconnables, - .small { + .iconnables { color: $purple-light; } diff --git a/client/assets/styles/scss/views/views-toolbar.scss b/client/assets/styles/scss/views/views-toolbar.scss index 8276c1150..cbe359918 100644 --- a/client/assets/styles/scss/views/views-toolbar.scss +++ b/client/assets/styles/scss/views/views-toolbar.scss @@ -133,9 +133,9 @@ // web error button .btn-web-state { - color: rgba($orange,.75); cursor: default; font-family: $sans-serif; + opacity: .5; padding: 0; .icons-alert { diff --git a/client/config/routes.js b/client/config/routes.js index 182ada309..250f5827f 100755 --- a/client/config/routes.js +++ b/client/config/routes.js @@ -73,6 +73,10 @@ module.exports = [ }, whitelistedOrgs: function (fetchWhitelistForDockCreated) { return fetchWhitelistForDockCreated(); + }, + booted: function (eventTracking, user) { + eventTracking.boot(user); + eventTracking.visitedOrgSelectPage(); } } }, { @@ -194,20 +198,56 @@ module.exports = [ url: '^/:userName/configure', templateUrl: 'environmentView', controller: 'EnvironmentController', - controllerAs: 'EC' + controllerAs: 'EC', + resolve: { + instancesByPod: function (fetchInstancesByPod, $stateParams, $state) { + $state.params.userName = $stateParams.userName; + return fetchInstancesByPod(); + } + } }, { state: 'base.instances', abstract: false, url: '^/:userName/', templateUrl: 'viewInstances', controller: 'ControllerInstances', - controllerAs: 'CIS' + controllerAs: 'CIS', + resolve: { + instancesByPod: function (fetchInstancesByPod, $stateParams, $state) { + $state.params.userName = $stateParams.userName; + return fetchInstancesByPod(); + }, + hasConfirmedSetup: function ( + $rootScope, + $state, + $stateParams, + $timeout, + ahaGuide, + featureFlags, + populateCurrentOrgService // Unused, but required so things are properly populated! + ) { + if (featureFlags.flags.aha && ahaGuide.isInGuide() && !ahaGuide.hasConfirmedSetup()) { + $timeout(function () { + $state.go('base.config', { + userName: $stateParams.userName + }); + }); + } + } + } }, { state: 'base.instances.instance', abstract: false, url: '^/:userName/:instanceName', templateUrl: 'viewInstance', - controller: 'ControllerInstance' + controller: 'ControllerInstance', + controllerAs: 'CI', + resolve: { + instancesByPod: function (fetchInstancesByPod, $stateParams, $state) { + $state.params.userName = $stateParams.userName; + return fetchInstancesByPod(); + } + } } ]; Object.freeze(module.exports); diff --git a/client/controllers/controllerApp.js b/client/controllers/controllerApp.js index db58e0f89..c90b31d71 100755 --- a/client/controllers/controllerApp.js +++ b/client/controllers/controllerApp.js @@ -3,7 +3,6 @@ require('app') .controller('ControllerApp', ControllerApp); - function ControllerApp( $localStorage, $ocLazyLoad, @@ -12,6 +11,7 @@ function ControllerApp( $state, $timeout, $window, + ahaGuide, configAPIHost, configEnvironment, configLoginURL, @@ -22,20 +22,19 @@ function ControllerApp( keypather, ModalService, pageName, + patchOrgMetadata, + promisify, currentOrg, - user, orgs, activeAccount ) { // Load ace after 10 seconds. Should improve user experience overall.. - $timeout(function () { - $ocLazyLoad.load('ui.ace'); - }, 10000); - this.activeAccount = activeAccount; this.user = user; var CA = this; + CA.ahaGuide = ahaGuide; + CA.currentOrg = currentOrg; fetchInstancesByPod() .then(function (instancesByPod) { @@ -74,10 +73,11 @@ function ControllerApp( actions: {}, state: $state }; - - if (user.socket) { - user.socket.joinOrgRoom(activeAccount.oauthId()); - } + $scope.$watch('dataApp.data.activeAccount', function (activeAccount) { + if (user.socket) { + user.socket.joinOrgRoom(activeAccount.oauthId()); + } + }); $rootScope.pageName = pageName; @@ -99,6 +99,40 @@ function ControllerApp( } }); + CA.showAhaNavPopover = false; + $scope.$on('launchAhaNavPopover', function () { + CA.showAhaNavPopover = !keypather.get(currentOrg, 'poppa.attrs.metadata.hasConfirmedSetup'); + }); + + CA.showAhaConfirmation = function(event) { + event.stopPropagation(); + event.preventDefault(); + CA.showAhaNavPopover = false; + $rootScope.$broadcast('showAddServicesPopover', false); + ModalService.showModal({ + controller: 'ConfirmationModalController', + controllerAs: 'CMC', + templateUrl: 'confirmSetupView' + }) + .then(function(modal) { + return modal.close; + }) + .then(function(confirmed) { + if (confirmed) { + return patchOrgMetadata(currentOrg.poppa.id(), { + metadata: { + hasConfirmedSetup: true + } + }) + .then(function(updatedOrg) { + ahaGuide.updateCurrentOrg(updatedOrg); + $state.go('base.instances', {userName: CA.activeAccount.oauthName()}); + }); + } + }) + .catch(errs.handler); + }; + /** * broadcast to child scopes when click event propagates up * to top level controller scope. diff --git a/client/controllers/controllerInstance.js b/client/controllers/controllerInstance.js index a3ae04e7e..35a7f0a26 100644 --- a/client/controllers/controllerInstance.js +++ b/client/controllers/controllerInstance.js @@ -12,7 +12,8 @@ function ControllerInstance( $state, $stateParams, $timeout, - OpenItems, + ahaGuide, + currentOrg, errs, eventTracking, favico, @@ -20,13 +21,26 @@ function ControllerInstance( fetchDockerfileForContextVersion, fetchInstances, fetchSettings, + fetchUser, getCommitForCurrentlyBuildingBuild, keypather, - fetchUser, + loading, + OpenItems, + instancesByPod, pageName, - setLastInstance, - loading + setLastInstance ) { + + var CIS = this; + CIS.showSidebar = false; + CIS.isInGuide = ahaGuide.isInGuide; + CIS.toggleSidebar = function (end) { + if (end === 'end') { + ahaGuide.endGuide(); + } + CIS.showSidebar = !CIS.showSidebar; + }; + $scope.$on('showAhaSidebar', CIS.toggleSidebar); var dataInstance = $scope.dataInstance = { data: { unsavedAcvs: [] @@ -98,8 +112,8 @@ function ControllerInstance( setLastInstance($stateParams.instanceName); loading('main', false); }) - .catch(function (err) { // We ONLY want to handle errors related to fetching instances so this catch is nested. - errs.handler(err); + .catch(function () { + // Don't handle the instance fetch err, because it's super annoying loading('main', false); setLastInstance(false); $state.go('base.instances', { @@ -200,4 +214,17 @@ function ControllerInstance( favico.setInstanceState(keypather.get($scope, 'dataInstance.data.instance')); }); }); + + if (ahaGuide.isInGuide()) { + if (keypather.get(instancesByPod, 'models.length')) { + if (instancesByPod.models.some(function (instance) { + return instance.attrs.hasAddedBranches || keypather.get(instance, 'children.models.length'); + })) { + // timeout for the animation + $timeout(function () { + CIS.showSidebar = true; + }); + } + } + } } diff --git a/client/controllers/controllerInstances.js b/client/controllers/controllerInstances.js index b0fe4c04d..98feb56de 100644 --- a/client/controllers/controllerInstances.js +++ b/client/controllers/controllerInstances.js @@ -8,35 +8,68 @@ require('app') function ControllerInstances( $filter, $localStorage, + $rootScope, + $scope, $state, - keypather, - setLastOrg, + activeAccount, + ahaGuide, + currentOrg, errs, - ModalService, fetchInstancesByPod, - activeAccount, + fetchRepoBranches, + keypather, + loading, + ModalService, + promisify, + setLastOrg, user ) { - var self = this; + var CIS = this; var userName = $state.params.userName; - self.searchBranches = null; - self.$storage = $localStorage.$default({ + CIS.isInGuide = ahaGuide.isInGuide; + CIS.isAddingFirstBranch = ahaGuide.isAddingFirstBranch; + CIS.isSettingUpRunnabot = ahaGuide.isSettingUpRunnabot; + CIS.currentOrg = currentOrg; + CIS.showAutofork = null; + CIS.searchBranches = null; + CIS.instanceBranches = null; + CIS.unbuiltBranches = null; + CIS.branchQuery = null; + CIS.$storage = $localStorage.$default({ instanceListIsClosed: false }); + + CIS.shouldShowPopover = true; + $scope.$on('popover-closed', function (event, pop) { + if (keypather.get(pop, 'data') === 'branchSelect') { + CIS.shouldShowPopover = true; + } + }); + + $scope.$on('popover-opened', function (event, pop) { + if (keypather.get(pop, 'data') === 'branchSelect') { + CIS.shouldShowPopover = false; + } + }); + + $scope.$on('showAutoLaunchPopover', function() { + CIS.showAutofork = true; + }); + fetchInstancesByPod() .then(function (instancesByPod) { - // If the state has already changed don't continue with old data. Let the new one execute. + // If the state has already changed don' t continue with old data. Let the new one execute. if (userName !== $state.params.userName) { return; } - self.instancesByPod = instancesByPod; - self.activeAccount = activeAccount; + CIS.instancesByPod = instancesByPod; + CIS.activeAccount = activeAccount; var instances = instancesByPod; var lastViewedInstance = keypather.get(user, 'attrs.userOptions.uiState.previousLocation.instance'); - function isInstanceMatch (instance, nameMatch) { + function isInstanceMatch(instance, nameMatch) { if (instance.destroyed || !instance.id()) { return false; } @@ -84,63 +117,124 @@ function ControllerInstances( }) .catch(errs.handler); + this.filterMatchedAnything = function () { + if (!CIS.searchBranches) { + return true; + } + if (!CIS.instancesByPod) { + return true; + } + + return CIS.instancesByPod.models.some(function (masterPod) { + return CIS.filterMasterInstance(masterPod) || CIS.shouldShowParent(masterPod); + }); + }; + this.filterMasterInstance = function (masterPod) { - if (!self.searchBranches) { + if (!CIS.searchBranches) { return true; } - var searchQuery = self.searchBranches.toLowerCase(); + var searchQuery = CIS.searchBranches.toLowerCase(); var instanceName = masterPod.getRepoAndBranchName() + masterPod.attrs.lowerName; return instanceName.toLowerCase().indexOf(searchQuery) !== -1; }; this.getFilteredInstanceList = function () { - if (!self.instancesByPod) { + if (!CIS.instancesByPod) { return null; } - if (!self.searchBranches) { - return self.instancesByPod; + if (!CIS.searchBranches) { + return CIS.instancesByPod.models; } - var searchQuery = self.searchBranches.toLowerCase(); - return self.instancesByPod - .filter(function (masterPod) { - var instanceName = masterPod.getRepoAndBranchName() + masterPod.attrs.lowerName; - return instanceName.toLowerCase().indexOf(searchQuery) !== -1 || - self.getFilteredChildren(masterPod).length > 0; - }); + return CIS.instancesByPod.models + .filter(CIS.filterMasterInstance); }; - this.getFilteredChildren = function (masterPod) { - if (!self.searchBranches) { - return masterPod.children.models; + this.getFilteredBranches = function () { + if (!CIS.branchQuery) { + return CIS.instanceBranches; } - var searchQuery = self.searchBranches.toLowerCase(); - return masterPod.children.models.filter(function (child) { - return child.attrs.lowerName.indexOf(searchQuery) !== -1; + var branchName; + var searchQuery = CIS.branchQuery.toLowerCase(); + return CIS.instanceBranches.filter(function (branch) { + branchName = branch.attrs.name.toLowerCase(); + return branchName.includes(searchQuery); }); }; this.shouldShowChild = function (childInstance) { - if (!self.searchBranches) { + if (!CIS.searchBranches) { return true; } - var searchQuery = self.searchBranches.toLowerCase(); - return childInstance.attrs.lowerName.indexOf(searchQuery) !== -1; + var searchQuery = CIS.searchBranches.toLowerCase(); + return childInstance.getBranchName().toLowerCase().indexOf(searchQuery) !== -1; }; this.shouldShowParent = function (masterPod) { - if (!self.searchBranches) { + if (!CIS.searchBranches) { return true; } - var searchQuery = self.searchBranches.toLowerCase(); - - var instanceName = masterPod.getRepoAndBranchName() + masterPod.attrs.lowerName; - if (instanceName.indexOf(searchQuery) !== -1) { - return true; + if (!masterPod.children) { + return false; } + return masterPod.children.models.some(CIS.shouldShowChild); + }; - return !!masterPod.children.models.find(function (child) { - return child.attrs.lowerName.indexOf(searchQuery) !== -1; + this.getUnbuiltBranches = function (instance, branches) { + var branchName; + var childInstances = instance.children.models.reduce(function (childHash, child) { + branchName = child.getBranchName(); + childHash[branchName] = branchName; + return childHash; + }, {}); + var instanceBranchName = instance.getBranchName(); + childInstances[instanceBranchName] = instanceBranchName; + var unbuiltBranches = branches.models.filter(function (branch) { + branchName = keypather.get(branch, 'attrs.name'); + return !childInstances[branchName]; }); + return unbuiltBranches; + }; + + this.popInstanceOpen = function (instance, open) { + CIS.instanceBranches = null; + CIS.poppedInstance = instance; + loading('fetchingBranches', true); + return CIS.getAllBranches(instance) + .then(function (branches) { + CIS.totalInstanceBranches = branches.models.length; + CIS.instanceBranches = CIS.getUnbuiltBranches(instance, branches); + loading('fetchingBranches', false); + }); + }; + + this.getAllBranches = function (instance) { + return promisify(currentOrg.github, 'fetchRepo')(instance.getRepoName()) + .then(function (repo) { + return fetchRepoBranches(repo); + }); + }; + + this.forkBranchFromInstance = function (branch, closePopover) { + var sha = branch.attrs.commit.sha; + var branchName = branch.attrs.name; + loading(branchName, true); + loading('buildingForkedBranch', true); + promisify(CIS.poppedInstance, 'fork')(branchName, sha) + .then(function (instance) { + var newInstances = instance.children.models.filter(function(childInstance) { + return childInstance.attrs.name === branchName + '-' + instance.attrs.name; + }); + loading(branchName, false); + loading('buildingForkedBranch', false); + closePopover(); + if (newInstances.length) { + $state.go('base.instances.instance', { + instanceName: newInstances[0].attrs.name + }); + } + }) + .catch(errs.handler); }; this.editInstance = function (instance) { @@ -157,28 +251,21 @@ function ControllerInstances( .catch(errs.handler); }; - this.openInviteAdminModal = function (instance) { - ModalService.showModal({ - controller: 'InviteAdminModalController', - controllerAs: 'IAMC', - templateUrl: 'inviteAdminModalView', - inputs: { - instance: instance, - isFromAutoDeploy: false - } - }) - .catch(errs.handler); - }; - - this.openEnableBranchesModal = function (instance) { - ModalService.showModal({ - controller: 'EnableBranchesModalController', - controllerAs: 'EBMC', - templateUrl: 'enableBranchesModalView', - inputs: { - instance: instance - } - }) - .catch(errs.handler); + this.setAutofork = function () { + CIS.poppedInstance.attrs.shouldNotAutofork = !CIS.poppedInstance.attrs.shouldNotAutofork; + if (CIS.isInGuide() && !CIS.poppedInstance.attrs.shouldNotAutofork) { + var childWatcher = $scope.$watch('CIS.poppedInstance.children.models.length', function (length) { + if (length) { + $rootScope.$broadcast('showAhaSidebar'); + childWatcher(); + } + }); + } else if (!CIS.poppedInstance.attrs.shouldNotAutofork) { + CIS.showAutofork = false; + } + promisify(CIS.poppedInstance, 'update')({ shouldNotAutofork: CIS.poppedInstance.attrs.shouldNotAutofork }) + .catch(function () { + CIS.poppedInstance.attrs.shouldNotAutofork = !CIS.poppedInstance.attrs.shouldNotAutofork; + }); }; } diff --git a/client/controllers/featureFlagsController.js b/client/controllers/featureFlagsController.js index fb413d6c5..29c74dad4 100644 --- a/client/controllers/featureFlagsController.js +++ b/client/controllers/featureFlagsController.js @@ -4,7 +4,11 @@ require('app') .controller('FeatureFlagsController', FeatureFlagsController); function FeatureFlagsController( - $localStorage + $localStorage, + ahaGuide ) { this.$localStorage = $localStorage; + this.resetAha = function() { + ahaGuide.resetGuide(); + }; } diff --git a/client/controllers/indexController.js b/client/controllers/indexController.js index e2ccf25cb..98d81e2d2 100755 --- a/client/controllers/indexController.js +++ b/client/controllers/indexController.js @@ -4,6 +4,7 @@ require('app') .controller('IndexController', IndexController); function IndexController( + $localStorage, $ocLazyLoad, $rootScope, $scope, diff --git a/client/directives/accountsSelect/popoverAccountMenu/viewPopoverAccountMenu.jade b/client/directives/accountsSelect/popoverAccountMenu/viewPopoverAccountMenu.jade index 5a2a9fd0b..7abd8b489 100644 --- a/client/directives/accountsSelect/popoverAccountMenu/viewPopoverAccountMenu.jade +++ b/client/directives/accountsSelect/popoverAccountMenu/viewPopoverAccountMenu.jade @@ -57,6 +57,15 @@ | Settings //- $root.featureFlags.billing //- ***************** + li.list-item.popover-list-item( + ng-click = "actions.openSettingsModal('githubIntegration')" + ng-if = "$root.featureFlags.gitHubIntegration" + ) + svg.iconnables + use( + xlink:href = "#icons-octicons-github-gray" + ) + | PR Bot li.list-item.popover-list-item( ng-click = "actions.openSettingsModal('slackIntegration')" ) @@ -173,6 +182,16 @@ style = "background-position: 6px center !important; padding-left: 24px !important;" type = "search" ) + label.list-item.popover-list-item.text-overflow( + style = "display: block; padding: 0 39px 0 15px;" + ) Reset Aha Guide + .toggle-wrapper( + style = "position: absolute; right: 12px; top: 11px;" + ) + input.toggle-input( + ng-click = "FFC.resetAha()" + type = "button" + ) label.list-item.popover-list-item.text-overflow( ng-repeat = "(flag, value) in $root.featureFlags" ng-show = "!searchText || flag.indexOf(searchText) > -1" diff --git a/client/directives/activePanel/tabs/logs/termController.js b/client/directives/activePanel/tabs/logs/termController.js index 22518e048..97d9a60c4 100644 --- a/client/directives/activePanel/tabs/logs/termController.js +++ b/client/directives/activePanel/tabs/logs/termController.js @@ -11,6 +11,17 @@ function TermController( $timeout, WatchOnlyOnce ) { + if (!$scope.tabItem) { + $scope.tabItem = { + attrs: { + terminalId: null + }, + state: { + saveState: function() {return null;} + } + }; + } + var termOnFn; var watchOnlyOnce = new WatchOnlyOnce($scope); $scope.termOpts = { diff --git a/client/directives/activePanel/tabs/viewTabs.jade b/client/directives/activePanel/tabs/viewTabs.jade index 1eda8cd45..268291835 100755 --- a/client/directives/activePanel/tabs/viewTabs.jade +++ b/client/directives/activePanel/tabs/viewTabs.jade @@ -40,7 +40,7 @@ button.btn.btn-xs.btn-add-tab( pop-over-actions = "popoverAddTab.actions" pop-over-active = "popoverAddTab.data.show" pop-over-data = "popoverAddTab.data" - pop-over-options = "{\"right\":-7,\"top\":37}" + pop-over-options = "{\"right\":-5,\"top\":37}" pop-over-template = "viewPopoverAddTab" ) svg.iconnables.icons-add diff --git a/client/directives/activePanel/toolbar/webViewToolbarView.jade b/client/directives/activePanel/toolbar/webViewToolbarView.jade index 51e0af583..b54e9ecba 100644 --- a/client/directives/activePanel/toolbar/webViewToolbarView.jade +++ b/client/directives/activePanel/toolbar/webViewToolbarView.jade @@ -1,23 +1,17 @@ small.small.sans-serif.label-url.hidden-xxxs URL: a.p.monospace.text-overflow( - href = "//praful-staging-codenow.runnableapp.com" + ng-href = "http://{{SMC.state.instance.getContainerHostname()}}" target = "_blank" -) praful-staging-codenow.runnableapp.com +) {{SMC.state.instance.getContainerHostname()}} svg.iconnables.icons-external use( xlink:href = "#icons-link-external" ) .grid-block.shrink.align-center.btn.btn-xs.btn-web-state( - ng-click = "state.error = !state.error" + ng-if = "!SMC.hasOpenPorts()" ) svg.iconnables.icons-alert use( xlink:href = "#icons-alert-alt" ) - | {{state.error ? 'No exposed ports.' : ''}} - | {{!state.error ? 'Bind to all interfaces.' : ''}} - |   - a.link( - href = "#" - target = "_blank" - ) Learn More + | No exposed ports. diff --git a/client/directives/components/ahaGuide/AhaGuideController.js b/client/directives/components/ahaGuide/AhaGuideController.js new file mode 100644 index 000000000..ca2bdc9e5 --- /dev/null +++ b/client/directives/components/ahaGuide/AhaGuideController.js @@ -0,0 +1,220 @@ + +'use strict'; + +require('app') + .controller('AhaGuideController', AhaGuideController); + +function AhaGuideController( + $scope, + $rootScope, + ahaGuide, + currentOrg, + errs, + eventTracking, + fetchInstancesByPod, + keypather, + patchOrgMetadata +) { + var AGC = this; + var animatedPanelListener = angular.noop; + // dismiss add service popover if open + $rootScope.$broadcast('showAddServicesPopover', false); + + if (keypather.has(currentOrg, 'poppa.attrs.id') && ahaGuide.isAddingFirstRepo()) { + fetchInstancesByPod() + .then(function (instances) { + if (instances.models.length) { + var config = checkContainerInstances(instances); + if (!config.workingRepoInstance) { + AGC.showError = true; + AGC.errorState = 'nonRunningContainer'; + updateCaption('exitedEarly'); + $rootScope.$broadcast('ahaGuideEvent', { + error: AGC.errorState + }); + } else if (ahaGuide.isAddingFirstRepo()) { + if (AGC.subStepIndex === 7) { + callPopover(config, instances); + updateCaption('success'); + } + } + } else { + ahaGuide.furthestSubstep(ahaGuide.steps.ADD_FIRST_REPO, 'addRepository'); + } + }) + .catch(errs.handler); + } + + $scope.$on('alert', function (event, alert) { + // alerts on container creation success + if (alert.text === 'Container Created' && alert.type === 'success') { + updateCaption('logs'); + fetchInstancesByPod() + .then(function (instances) { + var config = checkContainerInstances(instances); + if (config) { + callPopover(config, instances); + } + }) + .catch(errs.handler); + } + }); + + var buildLogListener = $scope.$on('buildStatusUpdated', function(event, buildStatus) { + if (ahaGuide.isAddingFirstRepo()) { + handleBuildUpdate(buildStatus); + } + }); + + $scope.$on('ahaGuideEvent', function(event, info) { + if (info.error === 'exitedEarly') { + AGC.showError = true; + AGC.errorState = info.error; + updateCaption('exitedEarly'); + } else if (info.error === 'nonRunningContainer') { + AGC.showError = true; + AGC.errorState = info.error; + } else if (info.error === 'buildFailed') { + AGC.showError = true; + AGC.errorState = info.error; + } else if (info.isClear) { + AGC.showError = false; + AGC.errorState = null; + } + }); + + var stopTabUpdate = $scope.$on('updatedTab', function(event, tabName) { + if (AGC.subStepIndex > 5) { + stopTabUpdate(); + } else { + updateCaption(tabName); + } + }); + + AGC.ahaGuide = ahaGuide; + AGC.configSteps = ahaGuide.stepList[ahaGuide.steps.ADD_FIRST_REPO].configSubsteps; + AGC.errorState = $scope.errorState; + AGC.hasConfirmedSetup = ahaGuide.hasConfirmedSetup; + AGC.isBuildSuccessful = false; + AGC.isInGuide = ahaGuide.isInGuide; + AGC.skipBranchMilestone = ahaGuide.skipBranchMilestone; + + // get the current milestone + var currentMilestone = ahaGuide.stepList[ahaGuide.getCurrentStep()]; + + AGC.title = currentMilestone.title; + updateCaption(AGC.subStep); + + // update steps and initiate digest loop + function updateCaption(status) { + if (!currentMilestone.subSteps[status]) { + return; + } + if (status === 'dockLoaded') { + animatedPanelListener(); + } + AGC.subStep = status; + AGC.className = currentMilestone.subSteps[status].className; + AGC.subStepIndex = currentMilestone.subSteps[status].step; + ahaGuide.furthestSubstep(ahaGuide.steps.ADD_FIRST_REPO, status); + + // tracking + switch (AGC.subStep) { + case 'containerSelection': + eventTracking.milestone2SelectTemplate(); + break; + case 'repository': + eventTracking.milestone2VerifyRepositoryTab(); + break; + case 'commands': + eventTracking.milestone2VerifyCommandsTab(); + break; + case 'logs': + eventTracking.milestone2Building(); + break; + case 'success': + eventTracking.milestone2BuildSuccess(); + break; + } + } + + function handleBuildUpdate(update) { + var buildStatus = update.status; + if (buildStatus === 'buildFailed' || buildStatus === 'stopped' || buildStatus === 'crashed') { + AGC.showError = true; + AGC.errorState = 'nonRunningContainer'; + $rootScope.$broadcast('ahaGuideEvent', { + error: 'buildFailed' + }); + } else if (buildStatus === 'starting') { + AGC.showError = false; + // as long as the build was successful that's ok + $rootScope.$broadcast('ahaGuideEvent', { + isClear: true + }); + AGC.isBuildSuccessful = true; + } else if (buildStatus === 'running') { + updateCaption('success'); + } + AGC.buildStatus = buildStatus; + AGC.caption = currentMilestone.buildStatus[buildStatus] || AGC.caption; + } + /** this checks all instances and whether there is a built repo instance and non repo instance + * @param {object} instances an object containing a collection of instances + * @return {object} config an object with two boolean properties, nonRepoInstance and workingRepoInstance + */ + function checkContainerInstances (instances) { + if (!instances) { + return null; + } + var config = {}; + var status; + var repoName; + instances.forEach(function(instance) { + status = instance.status(); + repoName = instance.getRepoName(); + if (repoName && status !== 'building' && status !== 'buildFailed') { + config.workingRepoInstance = true; + } else if (!repoName) { + config.nonRepoInstance = true; + } + }); + return config; + } + + /** this only calls popovers for one specific group. they have built a repo and nonrepo instance only. + * @param {object} config an object with two boolean properties, nonRepoInstance and workingRepoInstance + * @param {object} instances an object containing a collection of instances + */ + function callPopover(config, instances) { + if (config.workingRepoInstance && instances.models.length === 2) { + $rootScope.$broadcast('launchAhaNavPopover'); + AGC.showAhaNavPopover = true; + } else if (config.workingRepoInstance && instances.models.length === 1) { + $rootScope.$broadcast('showAddServicesPopover', true); + } + } + + $scope.$on('$destroy', function () { + animatedPanelListener(); + if (AGC.subStepIndex === 7 && !AGC.isBuildSuccessful) { + $rootScope.$broadcast('ahaGuideEvent', { + error: 'exitedEarly' + }); + } else if (ahaGuide.isAddingFirstRepo() && AGC.isBuildSuccessful && !AGC.showAhaNavPopover) { + $rootScope.$broadcast('showAddServicesPopover', true); + } + }); + + animatedPanelListener = $rootScope.$on('changed-animated-panel', function (e, panel) { + updateCaption(panel); + }); + + AGC.popoverActions = { + endGuide: ahaGuide.endGuide, + showSidebar: function () { + $rootScope.$broadcast('close-popovers'); + $rootScope.$broadcast('showAhaSidebar'); + } + }; +} diff --git a/client/directives/components/ahaGuide/addBranchGuide/addBranchGuideDirective.js b/client/directives/components/ahaGuide/addBranchGuide/addBranchGuideDirective.js new file mode 100644 index 000000000..51c54a9e0 --- /dev/null +++ b/client/directives/components/ahaGuide/addBranchGuide/addBranchGuideDirective.js @@ -0,0 +1,22 @@ +'use strict'; + +require('app') + .directive('addBranchGuide', addBranchGuide); + +function addBranchGuide( + ahaGuide +) { + return { + restrict: 'A', + templateUrl: 'addBranchGuideView', + scope: true, + link: function ($scope, elem, attrs) { + $scope.ahaGuide = { + steps: ahaGuide.steps, + getCurrentStep: ahaGuide.getCurrentStep + }; + $scope.subStep = $scope.AGC.subStep; + ahaGuide.furthestSubstep(ahaGuide.steps.ADD_FIRST_BRANCH, $scope.subStep); + } + }; +} diff --git a/client/directives/components/ahaGuide/addBranchGuide/addBranchGuideView.jade b/client/directives/components/ahaGuide/addBranchGuide/addBranchGuideView.jade new file mode 100644 index 000000000..aaf5d102d --- /dev/null +++ b/client/directives/components/ahaGuide/addBranchGuide/addBranchGuideView.jade @@ -0,0 +1,48 @@ +.grid-block.shrink.aha-meter( + ng-class = "{\ + 'aha-error': AGC.errorState || AGC.showError,\ + 'aha-meter-33': AGC.ahaGuide.isAddingFirstBranch(),\ + 'aha-meter-66': AGC.ahaGuide.isAddingFirstBranch() && subStep === 'selectBranch',\ + 'aha-meter-100': AGC.ahaGuide.getCurrentStep() > ahaGuide.steps.ADD_FIRST_BRANCH\ + }" +) + svg.iconnables( + ng-if = "!AGC.showError && !AGC.errorState" + ) + use( + ng-if = "AGC.ahaGuide.isAddingFirstBranch()" + xlink:href = "#icons-octicons-branch" + ) + use( + ng-if = "AGC.ahaGuide.getCurrentStep() > AGC.ahaGuide.steps.ADD_FIRST_BRANCH" + xlink:href = "#icons-check" + ) + + svg.iconnables.icons-alert( + ng-if = "AGC.errorState || AGC.showError" + ) + use( + xlink:href = "#icons-alert-alt" + ) + +.grid-block.vertical.aha-text + p.p.small.text-gray-light Step 3: Add a Branch + p.p( + ng-if = "subStep === 'addBranch'" + ) Click the ‘Add Branch’ button next to any repo name. + p.p( + ng-if = "subStep === 'selectBranch'" + ) Select a branch to add. + //- show in the branch menu if the repository has no branches. + p.p( + ng-if = "subStep === 'noBranches'" + ) Aw, no branches! Try another repository or + a.link( + ng-click = "AGC.skipBranchMilestone()" + target = "_blank" + ) skip this step + | . + //- show in the branch menu if the repository has no branches. + p.p( + ng-if = "subStep === 'deletedTemplate'" + ) You've deleted your repository template. Create another one to continue. diff --git a/client/directives/components/ahaGuide/ahaGuideDirective.js b/client/directives/components/ahaGuide/ahaGuideDirective.js new file mode 100644 index 000000000..86fd00e14 --- /dev/null +++ b/client/directives/components/ahaGuide/ahaGuideDirective.js @@ -0,0 +1,24 @@ +'use strict'; + +require('app') + .directive('ahaGuide', ahaGuide); + +/** + * @ngInject + */ +function ahaGuide( + +) { + return { + restrict: 'A', + templateUrl: 'ahaGuideView', + controller: 'AhaGuideController', + controllerAs: 'AGC', + bindToController: true, + scope: { + subStep: '@?', + subStepIndex: '=?', + errorState: '@?' + } + }; +} diff --git a/client/directives/components/ahaGuide/ahaGuideMenuPopoverView.jade b/client/directives/components/ahaGuide/ahaGuideMenuPopoverView.jade index 324ed00b4..f4e910b38 100644 --- a/client/directives/components/ahaGuide/ahaGuideMenuPopoverView.jade +++ b/client/directives/components/ahaGuide/ahaGuideMenuPopoverView.jade @@ -6,6 +6,8 @@ .popover-content ul.list.popover-list li.list-item.popover-list-item( - ng-click = "$root.featureFlags.ahaSidebar = true" + ng-click = "actions.showSidebar()" ) View All - li.list-item.popover-list-item End Guide + li.list-item.popover-list-item( + ng-click = "actions.endGuide()" + ) End Guide diff --git a/client/directives/components/ahaGuide/ahaGuideView.jade b/client/directives/components/ahaGuide/ahaGuideView.jade index 55c86a34c..d5d27a181 100644 --- a/client/directives/components/ahaGuide/ahaGuideView.jade +++ b/client/directives/components/ahaGuide/ahaGuideView.jade @@ -1,30 +1,35 @@ .grid-block.align-center( - ng-if = "state.showStep === 0" + ng-if = "AGC.ahaGuide.isChoosingOrg()" ng-include = "'createSandboxGuideView'" ) .grid-block.align-center( - ng-if = "state.showStep === 1" + ng-if = "staticAddRepo || showAhaNavPopover" + ng-include = "'staticAhaGuideTemplates'" +) + +.grid-block.align-center( + ng-if = "AGC.ahaGuide.isAddingFirstRepo()" ng-include = "'setUpRepositoryGuideView'" - ng-mouseenter = "state.showControls = true" - ng-mouseleave = "state.showControls = false" ) .grid-block.align-center( - ng-if = "state.showStep === 2" - ng-include = "'addBranchGuideView'" + ng-if = "AGC.ahaGuide.isAddingFirstBranch()" + add-branch-guide ) .grid-block.align-center( - ng-if = "state.showStep === 3" + ng-if = "showAutofork" ng-include = "'setUpRunnabotGuideView'" ) button.btn.btn-xs.white.btn-menu( ng-class = "{'active': ahaMenuGuidePopover.data.show}" - ng-if = "!state.hideMenu" + ng-if = "(AGC.isInGuide() && !AGC.ahaGuide.isChoosingOrg()) || staticAddRepo" pop-over + pop-over-actions = "AGC ? AGC.popoverActions : EC.actions" pop-over-active = "ahaMenuGuidePopover.data.show" + pop-over-no-broadcast = "true" pop-over-options = "{\"centered\":true,\"top\":36}" pop-over-template = "ahaGuideMenuPopoverView" ) diff --git a/client/directives/components/ahaGuide/ahaPopoverView.jade b/client/directives/components/ahaGuide/ahaPopoverView.jade index 13702aa13..a99f6da91 100644 --- a/client/directives/components/ahaGuide/ahaPopoverView.jade +++ b/client/directives/components/ahaGuide/ahaPopoverView.jade @@ -2,7 +2,9 @@ .popover-content .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( ng-include = "'ahaGuideView'" - ng-init = "state.showStep = 1" + ng-init = "showAhaNavPopover = true" ) .grid-block.justify-right.popover-footer - button.grid-block.shrink.btn.btn-sm.green Got It + button.grid-block.shrink.btn.btn-sm.green( + ng-click = "CA.showAhaNavPopover = false" + ) Got It diff --git a/client/directives/components/ahaGuide/ahaSidebar/ahaSidebarDirective.js b/client/directives/components/ahaGuide/ahaSidebar/ahaSidebarDirective.js new file mode 100644 index 000000000..392e82798 --- /dev/null +++ b/client/directives/components/ahaGuide/ahaSidebar/ahaSidebarDirective.js @@ -0,0 +1,25 @@ +'use strict'; + +require('app') + .directive('ahaSidebar', ahaSidebar); + +function ahaSidebar( + ahaGuide +) { + return { + restrict: 'A', + templateUrl: 'ahaSidebarView', + scope: { + toggleSidebar: '=', + showOverview: '=' + }, + link: function ($scope) { + $scope.steps = ahaGuide.steps; + $scope.getCurrentStep = ahaGuide.getCurrentStep; + $scope.isSettingUpRunnabot = ahaGuide.isSettingUpRunnabot; + $scope.isAddingFirstRepo = ahaGuide.isAddingFirstRepo; + $scope.isAddingFirstBranch = ahaGuide.isAddingFirstBranch; + $scope.getFurthestSubstep = ahaGuide.furthestSubstep; + } + }; +} diff --git a/client/directives/components/ahaGuide/ahaSidebar/ahaSidebarView.jade b/client/directives/components/ahaGuide/ahaSidebar/ahaSidebarView.jade new file mode 100644 index 000000000..b59dc1d37 --- /dev/null +++ b/client/directives/components/ahaGuide/ahaSidebar/ahaSidebarView.jade @@ -0,0 +1,122 @@ +.grid-block.shrink( + ng-if = "!showOverview" +) + header.grid-block.align-center.justify-center.aha-sidebar-header + h4.grid-content.strong.text-center.h4( + ng-if = "!isSettingUpRunnabot()" + ) + //- During ahas 1-3: + | Setup Guide + h4.grid-content.strong.text-center.h4( + ng-if = "isSettingUpRunnabot() && !$root.featureFlags.ahaBranchUrlStep" + ) + //- During aha 4: + | You did it! 👏 + h4.grid-content.strong.text-center.h4( + ng-if = "isSettingUpRunnabot() && $root.featureFlags.ahaBranchUrlStep" + ) + //- During aha 4: + | One more thing… + svg.grid-content.shrink.iconnables.icons-close( + ng-click = "toggleSidebar()" + ng-if = "!showOverview && !isSettingUpRunnabot()" + ) + use( + xlink:href = "#icons-close" + ) + + svg.grid-content.shrink.iconnables.icons-close( + ng-click = "toggleSidebar('end')" + ng-if = "!showOverview && isSettingUpRunnabot()" + ) + use( + xlink:href = "#icons-close" + ) + +//- At the start of milestone 2: +.grid-block.vertical.shrink.aha-overview( + ng-if = "showOverview" +) + header.grid-block.vertical.shrink.align-center.justify-center.aha-sidebar-header + h4.grid-content.shrink.text-center.h4 Let‘s get running! 👋 + .grid-block.shrink.vertical.padding-sm + p.grid-content.shrink.text-center.p It takes just 3 steps to get everything set up. But don’t worry — we’re here to help! + button.grid-content.shrink.btn.btn-md.green( + data-event-name = "Milestone 2: Started" + ng-click = "toggleSidebar()" + ) Get Started + +//- During ahas 1-3 +.grid-block.shrink.vertical.aha-guide-wrapper( + ng-if = "!isSettingUpRunnabot()" +) + .grid-block.shrink.align-center.padding-sm.aha-guide.disabled + .grid-block.shrink.aha-meter.aha-meter-100 + svg.iconnables + use( + xlink:href = "#icons-check" + ) + .grid-block.vertical.aha-text + p.p.strong Step 1: Choose your Organization + p.small This is the first step to success. + + .grid-block.shrink.align-center.padding-sm.aha-guide( + ng-class = "{\ + disabled: !isAddingFirstRepo(),\ + active: isAddingFirstRepo()\ + }" + ) + .grid-block.shrink.aha-meter( + ng-class = "{\ + 'aha-meter-11': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'addRepository',\ + 'aha-meter-22': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'containerSelection',\ + 'aha-meter-33': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'dockerfileMirroring',\ + 'aha-meter-44': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'nameContainer',\ + 'aha-meter-55': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'repository',\ + 'aha-meter-66': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'commands',\ + 'aha-meter-77': isAddingFirstRepo() && (getFurthestSubstep(steps.ADD_FIRST_REPO) === 'buildfiles' || getFurthestSubstep(steps.ADD_FIRST_REPO) === 'default' || getFurthestSubstep(steps.ADD_FIRST_REPO) === 'env' || getFurthestSubstep(steps.ADD_FIRST_REPO) === 'files' || getFurthestSubstep(steps.ADD_FIRST_REPO) === 'filesMirror' || getFurthestSubstep(steps.ADD_FIRST_REPO) === 'ports' || getFurthestSubstep(steps.ADD_FIRST_REPO) === 'translation'),\ + 'aha-meter-88': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'logs',\ + 'aha-meter-88': isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'exitedEarly',\ + 'aha-meter-100': (isAddingFirstRepo() && getFurthestSubstep(steps.ADD_FIRST_REPO) === 'success') || getCurrentStep() > steps.ADD_FIRST_REPO,\ + }" + ) + svg.iconnables + use( + ng-if = "getFurthestSubstep(steps.ADD_FIRST_REPO) !== 'success' && isAddingFirstRepo()" + xlink:href = "#icons-octicons-repo" + ) + use( + ng-if = "getFurthestSubstep(steps.ADD_FIRST_REPO) === 'success' || getCurrentStep() > steps.ADD_FIRST_REPO" + xlink:href = "#icons-check" + ) + .grid-block.vertical.aha-text + p.p.strong Step 2: Configure your Application + p.small Add your templates and get them running! + + .grid-block.shrink.align-center.padding-sm.aha-guide( + ng-class = "{'disabled': getCurrentStep() !== steps.ADD_FIRST_BRANCH}" + ) + .grid-block.shrink.aha-meter( + ng-class = "{\ + 'aha-meter-33': isAddingFirstBranch() && getFurthestSubstep(steps.ADD_FIRST_BRANCH) === 'addBranch',\ + 'aha-meter-66': isAddingFirstBranch() && getFurthestSubstep(steps.ADD_FIRST_BRANCH) === 'dockLoading',\ + 'aha-meter-100': getCurrentStep() > steps.ADD_FIRST_BRANCH\ + }" + ) + svg.iconnables + use( + ng-if = "getCurrentStep() <= steps.ADD_FIRST_BRANCH" + xlink:href = "#icons-octicons-branch" + ) + use( + ng-if = "getCurrentStep() > steps.ADD_FIRST_BRANCH" + xlink:href = "#icons-check" + ) + .grid-block.vertical.aha-text + p.p.strong Step 3: Add a Branch + p.small Your branches will update on every commit you make. + +.grid-block.vertical.align-center.form-github( + ng-if = "isSettingUpRunnabot()" + github-integration +) diff --git a/client/directives/components/ahaGuide/ahaSidebarView.jade b/client/directives/components/ahaGuide/ahaSidebarView.jade deleted file mode 100644 index 2123f5f67..000000000 --- a/client/directives/components/ahaGuide/ahaSidebarView.jade +++ /dev/null @@ -1,108 +0,0 @@ -.grid-block.shrink.align-center.justify-right - svg.iconnables.icons-close( - ng-click = "$root.featureFlags.ahaSidebar = false" - ng-if = "!$root.featureFlags.ahaOverview" - ) - use( - xlink:href = "#icons-close" - ) -.grid-block.vertical.shrink.justify-center.text-center.aha-overview( - ng-if = "$root.featureFlags.ahaOverview" -) - .grid-content.strong Welcome to your Sandbox! 👋 - | It’ll take a few steps to get everything set up. But don’t worry — we’re here to help! - button.grid-content.btn.btn-sm.green( - ng-click = "\ - $root.featureFlags.ahaOverview = false;\ - $root.featureFlags.ahaSidebar = false;\ - " - ) Get Started -.grid-block.vertical - .grid-block.shrink.align-center.padding-sm.aha-guide( - ng-class = "{'disabled': $root.featureFlags.aha1 || $root.featureFlags.aha2 || $root.featureFlags.aha3}" - ) - .grid-block.shrink.aha-meter( - ng-class = "{\ - 'aha-meter-50': $root.featureFlags.aha0,\ - 'aha-meter-100': $root.featureFlags.aha1 || $root.featureFlags.aha2 || $root.featureFlags.aha3\ - }" - ) - svg.iconnables - use( - ng-if = "$root.featureFlags.aha1 || $root.featureFlags.aha2 || $root.featureFlags.aha3" - xlink:href = "#icons-check" - ) - .grid-block.vertical.aha-text - p.p.strong Create your Sandbox - p.small This is the first step to success. - - .grid-block.shrink.align-center.padding-sm.aha-guide( - ng-class = "{'disabled': $root.featureFlags.aha0 || $root.featureFlags.aha2 || $root.featureFlags.aha3}" - ) - .grid-block.shrink.aha-meter( - ng-class = "{\ - 'aha-error': state.showError,\ - 'aha-meter-10': state.showSubStep === 0,\ - 'aha-meter-20': state.showSubStep === 1,\ - 'aha-meter-30': state.showSubStep === 2,\ - 'aha-meter-40': state.showSubStep === 3,\ - 'aha-meter-50': state.showSubStep === 4,\ - 'aha-meter-60': state.showSubStep === 5,\ - 'aha-meter-70': state.showSubStep === 6,\ - 'aha-meter-80': state.showSubStep === 7,\ - 'aha-meter-90': state.showSubStep === 8,\ - 'aha-meter-100': $root.featureFlags.aha2 || $root.featureFlags.aha3\ - }" - ) - svg.iconnables - use( - ng-if = "$root.featureFlags.aha1 && (!$root.featureFlags.aha2 || !$root.featureFlags.aha3)" - xlink:href = "#icons-octicons-repo" - ) - use( - ng-if = "$root.featureFlags.aha2 || $root.featureFlags.aha3" - xlink:href = "#icons-check" - ) - .grid-block.vertical.aha-text - p.p.strong Add your First Repository - p.small Configure your project and get it running! - - .grid-block.shrink.align-center.padding-sm.aha-guide( - ng-class = "{'disabled': $root.featureFlags.aha0 || $root.featureFlags.aha1 || $root.featureFlags.aha3}" - ) - .grid-block.shrink.aha-meter( - ng-class = "{\ - 'aha-meter-33': $root.featureFlags.aha2,\ - 'aha-meter-100': $root.featureFlags.aha3\ - }" - ) - svg.iconnables - use( - ng-if = "!$root.featureFlags.aha3" - xlink:href = "#icons-octicons-branch" - ) - use( - ng-if = "$root.featureFlags.aha3" - xlink:href = "#icons-check" - ) - .grid-block.vertical.aha-text - p.p.strong Add your First Branch - p.small Your branches will update on every commit you make. - - .grid-block.shrink.align-center.padding-sm.aha-guide( - ng-class = "{'disabled': $root.featureFlags.aha0 || $root.featureFlags.aha1 || $root.featureFlags.aha2}" - ) - .grid-block.shrink.aha-meter( - ng-class = "{'aha-meter-50': $root.featureFlags.aha3}" - ) - svg.iconnables - use( - xlink:href = "#icons-runnabot" - ) - //- if complete - //- use( - //- xlink:href = "#icons-check" - //- ) - .grid-block.vertical.aha-text - p.p.strong Set Up Runnabot - p.small Receive notifications on pull requests when your container is ready. diff --git a/client/directives/components/ahaGuide/components/addBranchGuideView.jade b/client/directives/components/ahaGuide/components/addBranchGuideView.jade deleted file mode 100644 index 6680148e8..000000000 --- a/client/directives/components/ahaGuide/components/addBranchGuideView.jade +++ /dev/null @@ -1,18 +0,0 @@ -.grid-block.shrink.aha-meter( - ng-class = "{\ - 'aha-meter-33': $root.featureFlags.aha2,\ - 'aha-meter-100': $root.featureFlags.aha3\ - }" -) - svg.iconnables - use( - ng-if = "!$root.featureFlags.aha3" - xlink:href = "#icons-octicons-branch" - ) - use( - ng-if = "$root.featureFlags.aha3" - xlink:href = "#icons-check" - ) -.grid-block.vertical.aha-text - p.p.small.text-gray-light Add your First Branch - p.p You can start adding branches by clicking the + button next to the name of the repository. diff --git a/client/directives/components/ahaGuide/components/createSandboxGuideView.jade b/client/directives/components/ahaGuide/components/createSandboxGuideView.jade index dac339141..5ee21981f 100644 --- a/client/directives/components/ahaGuide/components/createSandboxGuideView.jade +++ b/client/directives/components/ahaGuide/components/createSandboxGuideView.jade @@ -1,22 +1,25 @@ .grid-block.shrink.aha-meter( - ng-class = "{\ - 'aha-meter-33': $root.featureFlags.aha0 && state.showSubStep === 0,\ - 'aha-meter-66': $root.featureFlags.aha0 && state.showSubStep === 1,\ - 'aha-meter-100': $root.featureFlags.aha1 || $root.featureFlags.aha2 || $root.featureFlags.aha3\ - }" + ng-class = "\ + AGC.className\ + " ) svg.iconnables use( - ng-if = "$root.featureFlags.aha0" + ng-if = "AGC.subStep !== 'dockLoaded'" xlink:href = "#icons-cog" ) use( - ng-if = "$root.featureFlags.aha1 || $root.featureFlags.aha2 || $root.featureFlags.aha3" + ng-if = "AGC.subStep === 'dockLoaded'" xlink:href = "#icons-check" ) .grid-block.vertical.aha-text - p.p.small.text-gray-light Create your Sandbox - p.p - {{state.showSubStep === 0 ? 'Choose an organization to create your sandbox for.' : ''}} - {{state.showSubStep === 1 ? 'Hang tight!' : ''}} - {{state.showSubStep === 2 ? 'Continue to start configuring your project.' : ''}} + p.p.small.text-gray-light {{ AGC.title }} + p.p( + ng-if = "AGC.subStep === 'orgSelection'" + ) Select the organization you want to use with Runnable. + p.p( + ng-if = "AGC.subStep === 'dockLoading'" + ) Bear with us! + p.p( + ng-if = "AGC.subStep === 'dockLoaded'" + ) Continue to start configuring your project. diff --git a/client/directives/components/ahaGuide/components/setUpRepositoryGuideView.jade b/client/directives/components/ahaGuide/components/setUpRepositoryGuideView.jade deleted file mode 100644 index 270e4752d..000000000 --- a/client/directives/components/ahaGuide/components/setUpRepositoryGuideView.jade +++ /dev/null @@ -1,135 +0,0 @@ -.grid-block.align-center.shrink.spinner-wrapper.spinner-md.spinner-gray.in.js-animate( - ng-if = "state.showVerification" - ng-include = "'spinner'" -) - -.grid-block.shrink.aha-meter.js-animate( - ng-class = "{\ - 'aha-error': state.showError,\ - 'aha-meter-10': state.showSubStep === 0,\ - 'aha-meter-20': state.showSubStep === 1,\ - 'aha-meter-30': state.showSubStep === 2,\ - 'aha-meter-40': state.showSubStep === 3,\ - 'aha-meter-50': state.showSubStep === 4,\ - 'aha-meter-60': state.showSubStep === 5,\ - 'aha-meter-70': state.showSubStep === 6,\ - 'aha-meter-80': state.showSubStep === 7,\ - 'aha-meter-90': state.showSubStep === 8,\ - 'aha-meter-100': $root.featureFlags.aha2 || $root.featureFlags.aha3\ - }" - ng-if = "!state.showVerification" -) - svg.iconnables( - ng-if = "!state.showError" - ) - use( - ng-if = "$root.featureFlags.aha1 && !$root.featureFlags.aha2 && !$root.featureFlags.aha3 && state.showSubStep != 9" - xlink:href = "#icons-octicons-repo" - ) - use( - ng-if = "$root.featureFlags.aha2 || $root.featureFlags.aha3" - xlink:href = "#icons-check" - ) - svg.iconnables.icons-alert( - ng-if = "state.showError" - ) - use( - xlink:href = "#icons-alert-alt" - ) - -.grid-block.vertical.aha-text - .grid-block( - ng-if = "$root.canEditFeatureFlags() && state.showSubStep > 0" - ng-show = "state.showControls" - style = "position: absolute; right: 0; top: 0;" - ) - a.grid-content.shrink.red.small.link( - ng-click = "state.showVerification = !state.showVerification" - ng-if = "state.showSubStep === 7" - ) Verify - a.grid-content.shrink.red.small.link( - ng-click = "\ - state.showError = !state.showError;\ - state.showErrorType = 'CMD';\ - " - ng-if = "state.showSubStep === 7" - ) CMDErr - a.grid-content.shrink.red.small.link( - ng-click = "\ - state.showError = !state.showError;\ - state.showErrorType = 'build';\ - " - ng-if = "state.showSubStep === 7" - ) BuildErr - a.grid-content.shrink.red.small.link( - ng-click = "\ - $root.featureFlags.aha1 = false;\ - $root.featureFlags.aha2 = true;\ - " - ng-if = "state.showSubStep === 8" - ) NextMile - a.grid-content.shrink.red.small.link( - ng-click = "state.showSubStep = state.showSubStep - 1" - ng-if = "state.showSubStep < 9 && state.showSubStep > 1" - ) Prev - a.grid-content.shrink.red.small.link( - ng-click = "state.showSubStep = state.showSubStep + 1" - ng-if = "state.showSubStep < 8" - ) Next - p.p.small.text-gray-light Add your First Repository - p.p( - ng-if = "state.showSubStep === 0 && !state.showError && !state.showVerification" - ) Add your repository by clicking ‘Add Configuration’. - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 1 && !state.showError && !state.showVerification" - ) Select a repository to configure. - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 2 && !state.showError && !state.showVerification" - ) How would you like to configure your repo? - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 3 && !state.showError && !state.showVerification" - ) Give your configuration a name. - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 4 && !state.showError && !state.showVerification" - ) What does your repository run? - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 5 && !state.showError && !state.showVerification" - ) Choose commands and packages. - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 6 && !state.showError && !state.showVerification" - ) - | {{!state.fromMirroring ? 'If your app needs additional configuration…' : ''}} - | {{state.fromMirroring ? 'We‘ve imported your dockerfile, click ‘Save & Build’ to build it!' : ''}} - //- | FROM BLANK DOCKERFILE: When you‘re done editing your dockerfile, click ‘Save & Build’ to build it! - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 7" - ) - | {{!state.showError && !state.showVerification ? 'Now building. Build time varies depending on your configuration.' : ''}} - | {{state.showVerification ? 'Verifying Configuration…' : ''}} - | {{state.showErrorType === 'build' && state.showError ? 'Your build failed. Inspect your build logs for more information.' : ''}} - | {{state.showErrorType === 'CMD' && state.showError ? 'Your container failed to run. Inspect your CMD logs for more information.' : ''}} - //- | IF DETENTION ERROR: Your container is running! But it looks like something is misconfigured. - span.span( - ng-if = "state.showErrorType === 'exitedEarly'" - ) Your repository isn‘t running yet! Check the logs to debug any issues. If you‘re stumped, - //- this link should open in intercom with the prefilled message: - "I’m having trouble getting my first container up and running." - a.link ask our engineers - | ! - - p.p( - ng-class = "{'p-slide js-animate': state.showSubStep}" - ng-if = "state.showSubStep === 8 && !state.showError && !state.showVerification" - ) Your build is looking good! Check out its URL and click ‘Done’ if it looks good to you. - - //- in the popover - p.p( - ng-if = "$root.featureFlags.aha2" - ) Add more containers if your project requires it. Once you’re done, head to your containers to start adding branches. diff --git a/client/directives/components/ahaGuide/components/setUpRunnabotGuideView.jade b/client/directives/components/ahaGuide/components/setUpRunnabotGuideView.jade index 6d7575e11..bc352c4a5 100644 --- a/client/directives/components/ahaGuide/components/setUpRunnabotGuideView.jade +++ b/client/directives/components/ahaGuide/components/setUpRunnabotGuideView.jade @@ -1,14 +1,2 @@ -.grid-block.shrink.aha-meter( - ng-class = "{'aha-meter-50': $root.featureFlags.aha3}" -) - svg.iconnables - use( - xlink:href = "#icons-runnabot" - ) - //- if complete - //- use( - //- xlink:href = "#icons-check" - //- ) -.grid-block.vertical.aha-text - p.p.small.text-gray-light Set Up Runnabot - p.p Set up auto-branching before configuring Runnabot. +.grid-block.vertical.runnabot-text + p.p Get the most out of Runnabot by adding branches automatically. diff --git a/client/directives/components/ahaGuide/setupRepositoryGuide/setUpRepositoryGuideView.jade b/client/directives/components/ahaGuide/setupRepositoryGuide/setUpRepositoryGuideView.jade new file mode 100644 index 000000000..31b31bb3f --- /dev/null +++ b/client/directives/components/ahaGuide/setupRepositoryGuide/setUpRepositoryGuideView.jade @@ -0,0 +1,129 @@ +.grid-block.shrink.aha-meter.js-animate( + ng-class = "{\ + 'aha-error': AGC.showError || AGC.errorState,\ + 'aha-meter-11': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'addRepository',\ + 'aha-meter-22': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'containerSelection',\ + 'aha-meter-33': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'dockerfileMirroring',\ + 'aha-meter-44': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'nameContainer',\ + 'aha-meter-55': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'repository',\ + 'aha-meter-66': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'commands',\ + 'aha-meter-77': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'buildfiles' || AGC.ahaGuide.furthestStep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'default' || AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'env' || AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'files' || AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'filesMirror' || AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'ports' || AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'translation',\ + 'aha-meter-88': AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'logs' || AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'exitedEarly',\ + 'aha-meter-100': (AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'success') || getCurrentStep() > steps.ADD_FIRST_REPO,\ + }" +) + svg.iconnables( + ng-if = "!AGC.showError && !AGC.errorState" + ) + use( + ng-if = "AGC.ahaGuide.isAddingFirstRepo() && AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) !== 'success'" + xlink:href = "#icons-octicons-repo" + ) + use( + ng-if = "AGC.ahaGuide.furthestSubstep(AGC.ahaGuide.steps.ADD_FIRST_REPO) === 'success' || AGC.ahaGuide.getCurrentStep() > AGC.ahaGuide.steps.ADD_FIRST_REPO" + xlink:href = "#icons-check" + ) + svg.iconnables.icons-alert( + ng-if = "AGC.showError || AGC.errorState" + ) + use( + xlink:href = "#icons-alert-alt" + ) + +.grid-block.shrink.aha-meter.js-animate( + class = "aha-meter-100" + ng-class = "{'aha-error': AGC.showError || AGC.errorState}" + ng-if = "!state.showVerification && AGC.ahaGuide.getCurrentStep() > AGC.ahaGuide.steps.ADD_FIRST_REPO" +) + svg.iconnables( + ng-if = "!AGC.showError" + ) + use( + ng-if = "AGC.subStep === 'success' || AGC.isBuildSuccessful" + xlink:href = "#icons-check" + ) + svg.iconnables.icons-alert( + ng-if = "AGC.showError" + ) + use( + xlink:href = "#icons-alert-alt" + ) + +.grid-block.vertical.aha-text( + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.ahaGuide.isAddingFirstRepo()" +) + p.p.small.text-gray-light {{ AGC.title }} + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'addRepository'" + ) First, add your repository by clicking ‘Create Template’. + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'containerSelection'" + ) Select a service to configure. + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'dockerfileMirroring'" + ) How would you like to configure your repository? + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'nameContainer'" + ) Give your template a name. + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'repository'" + ) What does your repository run? + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'commands'" + ) Configure commands and packages. + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.configSteps.includes(AGC.subStep)" + ) Configure additional settings (if necessary). + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'buildfiles'" + ) When you’re done editing your Dockerfile, click ‘Save and Build’. + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'filesMirror'" + ) We’ve imported your Dockerfile. Click ‘Save & Build’ to build it! + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'logs'" + ) We‘re building! Build time varies depending on your build commands. + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError && AGC.subStep === 'success'" + ) Your build finished! Verify that it looks good, then click ‘Done’. + + +.grid-block.vertical.aha-text( + ng-if = "AGC.isInGuide() && AGC.showError && AGC.subStepIndex > 5" +) + p.p.small.text-gray-light {{ AGC.title }} + p.p( + ng-if = "AGC.isInGuide() && AGC.showError && AGC.errorState === 'buildFailed' && AGC.subStep === 'logs'" + ) Uh oh, there was an error! Inspect your logs for debug info. + + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && (AGC.errorState === 'nonRunningContainer' || AGC.subStep === 'exitedEarly')" + ) Your template isn’t running yet! Check the logs to debug any issues. If you’re stumped, + a.link( + href = "https://support.runnable.com/hc/en-us/articles/212930166" + target = "_blank" + ) chat with our devs + | . + + +.grid-block.vertical.aha-text( + class = "{{ AGC.className }}" + ng-if = "AGC.isInGuide() && !AGC.showError && !AGC.ahaGuide.isAddingFirstRepo()" +) + p.p.small.text-gray-light {{ AGC.title }} + p.p( + ng-class = "{'p-slide js-animate': AGC.subStepIndex}" + ng-if = "AGC.isInGuide() && !AGC.showError" + ) Choose a template to configure. diff --git a/client/directives/components/ahaGuide/setupRepositoryGuide/setupRepositoryGuideDirective.js b/client/directives/components/ahaGuide/setupRepositoryGuide/setupRepositoryGuideDirective.js new file mode 100644 index 000000000..29a7fa48b --- /dev/null +++ b/client/directives/components/ahaGuide/setupRepositoryGuide/setupRepositoryGuideDirective.js @@ -0,0 +1,26 @@ +'use strict'; + +require('app') + .directive('setupRepositoryGuide', setupRepositoryGuide); + +function setupRepositoryGuide( + ahaGuide +) { + return { + restrict: 'A', + templateUrl: 'setupRepositoryGuideView', + scope: true, + link: function ($scope, elem, attrs) { + $scope.ahaGuide = { + steps: ahaGuide.steps, + getCurrentStep: ahaGuide.getCurrentStep + }; + $scope.askEngineers = function () { + window.Intercom( + 'showNewMessage', + 'I’m having trouble getting my first container up and running.' + ); + }; + } + }; +} diff --git a/client/directives/components/ahaGuide/setupRepositoryGuide/staticAhaGuideTemplates.jade b/client/directives/components/ahaGuide/setupRepositoryGuide/staticAhaGuideTemplates.jade new file mode 100644 index 000000000..53e06546a --- /dev/null +++ b/client/directives/components/ahaGuide/setupRepositoryGuide/staticAhaGuideTemplates.jade @@ -0,0 +1,25 @@ +.grid-block.shrink.aha-meter.js-animate( + ng-class = "{\ + 'aha-meter-10': staticAddRepo,\ + 'aha-meter-100': showAhaNavPopover\ + }" +) + svg.iconnables( + ) + use( + ng-if = "staticAddRepo" + xlink:href = "#icons-octicons-repo" + ) + use( + ng-if = "showAhaNavPopover" + xlink:href = "#icons-check" + ) + +.grid-block.vertical.aha-text + p.p.small.text-gray-light Step 2: Configure your Application + p.p( + ng-if = "staticAddRepo" + ) Add your repository by clicking ‘Create Template’. + p.p( + ng-if = "showAhaNavPopover" + ) Once you’re done configuring, head to your containers to start adding branches. diff --git a/client/directives/components/buildLogs/buildLogsView.jade b/client/directives/components/buildLogs/buildLogsView.jade index 89404ad7d..dbec4ecaa 100644 --- a/client/directives/components/buildLogs/buildLogsView.jade +++ b/client/directives/components/buildLogs/buildLogsView.jade @@ -11,11 +11,11 @@ ) p.p( ng-if = "!BLC.showNoDockerfileError" - ) Sorry, we ran into an issue trying to build your container. + ) Sorry, we ran into an issue while building. p.p( ng-if = "BLC.showNoDockerfileError" ) - | We couldn't find a Dockerfile to build your container with. + | We couldn't find a Dockerfile to build. | Does it exist in your repository? button.btn.btn-sm.purple( ng-click = "BLC.actions.rebuildWithoutCache()" @@ -24,7 +24,7 @@ | or a.link.icons-intercom( ng-click = "BLC.actions.openIntercom()" - ) chat with a dev + ) chat with our devs .pre.build-log( ng-if = "\ diff --git a/client/directives/components/buildLogs/viewPopoverDebug.jade b/client/directives/components/buildLogs/viewPopoverDebug.jade index 4365d1a84..a5e954d4d 100644 --- a/client/directives/components/buildLogs/viewPopoverDebug.jade +++ b/client/directives/components/buildLogs/viewPopoverDebug.jade @@ -1,10 +1,10 @@ .popover.menu.bottom.popover-debug( - ng-class = "{in: active}" + ng-class = "{'in': active}" ng-style = "popoverStyle.getStyle()" style = "transform-origin: 90% 0;" ) .arrow.white( - style = "left: auto; right: 1px;" + style = "left: auto; right: 3px;" ) .popover-content .list.popover-list diff --git a/client/directives/components/containerStatusButton/containerStatusButtonView.jade b/client/directives/components/containerStatusButton/containerStatusButtonView.jade index 888a345e2..d13e26da3 100644 --- a/client/directives/components/containerStatusButton/containerStatusButtonView.jade +++ b/client/directives/components/containerStatusButton/containerStatusButtonView.jade @@ -19,7 +19,7 @@ button.btn.btn-md.btn-status( pop-over-actions = "CSBC.actions" pop-over-active = "CSBC.popoverActive" pop-over-controller = "CSBC" - pop-over-options = "{\"centered\":true,\"top\":45}" + pop-over-options = "{\"centered\":true,\"top\":46}" pop-over-template = "containerStatusOptionsPopoverView" pop-over-trigger = "activeAttr" ) diff --git a/client/directives/components/containerStatusButton/containerStatusOptionsPopoverView.jade b/client/directives/components/containerStatusButton/containerStatusOptionsPopoverView.jade index 0891951c9..755511ba1 100644 --- a/client/directives/components/containerStatusButton/containerStatusOptionsPopoverView.jade +++ b/client/directives/components/containerStatusButton/containerStatusOptionsPopoverView.jade @@ -31,7 +31,7 @@ ng-if = "!CSBC.instance.isMigrating() && !CSBC.isTesting() && ['running', 'stopped', 'crashed'].includes(CSBC.instance.status())" ) - //- rebuild without cache when no new config exists for this container + //- rebuild without cache when no new template exists for this container li.popover-list-item.multi-line( ng-click = "\ (!CSBC.instance.attrs.isolated && !CSBC.doesMatchMasterPod()) ? \ @@ -43,19 +43,19 @@ | {{CSBC.isTesting() ? 'Rerun Test' : 'Rebuild'}} .small {{CSBC.isTesting() ? 'Will rerun tests without cache.' : 'Will rebuild without cache.'}} - //- rebuild without cache when no new config exists for this container + //- rebuild without cache when no new template exists for this container li.popover-list-item.multi-line( ng-if = "$root.featureFlags.allowIsolatedUpdate && CSBC.instance.attrs.isolated && !CSBC.doesMatchMasterPod()" ng-click = "actions.updateConfigToMatchMaster()" ) .icons-status.orange - | Update Config and Rebuild - .small Will rebuild and update the configuration. + | Update Template and Rebuild + .small Will rebuild and update the template. .well.well-popover.orange.text-center.padding-xxs.small( ng-if = "(!CSBC.instance.attrs.isolated || $root.featureFlags.allowIsolatedUpdate) && !CSBC.doesMatchMasterPod()" - ) This container’s configuration has been changed. Rebuild to update. + ) This container’s template has been changed. Rebuild to update. - //- only show if the configuration has not been changed + //- only show if the template has not been changed .well.well-popover.orange.text-center.padding-xxs.small( ng-if = "['buildFailed', 'neverStarted'].includes(CSBC.instance.status()) && CSBC.doesMatchMasterPod()" ) Having build problems? Some errors can be resolved by rebuilding. diff --git a/client/directives/components/dnsConfiguration/dnsConfigurationPopoverView.jade b/client/directives/components/dnsConfiguration/dnsConfigurationPopoverView.jade index d4c0cce5b..316affea6 100644 --- a/client/directives/components/dnsConfiguration/dnsConfigurationPopoverView.jade +++ b/client/directives/components/dnsConfiguration/dnsConfigurationPopoverView.jade @@ -99,7 +99,7 @@ ul.list.popover-list( ng-if = "isActivePanel() && DCC.nonRepoDependencies.length !== 0" ) - li.list-item.small Service Containers + li.list-item.small Non-repository Containers li.list-item.popover-list-item.multi-line.disabled.grid-block.vertical.align-start( ng-repeat = "dependency in DCC.nonRepoDependencies" ) diff --git a/client/directives/components/editRepoCommit/autoDeployTooltip.jade b/client/directives/components/editRepoCommit/autoDeployTooltip.jade index 9f0f4e580..c928f143a 100644 --- a/client/directives/components/editRepoCommit/autoDeployTooltip.jade +++ b/client/directives/components/editRepoCommit/autoDeployTooltip.jade @@ -4,4 +4,4 @@ ) .arrow //- if disabled: Enable auto-deploy to automatically rebuild your container when new commits are pushed to GitHub. - .small.text-center Your container will rebuild when new commits are pushed to GitHub. + .small.text-center Will rebuild when new commits are pushed to GitHub. diff --git a/client/directives/components/editRepoCommit/editRepoCommitView.jade b/client/directives/components/editRepoCommit/editRepoCommitView.jade index d4d8e1055..a23d7e43a 100644 --- a/client/directives/components/editRepoCommit/editRepoCommitView.jade +++ b/client/directives/components/editRepoCommit/editRepoCommitView.jade @@ -3,7 +3,6 @@ ng-class = "{\ 'grid-block align-center justify-center': !activeCommit.attrs.commit.message,\ 'main-btn': !acv.attrs.additionalRepo,\ - 'deprecated': !acv.attrs.additionalRepo && !$root.featureFlags.testingFeature\ }" ng-click = "actions.openRepoDetailsModal()" ) @@ -47,7 +46,7 @@ ng-include = "'userButtonView'" pop-over pop-over-active = "state.active" - pop-over-options = "{\"left\":-24,\"top\":26}" + pop-over-options = "{\"left\":-23,\"top\":26}" pop-over-template = "userPopoverView" ) span.small( @@ -57,16 +56,12 @@ span.small( ng-if = "!$root.featureFlags.inviteFlows" ) {{activeCommit.attrs.commit.author.date | timeFrom}} - svg.iconnables.icons-arrow-forward - use( - xlink:href = "#icons-arrow-down" - ) //- auto-deploy w/ commit syncing //- show alternate text in autoDeployTooltip.jade if disabled .btn.btn-xs.btn-auto-deploy( ng-class = "{'btn-permissions': autoDeploy()}" - ng-if = "!acv.attrs.additionalRepo && $root.featureFlags.testingFeature" + ng-if = "!acv.attrs.additionalRepo" pop-over pop-over-hover-trigger pop-over-options = "{\"top\":30,\"centered\":true}" @@ -82,36 +77,4 @@ ng-if = "autoDeploy()" xlink:href = "#icons-alert-alt" ) - | {{autoDeploy() ? 'Auto-Deploy Disabled' : 'Auto-Deploying'}} - -//- auto-deploy w/o commit syncing -.btn.btn-sm.btn-auto-deploy-deprecated( - ng-if = "!acv.attrs.additionalRepo && !$root.featureFlags.testingFeature" -) - .iconnables.icons-launch.float-left 🚀 - span.underline.float-left( - tooltip = "Automatically rebuild your container when new commits are made." - tooltip-options = "{\"class\":\"bottom center text-center tooltip-definition\",\"left\":-85,\"top\":18}" - ) Auto-Deploy - button.btn.btn-xs.btn-permissions.float-right( - internal-modal-helper = "inviteAdminModalView" - ng-if = "$root.featureFlags.webhooks" - ) - svg.iconnables.icons-alert.float-left - use( - xlink:href = "#icons-alert-alt" - ) - | Permissions - label.toggle-wrapper.float-right - //- this would not be enabled when an webhooks admin is not present - input.toggle-input( - ng-disabled = "$root.isLoading.autoDeploy || $root.featureFlags.webhooks" - ng-false-value = "true" - ng-model = "autoDeploy" - ng-model-options = "{getterSetter: true}" - ng-true-value = "false" - type = "checkbox" - ) - .toggle-group.toggle-xs( - ng-if = "!$root.featureFlags.webhooks || $root.featureFlags.webhooksAdminPresent" - ) + | {{autoDeploy() ? 'Auto-Deploy Disabled' : 'Auto-Deploy Enabled'}} diff --git a/client/directives/components/explorer/explorerSectionHeading.jade b/client/directives/components/explorer/explorerSectionHeading.jade index a58fa70da..8c061f0b9 100644 --- a/client/directives/components/explorer/explorerSectionHeading.jade +++ b/client/directives/components/explorer/explorerSectionHeading.jade @@ -5,7 +5,7 @@ button.btn.gray.btn-xs.btn-icon( pop-over-actions = "FPC.actions" pop-over-active = "filePopover.data.show" pop-over-data = "filePopover.data" - pop-over-options = "{\"top\":36,\"centered\":true}" + pop-over-options = "{\"top\":35,\"centered\":true}" pop-over-template = "viewFileTreePopoverFileExplorerMenu" ) svg.iconnables.icons-add diff --git a/client/directives/components/explorer/explorerView.jade b/client/directives/components/explorer/explorerView.jade index ebccd4302..f2fac6139 100644 --- a/client/directives/components/explorer/explorerView.jade +++ b/client/directives/components/explorer/explorerView.jade @@ -15,7 +15,7 @@ pop-over pop-over-actions = "FPC.actions" pop-over-data = "filePopover.data" - pop-over-options = "{\"left\":-84,\"top\":35}" + pop-over-options = "{\"top\":35,\"centered\":true}" pop-over-template = "viewFileTreePopoverFileExplorerMenu" pop-over-active = "filePopover.data.show" ) diff --git a/client/directives/components/explorer/fileTreeDirDirective.js b/client/directives/components/explorer/fileTreeDirDirective.js index e7794e600..d810c696c 100755 --- a/client/directives/components/explorer/fileTreeDirDirective.js +++ b/client/directives/components/explorer/fileTreeDirDirective.js @@ -37,7 +37,6 @@ function fileTreeDir( link: function ($scope, element) { var actions = $scope.actions = {}; - $scope.data = {}; var inputElement = element[0].querySelector('input.tree-input'); $scope.editFolderName = false; diff --git a/client/directives/components/explorer/fileTreeDirItemView.jade b/client/directives/components/explorer/fileTreeDirItemView.jade index fda349ca7..bc2e5ce92 100644 --- a/client/directives/components/explorer/fileTreeDirItemView.jade +++ b/client/directives/components/explorer/fileTreeDirItemView.jade @@ -84,7 +84,7 @@ li.file( ng-readonly = "!fs.state.renaming" select-on = "fs.state.renaming" value = "{{fs.attrs.name}}" - ) {{fs.attrs.name}} + ) span.item-name {{fs.attrs.name}} li.folder.in( diff --git a/client/directives/components/explorer/fileTreeDirView.jade b/client/directives/components/explorer/fileTreeDirView.jade index c8490b331..f96ec2c34 100755 --- a/client/directives/components/explorer/fileTreeDirView.jade +++ b/client/directives/components/explorer/fileTreeDirView.jade @@ -45,7 +45,7 @@ li.folder( pop-over-actions = "popoverFilesRepositoryCommitToggle.actions" pop-over-active = "state.showAddRepo" pop-over-data = "popoverFilesRepositoryCommitToggle.data" - pop-over-options = "{\"left\":207,\"top\":-222}" + pop-over-options = "{\"left\":203,\"top\":-220}" pop-over-template = "viewPopoverFilesRepositoryCommitToggle" pop-over-trigger = "activeAttr" ) @@ -79,7 +79,7 @@ li.folder( pop-over-actions = "popoverFilesRepositoryCommitToggle.actions" pop-over-active = "acv.editing" pop-over-data = "popoverEditRepoCommit.data" - pop-over-options = "{\"left\":207,\"top\":-222}" + pop-over-options = "{\"left\":203,\"top\":-220}" pop-over-template = "viewPopoverFilesRepositoryCommitToggle" pop-over-trigger = "activeAttr" ) diff --git a/client/directives/components/gitHubIntegration/githubIntegrationController.js b/client/directives/components/gitHubIntegration/githubIntegrationController.js new file mode 100644 index 000000000..fc8114367 --- /dev/null +++ b/client/directives/components/gitHubIntegration/githubIntegrationController.js @@ -0,0 +1,60 @@ +'use strict'; + +require('app') + .controller('GithubIntegrationController', GithubIntegrationController); +/** + * @ngInject + */ +function GithubIntegrationController( + $interval, + $q, + $scope, + ahaGuide, + currentOrg, + errs, + fetchGithubUserIsAdminOfOrg, + isRunnabotPartOfOrg, + keypather, + loading +) { + var GIC = this; + var org = keypather.get(currentOrg, 'github.attrs.login'); + GIC.organizationName = org; + + function checkRunnabot() { + return isRunnabotPartOfOrg(org) + .then(function (hasRunnabot) { + GIC.hasRunnabot = hasRunnabot; + if (hasRunnabot) { + if (GIC.pollingInterval) { + $interval.cancel(GIC.pollingInterval); + } + return ahaGuide.hasRunnabot(); + } + }) + .catch(errs.handler); + } + + loading.reset('checkRunnabot'); + loading('checkRunnabot', true); + $q.all({ + isAdmin: fetchGithubUserIsAdminOfOrg(org), + hasRunnabot: checkRunnabot() + }) + .then(function (results) { + GIC.isAdmin = results.isAdmin; + }) + .catch(errs.handler) + .finally(function () { + loading('checkRunnabot', false); + }); + + GIC.pollCheckRunnabot = function () { + GIC.pollingInterval = $interval(checkRunnabot, 2000); + }; + + $scope.$on('$destroy', function () { + $interval.cancel(GIC.pollingInterval); + }); +} + diff --git a/client/directives/components/gitHubIntegration/githubIntegrationDirective.js b/client/directives/components/gitHubIntegration/githubIntegrationDirective.js new file mode 100644 index 000000000..0e9610c0f --- /dev/null +++ b/client/directives/components/gitHubIntegration/githubIntegrationDirective.js @@ -0,0 +1,20 @@ +'use strict'; + +require('app') + .directive('githubIntegration', githubIntegration); +/** + * @ngInject + */ +function githubIntegration( +) { + return { + restrict: 'A', + templateUrl: 'githubIntegrationView', + controller: 'GithubIntegrationController', + controllerAs: 'GIC', + bindToController: true, + scope: { + state: '=' + } + }; +} diff --git a/client/directives/components/gitHubIntegration/githubIntegrationView.jade b/client/directives/components/gitHubIntegration/githubIntegrationView.jade new file mode 100644 index 000000000..1519f6365 --- /dev/null +++ b/client/directives/components/gitHubIntegration/githubIntegrationView.jade @@ -0,0 +1,73 @@ +p.grid-content.shrink.p.text-center( + ng-if = "!$root.featureFlags.ahaBranchUrlStep" +) Now you can invite our bot to your GitHub org to get notifications on your pull requests: + +p.grid-content.shrink.p.text-center( + ng-if = "$root.featureFlags.ahaBranchUrlStep" +) Invite our bot to your GitHub org to get notifications on your pull requests: + +img.grid-content.shrink.img.img-comment( + alt = "Runnabot sample comment on a GitHub pull request." + height = "206" + src = "/build/images/runnabot-comment.png" + width = "358" +) + +//- if checking whether the user is an admin, or checking whether runnabot has been enabled +.grid-content.spinner-wrapper.spinner-md.spinner-gray( + ng-if = "$root.isLoading.checkRunnabot" + ng-include = "'spinner'" +) + +//- add 'disabled' attr if inviting runnabot, or if user isn't an admin + hide after successfully inviting runnabot +a.grid-content.shrink.btn.btn-md.green( + ng-disabled = "!GIC.isAdmin" + ng-click = "GIC.pollCheckRunnabot()" + ng-hide = "$root.isLoading.checkRunnabot || GIC.hasRunnabot" + ng-href = "https://github.com/orgs/{{GIC.organizationName}}/invitations/runnabot/edit" + target = "_blank" +) + svg.iconnables.icons-octicons-github + use( + xlink:href = "#icons-octicons-github" + ) + | Invite Runnabot + svg.iconnables.icons-link-external + use( + xlink:href = "#icons-link-external" + ) + +//- show after successfully inviting runnabot +.grid-block.align-center.shrink.runnabot-success( + ng-show = "GIC.hasRunnabot && !$root.isLoading.checkRunnabot" +) + img.grid-content.shrink.img( + height = "36" + src = "/build/images/runnabot-head.png" + width = "36" + ) + .grid-content.well.padding-xs.ignore-margin + .arrow + p.small.text-gray.text-left Thanks! See you soon on your pull requests. + +.grid-content.shrink.small.text-center( + ng-hide = "$root.isLoading.checkRunnabot" + ng-class = "{\ + 'text-gray': GIC.isAdmin,\ + 'text-red': !GIC.isAdmin\ + }" +) + span( + ng-if = "GIC.isAdmin" + ) This may affect your GitHub bill. + span( + ng-if = "!GIC.isAdmin" + ) Sorry, you’ll need help from an admin + br + | of your org to invite Runnabot. + + //- hiding until praful writes his doc + //- span + br + a.small.link More about Runnabot diff --git a/client/directives/components/instanceNavigtion/confirmBranchRemoveView.jade b/client/directives/components/instanceNavigtion/confirmBranchRemoveView.jade index 36f0a51bb..4a952bb19 100644 --- a/client/directives/components/instanceNavigtion/confirmBranchRemoveView.jade +++ b/client/directives/components/instanceNavigtion/confirmBranchRemoveView.jade @@ -1,8 +1,12 @@ .modal-backdrop.in .modal-dialog.modal-sm.modal-alert header.modal-body - p.p.strong Are you sure you want to remove this branch from your sandbox? - p.p You can add this branch again later, but any configuration changes will not be saved. + p.p.strong Are you sure you want to remove this branch from Runnable? + p.p You can add this branch again later, but any changes you’ve made will not be saved. footer.modal-footer.clearfix - button.btn.btn-sm.gray.float-left Cancel - button.btn.btn-sm.red.float-right Delete Branch \ No newline at end of file + button.btn.btn-sm.gray.float-left( + ng-click = "CMC.actions.cancel()" + ) Cancel + button.btn.btn-sm.red.float-right( + ng-click = "CMC.actions.confirm()" + ) Remove Branch diff --git a/client/directives/components/instanceNavigtion/confirmConfigDiscardView.jade b/client/directives/components/instanceNavigtion/confirmConfigDiscardView.jade index f68f8b8b9..88e95b41c 100644 --- a/client/directives/components/instanceNavigtion/confirmConfigDiscardView.jade +++ b/client/directives/components/instanceNavigtion/confirmConfigDiscardView.jade @@ -1,8 +1,8 @@ .modal-backdrop.in .modal-dialog.modal-sm.modal-alert header.modal-body - p.p.strong Are you sure you want to discard this configuration? - p.p You will lose any changes you have made, and this container will rebuild using the master configuration. + p.p.strong Are you sure you want to discard your changes? + p.p You will lose any changes you‘ve made, and this container will rebuild with the template configuration. footer.modal-footer.clearfix button.btn.btn-sm.gray.float-left Cancel - button.btn.btn-sm.red.float-right Discard Configuration \ No newline at end of file + button.btn.btn-sm.red.float-right Discard Changes diff --git a/client/directives/components/instanceNavigtion/instanceNavigationController.js b/client/directives/components/instanceNavigtion/instanceNavigationController.js index a9e4fb9c7..30d69d25b 100644 --- a/client/directives/components/instanceNavigtion/instanceNavigationController.js +++ b/client/directives/components/instanceNavigtion/instanceNavigationController.js @@ -127,6 +127,23 @@ function InstanceNavigationController( .catch(errs.handler); }; + INC.removeBranch = function () { + $rootScope.$broadcast('close-popovers'); + ModalService.showModal({ + controller: 'ConfirmationModalController', + controllerAs: 'CMC', + templateUrl: 'confirmBranchRemoveView' + }) + .then(function (modal) { + modal.close.then(function (confirmed) { + if (confirmed) { + promisify(INC.instance, 'destroy')(); + } + }); + }) + .catch(errs.handler); + }; + this.editInstance = function (event) { $rootScope.$broadcast('close-popovers'); event.stopPropagation(); diff --git a/client/directives/components/instanceNavigtion/instanceNavigationInternalsView.jade b/client/directives/components/instanceNavigtion/instanceNavigationInternalsView.jade index 87608f684..5997439a0 100644 --- a/client/directives/components/instanceNavigtion/instanceNavigationInternalsView.jade +++ b/client/directives/components/instanceNavigtion/instanceNavigationInternalsView.jade @@ -13,18 +13,18 @@ a.grid-block.align-center.a-sref( ) //- show repo names for repo containers in the master cluster span( - ng-if = "INC.instance.attrs.masterPod && INC.instance.getBranchName() && $root.featureFlags.autoIsolation" + ng-if = "INC.instance.attrs.masterPod && INC.instance.getBranchName() && $root.featureFlags.addBranches" ) {{INC.instance.getName()}}/ //- branch name or service name | {{getNavigationName()}} .grid-block.shrink.btn.btn-xxs.btn-badge( - ng-if = "INC.instance.attrs.isIsolationGroupMaster && !$root.featureFlags.autoIsolation" + ng-if = "INC.instance.attrs.isIsolationGroupMaster && !$root.featureFlags.addBranches" ) Isolated svg.grid-block.shrink.iconnables.icons-overflow( ng-class = "{'active': INC.popoverShown}" - ng-if = "!$root.featureFlags.autoIsolation && ((INC.masterInstance !== INC.instance && INC.instance.getBranchName()) || (!INC.instance.attrs.isIsolationGroupMaster && INC.instance.attrs.isolated))" + ng-if = "!$root.featureFlags.addBranches && ((INC.masterInstance !== INC.instance && INC.instance.getBranchName()) || (!INC.instance.attrs.isIsolationGroupMaster && INC.instance.attrs.isolated))" pop-over pop-over-active = "INC.popoverShown" pop-over-controller = "INC" @@ -42,23 +42,23 @@ a.grid-block.align-center.a-sref( pop-over-controller = "INC" pop-over-options = "{\"verticallyCentered\":true,\"left\":28}" pop-over-template = "instanceNavigationPopoverView" - ng-if = "$root.featureFlags.autoIsolation" + ng-if = "$root.featureFlags.addBranches" ) svg.iconnables - //- if this container's configuration is *not* modified use( + ng-if = "$root.featureFlags.addBranches && !INC.instance.attrs.isIsolationGroupMaster" xlink:href = "#icons-gear" ) - //- if this container's configuration *is* modified - //- use( - //- xlink:href = "#icons-gear-modified" - //- ) + use( + ng-if = "$root.featureFlags.addBranches && INC.instance.attrs.isIsolationGroupMaster || INC.instance.attrs.isolated" + xlink:href = "#icons-gear-modified" + ) //- config button for non-repo containers (pre auto-isolation) svg.grid-block.shrink.iconnables.icons-gear( ng-click="INC.editInstance($event)" - ng-if = "$root.featureFlags.editAnyInstance || INC.instance.attrs.masterPod && !INC.instance.getBranchName() && !INC.instance.attrs.isolated && !$root.featureFlags.autoIsolation" - title = "Configure" + ng-if = "$root.featureFlags.editAnyInstance || INC.instance.attrs.masterPod && !INC.instance.getBranchName() && !INC.instance.attrs.isolated && !$root.featureFlags.addBranches" + title = "Configure Template" ) use( xlink:href = "#icons-gear" diff --git a/client/directives/components/instanceNavigtion/instanceNavigationPopoverView.jade b/client/directives/components/instanceNavigtion/instanceNavigationPopoverView.jade index 390700048..94709d496 100644 --- a/client/directives/components/instanceNavigtion/instanceNavigationPopoverView.jade +++ b/client/directives/components/instanceNavigtion/instanceNavigationPopoverView.jade @@ -6,19 +6,37 @@ .popover-content( ng-if = "$root.featureFlags.autoIsolation" ) - ul.list.popover-list - - //- if this is a container in the master cluster - //- li.list-item.popover-list-item.multi-line + ul.list.popover-list( + ng-if = "INC.instance.attrs.masterPod" + ) + li.list-item.popover-list-item.multi-line( + ng-click = "INC.editInstance($event)" + ) svg.iconnables.icons-gear use( xlink:href = "#icons-gear" ) - | Configure - .small Affects all [config-name] containers. + | Configure Template + .small Affects all non-isolated [config-name] containers. + li.divider( + ng-if = "$root.featureFlags.containersViewTemplateControls" + ) + li.list-item.popover-list-item( + internal-modal-helper = "confirmDeleteServerView" + ng-if = "$root.featureFlags.containersViewTemplateControls" + ) + svg.iconnables.icons-delete + use( + xlink:href = "#icons-delete" + ) + | Delete Template - //- if this is a container that is not part of the master cluster - li.list-item.popover-list-item.multi-line + ul.list.popover-list( + ng-if = "!INC.instance.attrs.masterPod" + ) + li.list-item.popover-list-item.multi-line( + ng-click = "INC.editInstance($event)" + ) svg.iconnables.icons-gear use( xlink:href = "#icons-gear-modified" @@ -26,7 +44,7 @@ | Configure .small Affects only this container. - //- if this container has a modified configuration + //- if this container has a modified template //- li.list-item.popover-list-item.multi-line( //- internal-modal-helper = "confirmConfigDiscardView" //- ) @@ -34,14 +52,16 @@ use( xlink:href = "#icons-gear-delete" ) - | Discard Configuration - .small Restores master configuration. + | Discard Changes + .small To restore template configuration - //- if this is the primary container of an isolated cluster - //- li.list-item.popover-list-item.divider - //- li.list-item.popover-list-item( - //- internal-modal-helper = "confirmBranchRemoveView" - //- ) + li.list-item.popover-list-item.divider( + ng-if = "INC.instance.attrs.isIsolationGroupMaster" + ) + li.list-item.popover-list-item( + ng-click = "INC.removeBranch()" + ng-if = "INC.instance.attrs.isIsolationGroupMaster" + ) svg.iconnables use( xlink:href = "#icons-delete" @@ -49,27 +69,117 @@ | Remove Branch .popover-content( - ng-if = "!$root.featureFlags.autoIsolation" + ng-if = "$root.featureFlags.addBranches && !$root.featureFlags.autoIsolation" + ) + ul.list.popover-list( + ng-if = "INC.instance.attrs.masterPod" + ) + li.list-item.popover-list-item.multi-line( + ng-click = "INC.editInstance($event)" + ) + svg.iconnables.icons-gear + use( + xlink:href = "#icons-gear" + ) + | Configure Template + .small Affects all non-isolated {{ INC.instance.attrs.name }} containers. + li.divider( + ng-if = "$root.featureFlags.containersViewTemplateControls" + ) + li.list-item.popover-list-item( + internal-modal-helper = "confirmDeleteServerView" + ng-if = "$root.featureFlags.containersViewTemplateControls" + ) + svg.iconnables.icons-delete + use( + xlink:href = "#icons-delete" + ) + | Delete Template + + ul.list.popover-list( + ng-if = "!INC.instance.attrs.masterPod" + ) + .well.gray.padding-xs( + ng-if = "!INC.instance.attrs.isolated" + ) Isolating a branch allows you to… + ul.list.list-bulleted + li.list-item Create a separate environment to use with this branch. + li.list-item Configure this branch independently from your template. + li.list-item.popover-list-item( + ng-click = "INC.setupIsolation()" + ng-if = "!INC.instance.attrs.isolated" + ) + svg.iconnables.icons-gear + use( + xlink:href = "#icons-gear-modified" + ) + | Isolate Branch + span( + ng-if = "INC.shouldShowSetupModal" + ) … + + li.list-item.popover-list-item.multi-line( + ng-click = "INC.editInstance($event)" + ng-if = "INC.instance.attrs.isolated" + ) + svg.iconnables.icons-gear + use( + xlink:href = "#icons-gear-modified" + ) + | Configure + .small Affects only this container. + + li.divider + li.list-item.popover-list-item( + ng-click = "INC.disableIsolation()" + ng-if = "INC.instance.attrs.isIsolationGroupMaster" + ) + svg.iconnables + use( + xlink:href = "#icons-delete" + ) + | Disable Isolation + li.list-item.popover-list-item( + ng-click = "INC.deleteContainer()" + ng-if = "INC.instance.attrs.isolated && !INC.instance.attrs.isIsolationGroupMaster" + ) + svg.iconnables + use( + xlink:href = "#icons-isolation-disable" + ) + | Delete from Isolation + li.list-item.popover-list-item( + ng-click = "INC.removeBranch()" + ng-if = "!INC.instance.attrs.isolated" + ) + svg.iconnables + use( + xlink:href = "#icons-delete" + ) + | Remove Branch + + .popover-content( + ng-if = "!$root.featureFlags.addBranches && !$root.featureFlags.autoIsolation" ) //- menu items: - for an isolated branch: - - Configure Container + - Configure Template - (divider) - Add Container to Isolation - (divider) - Hide - for an isolated branch's containers: - - Configure Container + - Configure Template ul.list.popover-list li.list-item.popover-list-item( - ng-if = "INC.instance.attrs.isolated" ng-click = "INC.editInstance($event)" + ng-if = "INC.instance.attrs.isolated" ) svg.iconnables use( xlink:href = "#icons-gear" ) - | Configure Container + | Configure Template li.divider( ng-if = "INC.instance.attrs.isolated" ) @@ -78,11 +188,8 @@ ) p.p Isolating a branch allows you to… ul.list.list-bulleted.small - //- while we can only isolate with service containers: - li.list-item Copy your service containers to use with this branch. - //- once we can isolate with service containers and repo containers: - - li.list-item Create a separate stack of your containers to use with this branch. - li.list-item Configure this branch differently from your default. + li.list-item Create a separate environment to use with this branch. + li.list-item Configure this branch independently from your template. .btn.btn-sm.btn-block.green( ng-click = "INC.setupIsolation()" ) Enter Isolation @@ -94,8 +201,8 @@ ng-if = "INC.shouldShowSetupModal" ) … li.list-item.popover-list-item( - ng-if = "INC.instance.attrs.isIsolationGroupMaster" ng-click = "INC.disableIsolation()" + ng-if = "INC.instance.attrs.isIsolationGroupMaster" ) svg.iconnables use( @@ -103,14 +210,14 @@ ) | Disable Isolation li.list-item.popover-list-item( - ng-if = "INC.instance.attrs.isolated && !INC.instance.attrs.isIsolationGroupMaster" ng-click = "INC.deleteContainer()" + ng-if = "INC.instance.attrs.isolated && !INC.instance.attrs.isIsolationGroupMaster" ) svg.iconnables use( xlink:href = "#icons-isolation-disable" ) - | Delete Container from Isolation + | Delete from Isolation //- navListFilter li.divider( diff --git a/client/directives/components/isolationConfiguration/isolationConfigurationModalView.jade b/client/directives/components/isolationConfiguration/isolationConfigurationModalView.jade index c98ecebf4..be2123431 100644 --- a/client/directives/components/isolationConfiguration/isolationConfigurationModalView.jade +++ b/client/directives/components/isolationConfiguration/isolationConfigurationModalView.jade @@ -11,7 +11,7 @@ section.modal-body .grid-block.vertical.shrink.text-center.well.gray.small.padding-sm //- while we can only isolate with service containers: - | Select containers to create and isolate with + | Selected templates will be used to create containers for each branch of //- once we can isolate with service containers and repo containers: - | Selected containers will be isolated with .text-overflow( @@ -20,7 +20,7 @@ h4.grid-block.shrink.h4.text-gray.small.padding-xs( ng-if = "ICMC.repoInstances.length" - ) Repository Containers + ) Repository Templates .grid-block.vertical.shrink.list.list-bordered( ng-if = "ICMC.repoInstances.length" ) @@ -56,7 +56,7 @@ ) | {{instance.getMasterPodName()}} - //- this should get the class 'modded' if the selected branch uses a modified configuration + //- this should get the class 'modded' if the selected branch uses a modified template //- selecting this should automatically check the checkbox fancy-select.btn-xs.gray( value = "ICMC.instanceBranchMapping[instance.attrs.contextVersion.context]" @@ -70,12 +70,12 @@ ng-repeat = "child in $parent.instance.children.models" value = "child" ) {{$parent.child.getBranchName()}} - //- this element should only appear if the selected branch uses a modified configuration - //- small.small Branch uses a modified configuration + //- this element should only appear if the selected branch uses a modified template + //- small.small Branch uses a modified template h4.grid-block.shrink.h4.text-gray.small.padding-xs( ng-if = "ICMC.nonRepoInstances.length" - ) Non-Repository Containers + ) Non-Repository Templates .grid-block.vertical.shrink.list.list-bordered( ng-if = "ICMC.nonRepoInstances.length" ) @@ -92,7 +92,7 @@ ) | {{instance.getDisplayName()}} - section.modal-footer.clearfix + footer.modal-footer.clearfix button.btn.btn-md.white.float-left( ng-click = "ICMC.close()" ng-disabled = "$root.isLoading.createIsolation" @@ -100,4 +100,9 @@ button.btn.btn-md.green.float-right( ng-disabled = "$root.isLoading.createIsolation" ng-click = "ICMC.createIsolation()" - ) {{$root.isLoading.createIsolation ? 'Isolating...' : 'Enter Isolation'}} + ) + .spinner-wrapper.spinner-white.spinner-sm.float-left( + ng-if = "$root.isLoading.createIsolation" + ng-include = "'spinner'" + ) + span Isolate Branch diff --git a/client/directives/components/lists/branchCommitSelector/branchCommitSelectorView.jade b/client/directives/components/lists/branchCommitSelector/branchCommitSelectorView.jade index 5ca6ed0cd..aebf69d08 100644 --- a/client/directives/components/lists/branchCommitSelector/branchCommitSelectorView.jade +++ b/client/directives/components/lists/branchCommitSelector/branchCommitSelectorView.jade @@ -26,7 +26,7 @@ label.toggle-wrapper( .toggle-group.toggle-sm label.toggle-wrapper( - ng-if = "$root.featureFlags.testingFeature && BCSC.data.acv && !BCSC.data.acv.attrs.additionalRepo" + ng-if = "BCSC.data.acv && !BCSC.data.acv.attrs.additionalRepo" ) .strong Auto-Deploy .small Rebuild when new commits are pushed to GitHub. diff --git a/client/directives/components/mirrorDockerfile/mirrorDockerfileView.jade b/client/directives/components/mirrorDockerfile/mirrorDockerfileView.jade index d08dc9380..ade231cda 100644 --- a/client/directives/components/mirrorDockerfile/mirrorDockerfileView.jade +++ b/client/directives/components/mirrorDockerfile/mirrorDockerfileView.jade @@ -21,7 +21,7 @@ use( xlink:href = "#icons-file-new" ) - .grid-content Configure with Runnable + .grid-content Start with our setup guide //- if there is only one, [check] by default input.checkbox( name = "dockerfile" @@ -31,7 +31,7 @@ value = "new" ) - button.btn.btn-xs.btn-icon.btn-add + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add svg.iconnables.icons-check use( xlink:href = "#icons-check" @@ -39,13 +39,13 @@ small.small.text-gray.padding-xxs.label-from( ng-if = "!$root.isLoading.mirrorDockerfile && MDC.state.repo.dockerfiles.length > 0" -) Advanced Configuration +) Advanced Setup .grid-content.shrink.list.list-bordered( - ng-if = "$root.featureFlags.blankDockerfile" + ng-if = "$root.featureFlags.blankDockerfile && MDC.state.tabName === 'repos'" ) label.grid-block.list-item( - ng-class = "{'active': MDC.state.dockerfile === false}" + ng-class = "{'active': MDC.state.dockerfile === 'blank'}" ng-disabled = "$root.isLoading[MDC.name + 'SingleRepo']" ) svg.grid-content.shrink.iconnables.icons-dockerfile @@ -56,10 +56,11 @@ small.small.text-gray.padding-xxs.label-from( //- if there is only one, [check] by default input.checkbox( ng-disabled = "$root.isLoading[MDC.name + 'SingleRepo']" - ng-value = "false" + ng-model = 'MDC.state.configurationMethod' type = "radio" + value = "blankDockerfile" ) - button.btn.btn-xs.btn-icon.btn-add + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add svg.iconnables.icons-check use( xlink:href = "#icons-check" @@ -71,9 +72,9 @@ small.small.text-gray.padding-xxs.label-from( //- 'thisDockerfile' should be the name of the Dockerfile (ie. 'Dockerfile.prod', or 'Dockerfile.staging') to suppot multiple dockerfiles //- add .disabled class to the not selected item if loading label.grid-block.list-item( + ng-class="{'active': MDC.state.configurationMethod === 'dockerfile'}" ng-disabled = "$root.isLoading[MDC.name + 'SingleRepo']" ng-repeat = "dockerfile in MDC.state.repo.dockerfiles" - ng-class="{'active': MDC.state.configurationMethod === 'dockerfile'}" ) svg.grid-content.shrink.iconnables.icons-dockerfile use( @@ -94,7 +95,7 @@ small.small.text-gray.padding-xxs.label-from( type = "radio" value = "dockerfile" ) - button.btn.btn-xs.btn-icon.btn-add + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add svg.iconnables.icons-check use( xlink:href = "#icons-check" @@ -112,7 +113,7 @@ small.grid-block.shrink.align-center.small.text-gray.padding-xxs( .grid-block.vertical.shrink.well.gray.padding-sm.well-add-dockerfile( add-dockerfile branch-name = "MDC.branchName" - full-repo = "MDC.fullRepo" + full-repo = "MDC.getFullRepo()" ng-if = "viewState.showAddDockerfile" view-state = "viewState" ) diff --git a/client/directives/components/readOnlySwitch/confirmRollbackModalView.jade b/client/directives/components/readOnlySwitch/confirmRollbackModalView.jade index 885a7d469..3c81f3f0b 100644 --- a/client/directives/components/readOnlySwitch/confirmRollbackModalView.jade +++ b/client/directives/components/readOnlySwitch/confirmRollbackModalView.jade @@ -2,7 +2,7 @@ .modal-dialog.modal-sm.modal-alert header.modal-body p.p.strong Disable Dockerfile editing? - p.p Changes made to your Dockerfile will be discarded and your configuration will be restored to the state it was in before Dockerfile editing was enabled. + p.p Changes made to your Dockerfile will be discarded and your template will be restored to the state it was in before Dockerfile editing was enabled. footer.modal-footer.clearfix button.btn.btn-sm.gray.float-left( ng-click = "CMC.actions.cancel()" diff --git a/client/directives/components/readOnlySwitch/confirmSwitchToSimpleModeView.jade b/client/directives/components/readOnlySwitch/confirmSwitchToSimpleModeView.jade index 19ead6c79..6040d3f2d 100644 --- a/client/directives/components/readOnlySwitch/confirmSwitchToSimpleModeView.jade +++ b/client/directives/components/readOnlySwitch/confirmSwitchToSimpleModeView.jade @@ -7,8 +7,8 @@ use( xlink:href = "#icons-close" ) - p.p.strong.popover-title Disable editing and restore past configuration? - p.p Disabling will restore your container’s configuration from when editing was initially enabled. You can’t undo this action. + p.p.strong.popover-title Disable editing and restore past template? + p.p Disabling will restore your template from when editing was initially enabled. You can’t undo this action. footer.modal-footer.clearfix button.btn.btn-sm.gray.float-left( ng-click = "CMC.actions.cancel()" diff --git a/client/directives/components/readOnlySwitch/readOnlySwitchView.jade b/client/directives/components/readOnlySwitch/readOnlySwitchView.jade index 6f0a33690..0d21a8f50 100644 --- a/client/directives/components/readOnlySwitch/readOnlySwitchView.jade +++ b/client/directives/components/readOnlySwitch/readOnlySwitchView.jade @@ -7,6 +7,6 @@ input.toggle-input( .toggle-group.toggle-sm( tooltip tooltip-disabled = "!ROSC.state.instance || !ROSC.readOnly() || (ROSC.state.repo && ROSC.state.instance.attrs.lastBuiltSimpleContextVersion)" - tooltip-eval = "ROSC.state.repo ? 'We’re unable to disable editing. To resolve this, delete and re-create your container.' : 'Editing is always enabled on non-repository containers.'" + tooltip-eval = "ROSC.state.repo ? 'We’re unable to disable editing. To resolve this, delete and re-create your template.' : 'Editing is always enabled on non-repository templates.'" tooltip-options = "{\"class\":\"bottom bottom-arrow-right\",\"right\":0,\"top\":21}" ) diff --git a/client/directives/components/saveOpenItemsButton/saveOpenItemsButtonView.jade b/client/directives/components/saveOpenItemsButton/saveOpenItemsButtonView.jade index 8804ef37c..4e6f6bfdf 100644 --- a/client/directives/components/saveOpenItemsButton/saveOpenItemsButtonView.jade +++ b/client/directives/components/saveOpenItemsButton/saveOpenItemsButtonView.jade @@ -24,7 +24,7 @@ pop-over pop-over-active = "SOIBC.popoverActive" pop-over-controller = "SOIBC" - pop-over-options = "{\"right\":1,\"top\":47}" + pop-over-options = "{\"right\":-6,\"top\":47}" pop-over-template = "saveOpenItemsOptionsPopoverView" type = "button" ) diff --git a/client/directives/components/saveOpenItemsButton/saveOpenItemsOptionsPopoverView.jade b/client/directives/components/saveOpenItemsButton/saveOpenItemsOptionsPopoverView.jade index 79d15f7db..601041448 100644 --- a/client/directives/components/saveOpenItemsButton/saveOpenItemsOptionsPopoverView.jade +++ b/client/directives/components/saveOpenItemsButton/saveOpenItemsOptionsPopoverView.jade @@ -4,7 +4,7 @@ style = "transform-origin: 95% 0;" ) .arrow.white( - style = "left: auto; right: -1px;" + style = "left: auto; right: 6px;" ) .popover-content ul.popover-list diff --git a/client/directives/environment/environmentBody/serverCards/popoverServerOptions/serverOptionsCardPopover.jade b/client/directives/environment/environmentBody/serverCards/popoverServerOptions/serverOptionsCardPopover.jade index 4de4a76a4..7a877ce53 100644 --- a/client/directives/environment/environmentBody/serverCards/popoverServerOptions/serverOptionsCardPopover.jade +++ b/client/directives/environment/environmentBody/serverCards/popoverServerOptions/serverOptionsCardPopover.jade @@ -31,7 +31,7 @@ use( xlink:href = "#icons-server-modify" ) - | Rename Container + | Rename Template //- trigger delete confirm modal li.list-item.popover-list-item( ng-click = "actions.deleteServer()" @@ -40,4 +40,4 @@ use( xlink:href = "#icons-server-delete" ) - | Delete Container + | Delete Template diff --git a/client/directives/environment/environmentBody/serverCards/serverCardDirective.js b/client/directives/environment/environmentBody/serverCards/serverCardDirective.js index e23dba236..4c6f2892f 100644 --- a/client/directives/environment/environmentBody/serverCards/serverCardDirective.js +++ b/client/directives/environment/environmentBody/serverCards/serverCardDirective.js @@ -10,7 +10,6 @@ require('app') createServerObjectFromInstance, fetchDockerfileForContextVersion, fetchStackAnalysis, - helpCards, keypather, ModalService, parseDockerfileForCardInfoFromInstance, @@ -23,8 +22,7 @@ require('app') scope: { data: '=', actions: '=', - instance: '=', - helpCard: '=?' + instance: '=' }, link: function ($scope, ele) { var listeners = []; @@ -79,114 +77,11 @@ require('app') return '—'; }; - $scope.helpCards = helpCards; $scope.server = {}; $scope.activeAccount = currentOrg.github; // I'm unsure if this is used. - function scrollIntoView() { - $document.scrollToElement(ele, 100, 200); - } - - function calculateHelpCardsForRepoInstance (instance) { - if (instance.attrs.owner.username !== $state.params.userName) { return; } - // This may be a newInstance... just a placeholder - helpCards.removeByInstance(instance); - - var fullRepoName = keypather.get($scope.server.instance, 'contextVersion.getMainAppCodeVersion().attrs.repo'); - - return $q.all({ - stackAnalysis: fetchStackAnalysis(fullRepoName), - dependencies: promisify(instance, 'fetchDependencies', true)() - }) - .then(function (res) { - var stackAnalysis = res.stackAnalysis; - var dependencies = res.dependencies; - if (!stackAnalysis.serviceDependencies) { return; } - - stackAnalysis.serviceDependencies.forEach(function (dependency) { - var matchedInstance = $scope.data.instances.find(function (instance) { - return instance.attrs.lowerName === dependency; - }); - if (matchedInstance) { - var matchedDependency = dependencies.find(function (dep) { - return dep.attrs.shortHash === matchedInstance.attrs.shortHash; - }); - if (matchedDependency) { return; } - return helpCards.triggerCard('missingAssociation', { - instance: $scope.server.instance, - association: matchedInstance.attrs.name - }) - .then(function (helpCard) { - if (!helpCard) { return; } - addListener(helpCard, 'refresh', calculateHelpCardsForRepoInstance.bind(null, instance)); - addListener(helpCard, 'activate', scrollIntoView); - }).catch(errs.handler); - } - // Missing Dependency - return helpCards.triggerCard('missingDependency', { - instance: $scope.server.instance, - dependency: dependency - }) - .then(function (helpCard) { - if (!helpCard) { return; } - addListener(helpCard, 'refresh', calculateHelpCardsForRepoInstance.bind(null, instance)); - }).catch(errs.handler); - }); - }) - .catch(errs.handler); - } - - function calculateHelpCardsForNonRepoContainers (instance) { - return $q.when($scope.data.instances) - .then(function (instances) { - return $q.all(instances.filter(function (instance) { - return instance !== $scope.server.instance && keypather.get(instance, 'attrs._id'); - }) - .map(function (instance) { - if (keypather.get(instance, 'dependencies.models.length')) { - return $q.when(instance.dependencies); - } - return promisify(instance, 'fetchDependencies')(); - })); - }) - .then(function (dependencyList) { - return dependencyList.find(function (depList) { - return depList.find(function (dep) { - return dep.attrs.name === $scope.server.instance.attrs.name; - }); - }); - }) - .then(function (foundMatch) { - if (foundMatch) { return; } - var foundInstanceWithMainACV = $scope.data.instances.find(function (instance) { - return keypather.get(instance, 'contextVersion.getMainAppCodeVersion()'); - }); - if (!foundInstanceWithMainACV) { return; } - if (instance.attrs.owner.username !== $state.params.userName) { return; } - return helpCards.triggerCard('missingMapping', { - mapping: $scope.server.instance.attrs.name - }) - .then(function (helpCard) { - if (!helpCard) { return; } - addListener(helpCard, 'refresh', calculateHelpCardsForNonRepoContainers.bind(null, instance)); - }) - .catch(errs.handler); - }); - } - - function addListener (helpCard, name, cb) { - listeners.push({ - obj: helpCard, - key: name, - value: cb - }); - helpCard - .on(name, cb); - } - function handleNewInstanceUpdate (instance) { // This may be a newInstance... just a placeholder - helpCards.removeByInstance(instance); angular.extend($scope.server, createServerObjectFromInstance(instance)); if (!instance.contextVersion) { return; } @@ -201,11 +96,6 @@ require('app') } $scope.server.building = false; - var fullRepoName = keypather.get($scope.server.instance, 'contextVersion.getMainAppCodeVersion().attrs.repo'); - if (fullRepoName) { - return calculateHelpCardsForRepoInstance(instance); - } - return calculateHelpCardsForNonRepoContainers(instance); } $scope.$watchCollection('instance.attrs', function (n) { @@ -267,7 +157,6 @@ require('app') listeners.forEach(function (watcher) { watcher.obj.removeListener(watcher.key, watcher.value); }); - helpCards.removeByInstance($scope.server.instance); }); } }; diff --git a/client/directives/environment/environmentBody/serverCards/serverCardView.jade b/client/directives/environment/environmentBody/serverCards/serverCardView.jade index 52f11cbb6..293ef7468 100644 --- a/client/directives/environment/environmentBody/serverCards/serverCardView.jade +++ b/client/directives/environment/environmentBody/serverCards/serverCardView.jade @@ -29,7 +29,6 @@ ul.card-body.load( //- repository li.btn.white( ng-click = "openEditServerModal('repository')" - ng-class = "{'btn-hint': helpCards.cardIsActiveOnThisContainer(instance) && helpCards.getActiveCard().targets.repository}" ng-if = "server.repo && !server.advanced" ) svg.iconnables @@ -47,7 +46,6 @@ ul.card-body.load( //- commands li.btn.white( ng-click = "openEditServerModal('commands')" - ng-class = "{'btn-hint': helpCards.cardIsActiveOnThisContainer(instance) && helpCards.getActiveCard().targets.commands}" ng-if = "server.repo && !server.advanced" ) svg.iconnables @@ -65,7 +63,6 @@ ul.card-body.load( //- exposed ports li.btn.white( ng-click = "openEditServerModal('ports')" - ng-class = "{'btn-hint': helpCards.cardIsActiveOnThisContainer(instance) && helpCards.getActiveCard().targets.exposedPorts}" ng-if = "server.repo && !server.advanced" ) svg.iconnables @@ -82,7 +79,6 @@ ul.card-body.load( li.btn.white( ng-click = "openEditServerModal('env')" ng-class = "{\ - 'btn-hint': helpCards.cardIsActiveOnThisContainer(instance) && helpCards.getActiveCard().targets.environmentVariables,\ 'active-notification': $root.featureFlags.hostnameNotifications\ }" ) @@ -103,7 +99,6 @@ ul.card-body.load( //- container files li.btn.white( ng-click = "openEditServerModal('files')" - ng-class = "{'btn-hint': helpCards.cardIsActiveOnThisContainer(instance) && helpCards.getActiveCard().targets.containerFiles}" ng-if = "server.repo && !server.advanced" ) svg.iconnables @@ -119,7 +114,6 @@ ul.card-body.load( //- find & replace li.btn.white( ng-click = "openEditServerModal('translation')" - ng-class = "{'btn-hint': helpCards.cardIsActiveOnThisContainer(instance) && helpCards.getActiveCard().targets.findAndReplace}" ng-if = "\ server.repo && \ !server.instance.hasDockerfileMirroring() \ diff --git a/client/directives/environment/environmentBody/serverStatusCardHeaderDirective.js b/client/directives/environment/environmentBody/serverStatusCardHeaderDirective.js index 0e95f885b..66706a7ed 100644 --- a/client/directives/environment/environmentBody/serverStatusCardHeaderDirective.js +++ b/client/directives/environment/environmentBody/serverStatusCardHeaderDirective.js @@ -8,7 +8,6 @@ require('app') function serverStatusCardHeader( $rootScope, errs, - helpCards, ModalService, promisify ) { @@ -63,7 +62,6 @@ function serverStatusCardHeader( }); }) .catch(errs.handler); - helpCards.refreshAllCards(); } return confirmed; }); diff --git a/client/directives/environment/environmentBody/viewCardGrid.jade b/client/directives/environment/environmentBody/viewCardGrid.jade index cc1b8a98c..8249c3951 100644 --- a/client/directives/environment/environmentBody/viewCardGrid.jade +++ b/client/directives/environment/environmentBody/viewCardGrid.jade @@ -8,23 +8,25 @@ ) //- empty state -.card.gray.disabled.load.empty( +.card.gray.disabled.load.empty.p( ng-class = "{'deprecated': !$root.featureFlags.cardStatus}" - ng-if = "data.instances && !$root.isLoading.sidebar && !data.instances.models.length" + ng-if = "$root.featureFlags.aha && !EC.isInGuide() && !$root.isLoading.sidebar && data.instances && !data.instances.models.length" ) - svg.iconnables( - ng-click = "EC.triggerModal.newContainer()" - ) - use( - xlink:href = "#icons-new-repository-server" - ) - p.p Add your first repository container to get started. + h1.h1 😐 + | You don’t have any templates. + br + | Click the button above to create one. + +//- empty state +.card.gray.disabled.load.empty.p( + ng-class = "{'deprecated': !$root.featureFlags.cardStatus}" + ng-if = "!$root.featureFlags.aha && !$root.isLoading.sidebar && data.instances && !data.instances.models.length" +) Create a repository template to get started! //- server card .card.gray.disabled.load( actions = "actions" data = "data" - help-card = "state.helpCard" instance = "instance" ng-class = "{\ 'deprecated': !$root.featureFlags.cardStatus,\ diff --git a/client/directives/environment/environmentController.js b/client/directives/environment/environmentController.js index 041a3a3be..21b7c9a32 100755 --- a/client/directives/environment/environmentController.js +++ b/client/directives/environment/environmentController.js @@ -14,13 +14,13 @@ function EnvironmentController( $scope, $state, $timeout, - $window, + ahaGuide, + currentOrg, favico, - fetchUser, fetchDockerfileForContextVersion, - fetchInstancesByPod, fetchOrgMembers, - helpCards, + fetchUser, + instancesByPod, keypather, ModalService, pageName @@ -28,6 +28,17 @@ function EnvironmentController( var EC = this; EC.showInviteButton = false; + EC.isAddingFirstRepo = ahaGuide.isAddingFirstRepo; + EC.isInGuide = ahaGuide.isInGuide; + EC.showCreateTemplate = true; + EC.showOverview = true; + $scope.$on('ahaGuideEvent', function(event, info) { + if (info.isClear) { + EC.errorState = null; + } else { + EC.errorState = info.error; + } + }); var unbindUpdateTeammateInvitation = $rootScope.$on('updateTeammateInvitations', function (event, invitesCreated) { if (invitesCreated) { @@ -36,7 +47,7 @@ function EnvironmentController( }); $scope.$on('$destroy', unbindUpdateTeammateInvitation); - function updateShowInviteButton () { + function updateShowInviteButton() { return $q.all({ user: fetchUser(), members: fetchOrgMembers($state.params.userName) @@ -53,6 +64,7 @@ function EnvironmentController( EC.triggerModal = { newContainer: function () { + $rootScope.$broadcast('close-popovers'); return ModalService.showModal({ controller: 'NewContainerModalController', controllerAs: 'MC', // Shared @@ -82,64 +94,37 @@ function EnvironmentController( $scope.$state = $state; favico.reset(); pageName.setTitle('Configure - Runnable'); - $scope.data = { - helpCards: helpCards - }; - fetchInstancesByPod($state.userName) - .then(function (instancesCollection) { - $scope.data.instances = instancesCollection; - // Asynchronously fetch the Dockerfile - instancesCollection.forEach(function (instance) { - if (instance.hasDockerfileMirroring()) { - return fetchDockerfileForContextVersion(instance.contextVersion) - .then(function (dockerfile) { - instance.mirroredDockerfile = dockerfile; - }); - } - // Differentiate between non-fetched and non-existing - instance.mirroredDockerfile = null; - }); - }); + $scope.data = { }; + $scope.data.instances = instancesByPod; + + var isAddFirstRepo = ahaGuide.isAddingFirstRepo(); + + if (isAddFirstRepo && instancesByPod.models.length === 0) { + EC.showCreateTemplate = false; + EC.showSidebar = true; + } + + // Asynchronously fetch the Dockerfile and check for working instances + instancesByPod.forEach(function (instance) { + if (instance.hasDockerfileMirroring()) { + return fetchDockerfileForContextVersion(instance.contextVersion) + .then(function (dockerfile) { + instance.mirroredDockerfile = dockerfile; + }); + } + // Differentiate between non-fetched and non-existing + instance.mirroredDockerfile = null; + }); $scope.state = { validation: { env: {} }, - helpCard: null, newServerButton: { active: false } }; - $scope.help = helpCards.cards; - $scope.helpCards = helpCards; - - helpCards.clearAllCards(); - - $scope.helpUndock = false; - - var scrollHelper = function () { - var newVal = false; - if ($window.scrollY > 60) { - newVal = true; - } - if ($scope.helpUndock !== newVal) { - $scope.helpUndock = newVal; - $timeout(angular.noop); - } - }; - $scope.$on('helpCardScroll:enable', function () { - $window.addEventListener('scroll', scrollHelper); - scrollHelper(); - }); - $scope.$on('helpCardScroll:disable', function () { - $window.removeEventListener('scroll', scrollHelper); - }); - - $scope.$on('$destroy', function () { - $window.removeEventListener('scroll', scrollHelper); - }); - EC.alert = null; $scope.$on('alert', function (evt, data) { @@ -166,20 +151,30 @@ function EnvironmentController( subTab: 'billingForm' } }); - } + }, + showSidebar: function () { + EC.showSidebar = !EC.showSidebar; + EC.showCreateTemplate = true; + }, + endGuide: ahaGuide.endGuide }; - $scope.helpPopover = { - data: $scope.help, - actions: { - ignoreHelp: function (help) { - helpCards.ignoreCard(help); - }, - getHelp: function (help) { - helpCards.setActiveCard(help); - $rootScope.$broadcast('close-popovers'); + + $scope.$on('showAhaSidebar', EC.actions.showSidebar); + $scope.$on('showAddServicesPopover', function(event, toggle) { + EC.showAddServicePopover = toggle; + }); + + if (ahaGuide.isInGuide()) { + if (keypather.get(instancesByPod, 'models.length')) { + if (instancesByPod.models.some(function (instance) { + return instance.attrs.hasAddedBranches || keypather.get(instance, 'children.models.length'); + })) { + // timeout for the animation + $timeout(function () { + EC.showSidebar = true; + }); } } - }; - + } } diff --git a/client/directives/environment/environmentHeader/viewEnvironmentHeader.jade b/client/directives/environment/environmentHeader/viewEnvironmentHeader.jade index 9389432ce..10179e918 100644 --- a/client/directives/environment/environmentHeader/viewEnvironmentHeader.jade +++ b/client/directives/environment/environmentHeader/viewEnvironmentHeader.jade @@ -1,71 +1,37 @@ -button.grid-block.shrink.btn.btn-md.btn-icon.btn-help( - data-badge-count = "{{helpCards.cards.triggered.length}}" - ng-class = "{\ - 'active': state.helpButton.active || !state.helpButton.active && helpCards.cards.triggered.length > 0 && !helpCards.getActiveCard(),\ - 'badge': helpCards.cards.triggered.length > 0,\ - 'badge-blue': helpCards.cards.triggered.length > 0\ - }" - ng-click = "state.helpButton.active = true" - ng-if = "!$root.featureFlags.aha" - pop-over - pop-over-actions = "helpPopover.actions" - pop-over-active = "state.helpButton.active" - pop-over-data = "helpPopover.data" - pop-over-options = "{\"left\":60}" - pop-over-template = "viewHelpPopover" - pop-over-trigger = "activeAttr" - title = "Help Center" -) - svg.iconnables - use( - xlink:href = "#icons-help" - ) - -.help.help-top-card( - ng-if = "!state.helpButton.active && helpCards.cards.triggered.length > 0 && !helpCards.getActiveCard()" -) - .help-lists-wrapper - ul.triggered-help-list - li.grid-block.triggered-help-item.clearfix - button.btn.btn-icon.btn-icon-xs( - ng-click = "helpPopover.actions.ignoreHelp(helpCards.cards.triggered[0])" - ) - svg.iconnables - use( - xlink:href = "#icons-close" - ) - p.p.grid-content( - ng-bind-html = "helpCards.cards.triggered[0].label" - ) - button.btn.btn-xs.blue.grid-content.shrink( - ng-click = "helpPopover.actions.getHelp(helpCards.cards.triggered[0])" - ) Show Me - button.btn( - ng-click = "state.helpButton.active = true" - ) More Help - svg.iconnables - use( - xlink:href = "#icons-arrow-down" - ) - //- new server button button.grid-block.shrink.btn.btn-md.green( - ng-class = "{\ - 'scale': helpCards.getActiveCard().targets.newContainer,\ - 'scale-in-modal': $root.featureFlags.aha1\ - }" + ng-class = "{ 'scale-in-modal': EC.isAddingFirstRepo() }" ng-click = "EC.triggerModal.newContainer()" - ng-if = "!$root.featureFlags.ahaOverview" ) svg.iconnables.icons-add.float-left use( xlink:href = "#icons-add" ) - | Add Configuration + | Create Template + +.popover.bottom.in.popover-aha( + ng-if = "$root.featureFlags.aha && EC.showAddServicePopover" + style = "left: 0; margin: 0 auto; right: 0; top: 54px;" +) + .arrow.white + .popover-content + .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide + .grid-block.shrink.aha-meter.js-animate.aha-meter-100 + svg.iconnables + use( + xlink:href = "#icons-check" + ) + .grid-block.vertical.aha-text + p.p.small.text-gray-light Step 2: Configure your Application + p.p To add a database or other service to your application, click ‘Create Template’. + .grid-block.justify-right.popover-footer + button.grid-block.shrink.btn.btn-sm.green( + ng-click = "EC.showAddServicePopover = false" + ) Got It button.grid-block.shrink.btn.btn-sm.gray.btn-aha( - ng-click = "$root.featureFlags.ahaSidebar = true" - ng-if = "$root.featureFlags.aha && !$root.featureFlags.ahaSidebar" + ng-click = "EC.actions.showSidebar()" + ng-if = "$root.featureFlags.aha && EC.isInGuide()" tooltip = "Setup Guide" tooltip-options = "{\"class\":\"left\",\"right\":42,\"top\":0}" ) diff --git a/client/directives/environment/environmentView.jade b/client/directives/environment/environmentView.jade index 07a87d401..c89bf7988 100755 --- a/client/directives/environment/environmentView.jade +++ b/client/directives/environment/environmentView.jade @@ -42,76 +42,44 @@ //- environment page .grid-block.environment-wrapper( - ng-class = "{'empty': $root.featureFlags.aha && $root.featureFlags.aha1}" + ng-class = "{'empty': EC.isInGuide() && EC.isAddingFirstRepo() && data.instances.models.length === 0}" ) - header.grid-block.align-center.environment-header( ng-include = "'viewEnvironmentHeader'" ng-init = "state.helpButton = {active: false}" + ng-if = "!EC.isInGuide() || EC.showCreateTemplate" ) - .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( - ng-if = "$root.featureFlags.aha1ExitedEarly" - ng-include = "'ahaGuideView'" - ng-init = "\ - state.showStep = 1;\ - state.showSubStep = 7;\ - state.showError = true;\ - state.showErrorType = 'exitedEarly';\ - " + .environment-view-aha-guide( + ng-if = "$root.featureFlags.aha && EC.isInGuide()" ) + .grid-block.align-center.justify-center.padding-sm.aha-guide( + ng-show = "EC.isAddingFirstRepo() && EC.errorState && data.instances.models.length" + aha-guide + error-state = "EC.errorState" + sub-step-index = 7 + ) - .grid-block.environment-body.justify-center.clearfix( - ng-class = "{'align-center justify-center': $root.featureFlags.aha1}" - ) - .help-container( - ng-class = "{\ - 'fixed': helpUndock,\ - 'top': helpUndock\ - }" - ng-if = "helpCards.getActiveCard() && !$root.featureFlags.aha" + .grid-block.align-center.justify-center.padding-sm.aha-guide( + ng-if = "EC.isInGuide() && !EC.isAddingFirstRepo() && !data.instances.models.length" + aha-guide + error-state = "EC.errorState" + sub-step = "deletedTemplate" ) - .help( - ng-if = "helpCards.getActiveCard().targets.newContainer && state.newServerButton.active" - ) - button.btn.btn-xs.btn-icon( - ng-click = "helpCards.hideActiveCard()" - ) - svg.iconnables.icons-close - use( - xlink:href = "#icons-close" - ) - span( - ng-bind-html = "helpCards.getActiveCard().helpPopover.newContainer" - ) - .help( - ng-if = "!(helpCards.getActiveCard().targets.newContainer && state.newServerButton.active)" - ) - button.btn.btn-xs.btn-icon( - ng-click = "helpCards.hideActiveCard()" - ) - svg.iconnables.icons-close - use( - xlink:href = "#icons-close" - ) - span( - ng-bind-html = "helpCards.getActiveCard().helpTop" - ) + .grid-block.environment-body.justify-center.clearfix( + ng-class = "{'align-center justify-center': EC.showCreateTemplate && !data.instances.models.length}" + ) .modal-dialog.modal-sm( - ng-if = "$root.featureFlags.aha1 && !$root.featureFlags.ahaOverview" + ng-if = "$root.featureFlags.aha && EC.isInGuide() && EC.isAddingFirstRepo() && EC.showCreateTemplate && !data.instances.models.length" ) .grid-block.align-center.aha-guide.padding-md( ng-include = "'ahaGuideView'" - ng-init = "\ - state.showStep = 1;\ - state.showSubStep = 0;\ - " + ng-init = "staticAddRepo = true" ) .grid-block.card-grid.clearfix( - ng-class = "{'padding-top': helpCards.getActiveCard().helpTop}" - ng-if = "!$root.featureFlags.aha1" + ng-if = "!$root.featureFlags.aha || data.instances.models.length > 0 || !EC.isInGuide()" ng-include = "'viewCardGrid'" ) @@ -126,9 +94,11 @@ xlink:href = "#icons-team-invite" ) | Invite a teammate - | to help you set up your stack. + | to help you set up your project. .grid-block.vertical.aha-sidebar.padding-sm.js-animate( - ng-include = "'ahaSidebarView'" - ng-if = "$root.featureFlags.aha && $root.featureFlags.ahaSidebar" + aha-sidebar + toggle-sidebar = "EC.actions.showSidebar" + show-overview = "!EC.showCreateTemplate" + ng-if = "$root.featureFlags.aha && EC.isInGuide() && EC.showSidebar" ) diff --git a/client/directives/environment/modals/confirmDeleteServerView.jade b/client/directives/environment/modals/confirmDeleteServerView.jade index fd09b74dd..4ee6ed169 100644 --- a/client/directives/environment/modals/confirmDeleteServerView.jade +++ b/client/directives/environment/modals/confirmDeleteServerView.jade @@ -1,12 +1,12 @@ .modal-backdrop.in .modal-dialog.modal-sm.modal-alert header.modal-body - p.p.strong Are you sure you want to delete this container? - p.p Deleting this container cannot be undone. + p.p.strong Are you sure you want to delete this template? + p.p All containers that belong to this template will be deleted. This cannot be undone. footer.modal-footer.clearfix button.btn.btn-sm.gray.float-left( ng-click = "CMC.actions.cancel()" ) Cancel button.btn.btn-sm.red.float-right( ng-click = "CMC.actions.confirm()" - ) Delete Container \ No newline at end of file + ) Delete Template diff --git a/client/directives/environment/modals/confirmDiscardServerView.jade b/client/directives/environment/modals/confirmDiscardServerView.jade index f78c038fd..f4939cd69 100644 --- a/client/directives/environment/modals/confirmDiscardServerView.jade +++ b/client/directives/environment/modals/confirmDiscardServerView.jade @@ -1,7 +1,7 @@ .modal-backdrop.in .modal-dialog.modal-sm.modal-alert header.modal-body - p.p.strong Are you sure you want to discard this container? + p.p.strong Are you sure you want to discard this template? footer.modal-footer.clearfix button.btn.btn-sm.gray.float-left( ng-click = "CMC.actions.cancel()" diff --git a/client/directives/environment/modals/confirmSetupView.jade b/client/directives/environment/modals/confirmSetupView.jade new file mode 100644 index 000000000..5379fde30 --- /dev/null +++ b/client/directives/environment/modals/confirmSetupView.jade @@ -0,0 +1,34 @@ +.modal-backdrop.modal-confirm-setup + img.img( + src = "/build/images/runnabear-working.png" + ) + .modal-dialog.modal-md + .modal-body + svg.iconnables.icons-close( + ng-click = "CMC.actions.cancel()" + ) + use( + xlink:href = "#icons-close" + ) + .grid-block.vertical.padding-md + h3.grid-content.h3.text-center Is your application set up? + p.grid-content.p.text-center.text-gray Go back to resolve any configuration issues or to add any other templates. Need more help? Head over to our + a.link( + href = "https://support.runnable.com/" + target = "_blank" + ) docs + | or + //- Opens Intercom chat, and starts with "I need help setting up!": + a.link( + href = "https://support.runnable.com/hc/en-us/articles/212930166" + target = "_blank" + ) chat + | with our devs. + p.grid-content.p.text-center.text-gray If you’re done, continue to the Containers page. + footer.modal-footer.clearfix + button.btn.btn-md.btn-cancel.gray.float-left( + ng-click = "CMC.actions.cancel()" + ) Go Back + button.btn.btn-md.green.float-right( + ng-click = "$root.$broadcast('confirmedSetup');CMC.actions.confirm()" + ) Continue diff --git a/client/directives/environment/modals/forms/backupForm/backupFormView.jade b/client/directives/environment/modals/forms/backupForm/backupFormView.jade index a0aeca638..42bd59e54 100644 --- a/client/directives/environment/modals/forms/backupForm/backupFormView.jade +++ b/client/directives/environment/modals/forms/backupForm/backupFormView.jade @@ -47,8 +47,8 @@ ng-class = "{'disabled': false}" ) Back Up Now //- disable the above button and add tooltip if the container is down - //- tooltip = "You can’t back up while the container isn’t running." - //- tooltip-options = "{\"class\":\"bottom bottom-arrow-right\",\"right\":0,\"top\":30}" + tooltip = "You can’t back up while the container isn’t running." + tooltip-options = "{\"class\":\"bottom bottom-arrow-right\",\"right\":0,\"top\":24}" .ace-container.ace-backup( ng-class = "{\ diff --git a/client/directives/environment/modals/forms/formBuildfiles/viewFormBuildfiles.jade b/client/directives/environment/modals/forms/formBuildfiles/viewFormBuildfiles.jade index 7d6a9647d..e1afecb78 100644 --- a/client/directives/environment/modals/forms/formBuildfiles/viewFormBuildfiles.jade +++ b/client/directives/environment/modals/forms/formBuildfiles/viewFormBuildfiles.jade @@ -1,18 +1,3 @@ -.help-container( - ng-if = "SMC.helpCards.getActiveCard().helpPopover.containerFiles" -) - .help - button.btn.btn-xs.btn-icon( - ng-click = "SMC.helpCards.hideActiveCard()" - ) - svg.iconnables.icons-close - use( - xlink:href = "#icons-close" - ) - span( - ng-bind-html = "SMC.helpCards.getActiveCard().helpPopover.containerFiles" - ) - .grid-block.shrink.align-center.well.well-600.gray.can-disable.padding-sm( ng-if = "!SMC.state.advanced" ) diff --git a/client/directives/environment/modals/forms/formEnvironmentVariables/viewFormEnvironmentVariables.jade b/client/directives/environment/modals/forms/formEnvironmentVariables/viewFormEnvironmentVariables.jade index 78e5aeb28..b3d9f8211 100644 --- a/client/directives/environment/modals/forms/formEnvironmentVariables/viewFormEnvironmentVariables.jade +++ b/client/directives/environment/modals/forms/formEnvironmentVariables/viewFormEnvironmentVariables.jade @@ -1,20 +1,3 @@ -//- help things -.help-container( - ng-if = "SMC.helpCards.getActiveCard().helpPopover.environmentVariables && SMC.helpCards.cardIsActiveOnThisContainer(SMC.instance)" - ng-hide = "$root.featureFlags.fullScreen" -) - .help - button.btn.btn-xs.btn-icon( - ng-click = "SMC.helpCards.hideActiveCard()" - ) - svg.iconnables.icons-close - use( - xlink:href = "#icons-close" - ) - span( - ng-bind-html = "SMC.helpCards.getActiveCard().helpPopover.environmentVariables" - ) - //- begin form .label-description.clearfix( ng-hide = "$root.featureFlags.fullScreen" diff --git a/client/directives/environment/modals/forms/formFiles/containerFilesController.js b/client/directives/environment/modals/forms/formFiles/containerFilesController.js index b734ffe60..c73e35b60 100644 --- a/client/directives/environment/modals/forms/formFiles/containerFilesController.js +++ b/client/directives/environment/modals/forms/formFiles/containerFilesController.js @@ -3,6 +3,7 @@ require('app').controller('ContainerFilesController', ContainerFilesController); function ContainerFilesController( + $q, loadingPromises, promisify, errs, @@ -161,25 +162,39 @@ function ContainerFilesController( }, deleteFile: function (containerFile) { $rootScope.$broadcast('close-popovers'); - - var file = containerFile.fileModel || self.state.contextVersion.rootDir.contents.models.find(function (fileModel) { - return fileModel.attrs.name === containerFile.name; - }); - if (file) { - var containerIndex = self.state.containerFiles.indexOf(containerFile); - if (containerIndex > -1) { - self.state.containerFiles.splice(containerIndex, 1); - } - - return loadingPromises.add('editServerModal', - promisify(file, 'destroy')() - .then(function () { - return updateDockerfileFromState(self.state); - }) - .catch(errs.handler) - ); - } - + return loadingPromises.add( + 'editServerModal', + $q.when() + .then(function () { + if (containerFile.fileModel) { + return containerFile.fileModel; + } + return promisify(self.state.contextVersion.rootDir.contents, 'fetch')() + .then(function () { + return self.state.contextVersion.rootDir.contents.models.find(function (fileModel) { + return fileModel.attrs.name === containerFile.name; + }); + }); + }) + .then(function (file) { + if (file) { + return promisify(file, 'destroy')() + .then(function () { + return promisify(self.state.contextVersion.rootDir.contents, 'fetch')(); + }); + } + }) + .then(function () { + var containerIndex = self.state.containerFiles.indexOf(containerFile); + if (containerIndex > -1) { + self.state.containerFiles.splice(containerIndex, 1); + } + }) + .then(function () { + return updateDockerfileFromState(self.state); + }) + .catch(errs.handler) + ); } }, data: {} @@ -233,33 +248,7 @@ function ContainerFilesController( ); $rootScope.$broadcast('close-popovers'); }, - remove: function (sshKeyFile) { - var file = sshKeyFile.fileModel || self.state.contextVersion.rootDir.contents.models.find(function (fileModel) { - return fileModel.attrs.name === sshKeyFile.name; - }); - var containerIndex = self.state.containerFiles.indexOf(sshKeyFile); - if (containerIndex > -1) { - self.state.containerFiles.splice(containerIndex, 1); - } - - if (file) { - loadingPromises.add( - 'editServerModal', - promisify(file, 'destroy')() - .then(function () { - return promisify(self.state.contextVersion.rootDir.contents, 'fetch')(); - }) - .then(function () { - return updateDockerfileFromState(self.state); - }) - .catch(errs.handler) - ); - } else { - loadingPromises.add('editServerModal', updateDockerfileFromState(self.state)) - .catch(errs.handler); - } - $rootScope.$broadcast('close-popovers'); - } + remove: this.fileUpload.actions.deleteFile } }, getFileDate: function (sshKeyFile) { diff --git a/client/directives/environment/modals/forms/formFiles/viewFormFiles.jade b/client/directives/environment/modals/forms/formFiles/viewFormFiles.jade index e7dca402c..85d5de481 100644 --- a/client/directives/environment/modals/forms/formFiles/viewFormFiles.jade +++ b/client/directives/environment/modals/forms/formFiles/viewFormFiles.jade @@ -1,18 +1,3 @@ -.help-container( - ng-if = "SMC.helpCards.getActiveCard().helpPopover.containerFiles && SMC.helpCards.cardIsActiveOnThisContainer(SMC.instance)" -) - .help - button.btn.btn-xs.btn-icon( - ng-click = "SMC.helpCards.hideActiveCard()" - ) - svg.iconnables.icons-close - use( - xlink:href = "#icons-close" - ) - span( - ng-bind-html = "SMC.helpCards.getActiveCard().helpPopover.containerFiles" - ) - .ng-hide( pop-over pop-over-actions = "CFC.repositoryPopover.actions" diff --git a/client/directives/environment/modals/forms/formLogs/viewFormLogs.jade b/client/directives/environment/modals/forms/formLogs/viewFormLogs.jade index 669b5756d..eb4cab684 100644 --- a/client/directives/environment/modals/forms/formLogs/viewFormLogs.jade +++ b/client/directives/environment/modals/forms/formLogs/viewFormLogs.jade @@ -1,20 +1,3 @@ -.grid-block.shrink.justify-center.align-center.padding-xs.well.gray.aha-tips( - ng-if = "\ - !$root.isLoading.setupServerModalIsBuilding && \ - !SMC.isDirty() && \ - SMC.instance.status() === 'buildFailed' \ - " -) - svg.grid-content.shrink.iconnables - use( - xlink:href = "#icons-life-preserver" - ) - .grid-content.vertical - small.grid-content.small Having build problems? Some errors can be resolved by rebuilding the container. - button.btn.btn-xxs.orange( - ng-click = "SMC.rebuild(true, true)" - ) Rebuild Without Cache - //- tabs .btn-group.btn-toggle.btn-toggle-xs( ng-hide = "$root.featureFlags.fullScreen" @@ -31,7 +14,7 @@ button.btn.btn-xs.white( ng-class = "{'active': SMC.page === 'terminal'}" ng-click = "SMC.page = 'terminal'" - ng-disabled = "!SMC.instance.containers.models.length" + ng-disabled = "!SMC.instance.containers.models.length || SMC.instance.status() !== 'running'" ng-if = "$root.featureFlags.configTerminal" ) Terminal @@ -62,24 +45,68 @@ pre.pre.log-wrapper( ng-if = "SMC.page === 'run' && !SMC.showDebugCmd && $root.featureFlags.webToolbar" ) + .popover.bottom.padding-sm.popover-aha.popover-sm.in.sans-serif( + ng-if = "SMC.showUrlToolbar && SMC.page === 'run' && !SMC.showDebugCmd && $root.featureFlags.webToolbar" + style = "left: 12px; top: 60px;" + ) + .arrow.white + small.small.text-gray.float-left Use this URL to check out your application. + button.btn.btn-xs.gray.float-right( + ng-click = "SMC.showUrlToolbar = false" + ) Dismiss + //- build logs page .build-log-wrapper( build-logs - instance = 'SMC.instance' + scroll-glue + instance = "SMC.instance" ng-if = "SMC.instance" ng-show = "SMC.page === 'build'" ng-style = "($root.featureFlags.themeToggle || $root.featureFlags.fullScreenToggle) && {'padding-right': '42px'}" - scroll-glue ) //- cmd logs page .term-js.term-log( controller = "BoxLogController" - instance = 'SMC.instance' + instance = "SMC.instance" log-term ng-if = "SMC.instance.containers.models.length" ng-show = "SMC.page === 'run'" ) + //- terminal + .term-js( + controller = "TermController" + debug-container = "debugContainer" + instance = "SMC.instance" + tab-item = "item" + log-term + ng-if = "SMC.instance.containers.models.length" + ng-show = "SMC.page === 'terminal'" + ) .floating-controls( ng-include = "'viewFloatingControls'" ) + +.grid-block.shrink.justify-center.align-center.padding-xs.well.gray.aha-tips( + ng-if = "\ + !$root.isLoading.setupServerModalIsBuilding && \ + !SMC.isDirty() && \ + SMC.instance.status() === 'buildFailed' \ + " +) + svg.grid-content.shrink.iconnables + use( + xlink:href = "#icons-life-preserver" + ) + small.grid-content.small + //- If build error: + | Build problems? Sometimes rebuilding the container can resolve errors, otherwise inspect your build logs. + //- IF CMD error: + //- | Your container is having trouble running. Check the CMD Logs and your CMD Command. + a.link( + href = "https://support.runnable.com" + target = "_blank" + ) View Documentation + button.grid-content.shrink.btn.btn-xs.orange( + ng-click = "SMC.rebuild(true, true)" + ) Rebuild diff --git a/client/directives/environment/modals/forms/formRepository/repositoryFormDirective.js b/client/directives/environment/modals/forms/formRepository/repositoryFormDirective.js index 09f20e7f4..426353d3c 100644 --- a/client/directives/environment/modals/forms/formRepository/repositoryFormDirective.js +++ b/client/directives/environment/modals/forms/formRepository/repositoryFormDirective.js @@ -9,8 +9,7 @@ require('app') updateDockerfileFromState, parseDockerfileForDefaults, report, - watchOncePromise, - helpCards + watchOncePromise ) { return { restrict: 'A', @@ -23,7 +22,6 @@ require('app') startCommandCanDisable: '=?' }, link: function ($scope, element, attrs) { - $scope.helpCards = helpCards; $scope.data = { }; watchOncePromise($scope, 'state.containerFiles', true) .then(function (containerFiles) { diff --git a/client/directives/environment/modals/forms/formRepository/viewFormRepository.jade b/client/directives/environment/modals/forms/formRepository/viewFormRepository.jade index 0730a1cc7..b5993cb03 100644 --- a/client/directives/environment/modals/forms/formRepository/viewFormRepository.jade +++ b/client/directives/environment/modals/forms/formRepository/viewFormRepository.jade @@ -1,18 +1,3 @@ -.help-container( - ng-if = "helpCards.getActiveCard().helpPopover.repositories" -) - .help - button.btn.btn-xs.btn-icon( - ng-click = "helpCards.hideActiveCard()" - ) - svg.iconnables.icons-close - use( - xlink:href = "#icons-close" - ) - span( - ng-bind-html = "helpCards.getActiveCard().helpPopover.repositories" - ) - label.label.clearfix .label-col Packages small.small (optional) diff --git a/client/directives/environment/modals/forms/formStack/viewFormStack.jade b/client/directives/environment/modals/forms/formStack/viewFormStack.jade index 9173ef411..99b578486 100644 --- a/client/directives/environment/modals/forms/formStack/viewFormStack.jade +++ b/client/directives/environment/modals/forms/formStack/viewFormStack.jade @@ -19,7 +19,7 @@ ) .grid-block.shrink.align-center.well.well-600.gray.padding-sm .grid-content - .strong.text-gray-dark This is a test container. + .strong.text-gray-dark This is a test template. small.small We’ll report your test results on your pull-requests on GitHub. label.toggle-wrapper.shrink input.toggle-input( diff --git a/client/directives/environment/modals/forms/formTranslation/translationRulesDirective.js b/client/directives/environment/modals/forms/formTranslation/translationRulesDirective.js index ec546ff61..602af3822 100644 --- a/client/directives/environment/modals/forms/formTranslation/translationRulesDirective.js +++ b/client/directives/environment/modals/forms/formTranslation/translationRulesDirective.js @@ -7,8 +7,7 @@ require('app') parseDiffResponse, promisify, testAllTransformRules, - $document, - helpCards + $document ) { return { restrict: 'A', @@ -19,7 +18,6 @@ require('app') data: '=?' }, link: function ($scope, elem, attrs) { - $scope.helpCards = helpCards; $scope.$watch('state.contextVersion', function (contextVersion) { if (contextVersion && (keypather.get(contextVersion, 'getMainAppCodeVersion().attrs.transformRules.replace.length') || diff --git a/client/directives/environment/modals/forms/formTranslation/viewFormTranslation.jade b/client/directives/environment/modals/forms/formTranslation/viewFormTranslation.jade index 3fd69d1ec..69b054251 100644 --- a/client/directives/environment/modals/forms/formTranslation/viewFormTranslation.jade +++ b/client/directives/environment/modals/forms/formTranslation/viewFormTranslation.jade @@ -1,18 +1,3 @@ -.help-container( - ng-if = "helpCards.getActiveCard().helpPopover.findAndReplace && helpCards.cardIsActiveOnThisContainer(instance)" -) - .help - button.btn.btn-xs.btn-icon( - ng-click = "helpCards.hideActiveCard()" - ) - svg.iconnables.icons-close - use( - xlink:href = "#icons-close" - ) - span( - ng-bind-html = "helpCards.getActiveCard().helpPopover.findAndReplace" - ) - .btn-group.btn-toggle.btn-toggle-xs( ng-init = "page = 'rules'" ) diff --git a/client/directives/environment/modals/forms/guideForm/guideFormView.jade b/client/directives/environment/modals/forms/guideForm/guideFormView.jade index 229b434da..7ee073646 100644 --- a/client/directives/environment/modals/forms/guideForm/guideFormView.jade +++ b/client/directives/environment/modals/forms/guideForm/guideFormView.jade @@ -1,8 +1,9 @@ -p.p.grid-content.shrink.text-center.text-gray.padding-sm You have the basic parts of your application configured! Many applications require additional configuration. Use our tools to configure options specific to your application before building. +p.p.grid-content.shrink.text-center.text-gray.padding-sm You have the basic parts of your application set up and ready to build! If your application requires additional configuration, use our tools to change options before building. a.grid-content.shrink.link.small.text-gray( - href = "#" + href = "https://support.runnable.com/hc/en-us/categories/201194573-Quickstarts" + target = "_blank" ) View Setup Guide svg.iconnables.icons-external use( xlink:href= "#icons-link-external" - ) \ No newline at end of file + ) diff --git a/client/directives/environment/modals/forms/whitelistForm/whitelistFormView.jade b/client/directives/environment/modals/forms/whitelistForm/whitelistFormView.jade index 7864ca67e..aa0b1fd2a 100644 --- a/client/directives/environment/modals/forms/whitelistForm/whitelistFormView.jade +++ b/client/directives/environment/modals/forms/whitelistForm/whitelistFormView.jade @@ -2,7 +2,7 @@ .grid-block.shrink.align-center.well.well-600.gray.padding-sm .grid-content .strong.text-gray-dark Block External Access - small.small Prevents external access to this container. Internal access between containers is still allowed. + small.small Prevents external access to this template. Internal access between containers is still allowed. label.grid-content.toggle-wrapper.shrink input.toggle-input( ng-disabled = "WFC.isIsolationGroupMaster" diff --git a/client/directives/environment/modals/modalEditServer/editServerModalController.js b/client/directives/environment/modals/modalEditServer/editServerModalController.js index 17b70323f..f870b7fb7 100644 --- a/client/directives/environment/modals/modalEditServer/editServerModalController.js +++ b/client/directives/environment/modals/modalEditServer/editServerModalController.js @@ -12,7 +12,6 @@ function EditServerModalController( cleanStartCommand, errs, fetchInstancesByPod, - helpCards, instance, isTabNameValid, keypather, @@ -24,7 +23,6 @@ function EditServerModalController( close ) { var SMC = this; - SMC.helpCards = helpCards; var parentController = $controller('ServerModalController as SMC', { $scope: $scope }); angular.extend(SMC, { @@ -34,6 +32,7 @@ function EditServerModalController( 'enableMirrorMode': parentController.enableMirrorMode.bind(SMC), 'getNumberOfOpenTabs': parentController.getNumberOfOpenTabs.bind(SMC), 'getUpdatePromise': parentController.getUpdatePromise.bind(SMC), + 'hasOpenPorts': parentController.hasOpenPorts.bind(SMC), 'insertHostName': parentController.insertHostName.bind(SMC), 'isDirty': parentController.isDirty.bind(SMC), 'openDockerfile': parentController.openDockerfile.bind(SMC), diff --git a/client/directives/environment/modals/modalRename/viewModalRename.jade b/client/directives/environment/modals/modalRename/viewModalRename.jade index 61022489b..16ff2ff31 100644 --- a/client/directives/environment/modals/modalRename/viewModalRename.jade +++ b/client/directives/environment/modals/modalRename/viewModalRename.jade @@ -11,7 +11,7 @@ name = "rename" ) label.label.clearfix - .label-col Rename Container + .label-col Rename Template .input-col div( ng-class = "{\ @@ -32,7 +32,7 @@ ) ul.list.list-bulleted.list-validation - li.list-item Choose a unique name in your Sandbox + li.list-item Choose a unique name for this template li.list-item( ng-class = "{'ng-invalid': rename.$error.pattern}" ) Use letters, numbers, and hyphens (-) @@ -55,4 +55,4 @@ ) Cancel button.btn.btn-md.green.float-right( ng-disabled = "!rename.newName.$viewValue.length || rename.$invalid" - ) Rename Container \ No newline at end of file + ) Rename Template diff --git a/client/directives/environment/modals/modalSetupServer/pages/newRepositorySelectionView.jade b/client/directives/environment/modals/modalSetupServer/pages/newRepositorySelectionView.jade index ee4896a27..1e0d0a131 100644 --- a/client/directives/environment/modals/modalSetupServer/pages/newRepositorySelectionView.jade +++ b/client/directives/environment/modals/modalSetupServer/pages/newRepositorySelectionView.jade @@ -30,7 +30,7 @@ ) .p.empty .h3.strong Uh oh! - | We couldn’t find any repositories for this organization. If you think you think this is a mistake, + | We couldn’t find any repositories for this organization. If you think this is a mistake, a.link( intercom-link intro-message = "This thing wont fetch my GitHub repos." @@ -51,56 +51,55 @@ ul.list.list-servers( | ‘{{MC.repoFilter}}’. //- add active class when loading - li.list-item.multi-line.text-overflow( + li.grid-block.align-center.list-item.multi-line.text-overflow( ng-disabled = "$root.isLoading[MC.name + 'SingleRepo']" ng-click = "!$root.isLoading[MC.name + 'SingleRepo'] && MC.setRepo(repo, goToPanel)" ng-repeat = "\ repo in MC.githubRepos.models | \ repos: MC.repoFilter | \ - orderBy: '-attrs.updated_at' as noResults\ + orderBy: '-attrs.pushed_at' as noResults\ " ng-class = "{ \ 'active': repo.loading, \ 'disabled': $root.isLoading[MC.name + 'SingleRepo'] \ }" - ) {{ repo.attrs.name }} - .row.row-author( - ng-if = "$root.featureFlags.inviteFlows" - ) - //- if invite flows... - .btn-user.text-overflow( - ng-class = "{'active': MC.state.active}" - ng-include = "'userButtonView'" - pop-over - pop-over-active = "MC.state.active" - pop-over-options = "{\"left\":-24,\"top\":26}" - pop-over-template = "userPopoverView" - ) - span.small( + ) + .grid-content.text-overflow( + title = "{{ repo.attrs.name }}" + ) {{ repo.attrs.name }} + .row.row-author( ng-if = "$root.featureFlags.inviteFlows" - ) {{repo.attrs.updated_at | timeFrom}} - small.small( - ng-if = "!$root.featureFlags.inviteFlows" - ) {{repo.attrs.updated_at | timeFrom}} - svg.iconnables.icons-server( - ng-if = "$root.featureFlags.multipleRepositoryContainers" - pop-over - pop-over-hover-trigger - pop-over-options = "{\"top\":27,\"centered\":true}" - pop-over-template = "viewCountainerTooltip" - pop-over-trigger = "hover" ) - use( - xlink:href = "#icons-server" + //- if invite flows... + .btn-user.text-overflow( + ng-class = "{'active': MC.state.active}" + ng-include = "'userButtonView'" + pop-over + pop-over-active = "MC.state.active" + pop-over-options = "{\"left\":-22,\"top\":26}" + pop-over-template = "userPopoverView" ) - button.btn.btn-sm.btn-icon.btn-add( - ng-if = "!repo.loading" - ) - svg.iconnables.icons-add - use( - xlink:href = "#icons-add" + span.small( + ng-if = "$root.featureFlags.inviteFlows" + ) {{repo.attrs.pushed_at | timeFrom}} + small.small( + ng-if = "!$root.featureFlags.inviteFlows" + ) {{repo.attrs.pushed_at | timeFrom}} + svg.iconnables.icons-server( + ng-if = "$root.featureFlags.multipleRepositoryContainers" + pop-over + pop-over-hover-trigger + pop-over-options = "{\"top\":27,\"centered\":true}" + pop-over-template = "viewCountainerTooltip" + pop-over-trigger = "hover" ) - .spinner-wrapper.spinner-sm.spinner-green.in( + use( + xlink:href = "#icons-server" + ) + button.grid-content.shrink.btn.btn-sm.btn-icon.btn-add( + ng-if = "!repo.loading" + ) Select + .grid-content.shrink.spinner-wrapper.spinner-sm.spinner-green.in( ng-if = "repo.loading" ng-include = "'spinner'" ) diff --git a/client/directives/environment/modals/modalSetupServer/pages/templateSelectSectionView.jade b/client/directives/environment/modals/modalSetupServer/pages/templateSelectSectionView.jade index b10d6f94d..74c931a8b 100644 --- a/client/directives/environment/modals/modalSetupServer/pages/templateSelectSectionView.jade +++ b/client/directives/environment/modals/modalSetupServer/pages/templateSelectSectionView.jade @@ -19,7 +19,7 @@ ul.list.list-servers( ng-if = "!$root.isLoading.newContainerModalTemplates" ) - li.list-item.grid-block( + li.grid-block.align-center.list-item( ng-click = "MC.setTemplate(dependency, goToPanel)" ng-repeat = "\ dependency in MC.templateServers.models | \ @@ -28,14 +28,10 @@ ul.list.list-servers( orderBy:'attrs.name'\ " ) - img.img( + img.grid-content.shrink.img( height = "36" ng-src = "/build/images/logos/logo-icon-{{dependency.attrs.name.toLowerCase()}}.png" width = "36" ) - | {{dependency.attrs.name}} - button.btn.btn-sm.btn-icon.btn-add - svg.iconnables.icons-add - use( - xlink:href = "#icons-add" - ) + .grid-content {{dependency.attrs.name}} + button.grid-content.shrink.btn.btn-sm.btn-icon.btn-add Select diff --git a/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalController.js b/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalController.js index 7ef90175e..bd5813748 100644 --- a/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalController.js +++ b/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalController.js @@ -4,34 +4,33 @@ require('app') .controller('SetupMirrorServerModalController', SetupMirrorServerModalController); function SetupMirrorServerModalController( - $scope, $controller, $q, $rootScope, + $scope, + ahaGuide, cardInfoTypes, createAndBuildNewContainer, - createBuildFromContextVersionId, errs, eventTracking, - fetchUser, - helpCards, + fetchInstancesByPod, isTabNameValid, keypather, loading, loadingPromises, - ModalService, promisify, - OpenItems, - TAB_VISIBILITY, - updateDockerfileFromState, + // everything below comes from the modal open + build, close, instanceName, + masterBranch, + OpenItems, repo, - build, - masterBranch + TAB_VISIBILITY ) { var SMC = this; // Server Modal Controller (shared with EditServerModalController) - SMC.helpCards = helpCards; + SMC.isAddingFirstRepo = ahaGuide.isAddingFirstRepo; + SMC.showUrlToolbar = SMC.isAddingFirstRepo(); var parentController = $controller('ServerModalController as SMC', { $scope: $scope }); angular.extend(SMC, { @@ -43,6 +42,8 @@ function SetupMirrorServerModalController( 'getElasticHostname': parentController.getElasticHostname.bind(SMC), 'getNumberOfOpenTabs': parentController.getNumberOfOpenTabs.bind(SMC), 'getUpdatePromise': parentController.getUpdatePromise.bind(SMC), + 'handleInstanceUpdate': parentController.handleInstanceUpdate.bind(SMC), + 'hasOpenPorts': parentController.hasOpenPorts.bind(SMC), 'insertHostName': parentController.insertHostName.bind(SMC), 'isDirty': parentController.isDirty.bind(SMC), 'openDockerfile': parentController.openDockerfile.bind(SMC), @@ -113,6 +114,11 @@ function SetupMirrorServerModalController( repoSelected: true }); + fetchInstancesByPod() + .then(function (instances) { + SMC.data.instances = instances; + }); + SMC.state.mainRepoContainerFile.name = repo.attrs.name; SMC.state.promises.contextVersion = $q.when(SMC.state.contextVersion); @@ -124,6 +130,10 @@ function SetupMirrorServerModalController( SMC.openDockerfile(SMC.state, SMC.openItems) .finally(function () { loading(SMC.name, false); + }) + .catch(function(err) { + errs.handler(err); + close(); }); $scope.$on('resetStateContextVersion', function ($event, contextVersion, showSpinner) { @@ -182,6 +192,7 @@ function SetupMirrorServerModalController( if (instance) { SMC.instance = instance; SMC.state.instance = instance; + SMC.state.instance.on('update', SMC.handleInstanceUpdate); // Reset the opts, in the same way as `EditServerModalController` SMC.state.opts = { env: keypather.get(instance, 'attrs.env') || [], diff --git a/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalView.jade b/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalView.jade index 039dd2c6e..23f7b92de 100644 --- a/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalView.jade +++ b/client/directives/environment/modals/modalSetupServer/setupMirrorServerModalView.jade @@ -1,13 +1,12 @@ .modal-backdrop.in .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( - ng-if = "$root.featureFlags.aha1" - ng-include = "'ahaGuideView'" - ng-init = "\ - state.showStep = 1;\ - state.showSubStep = 6;\ - state.fromMirroring = true;\ - " + aha-guide + step-index = 1 + sub-step-index = 6 + sub-step = "filesMirror" + ng-if = "SMC.isAddingFirstRepo()" ) + form.modal-dialog.modal-edit( name = "SMC.serverForm" ng-include = "'serverModalView'" diff --git a/client/directives/environment/modals/modalSetupServer/setupServerModalController.js b/client/directives/environment/modals/modalSetupServer/setupServerModalController.js index cc6e68d4c..94264d480 100644 --- a/client/directives/environment/modals/modalSetupServer/setupServerModalController.js +++ b/client/directives/environment/modals/modalSetupServer/setupServerModalController.js @@ -9,13 +9,15 @@ function SetupServerModalController( $filter, $q, $rootScope, + ahaGuide, cardInfoTypes, createAndBuildNewContainer, createBuildFromContextVersionId, + dockerfileType, errs, eventTracking, fetchDockerfileFromSource, - helpCards, + fetchInstancesByPod, isTabNameValid, keypather, loading, @@ -31,7 +33,8 @@ function SetupServerModalController( masterBranch ) { var SMC = this; // Server Modal Controller (shared with EditServerModalController) - SMC.helpCards = helpCards; + SMC.isAddingFirstRepo = ahaGuide.isAddingFirstRepo; + SMC.showUrlToolbar = SMC.isAddingFirstRepo(); var parentController = $controller('ServerModalController as SMC', { $scope: $scope }); angular.extend(SMC, { @@ -43,6 +46,8 @@ function SetupServerModalController( 'getElasticHostname': parentController.getElasticHostname.bind(SMC), 'getNumberOfOpenTabs': parentController.getNumberOfOpenTabs.bind(SMC), 'getUpdatePromise': parentController.getUpdatePromise.bind(SMC), + 'handleInstanceUpdate': parentController.handleInstanceUpdate.bind(SMC), + 'hasOpenPorts': parentController.hasOpenPorts.bind(SMC), 'insertHostName': parentController.insertHostName.bind(SMC), 'isDirty': parentController.isDirty.bind(SMC), 'openDockerfile': parentController.openDockerfile.bind(SMC), @@ -110,14 +115,26 @@ function SetupServerModalController( // If a repo is passed into this controller, select that repo angular.extend(SMC.state, { - repo: repo, - build: build, - contextVersion: build.contextVersion, acv: build.contextVersion.getMainAppCodeVersion(), + advanced: dockerfileType, branch: masterBranch, - repoSelected: true, - advanced: false + build: build, + contextVersion: build.contextVersion, + repo: repo, + repoSelected: true }); + + fetchInstancesByPod() + .then(function (instances) { + SMC.data.instances = instances; + }); + + // if the blank docker file is chosen, we need to load it because it is already available + if (dockerfileType === 'blankDockerfile') { + SMC.openDockerfile({contextVersion: build.contextVersion}, SMC.openItems); + SMC.changeTab('buildfiles'); + } + SMC.state.mainRepoContainerFile.name = repo.attrs.name; SMC.state.promises.contextVersion = $q.when(SMC.state.contextVersion); @@ -235,7 +252,7 @@ function SetupServerModalController( var createPromise = loadingPromises.finished(SMC.name) .then(function () { loadingPromises.clear(SMC.name); - if (!SMC.state.advanced) { + if (!SMC.state.advanced || SMC.state.advanced === 'blankDockerfile') { return updateDockerfileFromState(SMC.state, false, true); } return true; @@ -256,6 +273,7 @@ function SetupServerModalController( if (instance) { SMC.instance = instance; SMC.state.instance = instance; + SMC.state.instance.on('update', SMC.handleInstanceUpdate); // Reset the opts, in the same way as `EditServerModalController` SMC.state.opts = { env: keypather.get(instance, 'attrs.env') || [], @@ -329,6 +347,9 @@ function SetupServerModalController( }; SMC.isPrimaryButtonDisabled = function (serverFormInvalid) { + if (SMC.state.advanced === 'blankDockerfile' || SMC.state.advanced === 'isMirroringDockerfile') { + return false; + } return ( (SMC.state.step === 2 && SMC.repositoryForm && SMC.repositoryForm.$invalid) || $filter('selectedStackInvalid')(SMC.state.selectedStack) diff --git a/client/directives/environment/modals/modalSetupServer/setupServerModalView.jade b/client/directives/environment/modals/modalSetupServer/setupServerModalView.jade index d0c532e74..7ac7f7f4a 100644 --- a/client/directives/environment/modals/modalSetupServer/setupServerModalView.jade +++ b/client/directives/environment/modals/modalSetupServer/setupServerModalView.jade @@ -1,11 +1,17 @@ .modal-backdrop.in .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( - ng-if = "$root.featureFlags.aha1" - ng-include = "'ahaGuideView'" - ng-init = "\ - state.showStep = 1;\ - state.showSubStep = 4;\ - " + aha-guide + ng-if = "SMC.state.advanced !== 'blankDockerfile' && SMC.isAddingFirstRepo()" + step-index = 1 + sub-step = "repository" + sub-step-index = 4 + ) + .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( + aha-guide + ng-if = "SMC.state.advanced === 'blankDockerfile' && SMC.isAddingFirstRepo()" + step-index = 1 + sub-step = "buildfiles" + sub-step-index = 6 ) form.modal-dialog.modal-edit( name = "SMC.serverForm" diff --git a/client/directives/environment/modals/nameNonRepoContainer/nameNonRepoContainerView.jade b/client/directives/environment/modals/nameNonRepoContainer/nameNonRepoContainerView.jade index 470f60b1c..5df5fa1c3 100644 --- a/client/directives/environment/modals/nameNonRepoContainer/nameNonRepoContainerView.jade +++ b/client/directives/environment/modals/nameNonRepoContainer/nameNonRepoContainerView.jade @@ -1,7 +1,7 @@ .modal-backdrop .modal-dialog.modal-sm.modals-rename header.modal-header - h1.modal-heading.text-overflow Create Container + h1.modal-heading.text-overflow Create Template svg.iconnables.icons-close( ng-click = "MC.actions.cancel()" ) @@ -13,7 +13,7 @@ name = "nameForm" ) label.label.clearfix - .label-col Container Name + .label-col Template Name .input-col div( ng-attr-data-length = "{{19 - nameForm.newName.$viewValue.length}}" @@ -38,7 +38,7 @@ ul.list.list-bulleted.list-validation li.list-item( ng-class = "{'ng-invalid': nameForm.$error.unique}" - ) Choose a unique name in your Sandbox + ) Choose a unique name for this template li.list-item( ng-class = "{'ng-invalid': nameForm.$error.pattern || nameForm.$error.noDoubleDash}" ) Use letters, numbers, and single hyphens (-) @@ -58,4 +58,4 @@ ng-include = "'spinner'" ng-if = "MC.saving" ) - span Create Container + span Create Template diff --git a/client/directives/environment/modals/popovers/viewPopoverFileOptions.jade b/client/directives/environment/modals/popovers/viewPopoverFileOptions.jade index 8143e6cc2..d84be8544 100644 --- a/client/directives/environment/modals/popovers/viewPopoverFileOptions.jade +++ b/client/directives/environment/modals/popovers/viewPopoverFileOptions.jade @@ -1,10 +1,10 @@ .popover.menu.bottom( - ng-class = "{in: active}" + ng-class = "{'in': active}" ng-style = "popoverStyle.getStyle()" style = "transform-origin: 90% 0" ) .arrow.white( - style = "left: auto; right: 3px" + style = "left: auto; right: 2px" ) .popover-content ul.popover-list diff --git a/client/directives/environment/modals/popovers/viewPopoverFilesUpload.jade b/client/directives/environment/modals/popovers/viewPopoverFilesUpload.jade index 13744709a..aa6658361 100644 --- a/client/directives/environment/modals/popovers/viewPopoverFilesUpload.jade +++ b/client/directives/environment/modals/popovers/viewPopoverFilesUpload.jade @@ -49,7 +49,7 @@ ng-model = "data.commands" spellcheck = "false" ) - small.small Add scripts to be run before your container is started. + small.small Add scripts to run before your container is started. .popover-footer.clearfix button.btn.btn-sm.green( diff --git a/client/directives/environment/modals/popovers/viewPopoverRenameContainer.jade b/client/directives/environment/modals/popovers/viewPopoverRenameContainer.jade index 212fbd529..5b0031f90 100644 --- a/client/directives/environment/modals/popovers/viewPopoverRenameContainer.jade +++ b/client/directives/environment/modals/popovers/viewPopoverRenameContainer.jade @@ -4,7 +4,7 @@ ng-class = "{'in': active}" ) .popover-content - h3.popover-title Rename Container + h3.popover-title Rename Template //- wrapper for validation div( @@ -26,7 +26,7 @@ ) ul.list.list-bulleted.list-validation - li.list-item Choose a unique name in your Sandbox + li.list-item Choose a unique name for this template li.list-item( ng-class = "{'ng-invalid': rename.$error.pattern}" ) Use letters, numbers, and hyphens (-) @@ -44,7 +44,7 @@ .popover-footer.clearfix button.btn.btn-sm.green( ng-disabled = "!rename.newName.$viewValue.length || rename.$invalid" - ) Rename Container + ) Rename Template button.btn.btn-sm.gray.btn-cancel( ng-click = "actions.cancel()" ) Cancel \ No newline at end of file diff --git a/client/directives/environment/modals/serverModalController.js b/client/directives/environment/modals/serverModalController.js index 02f54013e..8a4b49fc3 100644 --- a/client/directives/environment/modals/serverModalController.js +++ b/client/directives/environment/modals/serverModalController.js @@ -9,11 +9,10 @@ function ServerModalController( $rootScope, $scope, createBuildFromContextVersionId, + configUserContentDomain, errs, eventTracking, fetchDockerfileForContextVersion, - hasKeypaths, - helpCards, keypather, ModalService, loading, @@ -95,7 +94,7 @@ function ServerModalController( toRedeploy = !toRebuild && rebuildOrRedeploy === 'update'; loadingPromises.clear(SMC.name); - if (!SMC.openItems.isClean()) { + if (!SMC.openItems.isClean() || SMC.state.advanced === 'blankDockerfile') { return SMC.openItems.updateAllFiles(); } }) @@ -262,6 +261,10 @@ function ServerModalController( } }; + this.hasOpenPorts = function() { + return !!keypather.get(this, 'instance.attrs.container.ports'); + }; + this.onEnvChange = function (newEnvArray, oldEnvArray) { var SMC = this; if (!newEnvArray) { return; } @@ -273,11 +276,15 @@ function ServerModalController( this.resetStateContextVersion = function (contextVersion, shouldParseDockerfile) { var SMC = this; - if (keypather.get(contextVersion, 'attrs.buildDockerfilePath')) { - SMC.state.advanced = 'isMirroringDockerfile'; - } else { - SMC.state.advanced = !!keypather.get(contextVersion, 'attrs.advanced'); + + if (SMC.state.advanced !== 'blankDockerfile') { + if (keypather.get(contextVersion, 'attrs.buildDockerfilePath')) { + SMC.state.advanced = 'isMirroringDockerfile'; + } else { + SMC.state.advanced = !!keypather.get(contextVersion, 'attrs.advanced'); + } } + SMC.state.promises.contextVersion = loadingPromises.start( SMC.name, promisify(contextVersion, 'deepCopy')() @@ -337,7 +344,6 @@ function ServerModalController( $rootScope.$broadcast('close-popovers'); return SMC.rebuildAndOrRedeploy() .then(function () { - helpCards.refreshActiveCard(); $rootScope.$broadcast('alert', { type: 'success', text: 'Changes Saved' @@ -346,6 +352,16 @@ function ServerModalController( }); }; + this.handleInstanceUpdate = function () { + var buildStatus = this.instance.status(); + $rootScope.$broadcast('buildStatusUpdated', { + status: buildStatus + }); + if (buildStatus === 'running') { + this.page = 'run'; + } + }; + this.switchToMirrorMode = function (state, openItems, dockerfile) { var SMC = this; return loadingPromises.add(SMC.name, promisify(state.contextVersion, 'update')({ @@ -362,7 +378,7 @@ function ServerModalController( var SMC = this; var errorMessage = ''; errorMessage += '# There was an error retrieving the Dockerfile from your repo'; - errorMessage += '# This error occured when disabling mirrorring your Dockerfile'; + errorMessage += '# This error occured when disabling mirroring your Dockerfile'; var dockerfileBody = keypather.get(state, 'dockerfile.attrs.body') || errorMessage; return loadingPromises.add(SMC.name, promisify(state.contextVersion, 'update')({ advanced: true, @@ -479,8 +495,7 @@ function ServerModalController( var repo = SMC.state.repo; var repoName = repo.attrs.name; var repoOwner = repo.attrs.owner.login.toLowerCase(); - var domain = SMC.state.repo.opts.userContentDomain; - return repoName + '-staging-' + repoOwner + '.' + domain; + return repoName + '-staging-' + repoOwner + '.' + configUserContentDomain; } return ''; }; @@ -499,14 +514,15 @@ function ServerModalController( }; /** - * Updates the this.instance with all the states, emits the Changes Saved alert, and refreshes the - * help cards. If a failure occurs, the cv is reset, and the error propagates. + * Updates the this.instance with all the states, emits the Changes Saved alert. + * If a failure occurs, the cv is reset, and the error propagates. * @returns {Promise} Resolves when the instance update has been started, and the cv has been * reset. The error is uncaught, so a catch should be added to this */ this.getUpdatePromise = this.saveInstanceAndRefreshCards; this.changeTab = function (tabname) { + $rootScope.$broadcast('updatedTab', tabname); var SMC = this; if (keypather.get(SMC, 'serverForm.$invalid')) { if (keypather.get(SMC, 'serverForm.$error.required.length')) { diff --git a/client/directives/environment/modals/setupTemplate/setupTemplateModalController.js b/client/directives/environment/modals/setupTemplate/setupTemplateModalController.js index caebf20a0..076b8b7f4 100644 --- a/client/directives/environment/modals/setupTemplate/setupTemplateModalController.js +++ b/client/directives/environment/modals/setupTemplate/setupTemplateModalController.js @@ -15,7 +15,6 @@ function SetupTemplateModalController( fetchInstances, getNewForkName, promisify, - helpCards, ModalService, close, isolation @@ -26,7 +25,6 @@ function SetupTemplateModalController( STMC.templateServers = servers; }) .catch(errs.handler); - STMC.helpCard = helpCards.getActiveCard(); STMC.close = close; STMC.addServerFromTemplate = function (sourceInstance) { var instancesPromise = null; diff --git a/client/directives/environment/newUserPrompt.jade b/client/directives/environment/newUserPrompt.jade index f62b73258..db6159008 100644 --- a/client/directives/environment/newUserPrompt.jade +++ b/client/directives/environment/newUserPrompt.jade @@ -5,7 +5,7 @@ svg.iconnables.icons-close( xlink:href = "#icons-close" ) h1.grid-content.h1.justify-center.text-center Welcome to Runnable! -p.p.text-center You made it to the configuration page, where you set up your app. We’ve got guides for common stacks to help you get started. +p.p.text-center You made it to the template page, where you set up your app. We’ve got guides for common stacks to help you get started. .grid-block a.grid-content.shrink.btn.btn-xs.purple( href = "https://runnable.zendesk.com/hc/en-us/articles/208383313-Node-js-" diff --git a/client/directives/environment/tooltips/viewTooltipTools.jade b/client/directives/environment/tooltips/viewTooltipTools.jade index a611c309a..995b0582c 100644 --- a/client/directives/environment/tooltips/viewTooltipTools.jade +++ b/client/directives/environment/tooltips/viewTooltipTools.jade @@ -29,4 +29,4 @@ xlink:href = "#icons-keys-files" ) | Files & SSH Keys - small.p.small Your configuration is saved when you enable editing and will be restored if you disable editing in the future. + small.p.small Your template is saved when you enable editing and will be restored if you disable editing in the future. diff --git a/client/directives/help/viewHelpPopover.jade b/client/directives/help/viewHelpPopover.jade deleted file mode 100644 index 05011b5a6..000000000 --- a/client/directives/help/viewHelpPopover.jade +++ /dev/null @@ -1,68 +0,0 @@ -.popover.help( - ng-class = "{'in': active}" - ng-style = "popoverStyle.getStyle()" -) - .help-lists-wrapper - ul.triggered-help-list - li.grid-block.triggered-help-item.clearfix( - ng-repeat = "helpItem in data.triggered" - ) - button.btn.btn-icon.btn-icon-xs( - ng-click = "actions.ignoreHelp(helpItem)" - ) - svg.iconnables - use( - xlink:href = "#icons-close" - ) - p.p.grid-content( - ng-bind-html= "helpItem.label" - ) - button.btn.btn-xs.blue.grid-content.shrink( - ng-click = "actions.getHelp(helpItem)" - ) Show Me - .general-help-container - .strong General Help - ul.general-help-list - li.general-help-item( - ng-repeat = "helpItem in data.general" - ng-hide = "!showMore && $index > 5" - ng-click = "actions.getHelp(helpItem)" - ng-bind-html= "helpItem.label" - ) - li.general-help-item - a.link( - target = "_blank" - href = "http://www.youtube.com/watch?v=_uwPKIH990E" - ) View our Getting Started video - svg.iconnables.icons-link-external - use( - xlink:href = "#icons-link-external" - ) - li.general-help-item - a.link( - target = "_blank" - href = "https://runnable.zendesk.com/hc/en-us/articles/205184275-How-can-containers-in-my-Runnable-Sandbox-communicate-with-each-other-" - ) Connect two containers together - svg.iconnables.icons-link-external - use( - xlink:href = "#icons-link-external" - ) - - button.btn( - ng-click = "POC.closePopover()" - ) - svg.iconnables( - ng-if = '!data.triggered.length' - ) - use( - xlink:href = "#icons-close" - ) - - | {{data.triggered.length ? 'Less Help' : 'Close'}} - - svg.iconnables( - ng-if = 'data.triggered.length' - ) - use( - xlink:href = "#icons-arrow-down" - ) diff --git a/client/directives/instances/instance/branchMenuPopover/branchMenuPopoverView.jade b/client/directives/instances/instance/branchMenuPopover/branchMenuPopoverView.jade index f40d6c0af..621bd4f78 100644 --- a/client/directives/instances/instance/branchMenuPopover/branchMenuPopoverView.jade +++ b/client/directives/instances/instance/branchMenuPopover/branchMenuPopoverView.jade @@ -1,11 +1,27 @@ .popover.menu.right.popover-branch-menu( ng-class = "{'in': active}" - ng-style = "popoverStyle.getStyle()" + ng-style = "popoverStyle.getStyle(CIS.instanceBranches)" ) - .arrow.white + .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( + ng-if = "$root.featureFlags.aha && CIS.instanceBranches.length && CIS.isAddingFirstBranch()" + aha-guide + step-index = 2 + sub-step = "selectBranch" + ) + .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( + ng-if = "$root.featureFlags.aha && CIS.instanceBranches && !CIS.instanceBranches.length && CIS.isAddingFirstBranch()" + aha-guide + step-index = 2 + sub-step = "noBranches" + ) + + .arrow.white( + ng-style = "popoverStyle.getArrowStyle(CIS.instanceBranches)" + ) .grid-block.vertical.popover-content.empty( ng-if = "$root.featureFlags.autoIsolationSetup" ) + svg.svg.grid-block use( xlink:href = "#icons-branch-burst" @@ -16,10 +32,10 @@ ) Set Up Branches animated-panel-container.popover-views( - ng-if = "!$root.featureFlags.autoIsolationSetup" + ng-if = "!$root.featureFlags.autoIsolationSetup && !CIS.isPopoverOpen()" ) + //- this should be the default panel when auto-isolation is implemented animated-panel( - default = "true" name = "branchMenu" ) .popover-view @@ -62,12 +78,15 @@ ) | Include Containers… animated-panel( + default = "true" name = "addBranch" ) .popover-view.fade( ng-class = "{'in': isActivePanel()}" ) - .popover-header + .popover-header( + ng-if = "$root.featureFlags.autoIsolation" + ) svg.btn.btn-sm.iconnables.icons-arrow-backward( ng-class = "{'in': isActivePanel()}" ng-click = "goToPanel('branchMenu', 'back');" @@ -76,48 +95,74 @@ xlink:href = "#icons-arrow-down" ) | Add Branch - .popover-content( - ng-init = "state.branchesLoaded = null" - ) - .text-center.text-gray.small.padding-md( - ng-if = "branch.length === 0" - ) There are no branches to add. - + .popover-content .spinner-wrapper.spinner-sm.spinner-gray.spinner-center.in( - ng-click = "state.branchesLoaded = true" - ng-if = "!state.branchesLoaded" + ng-if = "$root.isLoading.fetchingBranches" ng-include = "'spinner'" ) .padding-xxs( - ng-if = "state.branchesLoaded" + ng-if = "CIS.instanceBranches" ) + .grid-block.shrink.align-center.well.gray.padding-xxs( + ng-if = "!$root.featureFlags.autoIsolation" + ) + .grid-content + .small Automatically add branches as they’re updated on GitHub. + button.btn.btn-xs.btn-permissions( + internal-modal-helper = "inviteAdminModalView" + ng-if = "$root.featureFlags.webhooks" + ng-include = "'permissionsButtonView'" + ) + label.grid-content.shrink.toggle-wrapper + input.toggle-input( + ng-click = "CIS.setAutofork()" + ng-disabled = "$root.featureFlags.webhooks" + ng-checked = "!CIS.poppedInstance.attrs.shouldNotAutofork" + type = "checkbox" + ) + .toggle-group.toggle-sm input.input.input-xs.input-search( - placeholder = "Filter" + ng-disabled = "$root.isLoading['buildingForkedBranch']" + ng-model = "CIS.branchQuery" + placeholder = "Filter by name" required type = "search" + ng-if = "CIS.instanceBranches.length" ) ul.list.popover-list( - ng-if = "state.branchesLoaded" + ng-if = "CIS.instanceBranches.length" ) - //- show when loading paginated list - //- li.list-item.padding-md - .spinner-wrapper.spinner-sm.spinner-gray.spinner-center.in( + li.grid-block.align-center.list-item.popover-list-item( + ng-class = "{\ + 'disabled': $root.isLoading['buildingForkedBranch'] && !$root.isLoading[branch.attrs.name],\ + 'active': $root.isLoading['buildingForkedBranch'] && $root.isLoading[branch.attrs.name]\ + }" + ng-repeat = "branch in CIS.getFilteredBranches()" + ng-click = "CIS.forkBranchFromInstance(branch, POC.closePopover);" + ) + svg.grid-content.shrink.iconnables.icons-branch + use( + xlink:href = "#icons-branch-alt" + ) + .grid-content.text-overflow( + title = "{{ branch.attrs.name }}" + ) {{ branch.attrs.name }} + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add( + ng-if = "!$root.isLoading[branch.attrs.name]" + ) Add + .grid-content.shrink.spinner-wrapper.spinner-sm.spinner-green( + ng-if = "$root.isLoading[branch.attrs.name]" ng-include = "'spinner'" ) - li.list-item.popover-list-item SAN-4377-Cant-add-files - button.btn.btn-xs.btn-icon.btn-add - svg.iconnables.icons-add - use( - xlink:href = "#icons-add" - ) - li.list-item.popover-list-item SAN-4342-auto-isolation-setup - button.btn.btn-xs.btn-icon.btn-add - svg.iconnables.icons-add - use( - xlink:href = "#icons-add" - ) - li.list-item.grid-block.justify-justified.list-pagination( - ) - //- ng-if = "paginated" - button.btn.btn-xs.gray.grid-content Last 50 - button.btn.btn-xs.gray.grid-content Next 50 + + .text-center.text-gray.small.padding-lg( + ng-if = "CIS.instanceBranches && !CIS.instanceBranches.length && CIS.totalInstanceBranches" + ) There are no branches to add. + + .text-center.text-gray.small.padding-lg( + ng-if = "CIS.instanceBranches && !CIS.instanceBranches.length && !CIS.totalInstanceBranches" + ) The repository has no branches. + + .text-center.text-gray.small.padding-lg( + ng-if = "CIS.instanceBranches && CIS.instanceBranches.length && CIS.totalInstanceBranches && !CIS.getFilteredBranches().length" + ) No branches match this filter. diff --git a/client/directives/instances/instance/branchMenuPopover/includesModalView.jade b/client/directives/instances/instance/branchMenuPopover/includesModalView.jade index 9100d0fbc..dafc6ae3e 100644 --- a/client/directives/instances/instance/branchMenuPopover/includesModalView.jade +++ b/client/directives/instances/instance/branchMenuPopover/includesModalView.jade @@ -1,14 +1,14 @@ .modal-backdrop.in .modal-dialog.modal-sm.modal-includes header.modal-header - h1.modal-heading Select Containers + h1.modal-heading Select Templates svg.iconnables.icons-close use( xlink:href = "#icons-close" ) section.modal-body - .grid-block.vertical.shrink.text-center.well.gray.small.padding-sm Select containers from your sandbox to be included with each branch. Changes will apply to all branches of repository-name. + .grid-block.vertical.shrink.text-center.well.gray.small.padding-sm Selected templates will be used to create containers for each branch. Changes will apply to all branches of repository-name. .grid-block.vertical.shrink( ng-include = "'branchSetupListView'" ) diff --git a/client/directives/instances/instance/branchMenuPopover/introAddBranch.jade b/client/directives/instances/instance/branchMenuPopover/introAddBranch.jade new file mode 100644 index 000000000..746fc1b11 --- /dev/null +++ b/client/directives/instances/instance/branchMenuPopover/introAddBranch.jade @@ -0,0 +1,35 @@ +.popover.menu.right.popover-branch-menu( + ng-class = "{'in': active}" + ng-style = "popoverStyle.getStyle()" +) + .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( + ng-if = "$root.featureFlags.aha && !$root.isLoading.fetchingBranches && CIS.isAddingFirstBranch()" + aha-guide + step-index = 2 + sub-step = "addBranch" + ) + + .grid-block.shrink.align-center.justify-center.padding-sm.show-autofork( + ng-if = "$root.featureFlags.aha && !$root.isLoading.fetchingBranches && CIS.showAutofork" + ng-include = "'ahaGuideView'" + ng-init = "showAutofork = true" + ) + + .arrow.white + + animated-panel-container.popover-views( + ng-if = "!$root.featureFlags.autoIsolationSetup && $root.isLoading.fetchingBranches" + ) + animated-panel( + default = "true" + name = "addBranch" + ) + .popover-view.fade( + class = "in" + ) + .popover-content( + ) + .spinner-wrapper.spinner-sm.spinner-gray.spinner-center.in( + ng-if = "$root.isLoading.fetchingBranches" + ng-include = "'spinner'" + ) diff --git a/client/directives/instances/instance/branchMenuPopover/templateMenuPopoverView.jade b/client/directives/instances/instance/branchMenuPopover/templateMenuPopoverView.jade new file mode 100644 index 000000000..aed80d6ef --- /dev/null +++ b/client/directives/instances/instance/branchMenuPopover/templateMenuPopoverView.jade @@ -0,0 +1,200 @@ +.popover.menu.right.popover-template-menu( + ng-class = "{'in': active}" + ng-init = "\ + state.tab = 'repo';\ + state.loading = null;\ + " + ng-style = "popoverStyle.getStyle()" +) + .arrow.gray( + style = "top: 39px;" + ) + .grid-block.popover-header + button.grid-block.align-center.btn.btn-radio( + ng-class = "{'active': state.tab === 'repo'}" + ng-click = "state.tab = 'repo'" + ng-disabled = "state.tab !== 'repo'&& state.loading" + ) + .grid-block.vertical.shrink Repository + .small From your GitHub org + button.grid-block.align-center.btn.btn-radio( + ng-class = "{'active': state.tab === 'nonRepo'}" + ng-click = "state.tab = 'nonRepo'" + ng-disabled = "state.tab !== 'nonRepo'&& state.loading" + ) + .grid-block.vertical.shrink Non-Repository + .small For a DB or service + .popover-content( + ng-if = "state.tab === 'repo'" + ng-init = "state.branchesLoaded = null" + ) + .spinner-wrapper.spinner-sm.spinner-gray.spinner-center( + ng-click = "state.branchesLoaded = true" + ng-if = "!state.branchesLoaded" + ng-include = "'spinner'" + ) + .padding-xxs( + ng-if = "state.branchesLoaded" + ) + input.input.input-xs.input-search( + ng-disabled = "state.loading" + ng-if = "branch.length !== 0" + placeholder = "Filter by name" + required + type = "search" + ) + .text-center.text-gray.small.padding-lg( + ng-if = "branch.length === 0" + ) There are no branches to add. + ul.list.popover-list( + ng-if = "state.branchesLoaded" + ) + li.grid-block.align-center.list-item.popover-list-item.multi-line( + ng-class = "{'active': state.loading}" + ng-click = "state.loading = !state.loading" + ) + svg.grid-content.shrink.iconnables.icons-repository + use( + xlink:href = "#icons-repository" + ) + .grid-block.vertical + .grid-content.text-overflow( + title = "button-clicker" + ) button-clicker + .grid-block( + ng-if = "$root.featureFlags.inviteFlows" + ) + .btn-user.text-overflow.no-touching( + ng-class = "{'active': state.active}" + ng-include = "'userButtonView'" + ) + span.small( + ng-if = "$root.featureFlags.inviteFlows" + )  — 3 days ago + + //- else + small.small( + ng-if = "!$root.featureFlags.inviteFlows" + ) Updated 3 days ago + + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add( + ng-if = "!state.loading" + ) Select + .grid-content.shrink.spinner-wrapper.spinner-sm.spinner-green( + ng-if = "state.loading" + ng-include = "'spinner'" + ) + li.grid-block.align-center.list-item.popover-list-item.multi-line( + ng-class = "{'disabled': state.loading}" + ) + svg.grid-content.shrink.iconnables.icons-repository + use( + xlink:href = "#icons-repository" + ) + .grid-block.vertical + .grid-content.text-overflow( + title = "api" + ) api + .grid-block( + ng-if = "$root.featureFlags.inviteFlows" + ) + .btn-user.text-overflow.no-touching( + ng-class = "{'active': state.active}" + ng-include = "'userButtonView'" + ) + span.small( + ng-if = "$root.featureFlags.inviteFlows" + )  — 3 days ago + + //- else + small.small( + ng-if = "!$root.featureFlags.inviteFlows" + ) Updated 3 days ago + + button.grid-block.shrink.btn.btn-xs.btn-icon.btn-add Select + li.grid-block.align-center.list-item.popover-list-item.multi-line( + ng-class = "{'disabled': state.loading}" + ) + svg.grid-content.shrink.iconnables.icons-repository + use( + xlink:href = "#icons-repository" + ) + .grid-block.vertical + .grid-content.text-overflow( + title = "runnable.com" + ) runnable.com + .grid-block( + ng-if = "$root.featureFlags.inviteFlows" + ) + .btn-user.text-overflow.no-touching( + ng-class = "{'active': state.active}" + ng-include = "'userButtonView'" + ) + span.small( + ng-if = "$root.featureFlags.inviteFlows" + )  — 3 days ago + + //- else + small.small( + ng-if = "!$root.featureFlags.inviteFlows" + ) Updated 3 days ago + + button.grid-block.shrink.btn.btn-xs.btn-icon.btn-add Select + .popover-content( + ng-if = "state.tab === 'nonRepo'" + ng-init = "state.servicesLoaded = null" + ) + .spinner-wrapper.spinner-sm.spinner-gray.spinner-center( + ng-click = "state.servicesLoaded = true" + ng-if = "!state.servicesLoaded" + ng-include = "'spinner'" + ) + .padding-xxs( + ng-if = "state.servicesLoaded" + ) + input.input.input-xs.input-search( + ng-disabled = "state.loading" + placeholder = "Filter by name" + required + type = "search" + ) + ul.list.popover-list( + ng-if = "state.servicesLoaded" + ) + li.grid-block.align-center.list-item.popover-list-item.multi-line( + ng-class = "{'active': state.loading}" + ng-click = "state.loading = !state.loading" + ) + img.grid-content.shrink.img( + height = "24" + ng-src = "/build/images/logos/logo-icon-cassandra.png" + width = "24" + ) + .grid-content Cassandra + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add( + ng-if = "!state.loading" + ) Select + .grid-content.shrink.spinner-wrapper.spinner-sm.spinner-green( + ng-if = "state.loading" + ng-include = "'spinner'" + ) + li.grid-block.align-center.list-item.popover-list-item.multi-line( + ng-class = "{'disabled': state.loading}" + ) + img.grid-content.shrink.img( + height = "24" + ng-src = "/build/images/logos/logo-icon-consul-server.png" + width = "24" + ) + .grid-content Consul-Server + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add Select + li.grid-block.align-center.list-item.popover-list-item.multi-line( + ng-class = "{'disabled': state.loading}" + ) + img.grid-content.shrink.img( + height = "24" + ng-src = "/build/images/logos/logo-icon-elasticsearch.png" + width = "24" + ) + .grid-content ElasticSearch + button.grid-content.shrink.btn.btn-xs.btn-icon.btn-add Select diff --git a/client/directives/instances/instance/branchSetupModal/branchSetupListView.jade b/client/directives/instances/instance/branchSetupModal/branchSetupListView.jade index 472bc5869..259223ec6 100644 --- a/client/directives/instances/instance/branchSetupModal/branchSetupListView.jade +++ b/client/directives/instances/instance/branchSetupModal/branchSetupListView.jade @@ -2,7 +2,7 @@ .empty.well.gray.padding-md( ng-if = "containers.length === 0" ) - .small.empty.text-center.text-gray Your sandbox doesn’t have any other containers yet, but you can continue and add containers later. + .small.empty.text-center.text-gray You don’t have any other templates yet, but you can continue and add more templates later. .grid-block.vertical.shrink.list.list-bordered .list-item.padding-xs diff --git a/client/directives/instances/instance/branchSetupModal/branchSetupModalView.jade b/client/directives/instances/instance/branchSetupModal/branchSetupModalView.jade index 45e14f9f9..eddf8cdfa 100644 --- a/client/directives/instances/instance/branchSetupModal/branchSetupModalView.jade +++ b/client/directives/instances/instance/branchSetupModal/branchSetupModalView.jade @@ -45,13 +45,13 @@ .empty.well.gray.padding-md( ng-if = "branches.length === 0" ) - .small.empty.text-center.text-gray We couldn’t find any branches for this container, but you can continue and add branches later. + .small.empty.text-center.text-gray We couldn’t find any branches, but you can continue and add branches later. //- hide this on initial load, but show when paginating h4.grid-block.shrink.align-center.h4.text-gray.small.padding-xs .grid-content Select branches to add: input.grid-content.shrink.input.input-xs.input-search( - placeholder = "Filter" + placeholder = "Filter by name" required type = "search" ) @@ -106,7 +106,7 @@ ) h1.modal-heading.fade( ng-class = "{'in': isActivePanel()}" - ) Select Containers + ) Select Templates svg.iconnables.icons-close.fade( ng-class = "{'in': isActivePanel()}" ) @@ -120,7 +120,7 @@ //- do not show this if there are no containers .grid-block.vertical.shrink.text-center.well.gray.small.padding-sm( ng-if = "containers.length > 0" - ) Select containers from your sandbox to be included with each branch. + ) Selected templates will be used to create containers for each branch. .grid-block.vertical.shrink( ng-include = "'branchSetupListView'" diff --git a/client/directives/instances/instance/instanceHeaderDirective.js b/client/directives/instances/instance/instanceHeaderDirective.js index a0fa40773..b4535dec1 100644 --- a/client/directives/instances/instance/instanceHeaderDirective.js +++ b/client/directives/instances/instance/instanceHeaderDirective.js @@ -8,8 +8,10 @@ require('app') */ function instanceHeader( $localStorage, + $rootScope, $stateParams, - fetchPullRequest + fetchPullRequest, + ahaGuide ) { return { restrict: 'A', @@ -32,6 +34,10 @@ function instanceHeader( } }); }); + $scope.toggleSidebar = function () { + $rootScope.$broadcast('showAhaSidebar'); + }; + $scope.isInGuide = ahaGuide.isInGuide; } }; } diff --git a/client/directives/instances/instance/instanceHeaderView.jade b/client/directives/instances/instance/instanceHeaderView.jade index ac64b11b8..0bbd0228c 100644 --- a/client/directives/instances/instance/instanceHeaderView.jade +++ b/client/directives/instances/instance/instanceHeaderView.jade @@ -47,10 +47,10 @@ ) {{key}}:{{value[0].HostPort}} | button.grid-block.shrink.btn.btn-sm.gray.btn-aha( - ng-click = "$root.featureFlags.ahaSidebar = true" - ng-if = "$root.featureFlags.aha && !$root.featureFlags.ahaSidebar" + ng-click = "toggleSidebar()" + ng-if = "$root.featureFlags.aha && isInGuide()" tooltip = "Setup Guide" - tooltip-options = "{\"class\":\"left\",\"right\":42,\"top\":0}" + tooltip-options = "{\"class\":\"left\",\"right\":44,\"top\":0}" ) svg.iconnables use( @@ -84,3 +84,24 @@ open-items = "openItems" save-open-items-button ) + + .popover.bottom.in.popover-aha.popover-aha-url.in( + ng-if = "$root.featureFlags.aha && $root.featureFlags.ahaBranchUrlStep" + ) + .arrow.white( + style = "left: auto; right: 59px;" + ) + .popover-content + .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide + .grid-block.shrink.aha-meter.js-animate.aha-meter-100 + svg.iconnables + use( + xlink:href = "#icons-check" + ) + .grid-block.vertical.aha-text + p.p.small.text-gray-light Step 3: Add a Branch + p.p Nice work! Now you can see the web output of your branch by visiting its URL. + .grid-block.justify-right.popover-footer + button.grid-block.shrink.btn.btn-sm.green( + ng-click = "EC.showAddServicePopover = false" + ) Got It diff --git a/client/directives/modals/directiveModal.js b/client/directives/modals/directiveModal.js index 9ca2a5dd3..1e46ba86d 100755 --- a/client/directives/modals/directiveModal.js +++ b/client/directives/modals/directiveModal.js @@ -9,7 +9,8 @@ function modal() { return { restrict: 'A', scope: { - data: '=?modalData', // Contains modal specific data + controller: '=?modalController', // Contains modal specific data + controllerAs: '@?modalControllerAs', // the property name used to access controller actions: '=?modalActions', // Contains modal specific actions template: '@modalTemplate', currentModel: '=?modalCurrentModel', // The object that contains the data to display @@ -19,8 +20,9 @@ function modal() { link: function ($scope, element, attrs) { function openModal() { $scope.$emit('open-modal', { - data: $scope.data, actions: $scope.actions, + controller: $scope.controller, + controllerAs: $scope.controllerAs, template: $scope.template, currentModel: $scope.currentModel, stateModel: $scope.stateModel diff --git a/client/directives/modals/directiveModalManager.js b/client/directives/modals/directiveModalManager.js index 54f7d1ecc..4b1823a30 100644 --- a/client/directives/modals/directiveModalManager.js +++ b/client/directives/modals/directiveModalManager.js @@ -58,7 +58,7 @@ function modalManager( } $scope.currentModalScope = currentModalScope = $scope.$new(true); - currentModalScope.data = options.data; + currentModalScope[options.controllerAs] = options.controller; currentModalScope.actions = options.actions; currentModalScope.template = options.template; currentModalScope.openFlag = options.openFlag; diff --git a/client/directives/modals/gracePeriodModal/forms/pausedSandboxView.jade b/client/directives/modals/gracePeriodModal/forms/pausedSandboxView.jade index dcd894b9f..48b30dd8b 100644 --- a/client/directives/modals/gracePeriodModal/forms/pausedSandboxView.jade +++ b/client/directives/modals/gracePeriodModal/forms/pausedSandboxView.jade @@ -12,13 +12,13 @@ span( title = "paws’d" ) paused - | your containers due to inactivity. Give us the go ahead and we’ll get you back up and running. + | your environments due to inactivity. Give us the go ahead and we’ll get you back up and running. footer.grid-block.vertical.modal-footer button.grid-content.btn.btn-md.green.icons-intercom( ng-click = "WBC.openIntercom()" ) Chat Now a.grid-content.link.text-gray.text-center( - href = "mailto:support@runnable.com?subject=I’m back! Please get my sandbox back up and running." + href = "mailto:support@runnable.com?subject=I’m back! Please get my environments back up and running." ) Email Support .grid-block.justify-center.modal-outer-footer( grace-period-footer diff --git a/client/directives/modals/gracePeriodModal/forms/paymentDueView.jade b/client/directives/modals/gracePeriodModal/forms/paymentDueView.jade index 670588c9e..e45cf9f39 100644 --- a/client/directives/modals/gracePeriodModal/forms/paymentDueView.jade +++ b/client/directives/modals/gracePeriodModal/forms/paymentDueView.jade @@ -15,7 +15,7 @@ ng-if = "EAC.activeAccount.isInGrace()" ) strong.strong Note: - | Your containers will be paused in {{EAC.activeAccount.graceHoursRemaining()}} hours if we cannot process a payment method. + | Your environments will be paused in {{EAC.activeAccount.graceHoursRemaining()}} hours if we cannot process a payment method. hr.hr .grid-block.vertical.form-payment.fade( ng-class = "{'in': isActivePanel()}" diff --git a/client/directives/modals/gracePeriodModal/forms/trialEndView.jade b/client/directives/modals/gracePeriodModal/forms/trialEndView.jade index 2172c89a1..9d19b91ac 100644 --- a/client/directives/modals/gracePeriodModal/forms/trialEndView.jade +++ b/client/directives/modals/gracePeriodModal/forms/trialEndView.jade @@ -17,7 +17,7 @@ | . small.grid-content.small.text-center.text-gray strong.strong Note: - | Your containers will be paused in {{EAC.activeAccount.graceHoursRemaining()}} hours if we cannot process a payment method. + | Your environments will be paused in {{EAC.activeAccount.graceHoursRemaining()}} hours if we cannot process a payment method. hr.hr .grid-block.vertical.form-payment.fade( ng-class = "{'in': isActivePanel()}" diff --git a/client/directives/modals/inviteAdminModal/inviteAdminModalController.js b/client/directives/modals/inviteAdminModal/inviteAdminModalController.js index ad5538bd1..d6c609011 100644 --- a/client/directives/modals/inviteAdminModal/inviteAdminModalController.js +++ b/client/directives/modals/inviteAdminModal/inviteAdminModalController.js @@ -2,7 +2,7 @@ require('app') .controller('InviteAdminModalController', InviteAdminModalController); -var DEFAULT_MESSAGE = 'Join my Sandbox on Runnable, where we can run the code in CodeNow\'s repositories on demand.\n\nI need your admin permissions to enable some features. Thanks!'; +var DEFAULT_MESSAGE = 'Join me on Runnable, where we can run the code in CodeNow\'s repositories on demand.\n\nI need your admin permissions to enable some features. Thanks!'; function InviteAdminModalController( $state, diff --git a/client/directives/modals/modalChooseOrganization/chooseOrganizationModalController.js b/client/directives/modals/modalChooseOrganization/chooseOrganizationModalController.js index 235beb7c2..fabc0b918 100644 --- a/client/directives/modals/modalChooseOrganization/chooseOrganizationModalController.js +++ b/client/directives/modals/modalChooseOrganization/chooseOrganizationModalController.js @@ -7,8 +7,10 @@ function ChooseOrganizationModalController( $rootScope, $scope, $state, + ahaGuide, createNewSandboxForUserService, errs, + eventTracking, featureFlags, fetchWhitelistForDockCreated, keypather, @@ -37,6 +39,7 @@ function ChooseOrganizationModalController( }; $scope.actions = { + selectedOrg: eventTracking.selectedOrg.bind(eventTracking), selectAccount: function (selectedOrgName) { $state.go('base.instances', { userName: selectedOrgName @@ -84,6 +87,7 @@ function ChooseOrganizationModalController( return selectedOrgName.toLowerCase() === org.oauthName().toLowerCase(); }); }; + COMC.isChoosingOrg = ahaGuide.isChoosingOrg; // Polling stuff COMC.cancelPolling = function () { diff --git a/client/directives/modals/modalChooseOrganization/chooseOrganizationModalView.jade b/client/directives/modals/modalChooseOrganization/chooseOrganizationModalView.jade index e828f4709..668cb157d 100644 --- a/client/directives/modals/modalChooseOrganization/chooseOrganizationModalView.jade +++ b/client/directives/modals/modalChooseOrganization/chooseOrganizationModalView.jade @@ -8,18 +8,14 @@ ) .grid-block.shrink.modal-dialog.modal-orgs( ng-class = "{\ - 'modal-lg': data.allAccounts.length > 1,\ - 'modal-sm': data.allAccounts.length <= 1\ + 'modal-lg': COS.allAccounts.length > 1,\ + 'modal-sm': COS.allAccounts.length <= 1\ }" ) .grid-block.shrink.align-center.justify-center.padding-md.aha-guide( - ng-if = "$root.featureFlags.aha" - ng-include = "'ahaGuideView'" - ng-init = "\ - state.showStep = 0;\ - state.showSubStep = 0;\ - state.hideMenu = true;\ - " + aha-guide + ng-if = "$root.featureFlags.aha && COS.isChoosingOrg()" + sub-step = "orgSelection" ) animated-panel-container animated-panel.modal-body.grid-block.vertical.padding-sm( @@ -30,7 +26,7 @@ p.p.padding-xs.text-center.fade( ng-class = "{'in': isActivePanel()}" ng-if = "!$root.featureFlags.aha" - ) Choose an organization for your Sandbox. If you don’t see your organization, check if you’ve granted access + ) Select the organization you want to use with Runnable. If you don’t see your organization, check if you’ve granted access a.link( href = "https://github.com/settings/connections/applications/d42d6634d4070c9d9bf9" target = "_blank" @@ -39,7 +35,7 @@ .grid-block.shrink.justify-center.align-center.padding-xs.well.gray.aha-tips.fade( ng-class = "{'in': isActivePanel()}" - ng-if = "$root.featureFlags.aha && data.allAccounts.length" + ng-if = "$root.featureFlags.aha && COS.allAccounts.length" ) svg.grid-content.shrink.iconnables use( @@ -54,7 +50,7 @@ //- empty state .empty.well.gray.padding-lg( - ng-if = "!data.allAccounts.length" + ng-if = "!COS.allAccounts.length" ) h3.h3.empty.text-gray.text-center We couldn’t find any organizations. small.small.empty.text-center Check if you’ve granted access to your organization @@ -65,15 +61,15 @@ | . If you don’t have permission to grant access, you’ll have to ask your admin to give the go ahead. form.grid-block.list.fade( - name = "data.orgSelectorForm" + name = "COS.orgSelectorForm" ng-class = "{'in': isActivePanel()}" - ng-if = "data.allAccounts.length" + ng-if = "COS.allAccounts.length" ) //- add .active class if this org is selected //- add [disabled] property when loading NOT class! label.grid-block.align-center.list-item.btn.white( - ng-class = "{'active': org.oauthName() === data.selectedOrgName}" - ng-repeat = "org in data.allAccounts" + ng-class = "{'active': org.oauthName() === COS.selectedOrgName}" + ng-repeat = "org in COS.allAccounts" title = "{{org.oauthName()}}" ) img.grid-block.shrink( @@ -83,7 +79,8 @@ ) span.grid-block.text-left.text-overflow {{org.oauthName()}} input.checkbox.hidden( - ng-model = "data.selectedOrgName" + ng-change = "actions.selectedOrg(org.oauthName())" + ng-model = "COS.selectedOrgName" value = "{{org.oauthName()}}" type = "radio" ) @@ -96,16 +93,14 @@ //- hide the footer if no orgs are found footer.grid-block.vertical.modal-footer.fade( ng-class = "{'in': isActivePanel()}" - ng-if = "data.allAccounts.length" + ng-if = "COS.allAccounts.length" ) //- disabled until an org is selected - button.grid-block.align-center.justify-center.btn.btn-md.green( - ng-click = "\ - actions.createOrCheckDock(data.selectedOrgName, goToPanel);\ - state.showSubStep = 1;\ - " - ng-disabled = "!data.selectedOrgName" - ) Create Sandbox + button.btn.btn-md.green( + data-event-name = "Org Confirmed" + ng-click = "actions.createOrCheckDock(COS.selectedOrgName, goToPanel)" + ng-disabled = "!COS.selectedOrgName" + ) Confirm Organization animated-panel.modal-body.grid-block.vertical( name = "dockLoading" @@ -114,14 +109,27 @@ ng-class = "{'in': isActivePanel()}" ) //- dock spinning up - p.p.text-gray.padding-xs.text-center We’re spinning up a sandbox for your org. It’ll take about + p.p.text-gray.padding-xs.text-center( + ng-if = "!$root.featureFlags.contingencyPlan" + ) We’re spinning up infrastructure for your org. It’ll take about strong.strong 10 minutes | . We’ll email you and update this page when it’s ready. - .grid-block.align-center.justify-center.padding-sm + .grid-block.align-center.justify-center.padding-sm( + ng-if = "!$root.featureFlags.contingencyPlan" + ) .spinner-wrapper.spinner-md.spinner-gray( ng-include = "'spinner'" ) //- ng-if = "isActivePanel()" + + p.p.text-gray.text-center.strong( + ng-if = "$root.featureFlags.contingencyPlan" + ) We’re running slower than normal due to high demand. + p.p.text-gray.text-center.padding-xs( + ng-if = "$root.featureFlags.contingencyPlan" + ) We’ll email you at + span {{ COS.user.attrs.email }} + | as soon we’re done spinning up your infrastructure. Thanks for your patience. animated-panel.modal-body.grid-block.vertical( name = "dockLoaded" ) @@ -129,15 +137,16 @@ ng-class = "{'in': isActivePanel()}" ) //- dock spun up - p.p.text-gray.padding-xs.text-center Your sandbox is ready for you now. + p.p.text-gray.padding-xs.text-center Thanks for waiting, we‘re ready for you now. //- show this button when done waiting footer.grid-block.vertical.modal-footer.fade( ng-class = "{'in': isActivePanel()}" ) - button.grid-block.align-center.justify-center.btn.btn-md.green( - ng-click = "actions.selectAccount(data.selectedOrgName)" - ) Go to your Sandbox! + button.btn.btn-md.green( + data-event-name = "Nav to Runnable (after infrastructure wait)" + ng-click = "actions.selectAccount(COS.selectedOrgName)" + ) Go to Runnable .grid-block.justify-center.modal-outer-footer( grace-period-footer hide-choose-org = "true" diff --git a/client/directives/modals/modalDeleteSandbox/viewModalDeleteSandbox.jade b/client/directives/modals/modalDeleteSandbox/viewModalDeleteSandbox.jade deleted file mode 100644 index 58589d2e0..000000000 --- a/client/directives/modals/modalDeleteSandbox/viewModalDeleteSandbox.jade +++ /dev/null @@ -1,8 +0,0 @@ -.modal-dialog.modal-sm.modal-alert - section.modal-body - p.p.strong Are you sure you want to delete your private Sandbox? - footer.modal-footer.clearfix - button.btn.btn-sm.gray.float-left( - ng-click = "defaultActions.close()" - ) Cancel - button.btn.btn-sm.red.float-right Delete Private Sandbox \ No newline at end of file diff --git a/client/directives/modals/modalGeneric/viewModalDeleteBox.jade b/client/directives/modals/modalGeneric/viewModalDeleteBox.jade index c172213b1..ea169cddb 100755 --- a/client/directives/modals/modalGeneric/viewModalDeleteBox.jade +++ b/client/directives/modals/modalGeneric/viewModalDeleteBox.jade @@ -2,7 +2,7 @@ ng-click = "$event.stopPropagation()" ) header.modal-header - h1.modal-heading Delete Container + h1.modal-heading Delete Template svg.iconnables.icons-close( ng-click = "defaultActions.cancel()" ) @@ -22,4 +22,4 @@ actions.deleteInstance(data.deleteLinked);\ defaultActions.cancel();\ " - ) Delete Container \ No newline at end of file + ) Delete Template diff --git a/client/directives/modals/modalNewContainer/newContainerModalController.js b/client/directives/modals/modalNewContainer/newContainerModalController.js index 221cd81e5..9df7bf714 100644 --- a/client/directives/modals/modalNewContainer/newContainerModalController.js +++ b/client/directives/modals/modalNewContainer/newContainerModalController.js @@ -6,6 +6,7 @@ require('app') function NewContainerModalController( $q, $timeout, + ahaGuide, createNewBuildAndFetchBranch, createNonRepoInstance, errs, @@ -14,73 +15,71 @@ function NewContainerModalController( fetchOwnerRepos, fetchRepoDockerfiles, getNewForkName, - helpCards, keypather, loading, ModalService, close, currentOrg ) { - var NCMC = this; - var helpCard = helpCards.getActiveCard(); - angular.extend(NCMC, { + var MC = this; + angular.extend(MC, { name: 'newContainerModal', - servicesActive: keypather.get(helpCard, 'id') === 'missingDependency', state: { tabName: 'repos', dockerfile: null, configurationMethod: null, namesForAllInstances: [] - } + }, + ahaGuide: ahaGuide }); // Start loading repos and templates - loading.reset(NCMC.name + 'Repos'); - loading.reset(NCMC.name + 'Templates'); - loading.reset(NCMC.name + 'SingleRepo'); + loading.reset(MC.name + 'Repos'); + loading.reset(MC.name + 'Templates'); + loading.reset(MC.name + 'SingleRepo'); // Fetch all repos from Github - loading(NCMC.name + 'Repos', true); + loading(MC.name + 'Repos', true); $q.all({ instances: fetchInstancesByPod(), repoList: fetchOwnerRepos(currentOrg.github.oauthName()) }) .then(function (data) { - NCMC.instances = data.instances; - NCMC.state.namesForAllInstances = NCMC.instances.map(function (instance) { + MC.instances = data.instances; + MC.state.namesForAllInstances = MC.instances.map(function (instance) { return instance.attrs.name; }); - NCMC.githubRepos = data.repoList; - NCMC.githubRepos.models.forEach(function (repo) { - repo.isAdded = NCMC.isRepoAdded(repo, data.instances); + MC.githubRepos = data.repoList; + MC.githubRepos.models.forEach(function (repo) { + repo.isAdded = MC.isRepoAdded(repo, data.instances); }); }) .catch(errs.handler) .finally(function () { - loading(NCMC.name + 'Repos', false); + loading(MC.name + 'Repos', false); }); - NCMC.fetchTemplateServers = function () { - loading(NCMC.name + 'Templates', true); + MC.fetchTemplateServers = function () { + loading(MC.name + 'Templates', true); // Fetch all non-repo containres return fetchInstances({ githubUsername: 'HelloRunnable' }) .then(function (servers) { - NCMC.templateServers = servers; - loading(NCMC.name + 'Templates', false); + MC.templateServers = servers; + loading(MC.name + 'Templates', false); return servers; }); }; - NCMC.changeTab = function (tabName) { + MC.changeTab = function (tabName) { if (!['repos', 'services'].includes(tabName)) { return; } - NCMC.state.tabName = tabName; + MC.state.tabName = tabName; // Reset repo and template - NCMC.state.templateSource = null; - NCMC.state.repo = null; - if (NCMC.state.tabName === 'services' && !NCMC.templateServers) { - NCMC.fetchTemplateServers(); + MC.state.templateSource = null; + MC.state.repo = null; + if (MC.state.tabName === 'services' && !MC.templateServers) { + MC.fetchTemplateServers(); } }; @@ -88,7 +87,7 @@ function NewContainerModalController( return repo.attrs.name.replace(/[^a-zA-Z0-9-]/g, '-'); } - NCMC.isRepoAdded = function (repo, instances) { + MC.isRepoAdded = function (repo, instances) { // Since the newServers may have faked repos (just containing names), just check the name return !!instances.find(function (instance) { var repoName = instance.getRepoName(); @@ -100,21 +99,21 @@ function NewContainerModalController( }); }; - NCMC.close = function () { - if (NCMC.state.closed) { return; } - NCMC.state.closed = true; + MC.close = function () { + if (MC.state.closed) { return; } + MC.state.closed = true; return close(); }; - NCMC.setTemplate = function (sourceInstance, goToPanelCb) { - NCMC.state.templateSource = sourceInstance; + MC.setTemplate = function (sourceInstance, goToPanelCb) { + MC.state.templateSource = sourceInstance; var instanceToForkName = sourceInstance.attrs.name; - loading(NCMC.name + 'SingleRepo', true); + loading(MC.name + 'SingleRepo', true); return fetchInstances() .then(function (instances) { - loading(NCMC.name + 'SingleRepo', false); + loading(MC.name + 'SingleRepo', false); var serverName = getNewForkName(instanceToForkName, instances, true); - NCMC.state.instanceName = serverName; + MC.state.instanceName = serverName; /** * Warning: Hack Ahead * @@ -132,9 +131,9 @@ function NewContainerModalController( .catch(errs.handler); }; - NCMC.addServerFromTemplate = function (sourceInstance) { + MC.addServerFromTemplate = function (sourceInstance) { var instanceToForkName = sourceInstance.attrs.name; - NCMC.close(); + MC.close(); return fetchInstances() .then(function (instances) { var serverName = getNewForkName(instanceToForkName, instances, true); @@ -153,61 +152,68 @@ function NewContainerModalController( .catch(errs.handler); }; - NCMC.setRepo = function (repo, goToPanelCb) { - if (repo.attrs.full_name === keypather.get(NCMC, 'state.repo.attrs.full_name')) { + MC.setRepo = function (repo, goToPanelCb) { + if (repo.attrs.full_name === keypather.get(MC, 'state.repo.attrs.full_name')) { return goToPanelCb('dockerfileMirroring'); } repo.loading = true; - NCMC.state.repo = repo; - loading(NCMC.name + 'SingleRepo', true); + MC.state.repo = repo; + loading(MC.name + 'SingleRepo', true); var fullName = keypather.get(repo, 'attrs.full_name'); var defaultBranch = keypather.get(repo, 'attrs.default_branch'); - NCMC.state.configurationMethod = null; - NCMC.state.instanceName = fullName.split('/')[1] || ''; - NCMC.state.instanceName = NCMC.state.instanceName.replace(/_/g, '-'); + MC.state.configurationMethod = null; + MC.state.instanceName = fullName.split('/')[1] || ''; + MC.state.instanceName = MC.state.instanceName.replace(/_/g, '-'); return fetchRepoDockerfiles(fullName, defaultBranch) .then(function (dockerfiles) { - if (dockerfiles.length === 0) { - NCMC.state.configurationMethod = 'new'; - } - loading(NCMC.name + 'SingleRepo', false); + loading(MC.name + 'SingleRepo', false); repo.loading = false; repo.dockerfiles = dockerfiles; - NCMC.state.dockerfile = null; + MC.state.dockerfile = null; return goToPanelCb('dockerfileMirroring'); }); }; - NCMC.createBuildAndGoToNewRepoModal = function (instanceName, repo, dockerfile, configurationMethod) { - loading(NCMC.name + 'SingleRepo', true); - return createNewBuildAndFetchBranch(currentOrg.github, repo, keypather.get(dockerfile, 'path')) + MC.createBuildAndGoToNewRepoModal = function (instanceName, repo, dockerfile, configurationMethod) { + var dockerfilePath; + loading(MC.name + 'SingleRepo', true); + + if (configurationMethod === 'dockerfile') { + dockerfilePath = keypather.get(dockerfile, 'path'); + } else { + dockerfilePath = ''; + } + return createNewBuildAndFetchBranch(currentOrg.github, repo, dockerfilePath, configurationMethod) .then(function (repoBuildAndBranch) { repoBuildAndBranch.instanceName = instanceName; if (configurationMethod === 'dockerfile' && dockerfile) { - NCMC.newMirrorRepositoryContainer(repoBuildAndBranch); + MC.newMirrorRepositoryContainer(repoBuildAndBranch); + } else if (configurationMethod === 'blankDockerfile'){ + MC.newRepositoryContainer(repoBuildAndBranch, configurationMethod); } else { - NCMC.newRepositoryContainer(repoBuildAndBranch); + MC.newRepositoryContainer(repoBuildAndBranch, false); } }) .finally(function () { - loading(NCMC.name + 'SingleRepo', false); + loading(MC.name + 'SingleRepo', false); }); }; - NCMC.createBuildFromTemplate = function (instanceName, sourceInstance) { - NCMC.close(); + MC.createBuildFromTemplate = function (instanceName, sourceInstance) { + MC.close(); return createNonRepoInstance(instanceName, sourceInstance) .catch(errs.handler); }; - NCMC.newRepositoryContainer = function (inputs) { - if (NCMC.state.closed) { return; } - NCMC.close(); + MC.newRepositoryContainer = function (inputs, configurationMethod) { + if (MC.state.closed) { return; } + MC.close(); ModalService.showModal({ controller: 'SetupServerModalController', controllerAs: 'SMC', templateUrl: 'setupServerModalView', inputs: angular.extend({ + dockerfileType: configurationMethod, instanceName: null, repo: null, build: null, @@ -216,9 +222,9 @@ function NewContainerModalController( }); }; - NCMC.newMirrorRepositoryContainer = function (inputs) { - if (NCMC.state.closed) { return; } - NCMC.close(); + MC.newMirrorRepositoryContainer = function (inputs) { + if (MC.state.closed) { return; } + MC.close(); ModalService.showModal({ controller: 'SetupMirrorServerModalController', controllerAs: 'SMC', diff --git a/client/directives/modals/modalNewContainer/newContainerModalView.jade b/client/directives/modals/modalNewContainer/newContainerModalView.jade index 0b6471925..25c91e622 100644 --- a/client/directives/modals/modalNewContainer/newContainerModalView.jade +++ b/client/directives/modals/modalNewContainer/newContainerModalView.jade @@ -1,11 +1,10 @@ .modal-backdrop.in .grid-block.shrink.align-center.justify-center.padding-sm.aha-guide( - ng-if = "$root.featureFlags.aha1" - ng-include = "'ahaGuideView'" - ng-init = "\ - state.showStep = 1;\ - state.showSubStep = 1;\ - " + aha-guide + ng-class = "{'p-slide js-animate': AGC.state.subStepIndex > 0}" + ng-if = "MC.ahaGuide.isAddingFirstRepo()" + sub-step = "containerSelection" + sub-step-index = 1 ) .modal-dialog.modal-lg animated-panel-container.modal-servers @@ -14,36 +13,37 @@ name = "containerSelection" ) header.modal-header.grid-block.justify-center( - ng-class = "{'lg': !$root.featureFlags.aha1}" + ng-class = "{'lg': !MC.ahaGuide.isAddingFirstRepo() || ( MC.instances.models && MC.instances.models.length !== 0 )}" ng-click = "MC.changeTab('nameContainer')" ) - .grid-block.shrink.btn( + .grid-block.align-center.shrink.btn.btn-radio( ng-class = "{'active': MC.state.tabName === 'repos'}" ng-click = "MC.changeTab('repos')" - ng-if = "!$root.featureFlags.aha1" + ng-if = "!MC.ahaGuide.isAddingFirstRepo() || ( MC.instances.models && MC.instances.models.length !== 0 )" ) svg.iconnables.icons-repository.grid-block.shrink use( xlink:href = "#icons-new-repository-server" ) - .btn-text.grid-block Create from a repository + .btn-text.grid-block.vertical.text-left Repository Template + small.small From your GitHub org + //- disable this button when loading a repository - .grid-block.shrink.btn( + .grid-block.align-center.shrink.btn.btn-radio( ng-disabled = "$root.isLoading[MC.name + 'SingleRepo']" ng-class = "{\ 'active': MC.state.tabName === 'services', \ - 'btn-hint': MC.servicesActive, \ 'disabled': $root.isLoading[MC.name + 'SingleRepo'] \ }" ng-click = "!$root.isLoading[MC.name + 'SingleRepo'] && MC.changeTab('services')" - ng-if = "!$root.featureFlags.aha1" + ng-if = "!MC.ahaGuide.isAddingFirstRepo() || ( MC.instances.models && MC.instances.models.length !== 0 )" ) svg.iconnables.icons-template.grid-block.shrink use( xlink:href = "#icons-server-new" ) - .btn-text.grid-block.vertical.text-left Create from a template - small.small For services and databases + .btn-text.grid-block.vertical.text-left Non-Repository Template + small.small For a database or service svg.iconnables.icons-close( ng-click = "MC.close()" ) @@ -73,8 +73,8 @@ xlink:href = "#icons-arrow-down" ) h1.modal-heading( - ng-if = "!$root.featureFlags.aha1" - ) Configuration Method + ng-if = "!MC.ahaGuide.isAddingFirstRepo()" + ) Setup Method svg.iconnables.icons-close( ng-click = "MC.close()" ) @@ -102,7 +102,7 @@ ng-include = "'spinner'" ng-if = "$root.isLoading[MC.name + 'SingleRepo']" ) - span() Next Step: Naming + span Next Step: Naming animated-panel.grid-block.vertical.modals-rename( name = "nameContainer" @@ -110,14 +110,15 @@ ) header.modal-header svg.iconnables.icons-arrow-backward( + ng-class = "{'disabled': $root.isLoading[MC.name + 'SingleRepo']}" ng-click = "!$root.isLoading[MC.name + 'SingleRepo'] && goToPanel(MC.state.templateSource ? 'containerSelection' : 'dockerfileMirroring', 'back')" ) use( xlink:href = "#icons-arrow-down" ) h1.modal-heading.text-overflow( - ng-if = "!$root.featureFlags.aha1" - ) Container Name + ng-if = "!MC.ahaGuide.isAddingFirstRepo()" + ) Template Name svg.iconnables.icons-close( ng-click = "MC.close()" ) @@ -155,7 +156,7 @@ ul.list.list-bulleted.list-validation li.list-item( ng-class = "{'ng-invalid': MC.nameForm.$error.unique}" - ) Choose a unique name in your Sandbox + ) Choose a unique name for your template li.list-item( ng-class = "{'ng-invalid': MC.nameForm.$error.pattern || MC.nameForm.$error.noDoubleDash}" ) Use letters, numbers, and single hyphens (-) @@ -174,15 +175,15 @@ MC.createBuildFromTemplate(MC.state.instanceName, MC.state.templateSource) \ " ) - //- should say "Next Step: Configure" if a repository container - //- should say "Create Container" if its a non-repository container - .spinner-wrapper.spinner-white.spinner-sm.in( - ng-include = "'spinner'" + //- should say "Next Step: Setup" if a repository container + //- should say "Create Template" if its a non-repository container + .spinner-wrapper.spinner-white.spinner-sm.float-left( ng-if = "$root.isLoading[MC.name + 'SingleRepo']" + ng-include = "'spinner'" ) span( ng-if = "MC.state.repo" - ) Next Step: Configure + ) Next Step: Setup span( ng-if = "MC.state.templateSource" - ) Create Container + ) Create Template diff --git a/client/directives/modals/modalNewContainer/tooltips/viewCountainerTooltip.jade b/client/directives/modals/modalNewContainer/tooltips/viewCountainerTooltip.jade index 1d91a3e5b..7edc089ce 100644 --- a/client/directives/modals/modalNewContainer/tooltips/viewCountainerTooltip.jade +++ b/client/directives/modals/modalNewContainer/tooltips/viewCountainerTooltip.jade @@ -5,4 +5,4 @@ .arrow .small.text-center This repo already exists br - | in your sandbox + | in your environment diff --git a/client/directives/modals/modalRepoDetail/repositoryDetailsModalController.js b/client/directives/modals/modalRepoDetail/repositoryDetailsModalController.js index 14ffd4100..8207441a2 100644 --- a/client/directives/modals/modalRepoDetail/repositoryDetailsModalController.js +++ b/client/directives/modals/modalRepoDetail/repositoryDetailsModalController.js @@ -49,12 +49,22 @@ function RepositoryDetailsModalController( }); }; + RDMC.hasCommitBeenUpdated = function () { + var newCommitSha = keypather.get(RDMC, 'data.commit.attrs.sha'); + var oldCommitSha = keypather.get(RDMC, 'appCodeVersion.attrs.commit'); + return newCommitSha && newCommitSha !== oldCommitSha; + }; + + RDMC.hasLockedBeenUpdated = function () { + return RDMC.data.locked !== RDMC.instance.attrs.locked; + }; + RDMC.updateInstance = function () { var updateInstance = function () { return $q.when() .then(function () { loading('main', true); - if (RDMC.data.locked === RDMC.instance.attrs.locked) { + if (!RDMC.hasLockedBeenUpdated()) { return; } return promisify(instance, 'update')({ @@ -62,7 +72,9 @@ function RepositoryDetailsModalController( }); }) .then(function () { - return updateInstanceWithNewAcvData(RDMC.instance, RDMC.appCodeVersion, RDMC.data); + if (RDMC.hasCommitBeenUpdated()) { + return updateInstanceWithNewAcvData(RDMC.instance, RDMC.appCodeVersion, RDMC.data); + } }) .finally(function () { loading('main', false); diff --git a/client/directives/modals/modalRepoDetail/repositoryDetailsModalView.jade b/client/directives/modals/modalRepoDetail/repositoryDetailsModalView.jade index 12f779c7b..b367d7cbc 100644 --- a/client/directives/modals/modalRepoDetail/repositoryDetailsModalView.jade +++ b/client/directives/modals/modalRepoDetail/repositoryDetailsModalView.jade @@ -28,4 +28,5 @@ ) Cancel button.btn.btn-md.green.float-right( ng-click = "RDMC.updateInstance()" - ) Save & Rebuild + ng-disabled = "!RDMC.hasCommitBeenUpdated() && !RDMC.hasLockedBeenUpdated()" + ) {{ RDMC.hasCommitBeenUpdated() ? 'Save and Build' : 'Save' }} diff --git a/client/directives/modals/settingsModal/forms/billingForm/billingHistoryForm/billingHistoryForm.jade b/client/directives/modals/settingsModal/forms/billingForm/billingHistoryForm/billingHistoryForm.jade index c410c466c..e14ee270c 100644 --- a/client/directives/modals/settingsModal/forms/billingForm/billingHistoryForm/billingHistoryForm.jade +++ b/client/directives/modals/settingsModal/forms/billingForm/billingHistoryForm/billingHistoryForm.jade @@ -10,9 +10,9 @@ .cell.text-overflow.small Paid by {{invoice.paidBy.githubUser.name || invoice.paidBy.githubUser.login}} .cell.monospace.text-right {{getBillingDate(invoice)}} -div( +.well.gray.ignore-margin.text-center.padding-sm.small( ng-if = "invoices.length === 0" -) You have no billing history right now. +) You have no billing history. //- if there's something older to load //-.label diff --git a/client/directives/modals/settingsModal/forms/billingForm/changePaymentForm/changePaymentForm.jade b/client/directives/modals/settingsModal/forms/billingForm/changePaymentForm/changePaymentForm.jade index 92ba4da14..6451dcc69 100644 --- a/client/directives/modals/settingsModal/forms/billingForm/changePaymentForm/changePaymentForm.jade +++ b/client/directives/modals/settingsModal/forms/billingForm/changePaymentForm/changePaymentForm.jade @@ -104,7 +104,7 @@ form.padding-md( ng-disabled="$root.isLoading.savePayment" ) - .grid-block.well.padding-xxs.small.justify-center.text-center( + .grid-content.well.padding-xxs.small.justify-center.text-center( payment-summary show-next = 'false' ) diff --git a/client/directives/modals/settingsModal/forms/billingForm/components/paymentSummary/paymentSummaryView.jade b/client/directives/modals/settingsModal/forms/billingForm/components/paymentSummary/paymentSummaryView.jade index 157b50903..d59d046db 100644 --- a/client/directives/modals/settingsModal/forms/billingForm/components/paymentSummary/paymentSummaryView.jade +++ b/client/directives/modals/settingsModal/forms/billingForm/components/paymentSummary/paymentSummaryView.jade @@ -4,6 +4,7 @@ span( strong.strong( ng-if = "currentOrg.poppa.isInTrial()" ) You will not be charged until your trial ends. + | span( ng-if = "currentOrg.poppa.isGraceExpired() || currentOrg.poppa.isInGrace()" diff --git a/client/directives/modals/settingsModal/forms/billingForm/components/planSummary/planSummaryView.jade b/client/directives/modals/settingsModal/forms/billingForm/components/planSummary/planSummaryView.jade index 7f7e71902..d0fc96d53 100644 --- a/client/directives/modals/settingsModal/forms/billingForm/components/planSummary/planSummaryView.jade +++ b/client/directives/modals/settingsModal/forms/billingForm/components/planSummary/planSummaryView.jade @@ -2,7 +2,7 @@ ng-if = 'plan' ) p.strong {{plan.name}} Plan - small.small Up to {{plan.maxConfigurations}} configurations + small.small Up to {{plan.maxConfigurations}} templates .grid-block.shrink.p.align-baseline( ng-if = 'plan' ) diff --git a/client/directives/modals/settingsModal/forms/billingForm/confirmationForm/confirmationForm.jade b/client/directives/modals/settingsModal/forms/billingForm/confirmationForm/confirmationForm.jade index d5a33a958..c65266b9c 100644 --- a/client/directives/modals/settingsModal/forms/billingForm/confirmationForm/confirmationForm.jade +++ b/client/directives/modals/settingsModal/forms/billingForm/confirmationForm/confirmationForm.jade @@ -17,7 +17,7 @@ footer.modal-footer.clearfix button.btn.btn-md.btn-block.green( ng-if = "currentOrg.poppa.isInActivePeriod()" - ) Go to your sandbox! + ) Go to your Environment button.btn.btn-md.btn-block.white( ng-click = "goToPanel('billingForm', 'back');" ng-if = "currentOrg.poppa.isInTrial()" diff --git a/client/directives/modals/settingsModal/forms/billingForm/planStatus/planStatusForm.jade b/client/directives/modals/settingsModal/forms/billingForm/planStatus/planStatusForm.jade index 0649eb315..6493c2f5d 100644 --- a/client/directives/modals/settingsModal/forms/billingForm/planStatus/planStatusForm.jade +++ b/client/directives/modals/settingsModal/forms/billingForm/planStatus/planStatusForm.jade @@ -1,7 +1,7 @@ //- changing plans .padding-md .clearfix - .label-col.full-width.text-center Configurations Used + .label-col.full-width.text-center Templates Used .meter( ng-class = "getMeterClass()" ) @@ -26,7 +26,7 @@ .tick .tick - .grid-block.align-center + .grid-block.align-center.plans-wrapper .grid-block.vertical.align-center.card.card-plan.disabled.padding-sm.text-center( ng-class = "{'active': PSFC.plan.id === 'runnable-starter'}" ng-mouseenter = "preview = 'runnable-starter'" @@ -47,7 +47,7 @@ | per month .grid-content.shrink.small Up to strong.strong 2 - | configurations. + | templates. .grid-block.vertical.align-center.card.card-plan.disabled.padding-sm.text-center( ng-class = "{'active': PSFC.plan.id === 'runnable-standard'}" @@ -69,7 +69,7 @@ | per month .grid-content.shrink.small Up to strong.strong 7 - | configurations. + | templates. .grid-block.vertical.align-center.card.card-plan.disabled.padding-sm.text-center( ng-class = "{'active': PSFC.plan.id === 'runnable-plus'}" @@ -91,7 +91,7 @@ | per month .grid-content.shrink.small Up to strong.strong 15 - | configurations. + | templates. label.grid-block.align-center.padding-xs.well.well-plan.well-summary.disabled( ng-if = "PSFC.discount" @@ -100,7 +100,7 @@ discount = "PSFC.discount" ) - p.grid-content.p.text-gray.text-center.padding-sm Your plan is automatically determined based on number of configurations at the end of each billing cycle. + p.grid-content.p.text-gray.text-center.padding-sm All plans require at least 3 users. Your plan is automatically determined based on number of templates at the end of each billing cycle. .grid-block.justify-center button.grid-block.shrink.btn.btn-md.green( diff --git a/client/directives/modals/settingsModal/forms/billingForm/trialForm/trialForm.jade b/client/directives/modals/settingsModal/forms/billingForm/trialForm/trialForm.jade index 9715ab510..c07dc0a39 100644 --- a/client/directives/modals/settingsModal/forms/billingForm/trialForm/trialForm.jade +++ b/client/directives/modals/settingsModal/forms/billingForm/trialForm/trialForm.jade @@ -21,11 +21,11 @@ ng-if = "currentOrg.poppa.trialDaysRemaining() > 3" ) You have strong.strong {{currentOrg.poppa.trialDaysRemaining()}} days - | left in your trial with unlimited configurations. + | left in your trial with unlimited templates. p.grid-content.small.text-gray( ng-if = "currentOrg.poppa.trialDaysRemaining() <= 3" - ) You only have {{currentOrg.poppa.trialDaysRemaining()}} days left in your trial! Add your payment info to ensure your team has uninterrupted access to their sandbox. + ) You only have {{currentOrg.poppa.trialDaysRemaining()}} days left in your trial! Add your payment info to ensure your team has uninterrupted access to Runnable. .grid-block.btn-wrapper( diff --git a/client/directives/modals/settingsModal/forms/gitHubForm/gitHubForm.jade b/client/directives/modals/settingsModal/forms/gitHubForm/gitHubForm.jade new file mode 100644 index 000000000..fb5d798e3 --- /dev/null +++ b/client/directives/modals/settingsModal/forms/gitHubForm/gitHubForm.jade @@ -0,0 +1,9 @@ +//- If loading: +//- .modal-form + .spinner-wrapper.spinner-md.spinner-gray.in( + ng-include = "'spinner'" + ) + +.modal-form.empty.grid-block.vertical.align-center.form-github( + github-integration +) diff --git a/client/directives/modals/settingsModal/forms/teamManagementForm/teamManagementFormView.jade b/client/directives/modals/settingsModal/forms/teamManagementForm/teamManagementFormView.jade index f1e7c0107..764e95717 100644 --- a/client/directives/modals/settingsModal/forms/teamManagementForm/teamManagementFormView.jade +++ b/client/directives/modals/settingsModal/forms/teamManagementForm/teamManagementFormView.jade @@ -27,7 +27,7 @@ .p.text-gray.text-center.padding-xs( ng-if = "state.fromTrialEnd" - ) We’ll only create or update containers for the selected users. + ) We’ll only create and update containers for the selected users. ol.list.list-bordered.list-teammates li.list-item( diff --git a/client/directives/modals/settingsModal/forms/teamManagementForm/teammateOptionsPopoverView.jade b/client/directives/modals/settingsModal/forms/teamManagementForm/teammateOptionsPopoverView.jade index 19d19b1cf..0f27db6ec 100644 --- a/client/directives/modals/settingsModal/forms/teamManagementForm/teammateOptionsPopoverView.jade +++ b/client/directives/modals/settingsModal/forms/teamManagementForm/teammateOptionsPopoverView.jade @@ -1,10 +1,10 @@ .popover.menu.bottom( - ng-class = "{in: active}" + ng-class = "{'in': active}" ng-style = "popoverStyle.getStyle()" style = "transform-origin: 90% 0" ) .arrow.white( - style = "left: auto; right: 0" + style = "left: auto; right: 1px" ) .popover-content ul.popover-list diff --git a/client/directives/modals/settingsModal/settingsModalView.jade b/client/directives/modals/settingsModal/settingsModalView.jade index 36b7113bb..85b414f77 100644 --- a/client/directives/modals/settingsModal/settingsModalView.jade +++ b/client/directives/modals/settingsModal/settingsModalView.jade @@ -29,20 +29,26 @@ .btn-text.grid-content Billing button.btn.btn-radio.grid-block.vertical( ng-class = "{'active': SEMC.currentTab === 'teamManagement'}" - ng-click = "\ - SEMC.currentTab = 'teamManagement';\ - " + ng-click = "SEMC.currentTab = 'teamManagement'" ) svg.iconnables.grid-content use( xlink:href = "#icons-team" ) .btn-text.grid-content Teammates + button.btn.btn-radio.grid-block.vertical( + ng-class = "{'active': SEMC.currentTab === 'githubIntegration'}" + ng-click = "SEMC.currentTab = 'githubIntegration'" + ng-if = "$root.featureFlags.gitHubIntegration" + ) + svg.iconnables.grid-content + use( + xlink:href = "#icons-octicons-github-gray" + ) + .btn-text.grid-content PR Bot button.btn.btn-radio.grid-block.vertical( ng-class = "{'active': SEMC.currentTab === 'slackIntegration'}" - ng-click = "\ - SEMC.currentTab = 'slackIntegration';\ - " + ng-click = "SEMC.currentTab = 'slackIntegration'" ) img.img.iconnables.grid-content( height = "24" @@ -62,6 +68,11 @@ ng-if = "SEMC.currentTab === 'teamManagement'" ) + div( + ng-if = "SEMC.currentTab === 'githubIntegration'" + ng-include = "'gitHubForm'" + ) + slack-integration-form( ng-if = "SEMC.currentTab === 'slackIntegration'" ) diff --git a/client/directives/navBar/viewNav.jade b/client/directives/navBar/viewNav.jade index 1dbf2171b..fb52867a3 100644 --- a/client/directives/navBar/viewNav.jade +++ b/client/directives/navBar/viewNav.jade @@ -4,9 +4,9 @@ data = "dataApp.data" ) -//- aha menu +//- aha menu --- This should be triggered via an event! .popover.right.in.popover-aha( - ng-if = "$root.featureFlags.aha2 && !$root.featureFlags.aha1ExitedEarly" + ng-if = "$root.featureFlags.aha && CA.showAhaNavPopover" ng-include = "'ahaPopoverView'" ) @@ -18,10 +18,10 @@ a.a( use( xlink:href = "#icons-gear-dark" ) - | Configure + | Templates a.a.disabled( - ng-if = "(dataApp.state.includes('base.config') && CA.instancesByPod && !CA.instancesByPod.models.length) || $root.featureFlags.aha1 || $root.featureFlags.aha1ExitedEarly" + ng-if = "(dataApp.state.includes('base.config') && CA.instancesByPod && !CA.instancesByPod.models.length) && !$root.featureFlags.containersViewTemplateControls" tooltip = "You don’t have any running containers yet!" tooltip-options = "{\"class\":\"right\",\"left\":75,\"top\":17}" ) @@ -32,10 +32,14 @@ a.a.disabled( | Containers a.a( - ng-if = "(dataApp.state.includes('base.instances') || !CA.instancesByPod || CA.instancesByPod.models.length) && !$root.featureFlags.aha1 && !$root.featureFlags.aha1ExitedEarly" + ng-if = "(dataApp.state.includes('base.instances') || !CA.instancesByPod || CA.instancesByPod.models.length) || $root.featureFlags.containersViewTemplateControls" ui-sref = "base.instances({ userName: CA.activeAccount.oauthName() })" ui-sref-active = "active" ) + .aha-overlay-div( + ng-if = "$root.featureFlags.aha && CA.ahaGuide.isAddingFirstRepo() && CA.instancesByPod.models.length && !CA.ahaGuide.hasConfirmedSetup()" + ng-click = "CA.showAhaConfirmation($event)" + ) svg.iconnables.icons-server-dark use( xlink:href = "#icons-server-dark" diff --git a/client/directives/ngclick.js b/client/directives/ngclick.js index 3a1415d24..67582e8f6 100644 --- a/client/directives/ngclick.js +++ b/client/directives/ngclick.js @@ -22,6 +22,7 @@ function ngClick( }); eventTracking.trackClicked({ attrs: cleanedAttrs, + eventName: attrs.eventName, text: text }); } diff --git a/client/directives/popovers/popOverController.js b/client/directives/popovers/popOverController.js index 3e4f5be83..30d0985c8 100644 --- a/client/directives/popovers/popOverController.js +++ b/client/directives/popovers/popOverController.js @@ -15,6 +15,7 @@ function PopOverController( var POC = this; POC.unbindDocumentClick = angular.noop; POC.unbindPopoverOpened = angular.noop; + POC.unbindSpecificPopoverOpened = angular.noop; POC.isPopoverActive = function () { return $scope.active; @@ -25,6 +26,11 @@ function PopOverController( $scope.active = false; POC.unbindDocumentClick(); POC.unbindPopoverOpened(); + POC.unbindSpecificPopoverOpened(); + $rootScope.$broadcast('popover-closed', { + template: $scope.template, + data: $scope.data + }); // We need a closure because they could technically re-open the popover and we want to manage THIS scope and THIS element. (function (popoverElementScope, popoverElement) { //Give the transition some time to finish! @@ -39,6 +45,10 @@ function PopOverController( }(POC.popoverElementScope, POC.popoverElement)); }; POC.openPopover = function () { + $rootScope.$broadcast('popover-opened', { + template: $scope.template, + data: $scope.data + }); $scope.popoverOptions = $scope.popoverOptions || {}; if (!exists($scope.popoverOptions.top) && !exists($scope.popoverOptions.bottom)) { @@ -48,20 +58,30 @@ function PopOverController( $scope.popoverOptions.left = 0; } - $rootScope.$broadcast('close-popovers'); + if (!$scope.noBroadcast) { + $rootScope.$broadcast('close-popovers'); + } + $timeout(function () { // If the click has no target we should close the popover. // If the click has a target and that target is on the page but not on our popover we should close the popover. // Otherwise we should keep the popover alive. POC.unbindDocumentClick = $scope.$on('app-document-click', function (event, target) { - if (!target || (target && $document[0].contains(target) && !POC.popoverElement[0].contains(target))) { + if (!$scope.userCannotClose && (!target || (target && $document[0].contains(target) && !POC.popoverElement[0].contains(target)))) { POC.closePopover(); } }); }, 0); - POC.unbindPopoverOpened = $scope.$on('close-popovers', function () { - POC.closePopover(); + POC.unbindPopoverOpened = $scope.$on('close-popovers', function (event, closeAllPopoversOverride) { + if (!$scope.userCannotClose || closeAllPopoversOverride) { + POC.closePopover(); + } + }); + POC.unbindSpecificPopoverOpened = $scope.$on('close-open-state-popover', function (event, popoverName) { + if ($scope.data && $scope.data.popoverName === popoverName) { + POC.closePopover(); + } }); var template = $templateCache.get($scope.template); diff --git a/client/directives/popovers/popOverDirective.js b/client/directives/popovers/popOverDirective.js index fda8b35ab..480760a63 100755 --- a/client/directives/popovers/popOverDirective.js +++ b/client/directives/popovers/popOverDirective.js @@ -9,14 +9,15 @@ require('app') var scopeVars = { - data: '=? popOverData', - popoverOptions: '=? popOverOptions', - noBroadcast: '=? popOverNoBroadcast', actions: '=? popOverActions', active: '=? popOverActive', - template: '= popOverTemplate', controller: '=? popOverController', - controllerAs: '@? popOverControllerAs' + controllerAs: '@? popOverControllerAs', + data: '=? popOverData', + noBroadcast: '=? popOverNoBroadcast', + popoverOptions: '=? popOverOptions', + template: '= popOverTemplate', + userCannotClose: '=? popOverUncloseable' }; function popOver( @@ -53,6 +54,7 @@ function popOver( return $log.error('Pop over needs a template'); } } + $scope.element = element; $scope.popoverOptions = $scope.popoverOptions || {}; $scope.active = $scope.active || false; $scope.popoverStyle = { @@ -60,7 +62,9 @@ function popOver( if (!$scope.active) { return previousStyle; } + var offset = {}; + var topMargin = 8; var scrollTop = $document.find('body')[0].scrollTop || $document.find('html')[0].scrollTop; if (keypather.get($scope, 'popoverOptions.mouse')) { @@ -99,11 +103,42 @@ function popOver( if (keypather.get($scope, 'popoverOptions.verticallyCentered')) { style.bottom = null; - style.top = Math.round((-POC.popoverElement[0].offsetHeight / 2 + offset.top + (offset.bottom - offset.top) / 2)) + 'px'; + var targetedTopVal = Math.round((-POC.popoverElement[0].offsetHeight / 2 + offset.top + (offset.bottom - offset.top) / 2)); + if ( + $scope.popoverOptions.pinToViewPort && // If true, make sure popover is not displayed outside the viewport + POC.popoverElement[0].offsetHeight + targetedTopVal > $document.find('body')[0].offsetHeight + ) { + targetedTopVal = $document.find('body')[0].offsetHeight - POC.popoverElement[0].offsetHeight - 8; + } + if (targetedTopVal < 0) { + targetedTopVal = topMargin; + } + + style.top = targetedTopVal + 'px'; } previousStyle = style; return style; + }, + + getArrowStyle: function() { + var style = {}; + var elementPosition = $scope.element[0].getBoundingClientRect(); + var elementCenter = (elementPosition.bottom + elementPosition.top) / 2; + + var popoverElementPosition = POC.popoverElement[0].getBoundingClientRect(); + + var topInt = parseInt($scope.popoverStyle.getStyle().top.replace('px', '')); + var isAtBottom = window.innerHeight - elementPosition.top < 180; + + var diff = Math.abs(popoverElementPosition.top - elementCenter); + + if (topInt > 8 && !isAtBottom || diff > POC.popoverElement[0].getBoundingClientRect().height) { + return style; + } else { + style.top = diff + 'px'; + return style; + } } }; @@ -129,8 +164,10 @@ function popOver( bottom: event.pageY } }; + POC.openPopover($scope.options); } + var trigger = attrs.popOverTrigger || 'click'; switch (trigger) { case 'rightClick': diff --git a/client/directives/popovers/popoverMoreContainers/viewMoreContainersPopover.jade b/client/directives/popovers/popoverMoreContainers/viewMoreContainersPopover.jade index 2675a4408..f48f69000 100644 --- a/client/directives/popovers/popoverMoreContainers/viewMoreContainersPopover.jade +++ b/client/directives/popovers/popoverMoreContainers/viewMoreContainersPopover.jade @@ -2,10 +2,10 @@ ng-class = "{in: active}" ng-style = "popoverStyle.getStyle()" ) - .popover-header api Containers + .popover-header api input.input.input-xs.input-search( ng-model = "data.repoFilter" - placeholder = "Filter by branch name" + placeholder = "Filter by name" required type = "search" select-on = "showFilter" diff --git a/client/directives/repositorySelector/views/viewPopoverFilesRepositoryOptions.jade b/client/directives/repositorySelector/views/viewPopoverFilesRepositoryOptions.jade index c90fcd2bd..eaab64423 100644 --- a/client/directives/repositorySelector/views/viewPopoverFilesRepositoryOptions.jade +++ b/client/directives/repositorySelector/views/viewPopoverFilesRepositoryOptions.jade @@ -69,7 +69,7 @@ ng-model = "repoSelector.data.commands" spellcheck = "false" ) - small.small Add scripts to be run before your container is started. + small.small Add scripts to run before your container is started. .popover-footer.clearfix.fade( ng-class = "{'in': isActivePanel()}" diff --git a/client/directives/repositorySelector/views/viewPopoverFilesRepositorySelect.jade b/client/directives/repositorySelector/views/viewPopoverFilesRepositorySelect.jade index 6e9d042c3..a68136a3d 100644 --- a/client/directives/repositorySelector/views/viewPopoverFilesRepositorySelect.jade +++ b/client/directives/repositorySelector/views/viewPopoverFilesRepositorySelect.jade @@ -36,7 +36,7 @@ ul.list.list-servers( ng-if = "repoSelector.data.githubRepos.models" ) - li.list-item.multi-line( + li.grid-block.align-center.list-item.multi-line( ng-class = "{\ 'active': repo.loading,\ 'no-touching': repoSelector.data.loading\ @@ -46,34 +46,37 @@ repo in repoSelector.data.githubRepos.models | \ repos: repoSelector.data.repoFilter | \ allBut: data.appCodeVersions | \ - orderBy: '-attrs.updated_at'\ + orderBy: '-attrs.pushed_at'\ " - ) {{ repo.attrs.name}} + ) + .grid-content.text-overflow( + title = "{{ repo.attrs.name}}" + ) {{ repo.attrs.name}} - //- if invite flows... - .row.row-author( - ng-if = "$root.featureFlags.inviteFlows" - ) - .btn-user.text-overflow.no-touching( - ng-class = "{'active': state.active}" - ng-include = "'userButtonView'" - ) - span.small( + //- if invite flows... + .row.row-author( ng-if = "$root.featureFlags.inviteFlows" - ) —{{repo.attrs.updated_at | timeFrom}} + ) + .btn-user.text-overflow.no-touching( + ng-class = "{'active': state.active}" + ng-include = "'userButtonView'" + ) + span.small( + ng-if = "$root.featureFlags.inviteFlows" + ) —{{repo.attrs.pushed_at | timeFrom}} - //- else - small.small( - ng-if = "!$root.featureFlags.inviteFlows" - ) Updated {{ repo.attrs.updated_at | timeFrom }} + //- else + small.small( + ng-if = "!$root.featureFlags.inviteFlows" + ) Updated {{ repo.attrs.pushed_at | timeFrom }} - svg.iconnables.icons-arrow-down( + svg.grid-content.shrink.iconnables.icons-arrow-down( ng-if = "!repo.loading" ) use( xlink:href = "#icons-arrow-down" ) - .spinner-wrapper.spinner-sm.spinner-purple.in( + .grid-content.shrink.spinner-wrapper.spinner-sm.spinner-purple.in( ng-if = "repo.loading" ng-include = "'spinner'" ) diff --git a/client/services/ahaGuideService.js b/client/services/ahaGuideService.js new file mode 100644 index 000000000..1bc90be98 --- /dev/null +++ b/client/services/ahaGuideService.js @@ -0,0 +1,343 @@ +'use strict'; + +require('app') + .factory('ahaGuide', ahaGuide); + +var STEPS = { + CHOOSE_ORGANIZATION: 1, + ADD_FIRST_REPO: 2, + ADD_FIRST_BRANCH: 3, + SETUP_RUNNABOT: 4, + COMPLETED: -1 +}; + +function ahaGuide( + $rootScope, + currentOrg, + fetchInstancesByPod, + isRunnabotPartOfOrg, + keypather, + patchOrgMetadata +) { + var instances = []; + var hasRunnabot = false; + function refreshInstances() { + return fetchInstancesByPod() + .then(function (fetchedInstances) { + instances = fetchedInstances; + }); + } + function refreshHasRunnabot() { + if (hasRunnabot) { return true; } + return isRunnabotPartOfOrg(keypather.get(currentOrg, 'github.attrs.login')) + .then(function (runnabot) { + if (runnabot && isInGuide()) { + endGuide() + .then(function() { + $rootScope.$broadcast('showAutoLaunchPopover'); + }); + } + hasRunnabot = runnabot; + return hasRunnabot; + }); + } + + refreshInstances(); + refreshHasRunnabot(); + + var stepList = {}; + stepList[STEPS.CHOOSE_ORGANIZATION] = { + title: 'Step 1: Choose your Organization', + subSteps: { + orgSelection: { + caption: 'Select the organization you want to use with Runnable.', + className: 'aha-meter-33' + }, + dockLoading: { + caption: 'Bear with us!', + className: 'aha-meter-66' + }, + dockLoaded: { + caption: 'Continue to start configuring your project.', + className: 'aha-meter-100' + } + }, + panelSteps: { + orgSelection: 0, + dockLoading: 1, + dockLoaded: 2 + }, + defaultSubstep: 'orgSelection' + }; + + stepList[STEPS.ADD_FIRST_REPO] = { + title: 'Step 2: Configure your Application', + subSteps: { + addRepository: { + className: 'aha-meter-11', + step: 0, + value: 10 + }, + containerSelection: { + className: 'aha-meter-22', + step: 1, + value: 20 + }, + dockerfileMirroring: { + className: 'aha-meter-33', + step: 2, + value: 30 + }, + nameContainer: { + className: 'aha-meter-44', + step: 3, + value: 40 + }, + repository: { + className: 'aha-meter-55', + step: 4, + value: 50 + }, + commands: { + className: 'aha-meter-66', + step: 5, + value: 60 + }, + buildfiles: { + className: 'aha-meter-77', + step: 6, + value: 70 + }, + default: { + className: 'aha-meter-77', + step: 6, + value: 70 + }, + env: { + className: 'aha-meter-77', + step: 6, + value: 70 + }, + files: { + className: 'aha-meter-77', + step: 6, + value: 70 + }, + filesMirror: { + className: 'aha-meter-77', + step: 6, + value: 70 + }, + ports: { + className: 'aha-meter-77', + step: 6, + value: 70 + }, + translation: { + className: 'aha-meter-77', + step: 6, + value: 70 + }, + logs: { + className: 'aha-meter-88', + step: 7, + value: 80 + }, + exitedEarly: { + className: 'aha-meter-88', + step: 7, + value: 80 + }, + success: { + className: 'aha-meter-100', + step: 8, + value: 90 + }, + complete: { + className: 'aha-meter-100', + step: 9, + value: 100 + } + }, + buildStatus: { + building: 'We‘re building! Build time varies depending on your build commands.', + starting: 'We‘re building! Build time varies depending on your build commands.', + running: 'Looking good! Check out your URL, and click ‘Done’ if it looks good to you too.', + stopped: 'Your template isn‘t running yet! Check the logs to debug any issues. If you‘re stumped, ask our engineers!', + cmdFailed: 'Your template isn‘t running yet! Check the logs to debug any issues. If you‘re stumped, ask our engineers!', + crashed: 'Your template isn‘t running yet! Check the logs to debug any issues. If you‘re stumped, ask our engineers!', + buildFailed: 'Your template isn‘t running yet! Check the logs to debug any issues. If you‘re stumped, ask our engineers!' + }, + configSubsteps: ['default', 'env', 'files', 'ports', 'translation'], + defaultSubstep: 'addRepository' + }; + + stepList[STEPS.ADD_FIRST_BRANCH] = { + title: 'Step 3: Add a Branch', + subSteps: { + addBranch: { + caption: 'Almost done! Click the + button next to a repo name to add a branch.', + className: 'aha-meter-33', + value: 33 + }, + selectBranch: { + className: 'aha-meter-66', + value: 66 + }, + noBranches: { + className: 'aha-meter-100', + value: 100 + }, + deletedTemplate: { + caption: 'You\'ve deleted your repository template. Create another one to continue.', + className: 'aha-meter-20', + value: -1 + } + }, + panelSteps: { }, + defaultSubstep: 'addBranch' + }; + + stepList[STEPS.SETUP_RUNNABOT] = { + subSteps: { + setupRunnabot: { + caption: 'Get the most out of Runnabot by adding branches automatically', + className: 'aha-meter-50' + } + } + }; + + var cachedSubstep = {}; + +/** + * Furthest Substep getter/setter + * + * When setting the substep, the new subStep must have a value greater than the previous step to + * be updated + * + * @param step {Number} Which step to reference when looking at the substep + * @param newSubstep {String} new substep to go to + * @returns {String} substep currently on + */ + function furthestSubstep(step, newSubstep) { + if (arguments.length > 1) { + if (!cachedSubstep[step]) { + cachedSubstep[step] = newSubstep; + } else { + var newStepValue = keypather.get(stepList[step], 'subSteps.' + newSubstep + '.value'); + var oldSubstepValue = keypather.get(stepList[step], 'subSteps.' + cachedSubstep[step] + '.value'); + if (newStepValue === -1 || newStepValue > oldSubstepValue) { + // automatically allow switch when an error state + cachedSubstep[step] = newSubstep; + } + } + } + return cachedSubstep[step] || stepList[step].defaultSubstep; + } + + var cachedStep; + $rootScope.$watch(function () { + cachedStep = null; + }); + $rootScope.$on('$stateChangeSuccess', function () { + refreshInstances(); + refreshHasRunnabot(); + }); + function getCurrentStep() { + if (!cachedStep) { + if ($rootScope.featureFlags.aha && !keypather.get(currentOrg, 'poppa.id')) { + cachedStep = STEPS.CHOOSE_ORGANIZATION; + } else if (!isInGuide()) { + cachedStep = STEPS.COMPLETED; + } else if (!hasConfirmedSetup()) { + cachedStep = STEPS.ADD_FIRST_REPO; + } else { + // loop over instances and see if any has ever had a branch launched + var hasBranchLaunched = false; + if (keypather.get(instances, 'models.length')) { + instances.models.some(function (instance) { + hasBranchLaunched = instance.attrs.hasAddedBranches || keypather.get(instance, 'children.models.length'); + return hasBranchLaunched; + }); + } + if (!hasBranchLaunched && !ahaGuide.skippedBranchMilestone) { + cachedStep = STEPS.ADD_FIRST_BRANCH; + } else if (!hasRunnabot) { + cachedStep = STEPS.SETUP_RUNNABOT; + } else { + cachedStep = STEPS.COMPLETED; + } + } + } + return cachedStep; + } + + function isInGuide () { + return keypather.get(currentOrg, 'poppa.attrs.metadata.hasAha'); + } + + function hasConfirmedSetup () { + return keypather.get(currentOrg, 'poppa.attrs.metadata.hasConfirmedSetup'); + } + + function updateCurrentOrg (updatedOrg) { + if (keypather.has(updatedOrg, 'metadata.hasAha') && keypather.has(updatedOrg, 'metadata.hasConfirmedSetup')) { + currentOrg.poppa.attrs.metadata = updatedOrg.metadata; + } + } + + function skipBranchMilestone () { + ahaGuide.skippedBranchMilestone = true; + $rootScope.$broadcast('showAhaSidebar'); + } + + function endGuide () { + $rootScope.$broadcast('close-popovers'); + return patchOrgMetadata(currentOrg.poppa.id(), { + metadata: { + hasAha: false + } + }) + .then(function (updatedOrg) { + updateCurrentOrg(updatedOrg); + }); + } + + function resetGuide() { + return patchOrgMetadata(currentOrg.poppa.id(), { + metadata: { + hasAha: true, + hasConfirmedSetup: false + } + }) + .then(function (updatedOrg) { + updateCurrentOrg(updatedOrg); + }); + } + + return { + endGuide: endGuide, + resetGuide: resetGuide, + getCurrentStep: getCurrentStep, + hasConfirmedSetup: hasConfirmedSetup, + hasRunnabot: refreshHasRunnabot, + isInGuide: isInGuide, + stepList: stepList, + steps: STEPS, + updateCurrentOrg: updateCurrentOrg, + furthestSubstep: furthestSubstep, + skipBranchMilestone: skipBranchMilestone, + isChoosingOrg: function() { + return getCurrentStep() === STEPS.CHOOSE_ORGANIZATION; + }, + isAddingFirstRepo: function () { + return getCurrentStep() === STEPS.ADD_FIRST_REPO; + }, + isAddingFirstBranch: function() { + return getCurrentStep() === STEPS.ADD_FIRST_BRANCH; + }, + isSettingUpRunnabot: function() { + return getCurrentStep() === STEPS.SETUP_RUNNABOT && !hasRunnabot; + } + }; +} diff --git a/client/services/configs/serviceIntercomAppId.js b/client/services/configs/serviceIntercomAppId.js new file mode 100644 index 000000000..234b58cf7 --- /dev/null +++ b/client/services/configs/serviceIntercomAppId.js @@ -0,0 +1,4 @@ +'use strict'; + +require('app') + .value('intercomAppId', require('config/environment').intercomAppId); diff --git a/client/services/configs/siftApiConfigService.js b/client/services/configs/siftApiConfigService.js new file mode 100755 index 000000000..9e015a6b0 --- /dev/null +++ b/client/services/configs/siftApiConfigService.js @@ -0,0 +1,4 @@ +'use strict'; + +require('app') + .value('siftApiConfig', require('config/api').siftApiKey); diff --git a/client/services/createAndBuildNewContainerService.js b/client/services/createAndBuildNewContainerService.js index af03c678a..db346b010 100644 --- a/client/services/createAndBuildNewContainerService.js +++ b/client/services/createAndBuildNewContainerService.js @@ -20,7 +20,7 @@ function createAndBuildNewContainer( fetchInstancesByPod, fetchPlan, fetchUser, - helpCards + keypather ) { return function (createPromiseForState, containerName, options) { options = options || {}; @@ -35,7 +35,7 @@ function createAndBuildNewContainer( plan: fetchPlan() }) .then(function (response) { - oldPlanId = response.plan.next.id; + oldPlanId = keypather.get(response, 'plan.next.id'); var instanceOptions = { name: containerName, owner: { @@ -51,7 +51,6 @@ function createAndBuildNewContainer( return $q.when(createPromiseForState); }) .then(function (newServerModel) { - helpCards.hideActiveCard(); if (options.isolation) { newServerModel.opts.isIsolationGroupMaster = false; newServerModel.opts.isolated = options.isolation.id(); @@ -78,12 +77,13 @@ function createAndBuildNewContainer( }); }) .then(function (instance) { - helpCards.refreshAllCards(); return instance; }) .catch(function (err) { // Remove it from the servers list - instance.dealloc(); + if (instance) { + instance.dealloc(); + } return $q.reject(err); }); }; diff --git a/client/services/createNewSandboxForUserService.js b/client/services/createNewSandboxForUserService.js index dfba27d2a..a66333e30 100644 --- a/client/services/createNewSandboxForUserService.js +++ b/client/services/createNewSandboxForUserService.js @@ -4,12 +4,14 @@ require('app') .factory('createNewSandboxForUserService', createNewSandboxForUserService); function createNewSandboxForUserService( + eventTracking, fetchUser, promisify ) { return function (orgName) { return fetchUser() .then(function (user) { + eventTracking.waitingForInfrastructure(orgName); return promisify(user, 'createUserWhitelist')({ name: orgName }); }); }; diff --git a/client/services/featureFlagService.js b/client/services/featureFlagService.js index 9ef1f8daa..242959269 100644 --- a/client/services/featureFlagService.js +++ b/client/services/featureFlagService.js @@ -7,28 +7,27 @@ function featureFlags( $localStorage ) { var defaultFeatureFlags = { - aha: false, - aha0: false, // step 1: create sandbox - aha1: false, // step 2: working repo config - aha1ExitedEarly: false, // step 2: if the user left the flow before getting a running config - aha2: false, // step 3: add branch - aha3: false, // step 4: runnabot - ahaOverview: false, // toggle sidebar - ahaSidebar: false, // toggle sidebar + addBranches: true, + aha: true, + ahaBranchUrlStep: false, allowIsolatedUpdate: false, autoIsolation: false, autoIsolationSetup: false, backup: false, - blankDockerfile: false, // allows users to skip the verification flow + blankDockerfile: true, // allows users to skip the verification flow billing: true, cardStatus: false, connections: false, - configTerminal: false, // flag for terminal in config view + configTerminal: true, // flag for terminal in config view + containersViewTemplateControls: false, + containersViewEmptyState: false, + contingencyPlan: false, dockerfileMirroringMultiple: false, editAnyInstance: false, emptyFolder: false, // shows empty folder markup fullScreen: false, // toggles full screen fullScreenToggle: false, // toggles the button that toggles full screen + gitHubIntegration: true, hostnameNotifications: false, hostnameTool: false, imAfraidOfTheDark: false, // toggles theme @@ -39,8 +38,8 @@ function featureFlags( multilineStartCmd: false, multipleRepositoryContainers: false, // for adding multiple containers with the same repository navListFilter: false, - nextPayment: false, // show the next payment date under payment summary newUserPrompt: false, // modal for new users + nextPayment: false, // show the next payment date under payment summary noBuildLogs: true, optionsInModal: false, // allows delete in modal renameContainer: false, @@ -52,7 +51,7 @@ function featureFlags( trial: false, // sets account to trial mode undoDelete: false, // undo delete configuration webhooks: false, - webToolbar: false, // webview toolbar + webToolbar: true, // webview toolbar whitelistIpFiltering: false }; diff --git a/client/services/patchOrgMetadata.js b/client/services/patchOrgMetadata.js new file mode 100644 index 000000000..3e54076bf --- /dev/null +++ b/client/services/patchOrgMetadata.js @@ -0,0 +1,22 @@ +'use strict'; + +require('app') + .factory('patchOrgMetadata', patchOrgMetadata); + +function patchOrgMetadata( + $http, + configAPIHost, + errs +) { + return function (orgId, params) { + return $http({ + method: 'patch', + url: configAPIHost + '/auth/whitelist/' + orgId, + data: params + }) + .then(function(response) { + return response.data; + }) + .catch(errs.handler); + }; +} diff --git a/client/services/runnabotService.js b/client/services/runnabotService.js new file mode 100644 index 000000000..2c504e3a7 --- /dev/null +++ b/client/services/runnabotService.js @@ -0,0 +1,22 @@ +'use strict'; + +require('app') + .factory('isRunnabotPartOfOrg', isRunnabotPartOfOrg); + +function isRunnabotPartOfOrg( + $http, + configAPIHost +) { + return function (orgName) { + return $http({ + method: 'get', + url: configAPIHost + '/github/orgs/' + orgName + '/memberships/runnabot' + }) + .then(function (data) { + return data.status < 400; // Github returns 404 when the user isn't part of the org + }) + .catch(function () { + return false; + }); + }; +} \ No newline at end of file diff --git a/client/services/serviceCreateNewBuild.js b/client/services/serviceCreateNewBuild.js index 167b18423..94efd9a73 100644 --- a/client/services/serviceCreateNewBuild.js +++ b/client/services/serviceCreateNewBuild.js @@ -13,6 +13,7 @@ function createNewBuild( return function (activeAccount, opts) { opts = opts || {}; var dockerfilePath = opts.dockerfilePath; + var configurationMethod = opts.configurationMethod; var thisUser, version; function createContext(user) { return promisify(user, 'createContext')({ @@ -32,6 +33,11 @@ function createNewBuild( buildDockerfilePath: dockerfilePath }); } + if (configurationMethod === 'blankDockerfile') { + return promisify(version, 'update')({ + advanced: true, + }); + } return version; }); } @@ -68,7 +74,7 @@ function createNewBuildAndFetchBranch( fetchStackData, promisify ) { - return function (activeAccount, repo, dockerfilePath) { + return function (activeAccount, repo, dockerfilePath, configurationMethod) { var inputs = { repo: repo, masterBranch: null, @@ -76,7 +82,10 @@ function createNewBuildAndFetchBranch( }; return fetchStackData(repo) .then(function () { - return createNewBuild(activeAccount, { dockerfilePath: dockerfilePath }); + return createNewBuild(activeAccount, { + configurationMethod: configurationMethod, + dockerfilePath: dockerfilePath + }); }) .then(function (buildWithVersion) { inputs.build = buildWithVersion; diff --git a/client/services/serviceEventTracking.js b/client/services/serviceEventTracking.js index 90507ea67..3c9583dd2 100644 --- a/client/services/serviceEventTracking.js +++ b/client/services/serviceEventTracking.js @@ -9,9 +9,11 @@ require('app') .service('eventTracking', EventTracking); var User = require('@runnable/api-client/lib/models/user'); +var UUID = require('node-uuid'); var _keypather; var _$location; var INTERCOM_APP_ID; +var SIFT_API_KEY; /** * EventTracking @@ -26,19 +28,20 @@ function EventTracking( $window, assign, keypather, - configEnvironment + intercomAppId, + siftApiConfig ) { - if (configEnvironment === 'production') { - INTERCOM_APP_ID = 'wqzm3rju'; // production ID - } else { - INTERCOM_APP_ID = 'xs5g95pd'; // test ID - } + INTERCOM_APP_ID = intercomAppId; + var self = this; + SIFT_API_KEY = siftApiConfig; + _keypather = keypather; _$location = $location; - this._Intercom = $window.Intercom; - this._user = null; - this.$window = $window; + self._Intercom = $window.Intercom; + self.analytics = $window.analytics; + self._user = null; + self.$window = $window; /** * Extend per-event data with specific properties @@ -46,8 +49,8 @@ function EventTracking( * @param {Object} data - data for given event to be extended * @return Object - extended event object */ - this.extendEventData = function (data) { - if (!this._user) { + self.extendEventData = function (data) { + if (!self._user) { $log.error('eventTracking.boot() must be invoked before reporting events'); } // username owner if server page @@ -57,8 +60,8 @@ function EventTracking( state: $state.$current.name, href: $window.location.href }; - if (angular.isFunction(keypather.get(this._user, 'oauthName'))) { - baseData.userName = this._user.oauthName(); + if (angular.isFunction(keypather.get(self._user, 'oauthName'))) { + baseData.userName = self._user.oauthName(); } if ($stateParams.userName) { baseData.instanceOwner = $stateParams.userName; @@ -73,9 +76,31 @@ function EventTracking( * Stub Intercom when SDK not present * (development/staging environments) */ - if (!this._Intercom || $browser.cookies().isModerating) { + if (!self._Intercom || $browser.cookies().isModerating) { // stub intercom if not present - this._Intercom = angular.noop; + self._Intercom = angular.noop; + } + + /** + * Stub Segment when SDK not present + * (development/staging environments) + */ + if (!self.analytics) { + // stub segment (analytics) if not present + self.analytics = { + ready: angular.noop, + track: angular.noop, + identify: angular.noop, + alias: angular.noop, + page: angular.noop, + group: angular.noop, + trackLink: angular.noop, + trackForm: angular.noop, + user: angular.noop, + debug: angular.noop, + on: angular.noop, + timeout: angular.noop + }; } /** @@ -83,7 +108,7 @@ function EventTracking( * @param {String} mixpanel SDK API method name * @params [1..n] optional arguments passed to mixpanel SDK */ - this._mixpanel = function () { + self._mixpanel = function () { if (!angular.isFunction(keypather.get($window, 'mixpanel.'+arguments[0]))) { // $log.info('Mixpanel JS SDK stubbed'); // $log.info(arguments); @@ -100,14 +125,15 @@ function EventTracking( } /** - * Intercom and Mixpanel user identification + * Intercom, Mixpanel, and Segment user identification * @throws Error * @param {Object} user - User Model instance * @return this */ EventTracking.prototype.boot = function (user, opts) { + var self = this; opts = opts || {}; - if (this._user) { return this; } + if (self._user) { return self; } if (!(user instanceof User)) { throw new Error('arguments[0] must be instance of User'); } @@ -115,14 +141,26 @@ EventTracking.prototype.boot = function (user, opts) { if (user.attrs._beingModerated) { user = new User(user.attrs._beingModerated, { noStore: true }); } else { - if (this.$window.fbq) { - this.$window.fbq('track', 'ViewContent', { + var session = window.sessionStorage.getItem('sessionId'); + if (!session) { + session = UUID.v4(); + window.sessionStorage.setItem('sessionId', session); + } + + var _sift = window._sift = window._sift || []; + _sift.push(['_setAccount', SIFT_API_KEY]); + _sift.push(['_setUserId', user.name]); + _sift.push(['_setSessionId', session]); + _sift.push(['_trackPageview']); + + self.analytics.ready(function () { + self.analytics.track('ViewContent', { action: 'LoggedIn' }); - } + }); } - this._user = user; + self._user = user; var data = { name: user.oauthName(), email: user.attrs.email, @@ -139,12 +177,12 @@ EventTracking.prototype.boot = function (user, opts) { // Mixpanel uses a string GUID to track anon users // If we're still tracking the user via GUID, we need to alias // Otherwise, we can just identify ourselves - if (angular.isString(this._mixpanel('get_distinct_id'))) { - this._mixpanel('alias', user.oauthId()); + if (angular.isString(self._mixpanel('get_distinct_id'))) { + self._mixpanel('alias', user.oauthId()); } else { - this._mixpanel('identify', user.oauthId()); + self._mixpanel('identify', user.oauthId()); } - this._Intercom('boot', data); + self._Intercom('boot', data); var userJSON = user.toJSON(); var firstName = ''; var lastName = ''; @@ -153,19 +191,39 @@ EventTracking.prototype.boot = function (user, opts) { firstName = displayName.split(/ (.+)/)[0]; lastName = displayName.split(/ (.+)/)[1]; } - this._mixpanel('people.set', { + self._mixpanel('people.set', { '$first_name': firstName, '$last_name': lastName, '$created': _keypather.get(userJSON, 'created'), '$email': _keypather.get(userJSON, 'email') }); - return this; + + // Segment + self.analytics.ready(function () { + self.analytics.identify(data.name, { + firstName: firstName, + lastName: lastName, + username: data.name, + email: _keypather.get(userJSON, 'email'), + createdAt: _keypather.get(userJSON, 'created'), + avatar: _keypather.get(userJSON, 'gravatar') + }); + self.analytics.alias(user.oauthId()); + self.analytics.alias(_keypather.get(userJSON, '_id')); + if (opts.orgName) { + self.analytics.group(data.company.id, { + name: data.company.name + }); + } + }); + return self; }; /** * Record user event toggling of selected commit in repository * Reports to: * - mixpanel + * - segment * @param {Object} data - key/value pairs of event data * - keys * - triggeredBuild: Boolean @@ -173,13 +231,17 @@ EventTracking.prototype.boot = function (user, opts) { * @return this */ EventTracking.prototype.toggledCommit = function (data) { + var self = this; var eventName = 'toggled-commit'; - var eventData = this.extendEventData({ + var eventData = self.extendEventData({ triggeredBuild: !!data.triggeredBuild, selectedCommit: data.acv }); - this._mixpanel('track', eventName, eventData); - return this; + self._mixpanel('track', eventName, eventData); + self.analytics.ready(function () { + self.analytics.track(eventName, eventData); + }); + return self; }; /** @@ -187,32 +249,42 @@ EventTracking.prototype.toggledCommit = function (data) { * Reports to: * - intercom * - mixpanel + * - segment * @param {Boolean} cache - build triggered without cache * @return this */ EventTracking.prototype.triggeredBuild = function (cache) { + var self = this; var eventName = 'triggered-build'; - var eventData = this.extendEventData({ + var eventData = self.extendEventData({ cache: cache }); - this._Intercom('trackEvent', eventName, eventData); - this._mixpanel('track', eventName, eventData); - return this; + self._Intercom('trackEvent', eventName, eventData); + self._mixpanel('track', eventName, eventData); + self.analytics.ready(function () { + self.analytics.track(eventName, eventData); + }); + return self; }; /** * Record user visit to states * Reports to: * - mixpanel + * - segment * @return this */ EventTracking.prototype.visitedState = function () { + var self = this; var eventName = 'visited-state'; - var eventData = this.extendEventData({ + var eventData = self.extendEventData({ referral: _$location.search().ref || 'direct' }); - this._mixpanel('track', eventName, eventData); - return this; + self._mixpanel('track', eventName, eventData); + self.analytics.ready(function () { + self.analytics.track(eventName, eventData); + }); + return self; }; /** @@ -221,8 +293,9 @@ EventTracking.prototype.visitedState = function () { * @return this */ EventTracking.prototype.update = function () { - this._Intercom('update'); - return this; + var self = this; + self._Intercom('update'); + return self; }; /** @@ -231,8 +304,13 @@ EventTracking.prototype.update = function () { * @returns {EventTracking} */ EventTracking.prototype.trackClicked = function (data) { - this._mixpanel('track', 'clicked - ' + _keypather.get(data, 'text'), data); - return this; + var self = this; + + self._mixpanel('track', 'Click', data); + self.analytics.ready(function () { + self.analytics.track('Click', data); + }); + return self; }; /** @@ -242,19 +320,21 @@ EventTracking.prototype.trackClicked = function (data) { * @returns {EventTracking} */ EventTracking.prototype.createdRepoContainer = function (org, repo) { - if (this._mixpanel) { - this._mixpanel('track', 'createRepoContainer', { + var self = this; + if (self._mixpanel) { + self._mixpanel('track', 'createRepoContainer', { org: org, repo: repo }); } - if (this.$window.fbq) { - this.$window.fbq('track', 'ViewContent', { + self.analytics.ready(function () { + self.analytics.track('ViewContent', { action: 'CreateContainer', - type: 'Repo' + type: 'Repo', + containerName: repo }); - } + }); }; /** @@ -263,16 +343,143 @@ EventTracking.prototype.createdRepoContainer = function (org, repo) { * @returns {EventTracking} */ EventTracking.prototype.createdNonRepoContainer = function (containerName) { - if (this._mixpanel) { - this._mixpanel('track', 'createNonRepoContainer', { + var self = this; + if (self._mixpanel) { + self._mixpanel('track', 'createNonRepoContainer', { containerName: containerName }); } - if (this.$window.fbq) { - this.$window.fbq('track', 'ViewContent', { + self.analytics.ready(function () { + self.analytics.track('ViewContent', { action: 'CreateContainer', - type: 'NonRepo' + type: 'NonRepo', + containerName: containerName }); - } + }); +}; + +/** + * Track user visit to /orgSelect page + * Reports to: + * - mixpanel + * - segment + * @return this + */ +EventTracking.prototype.visitedOrgSelectPage = function () { + var self = this; + var eventName = 'Visited org-select page'; + + self._mixpanel('track', eventName); + self.analytics.ready(function () { + self.analytics.track(eventName); + }); + return self; +}; + +/** + * Track user clicks on an org on the orgSelect page + * Reports to: + * - mixpanel + * - segment + * @return this + */ +EventTracking.prototype.selectedOrg = function (org) { + var self = this; + var eventName = 'Org Selected'; + + self._mixpanel('track', eventName, { + org: org + }); + self.analytics.ready(function () { + self.analytics.track(eventName, {org: org}); + }); + return self; +}; + + +/** + * Track org click on /orgSelect page + * Reports to: + * - segment + * @return this + */ +EventTracking.prototype.waitingForInfrastructure = function (orgName) { + var self = this; + var eventName = 'Waiting for infrastrucuture'; + + self.analytics.ready(function () { + self.analytics.track(eventName, {org: orgName}); + }); + return self; +}; + +/** + * Milestone 2: Select repository + * Reports to: + * - mixpanel + * @return this + */ +EventTracking.prototype.milestone2SelectTemplate = function () { + var self = this; + var eventName = 'Milestone 2: Select template'; + + self._mixpanel('track', eventName); + return self; +}; + +/** + * Milestone 2: Verify repository tab + * Reports to: + * - mixpanel + * @return this + */ +EventTracking.prototype.milestone2VerifyRepositoryTab = function () { + var self = this; + var eventName = 'Milestone 2: Verify repository tab'; + + self._mixpanel('track', eventName); + return self; +}; + +/** + * Milestone 2: Verify commands tab + * Reports to: + * - mixpanel + * @return this + */ +EventTracking.prototype.milestone2VerifyCommandsTab = function () { + var self = this; + var eventName = 'Milestone 2: Verify commands tab'; + + self._mixpanel('track', eventName); + return self; +}; + +/** + * Milestone 2: Building + * Reports to: + * - mixpanel + * @return this + */ +EventTracking.prototype.milestone2Building = function () { + var self = this; + var eventName = 'Milestone 2: Building'; + + self._mixpanel('track', eventName); + return self; +}; + +/** + * Milestone 2: Container popover + * Reports to: + * - mixpanel + * @return this + */ +EventTracking.prototype.milestone2BuildSuccess = function () { + var self = this; + var eventName = 'Milestone 2: Build success message (in modal)'; + + self._mixpanel('track', eventName); + return self; }; diff --git a/client/services/serviceFetch.js b/client/services/serviceFetch.js index 335172e9c..7c153d47a 100644 --- a/client/services/serviceFetch.js +++ b/client/services/serviceFetch.js @@ -26,6 +26,7 @@ require('app') .factory('fetchDebugContainer', fetchDebugContainer) .factory('fetchStackData', fetchStackData) // Github API + .factory('fetchGithubUserIsAdminOfOrg', fetchGithubUserIsAdminOfOrg) .factory('fetchGitHubUser', fetchGitHubUser) .factory('fetchGitHubMembers', fetchGitHubMembers) .factory('fetchGitHubAdminsByRepo', fetchGitHubAdminsByRepo) @@ -193,6 +194,7 @@ function fetchInstance( var fetchByPodCache = {}; function fetchInstancesByPod( + $q, $state, fetchInstances, fetchUser, @@ -200,6 +202,9 @@ function fetchInstancesByPod( ) { return function (username) { username = username || $state.params.userName; + if (!username) { + return $q.when([]); + } if (!fetchByPodCache[username]) { var userPromise = fetchUser(); fetchByPodCache[username] = fetchInstances({ @@ -697,6 +702,27 @@ function fetchGitHubUser( }); } +function fetchGithubUserIsAdminOfOrg( + $http, + configAPIHost, + keypather, + memoize +) { + return memoize(function (orgName) { + return $http({ + method: 'get', + url: configAPIHost + '/github/user/memberships/orgs/' + orgName + }) + .catch(function () { + return false; + }) + .then(function (response) { + return keypather.get(response, 'data.state') === 'active' && + keypather.get(response, 'data.role') === 'admin'; + }); + }); +} + /** * Given an org name and a repo name, fetch all github users who have admin access to a repo. This * returns a promise containing a map of all of the users, indexed by their github login. diff --git a/client/services/serviceFetchDockerfile.js b/client/services/serviceFetchDockerfile.js index 1792ae9c0..1022f6474 100644 --- a/client/services/serviceFetchDockerfile.js +++ b/client/services/serviceFetchDockerfile.js @@ -56,6 +56,7 @@ function doesDockerfileExist() { } function fetchDockerfileForContextVersion ( + $q, base64, doesDockerfileExist, fetchCommitsForFile, @@ -71,18 +72,18 @@ function fetchDockerfileForContextVersion ( if (buildDockerfilePath && repoFullName) { var branchName = keypather.get(acv, 'attrs.branch'); // Get everything before the last '/' and add a '/' at the end - var result = /^(\/?[^\/]*)\/([^\/]*)$/.exec(buildDockerfilePath); - if (result.length < 3) { - throw new Error('BuilddockerfilePath is invalid'); + var result = /^((\/?[^\/]*)*)\/([^\/]*)$/.exec(buildDockerfilePath); + if (keypather.get(result, 'length') < 3) { + return $q.reject(new Error('Dockerfile path is invalid')); } - var path = result && result[1] || ''; + var name = result[result.length - 1]; + var path = result[1]; // Get everything after the last '/' - var name = result && result[2] || ''; return fetchRepoDockerfile(repoFullName, branchName, buildDockerfilePath) .then(doesDockerfileExist) .then(function (dockerfile) { if (!dockerfile) { - return null; + return $q.reject(new Error('No Dockerfile in this repo')); } return fetchCommitsForFile(repoFullName, branchName, buildDockerfilePath) .then(function (commits) { diff --git a/client/services/serviceHandleErr.js b/client/services/serviceHandleErr.js index def9fa892..a4ba0e8eb 100644 --- a/client/services/serviceHandleErr.js +++ b/client/services/serviceHandleErr.js @@ -24,6 +24,10 @@ function errs ( $window.location = configAPIHost + '/auth/github?redirect=' + $window.location.protocol + '//' + $window.location.host + '/?auth'; return false; } + if (err.message === 'collection requires a client') { + // Fuck this error + return false; + } if (~noDisplayCodes.indexOf(keypather.get(err, 'data.statusCode'))) { return false; } return true; diff --git a/client/services/serviceHelpCards.js b/client/services/serviceHelpCards.js deleted file mode 100644 index 3fadb0902..000000000 --- a/client/services/serviceHelpCards.js +++ /dev/null @@ -1,312 +0,0 @@ -'use strict'; -var EventEmitter = require('events').EventEmitter; - -require('app') - .factory('helpCards', helpCardsFactory); - -function helpCardsFactory( - $interpolate, - $q, - keypather, - fetchSettings, - errs, - promisify, - $rootScope, - jsonHash -) { - -//POSSIBLE TARGETS: -//repository -//exposedPorts -//environmentVariables -//commands -//containerFiles -//findAndReplace -//newContainer -//buildCommand - var helpCards = { - 'general': [ - { - label: 'Connect to an external service', - targets: [ - 'environmentVariables', - 'findAndReplace' - ], - helpTop: 'Use Environment Variables or Find and Replace to connect to a service.', - helpPopover: { - 'environmentVariables': 'Add or update an environment variable to reference your external service.', - 'findAndReplace': 'Create a new string rule to connect to your external service.' - } - }, - { - label: 'Connect to an OAuth service', - targets: [ - 'environmentVariables', - 'findAndReplace' - ], - helpTop: 'Use Environment Variables or Find and Replace to update your OAuth credentials.', - helpPopover: { - 'environmentVariables': 'Add or update the environment variables for your OAuth credentials.', - 'findAndReplace': 'Add a string rule to update your OAuth credentials in your code.' - } - }, - { - label: 'Add a library', - targets: [ - 'commands', - ], - helpTop: 'Use Commands and Packages to add a library.', - }, - { - label: 'Seed a database', - targets: ['containerFiles'], - helpTop: 'Use Files & SSH Keys to upload seed data and import it using scripts.', - helpPopover: { - 'containerFiles': 'Click Upload File to select and upload your seed file. Then enter the scripts you need to import the data.' - } - } - ], - 'triggered': [ - // when we detect that one existing container depends on service for which there is no existing container - { - id: 'missingDependency', - label: '{{instance.getDisplayName()}} may need a {{dependency}} container.', - targets: [ - 'newContainer' - ], - helpTop: 'Click on the New Container button to add a {{dependency}} container.', - helpPopover: { - 'newContainer': 'Click Non-repository to add a {{dependency}} container.' - } - }, - // when we detect that one existing container depends on another existing contianer - { - id: 'missingAssociation', - label: 'You may need to connect {{instance.getDisplayName()}} to {{association}}.', - targets: [ - 'environmentVariables', - 'findAndReplace' - ], - helpTop: 'Use Environment Variables or Find and Replace to connect {{instance.getDisplayName()}} to {{association}}.', - helpPopover: { - 'environmentVariables': 'Connect to {{association}} by using its URL in an environment variable.', - 'findAndReplace': 'Connect to {{association}} by adding a string rule with its URL.' - } - }, - // when the user adds a non-repo container, but we can't detect which containers depend on it - { - id: 'missingMapping', - label: 'You may need to connect some repository containers to {{mapping}}.', - targets: [ - 'environmentVariables', - 'findAndReplace' - ], - helpTop: 'Use Environment Variables or Find and Replace to connect one or more of your repository containers to {{mapping}}.', - helpPopover: { - environmentVariables: 'Connect to {{mapping}} by using its URL in an environment variable.', - findAndReplace: 'Connect to {{mapping}} by adding a string rule with its URL.' - }, - highlightRepoContainers: true - } - ] - }; - - - - var HelpCard = function (config) { - var self = this; - Object.keys(config).forEach(function (key) { - self[key] = config[key]; - }); - - var cardClone = { - id: this.id, - type: this.type, - data: {} - }; - if (this.data && this.data.instance && this.data.instance.attrs) { - cardClone.data = { instance: this.data.instance.attrs.shortHash }; - } - - if (this.data) { - Object.keys(this.data).forEach(function (key) { - if (key !== 'instance') { - cardClone.data[key] = self.data[key]; - } - }); - } - this.hash = jsonHash.digest(cardClone); - }; - - HelpCard.prototype = Object.create(EventEmitter.prototype); - - helpCards.general = helpCards.general.map(function (cardConfig) { - cardConfig.type = 'general'; - var card = new HelpCard(cardConfig); - var targetHash = {}; - card.targets.forEach(function (target) { - if (keypather.get($rootScope, 'featureFlags.' + target) !== false) { - targetHash[target] = true; - } - }); - card.targets = targetHash; - return card; - }); - - var triggeredHash = {}; - helpCards.triggered.forEach(function (cardConfig) { - cardConfig.type = 'triggered'; - var card = new HelpCard(cardConfig); - var targetHash = {}; - card.targets.forEach(function (target) { - if (keypather.get($rootScope, 'featureFlags.' + target) !== false) { - targetHash[target] = true; - } - }); - card.targets = targetHash; - triggeredHash[card.id] = card; - }); - - helpCards.triggered = triggeredHash; - - - var currentCardHash = {}; - var activeCard = null; - var helpCardManager = { - cards: { - general: helpCards.general, - triggered: [] - }, - getActiveCard: function () { - return activeCard; - }, - setActiveCard: function (newCard) { - if (activeCard && activeCard !== newCard) { - activeCard.emit('deactivate'); - } - - if (newCard) { - newCard.emit('activate'); - $rootScope.$broadcast('helpCardScroll:enable'); - } else { - $rootScope.$broadcast('helpCardScroll:disable'); - } - - activeCard = newCard; - }, - clearAllCards: function () { - this.cards.triggered = []; - currentCardHash = {}; - activeCard = null; - }, - hideActiveCard: function () { - if (this.getActiveCard()) { - var helpCard = this.getActiveCard(); - currentCardHash[helpCard.hash] = null; - this.setActiveCard(null); - var index = this.cards.triggered.indexOf(helpCard); - this.cards.triggered.splice(index, 1); - } - }, - refreshActiveCard: function () { - if (this.getActiveCard()) { - this.getActiveCard().emit('refresh'); - this.setActiveCard(null); - } - }, - refreshAllCards: function () { - this.cards.triggered.forEach(function (card) { - if (!card.removed) { - card.emit('refresh'); - } - }); - currentCardHash = {}; - this.cards.triggered = []; - this.setActiveCard(null); - }, - cardIsActiveOnThisContainer: function (container) { - activeCard = this.getActiveCard(); - - return activeCard && - ( - activeCard.type === 'general' || - angular.equals(container, keypather.get(activeCard, 'data.instance')) || - ( activeCard.highlightRepoContainers && container.contextVersion.getMainAppCodeVersion() ) - ); - }, - removeByInstance: function (instance) { - this.cards.triggered - .filter(function (card) { - return keypather.get(card, 'data.instance.attrs.shortHash') === instance.attrs.shortHash; - }) - .forEach(function (card) { - if (!card.removed) { - card.emit('remove'); - } - }); - }, - triggerCard: function (cardId, data) { - var self = this; - return fetchSettings().then(function (settings) { - var ignoredHelpCards = settings.attrs.ignoredHelpCards || []; - - var cardConfig = helpCards.triggered[cardId]; - if (!cardConfig) { - throw new Error('Attempt to create a help card with invalid ID.'); - } - cardConfig = angular.copy(cardConfig); - - - cardConfig.label = $interpolate(cardConfig.label)(data); - cardConfig.helpTop = $interpolate(cardConfig.helpTop)(data); - Object.keys(cardConfig.helpPopover).forEach(function (key) { - cardConfig.helpPopover[key] = $interpolate(cardConfig.helpPopover[key])(data); - }); - - cardConfig.data = data; - - var helpCard = new HelpCard(cardConfig); - - if (!currentCardHash[helpCard.hash] && ignoredHelpCards.indexOf(helpCard.hash) === -1) { - self.cards.triggered.unshift(helpCard); - currentCardHash[helpCard.hash] = helpCard; - helpCard.on('remove', function () { - if (self.getActiveCard() === helpCard) { - self.setActiveCard(null); - } - helpCard.removed = true; - var index = self.cards.triggered.indexOf(helpCard); - self.cards.triggered.splice(index, 1); - currentCardHash[helpCard.hash] = null; - }); - helpCard.on('refresh', function () { - if (!helpCard.removed) { - helpCard.emit('remove'); - } - }); - } - return $q.when(currentCardHash[helpCard.hash]); - }) - .catch(errs.handler); - }, - ignoreCard: function (card) { - var index = this.cards.triggered.indexOf(card); - this.cards.triggered.splice(index, 1); - if (this.getActiveCard() === card) { - this.setActiveCard(null); - } - fetchSettings().then(function (settings) { - var ignoredHelpCards = settings.attrs.ignoredHelpCards || []; - ignoredHelpCards.push(card.hash); - - return promisify(settings, 'update')({ - json: { - ignoredHelpCards: ignoredHelpCards - } - }); - }) - .catch(errs.handler); - } - }; - return helpCardManager; -} diff --git a/client/services/serviceValidateEnvVars.js b/client/services/serviceValidateEnvVars.js index c116b5bb7..807eb9b8b 100644 --- a/client/services/serviceValidateEnvVars.js +++ b/client/services/serviceValidateEnvVars.js @@ -27,7 +27,7 @@ function validateEnvVars() { return; } // Check for syntactic validity - if (!/^([A-z]+[A-z0-9_]*)=.*$/.test(line)) { + if (!/^([A-z]+[A-z0-9_]*)=\S+$/.test(line)) { response.valid = false; response.errors.push(index); return; diff --git a/client/services/tabVisibilityService.js b/client/services/tabVisibilityService.js index 1a06e4a36..c1e561c85 100644 --- a/client/services/tabVisibilityService.js +++ b/client/services/tabVisibilityService.js @@ -21,7 +21,6 @@ require('app') advanced: true, basic: true, mirror: true, - featureFlagName: 'whitelist', nonRepo: true, step: 3 }, @@ -47,8 +46,8 @@ require('app') step: 3 }, buildfiles: { - basic: true, advanced: true, + basic: true, mirror: true, nonRepo: true, step: 3 diff --git a/client/templates/instances/viewInstances.jade b/client/templates/instances/viewInstances.jade index 5e1620072..6002f91c9 100644 --- a/client/templates/instances/viewInstances.jade +++ b/client/templates/instances/viewInstances.jade @@ -1,7 +1,7 @@ //- instance list .grid-block.shrink.list-instances( ng-class = "{\ - 'deprecated': !$root.featureFlags.autoIsolation,\ + 'deprecated': !$root.featureFlags.addBranches,\ 'in': !CIS.$storage.instanceListIsClosed\ }" ng-if = "CIS.instancesByPod" @@ -20,6 +20,5 @@ ) .grid-block.vertical.instance-wrapper( - ng-class = "{'empty': $root.featureFlags.aha && $root.featureFlags.aha2}" ui-view ) diff --git a/client/templates/instances/viewInstancesList.jade b/client/templates/instances/viewInstancesList.jade index d20ae562a..d7a786939 100644 --- a/client/templates/instances/viewInstancesList.jade +++ b/client/templates/instances/viewInstancesList.jade @@ -8,15 +8,32 @@ label.grid-block.vertical.label-search ) p.p.text-center.text-gray-light.padding-sm( - ng-if = "CIS.getFilteredInstanceList().length < 1" -) No containers match filter. + ng-if = "!CIS.filterMatchedAnything()" +) No containers match this filter. //- master cluster .list-clusters.master-cluster( - ng-if = "$root.featureFlags.autoIsolation" + ng-if = "$root.featureFlags.addBranches && CIS.getFilteredInstanceList().length" ) - .list-item-cluster + .grid-block.align-center.list-item-cluster.list-clusters-heading + span.grid-content.text-overflow Templates + //- div instead of button so safari doesn’t get weird with alignment + .grid-block.align-center.shrink.btn.btn-xxs.white( + ng-class = "{'active': state.active}" + ng-if = "$root.featureFlags.containersViewTemplateControls" + pop-over + pop-over-options = "{\"top\":-30,\"left\":114}" + pop-over-template = "templateMenuPopoverView" + ) Create Template + svg.iconnables.icons-arrow-forward + use( + xlink:href = "#icons-arrow-down" + ) + + .list-item-cluster( + ng-if = "!$root.featureFlags.containersViewEmptyState" + ) .grid-block.list-containers.vertical.text-overflow.open //- master repository containers .grid-block( @@ -24,18 +41,25 @@ p.p.text-center.text-gray-light.padding-sm( instance = "masterInstance" instance-navigation master-instance = "masterInstance" - ng-repeat = "masterInstance in CIS.instancesByPod.models | instanceHasRepo:true | orderBy: ['attrs.name'] track by masterInstance.attrs.name" + ng-repeat = "masterInstance in CIS.getFilteredInstanceList() | instanceHasRepo:true | orderBy: ['attrs.name'] track by masterInstance.attrs.name" ) //- master non-repository containers .grid-block( active-account = "CIS.activeAccount" instance = "masterInstance" instance-navigation - ng-repeat = "masterInstance in CIS.instancesByPod.models | instanceHasRepo:false | orderBy: ['attrs.name'] as nonRepoInstances track by masterInstance.attrs.name" + ng-repeat = "masterInstance in CIS.getFilteredInstanceList() | instanceHasRepo:false | orderBy: ['attrs.name'] as nonRepoInstances track by masterInstance.attrs.name" ) +.well.text-center( + ng-if = "$root.featureFlags.containersViewEmptyState" +) 😐 + br + .small You don’t have any templates. Click the button above to create one. + //- branch clusters .list-clusters( + ng-if = "!$root.featureFlags.containersViewEmptyState" ng-repeat = "masterInstance in CIS.instancesByPod | instanceHasRepo:true | orderBy: ['attrs.name'] track by masterInstance.attrs.name" ng-show = "CIS.shouldShowParent(masterInstance)" ) @@ -43,28 +67,63 @@ p.p.text-center.text-gray-light.padding-sm( //- cluster list heading .grid-block.align-center.list-item-cluster.list-clusters-heading //- repository name - span.grid-block.text-overflow( + span.grid-content.text-overflow( title = "{{masterInstance.getName()}}" ) {{masterInstance.getName()}} - //- '+' button for adding branches and configuring clusters - button.grid-block.shrink.btn.btn-xs.btn-icon.gray( - ng-class = "{'active': state.active}" - ng-if = "$root.featureFlags.autoIsolation" + .grid-block.align-center.shrink.btn.btn-xxs.white( + ng-if = "$root.featureFlags.addBranches" + ng-click = "CIS.popInstanceOpen(masterInstance)" pop-over - pop-over-options = "{\"verticallyCentered\":true,\"left\":24}" + pop-over-controller = "CIS" + pop-over-options = "{\"verticallyCentered\":true,\"left\":87,\"pinToViewPort\":true}" pop-over-template = "branchMenuPopoverView" - tooltip = "Add Branch" - tooltip-options = "{\"class\":\"bottom center text-center\",\"left\":-36,\"top\":24}" - ) - svg.iconnables + pop-over-data = "'branchSelect'" + ) Add Branch + //- '+' button for adding branches and configuring clusters + svg.iconnables.icons-arrow-forward use( - xlink:href = "#icons-add" + xlink:href = "#icons-arrow-down" ) + .stepOneUncloseablePopover( + ng-if = "$root.featureFlags.aha &&\ + $index === 0 &&\ + CIS.shouldShowPopover &&\ + !CIS.$storage.instanceListIsClosed &&\ + CIS.shouldShowParent(masterInstance) &&\ + (CIS.isAddingFirstBranch() && !masterInstance.attrs.hasAddedBranches)\ + " + pop-over + pop-over-active = "CIS.shouldShowPopover" + pop-over-controller = "CIS" + pop-over-options = "{\"verticallyCentered\":true,\"left\":6}" + pop-over-template = "introAddBranch" + pop-over-trigger = "activeAttr" + pop-over-uncloseable = "CIS.isAddingFirstBranch()" + pop-over-data = "'ahaTemplate'" + ) + + .autoForkPopover( + ng-if = "$root.featureFlags.aha &&\ + $index === 0 &&\ + !CIS.$storage.instanceListIsClosed &&\ + CIS.shouldShowParent(masterInstance) &&\ + !CIS.isAddingFirstBranch() &&\ + (CIS.showAutofork && masterInstance.attrs.shouldNotAutofork)\ + " + pop-over + pop-over-active = "CIS.showAutofork" + pop-over-controller = "CIS" + pop-over-options = "{\"verticallyCentered\":true,\"left\":6}" + pop-over-template = "introAddBranch" + pop-over-trigger = "activeAttr" + pop-over-data = "'ahaTemplate'" + ) + //- repo config button (pre auto-isolation) svg.grid-block.shrink.iconnables.icons-gear( ng-click = "CIS.editInstance(masterInstance)" - ng-if = "!$root.featureFlags.autoIsolation" - title = "Configure" + ng-if = "!$root.featureFlags.addBranches" + title = "Configure Template" ) use( xlink:href = "#icons-gear" @@ -76,7 +135,7 @@ p.p.text-center.text-gray-light.padding-sm( instance = "masterInstance" instance-navigation master-instance = "masterInstance" - ng-if = "!$root.featureFlags.autoIsolation && CIS.filterMasterInstance(masterInstance)" + ng-if = "!$root.featureFlags.addBranches && CIS.filterMasterInstance(masterInstance)" ) //- wraps auto-isolated clusters @@ -99,7 +158,7 @@ p.p.text-center.text-gray-light.padding-sm( //- non-repo containers (pre auto-isolation) .list-clusters( - ng-if = "!$root.featureFlags.autoIsolation" + ng-if = "!$root.featureFlags.addBranches" ng-show = "nonRepoInstances.length" ) .list-item-cluster( diff --git a/client/templates/svg/svgDefs.jade b/client/templates/svg/svgDefs.jade index c26a9f3b8..c07ac9a3f 100755 --- a/client/templates/svg/svgDefs.jade +++ b/client/templates/svg/svgDefs.jade @@ -13,6 +13,9 @@ svg(xmlns='http://www.w3.org/2000/svg') path(d='M10,8.006l-0.032,6A0.982,0.982,0,0,1,9.005,15H8.995a0.982,0.982,0,0,1-.962-0.994L8,8.006A0.982,0.982,0,0,1,8.962,7H9.038A0.982,0.982,0,0,1,10,8.006Z', transform='translate(0 -1.123)') symbol#icons-alert-alt(viewBox='0 0 18 15.877') path(d='M17.853,15.306L13.9,8.456,9.943,1.606a1.088,1.088,0,0,0-1.885,0L4.1,8.456l-3.955,6.85a1.088,1.088,0,0,0,.943,1.633H16.91A1.088,1.088,0,0,0,17.853,15.306ZM10,8.006l-0.032,6A0.982,0.982,0,0,1,9.005,15H8.995a0.982,0.982,0,0,1-.962-0.994L8,8.006A0.982,0.982,0,0,1,8.962,7H9.038A0.982,0.982,0,0,1,10,8.006Z', transform='translate(0 -1.061)') + symbol#icons-alert-alt-round(viewBox='0 0 18 18') + circle(cx='9', cy='9', r='9', fill='none') + path(d='M15.885,13.9L12.809,8.577,9.733,3.249a0.846,0.846,0,0,0-1.466,0L5.191,8.577,2.115,13.9a0.846,0.846,0,0,0,.733,1.27h12.3A0.846,0.846,0,0,0,15.885,13.9ZM9.778,8.227L9.753,12.893A0.763,0.763,0,0,1,9,13.667H9a0.763,0.763,0,0,1-.749-0.773L8.222,8.227a0.764,0.764,0,0,1,.749-0.782H9.029A0.764,0.764,0,0,1,9.778,8.227Z', transform='translate(0)') symbol#icons-alert-round(viewBox='0 0 30 30') path(d='M15,5c5.5,0,10,4.5,10,10s-4.5,10-10,10S5,20.5,5,15S9.5,5,15,5 M15,3C8.4,3,3,8.4,3,15s5.4,12,12,12s12-5.4,12-12S21.6,3,15,3L15,3z') path(d='M15,18.3c-0.6,0-1-0.4-1-1V9.5c0-0.6,0.4-1,1-1s1,0.4,1,1v7.8C16,17.8,15.6,18.3,15,18.3z') @@ -159,8 +162,6 @@ svg(xmlns='http://www.w3.org/2000/svg') path(d='M8.4,9.1c0.1,0,0.3,0,0.4,0.1c0.2,0.2,0.2,0.5,0,0.7l-4,4H8c0.3,0,0.5,0.2,0.5,0.5S8.3,15,8,15H3.5h0c0,0,0,0,0,0h0c0,0,0,0,0,0l0,0c0,0,0,0,0,0l0,0c-0.1,0-0.2,0-0.3-0.1c0,0,0,0,0,0v0c0,0,0,0-0.1-0.1C3,14.7,3,14.6,3,14.5c0,0,0,0,0,0V10c0-0.3,0.2-0.5,0.5-0.5S4,9.7,4,10v3.3l4-4C8.1,9.2,8.2,9.1,8.4,9.1z') symbol#icons-fat-check(viewBox='0 0 30 30') path(d='M24.3,9.2c-0.6-0.6-1.5-0.6-2.1,0l-8.5,8.5l-4.3-4.3c-0.6-0.6-1.5-0.6-2.1,0c-0.6,0.6-0.6,1.5,0,2.1l5.4,5.4c0.3,0.3,0.6,0.4,1.1,0.4c0.4,0,0.8-0.2,1.1-0.4l9.6-9.6C24.8,10.8,24.8,9.8,24.3,9.2z') - symbol#icons-github(viewBox='0 0 30 30') - path(d='M22.4,7.8c2,2,3,4.5,3.1,7.4c0,2.4-0.7,4.4-2,6.2c-1.3,1.8-3,3-5.1,3.8c-0.3,0-0.4,0-0.6-0.1c-0.1-0.1-0.2-0.2-0.2-0.4l0-2.9c0-0.5-0.1-0.9-0.2-1.2c-0.1-0.3-0.3-0.6-0.5-0.7c1.2-0.1,2.3-0.5,3.3-1.2c1-0.7,1.5-2,1.5-4c0-0.6-0.1-1.1-0.3-1.6c-0.2-0.5-0.4-0.9-0.8-1.3c0.1-0.1,0.2-0.5,0.2-0.9c0.1-0.5,0-1.1-0.3-1.8c0,0-0.2,0-0.7,0c-0.5,0.1-1.2,0.4-2.2,1.1C16.8,10,15.9,9.8,15,9.8c-0.9,0-1.8,0.1-2.6,0.3c-1-0.6-1.7-1-2.2-1.1c-0.5-0.1-0.7-0.1-0.7,0C9.2,9.8,9.1,10.5,9.2,11c0.1,0.5,0.1,0.8,0.2,0.9c-0.3,0.4-0.6,0.8-0.8,1.3c-0.2,0.5-0.3,1-0.3,1.6c0.1,2,0.6,3.3,1.5,4c1,0.7,2.1,1.1,3.3,1.2c-0.2,0.1-0.3,0.3-0.4,0.6c-0.1,0.2-0.2,0.5-0.3,0.9c-0.3,0.2-0.8,0.3-1.4,0.3c-0.6,0-1.2-0.4-1.7-1.1c0,0-0.1-0.2-0.4-0.5c-0.3-0.3-0.7-0.5-1.2-0.6c-0.1,0-0.2,0-0.4,0.1c-0.2,0.1-0.1,0.3,0.3,0.6c0,0,0.1,0.1,0.4,0.3c0.3,0.2,0.5,0.6,0.8,1.2c0,0.1,0.2,0.4,0.7,0.9c0.5,0.5,1.4,0.6,2.9,0.4l0,1.9c0,0.2-0.1,0.3-0.2,0.4c-0.1,0.1-0.3,0.2-0.6,0.1c-2.1-0.7-3.8-2-5.1-3.8c-1.3-1.8-2-3.8-2-6.2c0.1-3,1.1-5.5,3.1-7.4c2-2,4.5-3,7.4-3.1C18,4.8,20.5,5.8,22.4,7.8z') symbol#icons-help(viewBox='0 0 16.4 16.8') path(d='M0,14.6c0-1.3,0.9-2.2,2.2-2.2c1.3,0,2.1,0.9,2.1,2.2c0,1.3-0.8,2.2-2.1,2.2C0.9,16.8,0,15.8,0,14.6z M0.8,11.2L0.2,0.3h3.8L3.5,11.2H0.8z') path(d='M9.9,11.3l0-0.6c-0.1-1.2,0.3-2.5,1.4-3.8c0.8-0.9,1.4-1.7,1.4-2.5c0-0.8-0.6-1.4-1.8-1.4c-0.8,0-1.8,0.3-2.4,0.7L7.8,1c0.9-0.5,2.3-1,4-1c3.2,0,4.6,1.8,4.6,3.8c0,1.8-1.1,3-2,4.1c-0.9,1-1.3,1.9-1.2,3v0.4H9.9z M9.3,14.6c0-1.3,0.9-2.2,2.1-2.2c1.3,0,2.1,0.9,2.2,2.2c0,1.3-0.9,2.2-2.2,2.2C10.2,16.8,9.3,15.8,9.3,14.6z') @@ -222,19 +223,20 @@ svg(xmlns='http://www.w3.org/2000/svg') path(d='M3.059,11.928c0.479-0.479,0.522-1.071-0.436-2.029s-1.55-0.915-2.029-0.436c-1,1.052-0.436,2.9-0.436,2.9S2.033,12.954,3.059,11.928z') path(d='M12.523,0C10.605,0,9.009,0.668,5.8,2.565c-2.433-0.818-3.983-0.344-5.298,0.97l1.857,1.857c-1.385,1.385-1.15,1.679,0.972,3.8c1.945,1.945,2.249,2.249,3.663,0.835l1.994,1.994c1.358-1.358,1.814-2.947,0.891-5.528C11.734,3.311,12.523,2.102,12.523,0z') symbol#icons-life-preserver(viewBox='0 0 18 18') - path(d='M16,9a7,7,0,1,0-7,7A7,7,0,0,0,16,9ZM4.8,9A4.2,4.2,0,1,1,9,13.2,4.2,4.2,0,0,1,4.8,9Z', transform='translate(-1.5 -1.5)', fill='#ffffff', stroke='#c4c4c4', stroke-miterlimit='10') - path(d='M4.316,3.134H5.685a0.673,0.673,0,0,1,.673.673V6.191a0.675,0.675,0,0,1-.675.675H4.316a0.673,0.673,0,0,1-.673-0.673V3.807a0.673,0.673,0,0,1,.673-0.673Z', transform='translate(10.572 3.5) rotate(135)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') - path(d='M12.515,3.134h1.369a0.673,0.673,0,0,1,.673.673V6.191a0.675,0.675,0,0,1-.675.675H12.515a0.673,0.673,0,0,1-.673-0.673V3.807A0.673,0.673,0,0,1,12.515,3.134Z', transform='translate(5.902 -9.369) rotate(45)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') - path(d='M4.316,11.334H5.685a0.673,0.673,0,0,1,.673.673v2.384a0.675,0.675,0,0,1-.675.675H4.316a0.673,0.673,0,0,1-.673-0.673V12.007a0.673,0.673,0,0,1,.673-0.673Z', transform='translate(-2.297 24.569) rotate(-135)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') - path(d='M12.515,11.334h1.369a0.673,0.673,0,0,1,.673.673v2.384a0.675,0.675,0,0,1-.675.675H12.515a0.673,0.673,0,0,1-.673-0.673V12.007A0.673,0.673,0,0,1,12.515,11.334Z', transform='translate(-6.967 11.7) rotate(-45)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') + path(d='M16,9a7,7,0,1,0-7,7A7,7,0,0,0,16,9ZM4.8,9A4.2,4.2,0,1,1,9,13.2,4.2,4.2,0,0,1,4.8,9Z', transform='translate(0 0)', fill='#fff', stroke='#c4c4c4', stroke-miterlimit='10') + path(d='M4.316,3.134H5.685a0.673,0.673,0,0,1,.673.673V6.191a0.675,0.675,0,0,1-.675.675H4.316a0.673,0.673,0,0,1-.673-0.673V3.807a0.673,0.673,0,0,1,.673-0.673Z', transform='translate(12.072 5) rotate(135)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') + path(d='M12.515,3.134h1.369a0.673,0.673,0,0,1,.673.673V6.191a0.675,0.675,0,0,1-.675.675H12.515a0.673,0.673,0,0,1-.673-0.673V3.807a0.673,0.673,0,0,1,.673-0.673Z', transform='translate(7.402 -7.869) rotate(45)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') + path(d='M4.316,11.334H5.685a0.673,0.673,0,0,1,.673.673v2.384a0.675,0.675,0,0,1-.675.675H4.316a0.673,0.673,0,0,1-.673-0.673V12.007a0.673,0.673,0,0,1,.673-0.673Z', transform='translate(-0.797 26.07) rotate(-135)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') + path(d='M12.515,11.334h1.369a0.673,0.673,0,0,1,.673.673v2.384a0.675,0.675,0,0,1-.675.675H12.515a0.673,0.673,0,0,1-.673-0.673V12.007A0.673,0.673,0,0,1,12.515,11.334Z', transform='translate(-5.468 13.2) rotate(-45)', fill='#ef7883', stroke='#c14444', stroke-miterlimit='10') + rect(width='18', height='18', fill='none') symbol#icons-lightning(viewBox='0 0 10.692 17.006') path(d='M9.643,7.053C9.557,6.886,9.386,6.782,9.199,6.782l-2.144,0l2.604-6.084c0.065-0.155,0.05-0.332-0.043-0.472C9.524,0.084,9.367,0,9.199,0H2.355c-0.237,0-0.442,0.167-0.49,0.399L0.01,9.405c-0.03,0.147,0.007,0.3,0.102,0.417C0.208,9.938,0.35,10.006,0.5,10.006h1.731l-0.585,6.417c-0.039,0.229,0.086,0.456,0.302,0.545c0.062,0.025,0.127,0.038,0.191,0.038c0.158,0,0.312-0.075,0.407-0.21l7.059-9.224C9.714,7.419,9.729,7.219,9.643,7.053z') symbol#icons-link(viewBox='0 0 18 18') path(d='M3.8,17.8L3.8,17.8c-0.7,0-1.4-0.3-1.8-0.8l-1.2-1.2C0.3,15.4,0,14.7,0,14c0-0.7,0.3-1.4,0.8-1.9l3.9-3.9c0.3-0.3,0.9-0.3,1.2,0c0.3,0.3,0.3,0.9,0,1.2l-3.9,3.9c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l1.2,1.2c0.2,0.2,0.4,0.3,0.7,0.3l0,0c0.3,0,0.5-0.1,0.7-0.3L8.4,12c0.3-0.3,0.9-0.3,1.2,0c0.3,0.3,0.3,0.9,0,1.2l-3.9,3.9C5.2,17.6,4.5,17.8,3.8,17.8z') path(d='M9.1,9.8c-0.2,0-0.4-0.1-0.6-0.2c-0.3-0.3-0.3-0.9,0-1.2l3.9-3.9c0.4-0.4,0.4-1,0-1.3l-1.2-1.2c-0.4-0.4-1-0.4-1.3,0L5.9,5.9c-0.3,0.3-0.9,0.3-1.2,0C4.4,5.5,4.4,5,4.7,4.7l3.9-3.9c1-1,2.7-1,3.7,0l1.2,1.2c1,1,1,2.7,0,3.7L9.6,9.6C9.5,9.7,9.3,9.8,9.1,9.8z') symbol#icons-link-external(viewBox='0 0 18 18') - path(d='M8.5,7C8.4,7,8.2,7,8.1,6.9C8,6.7,8,6.3,8.1,6.1L13.3,1H10C9.7,1,9.5,0.8,9.5,0.5S9.7,0,10,0h4.5h0c0,0,0,0,0,0h0c0,0,0,0,0,0l0,0c0,0,0,0,0,0l0,0c0.1,0,0.2,0,0.3,0.1c0,0,0,0,0,0v0c0,0,0,0,0.1,0.1C15,0.3,15,0.4,15,0.5c0,0,0,0,0,0V5c0,0.3-0.2,0.5-0.5,0.5S14,5.3,14,5V1.7L8.9,6.9C8.8,7,8.6,7,8.5,7z') - path(d='M12.3,6.2L12,6.5V12c0,1.1-0.9,2-2,2H3c-1.1,0-2-0.9-2-2V5c0-1.1,0.9-2,2-2h5.5l0.3-0.3C8.5,2.5,8.2,2.3,8,2H3C1.3,2,0,3.3,0,5v7c0,1.7,1.3,3,3,3h7c1.7,0,3-1.3,3-3V7C12.7,6.8,12.5,6.5,12.3,6.2z') + path(d='M16.5,1.965A0.5,0.5,0,0,0,16.4,1.7a0.56,0.56,0,0,0-.07-0.074h0a0.5,0.5,0,0,0-.32-0.121H11.5a0.5,0.5,0,0,0,0,1h3.293L9.646,7.646a0.5,0.5,0,1,0,.707.707L15.5,3.207V6.5a0.5,0.5,0,0,0,1,0V2C16.5,1.991,16.5,1.978,16.5,1.965Z') + path(d='M13.817,7.718L13.5,8.035V13.5a2,2,0,0,1-2,2h-7a2,2,0,0,1-2-2v-7a2,2,0,0,1,2-2H9.965l0.317-.317A2.5,2.5,0,0,1,9.527,3.5H4.5a3,3,0,0,0-3,3v7a3,3,0,0,0,3,3h7a3,3,0,0,0,3-3V8.473A2.5,2.5,0,0,1,13.817,7.718Z') symbol#icons-lock(viewBox='0 0 15.725 17') path(d='M2.249,8a1.136,1.136,0,0,0-1.1,1.141V15.9a1.1,1.1,0,0,0,1.1,1.1H15.774a1.1,1.1,0,0,0,1.1-1.1V9.141A1.136,1.136,0,0,0,15.774,8H2.249Z', transform='translate(-1.149)', style='fill:#d8a956') path(d='M15.774,9.125l-0.025.016,0.025,6.734-13.5.025L2.249,9.125H15.774m0-1.125H2.249a1.136,1.136,0,0,0-1.1,1.141V15.9a1.1,1.1,0,0,0,1.1,1.1H15.774a1.1,1.1,0,0,0,1.1-1.1V9.141A1.136,1.136,0,0,0,15.774,8h0Z', transform='translate(-1.149)', style='fill:#ad8850') @@ -289,14 +291,12 @@ svg(xmlns='http://www.w3.org/2000/svg') symbol#icons-redirect(viewBox='0 0 16 35.173') path(d='M16,2V0C7.163,0,0,7.163,0,16c0,6.937,4.421,12.826,10.595,15.042l-4.764,2.224c-0.5,0.233-0.717,0.828-0.483,1.329c0.144,0.31,0.429,0.511,0.743,0.563c0.193,0.032,0.397,0.009,0.587-0.08l6.859-3.202c0.493-0.23,0.711-0.811,0.492-1.308l-3.046-6.93c-0.224-0.505-0.813-0.734-1.318-0.513c-0.505,0.223-0.735,0.812-0.513,1.318l2.064,4.695C5.848,27.179,2,22.037,2,16C2,8.28,8.28,2,16,2z') symbol#icons-repository(viewBox='0 0 18 18') - path(fill='#E6E6E6', d='M3.371,16.534c-1.213,0-2.905-0.66-2.905-1.734V13c0-1.442,2.23-1.484,2.484-1.484h10.628c0.4,0,0.419-0.001,0.604-0.012c0.128-0.007,0.92-0.041,0.92-0.041l0.078-0.034c0.126-0.053,0.243-0.114,0.353-0.185V14c0,0.941-0.323,2.534-1.534,2.534H3.371z') - path(fill='#808080', d='M15.068,11.93V14c0,0.831-0.284,2.068-1.068,2.068H3.371c-1.098,0-2.439-0.585-2.439-1.268V13c0-1.007,1.936-1.018,2.018-1.018H13.58c0.419,0,0.437-0.001,0.632-0.012c0.127-0.007,0.337-0.019,0.825-0.038L15.068,11.93M16,9.697c-0.069,0.66-0.484,1.085-1,1.303c-1.188,0.047-0.759,0.049-1.42,0.049H2.951C1.531,11.049,0,11.581,0,13v1.8C0,16.219,1.952,17,3.371,17H14c1.419,0,2-1.581,2-3V9.697L16,9.697z') - path(fill='#B3B3B3', d='M0.466,3c0.004-1.278,1.255-2.534,2.524-2.534h10.134c1.316,0.005,2.385,1.109,2.38,2.462L15.478,9.86c-0.11,0.366-0.365,0.567-0.588,0.678c-0.439,0.018-0.636,0.028-0.758,0.035c-0.17,0.01-0.187,0.011-0.553,0.011H2.951c-1.009,0-1.878,0.239-2.484,0.67V3z') - path(fill='#808080', d='M2.99,0.932l0.01,0h10.121c1.061,0.004,1.921,0.898,1.917,1.993l-0.027,6.858c-0.033,0.084-0.101,0.198-0.25,0.293c-0.364,0.015-0.54,0.025-0.652,0.031c-0.164,0.009-0.178,0.009-0.529,0.009H2.951c-0.753,0-1.44,0.125-2.018,0.36V3.003C0.936,1.888,2.067,0.932,2.99,0.932 M2.99,0C1.521,0,0.006,1.392,0,3v9h0.347c0.529-0.672,1.59-0.951,2.604-0.951H13.58c0.662,0,0.233-0.003,1.42-0.049c0.451-0.191,0.81-0.551,0.943-1.076l0.028-6.994c0.006-1.612-1.268-2.923-2.846-2.93H3C2.997,0,2.994,0,2.99,0L2.99,0z') - path(fill='#999999', d='M0.466,3C0.47,1.876,1.438,0.77,2.534,0.519v10.082c-0.842,0.061-1.552,0.288-2.067,0.654V3z') - path(fill='#808080', d='M2.068,1.209v8.969c-0.411,0.059-0.792,0.16-1.135,0.299V3.003C0.935,2.251,1.45,1.572,2.068,1.209 M2.99,0C1.521,0,0.006,1.392,0,3v9h0.347c0.529-0.672,1.59-0.951,2.604-0.951H3V0C2.997,0,2.994,0,2.99,0L2.99,0z') - polygon(fill='#EF9CA4', points='5.985,15.289 4.506,16.769 4.527,12.5 7.498,12.5 7.477,16.78') - path(fill='#F25058', d='M6.995,13l-0.013,2.579l-0.29-0.29l-0.707-0.707l-0.707,0.707l-0.266,0.266L5.024,13H6.995 M8,12H4.029L4,17.982l1.985-1.985l1.985,1.985L8,12L8,12z') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:gray;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#ef9ca4;stroke:#f25058;stroke-linejoin:round') + path(d='M5.5,11.5V0.5H14A2.5,2.5,0,0,1,16.5,3v8.5H5.5Z', style='fill:#bbb;stroke:#888;stroke-miterlimit:10') + path(d='M1.5,13.5V3A2.5,2.5,0,0,1,4,.5H5.5v13h-4Z', style='fill:#a1a1a1;stroke:#888;stroke-miterlimit:10') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:#888;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#ef9ca4;stroke:#f25058;stroke-linejoin:round') symbol#icons-repository-alt(viewBox='0 0 12 14.546') path(d='M12,0.909v10.909c-0.011,0.256-0.11,0.469-0.297,0.639c-0.188,0.17-0.422,0.261-0.703,0.27H6v1.818l-1.5-1.364L3,14.546v-1.818H1c-0.281-0.01-0.516-0.1-0.703-0.27C0.109,12.287,0.01,12.074,0,11.819V0.909C0.01,0.654,0.109,0.441,0.297,0.27S0.719,0.01,1,0h10c0.281,0.01,0.516,0.1,0.703,0.27S11.989,0.654,12,0.909z M11,10.001H1v1.818h2v-0.909h3v0.909h5V10.001zM11,0.909H2v8.182H11V0.909z M4,1.818H3v0.909h1V1.818z M4,3.637H3v0.909h1V3.637z M4,5.455H3v0.909h1V5.455z M4,8.182H3V7.273h1V8.182z') symbol#icons-search(viewBox='0 0 30 30') @@ -520,58 +520,44 @@ svg(xmlns='http://www.w3.org/2000/svg') path(fill='#3CCB5A', d='M12.5,9c1.93,0,3.5,1.57,3.5,3.5S14.43,16,12.5,16S9,14.43,9,12.5S10.57,9,12.5,9 M12.5,8C10.015,8,8,10.015,8,12.5s2.015,4.5,4.5,4.5s4.5-2.015,4.5-4.5S14.985,8,12.5,8L12.5,8z') polygon(fill='#3CCB5A', points='15,12 13,12 13,10 12,10 12,12 10,12 10,13 12,13 12,15 13,15 13,13 15,13 ') symbol#icons-new-repository-server(viewBox='0 0 18 18') - path(fill='#B3B3B3', d='M0.466,11.534V3c0.005-1.325,1.147-2.534,2.397-2.534h10.261c1.316,0.005,2.385,1.109,2.38,2.462l-0.027,7.053c-0.204,0.874-1.039,1.553-2.021,1.553H0.466z') - path(fill='#808080', d='M2.863,0.932l0.01,0h10.249c1.061,0.004,1.921,0.898,1.917,1.993l-0.028,6.943c-0.196,0.711-0.823,1.199-1.554,1.199H0.932V3.003C0.936,1.939,1.874,0.932,2.863,0.932 M2.863,0C1.394,0,0.006,1.392,0,3v9h13.457c1.227,0,2.232-0.867,2.486-2.017l0.028-7.053c0.006-1.612-1.268-2.923-2.846-2.93H2.873C2.87,0,2.866,0,2.863,0L2.863,0z') - path(fill='#E6E6E6', d='M3.371,15.534c-1.213,0-2.905-0.66-2.905-1.734V13c0-1.442,2.23-1.484,2.484-1.484h10.628c0.4,0,0.419-0.001,0.604-0.012c0.128-0.007,0.92-0.041,0.92-0.041l0.078-0.034c0.126-0.053,0.243-0.114,0.353-0.185V13c0,0.941-0.323,2.534-1.534,2.534H3.371z') - path(fill='#808080', d='M15.068,11.93V13c0,0.831-0.284,2.068-1.068,2.068H3.371c-1.098,0-2.439-0.585-2.439-1.268V13c0-1.007,1.936-1.018,2.018-1.018H13.58c0.419,0,0.437-0.001,0.632-0.012c0.127-0.007,0.337-0.019,0.825-0.038L15.068,11.93M16,9.697c-0.069,0.66-0.484,1.085-1,1.303c-1.188,0.047-0.759,0.049-1.42,0.049H2.951C1.531,11.049,0,11.581,0,13v0.8C0,15.219,1.952,16,3.371,16H14c1.419,0,2-1.581,2-3V9.697L16,9.697z') - polygon(fill='#EF9CA4', points='5.001,15.29 3.506,16.774 3.527,12.5 6.5,12.5 6.5,16.791 ') - path(fill='#F25058', d='M6,13v2.583L5.708,15.29l-0.704-0.706l-0.708,0.703l-0.284,0.282L4.024,13H6 M7,12H3.029L3,17.982l2-1.985L7,18V12L7,12z') - circle(fill='#FFFFFF', cx='12.5', cy='12.5', r='5.5') - path(fill='#EBFFEE', d='M12.5,16.5c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S14.706,16.5,12.5,16.5z') - path(fill='#3CCB5A', d='M12.5,9c1.93,0,3.5,1.57,3.5,3.5S14.43,16,12.5,16S9,14.43,9,12.5S10.57,9,12.5,9 M12.5,8C10.015,8,8,10.015,8,12.5s2.015,4.5,4.5,4.5s4.5-2.015,4.5-4.5S14.985,8,12.5,8L12.5,8z') - polygon(fill='#3CCB5A', points='15,12 13,12 13,10 12,10 12,12 10,12 10,13 12,13 12,15 13,15 13,13 15,13 ') - path(fill='#999999', d='M0.446,3c0.004-1.094,1.022-2.244,2.107-2.501v11.066c-0.607,0.029-1.464,0.134-2.107,0.466V3z') - path(fill='#808080', d='M2.107,1.162v9.98c-0.451,0.041-0.857,0.117-1.215,0.227l0-8.366C0.895,2.313,1.444,1.555,2.107,1.162M2.992,0C1.522,0,0.006,1.525,0,3v10.344c0-1.283,2.45-1.344,2.922-1.344C2.972,11.999,3,12,3,12V0C2.997,0,2.995,0,2.992,0L2.992,0z') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:gray;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#ef9ca4;stroke:#f25058;stroke-linejoin:round') + path(d='M5.5,11.5V0.5H14A2.5,2.5,0,0,1,16.5,3v8.5H5.5Z', style='fill:#bbb;stroke:#888;stroke-miterlimit:10') + path(d='M1.5,13.5V3A2.5,2.5,0,0,1,4,.5H5.5v13h-4Z', style='fill:#a1a1a1;stroke:#888;stroke-miterlimit:10') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:#888;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#ef9ca4;stroke:#f25058;stroke-linejoin:round') + circle(cx='13.5', cy='13.5', r='5.5', style='fill:#fff') + circle(cx='13.5', cy='13.5', r='4', style='fill:#ebffee;stroke:#3ccb5a;stroke-miterlimit:10') + line(x1='11', y1='13.5', x2='16', y2='13.5', style='fill:none;stroke:#3ccb5a;stroke-miterlimit:10') + line(x1='13.5', y1='16', x2='13.5', y2='11', style='fill:none;stroke:#3ccb5a;stroke-miterlimit:10') symbol#icons-repository-additional(viewBox='0 0 18 18') - path(fill='#B3B3B3', d='M0.6,11.6l0-8.7c0-1.3,1-2.4,2.2-2.4l10.3,0.1c1.3,0,2.4,1.1,2.4,2.5l0,7.1c-0.2,0.9-1,1.6-2,1.6H0.6z') - path(fill='#808080', d='M2.9,0l0,0.9L13.1,1C14.2,1,15,1.9,15,3l0,6.9c-0.2,0.7-0.8,1.2-1.6,1.2H1.1l0-8.2c0-1.1,0.8-2,1.7-2V0M2.9,0C1.4,0,0.2,1.3,0.2,2.9l0,9.1h13.3c1.2,0,2.2-0.9,2.5-2L16,3c0-1.6-1.3-2.9-2.8-2.9L2.9,0C2.9,0,2.9,0,2.9,0L2.9,0z') - path(fill='#E6E6E6', d='M3.4,16.6c-1.2,0-2.9-0.7-2.9-1.7v-1.8c0-1.4,2.2-1.5,2.5-1.5h10.6c0.4,0,0.4,0,0.6,0c0.1,0,0.9,0,0.9,0l0.1,0c0.1-0.1,0.2-0.1,0.4-0.2v2.8c0,0.9-0.3,2.5-1.5,2.5H3.4z') - path(fill='#808080', d='M15.1,12v2.1c0,0.8-0.3,2.1-1.1,2.1H3.4c-1.1,0-2.4-0.6-2.4-1.3v-1.8c0-1,1.9-1,2-1h10.6c0.4,0,0.4,0,0.6,0C14.3,12,14.5,12,15.1,12L15.1,12 M16,9.7c-0.1,0.7-0.5,1.1-1,1.3c-1.2,0-0.8,0-1.4,0H3c-1.4,0-3,0.5-3,2v1.8c0,1.4,2,2.2,3.4,2.2H14c1.4,0,2-1.6,2-3V9.7L16,9.7z') - path(fill='#A68AC4', d='M6.3,15.7c-0.1-0.1-0.2-0.1-0.4-0.1s-0.3,0-0.4,0.1l-1.1,1.1l0-4.3h3l0,4.3L6.3,15.7z') - path(fill='#834FAB', d='M7,13.1l0,2.6l-0.3-0.3C6.5,15.1,6.2,15,6,15s-0.5,0.1-0.7,0.3L5,15.6l0-2.6H7 M8,12.1H4l0,6l2-2l2,2L8,12.1L8,12.1z') - path(fill='#999999', d='M0.6,11.5l0-8.6c0-1.2,0.8-2.2,1.7-2.4v11H0.6z') - path(fill='#808080', d='M1.9,1.2V11H1.1l0-8.1C1.1,2.2,1.5,1.6,1.9,1.2 M2.9,0C1.4,0,0.2,1.3,0.2,2.9l0,9.1h2.7L2.9,0C2.9,0,2.9,0,2.9,0L2.9,0z') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:gray;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#ef9ca4;stroke:#f25058;stroke-linejoin:round') + path(d='M5.5,11.5V0.5H14A2.5,2.5,0,0,1,16.5,3v8.5H5.5Z', style='fill:#bbb;stroke:#888;stroke-miterlimit:10') + path(d='M1.5,13.5V3A2.5,2.5,0,0,1,4,.5H5.5v13h-4Z', style='fill:#a1a1a1;stroke:#888;stroke-miterlimit:10') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:#888;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#a68ac4;stroke:#834fab;stroke-linejoin:round') symbol#icons-repository-delete(viewBox='0 0 18 18') - path(fill='#B3B3B3', d='M0.6,11.6l0-8.7c0-1.3,1-2.4,2.2-2.4h10.3c1.3,0,2.4,1.1,2.4,2.5l0,7.1c-0.2,0.9-1,1.6-2,1.6H0.6z') - path(fill='#808080', d='M2.9,1L2.9,1l10.3,0C14.2,1,15,1.9,15,3l0,6.9c-0.2,0.7-0.8,1.2-1.6,1.2H1.1l0-8.2C1.1,1.8,1.9,1,2.9,1M2.9,0.1c-1.5,0-2.7,1.2-2.7,2.9l0,9.1h13.3c1.2,0,2.2-0.9,2.5-2L16,3c0-1.6-1.3-2.9-2.8-2.9L2.9,0.1C2.9,0.1,2.9,0.1,2.9,0.1L2.9,0.1z') - path(fill='#E6E6E6', d='M3.4,15.6c-1.2,0-2.9-0.7-2.9-1.7v-0.8c0-1.4,2.2-1.5,2.5-1.5h10.6c0.4,0,0.4,0,0.6,0c0.1,0,0.9,0,0.9,0l0.1,0c0.1-0.1,0.2-0.1,0.4-0.2v1.8c0,0.9-0.3,2.5-1.5,2.5H3.4z') - path(fill='#808080', d='M15.1,12v1.1c0,0.8-0.3,2.1-1.1,2.1H3.4c-1.1,0-2.4-0.6-2.4-1.3v-0.8c0-1,1.9-1,2-1h10.6c0.4,0,0.4,0,0.6,0C14.3,12,14.5,12,15.1,12L15.1,12 M16,9.7c-0.1,0.7-0.5,1.1-1,1.3c-1.2,0-0.8,0-1.4,0H3c-1.4,0-3,0.5-3,2v0.8c0,1.4,2,2.2,3.4,2.2H14c1.4,0,2-1.6,2-3V9.7L16,9.7z') - path(fill='#999999', d='M0.6,10.5l0-7.9c0-1.1,0.8-2,1.8-2.2v10H0.6z') - path(fill='#808080', d='M2,1.1v8.9H1.1l0-7.4C1.1,2,1.5,1.4,2,1.1 M2.9,0C1.4,0,0.2,1.2,0.2,2.7l0,8.3h2.7L2.9,0C2.9,0,2.9,0,2.9,0L2.9,0z') - path(fill='#A68AC4', d='M5.4,15.7c-0.1-0.1-0.2-0.1-0.4-0.1c-0.1,0-0.3,0-0.4,0.1l-1.1,1.1l0-4.3h3v4.3L5.4,15.7z') - path(fill='#834FAB', d='M6,13.1v2.6l-0.3-0.3C5.5,15.1,5.3,15,5,15c-0.3,0-0.5,0.1-0.7,0.3L4,15.6l0-2.6H6 M7,12.1H3l0,6l2-2l2,2V12.1L7,12.1z') - circle(fill='#FFFFFF', cx='12.5', cy='12.6', r='5.5') - path(fill='#FFF0F1', d='M12.5,16.6c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S14.7,16.6,12.5,16.6z') - path(fill='#F25058', d='M12.5,9.1c1.9,0,3.5,1.6,3.5,3.5s-1.6,3.5-3.5,3.5S9,14.5,9,12.6S10.6,9.1,12.5,9.1 M12.5,8.1c-2.5,0-4.5,2-4.5,4.5s2,4.5,4.5,4.5s4.5-2,4.5-4.5S15,8.1,12.5,8.1L12.5,8.1z') - rect(x='10', y='12.1', fill='#F25058', width='5', height='1') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:gray;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#ef9ca4;stroke:#f25058;stroke-linejoin:round') + path(d='M5.5,11.5V0.5H14A2.5,2.5,0,0,1,16.5,3v8.5H5.5Z', style='fill:#bbb;stroke:#888;stroke-miterlimit:10') + path(d='M1.5,13.5V3A2.5,2.5,0,0,1,4,.5H5.5v13h-4Z', style='fill:#a1a1a1;stroke:#888;stroke-miterlimit:10') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:#888;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#a68ac4;stroke:#834fab;stroke-linejoin:round') + circle(cx='13.5', cy='13.5', r='5.5', style='fill:#fff') + circle(cx='13.5', cy='13.5', r='4', style='fill:#fff0f1;stroke:#f25058;stroke-miterlimit:10') + line(x1='11', y1='13.5', x2='16', y2='13.5', style='fill:none;stroke:#f25058;stroke-miterlimit:10') symbol#icons-repository-new(viewBox='0 0 18 18') - path(fill='#B3B3B3', d='M0.6,11.6l0-8.7c0-1.3,1-2.4,2.2-2.4h10.3c1.3,0,2.4,1.1,2.4,2.5l0,7.1c-0.2,0.9-1,1.6-2,1.6H0.6z') - path(fill='#808080', d='M2.9,1L2.9,1l10.3,0C14.2,1,15,1.9,15,3l0,6.9c-0.2,0.7-0.8,1.2-1.6,1.2H1.1l0-8.2C1.1,1.8,1.9,1,2.9,1M2.9,0.1c-1.5,0-2.7,1.2-2.7,2.9l0,9.1h13.3c1.2,0,2.2-0.9,2.5-2L16,3c0-1.6-1.3-2.9-2.8-2.9L2.9,0.1C2.9,0.1,2.9,0.1,2.9,0.1L2.9,0.1z') - path(fill='#E6E6E6', d='M3.4,15.6c-1.2,0-2.9-0.7-2.9-1.7v-0.8c0-1.4,2.2-1.5,2.5-1.5h10.6c0.4,0,0.4,0,0.6,0c0.1,0,0.9,0,0.9,0l0.1,0c0.1-0.1,0.2-0.1,0.4-0.2v1.8c0,0.9-0.3,2.5-1.5,2.5H3.4z') - path(fill='#808080', d='M15.1,12v1.1c0,0.8-0.3,2.1-1.1,2.1H3.4c-1.1,0-2.4-0.6-2.4-1.3v-0.8c0-1,1.9-1,2-1h10.6c0.4,0,0.4,0,0.6,0C14.3,12,14.5,12,15.1,12L15.1,12 M16,9.7c-0.1,0.7-0.5,1.1-1,1.3c-1.2,0-0.8,0-1.4,0H3c-1.4,0-3,0.5-3,2v0.8c0,1.4,2,2.2,3.4,2.2H14c1.4,0,2-1.6,2-3V9.7L16,9.7z') - path(fill='#A68AC4', d='M5.4,15.7c-0.1-0.1-0.2-0.1-0.4-0.1c-0.1,0-0.3,0-0.4,0.1l-1.1,1.1l0-4.3h3v4.3L5.4,15.7z') - path(fill='#834FAB', d='M6,13.1v2.6l-0.3-0.3C5.5,15.1,5.3,15,5,15c-0.3,0-0.5,0.1-0.7,0.3L4,15.6l0-2.6H6 M7,12.1H3l0,6l2-2l2,2V12.1L7,12.1z') - path(fill='#999999', d='M0.6,10.5l0-7.9c0-1.1,0.8-2,1.8-2.2v10H0.6z') - path(fill='#808080', d='M2,1.1v8.9H1.1l0-7.4C1.1,2,1.5,1.4,2,1.1 M2.9,0C1.4,0,0.2,1.2,0.2,2.7l0,8.3h2.7L2.9,0C2.9,0,2.9,0,2.9,0L2.9,0z') - circle(fill='#FFFFFF', cx='12.5', cy='12.6', r='5.5') - path(fill='#EBFFEE', d='M12.5,16.6c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S14.7,16.6,12.5,16.6z') - path(fill='#3CCB5A', d='M12.5,9.1c1.9,0,3.5,1.6,3.5,3.5s-1.6,3.5-3.5,3.5S9,14.5,9,12.6S10.6,9.1,12.5,9.1 M12.5,8.1c-2.5,0-4.5,2-4.5,4.5s2,4.5,4.5,4.5s4.5-2,4.5-4.5S15,8.1,12.5,8.1L12.5,8.1z') - polygon(fill='#3CCB5A', points='15,12.1 13,12.1 13,10.1 12,10.1 12,12.1 10,12.1 10,13.1 12,13.1 12,15.1 13,15.1 13,13.1 15,13.1 ') - symbol#icons-runnabot(viewBox='0 0 18 18') - circle(cx='9', cy='9', r='9', fill='none') - path(d='M11.519,4.624H6.481A2.871,2.871,0,0,0,3.61,7.494V9.512a2.871,2.871,0,0,0,2.871,2.871H8.75c0.862,0.969,2.621,2.777,2.38,1.1a5.374,5.374,0,0,1-.07-1.1h0.46A2.871,2.871,0,0,0,14.39,9.512V7.494A2.871,2.871,0,0,0,11.519,4.624Z') - circle(cx='6.87', cy='8.503', r='0.937', fill='#fff') - circle(cx='11.13', cy='8.503', r='0.937', fill='#fff') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:gray;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#ef9ca4;stroke:#f25058;stroke-linejoin:round') + path(d='M5.5,11.5V0.5H14A2.5,2.5,0,0,1,16.5,3v8.5H5.5Z', style='fill:#bbb;stroke:#888;stroke-miterlimit:10') + path(d='M1.5,13.5V3A2.5,2.5,0,0,1,4,.5H5.5v13h-4Z', style='fill:#a1a1a1;stroke:#888;stroke-miterlimit:10') + path(d='M4,16.5a2.5,2.5,0,0,1,0-5H14a2.505,2.505,0,0,0,2.45-2H16.5V14A2.5,2.5,0,0,1,14,16.5H4Z', style='fill:#e6e6e6;stroke:#888;stroke-miterlimit:10') + polyline(points='8.5 12 8.5 17.25 7 16.25 5.5 17.25 5.5 12', style='fill:#a68ac4;stroke:#834fab;stroke-linejoin:round') + circle(cx='13.5', cy='13.5', r='5.5', style='fill:#fff') + circle(cx='13.5', cy='13.5', r='4', style='fill:#ebffee;stroke:#3ccb5a;stroke-miterlimit:10') + line(x1='11', y1='13.5', x2='16', y2='13.5', style='fill:none;stroke:#3ccb5a;stroke-miterlimit:10') + line(x1='13.5', y1='16', x2='13.5', y2='11', style='fill:none;stroke:#3ccb5a;stroke-miterlimit:10') symbol#icons-server-delete(viewBox='0 0 18 18') path(fill='#E6E6E6', d='M8,15.185c-0.129,0-0.258-0.03-0.374-0.088l-6.664-3.332C0.677,11.622,0.5,11.336,0.5,11.018v-6.35c0-0.318,0.177-0.604,0.462-0.747l6.665-3.332C7.742,0.531,7.871,0.5,8,0.5s0.258,0.031,0.374,0.088l6.665,3.332C15.323,4.063,15.5,4.35,15.5,4.667v6.35c0,0.313-0.182,0.606-0.462,0.747l-6.665,3.332C8.258,15.154,8.129,15.185,8,15.185z') path(fill='#999999', d='M8,1c0.052,0,0.104,0.012,0.15,0.035l6.665,3.332C14.929,4.425,15,4.54,15,4.668v6.35c0,0.128-0.071,0.243-0.185,0.3L8.15,14.649C8.104,14.673,8.052,14.685,8,14.685c-0.052,0-0.104-0.012-0.15-0.035l-6.665-3.332C1.071,11.26,1,11.145,1,11.017v-6.35c0-0.128,0.071-0.243,0.185-0.3L7.85,1.035C7.896,1.012,7.948,1,8,1 M8,0C7.795,0,7.591,0.047,7.403,0.141L0.738,3.473C0.286,3.7,0,4.162,0,4.668v6.35c0,0.506,0.286,0.968,0.738,1.194l6.665,3.332C7.591,15.638,7.795,15.685,8,15.685s0.409-0.047,0.597-0.141l6.665-3.332C15.714,11.985,16,11.523,16,11.017v-6.35c0-0.506-0.286-0.968-0.738-1.194L8.597,0.141C8.409,0.047,8.205,0,8,0L8,0z') @@ -606,6 +592,10 @@ svg(xmlns='http://www.w3.org/2000/svg') symbol#icons-octicons-branch(viewBox='0 0 18 18') circle(cx='9', cy='9', r='9', fill='none') path(d='M11.747,5.129a1.481,1.481,0,0,1,1.5,1.5,1.415,1.415,0,0,1-.205.756,1.549,1.549,0,0,1-.545.533V8.129a3.011,3.011,0,0,1-.937,2.062,3.011,3.011,0,0,1-2.063.938,1.624,1.624,0,0,0-.7.135,1.218,1.218,0,0,0-.445.346,1.5,1.5,0,1,1-2.6,1.02,1.415,1.415,0,0,1,.205-0.756A1.549,1.549,0,0,1,6.5,11.34V6.43A1.549,1.549,0,0,1,5.952,5.9a1.415,1.415,0,0,1-.205-0.756,1.5,1.5,0,1,1,3,0,1.415,1.415,0,0,1-.205.756A1.549,1.549,0,0,1,8,6.43V9.981a3.087,3.087,0,0,1,1.5-.34,1.613,1.613,0,0,0,1.5-1.5V7.93A1.549,1.549,0,0,1,10.452,7.4a1.415,1.415,0,0,1-.205-0.756,1.481,1.481,0,0,1,1.5-1.5V5.129ZM6.719,4.614a0.738,0.738,0,1,0,1.055,0A0.736,0.736,0,0,0,6.719,4.614Zm1.055,8.555a0.738,0.738,0,1,0-1.055,0A0.736,0.736,0,0,0,7.774,13.168Zm4.5-6a0.738,0.738,0,1,0-1.055,0A0.736,0.736,0,0,0,12.274,7.168Z') + symbol#icons-octicons-github(viewBox='0 0 18 18') + path(d='M15.372,2.628a9.007,9.007,0,0,1,.888,11.68,9.062,9.062,0,0,1-4.412,3.234,0.515,0.515,0,0,1-.475-0.1,0.486,0.486,0,0,1-.141-0.343l0.018-2.461a2.9,2.9,0,0,0-.193-1.046,1.664,1.664,0,0,0-.422-0.624,5.256,5.256,0,0,0,2.8-1.011q1.239-.905,1.31-3.436a3.844,3.844,0,0,0-.255-1.336,3.427,3.427,0,0,0-.677-1.072,2.44,2.44,0,0,0,.176-0.809,3.7,3.7,0,0,0-.264-1.582,1.289,1.289,0,0,0-.6.026,5.553,5.553,0,0,0-1.881.905,8.62,8.62,0,0,0-4.5,0A5.553,5.553,0,0,0,4.87,3.753a1.289,1.289,0,0,0-.6-0.026,3.7,3.7,0,0,0-.264,1.582,2.44,2.44,0,0,0,.176.809,3.429,3.429,0,0,0-.677,1.072,3.844,3.844,0,0,0-.255,1.336q0.07,2.531,1.3,3.436a5.232,5.232,0,0,0,2.812,1.011,1.45,1.45,0,0,0-.36.475,2.56,2.56,0,0,0-.22.738,2.487,2.487,0,0,1-1.2.22,1.815,1.815,0,0,1-1.424-.976,2.414,2.414,0,0,0-.484-0.562,1.325,1.325,0,0,0-.888-0.352q-0.527.018-.4,0.229a0.993,0.993,0,0,0,.413.352,1.937,1.937,0,0,1,.562.615,4.326,4.326,0,0,1,.352.65,1.648,1.648,0,0,0,.738.879,3.375,3.375,0,0,0,2.285.193L6.768,17.1a0.486,0.486,0,0,1-.141.343,0.515,0.515,0,0,1-.475.1A9.064,9.064,0,0,1,1.74,14.308,9,9,0,0,1,15.372,2.628Z') + symbol#icons-octicons-github-gray(viewBox='0 0 18 18') + path(fill='#555', d='M15.372,2.628a9.007,9.007,0,0,1,.888,11.68,9.062,9.062,0,0,1-4.412,3.234,0.515,0.515,0,0,1-.475-0.1,0.486,0.486,0,0,1-.141-0.343l0.018-2.461a2.9,2.9,0,0,0-.193-1.046,1.664,1.664,0,0,0-.422-0.624,5.256,5.256,0,0,0,2.8-1.011q1.239-.905,1.31-3.436a3.844,3.844,0,0,0-.255-1.336,3.427,3.427,0,0,0-.677-1.072,2.44,2.44,0,0,0,.176-0.809,3.7,3.7,0,0,0-.264-1.582,1.289,1.289,0,0,0-.6.026,5.553,5.553,0,0,0-1.881.905,8.62,8.62,0,0,0-4.5,0A5.553,5.553,0,0,0,4.87,3.753a1.289,1.289,0,0,0-.6-0.026,3.7,3.7,0,0,0-.264,1.582,2.44,2.44,0,0,0,.176.809,3.429,3.429,0,0,0-.677,1.072,3.844,3.844,0,0,0-.255,1.336q0.07,2.531,1.3,3.436a5.232,5.232,0,0,0,2.812,1.011,1.45,1.45,0,0,0-.36.475,2.56,2.56,0,0,0-.22.738,2.487,2.487,0,0,1-1.2.22,1.815,1.815,0,0,1-1.424-.976,2.414,2.414,0,0,0-.484-0.562,1.325,1.325,0,0,0-.888-0.352q-0.527.018-.4,0.229a0.993,0.993,0,0,0,.413.352,1.937,1.937,0,0,1,.562.615,4.326,4.326,0,0,1,.352.65,1.648,1.648,0,0,0,.738.879,3.375,3.375,0,0,0,2.285.193L6.768,17.1a0.486,0.486,0,0,1-.141.343,0.515,0.515,0,0,1-.475.1A9.064,9.064,0,0,1,1.74,14.308,9,9,0,0,1,15.372,2.628Z') symbol#icons-octicons-repo(viewBox='0 0 18 18') circle(cx='9', cy='9', r='9', fill='none') path(d='M13.293,4.446v7.806a0.63,0.63,0,0,1-.213.457,0.761,0.761,0,0,1-.5.193H9v1.3l-1.073-.976L6.854,14.2V12.9H5.422a0.761,0.761,0,0,1-.5-0.193,0.629,0.629,0,0,1-.212-0.457V4.446a0.629,0.629,0,0,1,.212-0.457,0.761,0.761,0,0,1,.5-0.193h7.156a0.761,0.761,0,0,1,.5.193A0.63,0.63,0,0,1,13.293,4.446Zm-0.716,6.505H5.422v1.3H6.854V11.6H9v0.651h3.578v-1.3Zm0-6.505H6.138V10.3h6.44V4.446ZM7.569,5.1H6.854V5.748H7.569V5.1Zm0,1.3H6.854V7.048H7.569V6.4Zm0,1.3H6.854V8.349H7.569V7.7Zm0,1.951H6.854V9H7.569V9.651h0Z') diff --git a/client/templates/viewBranchSelection.jade b/client/templates/viewBranchSelection.jade deleted file mode 100644 index 18df07073..000000000 --- a/client/templates/viewBranchSelection.jade +++ /dev/null @@ -1,28 +0,0 @@ -main.home.server-selection - img.logo.float-left( - height = "45" - src = "/build/images/runnable-logo-gray.svg" - width = "240" - ) - - h3.h3.header-thin Choose a container to run… - a.block-quote( - href = "#" - target = "_blank" - ) {{ message }} - //- spinner - .spinner-wrapper.spinner-md.in( - ng-if = "loading" - ng-include = "'spinner'" - ) - ol.list.list-bordered( - ng-show = "!loading" - ) - li.list-item( - ng-class = "{active: data.listExpand}" - ng-repeat = "instance in instances.models" - ) - .list-item-row( - ng-click = "selectInstance(instance)" - ) - h4.h4.header-thin {{ instance.attrs.name }} diff --git a/client/templates/viewInstance.jade b/client/templates/viewInstance.jade index 4222e2ec3..998c2054c 100644 --- a/client/templates/viewInstance.jade +++ b/client/templates/viewInstance.jade @@ -5,35 +5,9 @@ ) .grid-block.vertical.instance( - ng-if = "!isLoading.main" + ng-if = "!isLoading.main && !$root.featureFlags.containersViewEmptyState" ) - - .grid-block.shrink.vertical.instance-header( - ng-if = "$root.featureFlags.aha2" - ) - button.grid-block.shrink.btn.btn-sm.gray.btn-aha( - ng-click = "$root.featureFlags.ahaSidebar = true" - ng-if = "$root.featureFlags.aha && !$root.featureFlags.ahaSidebar" - tooltip = "Setup Guide" - tooltip-options = "{\"class\":\"left\",\"right\":42,\"top\":0}" - ) - svg.iconnables - use( - xlink:href = "#icons-help" - ) - - .grid-block.align-center.justify-center( - ng-if = "$root.featureFlags.aha2" - ) - .modal-dialog.modal-sm - .grid-block.align-center.aha-guide.padding-md( - ng-include = "'ahaGuideView'" - ng-init = "state.showStep = 2" - ) - - .grid-block.vertical( - ng-if = "!$root.featureFlags.aha2" - ) + .grid-block.vertical //- show if server build is being swapped out - orange when build is updating or has failed - green if build is successfully built @@ -84,6 +58,8 @@ ) .grid-block.vertical.aha-sidebar.padding-sm.js-animate( - ng-include = "'ahaSidebarView'" - ng-if = "$root.featureFlags.aha && $root.featureFlags.ahaSidebar" + aha-sidebar + toggle-sidebar = "CI.toggleSidebar" + show-overview = "false" + ng-if = "$root.featureFlags.aha && CI.isInGuide() && CI.showSidebar" ) diff --git a/client/templates/viewOrgSelect.jade b/client/templates/viewOrgSelect.jade index d1bb2a251..75282a982 100644 --- a/client/templates/viewOrgSelect.jade +++ b/client/templates/viewOrgSelect.jade @@ -1,7 +1,8 @@ div( modal modal-actions = "actions" - modal-data = "COS" + modal-controller = "COS" + modal-controller-as = "COS" modal-open-flag = "true" modal-template = "chooseOrganizationModalView" -) \ No newline at end of file +) diff --git a/layout.jade b/layout.jade index 1138b2ac8..04cca5047 100644 --- a/layout.jade +++ b/layout.jade @@ -47,9 +47,13 @@ html( if env === 'production' script. !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","page","once","off","on"];analytics.factory=function(t){return function(){var e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return analytics}};for(var t=0;t