removed old sammy app

This commit is contained in:
axolotle 2021-01-19 19:44:41 +01:00
parent d8056a1a4b
commit 00d9db0bc9
63 changed files with 0 additions and 15277 deletions

View file

@ -1,3 +0,0 @@
{
"sub": true
}

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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>&nbsp;
<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>&nbsp;
<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&nbsp;</span><span class="fa-thumb-tack"></span>
</button>
<button id="clear-btn" data-y18n-title="clear">
<span class="sr-only">Clear&nbsp;</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">&times;</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>

View file

@ -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']);
});
}
);
});
})();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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');
});
}
);
});
})();

View file

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

View file

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

View file

@ -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']);
});
}
}
});
});
})();

View file

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

View file

@ -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 () {});
})();

View file

@ -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("&");
}
});
})();

View file

@ -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");
}
});
})();

View file

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

View file

@ -1,3 +0,0 @@
{
"password": "Heslo"
}

7792
src/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -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;">&nbsp;</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>

View file

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

View file

@ -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">&hellip;</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>

View file

@ -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">&hellip;</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>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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">&hellip;</a>
<a href="#/users" class="hidden-xs">{{t 'users'}}</a>
<a href="#/groups" class="visible-xs">&hellip;</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>

View file

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

View file

@ -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">&hellip;</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>

View file

@ -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">&hellip;</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>

View file

@ -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">&hellip;</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>

View file

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