From 25cc8a796eb071090fac14037cc39e9bbb038dd2 Mon Sep 17 00:00:00 2001 From: Gary Paige Date: Tue, 21 Oct 2014 13:04:46 -0500 Subject: [PATCH] flex box and non radio button version --- CHANGELOG.md | 9 +- .../service_icons/icons_tabs_services.png | Bin 0 -> 1743 bytes app/assets/javascripts/init.js | 2 + app/assets/javascripts/jquery.finger_tabs.js | 68 ++++++++ app/assets/stylesheets/panamax.css.scss | 157 ++++++++++++++++++ .../stylesheets/panamax/service_details.scss | 3 +- .../stylesheets/panamax/service_tabs.css.scss | 105 ++++++++++++ .../services/_environment_variables.html.haml | 86 +++++----- app/views/services/_service_links.html.haml | 67 ++++---- app/views/services/_volumes.html.haml | 123 +++++++------- app/views/services/show.html.haml | 98 +++++++---- spec/features/manage_service_spec.rb | 2 +- .../fixtures/finger-tabs.html.haml | 17 ++ spec/javascripts/jquery.finger_tabs_spec.js | 75 +++++++++ vendor/ctl-base-ui | 2 +- 15 files changed, 644 insertions(+), 170 deletions(-) create mode 100644 app/assets/images/service_icons/icons_tabs_services.png create mode 100644 app/assets/javascripts/jquery.finger_tabs.js create mode 100644 app/assets/stylesheets/panamax/service_tabs.css.scss create mode 100644 spec/javascripts/fixtures/finger-tabs.html.haml create mode 100644 spec/javascripts/jquery.finger_tabs_spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 11bb574c..4a795a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog All notable changes to this project will be documented in this file. -0.2.5 - 2014-10-21 +LATEST ------------------ -### Fixed -- Ability to actually run an image from a private registry (#404) -- Service health error bubbling supressed (#403) + +### Added +- tab based service management view + 0.2.5 - 2014-10-20 ------------------ diff --git a/app/assets/images/service_icons/icons_tabs_services.png b/app/assets/images/service_icons/icons_tabs_services.png new file mode 100644 index 0000000000000000000000000000000000000000..ac0fcd3ab42cfa968531d5b5b4a2f168c0c8fe65 GIT binary patch literal 1743 zcmb_c`%@B#7Dn6gm2}rKAFX*!H%r0PL?g4@LQ&QvQ%o&0D^zfE@y%Mvy|iKsa#xK)PI=P&q@>ugOBoqp@Pamv3 zp$i0pH4cr(L27|GDCz?*DFB6gF<>l(aF!ha4DwV4G;iL*5OeT|6c|pode^q9{-Me>{IEXUC zmq;Wkv0ws$V2rDvpuqa9hHDHqJvSN|1F*4ib@lTL35ktOO~vDjix~_yo5vd&kxG@y z&w2b~g^5@D8LwJqh9~3z0NZzdhKX28&>Nd}xuNm7ukDDf|@P98k9pi1kLW=2Ix8*GC$K7kVZfGOVJ9>zJ{&wsQ+hmD|eJdI5A8JmB ztz^#vndDdyyngA(!;H~;W6+mQNbW%GI)&q;xD)ytSh2e!emZCn+@~j^%s3-}1 zV&%KMAyC0P{~?g2%9RNQopny|@Mc)X{?R1R#aQg9Lu(bw6Ze1>w^ge| z3DO;sHoca5(r<^;1{xK_FkAoT)XU#Y)bFZq3v?gSMf4vKokEy8<`8E9Q=6XQ)O>GX)`mZ;w}iYme^0nT3+@H=HT`Zj^i{v{Vk)3AsvYtB&kiE zb#iywSGD;z7@gdXw(!)n>Gzp3rcJO(bKSTb3~w%c77^qw^F6-uLfU)#d}jDD1r(7k zy#FMpv?4do*F;5j)E<%r-F}`7zW|xNI6T+&qs<5OuJdU(Guyu%yfA*EU&A9*wG;*j z%U&dDd#V^-mGYh)Jd~)W^o2-%Z{<>0d-a%uxH3r)xbzHrm_=_vZbXwQF*T);SRP~G zSE&6a)IO4q$~v^3b;tp{j~~ZHNF$MFQbFPyM=R8U(Glq3x&E$1S1_KB;}Mr1MFl6tzgZ#F z=(^;6h!YQ22xMJX^R+CT%h8*ON&Wg;(TBa#dSfMrn*kF<5D&ilzphd-w8!5T+XY|1 z(SG4CT=#LSBXBUi6i5ck^G=K2y3Mg0mO9t%7?|KGnJ>=OG}@0KnDn26T34+)4@5nt zys|`rMgNEWvp-z*t@!z=$t_5qH;|1DOkEusqS}i}u_K d;p5U^Kw4ax?d#6_O~&5@K!hSgT23Vs{|5@_ooN67 literal 0 HcmV?d00001 diff --git a/app/assets/javascripts/init.js b/app/assets/javascripts/init.js index 91992b24..2ed895a9 100644 --- a/app/assets/javascripts/init.js +++ b/app/assets/javascripts/init.js @@ -118,5 +118,7 @@ $('[data-toggle-target]').toggleTargetClass(); $('form.edit-registry').registryEditForm(); + + $('.tab-container').fingerTabs(); }; })(jQuery); diff --git a/app/assets/javascripts/jquery.finger_tabs.js b/app/assets/javascripts/jquery.finger_tabs.js new file mode 100644 index 00000000..d06aec39 --- /dev/null +++ b/app/assets/javascripts/jquery.finger_tabs.js @@ -0,0 +1,68 @@ +(function($) { + $.PMX.FingerTabs = function($el, options) { + var base = this; + + base.$el = $el; + + base.defaultOptions = { + cardSelector: '.card', + tabSelector: '.tab', + activeIconSelector: '.tab.active .icon', + tabsSelector: '.tabs', + hideSelector: '.hide', + labelSelector: 'label', + updateFormEvent: { event: 'progressiveForm:changed', + target: 'body' + } + }; + + base.init = function () { + base.options = $.extend({}, base.defaultOptions, options); + base.initializeTabs(); + base.bindEvents(); + }; + + base.initializeTabs = function() { + base.selectTab($(base.$el.find(base.options.tabSelector).first())); + }; + + base.bindEvents = function() { + base.$el.on('click', base.options.tabSelector, base.tabClickHandler); + base.$el.on('click', base.options.hideSelector, base.hideHandler); + $(base.options.updateFormEvent.target).on(base.options.updateFormEvent.event, base.dataChangedHandler); + }; + + base.hideHandler = function(e) { + base.$el.find(base.options.tabsSelector).toggleClass('slim'); + }; + + base.selectTab = function($tab_element) { + var tab_for = $tab_element.find(base.options.labelSelector).attr('for'); + base.resetTabs(); + $tab_element.addClass('active'); + base.$el.find(base.options.cardSelector + '.' + tab_for).addClass('active'); + + }; + + base.resetTabs = function() { + base.$el.find(base.options.cardSelector).removeClass('active'); + base.$el.find(base.options.tabSelector).removeClass('active'); + }; + + base.tabClickHandler = function(e) { + var $target = $(e.currentTarget); + base.selectTab($target); + }; + + base.dataChangedHandler = function(e) { + base.$el.find(base.options.activeIconSelector).addClass('changed'); + }; + }; + + $.fn.fingerTabs = function(options){ + return this.each(function(){ + (new $.PMX.FingerTabs($(this), options)).init(); + }); + }; + +})(jQuery); diff --git a/app/assets/stylesheets/panamax.css.scss b/app/assets/stylesheets/panamax.css.scss index bb622d24..4c03c8ed 100644 --- a/app/assets/stylesheets/panamax.css.scss +++ b/app/assets/stylesheets/panamax.css.scss @@ -762,3 +762,160 @@ pre.prettyprint { } } +.tab-container { + display: flex; + border-collapse: separate; + border-spacing: 10px 0; + width: 100%; + min-height: 460px; + + .tab, .tab > * { + box-sizing: border-box; + } + + .column { + flex-grow: 1; + flex-shrink: 1; + box-sizing: border-box; + vertical-align: top; + transition: all 0.6s; + -webkit-transition: all 0.6s; + -o-transition: all 0.6s; + -moz-transition: all 0.6s; + + .title { + height: 55px; + + h3 { + @include border-radius(3px); + background: $blue_grey; + color: $white; + font-weight: normal; + padding: 13px 15px; + font-size: 16px; + } + } + + .content { + box-sizing: border-box; + width: 100%; + min-height: 400px; + padding: 10px; + border: 1px solid #ccc; + } + } + + .card { + display: none; + padding-right: 10px; + + &.active { + display: block; + } + } + + .tabs { + flex-basis: 185px; + clear: both; + position: relative; + + .tab, .hide { + width: 185px; + height: 54px; + margin-bottom: 10px; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + border: 1px solid #ccc; + border-right: none; + position: relative; + background: $light_grey; + @include transition('all 0.6s'); + + &:hover { + background: $white; + } + + &.active { + background: $white; + + .text { + color: $dark_grey; + font-weight: bold; + } + + label { + border-right: 1px solid $white; + } + + } + + label { + left: 1px; + padding: 10px; + height: 52px; + position: absolute; + width: 100%; + display: inline-block; + + .icon { + float: left; + width: 20px; + height: 20px; + margin-top: 5px; + background-repeat: no-repeat; + } + + .text { + float: left; + font-size: 12px; + margin: 10px 0 0 10px; + color: $blue_grey; + @include transition('opacity 0s'); + transition-delay: 0.5s; + -webkit-transition-delay: 0.5s; + -o-transition-delay: 0.5s; + -moz-transition-delay: 0.5s; + opacity: 1.0; + } + } + } + + .hide:hover { + background: none; + .icon { + background-position: -20px 0; + } + } + + .hide { + border: none; + background: none; + height: 55px; + margin: 0; + + .icon { + background-position: 0 0; + } + } + + &.slim { + flex-basis: 40px; + + .tab { + width: 40px; + } + + label .text { + opacity: 0; + transition-delay: 0s; + -webkit-transition-delay: 0s; + -o-transition-delay: 0s; + -moz-transition-delay: 0s; + } + } + } + + .cards { + flex-basis: 510px; + } +} diff --git a/app/assets/stylesheets/panamax/service_details.scss b/app/assets/stylesheets/panamax/service_details.scss index 55d5679e..4d565733 100644 --- a/app/assets/stylesheets/panamax/service_details.scss +++ b/app/assets/stylesheets/panamax/service_details.scss @@ -149,6 +149,7 @@ .service-attributes { clear: both; padding-top: 10px; + margin-bottom: 20px; &:after { content: ''; @@ -214,7 +215,6 @@ .service-detail { width: 100%; - margin-bottom: 40px; font-size: 13px; > dl { @@ -395,7 +395,6 @@ } div.port-bindings { - margin-bottom: 50px; a + a#copy-endpoint { margin-left: 6px; diff --git a/app/assets/stylesheets/panamax/service_tabs.css.scss b/app/assets/stylesheets/panamax/service_tabs.css.scss new file mode 100644 index 00000000..6cc71d16 --- /dev/null +++ b/app/assets/stylesheets/panamax/service_tabs.css.scss @@ -0,0 +1,105 @@ +@import 'ctl_base_ui/mixins'; +@import 'ctl_base_ui/colors'; +@import 'ctl_base_ui/icons'; +@import 'panamax/buttons'; + + +.tab-container { + .column { + flex-grow: 0; + flex-shrink: 0; + } + + .card { + display: none; + padding-right: 10px; + + &.active { + display: block; + } + } + + .tabs { + .tab, .hide { + label { + .icon { + background-image: image-url('service_icons/icons_tabs_services.png'); + } + } + } + } + + .cards { + flex-basis: 510px; + } + + .docker-run { + flex-grow: 1; + flex-shrink: 1; + * { + box-sizing: border-box; + } + .content { + background: $light_grey; + + #docker-string { + margin: 0; + padding: 0; + + .run-segment { + white-space: normal; + } + } + } + } +} + +.tab label[for='links'] { + .icon { + background-position: 0 -30px; + + &.changed { + background-position: -21px -30px; + } + } +} + +.tab label[for='vars'] { + .icon { + background-position: 0 -60px; + + &.changed { + background-position: -21px -60px; + } + } +} + +.tab label[for='ports'] { + .icon { + background-position: 0 -90px; + + &.changed { + background-position: -21px -90px; + } + } +} + +.tab label[for='vols'] { + .icon { + background-position: 0 -120px; + + &.changed { + background-position: -21px -120px; + } + } +} + +.tab label[for='run'] { + .icon { + background-position: 0 -180px; + + &.changed { + background-position: -21px -180px; + } + } +} diff --git a/app/views/services/_environment_variables.html.haml b/app/views/services/_environment_variables.html.haml index 7a5e9729..a140d766 100644 --- a/app/views/services/_environment_variables.html.haml +++ b/app/views/services/_environment_variables.html.haml @@ -1,46 +1,48 @@ -%h3 - Environment Variables - .service-help-icon - .context-help - %section.tooltip - %aside.arrow - %span{ class: 'dismiss' } - %p - You can set any number of environment variables for a container. If a container is the child of a container via linking, docker generates variables which are pre-pended with the alias string. - %a{href: 'https://docs.docker.com/reference/run/#env-environment-variables'}Learn More. - %p To see if any additional environment variables were set at run time, review the Dockerfile of any base image. +.environment-variables + .title + %h3 + Environment Variables + .service-help-icon + .context-help + %section.tooltip + %aside.arrow + %span{ class: 'dismiss' } + %p + You can set any number of environment variables for a container. If a container is the child of a container via linking, docker generates variables which are pre-pended with the alias string. + %a{href: 'https://docs.docker.com/reference/run/#env-environment-variables'}Learn More. + %p To see if any additional environment variables were set at run time, review the Dockerfile of any base image. + .content + = f.fields_for :environment do |env_var| + - index = env_var.options[:child_index] -= f.fields_for :environment do |env_var| - - index = env_var.options[:child_index] + %dl.entries + %dt.variable-name + = env_var.label(:value, env_var.object.variable, data: { index: "#{index}" }) + .actions + %a{ class: 'edit-action' } + %dd.variable-value + - if env_var.object.value.blank? + = env_var.text_field :value, placeholder: 'enter a value', class: 'empty', data: { index: "#{index}" } + - else + %span{ data: { index: "#{index}" } } + = env_var.object.value + .actions + %a{ class: 'edit-action' } + %dd.actions + - checkbox_id = "select_environment_variable_#{index}" + = env_var.check_box :_deleted, { id: checkbox_id } + = env_var.hidden_field :variable, data: { index: "name_#{index}" } + = env_var.hidden_field :value, data: { index: "value_#{index}" } + %a{class: 'delete-action', title: 'remove', data: { checkbox: { selector: "##{checkbox_id}" } } } remove - %dl.entries - %dt.variable-name - = env_var.label(:value, env_var.object.variable, data: { index: "#{index}" }) - .actions - %a{ class: 'edit-action' } - %dd.variable-value - - if env_var.object.value.blank? - = env_var.text_field :value, placeholder: 'enter a value', class: 'empty', data: { index: "#{index}" } - - else - %span{ data: { index: "#{index}" } } - = env_var.object.value - .actions - %a{ class: 'edit-action' } - %dd.actions - - checkbox_id = "select_environment_variable_#{index}" - = env_var.check_box :_deleted, { id: checkbox_id } - = env_var.hidden_field :variable, data: { index: "name_#{index}" } - = env_var.hidden_field :value, data: { index: "value_#{index}" } - %a{class: 'delete-action', title: 'remove', data: { checkbox: { selector: "##{checkbox_id}" } } } remove + .additional-entries + = add_fields_for(:environment, f, { child_index: '_replaceme_' }) do |builder| + %dl + %dt.editing + = builder.text_field :variable, disabled: true, placeholder: 'Label', class: 'variable-name' + %dd.editing + = builder.text_field :value, disabled: true, placeholder: 'Value', class: 'variable-value' + %a.cancel Cancel -.additional-entries - = add_fields_for(:environment, f, { child_index: '_replaceme_' }) do |builder| - %dl - %dt.editing - = builder.text_field :variable, disabled: true, placeholder: 'Label', class: 'variable-name' - %dd.editing - = builder.text_field :value, disabled: true, placeholder: 'Value', class: 'variable-value' - %a.cancel Cancel - -%a.button-add Add an Environment Variable + %a.button-add Add an Environment Variable diff --git a/app/views/services/_service_links.html.haml b/app/views/services/_service_links.html.haml index 56235937..d1696d4c 100644 --- a/app/views/services/_service_links.html.haml +++ b/app/views/services/_service_links.html.haml @@ -1,33 +1,36 @@ -%h3 - Service Links - .service-help-icon - .context-help - %section.tooltip - %aside.arrow - %span{ class: 'dismiss' } - %p To create a functional link between two services, you will need to specify an Alias of the service you are linking to. - %p This Alias may be arbitrary or need to be consistent with information referenced in the Dockerfile of the base image or one of its supporting files. - %p - To learn more about docker links and the role of the Alias in creating the links, - %a{href: 'https://docs.docker.com/userguide/dockerlinks/#container-linking'} click here. -%ul - = f.fields_for :links do |link| - - checkbox_id = "select_link_#{link.options[:child_index]}" - %li - %a{data: { checkbox: { selector: "##{checkbox_id}" } }, class: 'remove-link', title: 'remove link'} remove - %span.link-name{title: 'Name'}= link.object.service_name - = ':' - %span.link-alias{title: 'Alias'}= link.object.alias - = link.check_box :_deleted, { id: checkbox_id } - = link.hidden_field :service_id - = link.hidden_field :service_name - = link.hidden_field :alias -%ul.additional-entries - %li.editing - = add_fields_for(:links, f, { child_index: '_replaceme_' }) do |builder| - = builder.select :service_id, linkable_service_options(services, service_id), { include_blank: true }, disabled: true, title: 'Name', class: 'link-name' - = ':' - = builder.text_field :alias, disabled: true, placeholder: 'enter link alias', title: 'Alias', id: nil, class: 'link-alias' - %a.cancel Cancel +.title + %h3 + Service Links + .service-help-icon + .context-help + %section.tooltip + %aside.arrow + %span{ class: 'dismiss' } + %p To create a functional link between two services, you will need to specify an Alias of the service you are linking to. + %p This Alias may be arbitrary or need to be consistent with information referenced in the Dockerfile of the base image or one of its supporting files. + %p + To learn more about docker links and the role of the Alias in creating the links, + %a{href: 'https://docs.docker.com/userguide/dockerlinks/#container-linking'} click here. +.content + .service-links + %ul + = f.fields_for :links do |link| + - checkbox_id = "select_link_#{link.options[:child_index]}" + %li + %a{data: { checkbox: { selector: "##{checkbox_id}" } }, class: 'remove-link', title: 'remove link'} remove + %span.link-name{title: 'Name'}= link.object.service_name + = ':' + %span.link-alias{title: 'Alias'}= link.object.alias + = link.check_box :_deleted, { id: checkbox_id } + = link.hidden_field :service_id + = link.hidden_field :service_name + = link.hidden_field :alias + %ul.additional-entries + %li.editing + = add_fields_for(:links, f, { child_index: '_replaceme_' }) do |builder| + = builder.select :service_id, linkable_service_options(services, service_id), { include_blank: true }, disabled: true, title: 'Name', class: 'link-name' + = ':' + = builder.text_field :alias, disabled: true, placeholder: 'enter link alias', title: 'Alias', id: nil, class: 'link-alias' + %a.cancel Cancel -%a.button-add Add a Linked Service + %a.button-add Add a Linked Service diff --git a/app/views/services/_volumes.html.haml b/app/views/services/_volumes.html.haml index 7800b98c..6d991166 100644 --- a/app/views/services/_volumes.html.haml +++ b/app/views/services/_volumes.html.haml @@ -1,60 +1,63 @@ -%h3 - Volumes - .service-help-icon - .context-help - %section.tooltip - %aside.arrow - %span{ class: 'dismiss' } - %h5 Data Volumes - %p Panamax currently supports the volume (-v) command for mounting a host directory as a data volume and adding a data volume to a container. - %h5 Data Volume Containers - %p - Panamax supports the --volumes-from command for mounting volumes shared by other services/containers. - %p - For additional information see - %a{ href: 'https://github.com/CenturyLinkLabs/panamax-ui/wiki/Managing-Volumes-within-Panamax' } Managing Volumes within Panamax -.data-containers - %h4 Data Volumes - %ul - %li.legend - host directory : container directory - = f.fields_for :volumes do |volume| - - checkbox_id = "select_volume_#{volume.options[:child_index]}" - %li{ title: "host: #{volume.object.host_path}, container: #{volume.object.container_path}" } - = optional_directory(volume.object.host_path) - \: - %span.container-path= volume.object.container_path - .actions - = volume.check_box :_deleted, { id: checkbox_id } - = volume.hidden_field :host_path - = volume.hidden_field :container_path - %a{ class: 'delete-action', title: 'remove', data: { checkbox: { selector: "##{checkbox_id}" } } } remove - %ul.additional-entries - %li.editing - = add_fields_for(:volumes, f, { child_index: '_replaceme_' }) do |builder| - = builder.text_field :host_path, disabled: true, placeholder: 'host path (optional)', title: 'host path', class: 'host-path' - = ':' - = builder.text_field :container_path, disabled: true, placeholder: 'container path', title: 'container path', class: 'container-path' - %a.cancel Cancel - %a.button-add Add a Volume -.mounted-containers - %h4 Data Volume Containers - %ul - %li.legend - name : container directory - = f.fields_for :volumes_from do |data| - - checkbox_id = "select_volume_from_#{data.options[:child_index]}" - %li{ title: "mount: #{data.object.service_name}" } - %strong.mount-point= data.object.service_name - = render 'mounted_volumes', service: @app.find_service_by_name(data.object.service_name) - .actions - = data.check_box :_deleted, { id: checkbox_id } - = data.hidden_field :service_id - = data.hidden_field :service_name - %a{ class: 'delete-action', title: 'remove', data: { checkbox: { selector: "##{checkbox_id}" } } } remove - %ul.additional-entries - %li.editing - = add_fields_for(:volumes_from, f, { child_index: '_replaceme_' }) do |builder| - = builder.select :service_id, linkable_service_options(services, service_id), { include_blank: true }, disabled: true, title: 'Mount', class: 'mount-point' - %a.cancel Cancel - %a.button-add Mount a new Data Volume Container +.volumes + .title + %h3 + Volumes + .service-help-icon + .context-help + %section.tooltip + %aside.arrow + %span{ class: 'dismiss' } + %h5 Data Volumes + %p Panamax currently supports the volume (-v) command for mounting a host directory as a data volume and adding a data volume to a container. + %h5 Data Volume Containers + %p + Panamax supports the --volumes-from command for mounting volumes shared by other services/containers. + %p + For additional information see + %a{ href: 'https://github.com/CenturyLinkLabs/panamax-ui/wiki/Managing-Volumes-within-Panamax' } Managing Volumes within Panamax + .content + .data-containers + %h4 Data Volumes + %ul + %li.legend + host directory : container directory + = f.fields_for :volumes do |volume| + - checkbox_id = "select_volume_#{volume.options[:child_index]}" + %li{ title: "host: #{volume.object.host_path}, container: #{volume.object.container_path}" } + = optional_directory(volume.object.host_path) + \: + %span.container-path= volume.object.container_path + .actions + = volume.check_box :_deleted, { id: checkbox_id } + = volume.hidden_field :host_path + = volume.hidden_field :container_path + %a{ class: 'delete-action', title: 'remove', data: { checkbox: { selector: "##{checkbox_id}" } } } remove + %ul.additional-entries + %li.editing + = add_fields_for(:volumes, f, { child_index: '_replaceme_' }) do |builder| + = builder.text_field :host_path, disabled: true, placeholder: 'host path (optional)', title: 'host path', class: 'host-path' + = ':' + = builder.text_field :container_path, disabled: true, placeholder: 'container path', title: 'container path', class: 'container-path' + %a.cancel Cancel + %a.button-add Add a Volume + .mounted-containers + %h4 Data Volume Containers + %ul + %li.legend + name : container directory + = f.fields_for :volumes_from do |data| + - checkbox_id = "select_volume_from_#{data.options[:child_index]}" + %li{ title: "mount: #{data.object.service_name}" } + %strong.mount-point= data.object.service_name + = render 'mounted_volumes', service: @app.find_service_by_name(data.object.service_name) + .actions + = data.check_box :_deleted, { id: checkbox_id } + = data.hidden_field :service_id + = data.hidden_field :service_name + %a{ class: 'delete-action', title: 'remove', data: { checkbox: { selector: "##{checkbox_id}" } } } remove + %ul.additional-entries + %li.editing + = add_fields_for(:volumes_from, f, { child_index: '_replaceme_' }) do |builder| + = builder.select :service_id, linkable_service_options(services, service_id), { include_blank: true }, disabled: true, title: 'Mount', class: 'mount-point' + %a.cancel Cancel + %a.button-add Mount a new Data Volume Container diff --git a/app/views/services/show.html.haml b/app/views/services/show.html.haml index 1bea1d40..54f87939 100644 --- a/app/views/services/show.html.haml +++ b/app/views/services/show.html.haml @@ -45,37 +45,79 @@ = form_for [@app, @service], html: { class: 'service-edit-form' } do |f| = render 'shared/errors', errors: f.object.errors .service-attributes - .column-left - .service-links - = render 'service_links', f: f, services: @app.services, service_id: @service.id - .port-detail - %h3 - Ports - .service-help-icon - .context-help - %section.tooltip - %aside.arrow - %span{ class: 'dismiss' } - %p Panamax currently supports port binding (-p) and port expose (--expose). - %p To allow Docker to auto assign a host port, leave the 'Host' field blank when adding a port rule. - %p Auto-assigned ports will receive a new port number each time the service is restarted. If you wish to control the port number on the host, please specify a host port when adding the rule. - %p Click the mapped endpoint to display the GUI, if available, on that port. - %p All ports exposed via EXPOSE in the Dockerfile will be noted as exposed by the base image. + .tab-container + .tabs.column + .hide + %label{ for: 'hide' } + .icon + .text Hide Tabs + .tab + %label{ for: 'links' } + .icon + .text Service Links + + .tab + %label{ for: 'vars' } + .icon + .text + Environment Variables + .tab + %label{ for: 'ports' } + .icon + .text + Ports + .tab + %label{ for: 'vols' } + .icon + .text + Volumes + .tab + %label{ for: 'run' } + .icon + .text + Docker Run Command + .cards.column + .card.links + = render 'service_links', f: f, services: @app.services, service_id: @service.id + .card.vars + = render 'environment_variables', f: f + .card.ports + .port-detail + .title + %h3 + Ports + .service-help-icon + .context-help + %section.tooltip + %aside.arrow + %span{ class: 'dismiss' } + %p Panamax currently supports port binding (-p) and port expose (--expose). + %p To allow Docker to auto assign a host port, leave the 'Host' field blank when adding a port rule. + %p Auto-assigned ports will receive a new port number each time the service is restarted. If you wish to control the port number on the host, please specify a host port when adding the rule. + %p Click the mapped endpoint to display the GUI, if available, on that port. + %p All ports exposed via EXPOSE in the Dockerfile will be noted as exposed by the base image. + + .content + .port-detail-forms + = render 'port_mappings', f: f + = render 'exposed_ports', f: f + .card.vols + = render 'volumes', f: f, services: @app.services, service_id: @service.id + .card.run + .title + %h3 Docker Run Command + .content + .docker-run-command + = f.text_field :command, placeholder: 'enter run command here (optional)' + .docker-run.column + .title + %h3 Docker Run + .content + %p#docker-string - .port-detail-forms - = render 'port_mappings', f: f - = render 'exposed_ports', f: f - .column-right - .environment-variables - = render 'environment_variables', f: f - .volumes - = render 'volumes', f: f, services: @app.services, service_id: @service.id - .docker-run-command - %h3 Docker run command - = f.text_field :command, placeholder: 'enter run command here (optional)' - %p#docker-string = f.submit 'Save all changes', class: 'button-positive', data: {disable_with: 'Saving...'} + = render 'shared/journal', title: 'CoreOS Journal - Service Activity Log', journal_path: journal_app_service_path(@app.id, @service.id, format: :json) diff --git a/spec/features/manage_service_spec.rb b/spec/features/manage_service_spec.rb index 40e5ea4d..72d5581f 100644 --- a/spec/features/manage_service_spec.rb +++ b/spec/features/manage_service_spec.rb @@ -27,7 +27,7 @@ expect(page).to have_content 'DB_PASSWORD pass@word01' end - within '.service-links', text: 'Service Links' do + within '.service-links' do expect(page).to have_content 'DB_1 : DB' end end diff --git a/spec/javascripts/fixtures/finger-tabs.html.haml b/spec/javascripts/fixtures/finger-tabs.html.haml new file mode 100644 index 00000000..05222dae --- /dev/null +++ b/spec/javascripts/fixtures/finger-tabs.html.haml @@ -0,0 +1,17 @@ +.tab-container + .tabs.column + .hide + %label{ for: 'hide' } + .icon + .text Hide Tabs + .tab + %label{ for: 'links' } + .icon + .text Service Links + .tab + %label{ for: 'second' } + .icon + .text Service Links + .cards.column + .card.links + .card.second diff --git a/spec/javascripts/jquery.finger_tabs_spec.js b/spec/javascripts/jquery.finger_tabs_spec.js new file mode 100644 index 00000000..cc959b46 --- /dev/null +++ b/spec/javascripts/jquery.finger_tabs_spec.js @@ -0,0 +1,75 @@ +describe('$.fn.fingerTabs', function() { + var subject; + + beforeEach(function() { + fixture.load('finger-tabs.html'); + subject = new $.PMX.FingerTabs($('.tab-container'), + { + updateFormEvent: { + event: 'formChange', + target: '.tab-container' + } + }); + }); + + it('sets first tab active', function() { + var first = $('.tab-container .tab').first(); + expect(first.hasClass('active')).toBeFalsy(); + subject.init(); + expect(first.hasClass('active')).toBeTruthy(); + }); + + describe('when clicking on a tab', function() { + + it('resets tabs to inactive state', function() { + var last = $('.tab-container .tab').last(), + first = $('.tab-container .tab').first(); + subject.init(); + last.addClass('active'); + first.click(); + expect(last.hasClass('active')).toBeFalsy(); + + }); + + it('add active class to tab', function() { + var last = $('.tab-container .tab').last(); + subject.init(); + last.click(); + expect(last.hasClass('active')).toBeTruthy(); + }); + + it('add active class to card', function() { + var last = $('.tab-container .tab').last(), + card = $('.tab-container .card').last(); + subject.init(); + last.click(); + expect(card.hasClass('active')).toBeTruthy(); + }); + }); + + describe('when clicking on hide', function() { + it('adds class slim when not present', function() { + var tabs = $('.tab-container .tabs'); + tabs.addClass('slim'); + $('.tab-container .hide').click(); + expect(tabs.hasClass('slim')).toBeTruthy(); + }); + + it('removes class slim when present', function() { + var tabs = $('.tab-container .tabs'); + tabs.addClass('slim'); + $('.tab-container .hide').click(); + expect(tabs.hasClass('slim')).toBeTruthy(); + }); + }); + + describe('when updateFormEvent', function() { + it('adds changed class to active tab icon', function() { + var icon; + subject.init(); + icon = $('.tab-container .tab.active .icon'); + $('.tab-container').trigger('formChange'); + expect(icon.hasClass('changed')).toBeTruthy(); + }); + }) +}); diff --git a/vendor/ctl-base-ui b/vendor/ctl-base-ui index a52d793e..7ef06c95 160000 --- a/vendor/ctl-base-ui +++ b/vendor/ctl-base-ui @@ -1 +1 @@ -Subproject commit a52d793e414b4271deac6fd2dc9b9279d67f209a +Subproject commit 7ef06c95d176e0b7995e8965aa76b386b7db42a9