diff --git a/Assistant/WebApp/Configurators/AWS.hs b/Assistant/WebApp/Configurators/AWS.hs index c2aeb34afb..e5831208fd 100644 --- a/Assistant/WebApp/Configurators/AWS.hs +++ b/Assistant/WebApp/Configurators/AWS.hs @@ -61,9 +61,9 @@ extractCreds i = AWSCreds (accessKeyID i) (secretAccessKey i) s3InputAForm :: AForm WebApp WebApp AWSInput s3InputAForm = AWSInput - <$> areq textField "Access Key ID" Nothing - <*> areq passwordField "Secret Access Key" Nothing - <*> areq (selectFieldList $ M.toList $ AWS.regionMap AWS.S3) "Datacenter" (Just $ AWS.defaultRegion AWS.S3) + <$> accessKeyIDField + <*> secretAccessKeyField + <*> datacenterField AWS.S3 <*> areq (selectFieldList storageclasses) "Storage class" (Just StandardRedundancy) <*> areq textField "Repository name" (Just "S3") where @@ -73,27 +73,35 @@ s3InputAForm = AWSInput , ("Reduced redundancy (costs less)", ReducedRedundancy) ] -textField' :: RenderMessage master FormMessage => Field sub master Text -textField' = Field - { fieldParse = fieldParse textField - , fieldView = \theId name attrs val _isReq -> - [whamlet| - -|] - } - glacierInputAForm :: AForm WebApp WebApp AWSInput glacierInputAForm = AWSInput - <$> areq textField "Access Key ID" Nothing - <*> areq passwordField "Secret Access Key" Nothing - <*> areq (selectFieldList $ M.toList $ AWS.regionMap AWS.Glacier) "Datacenter" (Just $ AWS.defaultRegion AWS.Glacier) + <$> accessKeyIDField + <*> secretAccessKeyField + <*> datacenterField AWS.Glacier <*> pure StandardRedundancy <*> areq textField "Repository name" (Just "glacier") awsCredsAForm :: AForm WebApp WebApp AWSCreds awsCredsAForm = AWSCreds - <$> areq textField "Access Key ID" Nothing - <*> areq passwordField "Secret Access Key" Nothing + <$> accessKeyIDField + <*> secretAccessKeyField + +accessKeyIDField :: AForm WebApp WebApp Text +accessKeyIDField = areq (textField `withNote` help) "Access Key ID" Nothing + where + help = [whamlet| + + Get Amazon access keys +|] + +secretAccessKeyField :: AForm WebApp WebApp Text +secretAccessKeyField = areq passwordField "Secret Access Key" Nothing + +datacenterField :: AWS.Service -> AForm WebApp WebApp Text +datacenterField service = areq (selectFieldList list) "Datacenter" defregion + where + list = M.toList $ AWS.regionMap service + defregion = Just $ AWS.defaultRegion service getAddS3R :: Handler RepHtml #ifdef WITH_S3 diff --git a/Assistant/WebApp/Configurators/Edit.hs b/Assistant/WebApp/Configurators/Edit.hs index 21ec8f5d5e..40a47a17e3 100644 --- a/Assistant/WebApp/Configurators/Edit.hs +++ b/Assistant/WebApp/Configurators/Edit.hs @@ -89,9 +89,10 @@ editRepositoryAForm :: RepoConfig -> AForm WebApp WebApp RepoConfig editRepositoryAForm def = RepoConfig <$> areq textField "Name" (Just $ repoName def) <*> aopt textField "Description" (Just $ repoDescription def) - <*> areq (selectFieldList $ customgroups++standardgroups) "Repository group" (Just $ repoGroup def) + <*> areq (selectFieldList groups `withNote` help) "Repository group" (Just $ repoGroup def) <*> areq checkBoxField "Syncing enabled" (Just $ repoSyncable def) where + groups = customgroups ++ standardgroups standardgroups :: [(Text, RepoGroup)] standardgroups = map (\g -> (T.pack $ descStandardGroup g , RepoGroupStandard g)) [minBound :: StandardGroup .. maxBound :: StandardGroup] @@ -99,6 +100,7 @@ editRepositoryAForm def = RepoConfig customgroups = case repoGroup def of RepoGroupCustom s -> [(T.pack s, RepoGroupCustom s)] _ -> [] + help = [whamlet|What's this?|] getEditRepositoryR :: UUID -> Handler RepHtml getEditRepositoryR = editForm False diff --git a/Assistant/WebApp/Configurators/Ssh.hs b/Assistant/WebApp/Configurators/Ssh.hs index 32be718296..15a02901f2 100644 --- a/Assistant/WebApp/Configurators/Ssh.hs +++ b/Assistant/WebApp/Configurators/Ssh.hs @@ -47,13 +47,13 @@ mkSshData s = SshData , rsyncOnly = False } -sshInputAForm :: SshInput -> AForm WebApp WebApp SshInput -sshInputAForm def = SshInput +sshInputAForm :: (Field WebApp WebApp Text) -> SshInput -> AForm WebApp WebApp SshInput +sshInputAForm hostnamefield def = SshInput <$> aopt check_hostname "Host name" (Just $ hostname def) <*> aopt check_username "User name" (Just $ username def) <*> aopt textField "Directory" (Just $ Just $ fromMaybe (T.pack gitAnnexAssistantDefaultDir) $ directory def) where - check_hostname = checkM (liftIO . checkdns) textField + check_hostname = checkM (liftIO . checkdns) hostnamefield checkdns t = do let h = T.unpack t r <- catchMaybeIO $ getAddrInfo canonname (Just h) Nothing @@ -89,7 +89,7 @@ getAddSshR :: Handler RepHtml getAddSshR = sshConfigurator $ do u <- liftIO $ T.pack <$> myUserName ((result, form), enctype) <- lift $ - runFormGet $ renderBootstrap $ sshInputAForm $ + runFormGet $ renderBootstrap $ sshInputAForm textField $ SshInput Nothing (Just u) Nothing case result of FormSuccess sshinput -> do @@ -115,7 +115,7 @@ getEnableRsyncR u = do case (parseSshRsyncUrl =<< M.lookup "rsyncurl" m, M.lookup "name" m) of (Just sshinput, Just reponame) -> sshConfigurator $ do ((result, form), enctype) <- lift $ - runFormGet $ renderBootstrap $ sshInputAForm sshinput + runFormGet $ renderBootstrap $ sshInputAForm textField sshinput case result of FormSuccess sshinput' | isRsyncNet (hostname sshinput') -> @@ -276,7 +276,7 @@ makeSshRepo forcersync setup sshdata = do getAddRsyncNetR :: Handler RepHtml getAddRsyncNetR = do ((result, form), enctype) <- runFormGet $ - renderBootstrap $ sshInputAForm $ + renderBootstrap $ sshInputAForm hostnamefield $ SshInput Nothing Nothing Nothing let showform status = page "Add a Rsync.net repository" (Just Config) $ $(widgetFile "configurators/addrsync.net") @@ -290,6 +290,19 @@ getAddRsyncNetR = do showform $ UnusableServer "That is not a rsync.net host name." _ -> showform UntestedServer + where + hostnamefield = textField `withNote` help + help = [whamlet| + + Help +
+
+ When you sign up for a Rsync.net account, you should receive an # + email from them with the host name and user name to put here. +
+ The host name will be something like "usw-s001.rsync.net", and the # + user name something like "7491" +|] makeRsyncNet :: SshInput -> String -> (Remote -> Handler ()) -> Handler RepHtml makeRsyncNet sshinput reponame setup = do diff --git a/Assistant/WebApp/Documentation.hs b/Assistant/WebApp/Documentation.hs index d3b2060de9..bee472dce1 100644 --- a/Assistant/WebApp/Documentation.hs +++ b/Assistant/WebApp/Documentation.hs @@ -35,3 +35,7 @@ getLicenseR = do setTitle "License" license <- liftIO $ readFile f $(widgetFile "documentation/license") + +getRepoGroupR :: Handler RepHtml +getRepoGroupR = page "About repository groups" (Just About) $ do + $(widgetFile "documentation/repogroup") diff --git a/Assistant/WebApp/Form.hs b/Assistant/WebApp/Form.hs index 633b5e0eb7..861afb17ed 100644 --- a/Assistant/WebApp/Form.hs +++ b/Assistant/WebApp/Form.hs @@ -27,9 +27,18 @@ textField = F.textField |] } +{- Also without required attribute. -} passwordField :: RenderMessage master FormMessage => Field sub master Text passwordField = F.passwordField { fieldView = \theId name attrs val _isReq -> toWidget [hamlet| |] } + +{- Makes a note widget be displayed after a field. -} +withNote :: RenderMessage master FormMessage => Field sub master a -> GWidget sub master () -> Field sub master a +withNote field note = field { fieldView = newview } + where + newview theId name attrs val isReq = + let fieldwidget = (fieldView field) theId name attrs val isReq + in [whamlet|^{fieldwidget}  ^{note}|] diff --git a/Assistant/WebApp/Page.hs b/Assistant/WebApp/Page.hs index 5787b8322e..3c849b0f3a 100644 --- a/Assistant/WebApp/Page.hs +++ b/Assistant/WebApp/Page.hs @@ -60,6 +60,7 @@ customPage navbaritem content = do addScript $ StaticR jquery_full_js addScript $ StaticR js_bootstrap_dropdown_js addScript $ StaticR js_bootstrap_modal_js + addScript $ StaticR js_bootstrap_collapse_js $(widgetFile "page") hamletToRepHtml $(hamletFile $ hamletTemplate "bootstrap") where diff --git a/Assistant/WebApp/routes b/Assistant/WebApp/routes index 7fa63c1db7..d9d8b42f99 100644 --- a/Assistant/WebApp/routes +++ b/Assistant/WebApp/routes @@ -3,6 +3,7 @@ /noscript/auto NoScriptAutoR GET /about AboutR GET /about/license LicenseR GET +/about/repogroups RepoGroupR GET /config ConfigR GET /config/repository RepositoriesR GET diff --git a/debian/changelog b/debian/changelog index 3328627d2d..1daa0c296d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -17,6 +17,8 @@ git-annex (3.20121128) UNRELEASED; urgency=low livedrive.com. Needs DAV version 0.3. * webapp: Prettify error display. * webapp: Fix bad interaction between required fields and modals. + * webapp: Added help buttons and links next to fields that require + explanations. -- Joey Hess Wed, 28 Nov 2012 13:31:07 -0400 diff --git a/static/js/bootstrap-collapse.js b/static/js/bootstrap-collapse.js new file mode 100644 index 0000000000..9a364468b7 --- /dev/null +++ b/static/js/bootstrap-collapse.js @@ -0,0 +1,138 @@ +/* ============================================================= + * bootstrap-collapse.js v2.0.2 + * http://twitter.github.com/bootstrap/javascript.html#collapse + * ============================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============================================================ */ + +!function( $ ){ + + "use strict" + + var Collapse = function ( element, options ) { + this.$element = $(element) + this.options = $.extend({}, $.fn.collapse.defaults, options) + + if (this.options["parent"]) { + this.$parent = $(this.options["parent"]) + } + + this.options.toggle && this.toggle() + } + + Collapse.prototype = { + + constructor: Collapse + + , dimension: function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + , show: function () { + var dimension = this.dimension() + , scroll = $.camelCase(['scroll', dimension].join('-')) + , actives = this.$parent && this.$parent.find('.in') + , hasData + + if (actives && actives.length) { + hasData = actives.data('collapse') + actives.collapse('hide') + hasData || actives.data('collapse', null) + } + + this.$element[dimension](0) + this.transition('addClass', 'show', 'shown') + this.$element[dimension](this.$element[0][scroll]) + + } + + , hide: function () { + var dimension = this.dimension() + this.reset(this.$element[dimension]()) + this.transition('removeClass', 'hide', 'hidden') + this.$element[dimension](0) + } + + , reset: function ( size ) { + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + [dimension](size || 'auto') + [0].offsetWidth + + this.$element[size ? 'addClass' : 'removeClass']('collapse') + + return this + } + + , transition: function ( method, startEvent, completeEvent ) { + var that = this + , complete = function () { + if (startEvent == 'show') that.reset() + that.$element.trigger(completeEvent) + } + + this.$element + .trigger(startEvent) + [method]('in') + + $.support.transition && this.$element.hasClass('collapse') ? + this.$element.one($.support.transition.end, complete) : + complete() + } + + , toggle: function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + } + + /* COLLAPSIBLE PLUGIN DEFINITION + * ============================== */ + + $.fn.collapse = function ( option ) { + return this.each(function () { + var $this = $(this) + , data = $this.data('collapse') + , options = typeof option == 'object' && option + if (!data) $this.data('collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.defaults = { + toggle: true + } + + $.fn.collapse.Constructor = Collapse + + + /* COLLAPSIBLE DATA-API + * ==================== */ + + $(function () { + $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { + var $this = $(this), href + , target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + , option = $(target).data('collapse') ? 'toggle' : $this.data() + $(target).collapse(option) + }) + }) + +}( window.jQuery ); \ No newline at end of file diff --git a/templates/configurators/addglacier.hamlet b/templates/configurators/addglacier.hamlet index ad15b2bb16..0e90ffebcd 100644 --- a/templates/configurators/addglacier.hamlet +++ b/templates/configurators/addglacier.hamlet @@ -6,23 +6,17 @@ offline cloud storage provider. It takes several hours for requested # files to be retrieved from Glacier, making it mostly suitable for # backups and long-term data archival. # +

