mirror of
https://github.com/YunoHost/yunohost-admin.git
synced 2024-09-03 20:06:15 +02:00
removed old sammy app
This commit is contained in:
parent
d8056a1a4b
commit
00d9db0bc9
63 changed files with 0 additions and 15277 deletions
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"sub": true
|
|
||||||
}
|
|
|
@ -1,316 +0,0 @@
|
||||||
/* Source Code Pro */
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-ExtraLight.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLight.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLight.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLight.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLight.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-ExtraLightIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLightIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLightIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLightIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-ExtraLightIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-Light.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Light.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-Light.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-Light.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Light.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-LightIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-LightIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-LightIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-LightIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-LightIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-Regular.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Regular.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-Regular.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-Regular.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Regular.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-It.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-It.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-It.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-It.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-It.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-Medium.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Medium.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-Medium.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-Medium.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Medium.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-MediumIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-MediumIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-MediumIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-MediumIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-MediumIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-Semibold.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Semibold.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-Semibold.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-Semibold.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Semibold.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-SemiboldIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-SemiboldIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-SemiboldIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-SemiboldIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-SemiboldIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-Bold.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Bold.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-Bold.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-Bold.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Bold.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-BoldIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-BoldIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-BoldIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-BoldIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-BoldIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 900;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-Black.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Black.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-Black.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-Black.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-Black.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-weight: 900;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceCodePro-BlackIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-BlackIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceCodePro-BlackIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceCodePro-BlackIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceCodePro-BlackIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-ExtraLight.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLight.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLight.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLight.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLight.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-ExtraLightIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLightIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLightIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLightIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-ExtraLightIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-Light.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Light.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-Light.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-Light.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Light.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-LightIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-LightIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-LightIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-LightIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-LightIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-Regular.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Regular.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-Regular.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-Regular.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Regular.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-It.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-It.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-It.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-It.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-It.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-Semibold.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Semibold.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-Semibold.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-Semibold.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Semibold.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-SemiboldIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-SemiboldIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-SemiboldIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-SemiboldIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-SemiboldIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-Bold.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Bold.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-Bold.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-Bold.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Bold.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-BoldIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-BoldIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-BoldIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-BoldIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-BoldIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 900;
|
|
||||||
font-style: normal;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-Black.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Black.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-Black.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-Black.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-Black.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Sans Pro';
|
|
||||||
font-weight: 900;
|
|
||||||
font-style: italic;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url('@{font-path}SourceSansPro-BlackIt.eot') format('embedded-opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-BlackIt.ttf.woff2') format('woff2'),
|
|
||||||
url('@{font-path}SourceSansPro-BlackIt.otf.woff') format('woff'),
|
|
||||||
url('@{font-path}SourceSansPro-BlackIt.otf') format('opentype'),
|
|
||||||
url('@{font-path}SourceSansPro-BlackIt.ttf') format('truetype');
|
|
||||||
}
|
|
||||||
|
|
1030
src/css/style.less
1030
src/css/style.less
File diff suppressed because it is too large
Load diff
122
src/gulpfile.js
122
src/gulpfile.js
|
@ -1,122 +0,0 @@
|
||||||
// Include Gulp
|
|
||||||
var gulp = require('gulp');
|
|
||||||
|
|
||||||
// Include required Gulp packages
|
|
||||||
var concat = require('gulp-concat'),
|
|
||||||
uglify = require('gulp-uglify'),
|
|
||||||
csslint = require('gulp-csslint'),
|
|
||||||
jshint = require('gulp-jshint'),
|
|
||||||
mustache = require('gulp-mustache'),
|
|
||||||
less = require('gulp-less'),
|
|
||||||
autoprefixer = require('gulp-autoprefixer'),
|
|
||||||
cssmin = require('gulp-cssmin'),
|
|
||||||
imagemin = require('gulp-imagemin'),
|
|
||||||
rename = require('gulp-rename')
|
|
||||||
util = require('gulp-util')
|
|
||||||
gulpif = require('gulp-if')
|
|
||||||
;
|
|
||||||
|
|
||||||
// Environment variables
|
|
||||||
isDev = (util.env.dev) ? true : false;
|
|
||||||
isProduction = !isDev;
|
|
||||||
|
|
||||||
// JS task
|
|
||||||
gulp.task('js', function() {
|
|
||||||
return gulp.src([
|
|
||||||
'node_modules/jquery/dist/jquery.js',
|
|
||||||
'node_modules/handlebars/dist/handlebars.js',
|
|
||||||
'node_modules/handlebars-intl/dist/handlebars-intl-with-locales.js',
|
|
||||||
'node_modules/sammy/lib/sammy.js',
|
|
||||||
'node_modules/sammy/lib/plugins/sammy.handlebars.js',
|
|
||||||
'node_modules/sammy/lib/plugins/sammy.json.js',
|
|
||||||
'node_modules/sammy/lib/plugins/sammy.storage.js',
|
|
||||||
'node_modules/bootstrap/dist/js/bootstrap.js',
|
|
||||||
'node_modules/isotope-layout/dist/isotope.pkgd.js',
|
|
||||||
'js/yunohost/y18n.js',
|
|
||||||
'js/yunohost/main.js',
|
|
||||||
'js/yunohost/helpers.js',
|
|
||||||
'js/yunohost/filters.js',
|
|
||||||
'js/yunohost/events.js',
|
|
||||||
'js/yunohost/controllers/*.js',
|
|
||||||
])
|
|
||||||
.pipe(gulpif(isProduction, uglify()))
|
|
||||||
.pipe(concat('script.min.js'))
|
|
||||||
.pipe(gulp.dest('./dist/js'))
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// JS Lint task
|
|
||||||
gulp.task('js-lint', function() {
|
|
||||||
return gulp.src('js/**/*.js')
|
|
||||||
.pipe(jshint())
|
|
||||||
.pipe(jshint.reporter('default'))
|
|
||||||
.pipe(jshint.reporter('fail'))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fonts
|
|
||||||
gulp.task('fonts', function() {
|
|
||||||
return gulp.src([
|
|
||||||
'node_modules/fork-awesome/fonts/*',
|
|
||||||
'node_modules/source-code-pro/EOT/*.eot',
|
|
||||||
'node_modules/source-code-pro/OTF/*.otf',
|
|
||||||
'node_modules/source-code-pro/TTF/*.ttf',
|
|
||||||
'node_modules/source-code-pro/WOFF/OTF/*.woff',
|
|
||||||
'node_modules/source-code-pro/WOFF2/TTF/*.woff2',
|
|
||||||
'node_modules/source-sans-pro/EOT/*.eot',
|
|
||||||
'node_modules/source-sans-pro/OTF/*.otf',
|
|
||||||
'node_modules/source-sans-pro/TTF/*.ttf',
|
|
||||||
'node_modules/source-sans-pro/WOFF/OTF/*.woff',
|
|
||||||
'node_modules/source-sans-pro/WOFF2/TTF/*.woff2',
|
|
||||||
])
|
|
||||||
.pipe(gulp.dest('./dist/fonts'))
|
|
||||||
});
|
|
||||||
|
|
||||||
// CSS task
|
|
||||||
gulp.task('css', function () {
|
|
||||||
return gulp.src('css/style.less')
|
|
||||||
.pipe(less())
|
|
||||||
.pipe(autoprefixer())
|
|
||||||
.pipe(rename({
|
|
||||||
suffix: '.min'
|
|
||||||
}))
|
|
||||||
.pipe(gulpif(isProduction, cssmin()))
|
|
||||||
.pipe(gulp.dest('./dist/css'))
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// CSS/Less lint task
|
|
||||||
gulp.task('css-lint', function() {
|
|
||||||
return gulp.src('css/style.less')
|
|
||||||
.pipe(less())
|
|
||||||
.pipe(autoprefixer())
|
|
||||||
.pipe(csslint())
|
|
||||||
.pipe(csslint.formatter())
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Images task
|
|
||||||
gulp.task('img', function () {
|
|
||||||
return gulp.src('img/*')
|
|
||||||
.pipe(gulpif(isProduction, imagemin()))
|
|
||||||
.pipe(gulp.dest('./dist/img'))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Views task
|
|
||||||
gulp.task('views', function () {
|
|
||||||
return gulp.src('views/**/*.ms')
|
|
||||||
.pipe(gulp.dest("./dist/views"));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Global build task
|
|
||||||
gulp.task('build', gulp.series('css', 'fonts', 'js', 'img', 'views'));
|
|
||||||
|
|
||||||
// Watch task
|
|
||||||
gulp.task('watch', function() {
|
|
||||||
gulp.watch('js/**/*.js', gulp.series('js'));
|
|
||||||
gulp.watch('css/*.less', gulp.series('css'));
|
|
||||||
gulp.watch('views/**/*.ms', gulp.series('views'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('lint', gulp.series('css-lint', 'js-lint'));
|
|
||||||
|
|
||||||
gulp.task('default', gulp.series('build', 'watch'));
|
|
|
@ -1,91 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>YunoHost admin</title>
|
|
||||||
<meta http-equiv="cache-control" content="no-cache" />
|
|
||||||
<meta name="format-detection" content="telephone=no" />
|
|
||||||
<meta name="viewport" content="user-scalable=no, width=device-width, height=device-height" />
|
|
||||||
<meta name="robots" content="noindex, nofollow">
|
|
||||||
<link rel="stylesheet" media="screen" href="dist/css/style.min.css?version=RANDOMID">
|
|
||||||
<link rel="shortcut icon" href="dist/img/ynhadmin_icon.png">
|
|
||||||
<script type="text/javascript" src="dist/js/script.min.js?version=RANDOMID"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="slider" role="application">
|
|
||||||
|
|
||||||
<header id="masthead" class="page-header">
|
|
||||||
<a href="#/" class="homelink slide back">
|
|
||||||
<img alt="accueil Yunohost" src="dist/img/icon.png" style="width: 70px;">
|
|
||||||
</a>
|
|
||||||
<span class="buttons">
|
|
||||||
<a role="button" class="user-interface-btn" href="/yunohost/sso/" title="Logout" data-y18n-title="user_interface_link">
|
|
||||||
<span data-y18n="user_interface_link">User interface</span>
|
|
||||||
<i class="fa-user"></i>
|
|
||||||
</a>
|
|
||||||
<a role="button" class="logout-btn" href="#/logout" title="Logout" data-y18n-title="logout">
|
|
||||||
<span data-y18n="logout">Logout</span>
|
|
||||||
<i class="fa-sign-out"></i>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div id="flashMessage">
|
|
||||||
<button id="toggle-btn" data-y18n-title="pin">
|
|
||||||
<span class="sr-only">Pin </span><span class="fa-thumb-tack"></span>
|
|
||||||
</button>
|
|
||||||
<button id="clear-btn" data-y18n-title="clear">
|
|
||||||
<span class="sr-only">Clear </span><span class="fa-trash-o"></span>
|
|
||||||
</button>
|
|
||||||
<div class="messages"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="slider-container">
|
|
||||||
|
|
||||||
<div id="slideBack" style="display: none;"></div>
|
|
||||||
|
|
||||||
<div id="main" role="main">
|
|
||||||
<a class="slide" href="#/">
|
|
||||||
<span class="ajax-loader" data-y18n="loading">loading…</span>
|
|
||||||
</a>
|
|
||||||
<noscript>
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<p> <strong> Warning ! </strong> You must enable Javascript to view this page properly. </p>
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="slideTo" style="display: none; z-index: 100;"></div>
|
|
||||||
|
|
||||||
</div><!--/#slider-container-->
|
|
||||||
|
|
||||||
<footer id="page-footer">
|
|
||||||
<div id="page-footer-links">
|
|
||||||
<a href="https://yunohost.org/docs" target="_blank"><i class="fa-book"></i> Documentation</a>
|
|
||||||
<a href="https://yunohost.org/help" target="_blank"><i class="fa-life-ring"></i> Need help?</a>
|
|
||||||
<a href="https://donate.yunohost.org/" target="_blank"><i class="fa-heart"></i> Donate</a>
|
|
||||||
</div>
|
|
||||||
<div id="yunohost-version"></div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
|
|
||||||
<div><div>
|
|
||||||
<header>
|
|
||||||
<button type="button" class="close" data-dismiss="modal">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
<span class="sr-only" data-y18n="close">Close</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="title" id="modalLabel"></h4>
|
|
||||||
</header>
|
|
||||||
<div class="content"></div>
|
|
||||||
<footer>
|
|
||||||
<button type="button" id="modal-cancel" data-modal-action="cancel" data-y18n="cancel">Cancel</button>
|
|
||||||
<button type="button" id="modal-confirm" data-modal-action="confirm" data-y18n="ok">OK</button>
|
|
||||||
</footer>
|
|
||||||
</div></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,791 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apps
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List installed apps
|
|
||||||
app.get('#/apps', function (c) {
|
|
||||||
c.api('GET', '/apps?full', {}, function(data) {
|
|
||||||
var apps = data['apps'];
|
|
||||||
for (var a in apps)
|
|
||||||
{
|
|
||||||
var app = apps[a]
|
|
||||||
app['label'] = app['permissions'][app['id'] + ".main"]['label']
|
|
||||||
}
|
|
||||||
c.arraySortById(apps);
|
|
||||||
c.view('app/app_list', {apps: apps});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function levelToColor(level) {
|
|
||||||
if (level >= 8) {
|
|
||||||
return 'best';
|
|
||||||
}
|
|
||||||
else if (level > 4) {
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
else if (level >= 1) {
|
|
||||||
return 'warning';
|
|
||||||
}
|
|
||||||
else if (isNaN(level)) {
|
|
||||||
return 'default';
|
|
||||||
} else {
|
|
||||||
return 'danger'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stateToColor(state) {
|
|
||||||
if (state === "high-quality") {
|
|
||||||
return 'best';
|
|
||||||
}
|
|
||||||
else if (state === "working") {
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 'danger';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function maintainedStateToColor(state) {
|
|
||||||
if ( state === "request_help" ) {
|
|
||||||
return 'info';
|
|
||||||
}
|
|
||||||
else if ( state === "request_adoption" ) {
|
|
||||||
return 'warning';
|
|
||||||
}
|
|
||||||
else if ( state === "orphaned" ) {
|
|
||||||
return 'danger';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 'success';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function combineColors(stateColor, levelColor, installable) {
|
|
||||||
if (stateColor === "danger" || levelColor === "danger") {
|
|
||||||
return 'danger';
|
|
||||||
}
|
|
||||||
else if (stateColor === "warning" || levelColor === "warning" || levelColor === "default") {
|
|
||||||
return 'warning';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 'info';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractMaintainer(manifest) {
|
|
||||||
if (manifest.maintainer === undefined)
|
|
||||||
{
|
|
||||||
if ((manifest.developer !== undefined) && (manifest.developer.name !== undefined))
|
|
||||||
{
|
|
||||||
return manifest.developer.name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Array.isArray(manifest.maintainer))
|
|
||||||
{
|
|
||||||
maintainers = [];
|
|
||||||
manifest.maintainer.forEach(function(maintainer) {
|
|
||||||
if (maintainer.name !== undefined)
|
|
||||||
{
|
|
||||||
maintainers.push(maintainer.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return maintainers.join(', ');
|
|
||||||
}
|
|
||||||
else if (manifest.maintainer.name !== undefined)
|
|
||||||
{
|
|
||||||
return manifest.maintainer.name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Display catalog home page where users chooses to browse a specific category
|
|
||||||
app.get('#/apps/catalog', function (c) {
|
|
||||||
c.api('GET', '/appscatalog?full&with_categories', {}, function (data) {
|
|
||||||
c.view('app/app_catalog_home', {categories: data["categories"]}, function() {
|
|
||||||
// Configure layout / rendering for app-category-cards
|
|
||||||
$('#category-selector').isotope({
|
|
||||||
itemSelector: '.app-category-card',
|
|
||||||
layoutMode: 'fitRows',
|
|
||||||
transitionDuration: 200
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Display app catalog for a specific category
|
|
||||||
app.get('#/apps/catalog/:category', function (c) {
|
|
||||||
var category_id = c.params['category'];
|
|
||||||
c.api('GET', '/appscatalog?full&with_categories', {}, function (data) {
|
|
||||||
var apps = [];
|
|
||||||
$.each(data['apps'], function(name, app) {
|
|
||||||
|
|
||||||
// Ignore not working apps
|
|
||||||
if (app.state === 'notworking') { return; }
|
|
||||||
|
|
||||||
// Ignore apps not in this category
|
|
||||||
if ((category_id !== "all") && (app.category !== category_id)) { return; }
|
|
||||||
|
|
||||||
app.id = app.manifest.id;
|
|
||||||
app.level = parseInt(app.level);
|
|
||||||
|
|
||||||
if (app.high_quality && app.level > 7)
|
|
||||||
{
|
|
||||||
app.state = "high-quality";
|
|
||||||
}
|
|
||||||
if ( app.maintained === false )
|
|
||||||
{
|
|
||||||
app.maintained = "orphaned";
|
|
||||||
}
|
|
||||||
else if ( app.maintained === true )
|
|
||||||
{
|
|
||||||
app.maintained = "maintained";
|
|
||||||
}
|
|
||||||
|
|
||||||
app.manifest.maintainer = extractMaintainer(app.manifest);
|
|
||||||
var isWorking = (app.state === 'working' || app.state === "high-quality") && app.level > 0;
|
|
||||||
|
|
||||||
app.installable = (!app.installed || app.manifest.multi_instance)
|
|
||||||
app.levelFormatted = isNaN(app.level) ? '?' : app.level;
|
|
||||||
|
|
||||||
app.levelColor = levelToColor(app.level);
|
|
||||||
app.stateColor = stateToColor(app.state);
|
|
||||||
app.maintainedColor = maintainedStateToColor(app.maintained);
|
|
||||||
app.installColor = combineColors(app.stateColor, app.levelColor);
|
|
||||||
|
|
||||||
app.updateDate = app.lastUpdate * 1000 || 0;
|
|
||||||
app.isSafe = (app.installColor !== 'danger');
|
|
||||||
app.isWorking = isWorking ? "isworking" : "notFullyWorking";
|
|
||||||
app.isHighQuality = (app.state === "high-quality") ? "isHighQuality" : "";
|
|
||||||
app.decentQuality = (app.level > 4)?"decentQuality":"badQuality";
|
|
||||||
|
|
||||||
apps.push(app);
|
|
||||||
});
|
|
||||||
|
|
||||||
var category = undefined;
|
|
||||||
$.each(data['categories'], function(i, this_category) {
|
|
||||||
if (this_category.id === category_id) { category = this_category; }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (category_id === "all") {
|
|
||||||
category = {title: y18n.t("all_apps"), icon: "search"};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort app list
|
|
||||||
c.arraySortById(apps);
|
|
||||||
|
|
||||||
// setup filtering of apps once the view is loaded
|
|
||||||
function setupFilterEvents () {
|
|
||||||
// Uses plugin isotope to filter apps (we could had ordering to)
|
|
||||||
var cardGrid = jQuery('#apps').isotope({
|
|
||||||
itemSelector: '.app-card',
|
|
||||||
layoutMode: 'fitRows',
|
|
||||||
transitionDuration: 200
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default filter is 'decent quality apps'
|
|
||||||
cardGrid.isotope({ filter: '.decentQuality' });
|
|
||||||
|
|
||||||
$(".subtag-selector button").on("click", function() {
|
|
||||||
var selector = $(this).parent();
|
|
||||||
$("button", selector).removeClass("active");
|
|
||||||
$(this).addClass("active");
|
|
||||||
cardGrid.isotope({ filter: filterApps });
|
|
||||||
});
|
|
||||||
|
|
||||||
filterApps = function () {
|
|
||||||
|
|
||||||
// Check text search
|
|
||||||
var input = jQuery("#filter-app-cards").val().toLowerCase();
|
|
||||||
if (jQuery(this).find('.app-title, .app-card-desc').text().toLowerCase().indexOf(input) <= -1) return false;
|
|
||||||
|
|
||||||
// Check subtags
|
|
||||||
var subtag = $(".subtag-selector button.active").data("subtag");
|
|
||||||
var this_subtags = jQuery(this).data("subtags");
|
|
||||||
if ((subtag !== undefined) && (subtag !== "all")) {
|
|
||||||
if ((subtag === "others") && (this_subtags !== "")) return false;
|
|
||||||
if ((subtag !== "others") && (this_subtags.split(",").indexOf(subtag) <= -1)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check quality criteria
|
|
||||||
var class_ = jQuery("#dropdownFilter").data("filter");
|
|
||||||
if ((class_ !== '*') && (! jQuery(this).hasClass(class_))) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
jQuery('.dropdownFilter').on('click', function() {
|
|
||||||
// change dropdown label
|
|
||||||
jQuery('#app-cards-list-filter-text').text(jQuery(this).find('.menu-item').text());
|
|
||||||
// change filter attribute
|
|
||||||
jQuery('#dropdownFilter').data("filter", jQuery(this).data("filter"));
|
|
||||||
// filter !
|
|
||||||
cardGrid.isotope({ filter: filterApps });
|
|
||||||
});
|
|
||||||
|
|
||||||
jQuery("#filter-app-cards").on("keyup", function() {
|
|
||||||
cardGrid.isotope({ filter: filterApps });
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#install-custom-app a[role='button']").on('click', function() {
|
|
||||||
|
|
||||||
var url = $("#install-custom-app input[name='url']")[0].value;
|
|
||||||
if (url.indexOf("github.com") < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('applications'),
|
|
||||||
y18n.t('confirm_install_custom_app'),
|
|
||||||
function(){
|
|
||||||
c.redirect_to('#/apps/install/custom/' + encodeURIComponent(url));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// render
|
|
||||||
c.view('app/app_catalog_category', {apps: apps, category: category}, setupFilterEvents);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get app information
|
|
||||||
app.get('#/apps/:app', function (c) {
|
|
||||||
c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(data) {
|
|
||||||
data.label = data.permissions[c.params['app']+".main"]['label']
|
|
||||||
|
|
||||||
data.permissions = data.permissions[c.params['app']+".main"]["allowed"];
|
|
||||||
|
|
||||||
// Multilingual description
|
|
||||||
data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ?
|
|
||||||
data.manifest.description[y18n.locale] :
|
|
||||||
data.manifest.description['en']
|
|
||||||
;
|
|
||||||
|
|
||||||
// Multi Instance settings
|
|
||||||
data.manifest.multi_instance = data.manifest.multi_instance ? y18n.t('yes') : y18n.t('no');
|
|
||||||
data.install_time = new Date(data.settings.install_time * 1000);
|
|
||||||
|
|
||||||
c.view('app/app_info', data, function() {
|
|
||||||
|
|
||||||
// Button to set the app as default
|
|
||||||
$('button[data-action="set-as-default"]').on("click", function() {
|
|
||||||
var app = $(this).data("app");
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('applications'),
|
|
||||||
y18n.t('confirm_app_default'),
|
|
||||||
function() { c.api('PUT', '/apps/'+app+'/default', {}, function() { c.refresh() }); }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Button to uninstall the app
|
|
||||||
$('button[data-action="uninstall"]').on("click", function() {
|
|
||||||
var app = $(this).data("app");
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('applications'),
|
|
||||||
y18n.t('confirm_uninstall', [app]),
|
|
||||||
function() {
|
|
||||||
c.api('DELETE', '/apps/'+ app, {}, function() {
|
|
||||||
c.redirect_to('#/apps');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// App actions
|
|
||||||
//
|
|
||||||
|
|
||||||
// Get app actions list
|
|
||||||
app.get('#/apps/:app/actions', function (c) {
|
|
||||||
c.api('GET', '/apps/'+c.params['app']+'/actions', {}, function(data) {
|
|
||||||
$.each(data.actions, function(_, action) {
|
|
||||||
formatYunoHostStyleArguments(action.arguments, c.params);
|
|
||||||
|
|
||||||
// Multilingual description
|
|
||||||
if (action.description && Array.isArray(action.description))
|
|
||||||
action.description = (typeof action.description[y18n.locale] !== 'undefined') ?
|
|
||||||
action.description[y18n.locale] :
|
|
||||||
action.description['en']
|
|
||||||
;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
c.view('app/app_actions', data);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Perform app action
|
|
||||||
app.put('#/apps/:app/actions/:action', function(c) {
|
|
||||||
// taken from app install
|
|
||||||
$.each(c.params, function(k, v) {
|
|
||||||
if (typeof(v) === 'object' && Array.isArray(v)) {
|
|
||||||
// And return only first value
|
|
||||||
c.params[k] = v[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var app_id = c.params['app'];
|
|
||||||
delete c.params['app'];
|
|
||||||
var action_id = c.params['action'];
|
|
||||||
delete c.params['action'];
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
'args': c.serialize(c.params.toHash())
|
|
||||||
}
|
|
||||||
|
|
||||||
c.api('PUT', '/apps/'+app_id+'/actions/'+action_id, params, function() {
|
|
||||||
c.redirect_to('#/apps/'+app_id+'/actions', {slide:false});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// App config panel
|
|
||||||
//
|
|
||||||
|
|
||||||
// Get app config panel
|
|
||||||
app.get('#/apps/:app/config-panel', function (c) {
|
|
||||||
c.api('GET', '/apps/'+c.params['app']+'/config-panel', {}, function(data) {
|
|
||||||
$.each(data.config_panel.panel, function(_, panel) {
|
|
||||||
$.each(panel.sections, function(_, section) {
|
|
||||||
formatYunoHostStyleArguments(section.options, c.params);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
c.view('app/app_config-panel', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('#/apps/:app/config', function(c) {
|
|
||||||
// taken from app install
|
|
||||||
$.each(c.params, function(k, v) {
|
|
||||||
if (typeof(v) === 'object' && Array.isArray(v)) {
|
|
||||||
// And return only first value
|
|
||||||
c.params[k] = v[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var app_id = c.params['app'];
|
|
||||||
delete c.params['app'];
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
'args': c.serialize(c.params.toHash())
|
|
||||||
}
|
|
||||||
|
|
||||||
c.api('POST', '/apps/'+app_id+'/config', params, function() {
|
|
||||||
c.redirect_to('#/apps/'+app_id+'/config-panel', {slide:false});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
// Helper function that formats YunoHost style arguments for generating a form
|
|
||||||
function formatYunoHostStyleArguments(args, params) {
|
|
||||||
if (!args) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is in place modification, I don't like it but it was done this way
|
|
||||||
$.each(args, function(k, v) {
|
|
||||||
|
|
||||||
// Default values
|
|
||||||
args[k].type = (typeof v.type !== 'undefined') ? v.type : 'string';
|
|
||||||
args[k].inputType = 'text';
|
|
||||||
args[k].isPassword = false;
|
|
||||||
args[k].isDisplayText = false;
|
|
||||||
args[k].required = (typeof v.optional !== 'undefined' && (v.optional == "true" || v.optional == true)) ? '' : 'required';
|
|
||||||
args[k].attributes = "";
|
|
||||||
args[k].helpText = "";
|
|
||||||
args[k].helpLink = "";
|
|
||||||
|
|
||||||
// Multilingual label
|
|
||||||
if (typeof args[k].ask === "string")
|
|
||||||
{
|
|
||||||
args[k].label = args[k].ask;
|
|
||||||
}
|
|
||||||
else if (typeof args[k].ask[y18n.locale] !== 'undefined') {
|
|
||||||
args[k].label = args[k].ask[y18n.locale];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
args[k].label = args[k].ask['en'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multilingual help text
|
|
||||||
if (typeof args[k].help !== 'undefined') {
|
|
||||||
args[k].helpText = (typeof args[k].help[y18n.locale] !== 'undefined') ?
|
|
||||||
args[k].help[y18n.locale] :
|
|
||||||
args[k].help['en']
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input with choices becomes select list
|
|
||||||
if (typeof args[k].choices !== 'undefined') {
|
|
||||||
// Update choices values with key and checked data
|
|
||||||
var choices = []
|
|
||||||
$.each(args[k].choices, function(ck, cv){
|
|
||||||
// Non key/value choices have numeric key, that we don't want.
|
|
||||||
if (typeof ck == "number") {
|
|
||||||
// Key is Value in this case.
|
|
||||||
ck = cv;
|
|
||||||
}
|
|
||||||
|
|
||||||
choices.push({
|
|
||||||
value: ck,
|
|
||||||
label: cv,
|
|
||||||
selected: (ck == args[k].default) ? true : false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
args[k].choices = choices;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case for domain input.
|
|
||||||
// Display a list of available domains
|
|
||||||
if (v.name == 'domain' || args[k].type == 'domain') {
|
|
||||||
args[k].choices = [];
|
|
||||||
$.each(params.domains, function(key, domain){
|
|
||||||
args[k].choices.push({
|
|
||||||
value: domain,
|
|
||||||
label: domain,
|
|
||||||
selected: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// FIXME : to be reworked, choices ain't indexed by names...
|
|
||||||
// args[k].choices[params.domains_main].selected = true;
|
|
||||||
|
|
||||||
// Custom help link
|
|
||||||
args[k].helpLink += "<a href='#/domains'>"+y18n.t('manage_domains')+"</a>";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case for admin / user input.
|
|
||||||
// Display a list of available users
|
|
||||||
if (v.name == 'admin' || args[k].type == 'user') {
|
|
||||||
args[k].choices = [];
|
|
||||||
$.each(params.users, function(username, user){
|
|
||||||
args[k].choices.push({
|
|
||||||
value: username,
|
|
||||||
label: user.fullname+' ('+user.mail+')',
|
|
||||||
selected: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Custom help link
|
|
||||||
args[k].helpLink += "<a href='#/users'>"+y18n.t('manage_users')+"</a>";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'app' type input display a list of available apps
|
|
||||||
if (args[k].type == 'app') {
|
|
||||||
args[k].choices = [];
|
|
||||||
$.each(params.apps, function(key, app){
|
|
||||||
args[k].choices.push({
|
|
||||||
value: app.id,
|
|
||||||
label: app.name,
|
|
||||||
selected: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Custom help link
|
|
||||||
args[k].helpLink += "<a href='#/apps'>"+y18n.t('manage_apps')+"</a>";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Boolean fields
|
|
||||||
if (args[k].type == 'boolean') {
|
|
||||||
args[k].inputType = 'checkbox';
|
|
||||||
|
|
||||||
// Checked or not ?
|
|
||||||
if (typeof args[k].default !== 'undefined') {
|
|
||||||
if (args[k].default == true) {
|
|
||||||
args[k].attributes = 'checked="checked"';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'default' is used as value, so we need to force it for checkboxes.
|
|
||||||
args[k].default = 1;
|
|
||||||
|
|
||||||
// Checkbox should not be required to be unchecked
|
|
||||||
args[k].required = '';
|
|
||||||
|
|
||||||
// Clone a hidden input with empty value
|
|
||||||
// https://stackoverflow.com/questions/476426/submit-an-html-form-with-empty-checkboxes
|
|
||||||
var inputClone = {
|
|
||||||
name : args[k].name,
|
|
||||||
inputType : 'hidden',
|
|
||||||
default : 0
|
|
||||||
};
|
|
||||||
args.push(inputClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'password' type input.
|
|
||||||
if (v.name == 'password' || args[k].type == 'password') {
|
|
||||||
// Change html input type
|
|
||||||
args[k].inputType = 'password';
|
|
||||||
args[k].isPassword = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args[k].type == "display_text") {
|
|
||||||
args[k].isDisplayText = true;
|
|
||||||
args[k].label = args[k].label.split("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function that build app installation form
|
|
||||||
app.helper('appInstallForm', function(appId, manifest, params) {
|
|
||||||
var data = {
|
|
||||||
id: appId,
|
|
||||||
manifest: manifest,
|
|
||||||
displayLicense: (manifest['license'] !== undefined && manifest['license'] !== 'free')
|
|
||||||
};
|
|
||||||
|
|
||||||
formatYunoHostStyleArguments(manifest.arguments.install, params);
|
|
||||||
|
|
||||||
// Multilingual description
|
|
||||||
if (typeof manifest.description === 'string')
|
|
||||||
{
|
|
||||||
data.description = manifest.description;
|
|
||||||
}
|
|
||||||
else if (typeof manifest.description[y18n.locale] !== 'undefined')
|
|
||||||
{
|
|
||||||
data.description = manifest.description[y18n.locale];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data.description = manifest.description['en'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi Instance settings boolean to text
|
|
||||||
data.manifest.multi_instance = manifest.multi_instance ? y18n.t('yes') : y18n.t('no');
|
|
||||||
|
|
||||||
// View app install form
|
|
||||||
c.view('app/app_install', data);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
// App installation form
|
|
||||||
app.get('#/apps/install/:app', function (c) {
|
|
||||||
c.api('GET', '/appscatalog?full', {}, function(data) {
|
|
||||||
var app_name = c.params["app"];
|
|
||||||
var app_infos = data["apps"][app_name];
|
|
||||||
if (app_infos['state'] === "validated")
|
|
||||||
{
|
|
||||||
app_infos['state'] = "official";
|
|
||||||
}
|
|
||||||
var state_color = stateToColor(app_infos['state']);
|
|
||||||
var level_color = levelToColor(parseInt(app_infos['level']));
|
|
||||||
var is_safe_for_install_color = combineColors(state_color, level_color);
|
|
||||||
|
|
||||||
if ((is_safe_for_install_color === "warning") || (is_safe_for_install_color === "danger"))
|
|
||||||
{
|
|
||||||
c.confirm(
|
|
||||||
y18n.t("applications"),
|
|
||||||
y18n.t("confirm_install_app_"+is_safe_for_install_color),
|
|
||||||
function(){
|
|
||||||
c.appInstallForm(
|
|
||||||
app_name,
|
|
||||||
app_infos.manifest,
|
|
||||||
c.params
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
c.redirect_to('#/apps/catalog');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
c.appInstallForm(
|
|
||||||
app_name,
|
|
||||||
app_infos.manifest,
|
|
||||||
c.params
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Install app (POST)
|
|
||||||
app.post('#/apps', function(c) {
|
|
||||||
// Warn admin if app is going to be installed on domain root.
|
|
||||||
if (c.params['path'] !== '/' || confirm(y18n.t('confirm_install_domain_root', [c.params['domain']]))) {
|
|
||||||
var params = {
|
|
||||||
label: c.params['label'],
|
|
||||||
app: c.params['app']
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check for duplicate arg produced by empty checkbox. (See inputClone)
|
|
||||||
delete c.params['label'];
|
|
||||||
delete c.params['app'];
|
|
||||||
$.each(c.params, function(k, v) {
|
|
||||||
if (typeof(v) === 'object' && Array.isArray(v)) {
|
|
||||||
// And return only first value
|
|
||||||
c.params[k] = v[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
params['args'] = c.serialize(c.params.toHash());
|
|
||||||
|
|
||||||
// Do not pass empty args.
|
|
||||||
if (params['args'] === "") {
|
|
||||||
delete params['args'];
|
|
||||||
}
|
|
||||||
|
|
||||||
c.api('POST', '/apps', params, function() {
|
|
||||||
c.redirect_to('#/apps');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
c.flash('warning', y18n.t('app_install_cancel'));
|
|
||||||
c.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Install custom app from github
|
|
||||||
app.get('#/apps/install/custom/:url', function(c) {
|
|
||||||
|
|
||||||
// Force trailing slash
|
|
||||||
url = c.params['url'];
|
|
||||||
url = url.replace(/\/?$/, '/');
|
|
||||||
raw_manifest_url = url.replace('github.com', 'raw.githubusercontent.com') + 'master/manifest.json'
|
|
||||||
|
|
||||||
// Fetch manifest.json
|
|
||||||
jQuery.ajax({ url: raw_manifest_url, type: 'GET' })
|
|
||||||
.done(function(manifest) {
|
|
||||||
// raw.githubusercontent.com serve content as plain text
|
|
||||||
manifest = jQuery.parseJSON(manifest) || {};
|
|
||||||
|
|
||||||
c.appInstallForm(
|
|
||||||
url,
|
|
||||||
manifest,
|
|
||||||
c.params
|
|
||||||
);
|
|
||||||
|
|
||||||
})
|
|
||||||
.fail(function(xhr) {
|
|
||||||
c.flash('fail', y18n.t('app_install_custom_no_manifest'));
|
|
||||||
c.redirect("#/apps/catalog/");
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// A small utility to convert a string to title case
|
|
||||||
// e.g. "hAvE a NicE dAy" --> "Have A Nice Day"
|
|
||||||
// Savagely stolen from https://stackoverflow.com/a/196991
|
|
||||||
function toFriendlyName(str) {
|
|
||||||
return str.split('.')[1].replace(/_/g, " ").replace(
|
|
||||||
/\w\S*/g,
|
|
||||||
function(txt) {
|
|
||||||
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get app change label page
|
|
||||||
app.get('#/apps/:app/changelabel', function (c) {
|
|
||||||
c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(app_data) {
|
|
||||||
var permissions_dict = app_data.permissions;
|
|
||||||
var permissions = [
|
|
||||||
permissions_dict[c.params['app'] + '.main']
|
|
||||||
];
|
|
||||||
permissions[0].name = c.params['app'] + '.main';
|
|
||||||
permissions[0].title = y18n.t('permission_main');
|
|
||||||
permissions[0].tile_available = permissions[0].url != null && ! permissions[0].url.startsWith('re:');
|
|
||||||
|
|
||||||
var i = 1;
|
|
||||||
for (var permission in permissions_dict) {
|
|
||||||
if (! permission.endsWith('.main')) {
|
|
||||||
permissions[i] = permissions_dict[permission];
|
|
||||||
permissions[i].name = permission;
|
|
||||||
permissions[i].label = permissions[i].sublabel;
|
|
||||||
permissions[i].title = toFriendlyName(permission);
|
|
||||||
permissions[i].tile_available = permissions_dict[permission].url != null && ! permissions_dict[permission].url.startsWith('re:');
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
'id': c.params['app'],
|
|
||||||
'label': permissions[0].label,
|
|
||||||
'permissions': permissions,
|
|
||||||
'show_tile': function(permission_name) {
|
|
||||||
return permissions_dict[permission_name].show_tile;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
c.view('app/app_changelabel', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change app label
|
|
||||||
app.post('#/apps/:app/changelabel', function (c) {
|
|
||||||
|
|
||||||
$.each($(".permission-row", c.target), function() {
|
|
||||||
var perm = $(this).data('permission');
|
|
||||||
if ('show_tile_' + perm in c.params) {
|
|
||||||
show_tile = "True";
|
|
||||||
} else {
|
|
||||||
show_tile = "False";
|
|
||||||
}
|
|
||||||
new_label = c.params["label_" + perm]
|
|
||||||
c.api('PUT', '/users/permissions/' + perm, {show_tile: show_tile, label: new_label}, function(data) {});
|
|
||||||
});
|
|
||||||
|
|
||||||
c.redirect_to('#/apps/'+ c.params['app']);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get app change URL page
|
|
||||||
app.get('#/apps/:app/changeurl', function (c) {
|
|
||||||
c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(app_data) {
|
|
||||||
c.api('GET', '/domains', {}, function(domain_data) {
|
|
||||||
|
|
||||||
// Display a list of available domains
|
|
||||||
var domains = [];
|
|
||||||
$.each(domain_data.domains, function(k, domain) {
|
|
||||||
domains.push({
|
|
||||||
value: domain,
|
|
||||||
label: domain,
|
|
||||||
// Select current domain
|
|
||||||
selected: (domain == app_data.settings.domain ? true : false)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
data = {
|
|
||||||
id: c.params['app'],
|
|
||||||
label: app_data.manifest.name,
|
|
||||||
domains: domains,
|
|
||||||
// Pre-fill with current path
|
|
||||||
path: app_data.settings.path
|
|
||||||
};
|
|
||||||
c.view('app/app_changeurl', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change app URL
|
|
||||||
app.post('#/apps/:app/changeurl', function (c) {
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('applications'),
|
|
||||||
y18n.t('confirm_app_change_url', [c.params['app']]),
|
|
||||||
function() {
|
|
||||||
params = {'domain': c.params['domain'], 'path': c.params['path']};
|
|
||||||
c.api('PUT', '/apps/' + c.params['app'] + '/changeurl', params, function(data) {
|
|
||||||
c.redirect_to('#/apps/'+ c.params['app']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -1,224 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backup
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
var config_hooks = [
|
|
||||||
'system_ldap',
|
|
||||||
'system_ssowat',
|
|
||||||
'system_cron',
|
|
||||||
'system_ssh',
|
|
||||||
'system_xmpp',
|
|
||||||
'system_mysql',
|
|
||||||
'system_yunohost',
|
|
||||||
'system_nginx'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Storage list
|
|
||||||
app.get('#/backup', function (c) {
|
|
||||||
var storages = [];
|
|
||||||
var item = {
|
|
||||||
id: 'local',
|
|
||||||
name: y18n.t('local_archives'),
|
|
||||||
uri: '/home/yunohost.backup/'
|
|
||||||
};
|
|
||||||
storages.push(item);
|
|
||||||
|
|
||||||
c.view('backup/backup', {'storages':storages});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Archive list
|
|
||||||
app.get('#/backup/:storage', function (c) {
|
|
||||||
c.api('GET', '/backup/archives?with_info', {}, function(data) {
|
|
||||||
data.storage = {
|
|
||||||
id: 'local',
|
|
||||||
name: y18n.t('local_archives')
|
|
||||||
};
|
|
||||||
data.archives2 = [];
|
|
||||||
$.each(data['archives'], function(name, info) {
|
|
||||||
info.name = name;
|
|
||||||
data.archives2.unshift(info)
|
|
||||||
});
|
|
||||||
data.archives = data.archives2;
|
|
||||||
data.locale = y18n.locale
|
|
||||||
c.view('backup/backup_list', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// View to create a backup
|
|
||||||
app.get('#/backup/:storage/create', function (c) {
|
|
||||||
var data = [];
|
|
||||||
data['storage'] = {
|
|
||||||
id:c.params['storage'],
|
|
||||||
name:y18n.t('local_archives')
|
|
||||||
};
|
|
||||||
c.api('GET', '/hooks/backup', {}, function(hooks) {
|
|
||||||
data['hooks'] = groupHooks(hooks['hooks']);
|
|
||||||
data['apps'] = {};
|
|
||||||
c.api('GET', '/apps?with_backup', {}, function(apps_list) {
|
|
||||||
data['apps'] = apps_list.apps;
|
|
||||||
c.view('backup/backup_create', data, function() {
|
|
||||||
|
|
||||||
// Configure buttons "select all" and "select none"
|
|
||||||
|
|
||||||
// Remove active style from buttons
|
|
||||||
$(".select_all-none input").click(function(){ $(this).toggleClass("active"); });
|
|
||||||
// Select all checkbox in this panel
|
|
||||||
$(".select_all").click(function(){
|
|
||||||
$(this).parents(".panel").children(".list-group").find("input").prop("checked", true);
|
|
||||||
});
|
|
||||||
// Deselect all checkbox in this panel
|
|
||||||
$(".select_none").click(function(){
|
|
||||||
$(this).parents(".panel").children(".list-group").find("input").prop("checked", false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Actually creating the backup
|
|
||||||
app.post('#/backup/:storage', function (c) {
|
|
||||||
var params = ungroupHooks(c.params['system_parts'],c.params['apps']);
|
|
||||||
c.api('POST', '/backup', params, function() {
|
|
||||||
c.redirect_to('#/backup/'+ c.params['storage']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get archive info
|
|
||||||
app.get('#/backup/:storage/:archive', function (c) {
|
|
||||||
c.api('GET', '/backup/archives/'+c.params['archive']+'?with_details', {}, function(data) {
|
|
||||||
data.storage = {
|
|
||||||
id: c.params['storage'],
|
|
||||||
name: y18n.t('local_archives')
|
|
||||||
};
|
|
||||||
data.name = c.params['archive'];
|
|
||||||
data.system_parts = groupHooks(Object.keys(data['system']),data['system']);
|
|
||||||
data.items = (data['system']!={} || data['apps']!=[]);
|
|
||||||
data.locale = y18n.locale;
|
|
||||||
c.view('backup/backup_info', data, function() {
|
|
||||||
|
|
||||||
// Select all checkbox in this panel
|
|
||||||
$('button[data-action="select_all"]').on('click', function() {
|
|
||||||
$(this).parents(".panel").children(".list-group").find("input").prop("checked", true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Deselect all checkbox in this panel
|
|
||||||
$('button[data-action="select_none"]').on('click', function() {
|
|
||||||
$(this).parents(".panel").children(".list-group").find("input").prop("checked", false);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('button[data-action="download"]').on('click', function() {
|
|
||||||
var archive = $(this).data('archive');
|
|
||||||
window.open('https://' + store.get('url') + '/backup/download/'+archive, '_blank');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delete button
|
|
||||||
$('button[data-action="delete"]').on('click', function() {
|
|
||||||
var storage = $(this).data('storage');
|
|
||||||
var archive = $(this).data('archive');
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('backup'),
|
|
||||||
y18n.t('confirm_delete', [archive]),
|
|
||||||
function(){
|
|
||||||
c.api('DELETE', '/backup/archives/'+archive, {}, function(data) {
|
|
||||||
c.redirect_to('#/backup/'+ storage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restore a backup
|
|
||||||
app.post('#/backup/:storage/:archive/restore', function (c) {
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('backup'),
|
|
||||||
y18n.t('confirm_restore', [c.params['archive']]),
|
|
||||||
$.proxy(function(c){
|
|
||||||
var params = ungroupHooks(c.params['system_parts'],c.params['apps']);
|
|
||||||
params['force'] = '';
|
|
||||||
c.api('POST', '/backup/restore/'+c.params['archive'], params, function(data) {
|
|
||||||
c.redirect_to('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
|
|
||||||
});
|
|
||||||
}, this, c)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function groupHooks(hooks, raw_infos) {
|
|
||||||
var data = {};
|
|
||||||
var rules = [
|
|
||||||
{
|
|
||||||
id:'configuration',
|
|
||||||
isIn:function (hook) {
|
|
||||||
return hook.indexOf('conf_')==0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
$.each(hooks, function(i, hook) {
|
|
||||||
var group_id=hook;
|
|
||||||
var hook_size=(raw_infos && raw_infos[hook] && raw_infos[hook].size)?raw_infos[hook].size:0;
|
|
||||||
$.each(rules, function(i, rule) {
|
|
||||||
if (rule.isIn(hook)) {
|
|
||||||
group_id = 'adminjs_group_'+rule.id;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(group_id in data) {
|
|
||||||
data[group_id] = {
|
|
||||||
name:y18n.t('hook_'+group_id),
|
|
||||||
value:data[group_id].value+','+hook,
|
|
||||||
description:data[group_id].description+', '+y18n.t('hook_'+hook),
|
|
||||||
size:data[group_id].size + hook_size
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
data[group_id] = {
|
|
||||||
name:y18n.t('hook_'+group_id),
|
|
||||||
value:hook,
|
|
||||||
description:(group_id==hook)?y18n.t('hook_'+hook+'_desc'):y18n.t('hook_'+hook),
|
|
||||||
size:hook_size
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ungroupHooks(system_parts, apps) {
|
|
||||||
|
|
||||||
var data = {};
|
|
||||||
data['apps'] = apps || [];
|
|
||||||
data['system'] = system_parts || [];
|
|
||||||
|
|
||||||
if (data['system'].constructor !== Array) {
|
|
||||||
data['system'] = [data['system']];
|
|
||||||
}
|
|
||||||
if (data['apps'].constructor !== Array) {
|
|
||||||
data['apps'] = [data['apps']];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some hook value contains multiple hooks separated by commas
|
|
||||||
var split_hooks = [];
|
|
||||||
$.each(data['system'], function(i, hook) {
|
|
||||||
split_hooks = split_hooks.concat(hook.split(','));
|
|
||||||
});
|
|
||||||
data['system'] = split_hooks;
|
|
||||||
|
|
||||||
if (data['system'].length == 0) {
|
|
||||||
delete data['system'];
|
|
||||||
}
|
|
||||||
if (data['apps'].length == 0) {
|
|
||||||
delete data['apps'];
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,152 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
// *********
|
|
||||||
// Diagnosis
|
|
||||||
// *********
|
|
||||||
|
|
||||||
app.get('#/diagnosis', function (c) {
|
|
||||||
c.api('POST', '/diagnosis/run?except_if_never_ran_yet', {}, function() {
|
|
||||||
updateDiagnosisView();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateDiagnosisView(state) {
|
|
||||||
|
|
||||||
c.api('GET', '/diagnosis/show?full', {}, function(data) {
|
|
||||||
|
|
||||||
if (typeof(data.reports) === "undefined")
|
|
||||||
{
|
|
||||||
data.reports = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare data to be displayed ...
|
|
||||||
for (var i = 0 ; i < data.reports.length ; i++)
|
|
||||||
{
|
|
||||||
// Convert timestamp to datetime
|
|
||||||
data.reports[i].time = new Date(data.reports[i].timestamp*1000);
|
|
||||||
data.reports[i].warnings = 0;
|
|
||||||
data.reports[i].errors = 0;
|
|
||||||
data.reports[i].ignored = 0;
|
|
||||||
for (var j = 0 ; j < data.reports[i].items.length ; j++)
|
|
||||||
{
|
|
||||||
var type_ = data.reports[i].items[j].status;
|
|
||||||
type_ = type_.toLowerCase();
|
|
||||||
var ignored = data.reports[i].items[j].ignored;
|
|
||||||
var icon = "";
|
|
||||||
var issue = false;
|
|
||||||
|
|
||||||
if (type_ == "success") {
|
|
||||||
icon = "check-circle";
|
|
||||||
}
|
|
||||||
else if (type_ == "info") {
|
|
||||||
icon = "info-circle";
|
|
||||||
}
|
|
||||||
else if (ignored == true) {
|
|
||||||
icon = type_;
|
|
||||||
if (type_ == "error") {
|
|
||||||
icon = "times"
|
|
||||||
}
|
|
||||||
type_ = "ignored";
|
|
||||||
data.reports[i].ignored++;
|
|
||||||
}
|
|
||||||
else if (type_ == "warning") {
|
|
||||||
icon = "warning";
|
|
||||||
issue = true;
|
|
||||||
data.reports[i].warnings++;
|
|
||||||
}
|
|
||||||
else if (type_ == "error") {
|
|
||||||
type_ = "danger";
|
|
||||||
icon = "times";
|
|
||||||
issue = true;
|
|
||||||
data.reports[i].errors++;
|
|
||||||
}
|
|
||||||
data.reports[i].items[j].status = type_;
|
|
||||||
data.reports[i].items[j].icon = icon;
|
|
||||||
data.reports[i].items[j].issue = issue;
|
|
||||||
// We want filter_args to be something like "dnsrecords,domain=yolo.test,category=xmpp"
|
|
||||||
data.reports[i].items[j].filter_args = data.reports[i].id;
|
|
||||||
for (prop in data.reports[i].items[j].meta) {
|
|
||||||
data.reports[i].items[j].filter_args = data.reports[i].items[j].filter_args + ","+prop+"="+data.reports[i].items[j].meta[prop];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
data.reports[i].noIssues = data.reports[i].warnings + data.reports[i].errors ? false : true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render and display the view
|
|
||||||
c.view('diagnosis/diagnosis_show', data, function() {
|
|
||||||
|
|
||||||
restoreDiagnosisViewState(state);
|
|
||||||
|
|
||||||
// Button for first diagnosis
|
|
||||||
$("button[data-action='run-full-diagnosis']").click(function() {
|
|
||||||
c.api('POST', '/diagnosis/run', {}, function(data) {
|
|
||||||
updateDiagnosisView();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure share with yunopaste button
|
|
||||||
$("button[data-action='share']").click(function() {
|
|
||||||
c.api('GET', '/diagnosis/show?share', {}, function(data) {
|
|
||||||
c.hideLoader();
|
|
||||||
window.open(data.url, '_blank');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure 'rerun diagnosis' button behavior
|
|
||||||
$("button[data-action='rerun-diagnosis']").click(function() {
|
|
||||||
var category = $(this).data("category");
|
|
||||||
c.api('POST', '/diagnosis/run?force', {"categories": [category]}, function(data) {
|
|
||||||
updateDiagnosisView(saveDiagnosisViewState());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure 'ignore' / 'unignore' buttons behavior
|
|
||||||
$("button[data-action='ignore']").click(function() {
|
|
||||||
var filter_args = $(this).data("filter-args");
|
|
||||||
c.api('POST', '/diagnosis/ignore', {'add_filter': filter_args.split(',') }, function(data) {
|
|
||||||
updateDiagnosisView(saveDiagnosisViewState());
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
$("button[data-action='unignore']").click(function() {
|
|
||||||
var filter_args = $(this).data("filter-args");
|
|
||||||
c.api('POST', '/diagnosis/ignore', {'remove_filter': filter_args.split(',') }, function(data) {
|
|
||||||
updateDiagnosisView(saveDiagnosisViewState());
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save current level of scroll + which panels are collapsed / not collapsed
|
|
||||||
function saveDiagnosisViewState() {
|
|
||||||
var collapse = {};
|
|
||||||
$(".panel-diagnosis").each(function(i, el) {
|
|
||||||
collapse[$(el).data("category")] = $($(".panel-body", el)[0]).hasClass("in");
|
|
||||||
});
|
|
||||||
return { "scroll": document.documentElement.scrollTop, "collapse": collapse };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore scroll + panel collapse state
|
|
||||||
function restoreDiagnosisViewState(state) {
|
|
||||||
if (typeof state === "undefined") { return; }
|
|
||||||
|
|
||||||
Object.keys(state.collapse).forEach(function(category) {
|
|
||||||
if (state.collapse[category]) {
|
|
||||||
$(".panel-diagnosis[data-category='"+category+"'] .panel-body").addClass("in");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$(".panel-diagnosis[data-category='"+category+"'] .panel-body").removeClass("in");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.scroll(0,state.scroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,253 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Domains
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List existing domains
|
|
||||||
app.get('#/domains', function (c) {
|
|
||||||
c.api('GET', '/domains', {}, function(data) {
|
|
||||||
c.api('PUT', '/domains/main', {}, function(data2) {
|
|
||||||
var domains = [];
|
|
||||||
$.each(data.domains, function(k, domain) {
|
|
||||||
domains.push({
|
|
||||||
url: domain,
|
|
||||||
main: (domain == data2.current_main_domain) ? true : false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Do not show main domain form if we have only 1 domain
|
|
||||||
var main_domain_form = (domains.length > 1) ? true: false;
|
|
||||||
|
|
||||||
// Sort domains with main domain first
|
|
||||||
domains.sort(function(a, b){ return -2*(a.main) + 1; });
|
|
||||||
c.view('domain/domain_list', {
|
|
||||||
domains: domains,
|
|
||||||
main_domain_form: main_domain_form
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add domain form
|
|
||||||
app.get('#/domains/add', function (c) {
|
|
||||||
$.get('https://dyndns.yunohost.org/domains', function() {})
|
|
||||||
.done(function(data){
|
|
||||||
c.params.ddomains = data.map(function(dom){return '.'+dom;});
|
|
||||||
})
|
|
||||||
.fail(function() {
|
|
||||||
c.params.ddomains = ['.nohost.me', '.noho.st', '.ynh.fr'];
|
|
||||||
})
|
|
||||||
.always(function() {
|
|
||||||
var data = {
|
|
||||||
ddomains: c.params.ddomains,
|
|
||||||
domains: c.params.domains,
|
|
||||||
allowDyndnsDomain: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allow only 1 DynDns domain.
|
|
||||||
var regex = data.ddomains.join('|');
|
|
||||||
$.each(data.domains, function(k, domain) {
|
|
||||||
if ( domain.search(regex) > 0 ) {
|
|
||||||
data.allowDyndnsDomain = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
c.view('domain/domain_add', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add domain (POST)
|
|
||||||
app.post('#/domains/add', function (c) {
|
|
||||||
var params = {};
|
|
||||||
var endurl = '';
|
|
||||||
if (c.params['domain'] === '') {
|
|
||||||
if (c.params['ddomain'] === '') {
|
|
||||||
c.flash('fail', y18n.t('error_select_domain'));
|
|
||||||
c.redirect_to('#/domains/add');
|
|
||||||
}
|
|
||||||
params.domain = c.params['ddomain'] + c.params['ddomain-ext'];
|
|
||||||
endurl = 'dyndns';
|
|
||||||
} else {
|
|
||||||
params.domain = c.params['domain'];
|
|
||||||
}
|
|
||||||
|
|
||||||
c.api('POST', '/domains?'+endurl, params, function(data) {
|
|
||||||
c.redirect_to('#/domains');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get existing domain info
|
|
||||||
app.get('#/domains/:domain', function (c) {
|
|
||||||
c.api('PUT', '/domains/main', {}, function(dataMain) {
|
|
||||||
var domain = {
|
|
||||||
name: c.params['domain'],
|
|
||||||
main: (c.params['domain'] == dataMain.current_main_domain) ? true : false,
|
|
||||||
url: "https://"+c.params['domain']
|
|
||||||
};
|
|
||||||
c.view('domain/domain_info', domain, function() {
|
|
||||||
|
|
||||||
// Configure "set default" button
|
|
||||||
$('button[data-action="set_default"]').on("click", function() {
|
|
||||||
var domain = $(this).data("domain");
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('domains'),
|
|
||||||
y18n.t('confirm_change_maindomain'),
|
|
||||||
function() {
|
|
||||||
c.api('PUT', '/domains/main', {new_main_domain: domain}, function() { c.refresh() });
|
|
||||||
}
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure delete button
|
|
||||||
$('button[data-action="delete"]').on("click", function() {
|
|
||||||
var domain = $(this).data("domain");
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('domains'),
|
|
||||||
y18n.t('confirm_delete', [domain]),
|
|
||||||
function(){
|
|
||||||
c.api('DELETE', '/domains/'+ domain, {}, function() {
|
|
||||||
c.redirect_to('#/domains');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Domain DNS
|
|
||||||
app.get('#/domains/:domain/dns', function (c) {
|
|
||||||
c.api('GET', '/domains/' + c.params['domain'] + '/dns', {}, function(data) {
|
|
||||||
var domain = {
|
|
||||||
name: c.params['domain'],
|
|
||||||
dns: data
|
|
||||||
};
|
|
||||||
c.view('domain/domain_dns', domain);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Domain certificate
|
|
||||||
app.get('#/domains/:domain/cert-management', function (c) {
|
|
||||||
c.api('GET', '/domains/cert-status/' + c.params['domain'] + '?full', {}, function(data) {
|
|
||||||
|
|
||||||
var s = data["certificates"][c.params['domain']];
|
|
||||||
var status_ = {
|
|
||||||
CA_type: s.CA_type.verbose,
|
|
||||||
CA_name: s.CA_name,
|
|
||||||
validity: s.validity,
|
|
||||||
ACME_eligible: s.ACME_eligible
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (s.summary.code) {
|
|
||||||
case "critical" :
|
|
||||||
status_.alert_type = "danger";
|
|
||||||
status_.alert_icon = "exclamation-circle" ;
|
|
||||||
status_.alert_message = y18n.t('certificate_alert_not_valid');
|
|
||||||
break;
|
|
||||||
case "warning" :
|
|
||||||
status_.alert_type = "warning";
|
|
||||||
status_.alert_icon = "exclamation-triangle";
|
|
||||||
status_.alert_message = y18n.t('certificate_alert_selfsigned');
|
|
||||||
break;
|
|
||||||
case "attention" :
|
|
||||||
if (status_.CA_type == "lets-encrypt") {
|
|
||||||
status_.alert_type = "warning";
|
|
||||||
status_.alert_icon = "clock-o";
|
|
||||||
status_.alert_message = y18n.t('certificate_alert_letsencrypt_about_to_expire');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
status_.alert_type = "danger";
|
|
||||||
status_.alert_icon = "clock-o";
|
|
||||||
status_.alert_message = y18n.t('certificate_alert_about_to_expire');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "good" :
|
|
||||||
status_.alert_type = "success";
|
|
||||||
status_.alert_icon = "check-circle";
|
|
||||||
status_.alert_message = y18n.t('certificate_alert_good');
|
|
||||||
break;
|
|
||||||
case "great" :
|
|
||||||
status_.alert_type = "success";
|
|
||||||
status_.alert_icon = "thumbs-up";
|
|
||||||
status_.alert_message = y18n.t('certificate_alert_great');
|
|
||||||
break;
|
|
||||||
default :
|
|
||||||
status_.alert_type = "warning"
|
|
||||||
status_.alert_icon = "question"
|
|
||||||
status_.alert_message = y18n.t('certificate_alert_unknown');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var actions_enabled = {
|
|
||||||
install_letsencrypt: false,
|
|
||||||
manual_renew_letsencrpt: false,
|
|
||||||
regen_selfsigned: false,
|
|
||||||
replace_with_selfsigned: false
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (s.CA_type.code) {
|
|
||||||
case "self-signed" :
|
|
||||||
actions_enabled.install_letsencrypt = true;
|
|
||||||
actions_enabled.regen_selfsigned = true;
|
|
||||||
break;
|
|
||||||
case "lets-encrypt" :
|
|
||||||
actions_enabled.manual_renew_letsencrpt = true;
|
|
||||||
actions_enabled.replace_with_selfsigned = true;
|
|
||||||
break;
|
|
||||||
default :
|
|
||||||
actions_enabled.replace_with_selfsigned = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
data_ = {
|
|
||||||
name: c.params['domain'],
|
|
||||||
status: status_,
|
|
||||||
actions_enabled : actions_enabled
|
|
||||||
};
|
|
||||||
|
|
||||||
c.view('domain/domain_cert', data_, function() {
|
|
||||||
// Configure install / renew buttons behavior
|
|
||||||
$("button[data-action]").on("click", function () {
|
|
||||||
var action = $(this).data("action"),
|
|
||||||
domain = $(this).data("domain"),
|
|
||||||
confirm_key = "",
|
|
||||||
api_url = "";
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'install-LE':
|
|
||||||
confirm_key = 'confirm_cert_install_LE';
|
|
||||||
api_url = '/domains/cert-install/' + domain;
|
|
||||||
break;
|
|
||||||
case 'regen-selfsigned':
|
|
||||||
confirm_key = 'confirm_cert_regen_selfsigned';
|
|
||||||
api_url = '/domains/cert-install/' + domain + "?self_signed";
|
|
||||||
break;
|
|
||||||
case 'renew-letsencrypt':
|
|
||||||
confirm_key = 'confirm_cert_manual_renew_LE';
|
|
||||||
api_url = '/domains/cert-renew/' + domain + "?force";
|
|
||||||
break;
|
|
||||||
case 'replace-with-selfsigned':
|
|
||||||
confirm_key = 'confirm_cert_revert_to_selfsigned';
|
|
||||||
api_url = '/domains/cert-install/' + domain + "?self_signed&force";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
c.flash('fail', y18n.t('unknown_action', [action]));
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('certificate'),
|
|
||||||
y18n.t(confirm_key, [domain]),
|
|
||||||
function(){ c.api('POST', api_url, {}, function() { c.refresh() }); }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,144 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Firewall
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Firewall status
|
|
||||||
app.get('#/tools/firewall', function (c) {
|
|
||||||
c.api('GET', '/firewall?raw', {}, function(data) {
|
|
||||||
var firewall = {
|
|
||||||
ports: {},
|
|
||||||
upnp: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reorganize ports
|
|
||||||
$.each(['ipv4', 'ipv6', 'uPnP'], function(i, protocol) {
|
|
||||||
$.each(['TCP', 'UDP'], function(j, connection) {
|
|
||||||
firewall.ports[connection] = firewall.ports[connection] || {};
|
|
||||||
$.each(data[protocol][connection], function(k, port) {
|
|
||||||
firewall.ports[connection][port] = firewall.ports[connection][port] || {};
|
|
||||||
firewall.ports[connection][port][protocol] = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get UPnP status
|
|
||||||
firewall.upnp = data.uPnP.enabled;
|
|
||||||
|
|
||||||
c.view('tools/tools_firewall', firewall, function() {
|
|
||||||
|
|
||||||
// Buttons in the 'ports' panel to open/close specific ports
|
|
||||||
$("button[data-port]").on("click", function() {
|
|
||||||
|
|
||||||
var port = $(this).data("port");
|
|
||||||
var action = $(this).data("action");
|
|
||||||
var protocol = $(this).data("protocol");
|
|
||||||
var connection = $(this).data("connection");
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('firewall'),
|
|
||||||
// confirm_firewall_open and confirm_firewall_close
|
|
||||||
y18n.t('confirm_firewall_' + action, [ port, y18n.t(protocol), y18n.t(connection)]),
|
|
||||||
function(){ c.togglePort(port, protocol, connection, action); }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Buttons to enable / disable UPnP
|
|
||||||
$("button[data-upnp]").on("click", function() {
|
|
||||||
var action = $(this).data("upnp");
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('firewall'),
|
|
||||||
// confirm_upnp_enable and confirm_upnp_disable
|
|
||||||
y18n.t('confirm_upnp_' + action),
|
|
||||||
function(){ c.api('GET', '/firewall/upnp', {action: action}, function() { c.refresh() }); }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update port status from form
|
|
||||||
app.post('#/tools/firewall/port', function (c) {
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('firewall'),
|
|
||||||
y18n.t('confirm_firewall_' + c.params['action'].toLowerCase(), [ c.params['port'], y18n.t(c.params['protocol']), y18n.t(c.params['connection']) ]),
|
|
||||||
function(){
|
|
||||||
c.togglePort(
|
|
||||||
c.params['port'],
|
|
||||||
c.params['protocol'],
|
|
||||||
c.params['connection'],
|
|
||||||
c.params['action']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle port status helper (available in every controller)
|
|
||||||
app.helper('togglePort', function(port, protocol, connection, action) {
|
|
||||||
var method = null,
|
|
||||||
endurl = [],
|
|
||||||
c = this
|
|
||||||
;
|
|
||||||
|
|
||||||
if (port != parseInt(port) || port < 0 || port > 65535) {
|
|
||||||
c.flash('fail', y18n.t('unknown_argument', [port]));
|
|
||||||
c.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (connection) {
|
|
||||||
case 'ipv4':
|
|
||||||
endurl = 'ipv4_only';
|
|
||||||
break;
|
|
||||||
case 'ipv6':
|
|
||||||
endurl = 'ipv6_only';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (protocol) {
|
|
||||||
case 'udp':
|
|
||||||
protocol = 'UDP';
|
|
||||||
break;
|
|
||||||
case 'both':
|
|
||||||
protocol = 'Both';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
protocol = 'TCP';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case "open":
|
|
||||||
method = 'POST';
|
|
||||||
break;
|
|
||||||
case "close":
|
|
||||||
method = 'DELETE';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
c.flash('fail', y18n.t('unknown_action', [action]));
|
|
||||||
c.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// port:
|
|
||||||
// protocol:
|
|
||||||
// - UDP
|
|
||||||
// - TCP
|
|
||||||
// - Both
|
|
||||||
// --ipv4-only:
|
|
||||||
// --ipv6-only:
|
|
||||||
// --no-upnp:
|
|
||||||
var params = {
|
|
||||||
port : port,
|
|
||||||
protocol : protocol
|
|
||||||
};
|
|
||||||
|
|
||||||
c.api(method, '/firewall/port?'+endurl, params, function() { c.refresh() });
|
|
||||||
|
|
||||||
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,99 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Home
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Home page
|
|
||||||
app.get('#/', function (c) {
|
|
||||||
c.view("home");
|
|
||||||
// N.B : if you need to run stuff at login time,
|
|
||||||
// see js/events.js instead
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Login
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.get('#/login', function (c) {
|
|
||||||
$('#masthead').show()
|
|
||||||
.find('.logout-btn').hide();
|
|
||||||
store.set('path-1', '#/login');
|
|
||||||
|
|
||||||
c.showLoader();
|
|
||||||
|
|
||||||
// We gonna retry 3 times to check if yunohost is installed
|
|
||||||
if (app.isInstalledTry === undefined) {
|
|
||||||
app.isInstalledTry = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
c.checkInstall(function(isInstalled) {
|
|
||||||
|
|
||||||
if (isInstalled) {
|
|
||||||
c.view('login', { 'domain': window.location.hostname });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof isInstalled !== 'undefined') {
|
|
||||||
c.redirect('#/postinstall');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the retry counter is still up, retry this function 5 sec
|
|
||||||
// later
|
|
||||||
if (app.isInstalledTry > 0) {
|
|
||||||
app.isInstalledTry--;
|
|
||||||
setTimeout(function() {
|
|
||||||
c.redirect('#/');
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
c.flash('fail', y18n.t('api_not_responding'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logout
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.post('#/login', function (c) {
|
|
||||||
// Store url from params, it could have change form 'run' state
|
|
||||||
store.set('url', c.params['domain'] +'/yunohost/api');
|
|
||||||
|
|
||||||
var params = {
|
|
||||||
password: c.params['password']
|
|
||||||
};
|
|
||||||
c.api('POST', '/login', params, function(data) {
|
|
||||||
store.set('connected', true);
|
|
||||||
c.trigger('login');
|
|
||||||
$('#masthead .logout-btn').fadeIn();
|
|
||||||
c.flash('success', y18n.t('logged_in'));
|
|
||||||
if (store.get('path')) {
|
|
||||||
c.redirect(store.get('path'));
|
|
||||||
} else {
|
|
||||||
c.redirect('#/');
|
|
||||||
}
|
|
||||||
}, undefined, false);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('#/logout', function (c) {
|
|
||||||
c.api('GET', '/logout', {}, function (data) {
|
|
||||||
store.clear('url');
|
|
||||||
store.clear('connected');
|
|
||||||
store.set('path', '#/');
|
|
||||||
c.trigger('logout');
|
|
||||||
c.flash('success', y18n.t('logged_out'));
|
|
||||||
c.redirect('#/login');
|
|
||||||
}, undefined, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,109 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Post installation
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Step 1 : introduction
|
|
||||||
app.get('#/postinstall', function(c) {
|
|
||||||
$('#masthead').hide();
|
|
||||||
c.checkInstall(function(isInstalled) {
|
|
||||||
if (isInstalled || typeof isInstalled === 'undefined') {
|
|
||||||
c.redirect_to('#/login');
|
|
||||||
} else {
|
|
||||||
c.view('postinstall/postinstall_1');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step 2 : domain
|
|
||||||
app.get('#/postinstall/domain', function(c) {
|
|
||||||
$('#masthead').hide();
|
|
||||||
$.get('https://dyndns.yunohost.org/domains', function() {})
|
|
||||||
.done(function(data){
|
|
||||||
c.params['ddomains'] = data.map(function(dom){return '.'+dom;});
|
|
||||||
})
|
|
||||||
.fail(function() {
|
|
||||||
c.params['ddomains'] = ['.nohost.me', '.noho.st', '.ynh.fr'];
|
|
||||||
})
|
|
||||||
.always(function() {
|
|
||||||
c.view('postinstall/postinstall_2', c.params, function() {
|
|
||||||
$('#domain, #ddomain').keyup(function(event){
|
|
||||||
if(event.keyCode == 13){
|
|
||||||
$('a.savedomain').click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('a.savedomain').on('click', function(e) {
|
|
||||||
if ($('#domain').val() === '') {
|
|
||||||
if ($('#ddomain').val() === '') {
|
|
||||||
e.preventDefault();
|
|
||||||
c.flash('fail', y18n.t('error_select_domain'));
|
|
||||||
} else {
|
|
||||||
domain = $('#ddomain').val() + $('select[name="ddomain-ext"]').val();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
domain = $('#domain').val();
|
|
||||||
}
|
|
||||||
store.set('maindomain', domain);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step 3 : administration passowrd
|
|
||||||
app.get('#/postinstall/password', function(c) {
|
|
||||||
$('#masthead').hide();
|
|
||||||
if (!store.get('maindomain')) {
|
|
||||||
c.redirect_to('#/postinstall/domain');
|
|
||||||
} else {
|
|
||||||
c.view('postinstall/postinstall_3', { 'domain': store.get('maindomain').toLowerCase() });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Execute post-installation
|
|
||||||
app.post('#/postinstall', function (c) {
|
|
||||||
|
|
||||||
var password = c.params['password'];
|
|
||||||
var confirmation = c.params['confirmation'];
|
|
||||||
var domain = c.params['domain'].toLowerCase();
|
|
||||||
|
|
||||||
// Check password ain't empty
|
|
||||||
if (password === '' || confirmation === '') {
|
|
||||||
c.flash('fail', y18n.t('password_empty'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check password matches confirmation
|
|
||||||
if (password !== confirmation) {
|
|
||||||
c.flash('fail', y18n.t('passwords_dont_match'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check domain ain't empty...
|
|
||||||
if (domain === '') {
|
|
||||||
c.flash('fail', y18n.t('error_select_domain'));
|
|
||||||
c.redirect_to('#/postinstall/domain', {slide: false});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask confirmation to the user
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('postinstall'),
|
|
||||||
y18n.t('confirm_postinstall', [c.params['domain']]),
|
|
||||||
// Start the actual postinstall process
|
|
||||||
function(){
|
|
||||||
store.set('url', window.location.hostname +'/yunohost/api');
|
|
||||||
store.set('user', 'admin');
|
|
||||||
c.api('POST', '/postinstall', {domain: domain, password: password}, function() {
|
|
||||||
c.flash('success', y18n.t('installation_complete'));
|
|
||||||
c.redirect_to('#/login');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,141 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Services
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// All services status
|
|
||||||
app.get('#/services', function (c) {
|
|
||||||
c.api('GET', '/services', {}, function(data) {
|
|
||||||
var data2 = {
|
|
||||||
services: []
|
|
||||||
};
|
|
||||||
$.each(data, function(k, v) {
|
|
||||||
v.name = k;
|
|
||||||
if (v.last_state_change == 'unknown')
|
|
||||||
{
|
|
||||||
v.last_state_change = 0;
|
|
||||||
}
|
|
||||||
data2.services.push(v);
|
|
||||||
});
|
|
||||||
|
|
||||||
data2.services.sort(function (a, b) {
|
|
||||||
if (a.name > b.name) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (a.name < b.name) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
c.view('service/service_list', data2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Status & actions for a service
|
|
||||||
app.get('#/services/:service', function (c) {
|
|
||||||
c.api('GET', '/services/'+ c.params['service'], {}, function(data) {
|
|
||||||
c.api('GET', '/services/'+ c.params['service'] +'/log', {number: 50}, function(data_log) {
|
|
||||||
|
|
||||||
data.name = c.params['service'];
|
|
||||||
if (data.last_state_change == 'unknown')
|
|
||||||
{
|
|
||||||
data.last_state_change = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.logs = [];
|
|
||||||
$.each(data_log, function(k, v) {
|
|
||||||
data.logs.push({filename: k, filecontent: v.join('\n')});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sort logs by filename, put the journalctl/systemd log on top
|
|
||||||
data.logs.sort(function(a,b) { return a.filename === "journalctl" ? -1 : b.filename === "journalctl" ? 1 : a.filename < b.filename ? -1 : a.filename > b.filename ? 1 : 0; });
|
|
||||||
|
|
||||||
c.view('service/service_info', data, function() {
|
|
||||||
|
|
||||||
// Don't allow user to stop critical services from the webadmin
|
|
||||||
$('button[data-action="stop"]').each(function() {
|
|
||||||
|
|
||||||
var critical = ['nginx', 'ssh', 'slapd', 'yunohost-api'];
|
|
||||||
var service = $(this).data('service');
|
|
||||||
|
|
||||||
if (critical.indexOf(service) >= 0)
|
|
||||||
{
|
|
||||||
$(this).hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure behavior for enable/disable and start/stop buttons
|
|
||||||
$('button[data-action="start"], button[data-action="restart"], button[data-action="stop"]').on('click', function() {
|
|
||||||
|
|
||||||
var service = $(this).data('service');
|
|
||||||
var action = $(this).data('action');
|
|
||||||
|
|
||||||
c.confirm(y18n.t("services"), y18n.t('confirm_service_' + action, [service]), function(){
|
|
||||||
|
|
||||||
if (action == "start")
|
|
||||||
{
|
|
||||||
var method = "PUT";
|
|
||||||
var url = "/services/" + service;
|
|
||||||
}
|
|
||||||
else if (action == "restart")
|
|
||||||
{
|
|
||||||
var method = "PUT";
|
|
||||||
var url = "/services/" + service + "/restart";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var method = "DELETE";
|
|
||||||
var url = "/services/" + service;
|
|
||||||
}
|
|
||||||
c.api(method, url, {}, function() { c.refresh(); });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure behavior for enable/disable and start/stop buttons
|
|
||||||
$('button[data-action="share"]').on('click', function() {
|
|
||||||
|
|
||||||
c.showLoader();
|
|
||||||
|
|
||||||
// Send to paste.yunohost.org
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: 'https://paste.yunohost.org/documents',
|
|
||||||
data: $("#logs").text(),
|
|
||||||
})
|
|
||||||
.done(function(data, textStatus, jqXHR) {
|
|
||||||
window.open('https://paste.yunohost.org/' + data.key, '_blank');
|
|
||||||
})
|
|
||||||
.fail(function() {
|
|
||||||
c.flash('fail', y18n.t('paste_error'));
|
|
||||||
})
|
|
||||||
.always(function(){
|
|
||||||
c.hideLoader();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Service log
|
|
||||||
app.get('#/services/:service/log', function (c) {
|
|
||||||
var params = {
|
|
||||||
number: 50
|
|
||||||
};
|
|
||||||
c.api('GET', '/services/'+ c.params['service'] +'/log', params, function(data) { // ?
|
|
||||||
data2 = { 'logs': [], 'name': c.params['service'] };
|
|
||||||
$.each(data, function(k, v) {
|
|
||||||
data2.logs.push({filename: k, filecontent: v.join('\n')});
|
|
||||||
});
|
|
||||||
|
|
||||||
c.view('service/service_log', data2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,243 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tools
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Tools list
|
|
||||||
app.get('#/tools', function (c) {
|
|
||||||
c.view('tools/tools_list');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update administration password
|
|
||||||
app.get('#/tools/adminpw', function (c) {
|
|
||||||
c.view('tools/tools_adminpw');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update administration password (PUT)
|
|
||||||
app.put('#/tools/adminpw', function (c) {
|
|
||||||
var params = {};
|
|
||||||
$.each(c.params.toHash(), function(key, value) {
|
|
||||||
if (value !== '') { params[key] = value; }
|
|
||||||
});
|
|
||||||
if ($.isEmptyObject(params)) {
|
|
||||||
c.flash('fail', y18n.t('error_modify_something'));
|
|
||||||
c.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (params['new_password'] !== params['confirm_new_password']) {
|
|
||||||
c.flash('fail', y18n.t('passwords_dont_match'));
|
|
||||||
c.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
c.api('POST', '/login', { 'password': params['old_password'] }, function(data) {
|
|
||||||
// Remove useless variable
|
|
||||||
delete params['old_password'];
|
|
||||||
delete params['confirm_new_password'];
|
|
||||||
|
|
||||||
// Update password and redirect to the home
|
|
||||||
c.api('PUT', '/adminpw', params, function(data) {
|
|
||||||
c.redirect_to('#/logout');
|
|
||||||
});
|
|
||||||
}, undefined, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// System update & upgrade
|
|
||||||
app.get('#/update', function (c) {
|
|
||||||
c.api('PUT', '/update', {}, function(data) {
|
|
||||||
c.api('GET', '/migrations?pending', {}, function(pending_migrations) {
|
|
||||||
data.pending_migrations = pending_migrations.migrations;
|
|
||||||
c.view('tools/tools_update', data, function() {
|
|
||||||
// Configure buttons behaviors
|
|
||||||
$("button[data-upgrade]").on("click", function() {
|
|
||||||
|
|
||||||
var what = $(this).data("upgrade").toLowerCase();
|
|
||||||
|
|
||||||
// Upgrade all apps or the system
|
|
||||||
|
|
||||||
if ((what == "system") || (what == "apps"))
|
|
||||||
{
|
|
||||||
var confirm_message = y18n.t('confirm_update_' + what);
|
|
||||||
var api_url = '/upgrade?'+what;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade a specific apps
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var confirm_message = y18n.t('confirm_update_specific_app', [what]);
|
|
||||||
var api_url = '/upgrade/apps?app='+what;
|
|
||||||
}
|
|
||||||
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('tools'),
|
|
||||||
confirm_message,
|
|
||||||
function(){
|
|
||||||
c.api('PUT', api_url, {}, function(data) {
|
|
||||||
c.redirect_to('#/tools/logs');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Display journals list
|
|
||||||
app.get('#/tools/logs', function (c) {
|
|
||||||
c.api('GET', "/logs?limit=40&with_details", {}, function(operations) {
|
|
||||||
operations = operations["operation"];
|
|
||||||
success_icons = {
|
|
||||||
true: 'check text-success',
|
|
||||||
false: 'close text-danger',
|
|
||||||
'?': 'question text-warning'
|
|
||||||
}
|
|
||||||
for (var log in operations)
|
|
||||||
{
|
|
||||||
operations[log].success_icon = success_icons[operations[log].success]
|
|
||||||
}
|
|
||||||
|
|
||||||
c.view('tools/tools_logs', {
|
|
||||||
"operations": operations,
|
|
||||||
"locale": y18n.locale
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// One journal
|
|
||||||
app.get(/\#\/tools\/logs\/(.*)(\?number=(\d+))?/, function (c) {
|
|
||||||
var params = "?path=" + c.params["splat"][0];
|
|
||||||
var number = (c.params["number"])?c.params["number"]:25;
|
|
||||||
params += "&filter_irrelevant&with_suboperations&number=" + number;
|
|
||||||
|
|
||||||
c.api('GET', "/logs/display" + params, {}, function(log) {
|
|
||||||
c.view('tools/tools_log', {
|
|
||||||
"log": log,
|
|
||||||
"next_number": log.logs.length == number ? number * 10:false,
|
|
||||||
"locale": y18n.locale
|
|
||||||
}, function() {
|
|
||||||
log = $("#main #log").html();
|
|
||||||
log = log.replace(/.*: ERROR - .*/g, function (match) { return '<span class="alert-danger">'+match+'</span>'});
|
|
||||||
log = log.replace(/.*: WARNING - .*/g, function (match) { return '<span class="alert-warning">'+match+'</span>'});
|
|
||||||
log = log.replace(/.*: SUCCESS - .*/g, function (match) { return '<span class="alert-success">'+match+'</span>'});
|
|
||||||
log = log.replace(/.*: INFO - .*/g, function (match) { return '<span class="alert-info">'+match+'</span>'});
|
|
||||||
$("#main #log").html(log);
|
|
||||||
|
|
||||||
// Configure behavior for the button to share log on Yunohost (it calls display --share)
|
|
||||||
$('button[data-action="share"]').on("click", function() {
|
|
||||||
c.api('GET', '/logs/display?path='+$(this).data('log-id')+'&share', {},
|
|
||||||
function(data) {
|
|
||||||
c.hideLoader();
|
|
||||||
window.open(data.url, '_blank');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reboot or shutdown button
|
|
||||||
app.get('#/tools/reboot', function (c) {
|
|
||||||
c.view('tools/tools_reboot', {}, function() {
|
|
||||||
// Configure reboot/shutdown buttons behavior
|
|
||||||
$("button[data-action]").on("click", function() {
|
|
||||||
var action = $(this).data("action");
|
|
||||||
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('tools_' + action),
|
|
||||||
y18n.t('confirm_reboot_action_' + action),
|
|
||||||
function(){
|
|
||||||
c.api('PUT', '/'+action+'?force', {}, function(data) {
|
|
||||||
// This code is not executed due to 502 response (reboot or shutdown)
|
|
||||||
c.redirect_to('#/logout');
|
|
||||||
}, function (xhr) {
|
|
||||||
c.flash('success', y18n.t('tools_' + action + '_done'))
|
|
||||||
// Disconnect from the webadmin
|
|
||||||
store.clear('url');
|
|
||||||
store.clear('connected');
|
|
||||||
store.set('path', '#/');
|
|
||||||
|
|
||||||
// Rename the page to allow refresh without ask for rebooting
|
|
||||||
window.location.href = window.location.href.split('#')[0] + '#/';
|
|
||||||
// Display reboot or shutdown info
|
|
||||||
// We can't use template because now the webserver is off
|
|
||||||
if (action == 'reboot') {
|
|
||||||
$('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-refresh"></i> ' + y18n.t('tools_rebooting') + '</div></div>');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-power-off"></i> ' + y18n.t('tools_shuttingdown') + '</div></div>');
|
|
||||||
}
|
|
||||||
|
|
||||||
c.hideLoader();
|
|
||||||
|
|
||||||
// Force scrollTop on page load
|
|
||||||
$('html, body').scrollTop(0);
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Migrations
|
|
||||||
app.get('#/tools/migrations', function (c) {
|
|
||||||
c.api('GET', '/migrations?pending', {}, function(pending_migrations) {
|
|
||||||
c.api('GET', '/migrations?done', {}, function(done_migrations) {
|
|
||||||
pending_migrations = pending_migrations.migrations;
|
|
||||||
done_migrations = done_migrations.migrations;
|
|
||||||
|
|
||||||
// Get rid of _ in the raw name of migrations (cosmetic)
|
|
||||||
for(var i = 0; i < pending_migrations.length; i++) {
|
|
||||||
pending_migrations[i].name = pending_migrations[i].name.replace(/_/g, " ")
|
|
||||||
if (pending_migrations[i].disclaimer)
|
|
||||||
{
|
|
||||||
pending_migrations[i].disclaimer = pending_migrations[i].disclaimer.replace(/\n/g, "<br />");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(var i = 0; i < done_migrations.length; i++) {
|
|
||||||
done_migrations[i].name = done_migrations[i].name.replace(/_/g, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.view('tools/tools_migrations', {
|
|
||||||
'pending_migrations' : pending_migrations.reverse(),
|
|
||||||
'done_migrations' : done_migrations.reverse()
|
|
||||||
}, function() {
|
|
||||||
|
|
||||||
// Configure button 'Run'
|
|
||||||
$('button[data-action="run"]').on("click", function() {
|
|
||||||
|
|
||||||
var disclaimerAcks = $(".disclaimer-ack");
|
|
||||||
for (var i = 0 ; i < disclaimerAcks.length ; i++)
|
|
||||||
{
|
|
||||||
if (! $(disclaimerAcks[i]).find("input:checked").val())
|
|
||||||
{
|
|
||||||
// FIXME / TODO i18n
|
|
||||||
c.flash('fail', "Some of these migrations require you to acknowledge a disclaimer before running them.");
|
|
||||||
c.refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
c.api('POST', '/migrations/migrate?accept_disclaimer', {}, function() { c.refresh(); });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure buttons 'Skip'
|
|
||||||
$('button[data-action="skip"]').on("click", function() {
|
|
||||||
var migration_id = $(this).data("migration");
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('migrations'),
|
|
||||||
y18n.t('confirm_migrations_skip'),
|
|
||||||
function(){
|
|
||||||
c.api('POST', '/migrations/migrate?skip&targets=' + migration_id, {}, function() { c.refresh() });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -1,382 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
var PASSWORD_MIN_LENGTH = 4;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Groups and permissions
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update group or permissions
|
|
||||||
*
|
|
||||||
* @model data organize in the same way than /users/groups?full&include_primary_groups
|
|
||||||
* @params.operation "add"|"remove"
|
|
||||||
* @params.type "members"|"permissions"
|
|
||||||
* @param.item Name of the user or the permission to add or remove
|
|
||||||
* @param.group Name of the group affected
|
|
||||||
*
|
|
||||||
* This function is built to be apply with params generated by the use of
|
|
||||||
* HTML dataset attributes (e.g. link in the partial inline view "label" in group_list.ms)
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
**/
|
|
||||||
function updateGroup(model, params) {
|
|
||||||
var type = params.type;
|
|
||||||
var action = params.action;
|
|
||||||
var item = params.item;
|
|
||||||
var groupname = params.group;
|
|
||||||
var group = data.groups[groupname];
|
|
||||||
var to = (action == 'add')?group[type]:group[type + 'Inv'];
|
|
||||||
var from = (action == 'add')?group[type+'Inv']:group[type];
|
|
||||||
// Do nothing, if array of destination already contains the item
|
|
||||||
if (from.indexOf(item) === -1) return;
|
|
||||||
|
|
||||||
// Hack to disable pacman loader if any
|
|
||||||
if ($('div.loader').length === 0) {
|
|
||||||
$('#main').append('<div class="loader loader-content" style="display: none"></div>');
|
|
||||||
}
|
|
||||||
$('div.loader').css('display', 'none');
|
|
||||||
|
|
||||||
// Update group
|
|
||||||
var params = {}; var url;
|
|
||||||
if (type == 'members') {
|
|
||||||
url = '/users/groups/' + groupname;
|
|
||||||
params[action] = [item];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
url = '/users/permissions/' + item;
|
|
||||||
params[action] = [groupname];
|
|
||||||
}
|
|
||||||
c.api('PUT', url, params, function(data_update) {
|
|
||||||
to.push(item);
|
|
||||||
from.splice(from.indexOf(item), 1);
|
|
||||||
updateView(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the view with the new model
|
|
||||||
*
|
|
||||||
* @model data organize in the same way than /users/groups?full&include_primary_groups
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
**/
|
|
||||||
function updateView(model) {
|
|
||||||
// Sort in aphanumerical order to improve user experience
|
|
||||||
for (var group in model.groups) {
|
|
||||||
model.groups[group].permissions.sort();
|
|
||||||
model.groups[group].permissionsInv.sort();
|
|
||||||
model.groups[group].members.sort();
|
|
||||||
model.groups[group].membersInv.sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manual render, we don't use c.view to avoid scrollTop and other
|
|
||||||
// uneeded behaviour
|
|
||||||
var rendered = c.render('views/user/group_list.ms', model);
|
|
||||||
rendered.swap(function () {
|
|
||||||
// Add click event to get a nice "reactive" interface
|
|
||||||
$("button[data-action='add'], button[data-action='remove']").on('click', function (e) {
|
|
||||||
updateGroup(model, $(this)[0].dataset);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('button[data-action="add-user-specific-permission"]').on('click', function (e) {
|
|
||||||
data.groups[$(this).data("user")].display = true;
|
|
||||||
updateView(data);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$('button[data-action="delete-group"]').on('click', function (e) {
|
|
||||||
|
|
||||||
var group = $(this).data("group");
|
|
||||||
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('groups'),
|
|
||||||
$('<div>'+ y18n.t('confirm_delete', [group]) +'</div>'),
|
|
||||||
function() {
|
|
||||||
c.api('DELETE', '/users/groups/'+ group, {}, function(data) { c.refresh(); });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
app.get('#/groups', function (c) {
|
|
||||||
c.api('GET', '/users/groups?full&include_primary_groups', {}, function(data_groups) {
|
|
||||||
c.api('GET', '/users', {}, function(data_users) {
|
|
||||||
c.api('GET', '/users/permissions?full', {}, function(data_permissions) {
|
|
||||||
//var perms = data_permissions.permissions;
|
|
||||||
var specific_perms = {};
|
|
||||||
var all_perms = data_permissions.permissions;
|
|
||||||
var users = Object.keys(data_users.users);
|
|
||||||
|
|
||||||
// Enrich groups data with primary group indicator and inversed items list
|
|
||||||
for (var group in data_groups.groups) {
|
|
||||||
data_groups.groups[group].primary = users.indexOf(group) !== -1;
|
|
||||||
data_groups.groups[group].permissionsInv = Object.keys(all_perms).filter(function(item) {
|
|
||||||
return data_groups.groups[group].permissions.indexOf(item) === -1;
|
|
||||||
}).filter(function(item) {
|
|
||||||
// Remove 'email', 'xmpp' and protected permission in visitors's permission choice list
|
|
||||||
return group != "visitors" || (item != "mail.main" && item != "xmpp.main" && ! all_perms[item].protected == true);
|
|
||||||
});
|
|
||||||
data_groups.groups[group].membersInv = users.filter(function(item) {
|
|
||||||
return data_groups.groups[group].members.indexOf(item) === -1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare all_users and visitors has special
|
|
||||||
data_groups.groups['all_users'].special = true;
|
|
||||||
data_groups.groups['visitors'].special = true;
|
|
||||||
|
|
||||||
// Data given to the view with 2 functions to convert technical
|
|
||||||
// permission id to display names
|
|
||||||
data = {
|
|
||||||
'groups':data_groups.groups,
|
|
||||||
'displayPermission': function (text) {
|
|
||||||
return all_perms[text].label;
|
|
||||||
},
|
|
||||||
'displayUser': function (text) {
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
'is_protected': function (item, type, group) {
|
|
||||||
if (type == 'permission' && group == 'visitors') {
|
|
||||||
return all_perms[item].protected;
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateView(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a new group
|
|
||||||
app.get('#/groups/create', function (c) {
|
|
||||||
c.view('user/group_create', {});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('#/groups/create', function (c) {
|
|
||||||
c.params['groupname'] = c.params['groupname'].replace(' ', '_').toLowerCase();
|
|
||||||
c.api('POST', '/users/groups', c.params.toHash(), function(data) {
|
|
||||||
c.redirect_to('#/groups');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Users
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// List existing users
|
|
||||||
app.get('#/users', function (c) {
|
|
||||||
c.api('GET', '/users', {}, function(data) {
|
|
||||||
c.view('user/user_list', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create user form
|
|
||||||
app.get('#/users/create', function (c) {
|
|
||||||
c.api('GET', '/domains', {}, function(data) {
|
|
||||||
|
|
||||||
// Password min length
|
|
||||||
data.password_min_length = PASSWORD_MIN_LENGTH;
|
|
||||||
c.view('user/user_create', data, function(){
|
|
||||||
var usernameField = $('#username');
|
|
||||||
usernameField.on('input', function(){
|
|
||||||
var emailLeft = $('#email-left');
|
|
||||||
emailLeft.html(usernameField.val());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create user (POST)
|
|
||||||
app.post('#/users/create', function (c) {
|
|
||||||
if (c.params['password'] != c.params['confirmation']) {
|
|
||||||
c.flash('fail', y18n.t('passwords_dont_match'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (c.params['password'].length < PASSWORD_MIN_LENGTH) {
|
|
||||||
c.flash('fail', y18n.t('passwords_too_short'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
c.params['domain'] = c.params['domain'].slice(1);
|
|
||||||
c.params['username'] = c.params['username'].trim();
|
|
||||||
|
|
||||||
c.api('POST', '/users', c.params.toHash(), function(data) {
|
|
||||||
c.redirect_to('#/users');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show user information
|
|
||||||
app.get('#/users/:user', function (c) {
|
|
||||||
c.api('GET', '/users/'+ c.params['user'], {}, function(data) {
|
|
||||||
c.view('user/user_info', data, function() {
|
|
||||||
|
|
||||||
// Configure delete button behavior
|
|
||||||
$('button[data-action="delete"]').on("click", function() {
|
|
||||||
var user = $(this).data("user");
|
|
||||||
|
|
||||||
var params = {};
|
|
||||||
|
|
||||||
// make confirm content
|
|
||||||
var purgeCheckbox = '<div><input type="checkbox" id="purge-user-data" name="purge-user-data"> <label for="purge-user-data">'+ y18n.t('purge_user_data_checkbox', [user]) +'</label></div>';
|
|
||||||
var purgeAlertMessage = '<div class="danger" style="display: none">⚠ '+ y18n.t('purge_user_data_warning') +'</div>';
|
|
||||||
var confirmModalContent = $('<div>'+ y18n.t('confirm_delete', [user]) +'<br><br>'+ purgeCheckbox +'<br>'+ purgeAlertMessage +'</div>');
|
|
||||||
|
|
||||||
// display confirm modal
|
|
||||||
c.confirm(
|
|
||||||
y18n.t('users'),
|
|
||||||
confirmModalContent,
|
|
||||||
function(){
|
|
||||||
c.api('DELETE', '/users/'+ user, params, function(data) {
|
|
||||||
c.redirect_to('#/users');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// toggle purge warning and parameter
|
|
||||||
confirmModalContent.find("input").click(function(){
|
|
||||||
|
|
||||||
if (confirmModalContent.find("input").is(':checked')) {
|
|
||||||
params.purge = "";
|
|
||||||
confirmModalContent.find(".danger").show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
delete params.purge;
|
|
||||||
confirmModalContent.find(".danger").hide();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Edit user form
|
|
||||||
app.get('#/users/:user/edit', function (c) {
|
|
||||||
c.api('GET', '/users/'+ c.params['user'], {}, function(data) {
|
|
||||||
c.api('GET', '/domains', {}, function(dataDomains) {
|
|
||||||
|
|
||||||
// Password min length
|
|
||||||
data.password_min_length = PASSWORD_MIN_LENGTH;
|
|
||||||
|
|
||||||
// User email use a fake splitted field
|
|
||||||
var email = data.mail.split('@');
|
|
||||||
data.email = {
|
|
||||||
username : email[0],
|
|
||||||
domain : email[1]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return quota with M unit
|
|
||||||
if (data['mailbox-quota'].limit) {
|
|
||||||
var unit = data['mailbox-quota'].limit.slice(-1);
|
|
||||||
var value = data['mailbox-quota'].limit.substr(0, data['mailbox-quota'].limit.length -1);
|
|
||||||
if (unit == 'b') {
|
|
||||||
data.quota = Math.ceil(value / (1024 * 1024));
|
|
||||||
}
|
|
||||||
else if (unit == 'k') {
|
|
||||||
data.quota = Math.ceil(value / 1024);
|
|
||||||
}
|
|
||||||
else if (unit == 'M') {
|
|
||||||
data.quota = value;
|
|
||||||
}
|
|
||||||
else if (unit == 'G') {
|
|
||||||
data.quota = Math.ceil(value * 1024);
|
|
||||||
}
|
|
||||||
else if (unit == 'T') {
|
|
||||||
data.quota = Math.ceil(value * 1024 * 1024);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {data.quota = 0;}
|
|
||||||
|
|
||||||
// Domains
|
|
||||||
data.domains = [];
|
|
||||||
$.each(dataDomains.domains, function(key, value) {
|
|
||||||
data.domains.push({
|
|
||||||
domain: value,
|
|
||||||
selected: (value == data.email.domain) ? true : false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
c.view('user/user_edit', data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update user information
|
|
||||||
app.put('#/users/:user', function (c) {
|
|
||||||
// Get full user object
|
|
||||||
c.api('GET', '/users/'+ c.params['user'], {}, function(user) {
|
|
||||||
// Force unit or disable quota
|
|
||||||
if (c.params['mailbox_quota']) {
|
|
||||||
c.params['mailbox_quota'] += "M";
|
|
||||||
}
|
|
||||||
else {c.params['mailbox_quota'] = 0;}
|
|
||||||
|
|
||||||
// concat email/domain pseudo field
|
|
||||||
if (c.params['mail'] !== c.params['email'] + c.params['domain']) {
|
|
||||||
c.params['mail'] = c.params['email'] + c.params['domain'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
c.params['mail'] = '';
|
|
||||||
}
|
|
||||||
// Clear temporary inputs
|
|
||||||
c.params['email'] = c.params['domain'] = '';
|
|
||||||
|
|
||||||
|
|
||||||
// force array type for mail aliases and redirections
|
|
||||||
if (typeof c.params['mailalias'] == 'string') {c.params['mailalias'] = [c.params['mailalias']];}
|
|
||||||
if (typeof c.params['mailforward'] == 'string') {c.params['mailforward'] = [c.params['mailforward']];}
|
|
||||||
|
|
||||||
// Check for added/removed aliases and redirections
|
|
||||||
c.params['add_mailalias'] = c.arrayDiff(c.params['mailalias'], user['mail-aliases']);
|
|
||||||
c.params['remove_mailalias'] = c.arrayDiff(user['mail-aliases'], c.params['mailalias']);
|
|
||||||
c.params['add_mailforward'] = c.arrayDiff(c.params['mailforward'], user['mail-forward']);
|
|
||||||
c.params['remove_mailforward'] = c.arrayDiff(user['mail-forward'], c.params['mailforward']);
|
|
||||||
|
|
||||||
// Clear temporary inputs
|
|
||||||
c.params['mailalias'] = c.params['mailforward'] = '';
|
|
||||||
|
|
||||||
// Remove empty inputs
|
|
||||||
var params = {};
|
|
||||||
$.each(c.params.toHash(), function(key, value) {
|
|
||||||
if (value.length > 0 && key !== 'user') { params[key] = value; }
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($.isEmptyObject(params)) {
|
|
||||||
c.flash('fail', y18n.t('error_modify_something'));
|
|
||||||
c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false});
|
|
||||||
} else {
|
|
||||||
if (params['password']) {
|
|
||||||
if (params['password'] == params['confirmation']) {
|
|
||||||
if (params['password'].length < PASSWORD_MIN_LENGTH) {
|
|
||||||
c.flash('fail', y18n.t('passwords_too_short'));
|
|
||||||
c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
params['change_password'] = params['password'];
|
|
||||||
c.api('PUT', '/users/'+ c.params['user'], params, function(data) {
|
|
||||||
c.redirect_to('#/users/'+ c.params['user']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.flash('fail', y18n.t('passwords_dont_match'));
|
|
||||||
c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
c.api('PUT', '/users/'+ c.params['user'], params, function(data) {
|
|
||||||
c.redirect_to('#/users/'+ c.params['user']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,45 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Events
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
app.bind('login', function(e, data) {
|
|
||||||
c.api('GET', '/users', {}, function(data) {
|
|
||||||
// Warn admin if no users are created.
|
|
||||||
if (typeof data.users !== 'undefined' && data.users.length === 0) {
|
|
||||||
c.flash('warning', y18n.t('warning_first_user'));
|
|
||||||
}
|
|
||||||
|
|
||||||
c.api('GET', '/versions', {}, function(data) {
|
|
||||||
$('#yunohost-version').html(y18n.t('footer_version', [data.yunohost.version, data.yunohost.repo]));
|
|
||||||
c.hideLoader();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.bind('logout', function(e, data) {
|
|
||||||
$('#yunohost-version').empty();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Konamicode ;P up up down down left right left right b a
|
|
||||||
var konami_code = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65],
|
|
||||||
konami_step = 0;
|
|
||||||
$(document).keydown(function (e) {
|
|
||||||
if (e.keyCode === konami_code[konami_step++]) {
|
|
||||||
if (konami_step === konami_code.length) {
|
|
||||||
konami_step = 0;
|
|
||||||
$('#main').addClass("with-nyancat");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
konami_step = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,54 +0,0 @@
|
||||||
(function() {
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filters
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
function prefetchDomains(req) {
|
|
||||||
// Preload domains list.
|
|
||||||
req.params.domains = [];
|
|
||||||
req.api('GET', '/domains', {}, function(data) {
|
|
||||||
req.params.domains = data.domains;
|
|
||||||
req.params.domains_main = data.main;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function prefetchUsers(req){
|
|
||||||
// Preload users lists.
|
|
||||||
req.params.users = [];
|
|
||||||
req.api('GET', '/users', {}, function(data) {
|
|
||||||
req.params.users = data.users;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.before(/domains\/add/, prefetchDomains);
|
|
||||||
app.before(/apps\/install\//, prefetchDomains);
|
|
||||||
app.before(/apps\/install\//, prefetchUsers);
|
|
||||||
app.before(/apps\/install\/custom\//, prefetchDomains);
|
|
||||||
app.before(/apps\/install\/custom\//, prefetchUsers);
|
|
||||||
app.before(/apps\/\w+\/actions/, prefetchUsers);
|
|
||||||
app.before(/apps\/\w+\/actions/, prefetchDomains);
|
|
||||||
app.before(/apps\/\w+\/config-panel/, prefetchUsers);
|
|
||||||
app.before(/apps\/\w+\/config-panel/, prefetchDomains);
|
|
||||||
|
|
||||||
|
|
||||||
app.before({except: {path: ['#/logout', '#/login', '#/postinstall', '#/postinstall/domain', '#/postinstall/password']}}, function (req) {
|
|
||||||
// Store path for further redirections
|
|
||||||
store.set('path-1', store.get('path'));
|
|
||||||
store.set('path', req.path);
|
|
||||||
|
|
||||||
// Redirect to login page if no credentials stored
|
|
||||||
if (!store.get('connected')) {
|
|
||||||
req.redirect('#/login');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.after(function () {});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,502 +0,0 @@
|
||||||
(function() {
|
|
||||||
|
|
||||||
// This is to fetch the "RANDOMID" after script.min.js?version= in
|
|
||||||
// index.html
|
|
||||||
// We later use the same ID as a hacky cache mechanism for the .ms files
|
|
||||||
var ynh_assets_version = document.getElementsByTagName('script')[0].src.split("=")[1];
|
|
||||||
|
|
||||||
// Get application context
|
|
||||||
var app = Sammy.apps['#main'];
|
|
||||||
var store = app.store;
|
|
||||||
|
|
||||||
// The logic used to temporily disable transition is from
|
|
||||||
// https://stackoverflow.com/a/16575811
|
|
||||||
function whichTransitionEvent(){
|
|
||||||
var t;
|
|
||||||
var el = document.createElement('fakeelement');
|
|
||||||
var transitions = {
|
|
||||||
'transition':'transitionend',
|
|
||||||
'OTransition':'oTransitionEnd',
|
|
||||||
'MozTransition':'transitionend',
|
|
||||||
'WebkitTransition':'webkitTransitionEnd'
|
|
||||||
}
|
|
||||||
|
|
||||||
for(t in transitions){
|
|
||||||
if( el.style[t] !== undefined ){
|
|
||||||
return transitions[t];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var transitionEvent = whichTransitionEvent();
|
|
||||||
|
|
||||||
function resetSliders()
|
|
||||||
{
|
|
||||||
// Disable transition effects
|
|
||||||
$('#slider-container').addClass('notransition');
|
|
||||||
// Delete the left/right temporary stuff only used during animation
|
|
||||||
$('#slideTo').css('display', 'none');
|
|
||||||
$('#slideTo').html("");
|
|
||||||
$('#slideBack').css('display', 'none');
|
|
||||||
$('#slideBack').html("");
|
|
||||||
// Set the margin-left back to 0
|
|
||||||
$('#slider-container').css('margin-left', '0');
|
|
||||||
// c.f. the stackoverflow thread
|
|
||||||
$('#slider-container')[0].offsetHeight;
|
|
||||||
// Remove the binding to this event handler for next times
|
|
||||||
// Re-enable transition effects
|
|
||||||
$('#slider-container').removeClass('notransition');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helpers
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
app.helpers({
|
|
||||||
|
|
||||||
//
|
|
||||||
// Pacman loader management
|
|
||||||
//
|
|
||||||
|
|
||||||
showLoader: function() {
|
|
||||||
app.loaded = false; // Not sure if that's really useful ... this is from old code with no explanation what it really does ...
|
|
||||||
if ($('div.loader').length === 0) {
|
|
||||||
$('#main').append('<div class="loader loader-content"></div>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hideLoader: function() {
|
|
||||||
app.loaded = true; // Not sure if that's really useful ... this is from old code with no explanation what it really does ...
|
|
||||||
$('div.loader').remove();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Flash helper to diplay instant notifications
|
|
||||||
flash: function (level, message) {
|
|
||||||
if (!store.get('flash')) {
|
|
||||||
store.set('flash', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper CSS class on main wrapper
|
|
||||||
$('#slider').addClass('with-flashMessage');
|
|
||||||
|
|
||||||
// If the line is a bash command
|
|
||||||
if (level === 'info' && message.charAt(0) === '+') {
|
|
||||||
level = 'log';
|
|
||||||
}
|
|
||||||
|
|
||||||
message = message.split("\n").join("<br />");
|
|
||||||
|
|
||||||
// If the message starts with a progress bar
|
|
||||||
progressbar = message.match(/^\[#*\+*\.*\] > /);
|
|
||||||
if (progressbar)
|
|
||||||
{
|
|
||||||
progressbar = progressbar[0];
|
|
||||||
// Remove the progress bar from the mesage
|
|
||||||
message = message.replace(progressbar,"");
|
|
||||||
// Compute percent
|
|
||||||
done = (progressbar.match(/#/g)||[]).length;
|
|
||||||
ongoing = (progressbar.match(/\+/g)||[]).length;
|
|
||||||
remaining = (progressbar.match(/\./g)||[]).length;
|
|
||||||
total = done + ongoing + remaining;
|
|
||||||
done = done * 100 / total;
|
|
||||||
ongoing = ongoing * 100 / total;
|
|
||||||
// Actually build the message with the progress bar
|
|
||||||
message = '<div class="progress"><div class="progress-bar progress-bar-success" role="progressbar" style="width:'+done+'%"></div><div class="progress-bar progress-bar-striped active" role="progressbar" style="width:'+ongoing+'%;"></div></div><p style="display: inline-block;">' + message + '</p>';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
message = '<p>'+message+'</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add message
|
|
||||||
$('#flashMessage .messages')
|
|
||||||
.prepend('<div class="alert alert-'+ level +'">'+message+'</div>');
|
|
||||||
|
|
||||||
// Scroll to top to view new messages
|
|
||||||
$('#flashMessage').scrollTop(0);
|
|
||||||
},
|
|
||||||
|
|
||||||
checkInstall: function(callback) {
|
|
||||||
// Get base url from store or guess from current url
|
|
||||||
var baseUrl = (store.get('url') !== null) ? store.get('url')
|
|
||||||
: window.location.hostname + '/yunohost/api';
|
|
||||||
|
|
||||||
// Call API endpoint
|
|
||||||
$.ajax({
|
|
||||||
dataType: "json",
|
|
||||||
url: 'https://'+ baseUrl +'/installed',
|
|
||||||
timeout: 3000,
|
|
||||||
success: function(data) {
|
|
||||||
callback(data.installed);
|
|
||||||
},
|
|
||||||
fail: function() {
|
|
||||||
callback(undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// API call
|
|
||||||
api: function(method, uri, data, callback, callbackOnFailure, websocket) {
|
|
||||||
c = this;
|
|
||||||
|
|
||||||
method = typeof method !== 'undefined' ? method : 'GET';
|
|
||||||
data = typeof data !== 'undefined' ? data : {};
|
|
||||||
if (window.navigator && window.navigator.language && (typeof data.locale === 'undefined')) {
|
|
||||||
data.locale = y18n.locale || window.navigator.language.substr(0, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
c.showLoader();
|
|
||||||
|
|
||||||
call = function(uri, callback, method, data, callbackOnFailure) {
|
|
||||||
|
|
||||||
// Define default callback for failures
|
|
||||||
if (typeof callbackOnFailure !== 'function') {
|
|
||||||
callbackOnFailure = function(xhr) {
|
|
||||||
if (xhr.status == 200) {
|
|
||||||
// Fail with 200, WTF
|
|
||||||
callback({});
|
|
||||||
}
|
|
||||||
// Unauthorized or wrong password
|
|
||||||
else if (xhr.status == 401) {
|
|
||||||
if (uri === '/login') {
|
|
||||||
c.flash('fail', y18n.t('wrong_password'));
|
|
||||||
} else {
|
|
||||||
c.flash('fail', y18n.t('unauthorized'));
|
|
||||||
c.redirect('#/login');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 500
|
|
||||||
else if (xhr.status == 500) {
|
|
||||||
try {
|
|
||||||
error_log = JSON.parse(xhr.responseText);
|
|
||||||
error_log.route = error_log.route.join(' ') + '\n';
|
|
||||||
error_log.arguments = JSON.stringify(error_log.arguments);
|
|
||||||
}
|
|
||||||
catch (e)
|
|
||||||
{
|
|
||||||
error_log = {};
|
|
||||||
error_log.route = "Failed to parse route";
|
|
||||||
error_log.arguments = "Failed to parse arguments";
|
|
||||||
error_log.traceback = xhr.responseText;
|
|
||||||
}
|
|
||||||
c.flash('fail', y18n.t('internal_exception', [error_log.route, error_log.arguments, error_log.traceback]));
|
|
||||||
}
|
|
||||||
// 502 Bad gateway means API is down
|
|
||||||
else if (xhr.status == 502) {
|
|
||||||
c.flash('fail', y18n.t('api_not_responding'));
|
|
||||||
}
|
|
||||||
// More verbose error messages first
|
|
||||||
else if (typeof xhr.responseText !== 'undefined') {
|
|
||||||
c.flash('fail', xhr.responseText);
|
|
||||||
}
|
|
||||||
// 0 mean "the connexion has been closed" apparently
|
|
||||||
else if (xhr.status == 0) {
|
|
||||||
var errorMessage = xhr.status+' '+xhr.statusText;
|
|
||||||
c.flash('fail', y18n.t('error_connection_interrupted', [errorMessage]));
|
|
||||||
console.log(xhr);
|
|
||||||
}
|
|
||||||
// Return HTTP error code at least
|
|
||||||
else {
|
|
||||||
var errorMessage = xhr.status+' '+xhr.statusText;
|
|
||||||
c.flash('fail', y18n.t('error_server_unexpected', [errorMessage]));
|
|
||||||
console.log(xhr);
|
|
||||||
}
|
|
||||||
|
|
||||||
c.hideLoader();
|
|
||||||
|
|
||||||
// Force scrollTop on page load
|
|
||||||
$('html, body').scrollTop(0);
|
|
||||||
store.clear('slide');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery.ajax({
|
|
||||||
url: 'https://' + store.get('url') + uri,
|
|
||||||
type: method,
|
|
||||||
crossdomain: true,
|
|
||||||
data: data,
|
|
||||||
traditional: true,
|
|
||||||
dataType: 'json'
|
|
||||||
})
|
|
||||||
.always(function(xhr, ts, error) {
|
|
||||||
})
|
|
||||||
.done(function(data) {
|
|
||||||
data = data || {};
|
|
||||||
callback(data);
|
|
||||||
})
|
|
||||||
.fail(callbackOnFailure);
|
|
||||||
};
|
|
||||||
|
|
||||||
websocket = typeof websocket !== 'undefined' ? websocket : true;
|
|
||||||
if (websocket) {
|
|
||||||
// Open a WebSocket connection to retrieve live messages from the moulinette
|
|
||||||
var ws = new WebSocket('wss://'+ store.get('url') +'/messages');
|
|
||||||
// Flag to avoid to call twice the API
|
|
||||||
// We need to set that in ws object as we need to use it in ws.onopen
|
|
||||||
// and several ws object could be running at the same time...
|
|
||||||
ws.api_called = false;
|
|
||||||
ws.onmessage = function(evt) {
|
|
||||||
// console.log(evt.data);
|
|
||||||
$.each($.parseJSON(evt.data), function(k, v) {
|
|
||||||
c.flash(k, v);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// If not connected, WebSocket connection will raise an error, but we do not want to interrupt API request
|
|
||||||
ws.onerror = function () {
|
|
||||||
ws.onopen();
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = function() { };
|
|
||||||
|
|
||||||
ws.onopen = function () {
|
|
||||||
if (!ws.api_called) {
|
|
||||||
ws.api_called = true;
|
|
||||||
call(uri, callback, method, data, callbackOnFailure);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
call(uri, callback, method, data, callbackOnFailure);
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// Ask confirmation to the user through the modal window
|
|
||||||
confirm: function(title, content, confirmCallback, cancelCallback) {
|
|
||||||
c = this;
|
|
||||||
|
|
||||||
// Default callbacks
|
|
||||||
confirmCallback = typeof confirmCallback !== 'undefined' ? confirmCallback : function() {};
|
|
||||||
cancelCallback = typeof cancelCallback !== 'undefined' ? cancelCallback : function() {};
|
|
||||||
|
|
||||||
c.hideLoader();
|
|
||||||
|
|
||||||
// Get modal element
|
|
||||||
var box = $('#modal');
|
|
||||||
|
|
||||||
// Modal title
|
|
||||||
if (typeof title === 'string' && title.length) {
|
|
||||||
$('.title', box).html(title);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
box.addClass('no-title');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modal content
|
|
||||||
$('.content', box).html(content);
|
|
||||||
|
|
||||||
// Clear any remaining click event that could still be there (e.g.
|
|
||||||
// clicking outside the modal window doesn't equal to clicking
|
|
||||||
// cancel...
|
|
||||||
$('footer button', box).unbind( "click" );
|
|
||||||
|
|
||||||
// Handle buttons
|
|
||||||
$('footer button', box)
|
|
||||||
.click(function(e){
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
$('#modal footer button').unbind( "click" );
|
|
||||||
// Reset & Hide modal
|
|
||||||
box.removeClass('no-title').modal('hide');
|
|
||||||
|
|
||||||
// Do corresponding callback
|
|
||||||
if ($(this).data('modal-action') == 'confirm') {
|
|
||||||
confirmCallback();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cancelCallback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show modal
|
|
||||||
return box.modal('show');
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// Render view (cross-browser)
|
|
||||||
view: function (view, data, callback) {
|
|
||||||
c = this;
|
|
||||||
|
|
||||||
// Default
|
|
||||||
callback = typeof callback !== 'undefined' ? callback : function() {};
|
|
||||||
|
|
||||||
// Hide loader and modal
|
|
||||||
c.hideLoader();
|
|
||||||
$('#modal').modal('hide');
|
|
||||||
|
|
||||||
// Render content
|
|
||||||
var rendered = this.render('dist/views/' + view + '.ms?version=' + ynh_assets_version, data);
|
|
||||||
|
|
||||||
// Update content helper
|
|
||||||
var leSwap = function() {
|
|
||||||
rendered.swap(function() {
|
|
||||||
// Clicking on those kind of CSS elements will trigger a
|
|
||||||
// slide effect i.e. the next view rendering will have
|
|
||||||
// store.get('slide') set to 'back' or 'to'
|
|
||||||
$('.slide, .btn-breadcrumb a:not(:last-child)').on('click', function() {
|
|
||||||
$(this).addClass('active');
|
|
||||||
if ($(this).hasClass('back') || $(this).parent('.btn-breadcrumb').length) {
|
|
||||||
store.set('slide', 'back');
|
|
||||||
} else {
|
|
||||||
store.set('slide', 'to');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Force scrollTop on page load
|
|
||||||
$('html, body').scrollTop(0);
|
|
||||||
|
|
||||||
// Run callback
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Slide back effect
|
|
||||||
if (store.get('slide') == 'back') {
|
|
||||||
|
|
||||||
store.clear('slide');
|
|
||||||
// Disable transition while we tweak CSS
|
|
||||||
$('#slider-container').addClass('notransition');
|
|
||||||
// "Delete" the left part of the slider
|
|
||||||
$('#slideBack').css('display', 'none');
|
|
||||||
|
|
||||||
// Push the slider to the left
|
|
||||||
$('#slider-container').css('margin-left', '-100%');
|
|
||||||
// slideTo is the right part, and should contain the old view,
|
|
||||||
// so we copypasta what's in the "center" slider (#main)
|
|
||||||
$('#slideTo').show().html($('#main').html());
|
|
||||||
// leSwap will put the new view in the "center" slider (#main)
|
|
||||||
leSwap();
|
|
||||||
|
|
||||||
// So now things look like:
|
|
||||||
// | |
|
|
||||||
// | the screen |
|
|
||||||
// | |
|
|
||||||
//
|
|
||||||
// . #main . #slideTo .
|
|
||||||
// . the new view . the old view .
|
|
||||||
// ^ ^
|
|
||||||
// margin-left: -100% currently shown
|
|
||||||
//
|
|
||||||
// =====>>> sliiiiide =====>>>
|
|
||||||
|
|
||||||
// Re-add transition effect
|
|
||||||
$('#slider-container').removeClass('notransition');
|
|
||||||
|
|
||||||
// add the transition event to detect the end of the transition effect
|
|
||||||
transitionEvent
|
|
||||||
&& $("#slider-container").off(transitionEvent)
|
|
||||||
&& $("#slider-container").on(transitionEvent, resetSliders);
|
|
||||||
|
|
||||||
// And actually play the transition effect that will move the container from left to right
|
|
||||||
$('#slider-container').css('margin-left', '0px');
|
|
||||||
}
|
|
||||||
// Slide to effect
|
|
||||||
else if (store.get('slide') == 'to') {
|
|
||||||
|
|
||||||
// Disable transition while we tweak CSS
|
|
||||||
$('#slider-container').addClass('notransition');
|
|
||||||
// "Delete" the right part of the slider
|
|
||||||
$('#slideTo').css('display', 'none');
|
|
||||||
// Push the slider to the right
|
|
||||||
$('#slider-container').css('margin-left', '0px');
|
|
||||||
// slideBack should contain the old view,
|
|
||||||
// so we copypasta what's in the "center" slider (#main)
|
|
||||||
$('#slideBack').show().html($('#main').html());
|
|
||||||
leSwap();
|
|
||||||
|
|
||||||
// So now things look like:
|
|
||||||
//
|
|
||||||
// | |
|
|
||||||
// | the screen |
|
|
||||||
// | |
|
|
||||||
//
|
|
||||||
// . . #slideBack . #main .
|
|
||||||
// . . the old view . the new view .
|
|
||||||
// ^ ^ ^
|
|
||||||
// margin-left: -100% currently shown
|
|
||||||
//
|
|
||||||
// <<<===== sliiiiide <<<=======
|
|
||||||
|
|
||||||
|
|
||||||
// Re-add transition effect
|
|
||||||
$('#slider-container').removeClass('notransition');
|
|
||||||
|
|
||||||
// add the transition event to detect the end of the transition effect
|
|
||||||
var transitionEvent = whichTransitionEvent();
|
|
||||||
transitionEvent
|
|
||||||
&& $("#slider-container").off(transitionEvent)
|
|
||||||
&& $("#slider-container").on(transitionEvent, resetSliders);
|
|
||||||
|
|
||||||
// And actually play the transition effect that will move the container from right to left
|
|
||||||
$('#slider-container').css('margin-left', '-100%');
|
|
||||||
}
|
|
||||||
// No slideing effect
|
|
||||||
else {
|
|
||||||
leSwap();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
redirect_to: function(destination, options) {
|
|
||||||
c = this;
|
|
||||||
|
|
||||||
options = options !== undefined ? options : {};
|
|
||||||
|
|
||||||
// If destination if the same as current url,
|
|
||||||
// we don't want to display the slide animation
|
|
||||||
// (or if the code explicitly state to disable slide animation)
|
|
||||||
if ((c.path.split("#")[1] == destination.split("#")[1]) || (options.slide == false))
|
|
||||||
{
|
|
||||||
store.clear('slide');
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a copy-pasta of some of the redirect/refresh code of
|
|
||||||
// sammy.js because for some reason calling the original
|
|
||||||
// redirect/refresh function in some context does not work >.>
|
|
||||||
// (e.g. if you're already on the page)
|
|
||||||
c.trigger('redirect', {to: destination});
|
|
||||||
c.app.last_location = c.path;
|
|
||||||
c.app.setLocation(destination);
|
|
||||||
c.app.trigger('location-changed');
|
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function() {
|
|
||||||
c = this;
|
|
||||||
c.redirect_to(c.path, {slide: false});
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
|
||||||
// Array / object helpers
|
|
||||||
//
|
|
||||||
|
|
||||||
arraySortById: function(arr) {
|
|
||||||
arr.sort(function(a, b){
|
|
||||||
if (a.id > b.id) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if (a.id < b.id) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
arrayDiff: function(arr1, arr2) {
|
|
||||||
arr1 = arr1 || [];
|
|
||||||
arr2 = arr2 || [];
|
|
||||||
return arr1.filter(function (a) {
|
|
||||||
return ((arr2.indexOf(a) == -1) && (a !== ""));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Serialize an object
|
|
||||||
serialize : function(obj) {
|
|
||||||
var str = [];
|
|
||||||
for(var p in obj)
|
|
||||||
if (obj.hasOwnProperty(p)) {
|
|
||||||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
|
|
||||||
}
|
|
||||||
return str.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -1,248 +0,0 @@
|
||||||
(function() {
|
|
||||||
|
|
||||||
var app = Sammy('#main', function (sam) {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sammy Configuration
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Plugins
|
|
||||||
sam.use('Handlebars', 'ms');
|
|
||||||
window.HandlebarsIntl.registerWith(Handlebars);
|
|
||||||
|
|
||||||
Handlebars.registerHelper('ucwords', function(str) {
|
|
||||||
return (str + '').replace(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, function ($1) {
|
|
||||||
return $1.toUpperCase();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Handlebars.registerHelper('humanSize', function(bytes) {
|
|
||||||
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
if (bytes === 0) return 'n/a';
|
|
||||||
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
|
||||||
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[[i]];
|
|
||||||
});
|
|
||||||
Handlebars.registerHelper('humanTime', function(time) {
|
|
||||||
return Math.round(time) + 's';
|
|
||||||
});
|
|
||||||
Handlebars.registerHelper('bitRate', function(bytes, time) {
|
|
||||||
var sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb'];
|
|
||||||
if (time === 0) return 'n/a';
|
|
||||||
var bps = bytes / time * 8;
|
|
||||||
var i = parseInt(Math.floor(Math.log(bps) / Math.log(1024)));
|
|
||||||
return Math.round(bps / Math.pow(1024, i), 2) + ' ' + sizes[[i]] + '/s';
|
|
||||||
});
|
|
||||||
|
|
||||||
Handlebars.registerHelper('t', function(y18n_key) {
|
|
||||||
var result = y18n.t(y18n_key, Array.prototype.slice.call(arguments, 1));
|
|
||||||
return new Handlebars.SafeString(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
Handlebars.registerHelper('concat', function() {
|
|
||||||
var outStr = '';
|
|
||||||
for(var arg in arguments){
|
|
||||||
if(typeof arguments[arg]!='object'){
|
|
||||||
outStr += arguments[arg];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return outStr;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Block helper to add a tooltip to any element
|
|
||||||
Handlebars.registerHelper('tooltip', function(tooltip, options) {
|
|
||||||
return new Handlebars.SafeString(
|
|
||||||
'<span data-toggle="tooltip" title="' + tooltip + '" data-placement="right">'
|
|
||||||
+ options.fn(this)
|
|
||||||
+ '</span>');
|
|
||||||
});
|
|
||||||
|
|
||||||
Handlebars.registerHelper('helpTooltip', function(text, url) {
|
|
||||||
var help = null;
|
|
||||||
var helpUrl = null;
|
|
||||||
if (text && text.string && text.string.trim() != "")
|
|
||||||
{
|
|
||||||
help = text.string;
|
|
||||||
}
|
|
||||||
if (url && url.string && url.string.trim() != "")
|
|
||||||
{
|
|
||||||
helpUrl = url.string;
|
|
||||||
}
|
|
||||||
if (help == null && helpUrl == null)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (helpUrl == null)
|
|
||||||
{
|
|
||||||
return new Handlebars.SafeString(
|
|
||||||
'<span data-toggle="tooltip" title="' + help + '" data-html="true" data-placement="right">'
|
|
||||||
+ ' <i class="fa-question-circle"></i>'
|
|
||||||
+ '</span>');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new Handlebars.SafeString(
|
|
||||||
'<span data-toggle="tooltip" title="' + help + '" data-html="true" data-placement="right">'
|
|
||||||
+ ' <a href="' + helpUrl + '" class="fa-question-circle"></a>'
|
|
||||||
+ '</span>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load tooltips on the page; needed if using tooltips
|
|
||||||
Handlebars.registerHelper('load_tooltips', function() {
|
|
||||||
return new Handlebars.SafeString(
|
|
||||||
'<script>'
|
|
||||||
+ '$(document).ready(function(){'
|
|
||||||
+ '$(\'[data-toggle="tooltip"]\').tooltip();'
|
|
||||||
+ '});'
|
|
||||||
+ '</script>');
|
|
||||||
});
|
|
||||||
|
|
||||||
// equality stuff because mustache/Handlebars is lame
|
|
||||||
// source https://stackoverflow.com/a/31632215
|
|
||||||
Handlebars.registerHelper({
|
|
||||||
eq: function(a, b) {
|
|
||||||
return a === b;
|
|
||||||
},
|
|
||||||
neq: function(a, b) {
|
|
||||||
return a !== b;
|
|
||||||
},
|
|
||||||
lt: function (v1, v2) {
|
|
||||||
return v1 < v2;
|
|
||||||
},
|
|
||||||
gt: function (v1, v2) {
|
|
||||||
return v1 > v2;
|
|
||||||
},
|
|
||||||
lte: function (v1, v2) {
|
|
||||||
return v1 <= v2;
|
|
||||||
},
|
|
||||||
gte: function (v1, v2) {
|
|
||||||
return v1 >= v2;
|
|
||||||
},
|
|
||||||
and: function () {
|
|
||||||
return Array.prototype.slice.call(arguments).every(function (arg) {
|
|
||||||
return (Array.isArray(arg))?arg.length !== 0:arg;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
or: function () {
|
|
||||||
return Array.prototype.slice.call(arguments, 0, -1).some(function (arg) {
|
|
||||||
return (Array.isArray(arg))?arg.length !== 0:arg;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Be able to call a function given in context
|
|
||||||
Handlebars.registerHelper('call', function () {
|
|
||||||
var args = Array.prototype.slice.call(arguments);
|
|
||||||
var func = args.shift();
|
|
||||||
args.pop();
|
|
||||||
return func.apply(null, args);
|
|
||||||
});
|
|
||||||
|
|
||||||
Handlebars.registerHelper('in', function(a) {
|
|
||||||
// skip first one
|
|
||||||
for (var i = 1; i < arguments.length; ++i) {
|
|
||||||
if (arguments[i] == a)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Look for supported type of storage to use
|
|
||||||
/**
|
|
||||||
* http://sammyjs.org/docs/api/0.7.4/all#Sammy.Store.LocalStorage
|
|
||||||
* LocalStorage is our favorite, as it allows multiple tabs
|
|
||||||
*/
|
|
||||||
var storageType;
|
|
||||||
if (Sammy.Store.isAvailable('local')) {
|
|
||||||
storageType = 'local';
|
|
||||||
} else if (Sammy.Store.isAvailable('session')) {
|
|
||||||
storageType = 'session';
|
|
||||||
} else if (Sammy.Store.isAvailable('cookie')) {
|
|
||||||
storageType = 'cookie';
|
|
||||||
} else {
|
|
||||||
storageType = 'memory';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize storage
|
|
||||||
sam.store = new Sammy.Store({name: 'storage', type: storageType});
|
|
||||||
sam.loaded = false;
|
|
||||||
sam.isInstalledTry = 3;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Application bootstrap
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
sam.bind('run', function () {
|
|
||||||
// Store url
|
|
||||||
sam.store.set('url', window.location.hostname + '/yunohost/api');
|
|
||||||
|
|
||||||
if (sam.store.get('connected')) {
|
|
||||||
this.api('GET', '/versions', {}, function(data) {
|
|
||||||
$('#yunohost-version').html(y18n.t('footer_version', [data.yunohost.version, data.yunohost.repo]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flash messages
|
|
||||||
var flashMessage = $('#flashMessage');
|
|
||||||
$('#toggle-btn', flashMessage).click(function(e) {
|
|
||||||
flashMessage.toggleClass('open');
|
|
||||||
});
|
|
||||||
$('#clear-btn', flashMessage).click(function(e) {
|
|
||||||
flashMessage.removeClass('open').find('.messages').html('');
|
|
||||||
$('#slider').removeClass('with-flashMessage');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Errors
|
|
||||||
*/
|
|
||||||
sam.notFound = function(){
|
|
||||||
// Redirect to home page on 404.
|
|
||||||
window.location = '#/';
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translations
|
|
||||||
*/
|
|
||||||
$.getJSON('locales/en.json', function(data){
|
|
||||||
y18n.translations['en'] = data;
|
|
||||||
y18n.translateInlineHTML();
|
|
||||||
});
|
|
||||||
|
|
||||||
// User defined language
|
|
||||||
if (window.navigator && window.navigator.language) {
|
|
||||||
y18n.locale = window.navigator.language.substr(0, 2);
|
|
||||||
if (y18n.locale !== 'en') {
|
|
||||||
$.getJSON('locales/'+ y18n.locale +'.json', function(data){
|
|
||||||
y18n.translations[y18n.locale] = data;
|
|
||||||
y18n.translateInlineHTML();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run the application
|
|
||||||
*/
|
|
||||||
$(document).ready(function () {
|
|
||||||
// Run Sammy.js application
|
|
||||||
app.run('#/');
|
|
||||||
|
|
||||||
// April fools easter egg ;)
|
|
||||||
var today = new Date();
|
|
||||||
if ((today.getDate() == 1) && (today.getMonth()+1 == 4))
|
|
||||||
{
|
|
||||||
$('#main').addClass("magikarp");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,114 +0,0 @@
|
||||||
;(function(y18n){
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
// Default options
|
|
||||||
var defaultOptions = {
|
|
||||||
defaultLocale: "en",
|
|
||||||
locale: "en",
|
|
||||||
placeholder: /(?:\{\{|%\{)(.*?)(?:\}\}?)/gm,
|
|
||||||
translations: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialization
|
|
||||||
*/
|
|
||||||
y18n.init = function() {
|
|
||||||
// Merge options with defaults.
|
|
||||||
for (var key in defaultOptions) {
|
|
||||||
y18n[key] = (typeof y18n[key] !== 'undefined') ? y18n[key] : defaultOptions[key];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
y18n.init();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTML Inline translation
|
|
||||||
*/
|
|
||||||
y18n.translateInlineHTML = function(){
|
|
||||||
// Inner HTML
|
|
||||||
[].forEach.call(
|
|
||||||
document.querySelectorAll('[data-y18n]'),
|
|
||||||
function(el){
|
|
||||||
el.innerHTML = y18n.translate(el.getAttribute('data-y18n'));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Attributes
|
|
||||||
[].forEach.call(
|
|
||||||
document.querySelectorAll('[data-y18n-title]'),
|
|
||||||
function(el){
|
|
||||||
el.title = y18n.translate(el.getAttribute('data-y18n-title'));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translation
|
|
||||||
*/
|
|
||||||
y18n.translate = function(key, options) {
|
|
||||||
options = options || {'locale' : y18n.locale};
|
|
||||||
options.locale = options.locale || y18n.locale;
|
|
||||||
|
|
||||||
// Get translation
|
|
||||||
var translation = this.lookup(key, options);
|
|
||||||
|
|
||||||
// Translation fallback
|
|
||||||
if ((typeof translation === 'undefined' || translation === key) && options.locale !== y18n.defaultLocale) {
|
|
||||||
options.locale = y18n.defaultLocale;
|
|
||||||
return this.translate(key, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variables remplacement
|
|
||||||
return (translation) ? translation.printf(options) : key;
|
|
||||||
};
|
|
||||||
|
|
||||||
y18n.lookup = function(key, options) {
|
|
||||||
// Default locale
|
|
||||||
if (typeof options.locale === 'undefined') {
|
|
||||||
options.locale = y18n.locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get translation string
|
|
||||||
if (typeof y18n.translations[options.locale] !== 'undefined') {
|
|
||||||
if (typeof y18n.translations[options.locale][key] !== 'undefined') {
|
|
||||||
return y18n.translations[options.locale][key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Save some typing
|
|
||||||
y18n.t = y18n.translate;
|
|
||||||
|
|
||||||
})(typeof(exports) === 'undefined' ? (this.y18n || (this.y18n = {})) : exports);
|
|
||||||
|
|
||||||
// http://monocleglobe.wordpress.com/2010/01/12/everybody-needs-a-little-printf-in-their-javascript/
|
|
||||||
String.prototype.printf = function (obj) {
|
|
||||||
var useArguments = false;
|
|
||||||
var _arguments = arguments;
|
|
||||||
var i = -1;
|
|
||||||
if (typeof _arguments[0] == "string") {
|
|
||||||
useArguments = true;
|
|
||||||
}
|
|
||||||
if (obj instanceof Array || useArguments) {
|
|
||||||
return this.replace(/\%s/g,
|
|
||||||
function (a, b) {
|
|
||||||
i++;
|
|
||||||
if (useArguments) {
|
|
||||||
if (typeof _arguments[i] == 'string') {
|
|
||||||
return _arguments[i];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Error("Arguments element is an invalid type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj[i];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return this.replace(/{([^{}]*)}/g,
|
|
||||||
function (a, b) {
|
|
||||||
var r = obj[b];
|
|
||||||
return typeof r === 'string' || typeof r === 'number' ? r : a;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"password": "Heslo"
|
|
||||||
}
|
|
7792
src/package-lock.json
generated
7792
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,47 +0,0 @@
|
||||||
{
|
|
||||||
"name": "yunohost-admin",
|
|
||||||
"description": "YunoHost Admin web interface",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/YunoHost/yunohost-admin"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "gulp build",
|
|
||||||
"build-dev": "gulp build --dev",
|
|
||||||
"watch": "gulp watch",
|
|
||||||
"watch-dev": "gulp watch --dev"
|
|
||||||
},
|
|
||||||
"author": "Yunohost",
|
|
||||||
"license": "AGPL-3.0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/YunoHost/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/YunoHost/yunohost-admin",
|
|
||||||
"devDependencies": {
|
|
||||||
"gulp": "^4.0.2",
|
|
||||||
"gulp-autoprefixer": "^7.0.1",
|
|
||||||
"gulp-concat": "^2.6.1",
|
|
||||||
"gulp-csslint": "^1.0.1",
|
|
||||||
"gulp-cssmin": "^0.2.0",
|
|
||||||
"gulp-if": "^3.0.0",
|
|
||||||
"gulp-imagemin": "^7.1.0",
|
|
||||||
"gulp-jshint": "^2.1.0",
|
|
||||||
"jshint": "^2.11.1",
|
|
||||||
"gulp-less": "^4.0.1",
|
|
||||||
"gulp-rename": "^2.0.0",
|
|
||||||
"gulp-uglify": "^3.0.2",
|
|
||||||
"gulp-mustache": "^5.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"bootstrap": "^3.3.7",
|
|
||||||
"fork-awesome": "^1.1.7",
|
|
||||||
"handlebars": "^4.7.6",
|
|
||||||
"handlebars-intl": "1.1.2",
|
|
||||||
"isotope-layout": "^3.0.6",
|
|
||||||
"jquery": "^3.5.1",
|
|
||||||
"js-cookie": "^2.2.1",
|
|
||||||
"sammy": "^0.7.6",
|
|
||||||
"source-code-pro": "^2.30.2",
|
|
||||||
"source-sans-pro": "^3.6.0"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps/{{app}}">{{app_name}}</a>
|
|
||||||
<a href="#/apps/{{app}}/actions">{{t 'app_actions'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="alert alert-warning" role="alert">{{t 'experimental_warning'}}</div>
|
|
||||||
|
|
||||||
{{#if actions}}
|
|
||||||
{{#actions}}
|
|
||||||
<form action="#/apps/{{../app}}/actions/{{id}}" method="PUT" class="form-horizontal form-app-install">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">{{name}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#if description}}
|
|
||||||
<p>{{description}}</p>
|
|
||||||
{{/if}}
|
|
||||||
{{#if arguments}}
|
|
||||||
{{#arguments}}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="{{name}}" class="col-sm-12">{{label}}</label>
|
|
||||||
|
|
||||||
{{#if helpText}}
|
|
||||||
<span class="help-block help-block--help col-sm-12">{{{helpText}}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="col-sm-12">
|
|
||||||
|
|
||||||
{{#if choices}}
|
|
||||||
<select id="{{name}}" name="{{name}}" required class="form-control" {{{attributes}}}>
|
|
||||||
{{#choices}}<option value="{{value}}" {{#if selected}}selected{{/if}}>{{label}}</option>{{/choices}}
|
|
||||||
</select>
|
|
||||||
{{else}}
|
|
||||||
<input type="{{inputType}}" id="{{name}}" name="{{name}}" class="form-control" value="{{default}}" placeholder="{{example}}" {{required}} {{{attributes}}}>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if helpLink}}
|
|
||||||
<span class="help-block help-block--link">{{{helpLink}}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if example}}
|
|
||||||
<span class="help-block help-block--example">{{t 'form_input_example' example}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/arguments}}
|
|
||||||
<hr>
|
|
||||||
{{/if}}
|
|
||||||
<input type="hidden" name="app" value="{{id}}">
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" class="btn btn-success slide back" value="{{t 'perform'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{{/actions}}
|
|
||||||
|
|
||||||
{{else}}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<span class="fa-exclamation-triangle"></span>
|
|
||||||
{{t 'app_no_actions'}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
|
@ -1,113 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps/catalog">{{t 'catalog'}}</a>
|
|
||||||
<a href="#/apps/catalog/{{category.id}}">{{category.title}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="input-group" id="app-filter-input">
|
|
||||||
<div class="input-group-btn"><a class="btn btn-primary" href="#/apps/catalog"><i class="fa-arrow-left"></i></a></div>
|
|
||||||
<span class="input-group-addon"><i class="fa-fw fa-{{category.icon}}"></i> {{category.title}}</span>
|
|
||||||
<span class="input-group-addon" style="background: white;border: none;"> </span>
|
|
||||||
<span class="input-group-addon"><i class="fa-search"></i></span>
|
|
||||||
<input type="text" id="filter-app-cards" class="form-control" role="textbox" placeholder="{{t 'search_for_apps'}}" aria-describedby="basic-addon0"/>
|
|
||||||
<div class="input-group-btn">
|
|
||||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<span id="app-cards-list-filter-text">{{t 'only_decent_quality_apps'}}</span> <span class="caret"></span>
|
|
||||||
</button>
|
|
||||||
<ul id="dropdownFilter" class="dropdown-menu" data-filter="decentQuality" role="menu">
|
|
||||||
<li role="presentation" class="button dropdownFilter" data-filter="isHighQuality"><a class="menu-item" role="menu-item" tabindex="-1">{{t 'only_highquality_apps'}}</a></li>
|
|
||||||
<li role="presentation" class="button dropdownFilter" data-filter="decentQuality"><a class="menu-item" role="menu-item" tabindex="-1">{{t 'only_decent_quality_apps'}}</a></li>
|
|
||||||
<li role="presentation" class="button dropdownFilter" data-filter="isworking"><a class="menu-item" role="menu-item" tabindex="-1">{{t 'only_working_apps'}}</a></li>
|
|
||||||
<li role="presentation" class="button dropdownFilter" data-filter="*"><a class="menu-item" role="menu-item" tabindex="-1">{{t 'all_apps'}}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="subtag-selector">
|
|
||||||
{{#if category.subtags}}
|
|
||||||
<button class="btn btn-default active" data-subtag="all">{{t 'all'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{#category.subtags}}
|
|
||||||
<button class="btn btn-default" data-subtag="{{id}}">{{title}}</button>
|
|
||||||
{{/category.subtags}}
|
|
||||||
{{#if category.subtags}}
|
|
||||||
<button class="btn btn-default" data-subtag="others">{{t 'others'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div id="apps" class="list-group grid">
|
|
||||||
{{#apps}}
|
|
||||||
<div class="app-card panel panel-default {{state}} {{isWorking}} {{isHighQuality}} {{decentQuality}} {{level}}-level" data-subtags="{{#subtags}}{{.}}{{#unless @last}},{{/unless}}{{/subtags}}">
|
|
||||||
<div class="panel-body">
|
|
||||||
<h2 class="app-title">
|
|
||||||
{{manifest.name}}
|
|
||||||
{{#if (eq state 'working') }}
|
|
||||||
{{#if (eq decentQuality 'badQuality')}}
|
|
||||||
<span class="label label-warning label-as-badge app-state" title="{{t 'app_state_low_quality_explanation' }}">{{t 'app_state_low_quality' }}</span>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
<span class="label label-{{stateColor}} label-as-badge app-state" title="{{t (concat 'app_state_' state '_explanation') }}">{{t (concat 'app_state_' state) }}</span>
|
|
||||||
{{/if}}
|
|
||||||
</h2>
|
|
||||||
<div class="app-card-desc">{{manifest.description}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="app-card-date-maintainer">
|
|
||||||
{{#if (eq maintainedColor 'danger') }}
|
|
||||||
<span class="text text-warning maintained-status" title="{{t (concat maintained '_details') }}"><i class="fa-fw fa-warning"></i> {{t maintained}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{{git.url}}" target="_BLANK" type="button" role="button" class="btn btn-default col-xs-4">
|
|
||||||
<i class="fa-fw fa-code"></i> Code
|
|
||||||
</a>
|
|
||||||
<a href="{{git.url}}/blob/master/README.md" target="_BLANK" type="button" role="button" class="btn btn-default col-xs-4">
|
|
||||||
<i class="fa-fw fa-book"></i> Readme
|
|
||||||
</a>
|
|
||||||
{{#installable}}
|
|
||||||
<a href="#/apps/install/{{manifest.id}}" type="button" role="button" class="btn btn-{{installColor}} col-xs-4 active">
|
|
||||||
<i class="fa-fw fa-plus"></i> {{t 'install'}}{{^isSafe}} <i class="fa-fw fa-warning"></i>{{/isSafe}}
|
|
||||||
</a>
|
|
||||||
{{/installable}}
|
|
||||||
{{^installable}}
|
|
||||||
<span type="button" class="btn btn-default col-sm-4 active disabled"> {{t 'installed'}}</span>
|
|
||||||
{{/installable}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/apps}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="install-custom-app" class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-download"></span> {{t 'custom_app_install'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<p class="alert alert-warning">
|
|
||||||
<span class="fa-fw fa-warning"></span>
|
|
||||||
{{t 'confirm_install_custom_app'}}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<form class="form-horizontal">
|
|
||||||
<div class="form-group has-feedback">
|
|
||||||
<label for="url" class="col-sm-12">{{t 'url'}}</label>
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<input type="url" id="url" name="url" class="form-control" value="" placeholder="https://github.com/USER/REPOSITORY" required pattern="^https://github.com/[a-zA-Z0-9-_.]+/[a-zA-Z0-9-_.]+[/]?$">
|
|
||||||
<p class="text-warning">
|
|
||||||
<span class="fa-fw fa-github"></span> {{t 'custom_app_url_only_github'}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="text-center">
|
|
||||||
<a role="button" class="btn btn-success slide">{{t 'install'}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,23 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps/catalog">{{t 'catalog'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div id="category-selector" class="list-group grid">
|
|
||||||
<a class="app-category-card panel panel-default" href="#/apps/catalog/all">
|
|
||||||
<div class="panel-body">
|
|
||||||
<h2 class="app-category-title" style="padding-top: 1em;"><span class="fa-fw fa-search"></span> {{t 'all_apps'}}</h2>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{{#categories}}
|
|
||||||
<a class="app-category-card panel panel-default" href="#/apps/catalog/{{id}}">
|
|
||||||
<div class="panel-body">
|
|
||||||
<h2 class="app-category-title"><span class="fa-fw fa-{{icon}}"></span> {{title}}</h2>
|
|
||||||
<h3 class="app-category-card-desc">{{description}}</h3>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{{/categories}}
|
|
||||||
</div>
|
|
|
@ -1,36 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps" class="hidden-xs">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps" class="visible-xs">…</a>
|
|
||||||
<a href="#/apps/{{id}}">{{label}}</a>
|
|
||||||
<a href="#/apps/{{id}}/changelabel">{{t 'app_manage_label_and_tiles'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/apps/{{id}}/changelabel" method="POST" class="form-horizontal form-app-install">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-tag"></span> {{t 'app_manage_label_and_tiles'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#each permissions}}
|
|
||||||
<div class="permission-row" data-permission="{{name}}">
|
|
||||||
<h3>{{title}}</h3>
|
|
||||||
{{#if tile_available}}
|
|
||||||
<p>{{t 'permission_corresponding_url'}}: <a href=https://{{url}}>https://{{url}}</a></p>
|
|
||||||
{{/if}}
|
|
||||||
<input type="text" id="label" name="label_{{name}}" class="form-control" value="{{label}}" required="required">
|
|
||||||
{{#if tile_available}}
|
|
||||||
<input type="checkbox" id="show_tile_{{name}}" name="show_tile_{{name}}" value="{{name}}" {{#if show_tile}} checked {{/if}}>
|
|
||||||
<label for="show_tile_{{name}}" style="font-weight:normal;"> {{t 'permission_show_tile_enabled'}}</label>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
{{/each}}
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'apply'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
|
@ -1,38 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps" class="hidden-xs">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps" class="visible-xs">…</a>
|
|
||||||
<a href="#/apps/{{id}}">{{label}}</a>
|
|
||||||
<a href="#/apps/{{id}}/changeurl">{{t 'app_change_url'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/apps/{{id}}/changeurl" method="POST" class="form-horizontal form-app-install">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-exchange"></span> {{t 'app_change_url'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="domain" class="col-sm-12">{{t 'domain'}}</label>
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<select id="domain" name="domain" required class="form-control" {{{attributes}}}>
|
|
||||||
{{#each domains}}<option value="{{this.value}}" {{#if selected}}selected{{/if}}>{{this.label}}</option>{{/each}}
|
|
||||||
</select>
|
|
||||||
<span class="help-block help-block--link"><a href='#/domains'>{{t 'manage_domains'}}</a></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label for="path" class="col-sm-12">{{t 'path_url'}}</label>
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<input class="col-sm-12" type="text" id="path" name="path" class="form-control" value="{{path}}" required="required">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<input type="hidden" name="app" value="{{id}}">
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" class="btn btn-success slide back" value="{{t 'app_change_url'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
|
@ -1,85 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps/{{app}}">{{app_name}}</a>
|
|
||||||
<a href="#/apps/{{app}}/config-panel">{{t 'config-panel'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="alert alert-warning" role="alert">{{t 'experimental_warning'}}</div>
|
|
||||||
|
|
||||||
|
|
||||||
{{#if config_panel}}
|
|
||||||
|
|
||||||
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
|
|
||||||
{{#config_panel}}
|
|
||||||
<h2>{{../app_name}} configuration panel</h2>
|
|
||||||
<hr>
|
|
||||||
<form class="form-horizontal" action="#/apps/{{../app}}/config" method="POST">
|
|
||||||
{{#panel}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="heading-{{ @index }}">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapse-{{ @index }}" aria-expanded="true" aria-controls="collapse-{{ @index }}">
|
|
||||||
{{name}}
|
|
||||||
<small>{{help}}</small>
|
|
||||||
</a>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div id="collapse-{{ @index }}" class="panel-collapse collapse {{#if (eq @index 0)}}in{{/if}}" role="tabpanel" aria-labelledby="heading-{{ @index }}">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#sections}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading"><h3 class="panel-title">{{name}} <small>{{help}}</small></h3></div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#options}}
|
|
||||||
<div class="form-group input-{{inputType}}">
|
|
||||||
<label for="{{name}}" class="col-sm-2 control-label">{{label}}</label>
|
|
||||||
|
|
||||||
<div class="col-sm-10">
|
|
||||||
|
|
||||||
{{#if choices}}
|
|
||||||
<select id="{{name}}" name="{{name}}" required class="form-control" {{{attributes}}}>
|
|
||||||
{{#choices}}<option value="{{value}}" {{#if selected}}selected{{/if}}>{{label}}</option>{{/choices}}
|
|
||||||
</select>
|
|
||||||
{{else if (eq inputType "checkbox")}}
|
|
||||||
<input type="{{inputType}}" id="{{name}}" name="{{name}}" class="form-control auto-width" value="{{default}}" placeholder="{{example}}" {{required}} {{{attributes}}}>
|
|
||||||
{{else}}
|
|
||||||
<input type="{{inputType}}" id="{{name}}" name="{{name}}" class="form-control" value="{{default}}" placeholder="{{example}}" {{required}} {{{attributes}}}>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if helpLink}}
|
|
||||||
<span class="help-block help-block--link">{{{helpLink}}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if example}}
|
|
||||||
<span class="help-block help-block--example">{{t 'form_input_example' example}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if helpText}}
|
|
||||||
<span class="help-block">{{{helpText}}}</span>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/options}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/sections}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/panel}}
|
|
||||||
<br>
|
|
||||||
<div><button class="btn btn-primary" type="submit">Save</button></div>
|
|
||||||
</form>
|
|
||||||
{{/config_panel}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{else}}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<span class="fa-exclamation-triangle"></span>
|
|
||||||
{{t 'app_config_panel_no_panel'}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
|
@ -1,88 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps/{{settings.id}}">{{settings.label}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-info-circle"></span> {{t 'infos'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{{t 'id'}}</dt>
|
|
||||||
<dd>{{settings.id}}</dd>
|
|
||||||
<dt>{{t 'label'}}</dt>
|
|
||||||
<dd>{{label}}</dd>
|
|
||||||
<dt>{{t 'description'}}</dt>
|
|
||||||
<dd>{{description}}</dd>
|
|
||||||
<dt>{{t 'version'}}</dt>
|
|
||||||
<dd>{{version}}</dd>
|
|
||||||
<dt>{{t 'multi_instance'}}</dt>
|
|
||||||
<dd>{{supports_multi_instance}}</dd>
|
|
||||||
<dt>{{t 'install_time'}}</dt>
|
|
||||||
<dd>{{formatTime install_time day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}</dd>
|
|
||||||
{{#if settings.domain}}
|
|
||||||
<dt>{{t 'url'}}</dt>
|
|
||||||
<dd><a href="https://{{settings.domain}}{{settings.path}}" target="_blank">https://{{settings.domain}}{{settings.path}}</a></dd>
|
|
||||||
{{/if}}
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-wrench"></span> {{t 'operations'}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'app_info_managelabel_desc' settings.label}}</p>
|
|
||||||
<a role="button" href="#/apps/{{settings.id}}/changelabel" class="btn btn-info slide">
|
|
||||||
<span class="fa-tag"></span> {{t 'app_manage_label_and_tiles'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'app_info_access_desc'}} {{#each permissions}} {{ucwords .}}{{#unless @last}}, {{/unless}} {{ else }} {{t 'nobody'}} {{/each}}
|
|
||||||
</p>
|
|
||||||
<a role="button" href="#/groups" class="btn btn-info slide">
|
|
||||||
<span class="fa-key-modern"></span> {{t 'groups_and_permissions_manage'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'app_info_default_desc' settings.domain}}</p>
|
|
||||||
<button class="btn btn-success" data-action="set-as-default" data-app="{{settings.id}}">
|
|
||||||
<span class="fa-star"></span> {{t 'app_make_default'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'app_info_changeurl_desc' settings.domain}}</p>
|
|
||||||
{{#if supports_change_url}}
|
|
||||||
<a href="#/apps/{{settings.id}}/changeurl" role="button" class="btn btn-info slide">
|
|
||||||
<span class="fa-exchange"></span> {{t 'app_change_url'}}
|
|
||||||
</a>
|
|
||||||
{{else}}
|
|
||||||
{{#tooltip (t 'app_info_change_url_disabled_tooltip') }}
|
|
||||||
<a href="#/apps/{{settings.id}}/changeurl" class="btn btn-info slide disabled">
|
|
||||||
<span class="fa-exchange"></span> {{t 'app_change_url'}}
|
|
||||||
</a>
|
|
||||||
{{/tooltip}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'app_info_uninstall_desc'}}</p>
|
|
||||||
<button class="btn btn-danger slide back" data-action="uninstall" data-app="{{settings.id}}">
|
|
||||||
<span class="fa-trash-o"></span> {{t 'uninstall'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{load_tooltips}}
|
|
|
@ -1,95 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps">{{t 'applications'}}</a>
|
|
||||||
<a href="#/apps/catalog">{{t 'catalog'}}</a>
|
|
||||||
<a href="#/apps/install/{{id}}">{{t 'install_name' manifest.name}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/apps" method="POST" class="form-horizontal form-app-install">
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-info-circle"></span> {{t 'infos'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{{t 'id'}}</dt>
|
|
||||||
<dd>{{manifest.id}}</dd>
|
|
||||||
<dt>{{t 'description'}}</dt>
|
|
||||||
<dd>{{description}}</dd>
|
|
||||||
{{#displayLicense}}
|
|
||||||
<dt>{{t 'license'}}</dt>
|
|
||||||
<dd>{{manifest.license}}</dd>
|
|
||||||
{{/displayLicense}}
|
|
||||||
<dt>{{t 'version'}}</dt>
|
|
||||||
<dd>{{manifest.version}}</dd>
|
|
||||||
<dt>{{t 'multi_instance'}}</dt>
|
|
||||||
<dd>{{manifest.multi_instance}}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-download"></span> {{t 'install'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="label" class="col-sm-12">{{t 'label_for_manifestname' manifest.name}}</label>
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<input type="text" id="label" name="label" class="form-control" value="{{manifest.name}}" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{#manifest.arguments.install}}
|
|
||||||
<div class="form-group">
|
|
||||||
{{#if isDisplayText}}
|
|
||||||
<div class="col-sm-12">
|
|
||||||
{{#label}}
|
|
||||||
<p>{{.}}</p>
|
|
||||||
{{/label}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<label for="{{name}}" class="col-sm-12">{{label}}</label>
|
|
||||||
|
|
||||||
|
|
||||||
{{#if helpText}}
|
|
||||||
<span class="help-block help-block--help col-sm-12">{{{helpText}}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="col-sm-12">
|
|
||||||
{{#if isPassword}}
|
|
||||||
<p class="text-warning">{{t 'good_practices_about_admin_password'}}</p>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if choices}}
|
|
||||||
<select id="{{name}}" name="{{name}}" required class="form-control" {{{attributes}}}>
|
|
||||||
{{#choices}}<option value="{{value}}" {{#if selected}}selected{{/if}}>{{label}}</option>{{/choices}}
|
|
||||||
</select>
|
|
||||||
{{else}}
|
|
||||||
<input type="{{inputType}}" id="{{name}}" name="{{name}}" class="form-control" value="{{default}}" placeholder="{{example}}" {{required}} {{{attributes}}}>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if helpLink}}
|
|
||||||
<span class="help-block help-block--link">{{{helpLink}}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if example}}
|
|
||||||
<span class="help-block help-block--example">{{t 'form_input_example' example}}</span>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/manifest.arguments.install}}
|
|
||||||
<hr>
|
|
||||||
<input type="hidden" name="app" value="{{id}}">
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'install'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
|
@ -1,32 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/apps">{{t 'applications'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions-group">
|
|
||||||
<a role="button" href="#/apps/catalog" class="btn btn-success slide">
|
|
||||||
<span class="fa-plus"></span> {{t 'install'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="list-group">
|
|
||||||
{{#apps}}
|
|
||||||
<a href="#/apps/{{id}}" class="list-group-item slide" title="{{t 'infos'}}">
|
|
||||||
<span class="fa-chevron-right pull-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">
|
|
||||||
{{label}} <small>{{name}}</small>
|
|
||||||
</h2>
|
|
||||||
<p class="list-group-item-text">{{description}}</p>
|
|
||||||
</a>
|
|
||||||
{{/apps}}
|
|
||||||
|
|
||||||
{{^apps}}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<span class="fa-exclamation-triangle"></span>
|
|
||||||
{{t 'no_installed_apps'}}
|
|
||||||
</div>
|
|
||||||
{{/apps}}
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/backup">{{t 'backup'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="list-group">
|
|
||||||
{{#each storages}}
|
|
||||||
<a href="#/backup/{{id}}" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{name}} <small>{{id}}</small></h2>
|
|
||||||
<p class="list-group-item-text">{{uri}}</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
</div>
|
|
|
@ -1,79 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/backup">{{t 'backup'}}</a>
|
|
||||||
<a href="#/backup/{{storage.id}}">{{t storage.name}}</a>
|
|
||||||
<a href="#/backup/{{storage.id}}/create">{{t 'backup_create'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/backup/{{storage.id}}" method="POST" class="form-horizontal">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-cube"></span>
|
|
||||||
{{t 'system'}}
|
|
||||||
<span class="select_all-none pull-right">
|
|
||||||
<input type="button" role="select_all" class="btn btn-default slide back select_all" value="{{t 'select_all'}}" />
|
|
||||||
<input type="button" role="select_none" class="btn btn-default slide back select_none" value="{{t 'select_none'}}" />
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="list-group">
|
|
||||||
{{#each hooks}}
|
|
||||||
<div class="list-group-item">
|
|
||||||
<input type="checkbox" id="{{@key}}" name="system_parts" value="{{value}}" checked class="nice-checkbox">
|
|
||||||
<label for="{{@key}}" class="pull-right"><span class="sr-only">{{t 'check'}}</span></label>
|
|
||||||
<h2 class="list-group-item-heading">{{name}}</h2>
|
|
||||||
<p class="list-group-item-text">{{description}}</p>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-cubes"></span>
|
|
||||||
{{t 'applications'}}
|
|
||||||
<span class="select_all-none pull-right">
|
|
||||||
<input type="button" role="select_all" class="btn btn-default slide back select_all" value="{{t 'select_all'}}" />
|
|
||||||
<input type="button" role="select_none" class="btn btn-default slide back select_none" value="{{t 'select_none'}}" />
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="list-group">
|
|
||||||
{{#each apps}}
|
|
||||||
<div class="list-group-item">
|
|
||||||
<input type="checkbox" id="{{id}}" name="apps" value="{{id}}" checked class="nice-checkbox">
|
|
||||||
<label for="{{id}}" class="pull-right"><span class="sr-only">{{t 'check'}}</span></label>
|
|
||||||
<h2 class="list-group-item-heading">{{label}} <small>{{id}}</small></h2>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<!--<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-lock"></span> {{t 'backup_optional_encryption'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="form-group has-feedback">
|
|
||||||
<label for="label" class="col-sm-12">{{t 'password'}}</label>
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<input type="password" id="password" name="password" class="form-control" placeholder="{{t 'backup_optional_password'}}">
|
|
||||||
<p class="text-warning">
|
|
||||||
{{t 'backup_encryption_warning'}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="separator"></div>-->
|
|
||||||
<span class="pull-right">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'backup_action'}}" />
|
|
||||||
</span>
|
|
||||||
</form>
|
|
|
@ -1,79 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/backup">{{t 'backup'}}</a>
|
|
||||||
<a href="#/backup/{{storage.id}}">{{storage.name}}</a>
|
|
||||||
<a href="#/backup/{{storage.id}}/{{name}}">{{name}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title" style="display: inline-block;"><span class="fa-fw fa-info-circle"></span> {{t 'infos'}}</h2>
|
|
||||||
<button class="btn btn-sm btn-success pull-right" data-action="download" data-storage="{{storage.id}}" data-archive="{{name}}">
|
|
||||||
<span class="fa-download"></span> {{t 'download'}}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm btn-danger slide pull-right" data-action="delete" data-storage="{{storage.id}}" data-archive="{{name}}">
|
|
||||||
<span class="fa-trash-o"></span> {{t 'delete'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{{t 'id'}}</dt><dd>{{ name }}</dd>
|
|
||||||
<dt>{{t 'created_at'}}</dt><dd>{{formatTime created_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}</dd>
|
|
||||||
<dt>{{t 'size'}}</dt><dd>{{ humanSize size}}</dd>
|
|
||||||
<dt>{{t 'path'}}</dt><dd>{{ path }}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<form action="#/backup/{{storage.id}}/{{name}}/restore" method="POST" class="form-horizontal">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title" style="display: inline-block;">
|
|
||||||
<span class="fa-fw fa-archive"></span>
|
|
||||||
{{t 'backup_content'}}
|
|
||||||
</h2>
|
|
||||||
<button type="button" data-action="select_all" class="btn btn-sm btn-default pull-right">{{t 'select_all'}}</button>
|
|
||||||
<button type="button" data-action="select_none" class="btn btn-sm btn-default pull-right">{{t 'select_none'}}</button>
|
|
||||||
</div>
|
|
||||||
{{#if items}}
|
|
||||||
<div class="list-group">
|
|
||||||
{{#each system_parts}}
|
|
||||||
<div class="list-group-item">
|
|
||||||
<input type="checkbox" id="{{@key}}" name="system_parts" value="{{value}}" checked class="nice-checkbox">
|
|
||||||
<label for="{{@key}}" class="pull-right"><span class="sr-only">{{t 'check'}}</span></label>
|
|
||||||
{{#if size}}
|
|
||||||
<h2 class="list-group-item-heading">{{name}} <small>({{ humanSize size }})</small></h2>
|
|
||||||
{{else}}
|
|
||||||
<h2 class="list-group-item-heading">{{name}}</h2>
|
|
||||||
{{/if}}
|
|
||||||
<p class="list-group-item-text">{{description}}</p>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
{{#each apps}}
|
|
||||||
<div class="list-group-item">
|
|
||||||
<input type="checkbox" id="{{@key}}" name="apps" value="{{@key}}" checked class="nice-checkbox">
|
|
||||||
<label for="{{@key}}" class="pull-right"><span class="sr-only">{{t 'check'}}</span></label>
|
|
||||||
<h2 class="list-group-item-heading">{{name}} <small>{{@key}} ({{ humanSize size }})</small></h2>
|
|
||||||
<p class="list-group-item-text">{{description}}</p>
|
|
||||||
<p class="list-group-item-text">{{t 'version'}} {{version}}</p>
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
<div class="list-group-item clearfix">
|
|
||||||
<span class="pull-right ">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'restore'}}" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<span class="fa-exclamation-triangle"></span>
|
|
||||||
{{t 'archive_empty'}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
|
@ -1,53 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/backup">{{t 'backup'}}</a>
|
|
||||||
<a href="#/backup/{{storage.id}}">{{storage.name}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions-group">
|
|
||||||
<a role="button" href="#/backup/{{storage.id}}/create" class="btn btn-success slide">
|
|
||||||
<span class="fa-plus"></span> {{t 'backup_new'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
{{#intl locales=locale}}
|
|
||||||
<div class="list-group">
|
|
||||||
{{#each archives}}
|
|
||||||
<a href="#/backup/{{../storage.id}}/{{name}}" class="list-group-item slide clearfix" title='{{formatTime created_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}'>
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{formatRelative created_at}} <small>{{name}} ({{humanSize size}})</small></h2>
|
|
||||||
<p class="list-group-item-text">{{path}}</p>
|
|
||||||
</a>
|
|
||||||
{{/each}}
|
|
||||||
{{^archives}}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<span class="fa-exclamation-triangle"></span>
|
|
||||||
{{t 'backups_no'}}
|
|
||||||
</div>
|
|
||||||
{{/archives}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{{/intl}}
|
|
||||||
|
|
||||||
<!--<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-download"></span> {{t 'upload_archive'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<form action="#/backup/{{storage.id}}/upload" method="POST" class="form-horizontal">
|
|
||||||
<div class="form-group has-feedback">
|
|
||||||
<label for="label" class="col-sm-12">{{t 'url'}}</label>
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<input type="file" id="url" name="url" value="" required pattern="^.*\.tar\.gz$">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" class="btn btn-success slide" value="{{t 'upload'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>-->
|
|
|
@ -1,67 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/diagnosis">{{t 'diagnosis'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions-group">
|
|
||||||
<button class="btn btn-success" data-action="share">
|
|
||||||
<span class="fa-cloud-upload"></span> {{t 'logs_share_with_yunopaste'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="alert alert-info text-center">
|
|
||||||
{{#if reports}}
|
|
||||||
<p>{{t 'diagnosis_explanation'}}</p>
|
|
||||||
{{else}}
|
|
||||||
<p>{{t 'diagnosis_first_run'}}</p>
|
|
||||||
<br>
|
|
||||||
<button class="btn btn-info" data-action="run-full-diagnosis"><span class="fa-fw fa-stethoscope"></span> {{t 'run_first_diagnosis'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-warning text-center">{{t 'diagnosis_experimental_disclaimer'}}</div>
|
|
||||||
|
|
||||||
{{#reports}}
|
|
||||||
<div class="panel panel-default panel-diagnosis" data-category="{{id}}">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title" style="display: inline-block; margin-right: 10px;">
|
|
||||||
<a data-toggle="collapse" href="#diagnosis-body-{{id}}">{{ description }}</a>
|
|
||||||
</h2>
|
|
||||||
{{#if noIssues}}{{#if items}}<span class="label label-success">{{t 'everything_good'}}</span>{{/if}}{{/if}}
|
|
||||||
{{#if errors}}<span class="label label-danger">{{t 'issues' errors }}</span>{{/if}}
|
|
||||||
{{#if warnings}}<span class="label label-warning">{{t 'warnings' warnings }}</span>{{/if}}
|
|
||||||
{{#if ignored}}<span class="label label-default">{{t 'ignored' ignored }}</span>{{/if}}
|
|
||||||
<button class="btn btn-sm {{#if items}}btn-info{{else}}btn-success{{/if}} pull-right" data-action="rerun-diagnosis" data-category="{{ id }}"><span class="fa-fw fa-refresh"></span> {{t 'rerun_diagnosis'}}</button>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body collapse {{#if errors}}in{{/if}}" id="diagnosis-body-{{id}}">
|
|
||||||
<ul class="list-group" style="margin-bottom: 0px">
|
|
||||||
<p>{{t 'last_ran' }} {{formatRelative time day="numeric" month="long" year="numeric" hour="numeric" minute="numeric" }}</p>
|
|
||||||
{{#items}}
|
|
||||||
<li class="list-group-item alert alert-{{status}} alert-{{status}}-yo clearfix diagnosis-item">
|
|
||||||
{{#if icon}}
|
|
||||||
<span class="fa-fw fa-{{icon}}"></span>
|
|
||||||
{{/if}}
|
|
||||||
{{{summary}}}
|
|
||||||
{{#if ignored}}
|
|
||||||
<button class="btn btn-sm btn-default pull-right" data-action="unignore" data-filter-args="{{ filter_args }}"><span class="fa-fw fa-bell"></span> {{t 'unignore'}}</button>
|
|
||||||
{{else}}
|
|
||||||
{{#if issue}}
|
|
||||||
<button class="btn btn-sm btn-warning pull-right" data-action="ignore" data-filter-args="{{ filter_args }}"><span class="fa-fw fa-bell-slash"></span> {{t 'ignore'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if details}}
|
|
||||||
<a role="button" class="btn btn-sm btn-default pull-right" data-toggle="collapse" href="#details-{{../id}}-{{@index}}" aria-expanded="false" aria-controls="details-{{../id}}-{{@index}}"><span class="fa-fw fa-level-down"></span>{{t 'details'}}</a>
|
|
||||||
<div class="collapse diagnosis-details" id="details-{{../id}}-{{@index}}">
|
|
||||||
<ul>
|
|
||||||
{{#details}}<li>{{{.}}}</li>{{/details}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</li>
|
|
||||||
{{/items}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/reports}}
|
|
|
@ -1,72 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/domains">{{t 'domains'}}</a>
|
|
||||||
<a href="#/domains/add">{{t 'domain_add'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/domains/add" method="POST" class="form-horizontal">
|
|
||||||
|
|
||||||
<div class="panel-group" id="accordion">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
|
|
||||||
{{t 'domain_add_panel_with_domain'}}
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="collapseOne" class="panel-collapse {{#if allowDyndnsDomain }}collapse{{/if}}">
|
|
||||||
<div class="panel-body">
|
|
||||||
<small>{{t 'domain_add_dns_doc'}}</small>
|
|
||||||
<hr>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="domain" class="col-sm-3 control-label">{{t 'domain_name'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="domain" name="domain" class="form-control" placeholder="{{t 'myserver_org'}}" pattern="^[a-z0-9-.]+.[a-z]$">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{#if allowDyndnsDomain }}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">
|
|
||||||
{{t 'domain_add_panel_without_domain'}}
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="collapseTwo" class="panel-collapse collapse">
|
|
||||||
<div class="panel-body">
|
|
||||||
<small>{{t 'domain_add_dyndns_doc'}}</small>
|
|
||||||
<hr>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="ddomain" class="col-sm-3 control-label">{{t 'domain_name'}}</label>
|
|
||||||
<div class="clearfix visible-xs"></div>
|
|
||||||
<div class="col-sm-4 col-xs-6">
|
|
||||||
<input type="text" id="ddomain" name="ddomain" class="form-control" placeholder="{{t 'myserver'}}" pattern="^[a-z0-9-]+$">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-xs-6">
|
|
||||||
<select class="form-control" name="ddomain-ext">
|
|
||||||
{{#ddomains}}
|
|
||||||
<option>{{.}}</option>
|
|
||||||
{{/ddomains}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="br"></div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'add'}}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
|
@ -1,84 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/domains">{{t 'domains'}}</a>
|
|
||||||
<a href="#/domains/{{name}}">{{name}}</a>
|
|
||||||
<a href="#/domains/{{name}}/cert-management">{{t 'certificate'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-lock"></span> {{t 'certificate_status'}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
|
|
||||||
<div class="alert alert-{{status.alert_type}}">
|
|
||||||
<span class="fa-fw fa-{{status.alert_icon}}"></span> {{status.alert_message}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>{{t 'certificate_authority'}}</dt>
|
|
||||||
<dd>{{status.CA_type}} ({{status.CA_name}})</dd>
|
|
||||||
<dt>{{t 'validity'}}</dt>
|
|
||||||
<dd>{{status.validity}} days</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-wrench"></span> {{t 'operations'}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
|
|
||||||
{{#if actions_enabled.install_letsencrypt}}
|
|
||||||
<div class="container">
|
|
||||||
{{#if status.ACME_eligible}}
|
|
||||||
<p><span class="fa-fw fa-check"></span>
|
|
||||||
{{t 'domain_is_eligible_for_ACME'}}</p>
|
|
||||||
{{else}}
|
|
||||||
<p><span class="fa-fw fa-meh-o"></span>
|
|
||||||
{{t 'domain_not_eligible_for_ACME'}}</p>
|
|
||||||
{{/if}}
|
|
||||||
<button class="btn btn-success {{#unless status.ACME_eligible}}disabled{{/unless}}" data-domain="{{name}}" data-action="install-LE" >
|
|
||||||
<span class="fa-star"></span> {{t 'install_letsencrypt_cert'}}
|
|
||||||
</button>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{#if actions_enabled.manual_renew_letsencrpt}}
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'manually_renew_letsencrypt_message'}}</p>
|
|
||||||
<button class="btn btn-warning" data-domain="{{name}}" data-action="renew-letsencrypt">
|
|
||||||
<span class="fa-refresh"></span> {{t 'manually_renew_letsencrypt'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if actions_enabled.regen_selfsigned}}
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'regenerate_selfsigned_cert_message'}}</p>
|
|
||||||
<button class="btn btn-warning" data-domain="{{name}}" data-action="regen-selfsigned">
|
|
||||||
<span class="fa-refresh"></span> {{t 'regenerate_selfsigned_cert'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
{{/if}}
|
|
||||||
{{#if actions_enabled.replace_with_selfsigned}}
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'revert_to_selfsigned_cert_message'}}</p>
|
|
||||||
<button class="btn btn-danger" data-domain="{{name}}" data-action="replace-with-selfsigned">
|
|
||||||
<span class="fa-exclamation-triangle"></span> {{t 'revert_to_selfsigned_cert'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/domains">{{t 'domains'}}</a>
|
|
||||||
<a href="#/domains/{{name}}">{{name}}</a>
|
|
||||||
<a href="#/domains/{{name}}/dns">{{t 'dns'}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<span class="fa-fw fa-warning"></span> {{t 'domain_dns_conf_is_just_a_recommendation' }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-globe"></span> {{t 'domain_dns_config'}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<pre>{{dns}}</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,57 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/domains">{{t 'domains'}}</a>
|
|
||||||
<a href="#/domains/{{name}}">{{name}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-globe"></span> {{name}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'domain_visit_url' url}}</p>
|
|
||||||
<a role="button" href="{{url}}" class="btn btn-success" target="_blank">
|
|
||||||
<span class="fa-fw fa-external-link"></span> {{t 'domain_visit'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'domain_default_desc'}}</p>
|
|
||||||
{{#if main}}
|
|
||||||
<p class="alert alert-info">
|
|
||||||
<span class="fa-star" title="{{t 'default'}}"></span> {{t 'domain_default_longdesc'}}
|
|
||||||
</p>
|
|
||||||
{{else}}
|
|
||||||
<button class="btn btn-primary" data-action="set_default" data-domain="{{name}}">
|
|
||||||
<span class="fa-fw fa-star"></span> {{t 'set_default'}}
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'domain_dns_longdesc'}}</p>
|
|
||||||
<a role="button" href="#/domains/{{name}}/dns" class="btn btn-default slide">
|
|
||||||
<span class="fa-fw fa-globe"></span> {{t 'domain_dns_config'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'certificate_manage'}}</p>
|
|
||||||
<a href="#/domains/{{name}}/cert-management" role="button" class="btn btn-default slide">
|
|
||||||
<span class="fa-fw fa-lock"></span> {{t 'ssl_certificate'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="container">
|
|
||||||
<p>{{t 'domain_delete_longdesc' name}}</p>
|
|
||||||
<button class="btn btn-danger" data-action="delete" data-domain="{{name}}">
|
|
||||||
<span class="fa-fw fa-trash-o"></span> {{t 'delete'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,25 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/domains">{{t 'domains'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions-group">
|
|
||||||
<a role="button" href="#/domains/add" class="btn btn-success slide">
|
|
||||||
<span class="fa-plus"></span> {{t 'domain_add'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="list-group">
|
|
||||||
{{#each domains}}
|
|
||||||
<a href="#/domains/{{url}}" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">
|
|
||||||
{{url}}
|
|
||||||
{{#if main}}<small><span class="fa-star" title="{{t 'default'}}"></span></small>{{/if}}
|
|
||||||
</h2>
|
|
||||||
<p class="list-group-item-text">https://{{url}}</p>
|
|
||||||
</a>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
|
@ -1,34 +0,0 @@
|
||||||
<div class="list-group">
|
|
||||||
<a href="#/users" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-users"></span> {{t 'users'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/domains" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-globe"></span> {{t 'domains'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/apps" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-cubes"></span> {{t 'applications'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/update" class="list-group-item slide">
|
|
||||||
<span class="fa-chevron-right pull-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-refresh"></span> {{t 'system_update'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/services" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-cog"></span> {{t 'services'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/tools" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-wrench"></span> {{t 'tools'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/diagnosis" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-stethoscope"></span> {{t 'diagnosis'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/backup" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading"><span class="fa-fw fa-archive"></span> {{t 'backup'}}</h2>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<form action="#/login" id="form" method="post">
|
|
||||||
<div class="input-group {{#domain}} hidden {{/domain}}">
|
|
||||||
<label for="domain" class="input-group-addon">
|
|
||||||
<span class="fa-fw fa-cloud"></span>
|
|
||||||
<span class="sr-only">{{t 'domain'}}</span>
|
|
||||||
</label>
|
|
||||||
<input type="{{#domain}}hidden{{else}}text{{/domain}}" id="domain" name="domain" class="form-control" placeholder="{{t 'myserver_org'}}" value="{{domain}}" />
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="password" class="input-group-addon">
|
|
||||||
<span class="fa-fw fa-lock"></span>
|
|
||||||
<span class="sr-only">{{t 'password'}}</span>
|
|
||||||
</label>
|
|
||||||
<input type="password" id="password" name="password" class="form-control" placeholder="{{t 'administration_password'}}" />
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<input id="submit" type="submit" value="{{t 'login'}}" class="btn btn-success pull-right" />
|
|
||||||
</form>
|
|
|
@ -1,15 +0,0 @@
|
||||||
|
|
||||||
<h2>{{t 'postinstall_intro_1'}}</h2>
|
|
||||||
<p class="lead">
|
|
||||||
{{t 'postinstall_intro_2'}}
|
|
||||||
<br />
|
|
||||||
{{t 'postinstall_intro_3'}}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="pull-left">
|
|
||||||
<a href="#/postinstall/domain" class="btn btn-lg btn-primary slide">
|
|
||||||
{{t 'begin'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
|
@ -1,81 +0,0 @@
|
||||||
<h2>{{t 'domain'}}</h2>
|
|
||||||
<p class="lead">
|
|
||||||
{{t 'postinstall_domain'}}
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
<div class="panel-group" id="accordion">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
|
|
||||||
{{t 'domain_add_panel_with_domain'}}
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="collapseOne" class="panel-collapse collapse">
|
|
||||||
<div class="panel-body">
|
|
||||||
<small>{{t 'domain_add_dns_doc'}}</small>
|
|
||||||
<div class="br"></div>
|
|
||||||
<div class="form-group">
|
|
||||||
<strong class="col-sm-3 control-label">{{t 'domain_name'}}</strong>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="domain" class="input-group-addon">
|
|
||||||
<span class="fa-fw fa-cloud"></span><span class="sr-only">{{t 'domain'}}</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" id="domain" name="domain" class="form-control" placeholder="{{t 'myserver_org'}}" pattern="^[a-z0-9-.]+.[a-z]$">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h4 class="panel-title">
|
|
||||||
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo">
|
|
||||||
{{t 'domain_add_panel_without_domain'}}
|
|
||||||
</a>
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div id="collapseTwo" class="panel-collapse collapse">
|
|
||||||
<div class="panel-body">
|
|
||||||
<small>{{t 'domain_add_dyndns_doc'}}</small>
|
|
||||||
<div class="br"></div>
|
|
||||||
<div class="form-group">
|
|
||||||
<strong class="col-sm-3 control-label">{{t 'domain_name'}}</strong>
|
|
||||||
<div class="clearfix visible-xs"></div>
|
|
||||||
<div class="col-sm-4 col-xs-6">
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="ddomain" class="input-group-addon">
|
|
||||||
<span class="fa-fw fa-cloud"></span><span class="sr-only">{{t 'domain'}}</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" id="ddomain" name="ddomain" class="form-control" placeholder="{{t 'myserver'}}" pattern="^[a-z0-9-]+$">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-xs-6">
|
|
||||||
<select class="form-control" name="ddomain-ext">
|
|
||||||
{{#ddomains}}
|
|
||||||
<option>{{.}}</option>
|
|
||||||
{{/ddomains}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div class="pull-left">
|
|
||||||
<a href="#/postinstall" class="btn btn-default btn-lg slide back">
|
|
||||||
<span class="fa-chevron-left"></span> {{t 'previous'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pull-right">
|
|
||||||
<a href="#/postinstall/password" class="btn btn-default btn-lg slide savedomain">
|
|
||||||
{{t 'next'}} <span class="fa-chevron-right"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
|
@ -1,41 +0,0 @@
|
||||||
<form action="#/postinstall" method="POST" class="form-horizontal">
|
|
||||||
|
|
||||||
<h2>{{t 'password'}}</h2>
|
|
||||||
<p class="lead">
|
|
||||||
{{t 'postinstall_password'}}
|
|
||||||
</p>
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
|
|
||||||
<div class="alert alert-warning">{{t 'good_practices_about_admin_password'}}</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password" class="col-sm-4 control-label">{{t 'administration_password'}}</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input required type="password" id="password" name="password" class="form-control" minlength="8" maxlength="127" placeholder="••••••">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="confirmation" class="col-sm-4 control-label">{{t 'password_confirmation'}}</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<input required type="password" id="confirmation" name="confirmation" class="form-control" minlength="8" maxlength="127" placeholder="••••••">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input name="domain" type="hidden" value="{{ domain }}">
|
|
||||||
|
|
||||||
<div class="pull-left">
|
|
||||||
<a href="#/postinstall/domain" class="btn btn-default btn-lg slide back">
|
|
||||||
<span class="fa-chevron-left"></span> {{t 'previous'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pull-right">
|
|
||||||
<input type="submit" class="btn btn-default btn-lg btn-primary" value="{{t 'Go !'}}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
|
@ -1,78 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/services">{{t 'services'}}</a>
|
|
||||||
<a href="#/services/{{service.name}}">{{name}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title" style="display: inline-block; margin-right: 10px;"><span class="fa-fw fa-info-circle"></span> {{name}}</h2>
|
|
||||||
{{#if (eq status "running")}}
|
|
||||||
<button class="btn btn-sm btn-danger pull-right" data-service="{{name}}" data-action="stop">
|
|
||||||
<span class="fa-fw fa-warning"></span> {{t 'stop'}}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm btn-warning pull-right" data-service="{{name}}" data-action="restart">
|
|
||||||
<span class="fa-fw fa-refresh"></span> {{t 'restart'}}
|
|
||||||
</button>
|
|
||||||
{{else}}
|
|
||||||
<button class="btn btn-sm btn-success pull-right" data-service="{{name}}" data-action="start">
|
|
||||||
<span class="fa-fw fa-play"></span> {{t 'start'}}
|
|
||||||
</button>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
|
|
||||||
<dt>{{t 'description'}}</dt>
|
|
||||||
<dd>{{description}}</dd>
|
|
||||||
|
|
||||||
<dt>{{t 'status'}}</dt>
|
|
||||||
<dd>
|
|
||||||
{{#if (eq status "running")}}
|
|
||||||
<span class="text-success">
|
|
||||||
<span class="fa-fw fa-check-circle"></span>
|
|
||||||
{{else}}
|
|
||||||
<span class="text-danger">
|
|
||||||
<span class="fa-fw fa-times"></span>
|
|
||||||
{{/if}}
|
|
||||||
{{t status}} </span> {{t 'since'}} {{formatRelative last_state_change day="numeric" month="long" year="numeric" hour="numeric" minute="numeric" }}
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dt>{{t 'service_start_on_boot'}}</dt>
|
|
||||||
{{#if (eq start_on_boot "enabled")}}
|
|
||||||
<dd class="text-success">
|
|
||||||
{{else}}
|
|
||||||
<dd class="text-danger">
|
|
||||||
{{/if}}
|
|
||||||
{{t start_on_boot}}
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dt>{{t 'configuration'}}</dt>
|
|
||||||
{{#if (eq configuration "valid")}}
|
|
||||||
<dd class="text-success">
|
|
||||||
{{else if (eq configuration "broken")}}
|
|
||||||
<dd class="text-danger">
|
|
||||||
{{else}}
|
|
||||||
<dd>
|
|
||||||
{{/if}}
|
|
||||||
{{t configuration}}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title" style="display: inline-block; margin-right: 10px;"><span class="fa-fw fa-book"></span> {{t 'logs'}}</h2>
|
|
||||||
<button class="btn btn-sm btn-success pull-right" data-action="share"><span class="fa-cloud-upload"></span> {{t 'logs_share_with_yunopaste'}}</button>
|
|
||||||
</div>
|
|
||||||
<div id="logs" class="panel-body">
|
|
||||||
{{#logs}}
|
|
||||||
<h2>{{filename}}</h2>
|
|
||||||
<pre class="service-log">{{filecontent}}</pre>
|
|
||||||
{{/logs}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,27 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/services">{{t 'services'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="list-group">
|
|
||||||
{{#services}}
|
|
||||||
<a href="#/services/{{name}}" class="list-group-item slide service-{{name}}">
|
|
||||||
<span class="fa-chevron-right pull-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{name}} <small>{{description}}</small></h2>
|
|
||||||
<div class="list-group-item-text">
|
|
||||||
{{#if (eq status "running")}}
|
|
||||||
<span class="text-success">
|
|
||||||
<span class="fa-fw fa-check-circle"></span>
|
|
||||||
{{else}}
|
|
||||||
<span class="text-danger">
|
|
||||||
<span class="fa-fw fa-times"></span>
|
|
||||||
{{/if}}
|
|
||||||
{{t status}}
|
|
||||||
</span>
|
|
||||||
{{t 'since'}} {{formatRelative last_state_change day="numeric" month="long" year="numeric" hour="numeric" minute="numeric" }}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
{{/services}}
|
|
||||||
</div>
|
|
|
@ -1,41 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/tools">{{t 'tools'}}</a>
|
|
||||||
<a href="#/tools/adminpw">{{t 'tools_adminpw'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="alert alert-warning">{{t 'good_practices_about_admin_password'}}</div>
|
|
||||||
|
|
||||||
<form action="#/tools/adminpw" method="PUT" class="form-horizontal">
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="old_password" class="col-sm-3 control-label">{{t 'tools_adminpw_current'}}</label>
|
|
||||||
<div class="col-sm-5 col-xs-12">
|
|
||||||
<input type="password" id="old_password" name="old_password" class="form-control" placeholder="{{t 'tools_adminpw_current_placeholder'}} "/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="new_password" class="col-sm-3 control-label">{{t 'password_new'}}</label>
|
|
||||||
<div class="col-sm-5 col-xs-12">
|
|
||||||
<input type="password" id="new_password" name="new_password" class="form-control" placeholder="{{t 'tools_adminpw_new_placeholder'}} "/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="confirm_new_password" class="col-sm-3 control-label">{{t 'password_confirmation'}}</label>
|
|
||||||
<div class="col-sm-5 col-xs-12">
|
|
||||||
<input type="password" id="confirm_new_password" name="confirm_new_password" class="form-control" placeholder="{{t 'tools_adminpw_confirm_placeholder'}}" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<input role="button" type="submit" class="btn btn-success slide back" value="{{t 'save'}}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
|
@ -1,177 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/tools">{{t 'tools'}}</a>
|
|
||||||
<a href="#/tools/firewall">{{t 'firewall'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-shield"></span> {{t 'ports'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<h3>{{t 'tcp'}}</h3>
|
|
||||||
<table class="table table-striped table-hover table-condensed table-firewall">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{t 'port'}}</th>
|
|
||||||
<th>{{t 'ipv4'}}</th>
|
|
||||||
<th>{{t 'ipv6'}}</th>
|
|
||||||
<th>{{t 'upnp'}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{#each ports.TCP}}
|
|
||||||
<tr>
|
|
||||||
<td>{{@key}}</td>
|
|
||||||
<td>
|
|
||||||
{{#if this.ipv4}}
|
|
||||||
<span class="fa-check"></span>
|
|
||||||
<button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv4">{{t 'close'}}</button>
|
|
||||||
{{else}}
|
|
||||||
<span></span>
|
|
||||||
<span class="fa-times"></span>
|
|
||||||
<button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv4">{{t 'open'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{#if this.ipv6}}
|
|
||||||
<span class="fa-check"></span>
|
|
||||||
<button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv6">{{t 'close'}}</button>
|
|
||||||
{{else}}
|
|
||||||
<span class="fa-times"></span>
|
|
||||||
<button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv6">{{t 'open'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{#if this.uPnP}}
|
|
||||||
<span class="fa-check"></span>
|
|
||||||
{{else}}
|
|
||||||
<span class="fa-times"></span>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<h3>{{t 'udp'}}</h3>
|
|
||||||
<table class="table table-striped table-hover table-condensed table-firewall">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{t 'port'}}</th>
|
|
||||||
<th>{{t 'ipv4'}}</th>
|
|
||||||
<th>{{t 'ipv6'}}</th>
|
|
||||||
<th>{{t 'upnp'}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{#each ports.UDP}}
|
|
||||||
<tr>
|
|
||||||
<td>{{@key}}</td>
|
|
||||||
<td>
|
|
||||||
{{#if this.ipv4}}
|
|
||||||
<span class="fa-check"></span>
|
|
||||||
<button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="udp" data-connection="ipv4">{{t 'close'}}</button>
|
|
||||||
{{else}}
|
|
||||||
<span></span>
|
|
||||||
<span class="fa-times"></span>
|
|
||||||
<button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="udp" data-connection="ipv4">{{t 'open'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{#if this.ipv6}}
|
|
||||||
<span class="fa-check"></span>
|
|
||||||
<button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="udp" data-connection="ipv6">{{t 'close'}}</button>
|
|
||||||
{{else}}
|
|
||||||
<span class="fa-times"></span>
|
|
||||||
<button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="udp" data-connection="ipv6">{{t 'open'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{#if this.uPnP}}
|
|
||||||
<span class="fa-check"></span>
|
|
||||||
{{else}}
|
|
||||||
<span class="fa-times"></span>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">{{t 'udp'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body table-responsive">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-cog"></span> {{t 'operations'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<form action="#/tools/firewall/port" method="POST" class="">
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-xs-6 col-sm-3">
|
|
||||||
<label for="action" class="control-label">{{t 'action'}}</label>
|
|
||||||
<select id="action" name="action" class="form-control" required>
|
|
||||||
<option value="open">{{t 'open'}}</option>
|
|
||||||
<option value="close">{{t 'close'}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 col-sm-3">
|
|
||||||
<label for="port" class="control-label">{{t 'port'}}</label>
|
|
||||||
<input type="number" id="port" name="port" class="form-control" required min="1" max="65535">
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 col-sm-3">
|
|
||||||
<label for="connection" class="control-label">{{t 'connection'}}</label>
|
|
||||||
<select id="connection" name="connection" class="form-control" required>
|
|
||||||
<option value="ipv4">{{t 'ipv4'}}</option>
|
|
||||||
<option value="ipv6">{{t 'ipv6'}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-6 col-sm-3">
|
|
||||||
<label for="protocol" class="control-label">{{t 'protocol'}}</label>
|
|
||||||
<select id="protocol" name="protocol" class="form-control" required>
|
|
||||||
<option value="tcp">{{t 'tcp'}}</option>
|
|
||||||
<option value="udp">{{t 'udp'}}</option>
|
|
||||||
<option value="both">{{t 'both'}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-xs-6 col-sm-3">
|
|
||||||
<input role="button" type="submit" class="btn btn-success slide back" value="{{t 'save'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-exchange"></span> {{t 'upnp'}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#if upnp}}
|
|
||||||
<p class="text-success">{{t 'upnp_enabled'}}</p>
|
|
||||||
<button class="btn btn-danger" data-upnp="disable">{{t 'disable'}}</button>
|
|
||||||
{{else}}
|
|
||||||
<p class="text-danger">{{t 'upnp_disabled'}}</p>
|
|
||||||
<button class="btn btn-success" data-upnp="enable">{{t 'enable'}}</button>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,30 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/tools">{{t 'tools'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="list-group">
|
|
||||||
<a href="#/tools/logs" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{t 'logs'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/tools/migrations" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{t 'migrations'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/tools/firewall" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{t 'firewall'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/tools/adminpw" class="list-group-item slide">
|
|
||||||
<span class="fa-chevron-right pull-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{t 'tools_adminpw'}}</h2>
|
|
||||||
</a>
|
|
||||||
<a href="#/tools/reboot" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{t 'tools_shutdown_reboot'}}</h2>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/tools">{{t 'tools'}}</a>
|
|
||||||
<a href="#/tools/logs">{{t 'logs'}}</a>
|
|
||||||
{{#if log.name}}
|
|
||||||
<a href="#/tools/logs/{{ log.name }}">{{ log.name }}</a>
|
|
||||||
{{else}}
|
|
||||||
<a href="#/tools/logs/{{ log.log_path }}">{{ log.log_path }}</a>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
{{#intl locales=locale}}
|
|
||||||
{{#if log.metadata}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title" id="description" style="display: inline-block; margin-right: 10px;"><span class="fa-fw fa-info-circle"></span> {{ log.description }}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<dl class="dl-horizontal" id="metadata">
|
|
||||||
<dt>{{t 'logs_path'}}</dt> <dd>{{ log.log_path }}</dd>
|
|
||||||
{{#if log.metadata.started_at}}<dt>{{t 'logs_started_at'}}</dt> <dd>{{formatTime log.metadata.started_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}</dd>
|
|
||||||
{{/if}}{{#if log.metadata.ended_at}}<dt>{{t 'logs_ended_at'}}</dt> <dd>{{formatTime log.metadata.ended_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}</dd>{{/if}}
|
|
||||||
{{#if log.metadata.error}}<dt>{{t 'logs_error'}}</dt> <dd>{{log.metadata.error}}</dd>{{/if}}
|
|
||||||
{{#if log.metadata.suboperations}}
|
|
||||||
<dt>{{t 'log_suboperations'}}</dt>
|
|
||||||
{{#log.metadata.suboperations}}
|
|
||||||
<dd>{{#unless success }}<span class="fa-fw fa-close text-danger"></span>{{/unless}}
|
|
||||||
<a href="#/tools/logs/{{ name }}">{{ description }}</a></dd>
|
|
||||||
{{/log.metadata.suboperations}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#unless log.metadata.success}}
|
|
||||||
<div class="alert alert-danger text-center">
|
|
||||||
<p>{{t 'operation_failed_explanation'}}</p>
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{/if}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title" style="display: inline-block; margin-right: 10px;">
|
|
||||||
<span class="fa-fw fa-file-text"></span> {{#if log.metadata}}{{t 'logs'}}{{else}}{{log.log_path}}{{/if}}</h2>
|
|
||||||
<button class="btn btn-sm btn-success pull-right" data-action="share" data-log-id="{{#if log.name}}{{ log.name }}{{else}}{{ log.log_path }}{{/if}}">
|
|
||||||
<span class="fa-cloud-upload"></span> {{t 'logs_share_with_yunopaste'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body overflow-auto">
|
|
||||||
{{#if next_number}}<a href="#/tools/logs/{{#if log.name}}{{ log.name }}{{else}}{{ log.log_path }}{{/if}}?number={{ next_number }}" class="btn btn-default full-width"><span class="fa-fw fa-plus"></span> {{t 'logs_more'}}</a>{{/if}}
|
|
||||||
<!-- no indent because pre is sensible to whitespaces -->
|
|
||||||
<pre id="log" class="full-width">{{#log.logs}}{{.}}
|
|
||||||
{{/log.logs}}</pre>
|
|
||||||
<center>
|
|
||||||
<button class="btn btn-success" data-action="share" data-log-id="{{#if log.name}}{{ log.name }}{{else}}{{ log.log_path }}{{/if}}">
|
|
||||||
<span class="fa-cloud-upload"></span> {{t 'logs_share_with_yunopaste'}}
|
|
||||||
</button>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/intl}}
|
|
|
@ -1,23 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/tools">{{t 'tools'}}</a>
|
|
||||||
<a href="#/tools/logs">{{t 'logs'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
{{#intl locales=locale}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-wrench"></span>{{t 'logs_operation'}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="list-group">
|
|
||||||
{{#operations}}
|
|
||||||
<a href="#/tools/logs/{{ name }}" class="list-group-item slide" title='{{formatTime started_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}'><small style="margin-right:20px;" >{{formatRelative started_at}}</small>
|
|
||||||
<span class="fa-fw fa-{{success_icon}}"></span> {{ description }}</a>
|
|
||||||
{{/operations}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/intl}}
|
|
|
@ -1,78 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/tools">{{t 'tools'}}</a>
|
|
||||||
<a href="#/tools/migrations">{{t 'migrations'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-cogs"></span> {{t 'migrations_pending'}}
|
|
||||||
{{#if pending_migrations}}
|
|
||||||
<div class="btn-toolbar pull-right">
|
|
||||||
<button class="btn btn-sm btn-success" data-action="run"><span class="fa-fw fa-play"></span> {{t 'run'}}</button>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if pending_migrations}}
|
|
||||||
<div class="list-group">
|
|
||||||
{{#pending_migrations}}
|
|
||||||
<div class="list-group-item clearfix">
|
|
||||||
<h3 class="list-group-item-heading">
|
|
||||||
{{ number }}. {{ description }}
|
|
||||||
<div class="btn-toolbar pull-right">
|
|
||||||
<button class="btn btn-xs btn-warning" style="color:white;" data-action="skip" data-migration="{{id}}"><span class="fa-fw fa-close"></span> {{t 'skip'}}</button>
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
{{#if disclaimer }}
|
|
||||||
<hr>
|
|
||||||
<p id="disclaimer-migration-{{number}}" class="list-group-item-text">
|
|
||||||
{{{ disclaimer }}}
|
|
||||||
<div style="margin-left:20px">
|
|
||||||
<label style="" id="disclaimer-ack-migration-{{number}}" class="checkbox disclaimer-ack">
|
|
||||||
<input type="checkbox"> I read and understood this disclaimer
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{/pending_migrations}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="panel-body">
|
|
||||||
<span class="text-success"><span class="fa-fw fa-check-circle"></span>{{t 'migrations_no_pending' }}</span>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<a role="button" data-toggle="collapse" data-parent="#accordion" href="#done_migrations" aria-expanded="true" aria-controls="done_migrations">
|
|
||||||
<span class="fa-fw fa-cogs"></span> {{t 'migrations_done'}}
|
|
||||||
</a>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{#if done_migrations}}
|
|
||||||
<div id="done_migrations" class="list-group panel-collapse collapse" role="tabpanel" aria-labelledby="heading-{{key}}">
|
|
||||||
{{#done_migrations}}
|
|
||||||
<div class="list-group-item clearfix">
|
|
||||||
<h3 class="list-group-item-heading">
|
|
||||||
{{ number }}. {{ description }}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
{{/done_migrations}}
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="panel-body">
|
|
||||||
<span class="text-info">{{t 'migrations_no_done' }}</span>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/tools">{{t 'tools'}}</a>
|
|
||||||
<a href="#/tools/reboot">{{t 'tools_shutdown_reboot'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<span class="fa-fw fa-wrench"></span> {{t 'operations'}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-danger" data-action="reboot">
|
|
||||||
<i class="fa-refresh"></i> {{t 'tools_reboot_btn'}}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button class="btn btn-danger" data-action="shutdown">
|
|
||||||
<i class="fa-power-off"></i> {{t 'tools_shutdown_btn'}}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,60 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/update">{{t 'system_update'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
{{#if pending_migrations}}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
{{t 'pending_migrations' "#/tools/migrations"}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-server"></span> {{t 'system'}}</h2>
|
|
||||||
</div>
|
|
||||||
{{#if system}}
|
|
||||||
<div class="list-group">
|
|
||||||
{{#system}}
|
|
||||||
<div class="list-group-item">
|
|
||||||
<h3 class="list-group-item-heading">
|
|
||||||
{{name}} <small>({{t 'from_to' current_version new_version}}) </small>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
{{/system}}
|
|
||||||
</div>
|
|
||||||
<div class="panel-footer">
|
|
||||||
<button class="btn btn-success" data-upgrade="system">{{t 'system_upgrade_all_packages_btn'}}</button>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="panel-body">
|
|
||||||
<span class="text-success"><span class="fa-fw fa-check-circle"></span> {{t 'system_packages_nothing'}}</span>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title"><span class="fa-fw fa-cubes"></span> {{t 'applications'}}</h2>
|
|
||||||
</div>
|
|
||||||
{{#if apps}}
|
|
||||||
<div class="list-group">
|
|
||||||
{{#apps}}
|
|
||||||
<div class="list-group-item clearfix">
|
|
||||||
<button class="btn btn-success pull-right" data-upgrade="{{id}}">{{t 'system_upgrade_btn'}}</button>
|
|
||||||
<h3 class="list-group-item-heading">{{label}} <small>{{id}}</small></h3>
|
|
||||||
<span class="list-group-item-text">{{t 'from_to' current_version new_version}}</span>
|
|
||||||
</div>
|
|
||||||
{{/apps}}
|
|
||||||
</div>
|
|
||||||
<div class="panel-footer">
|
|
||||||
<button class="btn btn-success" data-upgrade="apps">{{t 'system_upgrade_all_applications_btn'}}</button>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="panel-body">
|
|
||||||
<span class="text-success"><span class="fa-fw fa-check-circle"></span> {{t 'system_apps_nothing'}}</span>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
|
@ -1,30 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/users" class="visible-xs">…</a>
|
|
||||||
<a href="#/users" class="hidden-xs">{{t 'users'}}</a>
|
|
||||||
<a href="#/groups" class="visible-xs">…</a>
|
|
||||||
<a href="#/groups" class="hidden-xs">{{t 'groups_and_permissions'}}</a>
|
|
||||||
<a href="#/groups/create">{{t 'group_new'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/groups/create" method="POST" class="form-horizontal">
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="groupname" class="col-sm-3 control-label">{{t 'group_name'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="groupname" name="groupname" class="form-control" placeholder="my group name" required pattern="[A-Za-z0-9_ ]+">
|
|
||||||
<div class="help-block">{{t 'group_format_name_help'}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'save'}}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
|
@ -1,139 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/users">{{t 'users'}}</a>
|
|
||||||
<a href="#/groups">{{t 'groups_and_permissions'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions-group">
|
|
||||||
<a role="button" href="#/groups/create" class="btn btn-success slide">
|
|
||||||
<span class="fa-plus"></span> {{t 'group_new'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
{{!-- ======================== Partial inline view ======================= --}}
|
|
||||||
{{#*inline "label"}}
|
|
||||||
<span class="label label-default label-removable">
|
|
||||||
<span class="fa-fw fa-{{icon}}"></span>
|
|
||||||
{{text}}
|
|
||||||
<button data-type="{{type}}s" data-action="remove" data-item="{{value}}" data-group="{{group}}">
|
|
||||||
{{#unless protected}}
|
|
||||||
<span class="fa-close" style="margin-left:5px"></span>
|
|
||||||
<span class="sr-only">{{t 'delete'}}</span>
|
|
||||||
{{/unless}}
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
{{/inline}}
|
|
||||||
|
|
||||||
{{#*inline "labelsLine"}}
|
|
||||||
{{#each items}}
|
|
||||||
{{> label text=(call ../display .) value=. icon=../icon type=../type item=. group=../group protected=(call ../is_protected . ../type ../group)}}
|
|
||||||
{{/each}}
|
|
||||||
{{#if inv}}
|
|
||||||
<div class="btn-group">
|
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<span class="fa-plus"></span> {{t (concat 'group_add_' type)}}
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{{#each inv}}
|
|
||||||
<li><button data-type="{{../type}}s" data-action="add" data-item="{{.}}" data-group="{{../group}}" >{{call ../display .}}</button></li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
{{/inline}}
|
|
||||||
<div id="view-groups">
|
|
||||||
|
|
||||||
{{!-- ======================== Groups ======================= --}}
|
|
||||||
{{#each groups}}
|
|
||||||
{{#unless primary}}
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading" role="tab" id="heading-context-group-{{@key}}">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<a role="button" data-toggle="collapse" href="#collapse-group-{{@key}}" aria-expanded="false" aria-controls="collapse-group-{{@key}}">
|
|
||||||
<span class="fa-fw fa-group"></span> {{#if special}}{{t (concat 'group_' @key)}}{{else}}{{t 'group'}} "{{ucwords @key}}"{{/if}}
|
|
||||||
</a>
|
|
||||||
{{#unless special}}
|
|
||||||
<button class="group-delete" data-action="delete-group" data-group="{{@key}}">
|
|
||||||
<span class="fa-close"></span>
|
|
||||||
<span class="sr-only">{{t 'delete'}}</span>
|
|
||||||
</button>
|
|
||||||
{{/unless}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div id="collapse-group-{{@key}}" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="heading-context-group-{{@key}}">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<h3>{{t 'users'}}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
{{#if special}}
|
|
||||||
<div style="font-style:italic"><span class="fa-info-circle"></span> {{t (concat 'group_explain_' @key)}}</div>
|
|
||||||
{{#if (eq @key 'visitors')}}
|
|
||||||
<div style="font-style:italic">{{t 'group_explain_visitors_needed_for_external_client'}}</div>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
|
||||||
{{> labelsLine display=../displayUser icon="user" type="member" items=members inv=membersInv group=@key is_protected=../is_protected}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<h3>{{t 'permissions'}}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
{{> labelsLine display=../displayPermission icon="key-modern" type="permission" items=permissions inv=permissionsInv group=@key is_protected=../is_protected}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
|
|
||||||
{{!-- ====================== User specific permissions ==================== --}}
|
|
||||||
<div class="panel panel-info">
|
|
||||||
<div class="panel-heading" role="tab" id="heading-context-specific">
|
|
||||||
<h2 class="panel-title">
|
|
||||||
<a role="button" data-toggle="collapse" href="#collapse-specific" aria-expanded="false" aria-controls="collapse-specific">
|
|
||||||
<span class="fa-fw fa-group"></span> {{t 'group_specific_permissions'}}
|
|
||||||
</a>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div id="collapse-specific" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="heading-context-specific">
|
|
||||||
<div class="panel-body">
|
|
||||||
{{#each groups}}
|
|
||||||
{{#if (or (and primary permissions) display)}}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<h3><span class="fa-fw fa-user"></span> {{@key}}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
{{> labelsLine display=../displayPermission icon="key-modern" type="permission" items=permissions inv=permissionsInv group=@key is_protected=../is_protected}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
<div class="btn-group">
|
|
||||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<span class="fa-plus"></span> {{t 'group_add_member'}}
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{{#each groups}}
|
|
||||||
{{#if primary}}
|
|
||||||
{{#unless (or permissions display)}}
|
|
||||||
<li><button data-action="add-user-specific-permission" data-user="{{@key}}">{{@key}}</button></li>
|
|
||||||
{{/unless}}
|
|
||||||
{{/if}}
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,68 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/users" class="visible-xs">…</a>
|
|
||||||
<a href="#/users" class="hidden-xs">{{t 'users'}}</a>
|
|
||||||
<a href="#/users/create">{{t 'users_new'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/users/create" method="POST" class="form-horizontal">
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="username" class="col-sm-3 control-label">{{t 'user_username'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="username" name="username" class="form-control" placeholder="johndoe" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="firstname" class="col-sm-3 control-label">{{t 'user_fullname'}}</label>
|
|
||||||
<div class="clearfix visible-xs"></div>
|
|
||||||
<div class="col-sm-4 col-xs-6">
|
|
||||||
<input type="text" id="firstname" name="firstname" class="form-control" placeholder="John" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-xs-6">
|
|
||||||
<input type="text" name="lastname" class="form-control" placeholder="Doe" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="alert alert-warning col-sm-offset-3 col-sm-9"><i class="fa fa-lock fa-fw"></i> {{t 'good_practices_about_user_password'}}</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password" class="col-sm-3 control-label">{{t 'password'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="password" id="password" name="password" class="form-control" placeholder="••••••••••" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="confirmation" class="col-sm-3 control-label">{{t 'password_confirmation'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="password" id="confirmation" name="confirmation" class="form-control" placeholder="••••••••••" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="alert alert-info col-sm-offset-3 col-sm-9"><i class="fa fa-envelope fa-fw"></i> {{t 'tip_about_user_email'}}</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="mail" class="col-sm-3 control-label">{{t 'user_email'}}</label>
|
|
||||||
<div class="clearfix visible-xs"></div>
|
|
||||||
<div class="col-sm-4 col-xs-6">
|
|
||||||
<p id="email-left" class="form-control-static" style="text-align:right;">johndoe</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-xs-6">
|
|
||||||
<select class="form-control" name="domain">
|
|
||||||
{{#domains}}
|
|
||||||
<option>@{{.}}</option>
|
|
||||||
{{/domains}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'save'}}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
|
@ -1,102 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/users" class="visible-xs">…</a>
|
|
||||||
<a href="#/users" class="hidden-xs">{{t 'users'}}</a>
|
|
||||||
<a href="#/users/{{username}}">{{username}}</a>
|
|
||||||
<a href="#/users/{{username}}/edit">{{t 'user_username_edit' username}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<form action="#/users/{{username}}" method="PUT" class="form-horizontal">
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="username" class="col-sm-3 control-label">{{t 'user_username'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="text" id="username" name="username" class="form-control" value="{{username}}" disabled>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="firstname" class="col-sm-3 control-label">{{t 'user_fullname'}}</label>
|
|
||||||
<div class="clearfix visible-xs"></div>
|
|
||||||
<div class="col-sm-4 col-xs-6">
|
|
||||||
<input type="text" id="firstname" name="firstname" class="form-control" value="{{firstname}}">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-xs-6">
|
|
||||||
<input type="text" name="lastname" class="form-control" value="{{lastname}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password" class="col-sm-3 control-label">{{t 'password'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="password" id="password" name="password" class="form-control" placeholder="••••••••••">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="confirmation" class="col-sm-3 control-label">{{t 'password_confirmation'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input type="password" id="confirmation" name="confirmation" class="form-control" placeholder="••••••••••">
|
|
||||||
<div class="help-block">{{t 'good_practices_about_user_password'}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email" class="col-sm-3 control-label">{{t 'user_email'}}</label>
|
|
||||||
<div class="clearfix visible-xs"></div>
|
|
||||||
<input type="hidden" id="mail" name="mail" class="form-control" value="{{mail}}">
|
|
||||||
<div class="col-sm-4 col-xs-6">
|
|
||||||
<input type="text" id="email" name="email" class="form-control" value="{{email.username}}">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5 col-xs-6">
|
|
||||||
<select class="form-control" name="domain">
|
|
||||||
{{#domains}}
|
|
||||||
<option {{#if selected}}selected="selected"{{/if}}>@{{domain}}</option>
|
|
||||||
{{/domains}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="mailbox-quota" class="col-sm-3 control-label">{{t 'user_mailbox_quota'}}</label>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="number" min="0" id="mailbox-quota" name="mailbox_quota" class="form-control" value="{{quota}}" placeholder="{{t 'mailbox_quota_placeholder'}}">
|
|
||||||
<div class="input-group-addon">M</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-5">
|
|
||||||
<div class="help-block quota-help-block">{{t 'mailbox_quota_description'}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="mailalias" class="col-sm-3 control-label">{{t 'user_emailaliases'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
{{#mail-aliases}}
|
|
||||||
<input type="email" name="mailalias" class="form-control" value="{{.}}" style="margin-bottom: 5px;">
|
|
||||||
{{/mail-aliases}}
|
|
||||||
<input id="mailalias" type="email" name="mailalias" class="mailalias-input form-control" placeholder="{{t 'user_new_mail'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="mailforward" class="col-sm-3 control-label">{{t 'user_emailforward'}}</label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
{{#mail-forward}}
|
|
||||||
<input type="email" name="mailforward" class="form-control" value="{{.}}" style="margin-bottom: 5px;">
|
|
||||||
{{/mail-forward}}
|
|
||||||
<input id="mailforward" type="email" name="mailforward" class="mailforward-input form-control" placeholder="{{t 'user_new_forward'}}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center">
|
|
||||||
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'save'}}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
|
@ -1,69 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/users" class="hidden-xs">{{t 'users'}}</a>
|
|
||||||
<a href="#/users" class="visible-xs">…</a>
|
|
||||||
<a href="#/users/{{username}}">{{username}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h2 class="panel-title">{{fullname}}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-body row">
|
|
||||||
|
|
||||||
<div class="col-sm-3 col-lg-3 text-center">
|
|
||||||
<span class="fa-fw fa-user" style="font-size: 120px;"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" col-sm-9 col-lg-9">
|
|
||||||
<table class="table table-user-information">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{t 'user_username'}}</strong></td>
|
|
||||||
|
|
||||||
<td>{{username}}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{t 'user_email'}}</strong></td>
|
|
||||||
|
|
||||||
<td>{{mail}}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{t 'user_mailbox_quota'}}</strong></td>
|
|
||||||
<td>{{mailbox-quota.limit}}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{t 'user_mailbox_use'}}</strong></td>
|
|
||||||
<td>{{mailbox-quota.use}}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{t 'user_emailaliases'}}</strong></td>
|
|
||||||
|
|
||||||
<td>{{#if mail-aliases}} {{#mail-aliases}}
|
|
||||||
{{.}}<br>
|
|
||||||
{{/mail-aliases}} {{/if}}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>{{t 'user_emailforward'}}</strong></td>
|
|
||||||
|
|
||||||
<td>{{#if mail-forward}} {{#mail-forward}}
|
|
||||||
{{.}}<br>
|
|
||||||
{{/mail-forward}} {{/if}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<span class="pull-right">
|
|
||||||
<a role="button" href="#/users/{{username}}/edit" class="btn btn-info slide"><span class="fa-pencil-square-o"/> {{t 'user_username_edit' username}}</a>
|
|
||||||
<button class="btn btn-danger" data-action="delete" data-user="{{username}}"><span class="fa-trash-o"/> {{t 'delete'}}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,31 +0,0 @@
|
||||||
<div class="btn-breadcrumb">
|
|
||||||
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
|
|
||||||
<a href="#/users">{{t 'users'}}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions-group">
|
|
||||||
<a href="#/groups" class="btn btn-info">
|
|
||||||
<span class="fa-key-modern"></span> {{t 'groups_and_permissions_manage'}}
|
|
||||||
</a>
|
|
||||||
<a role="button" href="#/users/create" class="btn btn-success slide">
|
|
||||||
<span class="fa-plus"></span> {{t 'users_new'}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="separator"></div>
|
|
||||||
|
|
||||||
<div class="list-group">
|
|
||||||
{{#each users}}
|
|
||||||
<a href="#/users/{{@key}}" class="list-group-item slide clearfix">
|
|
||||||
<span class="pull-right fa-chevron-right"></span>
|
|
||||||
<h2 class="list-group-item-heading">{{@key}} <small>{{fullname}}</small></h2>
|
|
||||||
<p class="list-group-item-text">{{mail}}</p>
|
|
||||||
</a>
|
|
||||||
{{else}}
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<span class="fa-exclamation-triangle"></span>
|
|
||||||
{{t 'users_no'}}
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
|
|
||||||
</div>
|
|
Loading…
Reference in a new issue