webapp: Added help buttons and links next to fields that require explanations.

This commit is contained in:
Joey Hess 2012-12-02 22:33:30 -04:00
parent 98231b3248
commit 4f4209b833
14 changed files with 254 additions and 63 deletions

View file

@ -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|
<input id="#{theId}" name="#{name}" *{attrs} type="text" value="#{either id id val}">
|]
}
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|
<a href="https://portal.aws.amazon.com/gp/aws/securityCredentials#id_block">
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

View file

@ -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|<a href="@{RepoGroupR}">What's this?</a>|]
getEditRepositoryR :: UUID -> Handler RepHtml
getEditRepositoryR = editForm False

View file

@ -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|
<a .btn data-toggle="collapse" data-target="#help">
Help
<div #help .collapse>
<div>
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.
<div>
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

View file

@ -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")

View file

@ -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|
<input id="#{theId}" name="#{name}" *{attrs} type="password" value="#{either id id val}">
|]
}
{- 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}&nbsp;&nbsp;<span>^{note}</span>|]

View file

@ -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

View file

@ -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

2
debian/changelog vendored
View file

@ -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 <joeyh@debian.org> Wed, 28 Nov 2012 13:31:07 -0400

138
static/js/bootstrap-collapse.js vendored Normal file
View file

@ -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 );

View file

@ -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. #
<p>
<i .icon-warning-sign></i> By default, only files you place in #
"archive" directories will be archived in Amazon Glacier.
<p>
<i .icon-warning-sign></i> 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. #
<a href="http://aws.amazon.com/glacier/pricing/">
Pricing details
<p>
<i .icon-warning-sign></i> 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.
<p>
All data will be encrypted before being sent to Amazon Glacier.
<p>
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. #
<a href="https://portal.aws.amazon.com/gp/aws/securityCredentials#id_block">
Look up your access keys
<p>
<form .form-horizontal enctype=#{enctype}>
<fieldset>

View file

@ -14,16 +14,8 @@
<div .alert .alert-error>
<i .icon-warning-sign></i> #{msg}
$of _
<i .icon-warning-sign></i> #
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.
<p>
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.
<div>
Your data will be encrypted before it is sent to Rsync.net.
<p>
<form .form-horizontal enctype=#{enctype}>
<fieldset>

View file

@ -4,23 +4,15 @@
<p>
<a href="http://aws.amazon.com/s3/">Amazon S3</a> 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.
<p>
<i .icon-warning-sign></i> 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. #
<a href="http://aws.amazon.com/s3/pricing/">
Pricing details, including one year Free Usage Tier promotion
<p>
<i .icon-warning-sign></i> 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. #
<p>
All data will be encrypted before being sent to Amazon S3.
<p>
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. #
<a href="https://portal.aws.amazon.com/gp/aws/securityCredentials#id_block">
Look up your access keys
<p>
<form .form-horizontal enctype=#{enctype}>
<fieldset>

View file

@ -12,9 +12,6 @@
<div .alert .alert-error>
<i .icon-warning-sign></i> #{msg}
$of _
<i .icon-warning-sign></i> 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.
<p>
<form .form-horizontal enctype=#{enctype}>
<fieldset>

View file

@ -0,0 +1,38 @@
<div .span9 .hero-unit>
<h2>
Repository groups
<p>
Each repository you configure git-annex to use is a member of a #
repository group. Each group of repositories has a different #
purpose.
<p>
Let's start with the <b>client repositories</b>. 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.
<p>
The next most important repository group is the #
<b>transfer repositories</b>. #
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.
<p>
You can get along without any <b>backup repositories</b>, 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.
<p>
Next we come to the <b>archive repositories</b>.
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.
<p>
Finally, the <b>small archive repositories</b> 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.