+ By default, only files you place in # + "archive" directories will be archived in Amazon Glacier. +

+ You will be charged by Amazon for data # + uploaded to Glacier, as well as data downloaded from Glacier, and a # + monthly fee for data storage. # Pricing details -

- By default, only files you place in # - "archive" directories will be archived in Amazon Glacier. # - You will be charged by Amazon for data # - uploaded to Glacier, as well as data downloaded from Glacier, and a # - monthly fee for data storage.

All data will be encrypted before being sent to Amazon Glacier. -

- When you sign up to Amazon Glacier, they provide you with an Access # - Key ID, and a Secret Access Key. You will need to enter both below. # - These access keys will be stored in a file that only you can # - access. # - - Look up your access keys

diff --git a/templates/configurators/addrsync.net.hamlet b/templates/configurators/addrsync.net.hamlet index d6e434866a..528d63b068 100644 --- a/templates/configurators/addrsync.net.hamlet +++ b/templates/configurators/addrsync.net.hamlet @@ -14,16 +14,8 @@
#{msg} $of _ - # - All your data will be synced to the Rsync.net repository, so make # - sure it has enough available space. Your data will be encrypted before # - it is sent to Rsync.net. -

- When you sign up for a Rsync.net account, you receive an # - email from them with a host name and a username. Fill that # - information in below. You also likely don't want to use your whole # - rsync.net repository for git-annex alone, so git-annex will use a # - subdirectory of it, as configured below. +

