webapp: Added help buttons and links next to fields that require explanations.
This commit is contained in:
parent
98231b3248
commit
4f4209b833
14 changed files with 254 additions and 63 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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} <span>^{note}</span>|]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
2
debian/changelog
vendored
|
@ -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
138
static/js/bootstrap-collapse.js
vendored
Normal 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 );
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
38
templates/documentation/repogroup.hamlet
Normal file
38
templates/documentation/repogroup.hamlet
Normal 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.
|
Loading…
Reference in a new issue