Rewrote parts of the user interface.

This adds landing pages to restore and add, as requested in #2282 and #2293.
Added the app icon, and re-arranged the text as requested in #2286
This commit is contained in:
Kenneth Skovhede
2017-02-07 21:51:14 +01:00
parent a0c1c4440f
commit e4d8058cd3
29 changed files with 742 additions and 184 deletions
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="enable-background:new 0 0 612 612;"
viewBox="0 0 612 612"
y="0px"
x="0px"
id="Capa_1"
version="1.1"><metadata
id="metadata55"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs53" /><g
id="g3"><g
transform="matrix(0.97603036,0,0,0.94041294,9.1684184,49.485777)"
style="fill:#65b1dd;fill-opacity:1"
id="g5"><path
style="fill:#65b1dd;fill-opacity:1"
id="path7"
d="m 175.205,239.62 c 0.127,-1.965 -0.533,-3.902 -1.833,-5.381 l -58.84,-66.941 c -1.3,-1.479 -3.135,-2.381 -5.102,-2.508 -1.975,-0.126 -3.902,0.533 -5.381,1.833 -27.037,23.766 -49.479,51.794 -66.706,83.305 -0.944,1.729 -1.165,3.762 -0.611,5.651 0.554,1.89 1.836,3.483 3.565,4.427 l 78.205,42.748 c 1.131,0.619 2.352,0.912 3.557,0.912 2.627,0 5.174,-1.398 6.523,-3.866 11.386,-20.828 26.229,-39.359 44.114,-55.08 1.482,-1.298 2.384,-3.133 2.509,-5.1 z" /><path
style="fill:#65b1dd;fill-opacity:1"
id="path9"
d="m 201.462,214.829 c 1.334,2.515 3.907,3.948 6.568,3.948 1.174,0 2.365,-0.279 3.473,-0.867 20.962,-11.117 43.512,-18.371 67.025,-21.561 4.064,-0.551 6.913,-4.293 6.362,-8.358 L 272.911,99.675 c -0.551,-4.064 -4.304,-6.909 -8.358,-6.362 -35.708,4.843 -69.949,15.857 -101.772,32.736 -3.623,1.922 -5.002,6.416 -3.082,10.041 l 41.763,78.739 z" /><path
style="fill:#65b1dd;fill-opacity:1"
id="path11"
d="M 105.785,334.345 19.768,311.007 c -1.901,-0.514 -3.929,-0.255 -5.638,0.725 -1.709,0.98 -2.958,2.598 -3.475,4.499 C 3.586,342.295 0,369.309 0,396.523 c 0,4.657 0.111,9.329 0.342,14.284 0.185,3.981 3.468,7.083 7.414,7.083 0.116,0 0.234,-0.002 0.35,-0.008 l 89.031,-4.113 c 1.967,-0.09 3.82,-0.96 5.145,-2.415 1.327,-1.455 2.022,-3.38 1.93,-5.347 -0.155,-3.341 -0.23,-6.444 -0.23,-9.484 0,-18.02 2.365,-35.873 7.029,-53.066 1.071,-3.958 -1.268,-8.037 -5.226,-9.112 z" /><path
style="fill:#65b1dd;fill-opacity:1"
id="path13"
d="M 438.731,120.745 C 406.32,105.12 371.691,95.437 335.806,91.959 c -1.972,-0.198 -3.918,0.408 -5.439,1.659 -1.521,1.252 -2.481,3.056 -2.671,5.018 l -8.593,88.712 c -0.396,4.082 2.594,7.713 6.677,8.108 23.652,2.291 46.463,8.669 67.8,18.954 1.015,0.49 2.118,0.738 3.225,0.738 0.826,0 1.654,-0.139 2.45,-0.416 1.859,-0.649 3.385,-2.012 4.24,-3.786 l 38.7,-80.287 c 1.783,-3.694 0.232,-8.134 -3.464,-9.914 z" /><path
style="fill:#65b1dd;fill-opacity:1"
id="path15"
d="m 569.642,245.337 c 0.48,-1.911 0.184,-3.932 -0.828,-5.624 -18.432,-30.835 -41.933,-57.983 -69.848,-80.686 -1.529,-1.242 -3.48,-1.824 -5.447,-1.627 -1.959,0.203 -3.758,1.174 -5,2.702 l -56.237,69.144 c -1.242,1.529 -1.828,3.488 -1.625,5.447 0.201,1.959 1.173,3.758 2.702,5.002 18.47,15.019 34.015,32.975 46.205,53.369 1.392,2.326 3.855,3.618 6.383,3.618 1.297,0 2.61,-0.34 3.803,-1.054 L 566.251,249.9 c 1.689,-1.011 2.909,-2.652 3.391,-4.563 z" /><path
style="fill:#65b1dd;fill-opacity:1"
id="path17"
d="m 598.044,304.939 c -1.228,-3.915 -5.397,-6.096 -9.308,-4.867 l -85.048,26.648 c -3.915,1.226 -6.093,5.393 -4.867,9.306 6.104,19.486 9.199,39.839 9.199,60.494 0,3.041 -0.076,6.144 -0.23,9.484 -0.092,1.967 0.602,3.892 1.93,5.347 1.327,1.456 3.178,2.325 5.145,2.415 l 89.031,4.113 c 0.118,0.005 0.234,0.008 0.35,0.008 3.944,0 7.228,-3.103 7.414,-7.083 0.229,-4.955 0.342,-9.627 0.342,-14.284 -0.002,-31.214 -4.696,-62.026 -13.958,-91.581 z" /><path
style="fill:#65b1dd;fill-opacity:1"
id="path19"
d="m 324.99068,306.49081 c -1.281,0 -2.555,0.042 -3.824,0.11 l -120.65,-71.185 c -2.953,-1.745 -6.702,-1.308 -9.176,1.065 -2.476,2.371 -3.07,6.099 -1.456,9.121 l 65.815,123.355 c -0.242,2.376 -0.371,4.775 -0.371,7.195 0,18.608 7.246,36.101 20.403,49.258 13.158,13.158 30.652,20.404 49.26,20.404 18.608,0 36.101,-7.248 49.258,-20.404 13.158,-13.157 20.403,-30.65 20.403,-49.258 0,-18.608 -7.246,-36.101 -20.403,-49.258 -13.157,-13.157 -30.652,-20.403 -49.259,-20.403 z" /></g></g><g
id="g21" /><g
id="g23" /><g
id="g25" /><g
id="g27" /><g
id="g29" /><g
id="g31" /><g
id="g33" /><g
id="g35" /><g
id="g37" /><g
id="g39" /><g
id="g41" /><g
id="g43" /><g
id="g45" /><g
id="g47" /><g
id="g49" /></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