+ Your data will be encrypted before it is sent to Rsync.net.

diff --git a/templates/configurators/adds3.hamlet b/templates/configurators/adds3.hamlet index f4e6751897..16b62a5eb5 100644 --- a/templates/configurators/adds3.hamlet +++ b/templates/configurators/adds3.hamlet @@ -4,23 +4,15 @@

Amazon S3 is a cloud storage # provider. If you need a professional level of storage for your data, # - this is a good choice. # + this is a good choice. +

+ You will be charged by Amazon for data # + uploaded to S3, as well as data downloaded from S3, and a monthly fee # + for data storage. # Pricing details, including one year Free Usage Tier promotion -

- Do keep in mind that all your data # - will be synced to Amazon S3. You will be charged by Amazon for data # - uploaded to S3, as well as data downloaded from S3, and a monthly fee # - for data storage. #

All data will be encrypted before being sent to Amazon S3. -

- When you sign up to Amazon S3, they provide you with an Access # - Key ID, and a Secret Access Key. You will need to enter both below. # - These access keys will be stored in a file that only you can # - access. # - - Look up your access keys

diff --git a/templates/configurators/ssh/add.hamlet b/templates/configurators/ssh/add.hamlet index 70eb6b5dfd..ba7058a7ec 100644 --- a/templates/configurators/ssh/add.hamlet +++ b/templates/configurators/ssh/add.hamlet @@ -12,9 +12,6 @@
#{msg} $of _ - Do keep in mind that all your data # - will be synced to the server, so make sure it has enough available # - disk space and bandwidth, and that you trust it with your data.

diff --git a/templates/documentation/repogroup.hamlet b/templates/documentation/repogroup.hamlet new file mode 100644 index 0000000000..404efad64c --- /dev/null +++ b/templates/documentation/repogroup.hamlet @@ -0,0 +1,38 @@ +
+

+ Repository groups +

+ Each repository you configure git-annex to use is a member of a # + repository group. Each group of repositories has a different # + purpose. +

+ Let's start with the client repositories. These are the # + repositories that contain files that you can directly use. Generally # + you'll have one client repository per computer. The whole point of # + the git-annex assistant is to keep these repositories in sync as you # + change files, add files, delete files, etc. +

+ The next most important repository group is the # + transfer repositories. # + These are used to transfer files between clients, when the clients # + cannot directly talk to one-another. Transfer repositories only # + hold the files that need to be synced to other repositories, so they # + are relatively small. +

+ You can get along without any backup repositories, but they're # + a useful safeguard. These repositories accumulate every file they can # + get ahold of. A large removable drive makes a good backup repository. +

+ Next we come to the archive repositories. + The archive repositories coordinate together, so that each file is # + archived in only one place. When you move files into a folder named # + "archive", they'll be moved to an archive repository, and removed from # + all your client repositories. This is handy if you have old files # + you don't need anymore, but want to keep archived for later. # + When you copy or move a file out of an "archive" folder, it'll be # + retrieved from the archive repository. +

+ Finally, the small archive repositories are like other archive # + repositories, but smaller. While archive repositories normally accumulate # + every file they can, small archive repositories only accumulate files # + once you put them in an "archive" directory.