+63 -31
View File
@@ -57,7 +57,6 @@
<script type="text/javascript" src="scripts/app.js"></script>
<script type="text/javascript" src="scripts/menu.js"></script>
<script type="text/javascript" src="scripts/helper.js"></script>
<script type="text/javascript" src="scripts/angular-gettext-cli_compiled_js_output.js"></script>
@@ -90,6 +89,9 @@
<script type="text/javascript" src="scripts/controllers/LocalDatabaseController.js"></script>
<script type="text/javascript" src="scripts/controllers/DeleteController.js"></script>
<script type="text/javascript" src="scripts/controllers/CaptchaController.js"></script>
<script type="text/javascript" src="scripts/controllers/RestoreWizardController.js"></script>
<script type="text/javascript" src="scripts/controllers/AddWizardController.js"></script>
<script type="text/javascript" src="scripts/controllers/PauseController.js"></script>
<script type="text/javascript" src="scripts/filters/timeremaining.js"></script>
<script type="text/javascript" src="scripts/filters/highlight.js"></script>
@@ -121,10 +123,16 @@
<div class="container">
<div class="header">
<div class="logo">
<a href="#/">
<img class="mainlogo" ng-src="{{brandingService.appLogoPath}}"/>
</a>
<div class="logotext">
<a href="#/" class="home">{{brandingService.appName}}</a>
<div class="build-suffix" ng-hide="systemInfo.ServerVersionType == 'stable' || systemInfo.ServerVersionType == ''">{{systemInfo.ServerVersionType}}</div>
<div class="powered-by">{{brandingService.appSubtitle}}</div>
</div>
</div>
<div class="donate" ng-hide="systemInfo.SuppressDonationMessages">
<span translate>Donate</span>
@@ -139,55 +147,79 @@
</div>
<a href class="menubutton hidden" data-target="mainmenu" translate>Menu</a>
<div class="action-icons-small">
<div class="pause" ng-class="{'active': state.programState != 'Running'}" ng-click="pauseOptions()" title="{{state.programState == 'Running' ? 'Click to see the pause options' : 'Click to resume'}}"></div>
<div class="throttle" ng-click="throttleOptions()"></div>
</div>
<div class="statepadding">
<div class="state" ng-controller="StateController">
<div ng-hide="state.programState == 'Running'">
<strong style="margin-right: 0px;" translate>Duplicati is paused</strong>
- <a href ng-click="sendResume()" translate>resume now</a>
</div>
<div ng-hide="activeBackup == null">
<strong>{{'Running task:' | translate}} </strong>
{{activeBackup.Backup.Name}}
<a class="button" ng-click="stopTask()" ng-show="StopReqId == state.activeTask.Item1" translate>Force stop</a>
<a class="button" ng-click="stopTask()" ng-hide="StopReqId == state.activeTask.Item1" translate>Stop</a>
<progress-bar ng-text="StateText" ng-progress="Progress"></progress-bar>
</div>
<div ng-show="activeBackup == null &amp;&amp; state.activeTask != null">
<strong translate>Running task</strong>
<a class="button" ng-click="stopTask()" ng-show="StopReqId == state.activeTask.Item1" translate>Force stop</a>
<a class="button" ng-click="stopTask()" ng-hide="StopReqId == state.activeTask.Item1" translate>Stop</a>
</div>
<div ng-show="state.activeTask == null &amp;&amp; nextTask != null">
<strong>{{'Next task:' | translate}} </strong>
{{nextTask.Backup.Name}}
</div>
<div ng-show="state.activeTask == null &amp;&amp; nextTask == null &amp;&amp; nextScheduledTask != null"><strong translate>Next scheduled task:</strong> {{nextScheduledTask.Backup.Name}} <span title="{{nextScheduledTime | parsetimestamp}}">{{nextScheduledTime | moment: 'calendar'}}</span></div>
<div ng-show="state.activeTask == null &amp;&amp; nextTask == null &amp;&amp; nextScheduledTask == null" translate>No scheduled tasks</div>
</div>
<div class="action-icons">
<div class="pause" ng-class="{'active': state.programState != 'Running'}" ng-click="pauseOptions()" title="{{state.programState == 'Running' ? 'Click to see the pause options' : 'Click to resume'}}"></div>
<div class="throttle" ng-click="throttleOptions()"></div>
</div>
</div>
</div>
<div class="body">
<div class="mainmenu menu" id="mainmenu">
<ul>
<li>
<a href="#/" class="home" translate>Home</a>
<a href="#/" class="home" ng-class="{active: current_page == 'home'}" translate>Home</a>
</li>
<li>
<a href="#/add" class="add" translate>Add backup</a>
<a class="add" href="#/addstart" ng-class="{active: current_page == 'add'}" translate>Add backup</a>
</li>
<li>
<a href="#/restoredirect" class="restore" translate>Restore backup</a>
</li>
<li ng-show="state.programState == 'Running'">
<a href class="pause" id="contextmenulink_pause"><span translate>Pause</span></a>
<ul class="contextmenu" id="contextmenu_pause">
<li>
<a href ng-click="pause('5m')" class="pause-5" translate translate-params-number="5">{{number}} Minutes</a>
</li>
<li>
<a href ng-click="pause('10m')" class="pause-10" translate translate-params-number="10">{{number}} Minutes</a>
</li>
<li>
<a href ng-click="pause('15m')" class="pause-15" translate translate-params-number="15">{{number}} Minutes</a>
</li>
<li>
<a href ng-click="pause('30m')" class="pause-30" translate translate-params-number="30">{{number}} Minutes</a>
</li>
<li>
<a href ng-click="pause('1h')" class="pause-60" translate translate-params-number="1">{{number}} Hour</a>
</li>
<li>
<a href ng-click="pause()" class="pause-x" translate>Until resumed</a>
</li>
</ul>
<a href="#/restorestart" ng-class="{active: current_page == 'restore'}" class="restore" translate>Restore</a>
</li>
<li ng-hide="state.programState == 'Running'">
<a href ng-click="resume()" id="resume" class="resume" translate>Resume</a>
</li>
<li>
<a href="#/settings" class="settings" translate>Settings</a>
<a href="#/settings" class="settings" ng-class="{active: current_page == 'settings'}" translate>Settings</a>
</li>
<li>
<a href="#/log" class="log" translate>Show log</a>
<a href="#/log" class="log" ng-class="{active: current_page == 'log'}" translate>Show log</a>
</li>
<li>
<a href="#/about" class="about" translate>About</a>
<a href="#/about" class="about" ng-class="{active: current_page == 'about'}" translate>About</a>
</li>
<li ng-show="isLoggedIn">
<a href ng-click="log_out()" class="logout" translate>Log out</a>
+327 -61
View File
@@ -93,6 +93,13 @@ textarea {
overflow: auto;
}
.not-clickable {
cursor: default !important;
> a, span, div {
cursor: default !important;
}
}
.ui-match {
font-weight: bold;
color: darkgreen;
@@ -368,18 +375,39 @@ ul.notification {
}
.logo {
img.mainlogo {
height: 64px;
width: 64px;
float: left;
padding-right: 8px;
padding-top: 2px;
}
div.logotext {
float: left;
}
a {
float: left;
display: block;
line-height: normal;
}
div.build-suffix {
clear: both;
display: inline;
float: left;
font-size: 16px;
line-height: 16px;
}
div.powered-by {
font-size: 16px;
margin: 0px;
line-height: 16px;
float: left;
padding: 0px;
margin-top: -25px;
margin-left: 10px;
margin-left: 5px;
}
}
@@ -414,10 +442,10 @@ body {
position: relative;
.header {
height: 70px;
line-height: 70px;
background: @headerBg;
overflow: hidden;
min-height: 70px;
a {
color: @lColor;
@@ -432,9 +460,83 @@ body {
font-size: 30px;
font-weight: 700;
float: left;
padding-left: 50px;
padding-left: 40px;
}
.statepadding {
padding-right: 90px;
margin-left: 320px;
}
.state {
float: left;
color: @hColor;
width: 545px;
padding: 13px 15px;
margin: 10px 20px;
border: 1px @hColor solid;
font-weight: 300;
font-size: 18px;
overflow: hidden;
line-height: normal;
display: inline-block;
background-color: white;
text-overflow: ellipsis;
strong {
display: inline-block;
margin-right: 10px;
}
.button {
position: static;
margin-top: 70px;
}
}
.action-icons {
display: inline-block;
line-height: normal;
margin: 10px 0px;
padding: 13px 0px;
float: left;
}
.action-icons-small {
display: none;
float: right;
margin-top: 21px;
line-height: normal;
}
.action-icons,
.action-icons-small {
> .pause {
width: 26px;
height: 26px;
display: inline-block;
cursor: pointer;
background: url('../img/pause.png');
}
> .pause.active {
background: url('../img/resume.png');
}
> .throttle {
width: 26px;
height: 26px;
display: inline-block;
cursor: pointer;
background: url('../img/throttle-inactive.png');
}
> pause.active {
background: url('../img/throttle.png');
}
}
.about-header {
float: right;
padding-right: 20px;
@@ -493,11 +595,6 @@ body {
padding-left: 40px;
float: left;
.contextmenu {
left: 260px;
top: 0px;
}
> ul {
> li {
position: relative;
@@ -509,7 +606,10 @@ body {
display: block;
}
> a:hover,
> a:hover {
color: white;
}
> a.active {
color: white;
}
@@ -524,11 +624,6 @@ body {
> a.pause {
background: url('../img/mainmenu/pause.png') no-repeat 8px 7px;
> span {
padding-right: 25px;
background: url('../img/mainmenu/arrow_right.png') right center no-repeat;
}
}
> a.resume {
@@ -555,52 +650,102 @@ body {
background: url('../img/mainmenu/about.png') no-repeat 8px 7px;
}
> a.add:hover,
> a.pause {
> span {
padding-right: 25px;
background: url('../img/mainmenu/arrow_right.png') right center no-repeat;
}
}
> a.home.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/home.png') no-repeat 8px 7px;
}
> a.add.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/add.png') no-repeat 8px 7px;
}
> a.restore.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;
}
> a.pause.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/pause.png') no-repeat 8px 7px;
}
> a.resume.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;
}
> a.settings.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;
}
> a.log.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/log.png') no-repeat 8px 7px;
}
> a.about.active {
background: lighten(@lColor, 15%) url('../img/mainmenu/over/about.png') no-repeat 8px 7px;
}
> a.pause.open.active {
> span {
background: url('../img/mainmenu/over/arrow_down.png') right center no-repeat;
}
}
> a.add:hover {
background: @lColor url('../img/mainmenu/over/add.png') no-repeat 8px 7px;
}
> a.restore:hover,
> a.restore.active {
> a.restore:hover {
background: @lColor url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;
}
> a.pause:hover,
> a.pause.active {
> a.pause:hover {
background: @lColor url('../img/mainmenu/over/pause.png') no-repeat 8px 7px;
}
> a.pause:hover {
> span {
background: url('../img/mainmenu/over/arrow_right.png') right center no-repeat;
}
}
> a.resume:hover,
> a.resume.active {
> a.pause.open:hover {
> span {
background: url('../img/mainmenu/over/arrow_down.png') right center no-repeat;
}
}
> a.pause.open {
> span {
background: url('../img/mainmenu/arrow_down.png') right center no-repeat;
}
}
> a.resume:hover {
background: @lColor url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;
}
> a.settings:hover,
> a.settings.active {
> a.settings:hover {
background: @lColor url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;
}
> a.log:hover,
> a.log.active {
> a.log:hover {
background: @lColor url('../img/mainmenu/over/log.png') no-repeat 8px 7px;
}
> a.logout:hover,
> a.logout.active {
> a.logout:hover {
background: @lColor url('../img/mainmenu/over/logout.png') no-repeat 8px 7px;
}
> a.home:hover,
> a.home.active {
> a.home:hover {
background: @lColor url('../img/mainmenu/over/home.png') no-repeat 8px 7px;
}
> a.about:hover,
> a.about.active {
> a.about:hover {
background: @lColor url('../img/mainmenu/over/about.png') no-repeat 8px 7px;
}
}
@@ -627,6 +772,7 @@ body {
padding: 5px;
li {
a {
color: @lColor;
font-size: 15px;
@@ -636,10 +782,12 @@ body {
min-width: 200px;
padding: 4px 10px;
white-space: nowrap;
padding-left: 45px;
overflow: hidden;
text-overflow: ellipsis;
}
a:hover,
a.active {
a:hover {
background: @lColor;
color: white;
}
@@ -660,28 +808,7 @@ body {
display: inline-block;
}
.state {
color: @hColor;
width: 575px;
padding: 13px 15px;
border: 1px @hColor solid;
font-weight: 300;
font-size: 18px;
overflow: hidden;
strong {
display: inline-block;
margin-right: 10px;
}
.button {
position: static;
margin-top: 70px;
}
}
.tasks {
padding-top: 20px;
.tasklist {
.task {
@@ -695,6 +822,11 @@ body {
padding-bottom: 20px;
}
.task:first-child {
padding-top: 0px;
border-top: 0px none;
}
a {
font-size: 30px;
padding-left: 55px;
@@ -1147,7 +1279,7 @@ body {
div.restore {
@legends-steps: 2;
@legends-width: 450px;
@legends-width: 700px;
@legends-padding-left: (700px - @legends-width) / 2;
@circle-width: 43px;
@step-width: @legends-width / @legends-steps;
@@ -1169,6 +1301,40 @@ body {
}
}
div.restore.restore-direct {
@legends-steps: 4;
@legends-width: 700px;
@legends-padding-left: (700px - @legends-width) / 2;
@circle-width: 43px;
@step-width: @legends-width / @legends-steps;
.steps {
margin-left: (@step-width - @circle-width) / 2 + @legends-padding-left;
.step {
padding-left: @step-width - @circle-width;
}
}
.steps-legend {
padding-left: @legends-padding-left;
li {
width: @step-width;
}
}
.step:first-child {
padding-left: 0;
background: transparent;
}
.steps-legend {
padding-left: 20px;
}
}
div.headerthreedotmenu {
margin: 20px 0 20px 0;
@@ -1555,6 +1721,51 @@ div.modal-dialog {
}
}
.restorewizard,
.addwizard {
form.styled {
ul {
margin: 20px;
margin-left: 0px;
}
input[type="radio"] {
width: 20px;
margin-left: 5px;
margin-right: 5px;
}
label {
width: auto;
line-height: normal;
}
div.subtext {
clear: both;
margin-left: 30px;
padding-top: 5px;
color: lightgray;
}
}
}
.pauseoptions {
form.styled {
li {
line-height: normal;
padding: 0px;
input {
height: auto;
margin-top: 8px;
margin-right: 8px;
width: auto;
}
}
}
}
/* Progress bar styles from Bootstrap */
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
@@ -1816,13 +2027,44 @@ div.modal-dialog {
}
// Smaller screen sizes
@media (max-width: 1100px) {
@media (max-width: 1200px) {
body {
.container {
.header {
.donate {
display: none;
}
}
}
}
}
@media (max-width: 1100px) {
body {
.container {
.header {
min-height: 140px;
.statepadding {
padding-right: 90px;
margin-left: 0px;
}
.state {
width: 100%;
margin: 10px 40px;
clear: left;
float: left;
}
.action-icons {
display: none;
}
.action-icons-small {
display: inline-block;
}
.menubutton {
display: block;
@@ -1837,7 +2079,7 @@ div.modal-dialog {
color: #8f8f8f;
float: right;
top: 10px;
width: 80px;
padding-left: 20px;
text-transform: uppercase;
text-align: right;
}
@@ -1877,7 +2119,7 @@ div.modal-dialog {
.content {
float: none;
padding: 50px 20px;
padding: 20px 20px;
margin: 0 auto;
.state {
@@ -1925,6 +2167,19 @@ div.modal-dialog {
body {
.container {
.header {
.logo {
padding-left: 10px;
}
.statepadding {
padding-right: 60px;
}
.state {
margin-left: 10px;
}
}
.body {
.content {
div.add,
@@ -2013,7 +2268,7 @@ div.modal-dialog {
.container {
.body {
padding-bottom: 0;
padding-bottom: 10px;
.content {
div.add,
@@ -2213,12 +2468,23 @@ div.modal-dialog {
.container {
.header {
.logo {
padding-left: 20px;
padding-left: 5px;
}
.menubutton {
margin-right: 5px;
}
.state {
margin-left: 5px;
}
.statepadding {
padding-right: 40px;
}
.menubutton {
padding-left: 10px;
}
}
@@ -23,6 +23,12 @@ backupApp.config(['$routeProvider',
when('/add', {
templateUrl: 'templates/addoredit.html'
}).
when('/restorestart', {
templateUrl: 'templates/restorewizard.html'
}).
when('/addstart', {
templateUrl: 'templates/addwizard.html'
}).
when('/edit/:backupid', {
templateUrl: 'templates/addoredit.html'
}).
@@ -76,3 +82,25 @@ backupApp.run(function($injector) {
$injector.get('ProxyService');
} catch(e) {}
});
// Registers a global parseInt function
angular.module('backupApp').run(function($rootScope){
$rootScope.parseInt = function(str) {
return parseInt(str);
};
});
// Register a global back function
/*backupApp.run(function ($rootScope, $location) {
var history = [];
$rootScope.$on('$routeChangeSuccess', function() {
history.push($location.$$path);
});
$rootScope.back = function () {
var prevUrl = history.length > 1 ? history.splice(-2)[0] : "/home";
$location.path(prevUrl);
};
});*/
@@ -0,0 +1,13 @@
backupApp.controller('AddWizardController', function($scope, $location, gettextCatalog) {
$scope.selection = {
style: 'blank'
};
$scope.nextPage = function() {
if ($scope.selection.style == 'blank')
$location.path('/add');
else
$location.path('/import');
};
});
@@ -1,9 +1,10 @@
backupApp.controller('AppController', function($scope, $cookies, AppService, BrandingService, ServerStatus, SystemInfo, AppUtils) {
backupApp.controller('AppController', function($scope, $cookies, $location, AppService, BrandingService, ServerStatus, SystemInfo, AppUtils, DialogService, gettextCatalog) {
$scope.brandingService = BrandingService.watch($scope);
$scope.state = ServerStatus.watch($scope);
$scope.systemInfo = SystemInfo.watch($scope);
$scope.localized = {};
$scope.location = $location;
$scope.doReconnect = function() {
ServerStatus.reconnect();
@@ -26,4 +27,55 @@ backupApp.controller('AppController', function($scope, $cookies, AppService, Bra
}, AppUtils.connectionError);
};
$scope.pauseOptions = function() {
if ($scope.state.programState != 'Running') {
$scope.resume();
} else {
DialogService.htmlDialog(
gettextCatalog.getString('Pause options'),
'templates/pause.html',
[gettextCatalog.getString('OK'), gettextCatalog.getString('Cancel')],
function(index, text, cur) {
if (index == 0 && cur != null && cur.time != null) {
var time = cur.time;
$scope.pause(time == 'infinite' ? '' : time);
}
}
);
}
};
$scope.throttleOptions = function() {
alert('Throttle options are not implemented yet');
};
function updateCurrentPage() {
if ($location.$$path == '/')
$scope.current_page = 'home';
else if ($location.$$path == '/addstart' || $location.$$path == '/add' || $location.$$path == '/import')
$scope.current_page = 'add';
else if ($location.$$path == '/restorestart' || $location.$$path == '/restore' || $location.$$path == '/restoredirect' || $location.$$path.indexOf('/restore/') == 0)
$scope.current_page = 'restore';
else if ($location.$$path == '/settings')
$scope.current_page = 'settings';
else if ($location.$$path == '/log')
$scope.current_page = 'log';
else if ($location.$$path == '/about')
$scope.current_page = 'about';
else
$scope.current_page = '';
};
$scope.$on('serverstatechanged', function() {
// Unwanted jQuery interference, but the menu is built with this
if (ServerStatus.state.programState == 'Paused') {
$('#contextmenu_pause').removeClass('open');
$('#contextmenulink_pause').removeClass('open');
}
});
//$scope.$on('$routeUpdate', updateCurrentPage);
$scope.$watch('location.$$path', updateCurrentPage);
updateCurrentPage();
});
@@ -1,7 +1,6 @@
backupApp.controller('DialogController', function($scope, DialogService, gettextCatalog) {
$scope.state = DialogService.watch($scope);
function showTooltip(elem, msg) {
elem.addEventListener('mouseleave', function(e) {
e.currentTarget.setAttribute('class', 'button');
@@ -27,7 +26,7 @@ backupApp.controller('DialogController', function($scope, DialogService, gettext
DialogService.dismissCurrent();
if (cur.callback)
cur.callback(index, input);
cur.callback(index, input, cur);
};
});
@@ -0,0 +1,5 @@
backupApp.controller('PauseController', function($scope, $location, gettextCatalog) {
$scope.selection = $scope.$parent.state.CurrentItem;
$scope.selection.time = 'infinite';
});
@@ -1,4 +1,4 @@
backupApp.controller('RestoreController', function ($rootScope, $scope, $routeParams, $location, AppService, AppUtils, SystemInfo, ServerStatus, DialogService, gettextCatalog) {
backupApp.controller('RestoreController', function ($rootScope, $scope, $routeParams, $location, AppService, AppUtils, SystemInfo, ServerStatus, DialogService, BackupList, gettextCatalog) {
$scope.SystemInfo = SystemInfo.watch($scope);
$scope.AppUtils = AppUtils;
@@ -318,11 +318,14 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
var version = $scope.RestoreVersion + '';
var stamp = filesetStamps[version];
$scope.restore_step = 2;
function handleError(resp) {
var message = resp.statusText;
if (resp.data != null && resp.data.Message != null)
message = resp.data.Message;
$scope.restore_step = 1;
$scope.connecting = false;
$scope.ConnectionProgress = '';
DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to connect: {{message}}', { message: message }));
@@ -375,6 +378,7 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to build temporary database: {{message}}', { message: resp.data.ErrorMessage }));
$scope.connecting = false;
$scope.ConnectionProgress = '';
$scope.restore_step = 1;
}
}, handleError);
});
@@ -400,7 +404,7 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
if (resp.data.Status == 'Completed')
{
$scope.restore_step = 2;
$scope.restore_step = 3;
}
else
{
@@ -411,6 +415,7 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
if (resp.data != null && resp.data.Message != null)
message = resp.data.Message;
$scope.restore_step = 1;
$scope.connecting = false;
$scope.ConnectionProgress = '';
DialogService.dialog(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to connect: {{message}}', { message: message }));
@@ -419,11 +424,21 @@ backupApp.controller('RestoreController', function ($rootScope, $scope, $routePa
$scope.onClickComplete = function () {
$location.path('/');
}
};
$scope.trySetStep = function(pg) {
if ($scope.restore_step < 2)
$scope.restore_step = pg;
};
$scope.BackupID = $routeParams.backupid;
$scope.IsBackupTemporary = parseInt($scope.BackupID) != $scope.BackupID;
if (!$scope.IsBackupTemporary) {
$scope.$on('backuplistchanged', function() { $scope.Backup = BackupList.lookup[$scope.BackupID]; });
$scope.Backup = BackupList.lookup[$scope.BackupID];
}
// We pass in the filelist through a global variable
// ... bit ugly, but we do not want to do two remote queries,
// ... nor do we want to pass the information through the url
@@ -0,0 +1,14 @@
backupApp.controller('RestoreWizardController', function($scope, $location, BackupList, gettextCatalog) {
$scope.backups = BackupList.watch($scope);
$scope.selection = {
backupid: '-1'
};
$scope.nextPage = function() {
if ($scope.selection.backupid == '-1')
$location.path('/restoredirect');
else
$location.path('/restore/' + $scope.selection.backupid);
};
});
@@ -1,5 +0,0 @@
angular.module('backupApp').run(function($rootScope){
$rootScope.parseInt = function(str) {
return parseInt(str);
};
});
@@ -1,13 +1,11 @@
$(document).ready(function() {
$('html').on('click', function(e) {
$('#mainmenu').removeClass('mobile-open');
$('#threedotmenu_add_general').removeClass('open');
$('#threedotmenu_add_destination').removeClass('open');
$('#threedotmenu_add_destination_adv').removeClass('open');
$('#threedotmenu_add_source_folders').removeClass('open');
$('#threedotmenu_add_source_filters').removeClass('open');
$('#threedotmenu_add_options_adv').removeClass('open');
$('#contextmenu_pause').removeClass('open');
});
$('body').on('click', '.menubutton', function(e) {
@@ -16,12 +14,6 @@ $(document).ready(function() {
$('#mainmenu').toggleClass('mobile-open');
});
$('body').on('click', '#threedotmenubutton_add_general', function(e) {
e.stopPropagation();
e.preventDefault();
$('#threedotmenu_add_general').toggleClass('open');
});
$('body').on('click', '#threedotmenubutton_add_destination', function(e) {
e.stopPropagation();
e.preventDefault();
@@ -52,9 +44,4 @@ $(document).ready(function() {
$('#threedotmenu_add_options_adv').toggleClass('open');
});
$('body').on('click', '#contextmenulink_pause', function(e) {
e.stopPropagation();
e.preventDefault();
$('#contextmenu_pause').toggleClass('open');
});
});
@@ -1,6 +1,10 @@
backupApp.service('BrandingService', function() {
var state = { 'appName': 'Duplicati', 'appSubtitle': null };
var state = {
'appName': 'Duplicati',
'appSubtitle': null,
'appLogoPath': '../img/logo.png'
};
this.state = state;
this.watch = function(scope, m) {
File diff suppressed because one or more lines are too long
@@ -33,22 +33,8 @@
<div class="step step1" ng-class="{active: CurrentStep == 0}">
<div class="form">
<form class="styled">
<div class="headerthreedotmenu">
<h2 translate>General backup settings</h2>
<div class="contextmenu_container">
<a href title="{{'Menu' | translate}}"><img src="img/three_dots.png" id="threedotmenubutton_add_general" class="threedotmenubutton"/></a>
<div class="contextmenu" id="threedotmenu_add_general">
<ul>
<li>
<a href="#import" translate>Import a backup configuration from file</a>
</li>
</ul>
</div>
</div>
</div>
<div class="input text">
<label for="name" translate>Name</label>
<input type="text" name="name" id="name" ng-model="Backup.Name" placeholder="{{'My Photos' | translate}}"/>
@@ -131,7 +117,7 @@
<form class="styled">
<div class="box browser">
<div class="headerthreedotmenu">
<h2 translate>Folders</h2>
<h2 translate>Source data</h2>
<div class="contextmenu_container">
<a href title="{{'Menu' | translate}}"><img src="img/three_dots.png" id="threedotmenubutton_add_source_folders" class="threedotmenubutton"/></a>
@@ -0,0 +1,31 @@
<div ng-controller="AddWizardController" class="addwizard">
<h1><div translate>Add a new backup</div></h1>
<form class="styled">
<ul>
<li class="input" ng-click="selection.style = 'blank'">
<input type="radio" name="blank" id="blank" ng-model="selection.style" value="blank">
<label for="direct" translate>Configure a new backup</label>
<div class="subtext" translate>Enter configuration details</div>
</li>
<li class="input" ng-click="selection.style = 'importfile'">
<input type="radio" name="blank" id="blank" ng-model="selection.style" value="importfile">
<label for="direct">Import from a file</label>
<div class="subtext">Load the a configuration from an exported job</div>
</li>
<!--<li class="input" ng-click="selection.style = 'importremote'">
<input type="radio" name="blank" id="blank" ng-model="selection.style" value="importremote">
<label for="direct">From a remote backup</label>
<div class="subtext">Import configuration from a backup folder</div>
</li> -->
</ul>
<div class="buttons">
<input class="submit next" type="button" ng-click="nextPage()" value="{{'Next' | translate}} &gt;" />
</div>
</form>
</div>
@@ -1,40 +1,4 @@
<div class="home">
<div class="state" ng-controller="StateController">
<div ng-hide="state.programState == 'Running'">
<strong style="margin-right: 0px;" translate>Duplicati is paused</strong>
- <a href ng-click="sendResume()" translate>resume now</a>
</div>
<div ng-hide="activeBackup == null">
<strong>{{'Running task:' | translate}} </strong>
{{activeBackup.Backup.Name}}
<a class="button" ng-click="stopTask()" ng-show="StopReqId == state.activeTask.Item1" translate>Force stop</a>
<a class="button" ng-click="stopTask()" ng-hide="StopReqId == state.activeTask.Item1" translate>Stop</a>
<progress-bar ng-text="StateText" ng-progress="Progress"></progress-bar>
</div>
<div ng-show="activeBackup == null &amp;&amp; state.activeTask != null">
<strong translate>Running task</strong>
<a class="button" ng-click="stopTask()" ng-show="StopReqId == state.activeTask.Item1" translate>Force stop</a>
<a class="button" ng-click="stopTask()" ng-hide="StopReqId == state.activeTask.Item1" translate>Stop</a>
</div>
<div ng-show="state.activeTask == null &amp;&amp; nextTask != null">
<strong>{{'Next task:' | translate}} </strong>
{{nextTask.Backup.Name}}
</div>
<div ng-show="state.activeTask == null &amp;&amp; nextTask == null &amp;&amp; nextScheduledTask != null"><strong translate>Next scheduled task:</strong> {{nextScheduledTask.Backup.Name}} <span title="{{nextScheduledTime | parsetimestamp}}">{{nextScheduledTime | moment: 'calendar'}}</span></div>
<div ng-show="state.activeTask == null &amp;&amp; nextTask == null &amp;&amp; nextScheduledTask == null" translate>No scheduled tasks</div>
</div>
<div class="tasks" ng-controller="HomeController">
<div class="tasklist">
<div ng-repeat="item in backups" class="task">
@@ -15,7 +15,6 @@
<input type="hidden" name="callback" value="{{CallbackMethod}}">
<div class="buttons" ng-hide="Connecting">
<a href="#add" class="submit" translate>Cancel</a>
<a href ng-click="doSubmit()" translate>Import</a>
</div>
@@ -0,0 +1,36 @@
<div ng-controller="PauseController" class="pauseoptions">
<form class="styled">
<ul>
<li class="input" ng-click="selection.time = '5m'">
<input type="radio" name="fivemin" id="fivemin" ng-model="selection.time" value="5m">
<label for="fivemin" class="pause-5" translate translate-params-number="5">{{number}} Minutes</label>
</li>
<li class="input" ng-click="selection.time = '10m'">
<input type="radio" name="tenmin" id="tenmin" ng-model="selection.time" value="10m">
<label for="tenmin" class="pause-10" translate translate-params-number="10">{{number}} Minutes</label>
</li>
<li class="input" ng-click="selection.time = '15m'">
<input type="radio" name="fifteenmin" id="fifteenmin" ng-model="selection.time" value="15m">
<label for="fifteenmin" class="pause-15" translate translate-params-number="15">{{number}} Minutes</label>
</li>
<li class="input" ng-click="selection.time = '30m'">
<input type="radio" name="thirtymin" id="thirtymin" ng-model="selection.time" value="30m">
<label for="thirtymin" class="pause-30" translate translate-params-number="30">{{number}} Minutes</label>
</li>
<li class="input" ng-click="selection.time = '1h'">
<input type="radio" name="onehour" id="onehour" ng-model="selection.time" value="1h">
<label for="onehour" class="pause-60" translate translate-params-number="1">{{number}} Hour</label>
</li>
<li class="input" ng-click="selection.time = 'infinite'">
<input type="radio" name="infinite" id="infinite" ng-model="selection.time" value="infinite">
<label for="infinite" class="pause-x" translate>Until resumed</label>
</li>
</ul>
</form>
</div>
@@ -1,10 +1,49 @@
<script>$(function() { $( ".resizable" ).resizable(); });</script>
<div class="restore" ng-controller="RestoreController">
<div class="restore" ng-class="{'restore-direct': IsBackupTemporary}" ng-controller="RestoreController">
<div class="steps" ng-show="IsBackupTemporary &amp;&amp; restore_step &lt; 2">
<div class="step step1 not-clickable">
<span>1</span>
</div>
<div class="step step2 not-clickable">
<span>2</span>
</div>
<div class="step step3" ng-class="{active: restore_step == 0}" ng-click="trySetStep(0)">
<span>3</span>
</div>
<div class="step step4" ng-class="{active: restore_step == 1}" ng-click="trySetStep(1)">
<span>4</span>
</div>
</div>
<ol class="steps-legend" ng-show="IsBackupTemporary &amp;&amp; restore_step &lt; 2">
<li class="step1 not-clickable" translate>Backup location</li>
<li class="step2 not-clickable" translate>Encryption</li>
<li ng-class="{active: restore_step == 0}" class="step3" ng-click="trySetStep(0)" translate>Select files</li>
<li ng-class="{active: restore_step == 1}" class="step4" ng-click="trySetStep(1)" translate>Restore options</li>
</ol>
<div class="steps" ng-hide="IsBackupTemporary || restore_step &gt; 1">
<div class="step step1" ng-class="{active: restore_step == 0}" ng-click="trySetStep(0)">
<span>1</span>
</div>
<div class="step step2" ng-class="{active: restore_step == 1}" ng-click="trySetStep(1)">
<span>2</span>
</div>
</div>
<ol class="steps-legend" ng-hide="IsBackupTemporary || restore_step &gt; 1">
<li ng-class="{active: restore_step == 0}" class="step1" ng-click="trySetStep(0)" translate>Select files</li>
<li ng-class="{active: restore_step == 1}" class="step2" ng-click="trySetStep(1)" translate>Restore options</li>
</ol>
<form id="restore" class="styled">
<div ng-show="restore_step == 0 &amp;&amp; connecting == false">
<h1 translate>Restore files</h1>
<h1 ng-show="IsBackupTemporary" translate>Restore files</h1>
<h1 ng-hide="IsBackupTemporary" translate translate-params-backupname="Backup.Backup.Name">Restore files from {{backupname}}</h1>
<div class="input timestamp">
<label for="restoreversion" translate>Restore from</label>
@@ -95,7 +134,7 @@
</div>
<div ng-show="restore_step == 2 &amp;&amp; connecting == false">
<div ng-show="restore_step == 3 &amp;&amp; connecting == false">
<h3>
<div class="hint-text" translate>Your files and folders have been restored successfully.</div>
<div ng-hide="SystemInfo.SuppressDonationMessages">
@@ -1,4 +1,4 @@
<div class="restore" ng-controller="RestoreDirectController">
<div class="restore restore-direct" ng-controller="RestoreDirectController">
<form id="restore" class="styled">
<div ng-show="!connecting">
<div class="steps">
@@ -8,19 +8,27 @@
<div class="step step2" ng-class="{active: CurrentStep == 1}" ng-click="CurrentStep = 1">
<span>2</span>
</div>
<div class="step step3 not-clickable">
<span>3</span>
</div>
<div class="step step4 not-clickable">
<span>4</span>
</div>
</div>
<!-- .steps -->
<ol class="steps-legend">
<li ng-class="{active: CurrentStep == 0}" class="step1" ng-click="CurrentStep = 0" translate>Destination</li>
<li ng-class="{active: CurrentStep == 1}" class="step2" ng-click="CurrentStep = 1" translate>Options</li>
<li ng-class="{active: CurrentStep == 0}" class="step1" ng-click="CurrentStep = 0" translate>Backup location</li>
<li ng-class="{active: CurrentStep == 1}" class="step2" ng-click="CurrentStep = 1" translate>Encryption</li>
<li class="step3 not-clickable" translate>Select files</li>
<li class="step4 not-clickable" translate>Restore options</li>
</ol>
<!-- .steps-legend -->
<div class="steps-boxes">
<div class="step step1" ng-class="{active: CurrentStep == 0}">
<div class="headerthreedotmenu">
<h2 translate>Backup destination</h2>
<h2 translate>Backup location</h2>
<div class="contextmenu_container">
<a href title="{{'Menu' | translate}}"><img src="img/three_dots.png" id="threedotmenubutton_add_destination" class="threedotmenubutton"/></a>
@@ -0,0 +1,27 @@
<div ng-controller="RestoreWizardController" class="restorewizard">
<h1><div translate>Where do you want to restore from?</div></h1>
<form class="styled">
<ul>
<li class="input" ng-click="selection.backupid = '-1'">
<input type="radio" name="direct" id="direct" ng-model="selection.backupid" value="-1">
<label for="direct" translate>Direct restore from backup files ...</label>
<div class="subtext" translate>Point to your backup files and restore from there</div>
</li>
<li class="input" ng-repeat="item in backups" ng-click="selection.backupid = item.Backup.ID" >
<input type="radio" name="direct" id="backup-{{item.Backup.ID}}" ng-model="selection.backupid" value="{{item.Backup.ID}}">
<label for="backup-{{item.Backup.ID}}">{{item.Backup.Name}}</label>
<div ng-hide="item.Backup.Metadata == null || item.Backup.Metadata.TargetSizeString == null" class="subtext" translate translate-n="parseInt(item.Backup.Metadata.BackupListCount)" translate-plural="{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions">{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version</div>
<div ng-show="item.Backup.Metadata == null || item.Backup.Metadata.TargetSizeString == null" class="subtext" translate>Unknown backup size and versions</div>
</li>
</ul>
<div class="buttons">
<input class="submit next" type="button" ng-click="nextPage()" value="{{'Next' | translate}} &gt;" />
</div>
</form>
</div>