Merge branch 'stretch-testing' into stretch-stable

This commit is contained in:
Kay0u 2020-05-20 19:11:36 +00:00
commit 3f01bc51b5
70 changed files with 2187 additions and 2626 deletions

View file

@ -38,4 +38,4 @@ https://example.com/yunohost/admin/views/domain/domain_list.ms)
* Font-Awesome 4.5.0
* Handlebars 1.3.0
* Sammy 0.7.6
* Jquery-Cookie 2.1.0
* JS-cookie 2.1.0

76
debian/changelog vendored
View file

@ -1,3 +1,79 @@
yunohost-admin (3.8.3.1) testing; urgency=low
- Add tip about the diagnosis page if domain seems not ready for ACME (e7a4df1)
- Support raw string in 'ask' fields from manifest (for upcoming thing in core about default strings for install questions) (ee20f55)
-- Alexandre Aubin <alex.aubin@mailoo.org> Sat, 09 May 2020 20:24:00 +0200
yunohost-admin (3.8.3) testing; urgency=low
- [enh] Add a note and explanation about sharing the logs when viewing logs of shared operations
- [enh] Filter non-relevant line in operation log view
- [fix] Remove an unecessary call to app list on domain view
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 07 Apr 2020 04:15:00 +0000
yunohost-admin (3.8.2) testing; urgency=low
- [enh] Highlight error/warnings/... in tools > logs (#294)
- [enh] Hide stop button for critical services ? (#295)
- [fix] Stupid typo in upgrade controller prevented upgrading all apps (a3a0d8f)
- [fix] Custom app install + misc detail in regular app install (1592ab4)
- [fix] Make the 'all apps' button look like the others (9694d8a)
- [mod] Simplify log view (4480836)
- [enh] Save/restore collapse states when refreshing diagnosis view (a110e56)
- [enh] Add an explanation text on top of the diagnosis view (ccefdd6)
- [i18n] Improve translations for Esperanto, Spanish, French, Occitan, Polish, Nepali
Thanks to all contributors <3 ! (Quentí, Simon, amirale qt)
-- Alexandre Aubin <alex.aubin@mailoo.org> Wed, 29 Apr 2020 23:20:00 +0000
yunohost-admin (3.8.1.1) testing; urgency=low
- [hotfix] Pacman hanging forever after fetching version number..
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 19 Apr 2020 16:20:00 +0000
yunohost-admin (3.8.1) testing; urgency=low
- Many small cosmetic and mechanics improvements for the Diagnosis view
- Add a 'Restart' button for services
- Drop 'Security feed' and 'Download self-CA auth cert' features from the Tools section
- Move 'Share on Yunopaste' button to make it more obvious to users (dd2570e)
- Display 'All apps' button at the top in app category selector (#291)
- Improve translations for French, German, Catalan, Turkish
Thanks to all contributors <3 ! (Kay0u, Yasin S. T., Zeik0s, E.Gaspar, xaloc33)
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 19 Apr 2020 06:22:00 +0000
yunohost-admin (3.8.0) testing; urgency=low
# Major stuff
- [enh] New diagnosis interface (#209, 109f542, 95f8503, 9e0f2f7, 79839b5)
- [enh] App categories (#279)
- [enh] Rework service views (#274)
# Refactoring, cleaning
- Code refactoring, readability improvements, use buttons instead of links when it's not about changing page (#262, 4f544de, 44c27a5, 6177044)
- Remove the whole monitoring / glances stuff (#263)
- Remove the appslist system from the webadmin (#264)
- Remove app debug button, follow-up of corresponding PR on yunohost core (#271)
- Fix slider effect (787204b)
- Propagate parameter name change from core when changing domain (#260)
# i18n
- String definition / usage tests + cleaning of stale strings (#288)
- Update translations for Spanish, Esperanto, Basque, Turkish, Catalan, Hindi, German, Greek, Dutch, Polish, Portuguese, Chinese (Simplified), Nepali, French, Occitan,Italian, Bengali (Bangladesh), Hungarian
Thanks to all contributors <3 ! (Abdulkadir F. Ş., Aeris One, Aleks, Armando F. Bram, Giovanni G. Gustavo M., Hem S., Jeroen F., Juan, Kay0u, Patrick B., Quentí, Yifei D., advocatux, amirale qt, Elie G., frju365, ppr, Romain R., xaloc3)
-- Kay0u <pierre@kayou.io> Thu, 09 Apr 2020 20:18:35 +0000
yunohost-admin (3.7.1.1) stable; urgency=low
- [fix] Aleks forgot to properly fix the conflicts in oc.json ~.~

2
debian/control vendored
View file

@ -12,7 +12,7 @@ Architecture: all
Conflicts: yunohost-apps-admin
Replaces: yunohost-apps-admin
Depends: ${misc:Depends}
, yunohost (>= 3.7)
, yunohost (>= 3.8)
Description: web administration interface for yunohost
YunoHost aims to make self-hosting accessible to everyone. It configures
an email, Web and IM server alongside a LDAP base. It also provides

View file

@ -81,7 +81,7 @@ body {
}
.btn {
& + .btn {margin-left: 8px;}
& + .btn {margin-left: 8px; margin-right: 8px;}
}
button {
&:extend(.btn all);
@ -101,10 +101,6 @@ button {
color: transparent;
}
.label {
border-radius: 1px;
}
/*
* The top heading of the doc
*
@ -229,8 +225,7 @@ button {
.clearfix;
.make-row(12);
margin: 20px 0;
padding-top: 20px;
margin-top: 40px;
padding-top: 10px;
border-top: 1px solid #eee;
color: #999;
font-size: 0.9em;
@ -641,13 +636,18 @@ input[type='radio'].nice-radio {
background-color: darkorchid;
}
.app-category-card {
text-align: center;
}
// only one card for small screens
.app-card {
.app-card, .app-category-card {
width: 100%;
.btn-group {
width: 100%;
.btn{
margin-left: 0;
margin-right: 0;
}
}
}
@ -663,13 +663,16 @@ input[type='radio'].nice-radio {
}
}
.app-state {
font-size: 10px;
}
.app-title {
.app-title, .app-category-title {
margin-top: 5px;
font-weight: 600;
}
.app-card-desc {
.app-card-desc, .app-category-card-desc {
height: 6rem;
overflow: hidden;
}
@ -680,6 +683,7 @@ input[type='radio'].nice-radio {
margin-bottom: 3px;
margin-right: 7px;
margin-top: -5px;
height: 18px;
}
.auto-width {
@ -696,6 +700,7 @@ input[type='radio'].nice-radio {
.app-card .panel-body {
padding: 1.5rem;
padding-bottom: 0.5rem;
h3 {
margin-top: 0;
@ -707,17 +712,47 @@ input[type='radio'].nice-radio {
}
}
.app-category-card .panel-body {
padding: 2em;
height: 10em;
color: #333 !important;
}
.subtag-selector {
text-align: center;
}
.app-category-card {
text-decoration: none !important;
}
.app-category-card:hover {
border-color: #777;
}
.app-category-title {
line-height: 1em;
white-space: normal;
}
.app-category-card-desc {
white-space: normal;
}
/** Groups View **/
#view-groups {
.panel-heading a {
text-decoration: none;
&.group-delete {
float:right;
color:lighten(@label-danger-bg, 20%);
:hover {
color:@label-danger-bg;
}
}
.group-delete {
font-size: 24px;
line-height: 24px;
padding: 0;
float:right;
color:lighten(@label-danger-bg, 20%);
:hover {
color:@label-danger-bg;
}
}
.panel-body {
@ -731,6 +766,10 @@ input[type='radio'].nice-radio {
.dropdown-menu {
max-height: 200px;
overflow-y: auto;
button {
background: none;
}
}
.label-removable {
// The following match properties from regular btn's
@ -749,15 +788,18 @@ input[type='radio'].nice-radio {
margin-right:7px; // Spacing between labels
> a {
> button {
line-height: 12px;
margin-left:6px;
padding: 0;
padding-left:6px;
border-left: #ccc 1px solid;
color:lighten(@label-info-bg,20);
background-color:transparent;
text-decoration: none;
}
> a:hover {
> button:hover {
color:@label-info-bg;
}
}
@ -807,7 +849,7 @@ input[type='radio'].nice-radio {
}
// display 2 cards between 640 and 992px
.app-card {
.app-card, .app-category-card {
width: 47.9%;
margin: 1%;
}
@ -909,7 +951,7 @@ input[type='radio'].nice-radio {
// bootstrap breakpoint for large screen is 992px
@media screen and (min-width: 992px) {
.app-card {
.app-card, .app-category-card {
// display 3 cards by row
width: 31.3%;
margin: 1%;
@ -931,3 +973,58 @@ input[type='radio'].nice-radio {
float: none !important;
}
}
.notransition {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
transition: none !important;
}
/* Diagnosis styles */
.alert-success-yo {
background-color: #dff0d8;
border-color: #d6e9c6;
}
.alert-warning-yo {
background-color: #fcf8e3;
border-color: #faebcc;
}
.alert-danger-yo {
background-color: #f2dede;
border-color: #ebccd1;
}
.alert-info-yo {
background-color: #d9edf7;
border-color: #bce8f1;
}
.alert-ignored-yo {
background-color: ghostwhite;
border-color: lightgrey;
color: grey;
}
.diagnosis-item {
code {
word-break: break-all;
border-radius: 5px;
padding-top: 1px;
padding-bottom: 1px;
}
code.cmd {
word-break: break-word;
color: white;
background-color: #333;
}
ul > li {
padding-top: 0.3em;
}
}

View file

@ -81,8 +81,8 @@
</header>
<div class="content"></div>
<footer>
<button type="button" id="modal-cancel" data-action="cancel" data-y18n="cancel">Cancel</button>
<button type="button" id="modal-confirm" data-action="confirm" data-y18n="ok">OK</button>
<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>

View file

@ -10,7 +10,7 @@
// List installed apps
app.get('#/apps', function (c) {
c.api('/apps?installed', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8
c.api('GET', '/apps?full', {}, function(data) {
var apps = data['apps'];
c.arraySortById(apps);
c.view('app/app_list', {apps: apps});
@ -70,7 +70,7 @@
}
else
{
return 'success';
return 'info';
}
}
@ -107,131 +107,213 @@
}
}
// List available apps
app.get('#/apps/install', function (c) {
c.api('/apps', function (data) { // http://api.yunohost.org/#!/app/app_list_get_8
c.api('/apps?raw', function (dataraw) { // http://api.yunohost.org/#!/app/app_list_get_8
var apps = []
$.each(data['apps'], function(k, v) {
app = dataraw[v['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";
}
// 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
});
});
});
});
app.manifest.maintainer = extractMaintainer(app.manifest);
var isWorking = (app.state === 'working' || app.state === "high-quality") && app.level > 0;
// 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) {
// Keep only the first instance of each app and remove not working apps
if (!v['id'].match(/__[0-9]{1,5}$/) && (app.state !== 'notworking')) {
// Ignore not working apps
if (app.state === 'notworking') { return; }
app.installable = (!v.installed || app.manifest.multi_instance)
app.levelFormatted = isNaN(app.level) ? '?' : app.level;
// Ignore apps not in this category
if ((category_id !== "all") && (app.category !== category_id)) { return; }
app.levelColor = levelToColor(app.level);
app.stateColor = stateToColor(app.state);
app.maintainedColor = maintainedStateToColor(app.maintained);
app.installColor = combineColors(app.stateColor, app.levelColor);
app.id = app.manifest.id;
app.level = parseInt(app.level);
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";
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";
}
jQuery.extend(app, v);
apps.push(app);
}
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
});
// Sort app list
c.arraySortById(apps);
// Default filter is 'decent quality apps'
cardGrid.isotope({ filter: '.decentQuality' });
// 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('.grid').isotope({
itemSelector: '.app-card',
layoutMode: 'fitRows',
transitionDuration: 200
});
$(".subtag-selector button").on("click", function() {
var selector = $(this).parent();
$("button", selector).removeClass("active");
$(this).addClass("active");
cardGrid.isotope({ filter: filterApps });
});
filterByClassAndName = function () {
var input = jQuery("#filter-app-cards").val().toLowerCase();
var inputMatch = (jQuery(this).find('.app-title').text().toLowerCase().indexOf(input) > -1);
filterApps = function () {
var filterClass = jQuery("#dropdownFilter").attr("data-filter");
var classMatch = (filterClass === '*') ? true : jQuery(this).hasClass(filterClass);
return inputMatch && classMatch;
},
// Check text search
var input = jQuery("#filter-app-cards").val().toLowerCase();
if (jQuery(this).find('.app-title').text().toLowerCase().indexOf(input) <= -1) return false;
// Default filter is 'decent quality apps'
cardGrid.isotope({ filter: '.decentQuality' });
// 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;
}
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').attr("data-filter", jQuery(this).attr("data-filter"));
// filter !
cardGrid.isotope({ filter: filterByClassAndName });
});
// Check quality criteria
var class_ = jQuery("#dropdownFilter").data("filter");
if ((class_ !== '*') && (! jQuery(this).hasClass(class_))) return false;
jQuery("#filter-app-cards").on("keyup", function() {
cardGrid.isotope({ filter: filterByClassAndName });
});
};
return true;
},
// render
c.view('app/app_list_install', {apps: apps}, setupFilterEvents);
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('/apps/'+c.params['app']+'?raw', function(data) { // http://api.yunohost.org/#!/app/app_info_get_9
c.api('/users/permissions', function(data_permissions) {
c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(data) {
c.api('GET', '/users/permissions', {}, function(data_permissions) {
// Permissions
data.permissions = data_permissions.permissions[c.params['app']+".main"]["allowed"];
// Permissions
data.permissions = data_permissions.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']
;
// 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);
// 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);
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');
});
}
);
});
});
});
});
// Get app debug page
app.get('#/apps/:app/debug', function (c) {
c.api('/apps/'+c.params['app']+'/debug', function(data) {
c.view('app/app_debug', data);
});
});
//
// App actions
//
// Get app actions list
app.get('#/apps/:app/actions', function (c) {
c.api('/apps/'+c.params['app']+'/actions', function(data) {
c.api('GET', '/apps/'+c.params['app']+'/actions', {}, function(data) {
$.each(data.actions, function(_, action) {
formatYunoHostStyleArguments(action.arguments, c.params);
@ -249,7 +331,7 @@
});
});
// Perform application
// Perform app action
app.put('#/apps/:app/actions/:action', function(c) {
// taken from app install
$.each(c.params, function(k, v) {
@ -268,14 +350,18 @@
'args': c.serialize(c.params.toHash())
}
c.api('/apps/'+app_id+'/actions/'+action_id, function() { // http://api.yunohost.org/#!/app/app_install_post_2
c.redirect('#/apps/'+app_id+'/actions');
}, 'PUT', params);
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('/apps/'+c.params['app']+'/config-panel', function(data) {
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);
@ -301,18 +387,11 @@
'args': c.serialize(c.params.toHash())
}
c.api('/apps/'+app_id+'/config', function() { // http://api.yunohost.org/#!/app/app_install_post_2
c.redirect('#/apps/'+app_id+'/config-panel');
}, 'POST', params);
c.api('POST', '/apps/'+app_id+'/config', params, function() {
c.redirect_to('#/apps/'+app_id+'/config-panel', {slide:false});
});
})
// Special case for custom app installation.
app.get('#/apps/install/custom', function (c) {
// If we try to GET /apps/install/custom, it means that installation fail.
// Need to redirect to apps/install to get rid of pacamn and see the log.
c.redirect('#/apps/install');
});
// Helper function that formats YunoHost style arguments for generating a form
function formatYunoHostStyleArguments(args, params) {
if (!args) {
@ -333,10 +412,16 @@
args[k].helpLink = "";
// Multilingual label
args[k].label = (typeof args[k].ask[y18n.locale] !== 'undefined') ?
args[k].ask[y18n.locale] :
args[k].ask['en']
;
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') {
@ -463,16 +548,24 @@
displayLicense: (manifest['license'] !== undefined && manifest['license'] !== 'free')
};
formatYunoHostStyleArguments(data.manifest.arguments.install, params);
formatYunoHostStyleArguments(manifest.arguments.install, params);
// Multilingual description
data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ?
data.manifest.description[y18n.locale] :
data.manifest.description['en']
;
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 = data.manifest.multi_instance ? y18n.t('yes') : y18n.t('no');
data.manifest.multi_instance = manifest.multi_instance ? y18n.t('yes') : y18n.t('no');
// View app install form
c.view('app/app_install', data);
@ -481,9 +574,9 @@
// App installation form
app.get('#/apps/install/:app', function (c) {
c.api('/apps?raw', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8
c.api('GET', '/appscatalog?full', {}, function(data) {
var app_name = c.params["app"];
var app_infos = data[app_name];
var app_infos = data["apps"][app_name];
if (app_infos['state'] === "validated")
{
app_infos['state'] = "official";
@ -504,17 +597,16 @@
c.params
);
},
function(){
$('div.loader').remove();
c.redirect('#/apps/install');
function () {
c.redirect_to('#/apps/catalog');
}
);
}
else
{
c.appInstallForm(
c.params['app'],
data[c.params['app']].manifest,
app_name,
app_infos.manifest,
c.params
);
}
@ -547,124 +639,67 @@
delete params['args'];
}
c.api('/apps', function() { // http://api.yunohost.org/#!/app/app_install_post_2
c.redirect('#/apps');
}, 'POST', params);
c.api('POST', '/apps', params, function() {
c.redirect_to('#/apps');
});
}
else {
c.flash('warning', y18n.t('app_install_cancel'));
store.clear('slide');
c.redirect('#/apps/install');
c.refresh();
}
});
// Install custom app from github
app.post('#/apps/install/custom', function(c) {
app.get('#/apps/install/custom/:url', function(c) {
var params = {
label: c.params['label'],
app: c.params['url']
};
delete c.params['label'];
delete c.params['url'];
// Force trailing slash
url = c.params['url'];
url = url.replace(/\/?$/, '/');
raw_manifest_url = url.replace('github.com', 'raw.githubusercontent.com') + 'master/manifest.json'
c.confirm(
y18n.t('applications'),
y18n.t('confirm_install_custom_app'),
function(){
// 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) || {};
// Force trailing slash
params.app = params.app.replace(/\/?$/, '/');
c.appInstallForm(
url,
manifest,
c.params
);
// Get manifest.json to get additional parameters
jQuery.ajax({
url: params.app.replace('github.com', 'raw.githubusercontent.com') + 'master/manifest.json',
type: 'GET',
})
.done(function(manifest) {
// raw.githubusercontent.com serve content as plain text
manifest = jQuery.parseJSON(manifest) || {};
})
.fail(function(xhr) {
c.flash('fail', y18n.t('app_install_custom_no_manifest'));
c.redirect("#/apps/catalog/");
});
c.appInstallForm(
params.app,
manifest,
c.params
);
})
.fail(function(xhr) {
c.flash('fail', y18n.t('app_install_custom_no_manifest'));
store.clear('slide');
c.redirect('#/apps/install');
});
},
function(){
c.flash('warning', y18n.t('app_install_cancel'));
store.clear('slide');
c.redirect('#/apps/install');
}
);
});
// Remove installed app
app.get('#/apps/:app/uninstall', function (c) {
c.confirm(
y18n.t('applications'),
y18n.t('confirm_uninstall', [c.params['app']]),
function() {
c.api('/apps/'+ c.params['app'], function() { // http://api.yunohost.org/#!/app/app_remove_delete_4
c.redirect('#/apps');
}, 'DELETE');
},
function() {
store.clear('slide');
c.redirect('#/apps/'+ c.params['app']);
}
);
});
// Make app default
app.get('#/apps/:app/default', function (c) {
c.confirm(
y18n.t('applications'),
y18n.t('confirm_app_default'),
function() {
c.api('/apps/'+ c.params['app'] +'/default', function() { //
store.clear('slide');
c.redirect('#/apps/'+ c.params['app']);
}, 'PUT');
},
function() {
store.clear('slide');
c.redirect('#/apps/'+ c.params['app']);
}
);
});
// Get app change label page
app.get('#/apps/:app/changelabel', function (c) {
c.api('/apps/'+c.params['app']+'?raw', function(app_data) {
data = {
c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(app_data) {
data = {
id: c.params['app'],
label: app_data.settings.label,
};
c.view('app/app_changelabel', data);
};
c.view('app/app_changelabel', data);
});
});
// Change app label
app.post('#/apps/:app/changelabel', function (c) {
params = {'new_label': c.params['label']};
c.api('/apps/' + c.params['app'] + '/label', function(data) { // Call changelabel API
store.clear('slide');
c.redirect('#/apps/'+ c.params['app']);
}, 'PUT', params);
c.api('PUT', '/apps/' + c.params['app'] + '/label', params, function(data) {
c.redirect_to('#/apps/'+ c.params['app']);
});
});
// Get app change URL page
app.get('#/apps/:app/changeurl', function (c) {
c.api('/apps/'+c.params['app']+'?raw', function(app_data) {
c.api('/domains', function(domain_data) { // http://api.yunohost.org/#!/domain/domain_list_get_2
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 = [];
@ -696,14 +731,9 @@
y18n.t('confirm_app_change_url', [c.params['app']]),
function() {
params = {'domain': c.params['domain'], 'path': c.params['path']};
c.api('/apps/' + c.params['app'] + '/changeurl', function(data) { // Call changeurl API
store.clear('slide');
c.redirect('#/apps/'+ c.params['app']);
}, 'PUT', params);
},
function() {
store.clear('slide');
c.redirect('#/apps/'+ c.params['app'] + '/changeurl');
c.api('PUT', '/apps/' + c.params['app'] + '/changeurl', params, function(data) {
c.redirect_to('#/apps/'+ c.params['app']);
});
}
);
});

View file

@ -32,118 +32,9 @@
c.view('backup/backup', {'storages':storages});
});
// Storage list
app.get('#/storages/create', function (c) {
c.view('backup/storage_create', {});
});
// Create a storage
app.post('#/storages', function (c) {
store.clear('slide');
c.redirect('#/storages');
});
// 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('/hooks/backup', function(hooks) {
data['hooks'] = c.groupHooks(hooks['hooks']);
data['apps'] = {};
c.api('/apps?with_backup', function(apps_list) {
data['apps'] = apps_list.apps;
c.view('backup/backup_create', data, c.selectAllOrNone);
});
});
});
app.post('#/backup/:storage', function (c) {
var params = c.ungroupHooks(c.params['system_parts'],c.params['apps']);
c.api('/backup', function() {
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']);
}, 'POST', params);
});
// 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 = c.ungroupHooks(c.params['system_parts'],c.params['apps']);
params['force'] = '';
c.api('/backup/restore/'+c.params['archive'], function(data) {
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
}, 'POST', params);
}, this, c),
function(){
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
}
);
});
// Delete a backup
app.get('#/backup/:storage/:archive/delete', function (c) {
c.confirm(
y18n.t('backup'),
y18n.t('confirm_delete', [c.params['archive']]),
function(){
c.api('/backup/archives/'+c.params['archive'], function(data) {
c.redirect('#/backup/'+ c.params['storage']);
}, 'DELETE');
},
function(){
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
}
);
});
// Download a backup
app.get('#/backup/:storage/:archive/download', function (c) {
c.api('/backup/'+c.params['archive']+'/download', function(data) {
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
}, 'GET');
});
// Copy a backup
app.get('#/backup/:storage/:archive/copy', function (c) {
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
});
// Upload a backup
app.get('#/backup/:storage/:archive/upload', function (c) {
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
});
// Get archive info
app.get('#/backup/:storage/:archive', function (c) {
c.api('/backup/archives/'+c.params['archive']+'?with_details', function(data) {
data.storage = {
id: c.params['storage'],
name: y18n.t('local_archives')
};
data.other_storages = [];
data.name = c.params['archive'];
data.system_parts = c.groupHooks(Object.keys(data['system']),data['system']);
data.items = (data['system']!={} || data['apps']!=[]);
data.locale = y18n.locale
c.view('backup/backup_info', data, c.selectAllOrNone);
});
});
// Archive list
app.get('#/backup/:storage', function (c) {
c.api('/backup/archives?with_info', function(data) {
c.api('GET', '/backup/archives?with_info', {}, function(data) {
data.storage = {
id: 'local',
name: y18n.t('local_archives')
@ -159,4 +50,173 @@
});
});
// 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() {
// 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);
});
// 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

@ -0,0 +1,152 @@
(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

@ -10,8 +10,8 @@
// List existing domains
app.get('#/domains', function (c) {
c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_list_get_2
c.api('/domains/main', function(data2) {
c.api('GET', '/domains', {}, function(data) {
c.api('PUT', '/domains/main', {}, function(data2) {
var domains = [];
$.each(data.domains, function(k, domain) {
domains.push({
@ -29,7 +29,7 @@
domains: domains,
main_domain_form: main_domain_form
});
}, 'PUT');
});
});
});
@ -68,8 +68,7 @@
if (c.params['domain'] === '') {
if (c.params['ddomain'] === '') {
c.flash('fail', y18n.t('error_select_domain'));
store.clear('slide');
c.redirect('#/domains/add');
c.redirect_to('#/domains/add');
}
params.domain = c.params['ddomain'] + c.params['ddomain-ext'];
endurl = 'dyndns';
@ -77,42 +76,53 @@
params.domain = c.params['domain'];
}
c.api('/domains?'+endurl, function(data) { // http://api.yunohost.org/#!/domain/domain_add_post_1
c.redirect('#/domains');
}, 'POST', params);
c.api('POST', '/domains?'+endurl, params, function(data) {
c.redirect_to('#/domains');
});
});
// Get existing domain info
app.get('#/domains/:domain', function (c) {
c.api('/domains/main', function(dataMain) {
c.api('/apps?installed', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8
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() {
// FIXME - This dirty trick (along with the previous API call
// for apps installed) should be removed once letsencrypt_ynh
// is not used by many people anymore. Probably around 07/2017
// or end of 2017...
var enable_cert_management_ = true;
$.each(data['apps'], function(k, v) {
if (v.id == "letsencrypt") {
enable_cert_management_ = false;
}
// 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() });
}
)
});
var domain = {
name: c.params['domain'],
main: (c.params['domain'] == dataMain.current_main_domain) ? true : false,
url: "https://"+c.params['domain'],
enable_cert_management: enable_cert_management_
};
c.view('domain/domain_info', domain);
// 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');
});
}
);
});
});
}, 'PUT');
});
});
// Domain DNS
app.get('#/domains/:domain/dns', function (c) {
c.api('/domains/' + c.params['domain'] + '/dns', function(data) {
c.api('GET', '/domains/' + c.params['domain'] + '/dns', {}, function(data) {
var domain = {
name: c.params['domain'],
dns: data
@ -123,7 +133,7 @@
// Domain certificate
app.get('#/domains/:domain/cert-management', function (c) {
c.api('/domains/cert-status/' + c.params['domain'] + '?full', function(data) {
c.api('GET', '/domains/cert-status/' + c.params['domain'] + '?full', {}, function(data) {
var s = data["certificates"][c.params['domain']];
var status_ = {
@ -199,132 +209,45 @@
status: status_,
actions_enabled : actions_enabled
};
c.view('domain/domain_cert', data_);
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() }); }
);
});
});
});
});
// Install let's encrypt certificate on domain
app.get('#/domains/:domain/cert-install-LE', function (c) {
c.confirm(
y18n.t('certificate'),
y18n.t('confirm_cert_install_LE', [c.params['domain']]),
function(){
c.api('/domains/cert-install/' + c.params['domain'], function(data) {
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}, 'POST');
},
function(){
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}
);
});
// Regenerate a self-signed certificate
app.get('#/domains/:domain/cert-regen-selfsigned', function (c) {
c.confirm(
y18n.t('certificate'),
y18n.t('confirm_cert_regen_selfsigned', [c.params['domain']]),
function(){
c.api('/domains/cert-install/' + c.params['domain'] + "?self_signed", function(data) {
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}, 'POST');
},
function(){
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}
);
});
// Manually renew a Let's Encrypt certificate
app.get('#/domains/:domain/cert-renew-letsencrypt', function (c) {
c.confirm(
y18n.t('certificate'),
y18n.t('confirm_cert_manual_renew_LE', [c.params['domain']]),
function(){
c.api('/domains/cert-renew/' + c.params['domain'] + "?force", function(data) {
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}, 'POST');
},
function(){
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}
);
});
// Replace valid cert with self-signed
app.get('#/domains/:domain/cert-replace-with-selfsigned', function (c) {
c.confirm(
y18n.t('certificate'),
y18n.t('confirm_cert_revert_to_selfsigned', [c.params['domain']]),
function(){
c.api('/domains/cert-install/' + c.params['domain'] + "?self_signed&force", function(data) {
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}, 'POST');
},
function(){
store.clear('slide');
c.redirect('#/domains/'+c.params['domain']+'/cert-management');
}
);
});
// Remove existing domain
app.get('#/domains/:domain/delete', function (c) {
c.confirm(
y18n.t('domains'),
y18n.t('confirm_delete', [c.params['domain']]),
function(){
c.api('/domains/'+ c.params['domain'], function(data) { // http://api.yunohost.org/#!/domain/domain_remove_delete_3
store.clear('slide');
c.redirect('#/domains');
}, 'DELETE');
},
function(){
store.clear('slide');
c.redirect('#/domains');
}
);
});
// Set default domain
app.post('#/domains', function (c) {
if (c.params['domain'] === '') {
c.flash('fail', y18n.t('error_select_domain'));
store.clear('slide');
c.redirect('#/domains');
} else {
c.confirm(
y18n.t('domains'),
y18n.t('confirm_change_maindomain'),
function(){
var params = {
new_domain: c.params['domain']
};
c.api('/domains/main', function(data) { // http://api.yunohost.org/#!/tools/tools_maindomain_put_1
store.clear('slide');
c.redirect('#/domains');
}, 'PUT', params);
// Wait 15s and refresh the page
var refreshDomain = window.setTimeout(function(){
store.clear('slide');
c.redirect('#/domains');
}, 15000);
},
function(){
store.clear('slide');
c.redirect('#/domains');
}
);
}
});
})();

View file

@ -10,7 +10,7 @@
// Firewall status
app.get('#/tools/firewall', function (c) {
c.api('/firewall?raw', function(data) {
c.api('GET', '/firewall?raw', {}, function(data) {
var firewall = {
ports: {},
upnp: false
@ -30,28 +30,49 @@
// Get UPnP status
firewall.upnp = data.uPnP.enabled;
c.view('tools/tools_firewall', firewall);
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() }); }
);
});
});
});
});
// Enable/Disable UPnP
app.get('#/tools/firewall/upnp/:action', function (c) {
// Update port status from form
app.post('#/tools/firewall/port', function (c) {
c.confirm(
y18n.t('firewall'),
// confirm_upnp_enable and confirm_upnp_disable
y18n.t('confirm_upnp_' + c.params['action'].toLowerCase()),
y18n.t('confirm_firewall_' + c.params['action'].toLowerCase(), [ c.params['port'], y18n.t(c.params['protocol']), y18n.t(c.params['connection']) ]),
function(){
var params = {
action : c.params['action']
};
c.api('/firewall/upnp', function(data) {
store.clear('slide');
c.redirect('#/tools/firewall');
}, 'GET', params);
},
function(){
store.clear('slide');
c.redirect('#/tools/firewall');
c.togglePort(
c.params['port'],
c.params['protocol'],
c.params['connection'],
c.params['action']
);
}
);
});
@ -65,8 +86,7 @@
if (port != parseInt(port) || port < 0 || port > 65535) {
c.flash('fail', y18n.t('unknown_argument', [port]));
store.clear('slide');
c.redirect('#/tools/firewall');
c.refresh();
}
switch (connection) {
@ -98,75 +118,27 @@
break;
default:
c.flash('fail', y18n.t('unknown_action', [action]));
store.clear('slide');
c.redirect('#/tools/firewall');
c.refresh();
}
if (method !== null && protocol !== null && port !== null) {
// port:
// protocol:
// - UDP
// - TCP
// - Both
// --ipv4-only:
// --ipv6-only:
// --no-upnp:
var params = {
port : port,
protocol : protocol
};
c.api('/firewall/port?'+endurl, function(data) {
store.clear('slide');
c.redirect('#/tools/firewall');
}, method, params);
}
else {
store.clear('slide');
c.redirect('#/tools/firewall');
}
// 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;
});
// Update port status from direct link
// #/firewall/port/{{@key}}/tcp/ipv4/close
app.get('#/tools/firewall/port/:port/:protocol/:connection/:action', function (c) {
c.confirm(
y18n.t('firewall'),
// confirm_firewall_open and confirm_firewall_close
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']
);
},
function(){
store.clear('slide');
c.redirect('#/tools/firewall');
}
);
});
// 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']
);
},
function(){
store.clear('slide');
c.redirect('#/tools/firewall');
}
);
});
})();
})();

View file

@ -24,45 +24,36 @@
$('#masthead').show()
.find('.logout-btn').hide();
store.set('path-1', '#/login');
if ($('div.loader').length === 0) {
$('#main').append('<div class="loader loader-content"></div>');
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) {
// Remove loader
$('div.loader').remove();
// Pass domain to hide form field
c.view('login', { 'domain': window.location.hostname });
} else if (typeof isInstalled === 'undefined') {
if (app.isInstalledTry > 0) {
app.isInstalledTry--;
app.loaded = false; // Show pacman
setTimeout(function() {
c.redirect('#/');
}, 5000);
}
else {
// Reset count
app.isInstalledTry = 3;
return;
}
// API is not responding after 3 try
$( document ).ajaxError(function( event, request, settings ) {
// Display error if status != 200.
// .ajaxError fire even with status code 200 because json is sometimes not valid.
if (request.status !== 200) c.flash('fail', y18n.t('api_not_responding', [request.status+' '+request.statusText] ));
// Unbind directly
$(document).off('ajaxError');
});
// Remove pacman
app.loaded = true;
$('div.loader').remove();
}
} else {
$('div.loader').remove();
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'));
}
});
});
@ -80,7 +71,7 @@
var params = {
password: c.params['password']
};
c.api('/login', function(data) {
c.api('POST', '/login', params, function(data) {
store.set('connected', true);
c.trigger('login');
$('#masthead .logout-btn').fadeIn();
@ -90,19 +81,19 @@
} else {
c.redirect('#/');
}
}, 'POST', params, false);
}, undefined, false);
});
app.get('#/logout', function (c) {
c.api('/logout', function (data) {
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');
}, 'GET', {}, false);
}, undefined, false);
});
})();

View file

@ -1,50 +0,0 @@
(function() {
// Get application context
var app = Sammy.apps['#main'];
var store = app.store;
/**
* Monitor
*
*/
// Server monitoring
app.get('#/tools/monitor', function (c) {
var monitorData = {};
// Why this method ?
c.api('/services/glances', function(data) { // ?
monitorData.status = true;
if (data.status == 'running') {
c.api('/monitor/system', function(data) {
monitorData.system = data;
c.api('/monitor/disk', function(data) {
monitorData.disk = data;
c.api('/monitor/network', function(data) {
monitorData.network = data;
// Remove useless interface
delete monitorData.network.usage.lo;
// Get YunoHost versions too
c.api('/diagnosis', function(diagnosis) {
monitorData.versions = diagnosis.packages;
c.view('tools/tools_monitoring', monitorData);
});
});
});
});
}
else {
monitorData.status = false;
c.view('tools/tools_monitoring', monitorData);
}
}, 'GET');
});
})();

View file

@ -13,7 +13,7 @@
$('#masthead').hide();
c.checkInstall(function(isInstalled) {
if (isInstalled || typeof isInstalled === 'undefined') {
c.redirect('#/login');
c.redirect_to('#/login');
} else {
c.view('postinstall/postinstall_1');
}
@ -41,7 +41,6 @@
if ($('#domain').val() === '') {
if ($('#ddomain').val() === '') {
e.preventDefault();
store.clear('slide');
c.flash('fail', y18n.t('error_select_domain'));
} else {
domain = $('#ddomain').val() + $('select[name="ddomain-ext"]').val();
@ -51,7 +50,7 @@
}
store.set('maindomain', domain);
});
}, false); // We disable enableSlide because that causes some issues with accordion when using the 'previous' button
});
});
});
@ -59,49 +58,52 @@
app.get('#/postinstall/password', function(c) {
$('#masthead').hide();
if (!store.get('maindomain')) {
store.clear('slide');
c.redirect('#/postinstall/domain');
c.redirect_to('#/postinstall/domain');
} else {
c.view('postinstall/postinstall_3', { 'domain': store.get('maindomain').toLowerCase() },
function() { },
false); // We disable enableSlide because that causes some issues with accordion when using the 'previous' button
c.view('postinstall/postinstall_3', { 'domain': store.get('maindomain').toLowerCase() });
}
});
// Execute post-installation
app.post('#/postinstall', function (c) {
if (c.params['password'] === '' || c.params['confirmation'] === '') {
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;
}
else if (c.params['password'] == c.params['confirmation']) {
if (c.params['domain'] === '') {
c.flash('fail', y18n.t('error_select_domain'));
store.clear('slide');
c.redirect('#/postinstall/domain');
} else {
var params = {
domain: c.params['domain'].toLowerCase()
};
}
c.confirm(
y18n.t('postinstall'),
y18n.t('confirm_postinstall', [c.params['domain']]),
function(){
params.password = c.params['password'];
store.set('url', window.location.hostname +'/yunohost/api');
store.set('user', 'admin');
c.api('/postinstall', function(data) { // http://api.yunohost.org/#!/tools/tools_postinstall_post_0
c.redirect('#/login');
}, 'POST', params);
},
function(){
}
);
} else {
// 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

@ -10,21 +10,15 @@
// All services status
app.get('#/services', function (c) {
c.api('/services', function(data) { // ?
c.api('GET', '/services', {}, function(data) {
var data2 = {
services: []
};
$.each(data, function(k, v) {
v.name = k;
// Handlebars want booleans
v.is_loaded = (v.loaded=='enabled') ? true : false;
v.is_running = (v.active=='active') ? true : false;
// Translate status and loaded state
v.status = y18n.t(v.status);
v.loaded = y18n.t(v.loaded);
if (v.active_at == 'unknown')
if (v.last_state_change == 'unknown')
{
delete v.active_at;
v.last_state_change = 0;
}
data2.services.push(v);
});
@ -45,28 +39,88 @@
// Status & actions for a service
app.get('#/services/:service', function (c) {
c.api('/services/'+ c.params['service'], function(data) { // ?
var data2 = {
service: data
};
data2.service.name = c.params['service'];
// Handlebars want booleans
data2.service.is_loaded = (data.loaded=='enabled') ? true : false;
data2.service.is_running = (data.active=='active') ? true : false;
// Translate status and loaded state
data2.service.active = y18n.t(data.active);
data2.service.loaded = y18n.t(data.loaded);
if (data.active_at != 'unknown')
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')
{
data2.service.active_at = data.active_at;
data.last_state_change = 0;
}
else
{
data2.service.active_at = 0;
}
store.clear('slide');
c.view('service/service_info', data2);
}, 'GET');
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(),
})
.success(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
@ -74,63 +128,14 @@
var params = {
number: 50
};
c.api('/services/'+ c.params['service'] +'/log', function(data) { // ?
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);
}, 'GET', params);
});
// Enable/Disable & Start/Stop service
app.get('#/services/:service/:action', function (c) {
c.confirm(
"Service",
// confirm_service_start, confirm_service_stop, confirm_service_enable and confirm_service_disable
y18n.t('confirm_service_' + c.params['action'].toLowerCase(), [c.params['service']]),
function(){
var method = null,
endurl = c.params['service'];
switch (c.params['action']) {
case 'start':
method = 'PUT';
break;
case 'stop':
method = 'DELETE';
break;
case 'enable':
method = 'PUT';
endurl += '/enable';
break;
case 'disable':
method = 'DELETE';
endurl += '/enable';
break;
default:
c.flash('fail', y18n.t('unknown_action', [c.params['action']]));
store.clear('slide');
c.redirect('#/services/'+ c.params['service']);
}
if (method && endurl) {
c.api('/services/'+ endurl, function(data) {
store.clear('slide');
c.redirect('#/services/'+ c.params['service']);
}, method);
}
else {
store.clear('slide');
c.redirect('#/services/'+ c.params['service']);
}
},
function(){
store.clear('slide');
c.redirect('#/services/'+ c.params['service']);
}
);
});
});
})();

View file

@ -26,77 +26,69 @@
});
if ($.isEmptyObject(params)) {
c.flash('fail', y18n.t('error_modify_something'));
store.clear('slide');
c.redirect('#/tools/adminpw');
} else if (params['new_password'] !== params['confirm_new_password']) {
c.flash('fail', y18n.t('passwords_dont_match'));
store.clear('slide');
c.redirect('#/tools/adminpw');
} else {
c.api('/login', function(data) {
// Remove useless variable
delete params['old_password'];
delete params['confirm_new_password'];
// Update password and redirect to the home
c.api('/adminpw', function(data) { // http://api.yunohost.org/#!/tools/tools_adminpw_put_3
c.redirect('#/logout');
}, 'PUT', params);
}, 'POST', { 'password': params['old_password'] }, false);
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('/update', function(data) {
c.view('update/update', data);
}, 'PUT');
});
c.api('PUT', '/update', {}, function(data) {
c.view('tools/tools_update', data, function() {
// Configure buttons behaviors
$("button[data-upgrade]").on("click", function() {
// Upgrade apps or packages
app.get('#/upgrade/:type', function (c) {
c.confirm(
y18n.t('tools'),
// confirm_update_apps and confirm_update_packages
y18n.t('confirm_update_' + c.params['type'].toLowerCase()),
function(){
c.api('/upgrade?'+c.params["type"],
function(data) {
store.clear('slide');
c.redirect('#/tools/logs');
},
'PUT');
},
function(){
store.clear('slide');
c.redirect('#/update');
}
);
});
var what = $(this).data("upgrade").toLowerCase();
// Upgrade a specific apps
app.get('#/upgrade/apps/:app', function (c) {
c.confirm(
y18n.t('tools'),
y18n.t('confirm_update_specific_app', [c.params['app']]),
function(){
c.api('/upgrade/apps?app='+c.params['app'].toLowerCase(),
function(data) {
store.clear('slide');
c.redirect('#/tools/logs');
},
'PUT');
},
function(){
store.clear('slide');
c.redirect('#/update');
}
);
// 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("/logs?limit=25&with_details", function(categories) {
c.api('GET', "/logs?limit=25&with_details", {}, function(categories) {
data = [];
category_icons = {
'operation': 'wrench',
@ -137,153 +129,85 @@
app.get(/\#\/tools\/logs\/(.*)(\?number=(\d+))?/, function (c) {
var params = "?path=" + c.params["splat"][0];
var number = (c.params["number"])?c.params["number"]:50;
params += "&number=" + number;
c.api("/logs/display" + params, function(log) {
params += "&filter_irrelevant&number=" + number;
c.api('GET', "/logs/display" + params, {}, function(log) {
if ('metadata' in log) {
if (!'env' in log.metadata && 'args' in log.metadata) {
log.metadata.env = log.metadata.args
}
}
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');
});
});
});
});
});
// Download SSL Certificate Authority
app.get('#/tools/ca', function (c) {
c.view('tools/tools_ca');
});
// Security feed
app.get('#/tools/security-feed', function (c) {
var data = {
items: []
};
// Get security feed and display items
var forumUrl = 'https://forum.yunohost.org';
var securityUrl = 'https://forum.yunohost.org/c/security';
var securityFeed = 'https://yunohost.org/security.rss';
data.url = {
web: securityUrl,
rss: securityFeed
};
$.ajax({
url: securityFeed,
// dataType: (jQuery.browser.msie) ? "text" : "xml",
dataType: "xml"
})
.done(function(xml){
// Loop through items
$('item', xml).each(function(k, v) {
var link = $('link', v)[0].innerHTML;
if (typeof link == 'string' && link !== '' && link.charAt(0) == '/') {
link = forumUrl+link;
}
var description = $('description', v)[0].textContent;
description = description.replace('href="/','href="'+forumUrl+'/');
var item = {
guid: $('guid', v)[0].innerHTML,
title: $('title', v)[0].innerHTML,
url: link,
desc: description,
date: $('pubDate', v)[0].innerHTML.split(' +')[0],
};
data.items.push(item);
});
c.view('tools/tools_security_feed', data);
})
.fail(function() {
c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed]));
});
});
// Reboot or shutdown button
app.get('#/tools/reboot', function (c) {
c.view('tools/tools_reboot');
});
c.view('tools/tools_reboot', {}, function() {
// Configure reboot/shutdown buttons behavior
$("button[data-action]").on("click", function() {
var action = $(this).data("action");
// Reboot or shutdown actions
app.get('#/tools/reboot/:action', function (c) {
var action = c.params['action'].toLowerCase();
if (action == 'reboot' || action == 'shutdown') {
c.confirm(
y18n.t('tools_' + action),
// confirm_reboot_action_reboot or confirm_reboot_action_shutdown
y18n.t('confirm_reboot_action_' + action),
function(){
c.api('/'+action+'?force', function(data) {
// This code is not executed due to 502 response (reboot or shutdown)
c.redirect('#/logout');
}, 'PUT', {}, false, function (xhr) {
c.flash('success', y18n.t('tools_' + action + '_done'))
// Disconnect from the webadmin
store.clear('url');
store.clear('connected');
store.set('path', '#/');
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>');
}
// 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>');
}
// Remove loader if any
$('div.loader').remove();
c.hideLoader();
// Force scrollTop on page load
$('html, body').scrollTop(0);
store.clear('slide');
});
},
function(){
store.clear('slide');
c.redirect('#/tools/reboot');
}
);
}
else {
c.flash('fail', y18n.t('unknown_action', [action]));
store.clear('slide');
c.redirect('#/tools/reboot');
}
});
// Diagnosis
app.get('#/tools/diagnosis(/:private)?', function (c) {
// See http://sammyjs.org/docs/routes for splat documentation
var private = (c.params.splat[0] == 'private');
var endurl = (private) ? '?private' : '';
c.api('/diagnosis'+endurl, function(diagnosis) {
c.view('tools/tools_diagnosis', {
'diagnosis' : JSON.stringify(diagnosis, undefined, 4),
'raw' : diagnosis,
'private' : private
// Force scrollTop on page load
$('html, body').scrollTop(0);
}, false);
});
});
});
});
// Reboot or shutdown button
// Migrations
app.get('#/tools/migrations', function (c) {
c.api('/migrations?pending', function(pending_migrations) {
c.api('/migrations?done', function(done_migrations) {
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;
@ -302,148 +226,39 @@
c.view('tools/tools_migrations', {
'pending_migrations' : pending_migrations.reverse(),
'done_migrations' : done_migrations.reverse()
});
});
});
});
}, function() {
app.get('#/tools/migrations/run', function (c) {
var disclaimerAcks = $(".disclaimer-ack");
var withAcceptDisclaimerFlag = false;
for (var i = 0 ; i < disclaimerAcks.length ; i++)
{
console.log($(disclaimerAcks[i]).find("input:checked").val());
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.redirect('#/tools/migrations');
return;
}
else
{
withAcceptDisclaimerFlag = true;
}
};
// Configure button 'Run'
$('button[data-action="run"]').on("click", function() {
// Not sure if necessary, but this distinction is to avoid accidentally
// triggering a migration with a disclaimer if one goes to the
// /tools/migrations/run page "directly" somehow ...
if (withAcceptDisclaimerFlag)
{
c.api('/migrations/migrate?accept_disclaimer',
function (data) {
store.clear('slide');
c.redirect('#/tools/migrations');
}, 'POST')
}
else
{
c.api('/migrations/migrate',
function (data) {
store.clear('slide');
c.redirect('#/tools/migrations');
}, 'POST')
}
});
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;
}
};
app.get('#/tools/migrations/skip/:migration_id', function (c) {
c.confirm(
y18n.t('migrations'),
y18n.t('confirm_migrations_skip'),
function(){
c.api('/migrations/migrate?skip&targets=' + c.params['migration_id'], function(data) {
store.clear('slide');
c.redirect('#/tools/migrations');
}, 'POST');
},
function(){
store.clear('slide');
c.redirect('#/tools/migrations');
}
);
});
c.api('POST', '/migrations/migrate?accept_disclaimer', {}, function() { c.refresh(); });
});
// List available apps lists
app.get('#/tools/appslists', function (c) {
c.api('/appslists', function(data) {
list = [];
$.each(data, function(listname, listinfo) {
list.push({
'name': listname,
'url': listinfo['url'],
'lastUpdate': listinfo['lastUpdate']
// 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() });
}
);
});
});
c.view('tools/tools_appslists_list', {
appslists: list
});
}, 'GET');
});
// Add a new apps list
app.post('#/tools/appslists', function (c) {
list = {
'name' : c.params['appslist_name'],
'url' : c.params['appslist_url']
}
c.api('/appslists', function(data) {
store.clear('slide');
c.redirect('#/tools/appslists/' + list.name);
}, 'PUT', list);
});
// Show appslist info and operations
app.get('#/tools/appslists/:appslist', function (c) {
c.api('/appslists', function(data) {
if (typeof data[c.params['appslist']] !== 'undefined') {
list = {
'name' : c.params['appslist'],
'url': data[c.params['appslist']]['url'],
'lastUpdate': data[c.params['appslist']]['lastUpdate'],
'removable' : (c.params['appslist'] !== 'yunohost') ? true : false // Do not remove default apps list
};
c.view('tools/tools_appslists_info', {appslist: list});
}
else {
c.flash('warning', y18n.t('appslists_unknown_list', [c.params['appslist']]));
store.clear('slide');
c.redirect('#/tools/appslists');
}
}, 'GET');
});
// Refresh available apps list
app.get('#/tools/appslists/refresh', function (c) {
c.api('/appslists', function(data) {
// c.redirect(store.get('path'));
c.redirect('#/apps/install');
}, 'PUT');
});
// Refresh specific apps list
app.get('#/tools/appslists/:appslist/refresh', function (c) {
c.api('/appslists', function(data) {
c.redirect('#/tools/appslists');
}, 'PUT', {'name' : c.params['appslist']});
});
// Remove apps list
app.get('#/tools/appslists/:appslist/remove', function (c) {
c.confirm(
y18n.t('appslist'),
y18n.t('appslists_confirm_remove', [c.params['app']]),
function() {
c.api('/appslists', function() {
c.redirect('#/tools/appslists');
}, 'DELETE', {'name' : c.params['appslist']});
},
function() {
store.clear('slide');
c.redirect('#/tools/appslists/'+ c.params['appslist']);
}
);
});
});
});
})();

View file

@ -31,20 +31,20 @@
* @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
* 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 operation = params.operation;
var action = params.action;
var item = params.item;
var groupname = params.group;
var group = data.groups[groupname];
var to = (operation == 'add')?group[type]:group[type + 'Inv'];
var from = (operation == 'add')?group[type+'Inv']:group[type];
// Do nothing, if array of destination already contains the item
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
@ -52,22 +52,22 @@
$('#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[operation] = [item];
params[action] = [item];
}
else {
url = '/users/permissions/' + item;
params[operation] = [groupname];
params[action] = [groupname];
}
c.api(url, function(data_update) {
c.api('PUT', url, params, function(data_update) {
to.push(item);
from.splice(from.indexOf(item), 1);
updateView(data);
}, 'PUT', params);
});
}
/**
@ -85,29 +85,41 @@
model.groups[group].members.sort();
model.groups[group].membersInv.sort();
}
// Manual render, we don't use c.view to avoid scrollTop and other
// 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
jQuery(".group-update").on('click', function (e) {
updateGroup(model, jQuery(this)[0].dataset);
$("button[data-action='add'], button[data-action='remove']").on('click', function (e) {
updateGroup(model, $(this)[0].dataset);
return false;
});
jQuery(".group-add-user").on('click', function (e) {
data.groups[$(this)[0].dataset.user].display = true;
$('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('/users/groups?full&include_primary_groups', function(data_groups) {
c.api('/users', function(data_users) {
c.api('/users/permissions?short', function(data_permissions) {
c.api('GET', '/users/groups?full&include_primary_groups', {}, function(data_groups) {
c.api('GET', '/users', {}, function(data_users) {
c.api('GET', '/users/permissions?short', {}, function(data_permissions) {
//var perms = data_permissions.permissions;
var specific_perms = {};
var all_perms = data_permissions.permissions;
@ -129,7 +141,7 @@
// 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 = {
@ -166,33 +178,9 @@
app.post('#/groups/create', function (c) {
c.params['groupname'] = c.params['groupname'].replace(' ', '_').toLowerCase();
c.api('/users/groups', function(data) {
c.redirect('#/groups');
}, 'POST', c.params.toHash());
});
app.get('#/groups/:group/delete', function (c) {
var params = {};
// make confirm content
var confirmModalContent = $('<div>'+ y18n.t('confirm_delete', [c.params['group']]) +'</div>');
// display confirm modal
c.confirm(
y18n.t('groups'),
confirmModalContent,
function(){
c.api('/users/groups/'+ c.params['group'], function(data) {
c.redirect('#/groups');
}, 'DELETE', params);
},
function(){
//store.clear('slide');
c.redirect('#/groups');
}
);
c.api('POST', '/users/groups', c.params.toHash(), function(data) {
c.redirect_to('#/groups');
});
});
/**
@ -202,14 +190,14 @@
// List existing users
app.get('#/users', function (c) {
c.api('/users', function(data) { // http://api.yunohost.org/#!/user/user_list_get_3
c.api('GET', '/users', {}, function(data) {
c.view('user/user_list', data);
});
});
// Create user form
app.get('#/users/create', function (c) {
c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_list_get_2
c.api('GET', '/domains', {}, function(data) {
// Password min length
data.password_min_length = PASSWORD_MIN_LENGTH;
@ -229,8 +217,7 @@
app.post('#/users/create', function (c) {
if (c.params['password'] == c.params['confirmation']) {
if (c.params['password'].length < PASSWORD_MIN_LENGTH) {
c.flash('fail', y18n.t('password_too_short'));
store.clear('slide');
c.flash('fail', y18n.t('passwords_too_short'));
}
else {
// Force unit or disable quota
@ -242,28 +229,63 @@
// Compute email field
c.params['mail'] = c.params['email'] + c.params['domain'];
c.api('/users', function(data) { // http://api.yunohost.org/#!/user/user_create_post_2
c.redirect('#/users');
}, 'POST', c.params.toHash());
c.api('POST', '/users', c.params.toHash(), function(data) {
c.redirect_to('#/users');
});
}
} else {
c.flash('fail', y18n.t('passwords_dont_match'));
store.clear('slide');
//c.redirect('#/users/create');
}
});
// Show user information
app.get('#/users/:user', function (c) {
c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_info_get_0
c.view('user/user_info', data);
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('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_info_get_0
c.api('/domains', function(dataDomains) { // http://api.yunohost.org/#!/domain/domain_list_get_2
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;
@ -314,7 +336,7 @@
// Update user information
app.put('#/users/:user', function (c) {
// Get full user object
c.api('/users/'+ c.params['user'], function(user) {
c.api('GET', '/users/'+ c.params['user'], {}, function(user) {
// Force unit or disable quota
if (c.params['mailbox_quota']) {
c.params['mailbox_quota'] += "M";
@ -353,79 +375,32 @@
if ($.isEmptyObject(params)) {
c.flash('fail', y18n.t('error_modify_something'));
store.clear('slide');
c.redirect('#/users/'+ c.params['user'] + '/edit');
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('password_too_short'));
store.clear('slide');
c.redirect('#/users/'+ c.params['user'] + '/edit');
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('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_update_put_1
c.redirect('#/users/'+ c.params['user']);
}, 'PUT', params);
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'));
store.clear('slide');
c.redirect('#/users/'+ c.params['user'] + '/edit');
c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false});
}
}
else {
c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_update_put_1
c.redirect('#/users/'+ c.params['user']);
}, 'PUT', params);
c.api('PUT', '/users/'+ c.params['user'], params, function(data) {
c.redirect_to('#/users/'+ c.params['user']);
});
}
}
}, 'GET');
});
// Remove existing user
app.get('#/users/:user/delete', function (c) {
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', [c.params['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', [c.params['user']]) +'<br><br>'+ purgeCheckbox +'<br>'+ purgeAlertMessage +'</div>');
// display confirm modal
c.confirm(
y18n.t('users'),
confirmModalContent,
function(){
c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_delete_delete_4
c.redirect('#/users');
}, 'DELETE', params);
},
function(){
store.clear('slide');
c.redirect('#/users/'+ c.params['user']);
}
);
// toggle purge warning and parameter
confirmModalContent.find("input").click(function(){
if (confirmModalContent.find("input").is(':checked')) {
params.purge = "";
confirmModalContent.find(".warning").show();
}
else {
delete params.purge;
confirmModalContent.find(".warning").hide();
};
});
});
})();

View file

@ -8,82 +8,15 @@
*
*/
app.bind('login', function(e, data) {
c.api('/users', function(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'));
}
/*
* Disabling this for now because there's a duplicate Access Allow
* Origin header thing preventing it from working and people keep
* complaining about it and we havent effectively used this in 2
* years anyway despite various security issues.
*
*
// Get security feed and display new items
var securityFeed = 'https://yunohost.org/security.rss';
var forumUrl = 'https://forum.yunohost.org';
$.ajax({
url: securityFeed,
// dataType: (jQuery.browser.msie) ? "text" : "xml",
dataType: "xml"
})
.done(function(xml){
// Get viewed security alerts from cookie
var viewedItems = Cookies.get('ynhSecurityViewedItems') || [];
// Get 6 month earlier date
var SixMonthEarlier = new Date();
SixMonthEarlier.setMonth(SixMonthEarlier.getMonth() - 6);
// Loop through items in a reverse order (older first)
$($('item', xml).get().reverse()).each(function(k, v) {
var link = $('link', v).text();
if (typeof link == 'string' && link !== '' && link.charAt(0) == '/') {
link = forumUrl+link;
}
// var description=$('description', v).text();
// description=description.replace('href="/','href="'+forumUrl+'/');
var item = {
guid: $('guid', v).text(),
title: $('title', v).text(),
url: link,
// desc: description,
date: new Date($('pubDate', v).text()),
};
// If item is not already viewed and is not older than 6 month
if (viewedItems.indexOf(item.guid) === -1 && (item.date.getTime() > SixMonthEarlier.getTime())) {
// Show security message to administrator
var warning = item.title + ' - ' +
item.date.toISOString().substring(0, 10) +
' (<a href="'+ item.url +'" class="alert-link" target="_blank">'+y18n.t('read_more')+'</a>)';
c.flash('warning', warning);
// Store viewed item
viewedItems.push(item.guid);
}
});
// Saved viewed items to cookie
Cookies.set('ynhSecurityViewedItems', viewedItems, {
expires: 7
});
})
.fail(function(stuff) {
c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed]));
});
*/
c.api("/diagnosis", function(data) {
versions = data.packages;
$('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo]));
if (data.security["CVE-2017-5754"].vulnerable) {
c.flash('danger', y18n.t('meltdown'));
}
$('div.loader').remove();
c.api('GET', '/versions', {}, function(data) {
$('#yunohost-version').html(y18n.t('footer_version', [data.yunohost.version, data.yunohost.repo]));
c.hideLoader();
});
});
});

View file

@ -12,7 +12,7 @@
function prefetchDomains(req) {
// Preload domains list.
req.params.domains = [];
req.api('/domains', function(data) {
req.api('GET', '/domains', {}, function(data) {
req.params.domains = data.domains;
});
}
@ -20,7 +20,7 @@
function prefetchUsers(req){
// Preload users lists.
req.params.users = [];
req.api('/users', function(data) {
req.api('GET', '/users', {}, function(data) {
req.params.users = data.users;
});
}
@ -28,6 +28,8 @@
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);

View file

@ -3,20 +3,64 @@
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({
// Serialize an object
serialize : function(obj) {
var str = [];
for(var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
//
// 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>');
}
return str.join("&");
},
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
@ -85,7 +129,7 @@
},
// API call
api: function(uri, callback, method, data, websocket, callbackOnFailure) {
api: function(method, uri, data, callback, callbackOnFailure, websocket) {
c = this;
method = typeof method !== 'undefined' ? method : 'GET';
@ -93,96 +137,69 @@
if (window.navigator && window.navigator.language && (typeof data.locale === 'undefined')) {
data.locale = y18n.locale || window.navigator.language.substr(0, 2);
}
app.loaded = false;
if ($('div.loader').length === 0) {
$('#main').append('<div class="loader loader-content"></div>');
}
c.showLoader();
call = function(uri, callback, method, data, callbackOnFailure) {
var args = data;
// TODO: change this code
if (uri === '/postinstall') {
var post_installing = false;
setInterval(function () {
post_installing = true;
}, 1500);
}
// Define default callback for failures
if (typeof callbackOnFailure !== 'function') {
callbackOnFailure = function(xhr) {
// Postinstall is a custom case, we have to wait that
// operation is done before doing anything
if ((uri === '/postinstall') && (post_installing)) {
interval = window.location.hostname === args.domain ? 20000 : 5000;
checkInstall = setInterval(function () {
c.checkInstall(function(isInstalled) {
if (isInstalled || typeof isInstalled === 'undefined') {
c.flash('success', y18n.t('installation_complete'));
clearInterval(checkInstall);
window.location.href = 'https://'+ window.location.hostname +'/yunohost/admin/';
}
});
}, interval);
if (xhr.status == 200) {
// Fail with 200, WTF
callback({});
}
// Regular errors
// 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 {
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);
}
// Remove loader if any
$('div.loader').remove();
// Force scrollTop on page load
$('html, body').scrollTop(0);
store.clear('slide');
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');
};
}
@ -237,78 +254,17 @@
},
// Render view (cross-browser)
view: function (view, data, callback, enableSlide) {
// Ask confirmation to the user through the modal window
confirm: function(title, content, confirmCallback, cancelCallback) {
c = this;
// Default
callback = typeof callback !== 'undefined' ? callback : function() {};
enableSlide = (typeof enableSlide !== 'undefined') ? enableSlide : true; // Change to false to disable animation
app.loaded = true;
// Hide loader and modal
$('div.loader').remove();
$('#modal').modal('hide');
// Render content
var rendered = this.render('views/'+ view +'.ms', data);
// Update content helper
var leSwap = function() {
rendered.swap(function() {
// Slide direction
if (enableSlide) {
$('.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');
}
});
}
// Paste <pre> helper
c.prePaste();
// Run callback
callback();
// Force scrollTop on page load
$('html, body').scrollTop(0);
});
};
// Slide back effect
if (enableSlide && store.get('slide') == 'back') {
store.clear('slide');
$('#slideBack').css('display', 'none');
$('#slider-container').css('margin-left', '-100%');
$('#slideTo').show().html($('#main').html());
leSwap();
$('#slider-container').css('margin-left', '0px');
}
// Slide to effect
else if (enableSlide && store.get('slide') == 'to') {
store.clear('slide');
$('#slideTo').css('display', 'none');
$('#slider-container').css('margin-left', '0px');
$('#slideBack').show().html($('#main').html());
leSwap();
$('#slider-container').css('margin-left', '-100%');
}
// No slideing effect
else {
leSwap();
}
},
confirm: function(title, content, confirmCallback, cancelCallback) {
// Default callbacks
confirmCallback = typeof confirmCallback !== 'undefined' ? confirmCallback : function() {};
cancelCallback = typeof cancelCallback !== 'undefined' ? cancelCallback : function() {};
c.hideLoader();
// Get modal element
var box = $('#modal');
@ -335,12 +291,10 @@
$('#modal footer button').unbind( "click" );
// Reset & Hide modal
box
.removeClass('no-title')
.modal('hide');
box.removeClass('no-title').modal('hide');
// Do corresponding callback
if ($(this).data('action') == 'confirm') {
if ($(this).data('modal-action') == 'confirm') {
confirmCallback();
}
else {
@ -352,19 +306,162 @@
return box.modal('show');
},
selectAllOrNone: function () {
// 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);
});
// 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('views/'+ view +'.ms', 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) {
@ -385,103 +482,15 @@
});
},
groupHooks: function(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;
},
ungroupHooks: function(system_parts,apps) {
var data = {};
data['apps'] = apps || [];
data['system'] = system_parts || [];
if (data['system'].constructor !== Array) {
data['system'] = [data['system']];
// Serialize an object
serialize : function(obj) {
var str = [];
for(var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
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;
},
// Paste <pre>
prePaste: function() {
var pasteButtons = $('button[data-paste-content],a[data-paste-content]');
pasteButtons.on('click', function(){
// Get paste content element
var preElement = $($(this).data('paste-content'));
// Add pacman loader
$('#main').append('<div class="loader loader-content"></div>');
// Send to paste.yunohost.org
$.ajax({
type: "POST",
url: 'https://paste.yunohost.org/documents',
data: preElement.text(),
})
.success(function(data, textStatus, jqXHR) {
window.open('https://paste.yunohost.org/' + data.key, '_blank');
})
.fail(function() {
c.flash('fail', y18n.t('paste_error'));
})
.always(function(){
// Remove pacman
$('div.loader').remove();
});
});
return str.join("&");
}
});
});
})();

View file

@ -181,9 +181,8 @@
sam.store.set('url', window.location.hostname + '/yunohost/api');
if (sam.store.get('connected')) {
this.api('/diagnosis', function(diagnosis) {
versions = diagnosis.packages;
$('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo]));
this.api('GET', '/versions', {}, function(data) {
$('#yunohost-version').html(y18n.t('footer_version', [data.yunohost.version, data.yunohost.repo]));
});
}

View file

@ -249,7 +249,7 @@
"select_user": "Seleccioneu un usuari",
"service_description": "Descripció:",
"service_log": "Registre %s",
"service_start_on_boot": "Iniciar a l'engegada: ",
"service_start_on_boot": "Iniciar a l'engegada",
"service_status": "Estat: ",
"services": "Serveis",
"services_list": "Llista de serveis",
@ -266,11 +266,11 @@
"swap": "Memòria d'intercanvi",
"system": "Sistema",
"system_apps": "Aplicacions",
"system_apps_nothing": "No hi ha aplicacions per actualitzar.",
"system_apps_nothing": "Totes les aplicacions estan actualitzades!",
"system_delayed_upgrade": "S'ha posposat l'actualització",
"system_delayed_upgrade_warning": "<b>%s</b> serà actualitzat automàticament durant la pròxima hora.",
"system_packages": "Paquets",
"system_packages_nothing": "No hi ha paquets per actualitzar.",
"system_packages": "Paquets del sistema",
"system_packages_nothing": "Tots els paquets del sistema estan actualitzats!",
"system_update": "Actualització del sistema",
"system_upgrade": "Actualització del sistema",
"system_upgrade_btn": "Actualització",
@ -354,7 +354,7 @@
"validity": "Validesa",
"domain_is_eligible_for_ACME": "Aquest domini sembla estar configurat correctament per instal·lar el certificat Let's Encrypt!",
"run": "Executar",
"domain_not_eligible_for_ACME": "Aquest domini sembla que no està configurat per a un certificat Let's Encrypt. Comprova la configuració DNS i la accessibilitat del servidor HTTP.",
"domain_not_eligible_for_ACME": "Aquest domini sembla que no està configurat per a un certificat Let's Encrypt. Comprova la configuració DNS i la accessibilitat del servidor HTTP. Les seccions «registres DNS» i «Web» de <a href='#/diagnosis'>la pàgina de diagnòstic</a> pot ajudar-vos a entendre el que està mal configurat.",
"install_letsencrypt_cert": "Instal·la un certificat Let's Encrypt",
"manually_renew_letsencrypt_message": "El certificat es renovarà automàticament durant els últims 15 dies de validesa. El podeu renovar manualment si ho desitgeu. (No recomanat).",
"manually_renew_letsencrypt": "Renovar manualment ara",
@ -471,5 +471,13 @@
"app_state_low_quality": "baixa qualitat",
"all": "Tot",
"run_first_diagnosis": "Executa el diagnòstic inicial",
"diagnosis_first_run": "La funció de diagnòstic intentarà identificar problemes habituals de diferents aspectes del servidor per tal d'assegurar que tot funcioni de la millor manera possible. No tingueu por si veieu uns quants errors just després de configurar el servidor: està precisament fet per ajudar a identificar els problemes i oferir una guia de com arreglar-los. El diagnòstic també s'executarà dues vegades al dia i enviarà un correu a l'administrador si apareix algun error."
"diagnosis_first_run": "La funció de diagnòstic intentarà identificar problemes habituals de diferents aspectes del servidor per tal d'assegurar que tot funcioni de la millor manera possible. No tingueu por si veieu uns quants errors just després de configurar el servidor: està precisament fet per ajudar a identificar els problemes i oferir una guia de com arreglar-los. El diagnòstic també s'executarà dues vegades al dia i s'enviarà un correu a l'administrador si es troben errors.",
"confirm_service_restart": "Esteu segur de voler reiniciar %s?",
"group_explain_visitors_needed_for_external_client": "Vigileu ja que s'ha de permetre els visitants en algunes aplicacions si voleu utilitzar-les amb clients externs. És el cas, per exemple, de Nextcloud si voleu utilitzar el client de sincronització en el telèfon mòbil o en l'ordinador.",
"groups": "Grups",
"restart": "Reiniciar",
"unmaintained_details": "Fa temps que no es manté aquesta aplicació i la persona que la mantenia ja no ho fa o no té temps per fer-ho. Us convidem a mirar el repositori de l'aplicació per oferir la vostra ajuda",
"issues": "%s problemes",
"operation_failed_explanation": "Aquesta operació ha fallat! Ens sap molt greu :( Podeu intentar <a href='https://yunohost.org/help'>demanar ajuda</a>. Si us plau doneu *el registre complet* de l'operació a la gent que intenta ajudar-vos. Podeu fer-ho fent clic al botó verd \"Compartir amb Yunopaste\". Quan compartiu els registres, YunoHost intentarà anonimitzar automàticament dades privades com els noms de domini i les IPs.",
"diagnosis_explanation": "La funció de diagnòstic intentarà identificar els errors més comuns en diferents aspectes del servidor per verificar que tot funciona correctament. El diagnòstic s'executa automàticament dues vegades al dia i s'envia un correu electrònic a l'administrador si es troben errors. Tingueu en compte que no tots els tests seran rellevants si no s'utilitzen algunes funcions específiques (com per exemple XMPP) o pot ser que falli si teniu un sistema amb una configuració complexa. En aquests casos, i si sabeu el que feu, podeu ignorar els problemes o avisos corresponents."
}

View file

@ -3,7 +3,7 @@
"add": "Hinzufügen",
"administration_password": "Verwaltungspasswort",
"allowed_users": "Zugelassene Benutzer",
"api_not_responding": "API antwortet nicht",
"api_not_responding": "Die YunoHost-API antwortet nicht. Vielleicht ist 'yunohost-api' ausgefallen oder wurde neu gestartet?",
"app_access": "Zugriffsrechte",
"app_access_addall_btn": "Zugriff für alle zulassen",
"app_access_addall_desc": "Alle existierenden Benutzer werden Zugriff auf %s haben.",
@ -14,7 +14,7 @@
"app_access_title": "%s Zugriffsrechte",
"app_debug_no_logs": "Anwendungslogs stehen nicht zur Verfügung",
"app_debug_tab": "Debugging Informationen anzeigen",
"app_info_access_desc": "Zugriffsrechte verwalten. Erlaubte Nutzer: %s",
"app_info_access_desc": "Gruppen / Benutzer, die auf diese App zugreifen dürfen:",
"app_info_debug_desc": "Debugging Informationen für diese Applikation anzeigen.",
"app_info_default_desc": "Hauptdomain auf diese App (%s) weiterleiten.",
"app_info_uninstall_desc": "Diese App löschen.",
@ -120,7 +120,7 @@
"hook_data_home": "Benutzerdaten",
"hook_data_home_desc": "Die Daten des Benutzers werden gespeichert unter /home/USER",
"hook_data_mail": "E-Mail",
"hook_data_mail_desc": "E-Mail Adressen auf dem Server",
"hook_data_mail_desc": "Roth-E-Mails auf dem Server gespeichert",
"hostname": "Hostname",
"id": "ID",
"inactive": "Inaktiv",
@ -143,7 +143,7 @@
"local_ip": "Lokale IP",
"log": "Log",
"logged_in": "Angemeldet",
"logged_out": "Ausgeloggt",
"logged_out": "Abgemeldet",
"login": "Anmelden",
"logout": "Abmelden",
"mailbox_quota_description": "Zum Beispiel, eine CD verfügt über 700M, eine über 4700M.",
@ -202,7 +202,7 @@
"save": "Speichern",
"select_user": "Benutzer auswählen",
"service_log": "%s Log",
"service_start_on_boot": "Beim Hochfahren starten: ",
"service_start_on_boot": "Beim Hochfahren starten",
"service_status": "Status: ",
"services": "Dienste",
"services_list": "Dienstübersicht",
@ -218,11 +218,11 @@
"swap": "Auslagerungsspeicher",
"system": "System",
"system_apps": "Apps",
"system_apps_nothing": "Es gibt keine Aktualisierungen für deine Apps.",
"system_apps_nothing": "Allen Apps sind auf dem neuesten Stand!",
"system_delayed_upgrade": "Zurückgestellte Aktualisierung",
"system_delayed_upgrade_warning": "<b>%s</b> wird automatisch innerhalb der nächsten Stunde aktualisiert.",
"system_packages": "Pakete",
"system_packages_nothing": "Keine Pakete zur Aktualisierung gefunden.",
"system_packages": "Systempakete",
"system_packages_nothing": "Alle Systempakete sind auf dem neuesten Stand!",
"system_update": "System aktualisieren",
"system_upgrade": "Systemaktualisierung",
"system_upgrade_btn": "Aktualisieren",
@ -347,11 +347,11 @@
"tools_reboot": "Starte deine Server neu",
"tools_reboot_btn": "Neustart",
"tools_reboot_done": "Starte neu...",
"tools_rebooting": "Dein Server startet neu. Um zur Verwaltungsoberfläche zurückzukehren, musst du warten bis der Server hochgefahren ist. Feststellen kannst du es, in dem du die Seite neu lädst.",
"tools_rebooting": "Dein Server startet neu. Um zur Verwaltungsoberfläche zurückzukehren, musst du warten bis der Server hochgefahren ist. Prüfen kannst du es, in dem du die Seite neu lädst (F5).",
"tools_shutdown": "Fahre deinen Server herunter",
"tools_shutdown_btn": "Herunterfahren",
"tools_shutdown_done": "Fahre herunter...",
"tools_shuttingdown": "Dein Server wird heruntergefahren. Solange dein Server ausgeschaltet ist, kannst du das Verwaltungsoberfläche nicht benutzen.",
"tools_shuttingdown": "Dein Server wird heruntergefahren. Solange dein Server ausgeschaltet ist, kannst du die Verwaltungsoberfläche nicht benutzen.",
"tools_shutdown_reboot": "Herunterfahren/Neustarten",
"appslists": "Applikationslisten",
"appslists_no_lists": "Keine Applikationslisten",
@ -472,5 +472,13 @@
"others": "Anderes",
"catalog": "Katalog",
"app_state_low_quality": "geringe Qualität",
"all": "Alle"
"all": "Alle",
"confirm_service_restart": "Bist du sicher, dass du %s neustarten möchtest?",
"run_first_diagnosis": "Initiale Diagnose läuft",
"diagnosis_first_run": "Die Diagnose Funktion wird versuchen, gängige Probleme in verschiedenen Teilen deines Servers zu finden, damit alles reibungslos läuft. Bitte werde nicht panisch, wenn du ein paar Fehlermeldungen siehst nachdem du deinen Server aufgesetzt hast: es soll versuchen dir zu helfen, Probleme zu identifizieren und Tipps für Lösungen zu zeigen. Die Diagnose wird auch automatisch zweimal täglich ausgeführt, falls Fehler gefunden werden, bekommt der Administrator ein E-Mail.",
"unmaintained_details": "Diese Anwendung wurde seit einiger Zeit nicht gewartet und der frühere Wart hat die Anwendung aufgegeben oder hat keine Zeit mehr für die Wartung. Du bist herzlich eingeladen dir die Quellen und Unterlagen anzusehen und Hilfe zu leisten",
"group_explain_visitors_needed_for_external_client": "Sei vorsichtig und beachte, dass du manche Anwendungen für externe Besucher freigeben musst, falls du beabsichtigst, diese mit externen Clients aufzurufen. Zum Beispiel trifft das auf Nextcloud zu, wenn du eine Synchronisation auf dem Smartphone oder Desktop PC haben möchtest.",
"groups": "Gruppen",
"issues": "%s Probleme",
"restart": "Neustart"
}

View file

@ -3,63 +3,51 @@
"active": "Active",
"add": "Add",
"advanced": "Advanced",
"remove": "Remove",
"administration_password": "Administration password",
"all": "All",
"all_apps": "All apps",
"api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
"app_change_label": "Change Label",
"app_change_url": "Change URL",
"app_debug_no_logs": "Application's logs are not available",
"app_debug_tab": "Display debug information",
"app_info_access_desc": "Groups / users currently allowed to access this app:",
"app_info_changelabel_desc": "Change app label in the portal.",
"app_info_debug_desc": "Display debugging information for this application.",
"app_info_default_desc": "Redirect domain root to this application (%s).",
"app_info_changeurl_desc": "Change the access URL of this application (domain and/or path).",
"app_info_change_url_disabled_tooltip": "This feature hasn't been implemented in this app yet",
"app_info_uninstall_desc": "Remove this application.",
"app_install_cancel": "Installation cancelled.",
"app_install_custom_no_manifest": "No manifest.json file",
"app_list": "App list",
"app_level": "App level",
"app_make_default": "Make default",
"app_no_actions": "This application doesn't have any actions",
"app_repository": "Application origin: ",
"app_state": "Application state: ",
"app_state_inprogress": "in progress",
"app_state_inprogress": "not yet working",
"app_state_inprogress_explanation": "This maintainer of this app declared that this application is not ready yet for production use. BE CAREFUL!",
"app_state_notworking": "not working",
"app_state_notworking_explanation": "This maintainer of this app declared it as 'not working'. IT WILL BREAK YOUR SYSTEM!",
"app_state_low_quality": "low quality",
"app_state_low_quality_explanation": "This app may be functional, but may still contain issues, or is not fully integrated with YunoHost, or it does not respect the good practices.",
"app_state_high-quality": "high quality",
"app_state_high-quality_explanation": "This app is well-integrated with YunoHost. It has been (and is!) peer-reviewed by the YunoHost app team. It can be expected to be safe and maintained on the long-term.",
"app_state_working": "working",
"app_state_working_explanation": "The maintainer of this app declared it as 'working'. It means that it should be functional (c.f. application level) but is not necessarily peer-reviewed, it may still contain issues or is not fully integrated with YunoHost.",
"application": "Application",
"applications": "Applications",
"archive_empty": "Empty archive",
"available": "Available",
"available_apps": "Available apps",
"backup": "Backup",
"backup_action": "Backup",
"backup_archive_copy": "Copy this archive on another storage",
"backup_archive_delete": "Delete this archive",
"backup_archive_download": "Download this archive",
"backup_content": "Backup content",
"backup_create": "Create a backup",
"backup_encryption_warning": "Don't forget this password, you'll need it if you want restore the archive",
"backup_new": "New backup",
"backup_optional_encryption": "Optional encryption",
"backup_optional_password": "Optional password",
"backup_type": "Type",
"backups_no": "No backup",
"begin": "Begin",
"bit_rate": "Bit rate",
"both": "Both",
"cancel": "Cancel",
"catalog": "Catalog",
"check": "Check",
"check_mx": "MX record",
"check_stmp": "port 25 access",
"close": "Close",
"configuration": "Configuration",
"confirm_app_change_url": "Are you sure you want to change the app access URL?",
"confirm_app_default": "Are you sure you want to make this app default?",
"confirm_change_maindomain": "Are you sure you want to change the main domain?",
@ -73,10 +61,9 @@
"confirm_migrations_skip": "Skipping migrations is not recommended. Are you sure you want to do that?",
"confirm_postinstall": "You are about to launch the post-installation process on the domain %s. It may take a few minutes, *do not interrupt the operation*.",
"confirm_restore": "Are you sure you want to restore %s?",
"confirm_service_restart": "Are you sure you want to restart %s?",
"confirm_service_start": "Are you sure you want to start %s?",
"confirm_service_stop": "Are you sure you want to stop %s?",
"confirm_service_enable": "Are you sure you want to enable %s?",
"confirm_service_disable": "Are you sure you want to disable %s?",
"confirm_uninstall": "Are you sure you want to uninstall %s?",
"confirm_update_apps": "Are you sure you want to update all applications?",
"confirm_update_system": "Are you sure you want to update all system packages?",
@ -86,25 +73,21 @@
"confirm_reboot_action_reboot": "Are you sure you want to reboot your server?",
"confirm_reboot_action_shutdown": "Are you sure you want to shutdown your server?",
"connection": "Connection",
"copy": "Copy",
"count_min": "%s min",
"cpu_load": "CPU Load",
"created_at": "Created at",
"cumulative_usage": "Cumulative usage",
"current_maintainer_title": "Current maintainer of this package",
"custom_app_install": "Install custom app",
"custom_app_url_only_github": "Currently only from GitHub",
"default": "Default",
"delete": "Delete",
"description": "Description",
"details": "Details",
"domain_dns_conf_is_just_a_recommendation": "This page shows you the *recommended* configuration. It does *not* configure the DNS for you. It is your responsability to configure your DNS zone in your DNS registrar according to this recommendation.",
"diagnosis": "Diagnosis",
"diagnosis_hide_private": "Show diagnostic information without private data",
"diagnosis_view_private": "Show diagnostic information including private data",
"diagnosis_with_private": "Diagnosis with private data",
"diagnosis_experimental_disclaimer": "Be aware that the diagnosis feature is still experimental and being polished, and it may not be fully reliable.",
"diagnosis_first_run": "The diagnosis feature will attempt to identify common issues on the different aspects of your server to make sure everything runs smoothly. Please do not panic if you see a bunch of errors right after setting up your server: it is precisely meant to help you to identify issues and guide you to fix them. The diagnosis will also run automatically twice a day and an email is sent to the administrator if issues are found.",
"diagnosis_explanation": "The diagnosis feature will attempt to identify common issues on the different aspects of your server to make sure everything runs smoothly. The diagnosis runs automatically twice a day and an email is sent to the administrator if issues are found. Note that some tests may not be relevant if you do not want to use some specific features (for example XMPP) or may fail if you have a complex setup. In such cases, and if you know what you are doing, it is alright to ignore the corresponding issues or warnings.",
"run_first_diagnosis": "Run initial diagnosis",
"disable": "Disable",
"disabled": "Disabled",
"disk": "Disk",
"dns": "DNS",
"domain": "Domain",
"domain_add": "Add domain",
@ -112,36 +95,28 @@
"domain_add_dyndns_doc": "… and I want a dynamic DNS service.",
"domain_add_panel_with_domain": "I already have a domain name…",
"domain_add_panel_without_domain": "I don't have a domain name…",
"domain_default": "Default domain",
"domain_default_desc": "The default domain is the connection domain where users log in.",
"domain_default_longdesc": "This is your default domain.",
"domain_delete_longdesc": "Delete this domain",
"domain_dns_config": "DNS configuration",
"domain_dns_longdesc": "View DNS configuration",
"domain_list": "Domain list",
"domain_name": "Domain name",
"domain_select": "Select domain",
"domain_visit": "Visit",
"domain_visit_url": "Visit %s",
"domains": "Domains",
"download": "Download",
"enable": "Enable",
"enabled": "Enabled",
"error_modify_something": "You should modify something",
"error_retrieve_feed": "Could not retrieve feed: %s. You might have a plugin prevent your browser from performing this request (or the website is down).",
"error_select_domain": "You should indicate a domain",
"error_server": "Server error",
"error_server_unexpected": "Unexpected server error (%s)",
"error_connection_interrupted": "The server closed the connection instead of answering it. Has nginx or the yunohost-api been restarted or stoppted for some reason? (Error code/message: %s)",
"everything_good": "Everything good!",
"experimental_warning": "Warning: this feature is experimental and not consider stable, you shouldn't be using it except if you know what you are doing.",
"filesystem": "Filesystem",
"firewall": "Firewall",
"footer_version": "Powered by <a href='https://yunohost.org'>YunoHost</a> %s (%s).",
"form_input_example": "Example: %s",
"free": "Free",
"from_to": "from %s to %s",
"fs_type": "FS Type",
"gateway": "Gateway: ",
"good_practices_about_admin_password": "You are now about to define a new admin password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
"group": "Group",
@ -156,6 +131,7 @@
"group_explain_visitors": "This is a special group representing anonymous visitors",
"group_explain_visitors_needed_for_external_client": "Be careful that you need to keep some applications allowed to visitors if you intend to use them with external clients. For example, this is the case for Nextcloud if you want intend to use a synchronization client on your smartphone or desktop computer.",
"group_specific_permissions": "User specific permissions",
"groups": "Groups",
"groups_and_permissions": "Groups and permissions",
"groups_and_permissions_manage": "Manage groups and permissions",
"permissions": "Permissions",
@ -175,8 +151,9 @@
"hook_data_home_desc": "User data located in /home/USER",
"hook_data_mail": "Mail",
"hook_data_mail_desc": "Raw emails stored on the server",
"hostname": "Hostname",
"id": "ID",
"ignore": "Ignore",
"ignored": "%s ignored",
"inactive": "Inactive",
"infos": "Info",
"install": "Install",
@ -184,21 +161,16 @@
"install_time": "Install time",
"installation_complete": "Installation complete",
"installed": "Installed",
"installed_apps": "Installed apps",
"installing": "Installing",
"interface": "Interface",
"internal_exception": "<strong>Yunohost encountered an internal error:/</strong><br><em>Really sorry about that.<br>You should look for help on <a href=\"https://forum.yunohost.org/\">the forum</a> or <a href=\"https://chat.yunohost.org/\">the chat</a> to fix the situation, or report the bug on <a href=\"https://github.com/YunoHost/issues\">the bugtracker</a>.</em><br>The following information might be useful for the person helping you:<h3>Action</h3><pre>%s%s</pre><h3>Traceback</h3><pre>%s</pre>",
"io": "I/O",
"ipv4": "IPv4",
"ipv6": "IPv6",
"issues": "%s issues",
"label": "Label",
"label_for_manifestname": "Label for %s",
"level": "level",
"last_ran": "Last time ran:",
"license": "License",
"loading": "Loading …",
"local_archives": "Local archives",
"local_ip": "Local IP",
"log": "Log",
"logged_in": "Logged in",
"logged_out": "Logged out",
"login": "Login",
@ -210,49 +182,35 @@
"manage_apps": "Manage apps",
"manage_domains": "Manage domains",
"manage_users": "Manage users",
"memory": "Memory",
"menu": "Menu",
"migrations": "Migrations",
"migrations_pending": "Pending migrations",
"migrations_done": "Previous migrations",
"migrations_no_pending": "No pending migrations",
"migrations_no_done": "No previous migrations",
"mode": "Mode",
"monitoring": "Monitoring",
"monitoring_check_glances": "Check <a href='#/services/glances'>glances</a> service status.",
"monitoring_disabled": "Monitoring is not enabled.",
"mount_point": "Mount point",
"multi_instance": "Multi instance",
"myserver": "myserver",
"myserver_org": "myserver.org",
"network": "Network",
"next": "Next",
"no": "No",
"no_installed_apps": "No installed apps.",
"no_log": "No log.",
"nobody": "Nobody",
"non_compatible_api": "Non-compatible API",
"ok": "OK",
"only_highquality_apps": "Only high-quality apps",
"only_working_apps": "Only working apps",
"only_decent_quality_apps": "Only decent quality apps",
"open": "Open",
"operations": "Operations",
"orphaned": "not maintained",
"orphaned_details": "This app is not maintained anymore. It may still be working but won't receive any upgrade. Feel free to come and revive it!",
"os": "OS",
"orphaned": "Not maintained",
"orphaned_details": "This app has not been maintained for quite some time. It may still be working, but won't receive any upgrade until somebody volunteers to take care of it. Feel free to contribute to revive it!",
"others": "Others",
"operation_failed_explanation": "This operation failed! Really sorry about that :( You can try to <a href='https://yunohost.org/help'>ask for help</a>. Please provide *the full log* of the operation to the people helping you. You can do so by clicking on the 'Share with Yunopaste' green button. When sharing the logs, YunoHost will automatically attempt to anonymize private data like domain names and IPs.",
"password": "Password",
"password_confirmation": "Password confirmation",
"password_description": "Password must be at least %s characters long.",
"password_empty": "The password field is empty",
"password_new": "New password",
"passwords_dont_match": "Passwords don't match",
"passwords_too_short": "Password is too short",
"path": "Path",
"diagnosis": "Diagnosis",
"diagnosis_with_private": "Diagnosis with private data",
"diagnosis_view_private": "Show diagnosis with private data",
"diagnosis_hide_private": "Show diagnosis without private data",
"logs": "Logs",
"logs_operation": "Operations made on system with YunoHost",
"logs_history": "History of command run on system",
@ -262,7 +220,6 @@
"logs_service": "Services logs",
"logs_app": "Apps logs",
"logs_no_logs_registered": "No log registered for this category",
"logs_end_with_error": "This log finished with the error:",
"logs_error": "Error",
"logs_ended_at": "End",
"logs_started_at": "Start",
@ -280,54 +237,37 @@
"postinstall_intro_3": "You can obtain more information by visiting the <a href='//yunohost.org/postinstall' target='_blank'>appropriate documentation page</a>",
"postinstall_password": "This password will be used to manage everything on your server. Take the time to choose it wisely.",
"previous": "Previous",
"process": "Process",
"protocol": "Protocol",
"public_ip": "Public IP: ",
"ram": "RAM",
"read": "Read",
"read_more": "Read more",
"reception": "Reception",
"refresh_app_list": "Refresh list",
"rerun_diagnosis": "Rerun diagnosis",
"request_adoption": "waiting adoption",
"request_adoption_details": "The current maintainer would like to stop maintaining this app. Feel free to propose yourself as the new maintainer!",
"request_help": "need help",
"request_help_details": "The current maintainer would like some help with the maintainance of this app. Feel free to come contribute on it!",
"restore": "Restore",
"restart": "Restart",
"run": "Run",
"running": "Running",
"save": "Save",
"search_for_apps": "Search for apps...",
"select_all": "Select all",
"select_none": "Select none",
"service_description": "Description:",
"service_log": "%s log",
"service_start_on_boot": "Start on boot: ",
"service_status": "Status: ",
"service_start_on_boot": "Start on boot",
"services": "Services",
"services_list": "Service list",
"set_default": "Set default",
"size": "Size",
"since": "since",
"skip": "Skip",
"start": "Start",
"started_at": "Started at:",
"status": "Status",
"stop": "Stop",
"storage_create": "Add remote storage",
"storages_new": "New remote storage",
"storages_no": "No storages.",
"swap": "Swap",
"system": "System",
"system_apps": "Apps",
"system_apps_nothing": "All apps are up to date!",
"system_packages": "System packages",
"system_packages_nothing": "All system packages are up to date!",
"system_update": "System update",
"system_upgrade": "System upgrade",
"system_upgrade_btn": "Upgrade",
"system_upgrade_all_applications_btn": "Upgrade all applications",
"system_upgrade_all_packages_btn": "Upgrade all packages",
"tcp": "TCP",
"time_since_update": "Time since update: ",
"tools": "Tools",
"tools_adminpw": "Change administration password",
"tools_adminpw_confirm_placeholder": "Confirm the new password",
@ -349,23 +289,20 @@
"tools_shutdown_done": "Shutting down...",
"tools_shuttingdown": "Your server is powering off. As long as your server is off, you won't be able to use the web administration.",
"tools_shutdown_reboot": "Shutdown/Reboot",
"total": "Total",
"transmission": "Transmission",
"udp": "UDP",
"unauthorized": "Unauthorized",
"unignore": "Unignore",
"uninstall": "Uninstall",
"unknown_action": "Unknown action %s",
"unknown_argument": "Unknown argument: %s",
"unmaintained": "Unmaintained",
"unmaintained_details": "This app has not been update for quite a while and the previous maintainer has gone away or does not have time to maintain this app. Feel free to check the app repository to provide your help",
"upload": "Upload",
"upload_archive": "Archive upload",
"upnp": "UPnP",
"upnp_disabled": "UPnP is disabled.",
"upnp_enabled": "UPnP is enabled.",
"uptime": "Uptime",
"url": "URL",
"usage": "Usage",
"used": "Used",
"user_email": "Email",
"user_emailaliases": "Mail aliases",
"user_emailforward": "Mail forward",
@ -378,13 +315,11 @@
"user_username": "Username",
"user_username_edit": "Edit %ss account",
"users": "Users",
"users_list": "User list",
"users_new": "New user",
"users_no": "No users.",
"versions": "Versions",
"version": "Version",
"warnings": "%s warnings",
"warning_first_user": "You probably need to <a href='#/users/create' class='alert-link'>create a user</a> first.",
"write": "Write",
"wrong_password": "Wrong password",
"yes": "Yes",
"certificate_alert_not_valid": "CRITICAL: Current certificate is not valid! HTTPS won't work at all!",
@ -395,7 +330,6 @@
"certificate_alert_great": "Great! You're using a valid Let's Encrypt certificate!",
"certificate_alert_unknown": "Unknown status",
"certificate_manage": "Manage SSL certificate",
"certificate_old_letsencrypt_app_conflict": "The 'letsencrypt' app is currently installed and conflicts with this feature. Please uninstall it first to use the new certificate management interface.",
"ssl_certificate": "SSL certificate",
"confirm_cert_install_LE": "Are you sure you want to install a Let's Encrypt certificate for this domain?",
"confirm_cert_regen_selfsigned": "Are you sure you want to regenerate a self-signed certificate for this domain?",
@ -406,25 +340,14 @@
"certificate_authority": "Certification authority",
"validity": "Validity",
"domain_is_eligible_for_ACME": "This domain seems correctly configured to install a Let's Encrypt certificate!",
"domain_not_eligible_for_ACME": "This domain doesn't seem ready for a Let's Encrypt certificate. Please check your DNS configuration and HTTP server reachability.",
"domain_not_eligible_for_ACME": "This domain doesn't seem ready for a Let's Encrypt certificate. Please check your DNS configuration and HTTP server reachability. The 'DNS records' and 'Web' section in <a href='#/diagnosis'>the diagnosis page</a> can help you understand what is misconfigured.",
"install_letsencrypt_cert": "Install a Let's Encrypt certificate",
"manually_renew_letsencrypt_message": "Certificate will be automatically renewed during the last 15 days of validity. You can manually renew it if you want to. (Not recommended).",
"manually_renew_letsencrypt": "Manually renew now",
"meltdown": "You are vulnerable to the <a target=\"_blank\" href=\"https://meltdownattack.com/\">meltdown</a> critical security vulnerability. To fix that, you need to <a href=\"#/update\">update your system</a> then <a href=\"#/tools/reboot\">reboot it</a> to load the new linux kernel.",
"regenerate_selfsigned_cert_message": "If you want, you can regenerate the self-signed certificate.",
"regenerate_selfsigned_cert": "Regenerate self-signed certificate",
"revert_to_selfsigned_cert_message": "If you really want to, you can reinstall a self-signed certificate. (Not recommended)",
"revert_to_selfsigned_cert": "Revert to a self-signed certificate",
"appslists": "Applications lists",
"appslists_no_lists": "No applications lists",
"appslists_custom": "Custom applications list",
"appslists_manage": "Manage applications lists",
"appslists_confirm_remove": "Are you sure you want to remove this applications list?",
"appslists_info_refresh_desc": "Refresh applications status for this list.",
"appslists_info_remove_desc": "Applications from this list will not be available anymore.",
"appslists_last_update": "Last update",
"appslists_unknown_list": "Unknown apps list: %s",
"name": "Name",
"purge_user_data_checkbox": "Purge %s's data? (This will remove the content of it's home and mail directories.)",
"purge_user_data_checkbox": "Purge %s's data? (This will remove the content of its home and mail directories.)",
"purge_user_data_warning": "Purging user's data is not reversible. Be sure you know what you're doing!"
}

View file

@ -227,7 +227,7 @@
"uninstall": "Malinstali",
"users_new": "Nova uzanto",
"users": "Uzantoj",
"system_apps_nothing": "Ne estas programoj por ĝisdatigi.",
"system_apps_nothing": "Ĉiuj aplikoj estas ĝisdatigitaj!",
"used": "Uzata",
"tools_security_feed_view_items": "Vidu ĉiujn sekurecajn sciigojn",
"certificate_alert_letsencrypt_about_to_expire": "Nuna atestilo estas preskaŭ eksvalidiĝi. Ĝi baldaŭ renoviĝu aŭtomate.",
@ -259,7 +259,7 @@
"install_letsencrypt_cert": "Instalu atestilon Lasu-Ĉifri",
"appslists_custom": "Propra listo listo",
"no_log": "Neniu ŝtipo.",
"system_packages_nothing": "Ne estas pakoj por ĝisdatigi.",
"system_packages_nothing": "Ĉiuj sistemaj pakoj estas ĝisdatigitaj !",
"system_upgrade_all_applications_btn": "Ĝisdatigu ĉiujn aplikojn",
"postinstall": "post Instalaĵo",
"service_description": "Priskribo:",
@ -270,7 +270,7 @@
"unauthorized": "Ne rajtigita",
"wrong_password": "Erara pasvorto",
"tools_security_feed": "Sekurecaj sciigoj",
"system_packages": "Pakoj",
"system_packages": "Sistempakaĵoj",
"certificate_alert_unknown": "Nekonata stato",
"tools_shutdown_reboot": "Ŝalti/Rekomenci",
"logs_started_at": "Komencu",
@ -325,7 +325,7 @@
"tools_adminpw_current": "Aktuala Pasvorto",
"appslists_manage": "Administri listojn de aplikoj",
"menu": "menuo",
"service_start_on_boot": "Komencu je ekkuro: ",
"service_start_on_boot": "Komencu je ekkuro",
"storages_new": "Nova fora stokado",
"storage_create": "Aldonu forajn stokadojn",
"regenerate_selfsigned_cert_message": "Se vi volas, vi povas regeneri la mem-subskribitan atestilon.",
@ -457,5 +457,13 @@
"app_state_low_quality": "malkvalita",
"app_state_low_quality_explanation": "Ĉi tiu app eble funkcias, sed ankoraŭ enhavas problemojn, aŭ ne plene integras kun YunoHost, aŭ ĝi ne respektas la bonajn praktikojn.",
"catalog": "Katalogo",
"others": "Aliaj"
"others": "Aliaj",
"confirm_service_restart": "Ĉu vi certas, ke vi volas rekomenci %s ?",
"diagnosis_first_run": "La diagnoza funkcio provos identigi oftajn problemojn pri la diversaj aspektoj de via servilo por certigi, ke ĉio funkcias glate. Bonvolu ne panikiĝi, se vi vidas amason da eraroj tuj post agordo de via servilo: ĝi ĝuste celas helpi vin identigi problemojn kaj gvidi vin ripari ilin. La diagnozo ankaŭ funkcios aŭtomate dufoje ĉiutage kaj retpoŝtu al la administranto se iuj problemoj ekestos.",
"group_explain_visitors_needed_for_external_client": "Atentu, ke vi bezonas konservi iujn aplikaĵojn permesitajn al vizitantoj se vi intencas uzi ilin kun eksteraj klientoj. Ekzemple, ĉi tiu estas la kazo de Nextcloud se vi volas intenci uzi sinkronigan klienton en via inteligenta telefono aŭ labortabla komputilo.",
"restart": "Rekomenci",
"unmaintained_details": "Ĉi tiu app ne estis ĝisdatigita antaŭ tre tempo kaj la antaŭa prizorganto foriĝis aŭ ne havas tempon por subteni ĉi tiun app. Bonvolu kontroli la app-deponejon por doni vian helpon",
"run_first_diagnosis": "Kuru komencan diagnozon",
"groups": "Grupoj",
"issues": "%s aferoj"
}

View file

@ -24,7 +24,7 @@
"app_make_default": "Establecer como predeterminado",
"app_repository": "Origen de la aplicación: ",
"app_state": "Estado de la aplicación: ",
"app_state_inprogress": "en marcha",
"app_state_inprogress": "Todavía no trabajando",
"app_state_notworking": "no funciona",
"app_state_validated": "Validado",
"app_state_working": "funciona",
@ -222,7 +222,7 @@
"save": "Guardar",
"select_user": "Seleccionar usuario",
"service_log": "%s log",
"service_start_on_boot": "Inicio en el arranque: ",
"service_start_on_boot": "Inicie en el arranque",
"service_status": "Estado: ",
"services": "Servicios",
"services_list": "Lista de servicios",
@ -238,11 +238,11 @@
"swap": "Swap",
"system": "Sistema",
"system_apps": "Aplicaciones",
"system_apps_nothing": "No hay aplicaciones para actualizar.",
"system_apps_nothing": "¡Todas las aplicaciones están actualizadas!",
"system_delayed_upgrade": "Retraso en actualización",
"system_delayed_upgrade_warning": "<b>%s</b> será actualizada automáticamente en la próxima hora.",
"system_packages": "Paquetes",
"system_packages_nothing": "No hay paquetes para actualizar.",
"system_packages_nothing": "¡Todos los paquetes del sistema están actualizados!",
"system_update": "Actualización del sistema",
"system_upgrade": "Actualización del sistema",
"system_upgrade_btn": "Actualización",
@ -376,14 +376,14 @@
"from_to": "desde %s a %s",
"only_highquality_apps": "Solo aplicaciones de alta calidad",
"only_decent_quality_apps": "Solo aplicaciones de calidad aceptable",
"orphaned": "sin mantenimiento",
"orphaned": "No mantenido",
"request_adoption": "pendiente de adopción",
"request_adoption_details": "Al mantenedor actual le gustaría dejar de mantener esta aplicación. ¡No dude en ofrecerse como el nuevo mantenedor!",
"request_help": "necesita ayuda",
"app_state_inprogress_explanation": "El mantenedor de esta aplicación declara que aún no está lista para su uso en producción. ¡TENGA CUIDADO!",
"app_state_high-quality_explanation": "Esta aplicación está bien integrada en YunoHost. Ha sido (¡y continua siendo!) revisada por especialistas del equipo de aplicaciones de YunoHost. Se puede esperar que sea segura y mantenida a largo plazo.",
"app_state_working_explanation": "El mantenedor de esta aplicación declara que «funciona». Significa que debería ser funcional (comparada a nivel de aplicación) pero no está revisada por especialistas necesariamente, puede tener aún problemas o no está totalmente integrada en YunoHost.",
"orphaned_details": "Esta aplicación ya no se mantiene. Puede que funcione pero no se actualizará. ¡No dude en venir y reanimarla!",
"orphaned_details": "Esta aplicación no se ha mantenido durante bastante tiempo. Todavía puede estar funcionando, pero no recibirá ninguna actualización hasta que alguien se ofrezca como voluntario para encargarse de ello. ¡Siéntase libre de contribuir para revivirlo!",
"request_help_details": "Al mantenedor actual le gustaría recibir alguna ayuda con el mantenimiento de esta aplicación. ¡No dude en venir a contribuir!",
"tools_rebooting": "Su servidor se está reiniciando. Para volver a la interfaz de administración web necesita esperar a que el servidor esté listo. Puede comprobarlo recargando esta página (F5).",
"tools_shutdown_btn": "Apagar",
@ -466,5 +466,19 @@
"last_ran": "Ejecutado por última vez:",
"unignore": "Dejar de ignorar",
"warnings": "%s avisos",
"all": "Todo"
"all": "Todo",
"diagnosis_first_run": "El diagnostico intentara identificar los errores comunes en diferentes aspectos de su servidor para asegurar que todo funcione de la mejor manera. No te asustes si observas algunas errores justo después de la configuracion de tu servidor. Estos le ayudara a identificar los incidentes y le guiara para corregir los. El diagnostico correrá 2 veces al día y enviara un correo electrónico al administrador si encuentra incidentes.",
"since": "desde",
"others": "Otros",
"run_first_diagnosis": "Procesando el primer diagnostico",
"configuration": "Configuración",
"catalog": "Catálogo",
"app_state_low_quality_explanation": "Asta app esta probablemente funcional, pero puede fallar, no esta completamente integrada con YunoHost, o no respeta las buenas practicas.",
"app_state_low_quality": "baja qualidad",
"confirm_service_restart": "¿Estás seguro de que quieres reiniciar %s ?",
"group_explain_visitors_needed_for_external_client": "Tenga cuidado de que necesita mantener algunas aplicaciones permitidas a los visitantes si tiene la intención de usarlas con clientes externos. Por ejemplo, este es el caso de Nextcloud si desea utilizar un cliente de sincronización en su teléfono inteligente o computadora de escritorio.",
"issues": "%s problemas",
"unmaintained_details": "Esta aplicación no se ha actualizado durante bastante tiempo y el responsable anterior se ha ido o no tiene tiempo para mantenerla. No dude en consultar el repositorio de aplicaciones para brindar su ayuda",
"groups": "Grupos",
"restart": "Reiniciar"
}

View file

@ -27,7 +27,7 @@
"app_state_inprogress": "ne fonctionne pas encore",
"app_state_notworking": "Non fonctionnelle",
"app_state_validated": "Validée",
"app_state_working": "Fonctionnelle",
"app_state_working": "fonctionnelle",
"application": "Application",
"applications": "Applications",
"archive_empty": "Larchive est vide",
@ -65,7 +65,7 @@
"confirm_change_maindomain": "Voulez-vous vraiment changer le domaine principal ?",
"confirm_delete": "Voulez-vous vraiment supprimer %s ?",
"confirm_firewall": "Voulez-vous vraiment %s le port %s (protocole: %s, connexion: %s)",
"confirm_install_custom_app": "AVERTISSEMENT ! Linstallation dapplications tierces peut compromettre lintégrité et la sécurité de votre système. Vous ne devriez probablement PAS linstaller si vous ne savez pas ce que vous faites. Prenez-vous ce risque ?",
"confirm_install_custom_app": "ATTENTION ! Linstallation dapplications tierces peut compromettre lintégrité et la sécurité de votre système. Vous ne devriez probablement PAS linstaller si vous ne savez pas ce que vous faites. Prenez-vous ce risque ?",
"confirm_install_domain_root": "Vous ne pourrez pas installer d'autres applications sur %s. Continuer ?",
"confirm_postinstall": "Vous êtes sur le point de lancer le processus de post-installation sur le domaine %s. Cela peut prendre du temps, *n'interrompez pas l'opération avant la fin*.",
"confirm_restore": "Voulez-vous vraiment restaurer %s ?",
@ -139,7 +139,7 @@
"hook_data_home": "Données de lutilisateur",
"hook_data_home_desc": "Les données de lutilisateur situées dans /home/USER",
"hook_data_mail": "Courriel",
"hook_data_mail_desc": "Les courriels qui sont stockés sur le serveur",
"hook_data_mail_desc": "Courriels stockés sur le serveur",
"hostname": "Nom d'hôte",
"id": "ID",
"inactive": "Inactif",
@ -224,7 +224,7 @@
"select_all": "Tout sélectionner",
"select_none": "Tout désélectionner",
"service_log": "Journal de %s",
"service_start_on_boot": "Lancer au démarrage : ",
"service_start_on_boot": "Lancement au démarrage :",
"service_status": "Statut : ",
"services": "Services",
"services_list": "Liste des services",
@ -239,11 +239,11 @@
"swap": "Espace déchange",
"system": "Système",
"system_apps": "Applications",
"system_apps_nothing": "Il n'y a aucune application à mettre à jour.",
"system_apps_nothing": "Toutes les applications sont à jour !",
"system_delayed_upgrade": "Mise à jour différée",
"system_delayed_upgrade_warning": "<b>%s</b> sera mis-à-jour automatiquement durant l'heure suivante.",
"system_packages": "Paquets",
"system_packages_nothing": "Aucun paquet n'est à mettre à jour.",
"system_packages": "Paquets système",
"system_packages_nothing": "Tous les packages système sont à jour !",
"system_update": "Mettre à jour le système",
"system_upgrade": "Mise à jour du système",
"system_upgrade_btn": "Mettre à jour",
@ -318,7 +318,7 @@
"certificate_authority": "Autorité de certification",
"validity": "Validité",
"domain_is_eligible_for_ACME": "Ce domaine semble correctement configuré pour installer un certificat Lets Encrypt !",
"domain_not_eligible_for_ACME": "Ce domaine ne semble pas prêt pour installer un certificat Lets Encrypt. Veuillez vérifier votre configuration DNS et laccessibilité HTTP de votre serveur.",
"domain_not_eligible_for_ACME": "Ce domaine ne semble pas prêt pour installer un certificat Lets Encrypt. Veuillez vérifier votre configuration DNS et laccessibilité HTTP de votre serveur. Les sections 'enregistrement DNS' et 'Web' dans <a href='#/diagnosis'>la page de diagnostic</a> peuvent vous aider à comprendre ce qui est mal configurée.",
"install_letsencrypt_cert": "Installer un certificat Lets Encrypt",
"manually_renew_letsencrypt_message": "Le certificat sera renouvelé automatiquement durant les 15 derniers jours de sa validité. Vous pouvez le renouveler manuellement si vous le souhaitez (non recommandé).",
"manually_renew_letsencrypt": "Renouveler manuellement maintenant",
@ -399,7 +399,7 @@
"error_connection_interrupted": "Le serveur a fermé la connexion au lieu dy répondre. Est-ce que Nginx a été redémarré ou est-ce que l'API YunoHost s'est-elle arrêtée pour diverses raisons ? (code/message derreur : %s)",
"experimental_warning": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas lutiliser à moins que vous ne sachiez ce que vous faites...",
"good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe administrateur. Le mot de passe doit comporter au moins 8 caractères — bien quil soit recommandé dutiliser un mot de passe plus long (cest-à-dire une phrase secrète) et/ou dutiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).",
"good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien quil soit recommandé dutiliser un mot de passe plus long (cest-à-dire une phrase secrète) et/ou dutiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.",
"good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe pour l'utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien quil soit recommandé dutiliser un mot de passe plus long (cest-à-dire une phrase secrète) et/ou dutiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.",
"level": "niveau",
"only_official_apps": "Applications officielles uniquement",
"only_working_apps": "Applications fonctionnelles uniquement",
@ -422,28 +422,28 @@
"logs_more": "Afficher plus de lignes",
"search_for_apps": "Recherche des applications…",
"unmaintained": "Non maintenue",
"purge_user_data_checkbox": "Purger les données de % s ? (Cela supprimera le contenu de ses répertoires home et mail.)",
"purge_user_data_checkbox": "Purger les données de l'utilisateur %s ? (Cela supprimera le contenu de ses répertoires home et mail.)",
"purge_user_data_warning": "La purge des données de lutilisateur nest pas réversible. Assurez-vous de savoir ce que vous faites !",
"version": "Version",
"confirm_update_system": "Voulez-vous vraiment mettre à jour tous les paquets système ?",
"app_state_inprogress_explanation": "Le mainteneur de cette application a indiqué qu'elle n'est pas encore prête pour une utilisation en production. FAITES ATTENTION !",
"app_state_notworking_explanation": "Le mainteneur de cette application a déclaré celle-ci comme \"non fonctionnelle\" SON INSTALLATION PEUT CASSER VOTRE SYSTÈME !",
"app_state_inprogress_explanation": "Le responsable de cette application a indiqué qu'elle n'est pas encore prête pour une utilisation en production. SOYEZ VIGILANT(E) !",
"app_state_notworking_explanation": "Le responsable de cette application a déclaré celle-ci comme \"non fonctionnelle\". SON INSTALLATION POURRAIT CASSER VOTRE SYSTÈME !",
"app_state_high-quality": "Haute qualité",
"app_state_high-quality_explanation": "Cette application est bien intégrée dans YunoHost. Elle a été (et est !) revue par l'équipe applicative de YunoHost. On peut s'attendre à ce qu'elle soit sûre et maintenue sur le long terme.",
"app_state_working_explanation": "Le responsable de cette application l'a déclarée comme fonctionnelle. Cela signifie qu'elle doit être utilisable (c.f. niveau de l'application) mais n'est pas nécessairement revue, elle peut encore contenir des bogues ou bien n'est pas entièrement intégrée dans YunoHost.",
"app_state_high-quality_explanation": "Cette application est bien intégrée à YunoHost. Elle a été (et est !) revue par l'équipe applicative de YunoHost. On peut s'attendre à ce qu'elle soit sûre et maintenue sur le long terme.",
"app_state_working_explanation": "Le responsable de cette application l'a déclarée comme 'fonctionnelle'. Cela signifie qu'elle doit fonctionner (voir son niveau d'intégration) mais n'est pas nécessairement revue, elle peut encore contenir des bugs ou bien n'est pas entièrement intégrée à YunoHost.",
"hook_conf_ynh_currenthost": "Domaine principal actuel",
"license": "Licence",
"maintained": "Maintenue",
"maintained_details": "Cette application a été maintenue par son responsable au cours des tout derniers mois.",
"only_highquality_apps": "Uniquement des applications de haute qualité",
"maintained_details": "Cette application a été maintenue par son responsable au cours des derniers mois.",
"only_highquality_apps": "Uniquement des applications de haute-qualité",
"only_decent_quality_apps": "Seulement des applications d'une qualité décente",
"orphaned": "Non maintenue",
"orphaned_details": "Cette application n'a pas été maintenue depuis un certain temps. Il peut encore fonctionner, mais ne recevra aucune mise à niveau jusqu'à ce que quelqu'un se porte volontaire pour s'en occuper. N'hésitez pas à contribuer à la faire revivre !",
"request_adoption": "en attente de repreneur",
"request_adoption_details": "Le responsable actuel aimerait arrêter de maintenir cette application. N'hésitez pas à vous proposer comme nouveau responsable !",
"request_adoption": "en attente de responsable",
"request_adoption_details": "Le responsable actuel ne souhaite plus s'occuper de cette application. N'hésitez pas à vous proposer comme nouveau responsable !",
"request_help": "besoin d'assistance",
"request_help_details": "Le responsable actuel aimerait de l'aide pour la maintenance de cette application. N'hésitez pas à y contribuer !",
"advanced": "Avancée",
"advanced": "Avancé",
"from_to": "de %s à %s",
"group_name": "Nom du groupe",
"nobody": "Personne",
@ -473,10 +473,18 @@
"configuration": "Configuration",
"since": "depuis",
"all": "Tout",
"app_state_low_quality": "basse qualité",
"app_state_low_quality": "faible qualité",
"app_state_low_quality_explanation": "Cette application peut être fonctionnelle, mais peut toujours contenir des problèmes, ou n'est pas entièrement intégrée à YunoHost, ou elle ne respecte pas les bonnes pratiques.",
"catalog": "Catalogue",
"others": "Autres",
"diagnosis_first_run": "La fonctionnalité de diagnostic va tenter de trouver certains problèmes communs sur différents aspects de votre serveur pour être sûr que tout fonctionne normalement. Merci de ne pas paniquer si vous voyez une multitude d'erreurs après avoir configuré votre serveur : la fonctionnalité est précisément prévue pour les identifier et vous aider à les résoudre. Le diagnostic sera également effectué deux fois par jour et enverra un mail à l'administrateur si des erreurs sont détectées.",
"run_first_diagnosis": "Démarrer le diagnostic initial"
"run_first_diagnosis": "Démarrer le diagnostic initial",
"confirm_service_restart": "Êtes-vous certain de vouloir redémarrer %s ?",
"groups": "Groupes",
"restart": "Redémarrer",
"unmaintained_details": "Cette application n'a pas été mise à jour depuis un bon moment et le responsable précédent est parti ou n'a pas le temps de maintenir cette application. N'hésitez pas à consulter le référentiel des applications pour apporter votre aide",
"group_explain_visitors_needed_for_external_client": "Veillez à ce que certaines applications soient autorisées pour les visiteurs si vous avez l'intention de les utiliser avec des clients externes. Par exemple, c'est le cas pour Nextcloud si vous souhaitez avoir l'intention d'utiliser un client de synchronisation sur votre smartphone ou ordinateur de bureau.",
"issues": "%s problèmes",
"operation_failed_explanation": "L'opération a échoué ! Veuillez-nous excuser pour ça :( Vous pouvez essayer de <a href='https://yunohost.org/help'>demander de l'aide</a>. Merci de fournir *le log complet* de l'opération pour les personnes qui vont vous aider. Vous pouvez cliquer sur le bouton vert 'Partager avec Yunopaste'. Quand vous partagez les logs, YunoHost essaie automatiquement d'anonymiser les informations privées comme le nom de domaine et l'adresses IP.",
"diagnosis_explanation": "La fonctionnalité de diagnostic va tenter de trouver certains problèmes communs sur différents aspects de votre serveur pour être sûr que tout fonctionne normalement. Le diagnostic sera également effectué deux fois par jour et enverra un mail à l'administrateur si des erreurs sont détectées. À noter que certains tests ne seront pas montrés si vous n'utilisez pas certaines fonctions spécifiques (XMPP, par exemple) ou s'ils échouent à cause d'une configuration trop complexe. Dans ce cas, et si vous savez ce que vous avez modifié, vous pouvez ignorer les problèmes et les avertissements correspondantes."
}

View file

@ -3,7 +3,7 @@
"add": "Aggiungi",
"administration_password": "Password amministrazione",
"allowed_users": "Utenti consentiti",
"api_not_responding": "Le API non rispondono",
"api_not_responding": "Le API YunoHost non rispondono. Forse 'yunohost-api' è giù o è stato riavviato?",
"app_access": "Accesso",
"app_access_addall_btn": "Attiva l'accesso per tutti",
"app_access_addall_desc": "Tutti gli utenti esistenti avranno accesso a %s.",
@ -425,5 +425,26 @@
"purge_user_data_checkbox": "Eliminare i dati di %s? (Questo rimuoverà il contenuto delle sue cartelle home e mail.)",
"purge_user_data_warning": "L'eliminazione dei dati utente non è annullabile. Assicurati di sapere cosa stai facendo!",
"hook_conf_ynh_currenthost": "Dominio principale attuale",
"confirm_update_system": "Sei sicuro di voler aggiornare tutti i pacchetti di sistema?"
"confirm_update_system": "Sei sicuro di voler aggiornare tutti i pacchetti di sistema?",
"app_state_inprogress_explanation": "Questo maintainer di questa applicazione ha dichiarato che l'applicazione non è ancora pronta per l'uso in produzione. ATTENZIONE!",
"app_state_notworking_explanation": "Questo maintainer di questa applicazione ha dichiarato che \"non funziona\". SI ROMPERÀ IL VOSTRO SISTEMA!",
"app_state_high-quality": "alta qualità",
"group_add_permission": "Aggiungere un permesso",
"group_explain_all_users": "Questo è un gruppo speciale contenente tutti gli account di tutti gli utenti sul server",
"group": "Gruppo",
"group_name": "Nome del gruppo",
"group_all_users": "Tutti gli utenti",
"group_visitors": "Visitori",
"group_format_name_help": "È possibile utilizzare caratteri alfanumerici e spazio",
"group_add_member": "Aggiungere un utente",
"app_state_high-quality_explanation": "Questa applicazione è ben integrata con YunoHost. E 'stato (ed è!) peer-reviewed dal team di YunoHost app. Ci si può aspettare che sia sicuro e mantenuto a lungo termine.",
"details": "Dettagli",
"diagnosis_experimental_disclaimer": "Siate consapevoli che la funzione di diagnosi è ancora sperimentale e in fase di perfezionamento, e potrebbe non essere completamente affidabile.",
"errors": "%s errori",
"everything_good": "Tutto bene!",
"from_to": "da %s a %s",
"configuration": "Configuratione",
"advanced": "Avanzate",
"app_state_working_explanation": "Il manutentore di questa applicazione lo ha dichiarato \"funzionante\". Significa che dovrebbe essere funzionale (c.f. livello di applicazione) ma non è necessariamente sottoposta a peer-reviewing, può ancora contenere problemi o non è pienamente integrata con YunoHost.",
"group_new": "Nuovo gruppo"
}

View file

@ -2,7 +2,7 @@
"configuration": "कन्फिगरेसन",
"close": "बन्द",
"check": "जाँच गर्नुहोस्",
"cancel": "रद्द",
"cancel": "रद्द गर्नुहोस्",
"both": "दुबै",
"begin": "सुरु गर्नुहोस्",
"backups_no": "कुनै ब्याकअप छैन",
@ -54,5 +54,8 @@
"remove": "हटाउनुहोस्",
"add": "थप्नुहोस्",
"active": "सक्रिय",
"action": "कार्य"
"action": "कार्य",
"password": "पासवर्ड",
"ok": "ठिक छ",
"logged_out": "लग आउट"
}

View file

@ -241,7 +241,7 @@
"save": "Salvagardar",
"select_user": "Causissètz un utilizaire",
"service_log": "Jornal de %s",
"service_start_on_boot": "Lançar en aviar: ",
"service_start_on_boot": "Lançar en aviar",
"service_status": "Estatut: ",
"services": "Servicis",
"services_list": "Lista dels servicis",
@ -257,7 +257,7 @@
"swap": "Espaci descambi",
"system": "Sistèma",
"system_apps": "Aplicacions",
"system_apps_nothing": "I a pas cap daplicacion de metre a jorn.",
"system_apps_nothing": "Totas las aplicacions son a jorn !",
"system_delayed_upgrade": "Mesa a jorn reportada",
"system_delayed_upgrade_warning": "<b>%s</b> serà mes a jorn dins lora venenta.",
"system_packages": "Paquets sistèma",
@ -374,7 +374,7 @@
"certificate_alert_about_to_expire": "Atencion: lo certificat actual es a man dexpirar! Serà pas renovat automaticament!",
"certificate_alert_great": "Perfièch! Sètz a utilizar un certificat Lets Encrypt valid!",
"certificate_old_letsencrypt_app_conflict": "Laplicacion «letsencrypt» es actualament installada e es en conflicte amb aquesta foncionalitat. Mercés de la desinstallar per utilizar la novèla interfàcia de gestion dels certificats.",
"domain_not_eligible_for_ACME": "Aqueste domeni sembla pas prèst per un certificat Lets Encrypt. Mercés de verificar la configuracion DNS e laccessibilitat del servidor HTTP.",
"domain_not_eligible_for_ACME": "Aqueste domeni sembla pas prèst per un certificat Lets Encrypt. Mercés de verificar la configuracion DNS e laccessibilitat del servidor HTTP. Las seccions «enregistrament DNS» e «Web» de <a href='#/diagnosis'>la pagina de diagnostic</a> pòt vos ajudar a comprendre çò ques pas configurat coma cal.",
"manually_renew_letsencrypt_message": "Lo certificat serà renovat automaticament pendent los 15 darrièrs jorns de validitat. Podètz lo renovar manualament se volètz. (Pas recomandat).",
"meltdown": "Sètz vulnerable a la falha de seguretat critica <a target=\"_blank\" href=\"https://meltdownattack.com/\">meltdown</a>. Per dire de resòlvre aqueste problèma, vos cal <a href=\"#/update\">actualizar vòstre sistèma</a> puèi <a href=\"#/tools/reboot\"> lo tornar aviar</a> per cargar lo nòu nuclèu linux.",
"revert_to_selfsigned_cert_message": "So volètz vertadièrament, podètz tornar installar lo certificat auto-signat. (Pas recomandat)",
@ -470,5 +470,10 @@
"all": "Totas",
"app_state_low_quality": "qualitat bassa",
"catalog": "Catalòg",
"others": "Autras"
"others": "Autras",
"run_first_diagnosis": "Executar lo diagnostic inicial",
"confirm_service_restart": "Volètz vertadièrament reaviar %s?",
"restart": "Reaviar",
"groups": "Grops",
"issues": "%s problèmas"
}

View file

@ -1,5 +1,5 @@
{
"password": "hasło",
"password": "Hasło",
"action": "Akcja",
"active": "Aktywny",
"add": "Dodaj",

View file

@ -75,7 +75,7 @@
"free": "Livre",
"fs_type": "Tipo de Sistema de Ficheiros",
"gateway": "Gateway: ",
"hook_conf_ldap": "LDAP",
"hook_conf_ldap": "Base de dados LDAP",
"hook_conf_nginx": "Nginx",
"hook_conf_ssh": "SSH",
"hook_conf_ssowat": "SSOwat",
@ -224,7 +224,7 @@
"yes": "Sim",
"cancel": "Cancelar",
"remove": "Remover",
"api_not_responding": "API não está respondendo",
"api_not_responding": "A API YunoHost não está respondendo. Talvez 'yunohost-api' esteja desligada ou foi reiniciada?",
"app_change_label": "Mudar etiqueta",
"app_change_url": "Mudar URL",
"app_debug_no_logs": "Logs da aplicação não estão disponíveis",
@ -243,7 +243,7 @@
"app_no_actions": "Esta aplicação não tem nenhuma ação",
"app_repository": "Origem da aplicação: ",
"app_state": "Estado da aplicação: ",
"app_state_inprogress": "Em curso",
"app_state_inprogress": "ainda não está funcionando",
"app_state_inprogress_explanation": "O responsável desta aplicação afirmou que não está pronta ainda para ser usada em produção. TENHA CUIDADO!",
"app_state_notworking": "Não funcionando",
"app_state_notworking_explanation": "O responsável desta aplicação afirmou que não está funcionando. INSTALANDO-A PODE QUEBRAR SEU SISTEMA!",
@ -318,5 +318,6 @@
"diagnosis_experimental_disclaimer": "Esteja ciente de que a ferramenta de diagnóstico ainda é experimental e está em fase de aperfeiçoamento, e pode não ser totalmente confiável.",
"details": "Detalhes",
"catalog": "Catálogo",
"configuration": "Configuração"
"configuration": "Configuração",
"confirm_service_restart": "Tem certeza que deseja reiniciar %s?"
}

View file

@ -1,7 +1,7 @@
{
"add": "Ekle",
"administration_password": "Yönetici parolası",
"api_not_responding": "YunoHost API'si cevap vermiyor. Belki 'yunohost-api' çalışmıyor veya yeniden başlatıldı?",
"api_not_responding": "YunoHost API'si yanıt vermiyor. Belki 'yunohost-api' çalışmıyor veya yeniden başlatıldı?",
"both": "İkisi birden",
"cancel": "İptal etmek",
"close": "Kapat",
@ -15,13 +15,13 @@
"delete": "Sil",
"description": "Açıklama",
"disable": "Devredışı bırak",
"domain": "Domain",
"domain": "Alan adı",
"domain_name": "Domain ismi",
"domains": "Domainler",
"enable": "Devreye al",
"error_modify_something": "Bir şeyleri değiştirmelisiniz",
"error_occured": "Bir hata oluştu, tekrar deneyiniz",
"error_retrieve_feed": "Akış alınamadı : %s",
"error_retrieve_feed": "Feed alınamadı:% s. Tarayıcınızın bu isteği yerine getirmesini engelleyen bir eklentiniz olabilir (veya web sitesi kapalı).",
"error_select_domain": "Domain belirtmelisiniz",
"error_server": "Sunucu hatası",
"home": "Başlangıç",
@ -80,5 +80,24 @@
"remove": "Kaldır",
"advanced": "Gelişmiş",
"active": "Etkin",
"action": "Eylem"
"action": "Eylem",
"app_state_notworking_explanation": "Bu uygulamanın bu geliştiricisi 'çalışmıyor' olarak ilan etti. SİSTEMİNİZİ KIRACAK!",
"app_install_custom_no_manifest": "Manifest.json dosyası yok",
"app_state_inprogress_explanation": "Bu uygulamanın bu sürdürücü, bu uygulamanın henüz üretim kullanımına hazır olmadığınııkladı. DİKKATLİ OL!",
"app_state_high-quality_explanation": "Bu uygulama YunoHost ile iyi entegre edilmiştir. YunoHost uygulama ekibi tarafından hakemli bir incelemedir. Güvenli olması ve uzun vadede korunması beklenebilir.",
"last_ran": "Son sefer:",
"app_state_high-quality": "yüksek kalite",
"app_info_uninstall_desc": "Bu uygulamayı kaldırın.",
"app_state_low_quality": "Düşük kalite",
"app_state_low_quality_explanation": "Bu uygulama işlevsel olabilir, ancak yine de sorunlar içerebilir veya YunoHost ile tamamen entegre değildir veya iyi uygulamalara uymaz.",
"purge_user_data_checkbox": "%s verileri temizlensin mi? (Bu işlem ana ve posta dizinlerinin içeriğini silecektir.)",
"app_info_change_url_disabled_tooltip": "Bu özellik bu uygulamada henüz uygulanmadı",
"app_state_notworking": "çalışmıyor",
"app_info_default_desc": "Alan adı kökünü bu uygulamaya yönlendirin (% s).",
"app_state_inprogress": "Henüz çalışmıyor",
"app_info_changeurl_desc": "Bu uygulamanın erişim URL'sini değiştirin (alan adı ve / veya yol).",
"app_install_cancel": "Yükleme iptal edildi.",
"app_info_changelabel_desc": "Portaldaki uygulama etiketini değiştirin.",
"app_make_default": "Varsayılan yap",
"app_no_actions": "Bu uygulamanın herhangi bir eylemi yok"
}

View file

@ -11,5 +11,6 @@
"logged_out": "登出",
"cancel": "取消",
"ok": "好",
"password": "密码"
"password": "密码",
"logged_in": "登录"
}

View file

@ -1,16 +1,20 @@
<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/install">{{t 'install'}}</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">
<span class="input-group-addon"><i class="fas fa-search"></i></span>
<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 type="button" role="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<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">
@ -24,32 +28,51 @@
<div class="separator"></div>
<div class="list-group grid">
<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 {{status}} {{state}} {{isWorking}} {{isHighQuality}} {{decentQuality}} {{level}}-level">
<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">{{name}}</h2>
<div class="category">
<span class="label label-{{stateColor}} label-as-badge app-state" title="{{t (concat 'app_state_' state '_explanation') }}">{{t (concat 'app_state_' state) }}</span>
<a target="_BLANK" href="https://yunohost.org/#/packaging_apps_levels"><span class="label label-{{levelColor}} label-as-badge app-level" title="{{t 'app_level'}}">{{t 'level'}} {{levelFormatted}}</span></a>
<span class="label label-{{maintainedColor}} label-as-badge maintained-status" title="{{t (concat maintained '_details') }}"> {{t maintained}}</span>
</div>
<div class="app-card-desc">{{description}}</div>
<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">
<i class="fa-refresh"></i> {{formatDate updateDate day="numeric" month="long" year="numeric"}} -
<span title="{{t 'current_maintainer_title'}}" class="maintained"></span><i class="fa-user"></i> {{manifest.maintainer}}</span>
{{#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-globe"></i> Code
<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-book"></i> Readme
<i class="fa-fw fa-book"></i> Readme
</a>
{{#installable}}
<a href="#/apps/install/{{id}}" type="button" role="button" class="btn btn-{{installColor}} col-xs-4 active">
<i class="fa-plus"></i> {{t 'install'}}{{^isSafe}} <i class="fa-warning"></i>{{/isSafe}}
<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}}
@ -60,28 +83,29 @@
{{/apps}}
</div>
<div class="panel panel-default">
<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-warning"></span>
<span class="fa-fw fa-warning"></span>
{{t 'confirm_install_custom_app'}}
</p>
<form action="#/apps/install/custom" method="POST" class="form-horizontal">
<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-github"></span> {{t 'custom_app_url_only_github'}}
<span class="fa-fw fa-github"></span> {{t 'custom_app_url_only_github'}}
</p>
</div>
</div>
<div class="form-group">
<div class="text-center">
<input type="submit" role="button" class="btn btn-success slide" value="{{t 'install'}}">
<a role="button" class="btn btn-success slide">{{t 'install'}}</a>
</div>
</div>
</form>

View file

@ -0,0 +1,23 @@
<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,39 +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/{{name}}">{{label}}</a>
<a href="#/apps/{{name}}/debug">{{t 'debug'}}</a>
</div>
<div class="separator"></div>
{{#if services}}
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
{{#services}}
<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 }}">
<span class="fa-fw fa-info-circle"></span> {{t 'service_log' name }}
</a>
</h2>
</div>
<div id="collapse-{{ @index }}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-{{ @index }}">
<div class="panel-body">
{{#logs}}
<h3>{{file_name}}</h3>
<pre id="service-log-{{ @index }}" class="service-log">{{file_content}}</pre>
<button data-paste-content="#service-log-{{ @index }}"><i class="fa-cloud-upload"></i> {{t 'upload'}}</button>
{{/logs}}
</div>
</div>
</div>
{{/services}}
</div>
{{else}}
<div class="alert alert-warning">
<span class="fa-exclamation-triangle"></span>
{{t 'app_debug_no_logs'}}
</div>
{{/if}}

View file

@ -22,7 +22,7 @@
<dt>{{t 'version'}}</dt>
<dd>{{version}}</dd>
<dt>{{t 'multi_instance'}}</dt>
<dd>{{manifest.multi_instance}}</dd>
<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}}
@ -57,14 +57,14 @@
<hr>
<div class="container">
<p>{{t 'app_info_default_desc' settings.domain}}</p>
<a role="button" href="#/apps/{{settings.id}}/default" class="btn btn-success slide">
<button class="btn btn-success" data-action="set-as-default" data-app="{{settings.id}}">
<span class="fa-star"></span> {{t 'app_make_default'}}
</a>
</button>
</div>
<hr>
<div class="container">
<p>{{t 'app_info_changeurl_desc' settings.domain}}</p>
{{#if change_url}}
{{#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>
@ -79,16 +79,9 @@
<hr>
<div class="container">
<p>{{t 'app_info_uninstall_desc'}}</p>
<a href="#/apps/{{settings.id}}/uninstall" role="button" class="btn btn-danger slide back">
<button class="btn btn-danger slide back" data-action="uninstall" data-app="{{settings.id}}">
<span class="fa-trash-o"></span> {{t 'uninstall'}}
</a>
</div>
<hr>
<div class="container">
<p>{{t 'app_info_debug_desc'}}</p>
<a href="#/apps/{{settings.id}}/debug" role="button" class ="btn btn-warning slide">
<span class="fa-bug"></span> {{t 'app_debug_tab'}}
</a>
</button>
</div>
</div>
</div>

View file

@ -1,8 +1,8 @@
<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/install">{{t 'install'}}</a>
<a href="#/apps/install/{{id}}">{{manifest.name}}</a>
<a href="#/apps/catalog">{{t 'catalog'}}</a>
<a href="#/apps/install/{{id}}">{{t 'install_name' manifest.name}}</a>
</div>
<div class="separator"></div>
@ -16,7 +16,7 @@
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{{t 'id'}}</dt>
<dd>{{id}}</dd>
<dd>{{manifest.id}}</dd>
<dt>{{t 'description'}}</dt>
<dd>{{description}}</dd>
{{#displayLicense}}

View file

@ -4,7 +4,7 @@
</div>
<div class="actions-group">
<a role="button" href="#/apps/install" class="btn btn-success slide">
<a role="button" href="#/apps/catalog" class="btn btn-success slide">
<span class="fa-plus"></span> {{t 'install'}}
</a>
</div>
@ -16,7 +16,7 @@
<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>
{{settings.label}} <small>{{name}}</small>
</h2>
<p class="list-group-item-text">{{description}}</p>
</a>

View file

@ -3,12 +3,6 @@
<a href="#/backup">{{t 'backup'}}</a>
</div>
<div class="actions-group">
<!--<a role="button" href="#/storages/create" class="btn btn-success slide">
<span class="fa-plus"></span> {{t 'storages_new'}}
</a>-->
</div>
<div class="separator"></div>
<div class="list-group">
@ -18,10 +12,6 @@
<h2 class="list-group-item-heading">{{name}} <small>{{id}}</small></h2>
<p class="list-group-item-text">{{uri}}</p>
</a>
{{else}}
<div class="alert alert-warning">
<span class="fa-exclamation-triangle"></span>
{{t 'storages_no'}}
</div>
{{/each}}

View file

@ -83,41 +83,11 @@
</h2>
</div>
<div class="panel-body">
<!--<div class="container">
<p>{{t 'backup_archive_download'}}</p>
<a role="button" class="btn btn-info slide" href="#/backup/{{storage.id}}/{{name}}/download">
<span class="fa-download"></span> {{t 'download'}}
</a>
</div>
<hr>-->
<div class="container">
<p>{{t 'backup_archive_delete'}}</p>
<a href="#/backup/{{storage.id}}/{{name}}/delete" role="button" class="btn btn-danger slide">
<button class="btn btn-danger slide" data-action="delete" data-storage="{{storage.id}}" data-archive="{{name}}">
<span class="fa-trash-o"></span> {{t 'delete'}}
</a>
</button>
</div>
{{#if other_storages}}
<hr>
<div class="container">
<p>{{t 'backup_archive_copy'}}</p>
<form action="#/backup/{{storage.id}}/{{name}}/copy" 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">
<select id="storage" name="storage" class="form-control" required>
{{#each storages}}
<option value="{{id}}">{{name}} ({{uri}})</option>
{{/each}}
</select>
</div>
</div>
<div class="form-group">
<div class="text-center">
<input type="submit" role="button" class="btn btn-success slide" value="{{t 'copy'}}">
</div>
</div>
</form>
</div>
{{/if}}
</div>
</div>

View file

@ -1,52 +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}}/create">{{t 'storage_create'}}</a>
</div>
<div class="separator"></div>
<form action="#/storages" method="POST" class="form-horizontal">
<div class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label for="type" class="col-sm-3 control-label">{{t 'backup_type'}}</label>
<div class="col-sm-9">
<select class="form-control" name="type">
<option>sftp</option>
<option>ftp</option>
<option>rsync</option>
</select>
</div>
</div>
<div class="form-group">
<label for="domain" class="col-sm-3 control-label">{{t 'domain'}}</label>
<div class="col-sm-9">
<input type="text" id="domain" name="domain" class="form-control" placeholder="monserver.fr" required>
</div>
</div>
<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="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="path" class="col-sm-3 control-label">{{t 'path'}}</label>
<div class="col-sm-9">
<input type="text" id="path" name="path" class="form-control" placeholder="~/" required>
</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

@ -0,0 +1,67 @@
<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

@ -46,36 +46,36 @@
<p><span class="fa-fw fa-meh-o"></span>
{{t 'domain_not_eligible_for_ACME'}}</p>
{{/if}}
<a role="button" href="#/domains/{{name}}/cert-install-LE" class="btn btn-success {{#unless status.ACME_eligible}}disabled{{/unless}}">
<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'}}
</a>
</button>
<hr>
</div>
{{/if}}
{{#if actions_enabled.manual_renew_letsencrpt}}
<div class="container">
<p>{{t 'manually_renew_letsencrypt_message'}}</p>
<a role="button" href="#/domains/{{name}}/cert-renew-letsencrypt" class="btn btn-warning">
<button class="btn btn-warning" data-domain="{{name}}" data-action="renew-letsencrypt">
<span class="fa-refresh"></span> {{t 'manually_renew_letsencrypt'}}
</a>
</button>
</div>
<hr>
{{/if}}
{{#if actions_enabled.regen_selfsigned}}
<div class="container">
<p>{{t 'regenerate_selfsigned_cert_message'}}</p>
<a href="#/domains/{{name}}/cert-regen-selfsigned" role="button" class="btn btn-warning">
<button class="btn btn-warning" data-domain="{{name}}" data-action="regen-selfsigned">
<span class="fa-refresh"></span> {{t 'regenerate_selfsigned_cert'}}
</a>
</button>
</div>
<hr>
{{/if}}
{{#if actions_enabled.replace_with_selfsigned}}
<div class="container">
<p>{{t 'revert_to_selfsigned_cert_message'}}</p>
<a href="#/domains/{{name}}/cert-replace-with-selfsigned" role="button" class="btn btn-danger">
<button class="btn btn-danger" data-domain="{{name}}" data-action="replace-with-selfsigned">
<span class="fa-exclamation-triangle"></span> {{t 'revert_to_selfsigned_cert'}}
</a>
</button>
</div>
{{/if}}
</div>

View file

@ -12,67 +12,46 @@
<span class="fa-fw fa-globe"></span> {{name}}
</h2>
</div>
<div class="panel-body">
{{#if main}}
<p class="alert alert-success">
<span class="fa-star" title="{{t 'default'}}"></span> {{t 'domain_default_longdesc'}}
</p>
{{/if}}
<p><a href="{{url}}" target="_blank">{{url}}</a></p>
</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 'domain_visit_url' url}}</p>
<a role="button" href="{{url}}" class="btn btn-success" target="_blank">
{{t 'domain_visit'}} <span class="fa-fw fa-external-link"></span>
<span class="fa-fw fa-external-link"></span> {{t 'domain_visit'}}
</a>
</div>
{{#unless main}}
<hr>
<div class="container">
<p>{{t 'domain_default_desc'}}</p>
<form method="POST" action="#/domains">
<input type="hidden" name="domain" value="{{name}}" required class="form-control">
<button type="submit" class="btn btn-primary slide back" value="{{t 'set_default'}}">
{{t 'set_default'}} <span class="fa-fw fa-star"></span>
</button>
</form>
{{#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>
{{/unless}}
<hr>
<div class="container">
<p>{{t 'domain_dns_longdesc'}}</p>
<a role="button" href="#/domains/{{name}}/dns" class="btn btn-default slide">
{{t 'domain_dns_config'}} <span class="fa-fw fa-globe"></span>
<span class="fa-fw fa-globe"></span> {{t 'domain_dns_config'}}
</a>
</div>
<hr>
<div class="container">
<p>{{t 'certificate_manage'}}</p>
{{#unless enable_cert_management}}
<p><span class="fa-fw fa-exclamation-circle"></span>
{{t 'certificate_old_letsencrypt_app_conflict'}}
</p>
{{/unless}}
<a href="#/domains/{{name}}/cert-management" role="button" class="btn btn-default slide {{#unless enable_cert_management}}disabled{{/unless}}">
{{t 'ssl_certificate'}} <span class="fa-fw fa-lock"></span>
<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>
<a href="#/domains/{{name}}/delete" role="button" class="btn btn-danger slide back">
{{t 'delete'}} <span class="fa-fw fa-trash-o"></span>
</a>
<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

@ -21,10 +21,5 @@
</h2>
<p class="list-group-item-text">https://{{url}}</p>
</a>
{{else}}
<div class="alert alert-warning">
<span class="fa-exclamation-triangle"></span>
{{t 'domains_no'}}
</div>
{{/each}}
</div>

View file

@ -23,6 +23,10 @@
<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>

View file

@ -1,7 +1,7 @@
<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}}">{{service.name}}</a>
<a href="#/services/{{service.name}}">{{name}}</a>
</div>
<div class="separator"></div>
@ -9,76 +9,70 @@
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-info-circle"></span> {{t 'infos'}}</h2>
<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">
{{#service}}
<dl class="dl-horizontal">
<dt>{{t 'name'}}</dt>
<dd>{{name}}</dd>
<dt>{{t 'description'}}</dt>
<dd>{{description}}</dd>
</dl>
{{/service}}
<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"><span class="fa-fw fa-wrench"></span> {{t 'status'}}</h2>
<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 class="panel-body">
<dl class="dl-horizontal">
{{#service}}
<div class="pull-left">
{{t 'service_start_on_boot'}}
<span class="text-{{#is_loaded}}success{{/is_loaded}}{{^is_loaded}}danger{{/is_loaded}}">
{{loaded}}
</span>
<br>
{{t 'service_status'}}
<span class="text-{{#is_running}}success{{/is_running}}{{^is_running}}danger{{/is_running}}">
{{active}}
</span>
<br>
{{t 'started_at'}}
{{#active_at}}
{{formatTime . day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}
{{/active_at}}
{{^active_at}}
{{t 'unknown'}}
{{/active_at}}
</div>
<div class="pull-right">
{{#is_loaded}}
<a href="#/services/{{name}}/disable" role="button" class="btn btn-danger">
<span class="fa-square-o"></span> {{t 'disable'}}
</a>
{{/is_loaded}}
{{^is_loaded}}
<a href="#/services/{{name}}/enable" role="button" class="btn btn-success">
<span class="fa-check-square-o"></span> {{t 'enable'}}
</a>
{{/is_loaded}}
{{#is_running}}
<a href="#/services/{{name}}/stop" role="button" class="btn btn-danger">
<span class="fa-stop"></span> {{t 'stop'}}
</a>
{{/is_running}}
{{^is_running}}
<a href="#/services/{{name}}/start" role="button" class="btn btn-success">
<span class="fa-play"></span> {{t 'start'}}
</a>
{{/is_running}}
<a href="#/services/{{name}}/log" role="button" class="btn btn-default slide">
<span class="fa-book"></span> {{t 'log'}}
</a>
</div>
{{/service}}
</dl>
<div id="logs" class="panel-body">
{{#logs}}
<h2>{{filename}}</h2>
<pre class="service-log">{{filecontent}}</pre>
{{/logs}}
</div>
</div>

View file

@ -11,18 +11,16 @@
<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">
{{t 'service_status'}}
<span class="text-{{#is_running}}success{{/is_running}}{{^is_running}}danger{{/is_running}}">
{{active}}
</span>
<br>
{{t 'started_at'}}
{{#active_at}}
{{formatTime . day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}
{{/active_at}}
{{^active_at}}
{{t 'unknown'}}
{{/active_at}}
{{#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}}

View file

@ -1,16 +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/{{name}}">{{name}}</a>
<a href="#/services/{{name}}/log">{{t 'log'}}</a>
</div>
<div class="separator"></div>
<div class="container">
{{#logs}}
<h2>{{filename}}</h2>
<pre id="log" class="service-log">{{filecontent}}</pre>
<button data-paste-content="#log"><i class="fa-cloud-upload"></i> {{t 'upload'}}</button>
{{/logs}}
</div>

View file

@ -1,49 +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/appslists">{{t 'appslists'}}</a>
<a href="#/tools/appslists/{{name}}">{{appslist.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-info-circle"></span> {{t 'infos'}}</h2>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{{t 'name'}}</dt>
<dd>{{appslist.name}}</dd>
<dt>{{t 'url'}}</dt>
<dd>{{appslist.url}}</dd>
<dt>{{t 'appslists_last_update'}}</dt>
<dd>{{formatTime appslist.lastUpdate day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}</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">
<div class="container">
<p>{{t 'appslists_info_refresh_desc'}}</p>
<a href="#/tools/appslists/{{appslist.name}}/refresh" role="button" class="btn btn-info slide">
<span class="fa-refresh"></span> {{t 'refresh_app_list'}}
</a>
</div>
{{#appslist.removable}}
<hr>
<div class="container">
<p>{{t 'appslists_info_remove_desc'}}</p>
<a role="button" href="#/tools/appslists/{{appslist.name}}/remove" class="btn btn-danger slide back">
<span class="fa-trash-o"></span> {{t 'remove'}}
</a>
</div>
{{/appslist.removable}}
</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="#/tools">{{t 'tools'}}</a>
<a href="#/tools/appslists">{{t 'appslists'}}</a>
</div>
<div class="separator"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-list"></span> {{t 'appslists'}}</h2>
</div>
<div class="list-group">
{{#appslists}}
<a href="#/tools/appslists/{{name}}" class="list-group-item">
<span class="fa-chevron-right pull-right"></span>
<h2 class="list-group-item-heading">
{{name}}
</h2>
</a>
{{/appslists}}
{{^appslists}}
<p class="list-group-item text-warning">
<span class="fa-exclamation-triangle"></span>
{{t 'appslists_no_lists'}}
</p>
{{/appslists}}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-plus"></span> {{t 'appslists_custom'}}</h2>
</div>
<div class="panel-body">
<form action="#/tools/appslists" method="POST" class="form-horizontal">
<div class="form-group has-feedback">
<label for="appslist_name" class="col-md-2 col-sm-12 control-label">{{t 'name'}}</label>
<div class="col-md-10 col-sm-12">
<input type="text" id="appslist_name" name="appslist_name" class="form-control" value="" required />
</div>
</div>
<div class="form-group has-feedback">
<label for="appslist_url" class="col-md-2 col-sm-12 control-label">{{t 'url'}}</label>
<div class="col-md-10 col-sm-12">
<input type="url" id="appslist_url" name="appslist_url" class="form-control" value="" placeholder="https://some.domain.tld/somelist.json" required />
</div>
</div>
<div class="form-group">
<div class="col-md-10 col-md-push-2 col-sm-12">
<input type="submit" class="btn btn-success slide" value="{{t 'add'}}">
</div>
</div>
</form>
</div>
</div>

View file

@ -1,14 +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/ca">{{t 'tools_download_ca'}}</a>
</div>
<div class="separator"></div>
<div class="panel panel-default">
<div class="panel-body">
<p>{{t 'tools_download_ca_desc'}}</p>
<a role="button" href="ca.crt" class="btn btn-success">CA.crt</a>
</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="#/tools">{{t 'tools'}}</a>
<a href="#/tools/diagnosis">{{t 'diagnosis'}}</a>
{{#private}}
<a href="#/tools/diagnosis/private">{{t 'diagnosis_with_private'}}</a>
{{/private}}
</div>
<div class="separator"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-stethoscope"></span> {{t 'diagnosis'}}</h2>
</div>
<div class="panel-body">
<pre id="diagnosis">{{ diagnosis }}</pre>
{{#if private}}
<a class="btn btn-primary" role="button" href="#/tools/diagnosis"><i class="fa-eye-slash"></i> {{t 'diagnosis_hide_private'}}</a>
{{else}}
<a class="btn btn-primary" role="button" href="#/tools/diagnosis/private"><i class="fa-eye"></i> {{t 'diagnosis_view_private'}}</a>
{{/if}}
<button data-paste-content="#diagnosis"><i class="fa-cloud-upload"></i> {{t 'upload'}}</button>
</div>
</div>

View file

@ -29,20 +29,20 @@
<td>
{{#if this.ipv4}}
<span class="fa-check"></span>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/tcp/ipv4/close">{{t 'close'}}</a>
<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>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/tcp/ipv4/open">{{t 'open'}}</a>
<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>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/tcp/ipv6/close">{{t 'close'}}</a>
<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>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/tcp/ipv6/open">{{t 'open'}}</a>
<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>
@ -75,20 +75,20 @@
<td>
{{#if this.ipv4}}
<span class="fa-check"></span>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/udp/ipv4/close">{{t 'close'}}</a>
<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>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/udp/ipv4/open">{{t 'open'}}</a>
<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>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/udp/ipv6/close">{{t 'close'}}</a>
<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>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/udp/ipv6/open">{{t 'open'}}</a>
<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>
@ -168,10 +168,10 @@
<div class="panel-body">
{{#if upnp}}
<p class="text-success">{{t 'upnp_enabled'}}</p>
<a role="button" href="#/tools/firewall/upnp/disable" class="btn btn-danger">{{t 'disable'}}</a>
<button class="btn btn-danger" data-upnp="disable">{{t 'disable'}}</button>
{{else}}
<p class="text-danger">{{t 'upnp_disabled'}}</p>
<a role="button" href="#/tools/firewall/upnp/enable" class="btn btn-success">{{t 'enable'}}</a>
<button class="btn btn-success" data-upnp="enable">{{t 'enable'}}</button>
{{/if}}
</div>
</div>

View file

@ -6,10 +6,6 @@
<div class="separator"></div>
<div class="list-group">
<a href="#/tools/diagnosis" class="list-group-item slide clearfix">
<span class="pull-right fa-chevron-right"></span>
<h2 class="list-group-item-heading">{{t 'diagnosis'}}</h2>
</a>
<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>
@ -18,41 +14,17 @@
<span class="pull-right fa-chevron-right"></span>
<h2 class="list-group-item-heading">{{t 'migrations'}}</h2>
</a>
<a href="#/tools/reboot" class="list-group-item slide clearfix">
<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 'tools_shutdown_reboot'}}</h2>
<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>
</div>
<div class="separator"></div>
<div class="separator"></div>
<h2 style="font-weight:600; padding-left:0.5em;">{{t 'advanced'}}</h2>
<div class="list-group">
<a href="#/tools/monitor" class="list-group-item slide clearfix">
<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 'monitoring'}}</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/security-feed" class="list-group-item slide">
<span class="fa-chevron-right pull-right"></span>
<h2 class="list-group-item-heading">{{t 'tools_security_feed'}}</h2>
</a>
<a href="#/tools/appslists" class="list-group-item slide clearfix">
<span class="pull-right fa-chevron-right"></span>
<h2 class="list-group-item-heading">{{t 'appslists'}}</h2>
</a>
<a href="#/tools/ca" class="list-group-item slide">
<span class="fa-chevron-right pull-right"></span>
<h2 class="list-group-item-heading">{{t 'tools_download_ca'}}</h2>
<h2 class="list-group-item-heading">{{t 'tools_shutdown_reboot'}}</h2>
</a>
</div>

View file

@ -9,30 +9,31 @@
{{/if}}
</div>
<div class="actions-group">
<a href="javascript:void(null);" onclick="c.api('/logs/display?path={{#if log.name}}{{ log.name }}{{else}}{{ log.log_path }}{{/if}}&share', function(data) { $('div.loader').remove(); window.open(data.url, '_blank'); });" class="btn btn-success">
<span class="fa-cloud-upload"></span> {{t 'logs_share_with_yunopaste'}}
</a>
</div>
<div class="separator"></div>
{{#intl locales=locale}}
{{#if log.metadata}}
<div class="panel panel-default">
<div class="panel-heading">
<!-- PLEASE DON'T INDENT THIS CODE, IT IS PASTED ON YUNOPASTE -->
<h2 class="panel-title" id="description"><span class="fa-fw fa-info-circle"></span> {{ log.description }}</h2>
<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>{{t 'logs_end_with_error'}} {{log.metadata.error}}</dd>{{/if}}
{{#if log.metadata.error}}<dt>{{t 'logs_error'}}</dt> <dd>{{log.metadata.error}}</dd>{{/if}}
</dl>
</div>
</div>
{{#if log.metadata.error}}
<div class="alert alert-danger text-center">
<p>{{t 'operation_failed_explanation'}}</p>
</div>
{{/if}}
<!--
{{#if log.metadata.env}}
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="heading-context">
@ -52,17 +53,27 @@
</div>
</div>
{{/if}}
-->
{{/if}}
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-file-text"></span> {{#if log.metadata}}{{t 'logs'}}{{else}}{{log.log_path}}{{/if}}</h2>
<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

@ -22,7 +22,7 @@
<div id="collapse-{{key}}" class="panel-collapse{{#if @first}}{{else}} collapse{{/if}}" role="tabpanel" aria-labelledby="heading-{{key}}">
<div class="list-group">
{{#value}}
<a href="#/tools/logs/{{ name }}" class="list-group-item" title='{{formatTime started_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}'><small style="margin-right:20px;" >{{formatRelative started_at}}</small>
<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>
{{/value}}
</div>

View file

@ -11,7 +11,7 @@
<h2 class="panel-title"><span class="fa-fw fa-cogs"></span> {{t 'migrations_pending'}}
{{#if pending_migrations}}
<div class="btn-toolbar pull-right">
<a href="#/tools/migrations/run" class="btn btn-sm btn-success"><span class="fa-fw fa-play"></span> {{t 'run'}}</a>
<button class="btn btn-sm btn-success" data-action="run"><span class="fa-fw fa-play"></span> {{t 'run'}}</button>
</div>
{{/if}}
</h2>
@ -24,7 +24,7 @@
<h3 class="list-group-item-heading">
{{ number }}. {{ description }}
<div class="btn-toolbar pull-right">
<a href="#/tools/migrations/skip/{{ id }}" class="btn btn-xs btn-warning" style="color:white;"><span class="fa-fw fa-close"></span> {{t 'skip'}}</a>
<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 }}

View file

@ -1,283 +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/monitor">{{t 'monitoring'}}</a>
</div>
<div class="separator"></div>
{{#if status}}
<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 'hostname'}}</dt>
<dd>{{system.infos.hostname}}</dd>
<dt>{{t 'os'}}</dt>
<dd>{{ucwords system.infos.linux_distro}} {{system.infos.platform}} ({{system.infos.os_name}} {{system.infos.os_version}})</dd>
<dt>{{t 'uptime'}}</dt>
<dd>{{system.uptime}}</dd>
</dl>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-cogs"></span> {{t 'versions'}}</h2>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
{{#each versions}}
<dt>{{@key}}</dt>
<dd>{{version}} ({{repo}})</dd>
{{/each}}
</dl>
</div>
</div>
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
<span class="fa-fw fa-check-square-o"></span>
<a data-toggle="collapse" data-parent="#accordion" href="#check">{{t 'check'}}</a>
</h2>
</div>
<div id="check" class="panel-collapse collapse">
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{{t 'check_stmp'}}</dt>
<dd>{{network.check.smtp_check}}</dd>
<dt>{{t 'check_mx'}}</dt>
<dd>
{{#if network.check.mx_check.mx0}}
<ul>
{{#each network.check.mx_check}}
<li>{{this}}</li>
{{/each}}
</ul>
{{else}}
{{network.check.mx_check}}
{{/if}}
</dd>
</dl>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
<span class="fa-fw fa-cog"></span>
<a data-toggle="collapse" data-parent="#accordion" href="#system">{{t 'system'}}</a>
</h2>
</div>
<div id="system" class="panel-collapse collapse">
<div class="panel-body row">
<div class="col-md-4">
<h3>{{t 'memory'}}</h3>
<h4>{{t 'ram'}}</h4>
<table class="table table-condensed">
<tr>
<td>{{t 'used'}}</td>
<td>{{humanSize system.memory.ram.used}} ({{system.memory.ram.percent}} %)</td>
</tr>
<tr>
<td>{{t 'free'}}</td>
<td>{{humanSize system.memory.ram.free}}</td>
</tr>
<tr class="active">
<td>{{t 'total'}}</td>
<td>{{humanSize system.memory.ram.total}}</td>
</tr>
</table>
<h4>{{t 'swap'}}</h4>
<table class="table table-condensed">
<tr>
<td>{{t 'used'}}</td>
<td>{{humanSize system.memory.swap.used}} ({{system.memory.swap.percent}} %)</td>
</tr>
<tr>
<td>{{t 'free'}}</td>
<td>{{humanSize system.memory.swap.free}}</td>
</tr>
<tr class="active">
<td>{{t 'total'}}</td>
<td>{{humanSize system.memory.swap.total}}</td>
</tr>
</table>
</div>
<div class="col-md-4">
<h3>{{t 'cpu_load'}}</h3>
<table class="table table-condensed">
<tr>
<td>{{t 'count_min' "1"}}</td>
<td>{{system.cpu.load.min1}}</td>
</tr>
<tr>
<td>{{t 'count_min' "5"}}</td>
<td>{{system.cpu.load.min5}}</td>
</tr>
<tr>
<td>{{t 'count_min' "15"}}</td>
<td>{{system.cpu.load.min15}}</td>
</tr>
</table>
</div>
<div class="col-md-4">
<h3>{{t 'process'}}</h3>
<table class="table table-condensed">
<tr>
<td>{{t 'running'}}</td>
<td>{{system.process.running}}</td>
</tr>
<tr>
<td>{{t 'sleeping'}}</td>
<td>{{system.process.sleeping}}</td>
</tr>
<tr class="active">
<td>{{t 'total'}}</td>
<td>{{system.process.total}}</td>
</tr>
</table>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
<span class="fa-fw fa-cloud"></span>
<a data-toggle="collapse" data-parent="#accordion" href="#network">{{t 'network'}}</a>
</h2>
</div>
<div id="network" class="panel-collapse collapse">
<div class="panel-body">
<b>{{t 'public_ip'}}</b>{{network.infos.public_ip}}
<br>
<b>{{t 'gateway'}}</b>{{network.infos.gateway}}
<h3>{{t 'local_ip'}}</h3>
<table class="table table-condensed">
<thead>
<tr>
<th>{{t 'interface'}}</th>
<th>{{t 'ipv4'}}</th>
<th>{{t 'ipv6'}}</th>
</tr>
</thead>
<tbody>
{{#each network.infos.local_ip}}
<tr>
<td>{{@key}}</td>
<td>{{ ipv4 }}</td>
<td>{{ ipv6 }}</td>
</tr>
{{/each}}
</tbody>
</table>
<h3>{{t 'usage'}}</h3>
{{#each network.usage}}
<div class="clearfix">
<table class="table table-condensed">
<thead>
<tr>
<th>
<h4>
{{@key}}
<small>{{t 'time_since_update'}}{{humanTime time_since_update}}</small>
</h4>
</th>
<th>{{t 'bit_rate'}}</th>
<th>{{t 'cumulative_usage'}}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{t 'transmission'}}</td>
<td>{{bitRate tx time_since_update}}</td>
<td>{{humanSize cumulative_tx}}</td>
</tr>
<tr>
<td>{{t 'reception'}}</td>
<td>{{bitRate rx time_since_update}}</td>
<td>{{humanSize cumulative_rx}}</td>
</tr>
</tbody>
</table>
</div>
{{/each}}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
<span class="fa-fw fa-hdd-o"></span>
<a data-toggle="collapse" data-parent="#accordion" href="#disk">{{t 'disk'}}</a>
</h2>
</div>
<div id="disk" class="panel-collapse collapse">
<div class="panel-body">
{{#each disk}}
<div class="clearfix">
<h3>{{@key}}</h3>
<div class="row">
<div class="col-md-6">
<h4>{{t 'filesystem'}}</h4>
<table class="table table-condensed">
<tr>
<td>{{t 'fs_type'}}</td><td>{{ filesystem.fs_type }}</td>
</tr>
<tr>
<td>{{t 'mount_point'}}</td><td>{{ filesystem.mnt_point }}</td>
</tr>
<tr>
<td>{{t 'size'}}</td><td>{{humanSize filesystem.size }}</td>
</tr>
<tr>
<td>{{t 'used'}}</td><td>{{humanSize filesystem.used }}</td>
</tr>
<tr class="active">
<td>{{t 'available'}}</td><td>{{humanSize filesystem.avail }}</td>
</tr>
</table>
</div>
<div class="col-md-6">
<h4>{{t 'io'}} <small>{{t 'time_since_update'}}{{humanTime io.time_since_update }}</small></h4>
<table class="table table-condensed">
<tr>
<td>{{t 'read'}}</td><td>{{humanSize io.read_bytes }}</td>
</tr>
<tr>
<td>{{t 'write'}}</td><td>{{humanSize io.write_bytes }}</td>
</tr>
</table>
</div>
</div>
{{/each}}
</div>
</div>
</div>
</div><!-- .panel-group -->
{{else}}
<div class="alert alert-warning">
<span class="fa-exclamation-triangle"></span>
{{t 'monitoring_disabled'}}
<br>
{{t 'monitoring_check_glances'}}
</div>
{{/if}}

View file

@ -15,14 +15,14 @@
</div>
<div class="panel-body">
<p>
<a role="button" href="#/tools/reboot/reboot" class="btn btn-danger">
<button class="btn btn-danger" data-action="reboot">
<i class="fa-refresh"></i> {{t 'tools_reboot_btn'}}
</a>
</button>
</p>
<p>
<a role="button" href="#/tools/reboot/shutdown" class="btn btn-danger">
<button class="btn btn-danger" data-action="shutdown">
<i class="fa-power-off"></i> {{t 'tools_shutdown_btn'}}
</a>
</button>
</p>
</div>
</div>

View file

@ -1 +0,0 @@
<div class="alert alert-warning"><i class="fa-refresh"></i> {{t 'tools_rebooting'}}</div>

View file

@ -1,34 +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/ca">{{t 'tools_security_feed'}}</a>
</div>
<div class="separator"></div>
{{#if items}}
<div class="list-group">
{{#items}}
<div class="list-group-item">
<!-- <a href="{{url}}" class="fa-chevron-right pull-right"></a> -->
<h2 class="list-group-item-heading"><a href="{{url}}">{{title}}</a></h2>
<div class="list-group-item-text">
<p class="pub-date"><em>{{date}}</em></p>
{{{desc}}}
<p class="text-right">
<a href="{{url}}" role="button" class="btn btn-default">{{t 'read_more'}}</a>
</p>
</div>
</div>
{{/items}}
</div>
{{else}}
<div class="alert alert-success">
<span class="fa-thumbs-o-up"></span> {{t 'tools_security_feed_no_items'}}
</div>
{{/if}}
<div>
<a role="button" href="{{url.web}}" class="btn btn-success" target="_blank"><span class="fa-list"></span> {{t 'tools_security_feed_view_items'}}</a>
<a role="button" href="{{url.rss}}" class="btn btn-warning" target="_blank"><span class="fa-rss"></span> {{t 'tools_security_feed_subscribe_rss'}}</a>
</div>

View file

@ -1 +0,0 @@
<div class="alert alert-warning"><i class="fa-power-off"></i> {{t 'tools_shuttingdown'}}</div>

View file

@ -20,7 +20,7 @@
{{/system}}
</div>
<div class="panel-footer">
<a href="#/upgrade/system" role="button" class="btn btn-success">{{t 'system_upgrade_all_packages_btn'}}</a>
<button class="btn btn-success" data-upgrade="system">{{t 'system_upgrade_all_packages_btn'}}</button>
</div>
{{else}}
<div class="panel-body">
@ -37,14 +37,14 @@
<div class="list-group">
{{#apps}}
<div class="list-group-item clearfix">
<a href="#/upgrade/apps/{{id}}" role="button" class="btn btn-success pull-right">{{t 'system_upgrade_btn'}}</a>
<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>
<p class="list-group-item-text">{{t 'from_to' current_version new_version}}</p>
<span class="list-group-item-text">{{t 'from_to' current_version new_version}}</span>
</div>
{{/apps}}
</div>
<div class="panel-footer">
<a role="button" href="#/upgrade/apps" class="btn btn-success">{{t 'system_upgrade_all_applications_btn'}}</a>
<button class="btn btn-success" data-upgrade="apps">{{t 'system_upgrade_all_applications_btn'}}</button>
</div>
{{else}}
<div class="panel-body">

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="#/update">{{t 'system_update'}}</a>
<a href="#/upgrade">{{t 'system_upgrade'}}</a>
</div>
<div class="separator"></div>
{{#if logs}}
<pre id="upgrade-log" class="upgrade-log log">
{{#logs}}
{{.}}
{{/logs}}
</pre>
<button data-paste-content="#upgrade-log"><i class="fa-cloud-upload"></i> {{t 'upload'}}</button>
{{else}}
{{t 'no_log'}}
{{/if}}

View file

@ -3,7 +3,7 @@
<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 'group_permissions'}}</a>
<a href="#/groups" class="hidden-xs">{{t 'groups_and_permissions'}}</a>
<a href="#/groups/create">{{t 'group_new'}}</a>
</div>

View file

@ -17,10 +17,10 @@
<span class="label label-default label-removable">
<span class="fa-fw fa-{{icon}}"></span>
{{text}}
<a role="button" data-type="{{type}}s" data-operation="remove" data-item="{{value}}" data-group="{{group}}" class="group-update">
<button data-type="{{type}}s" data-action="remove" data-item="{{value}}" data-group="{{group}}">
<span class="fa-close" style="margin-left:5px"></span>
<span class="sr-only">{{t 'delete'}}</span>
</a>
</button>
</span>
{{/inline}}
@ -35,7 +35,7 @@
</button>
<ul class="dropdown-menu">
{{#each inv}}
<li><a href="#" data-type="{{../type}}s" data-operation="add" data-item="{{.}}" data-group="{{../group}}" class="group-update">{{call ../display .}}</a></li>
<li><button data-type="{{../type}}s" data-action="add" data-item="{{.}}" data-group="{{../group}}" >{{call ../display .}}</button></li>
{{/each}}
</ul>
</div>
@ -53,10 +53,10 @@
<span class="fa-fw fa-group"></span> {{#if special}}{{t (concat 'group_' @key)}}{{else}}{{t 'group'}} "{{ucwords @key}}"{{/if}}
</a>
{{#unless special}}
<a href="#/groups/{{@key}}/delete" role="button" class="group-delete">
<button class="group-delete" data-action="delete-group" data-group="{{@key}}">
<span class="fa-close"></span>
<span class="sr-only">{{t 'delete'}}</span>
</a>
</button>
{{/unless}}
</h2>
</div>
@ -125,7 +125,7 @@
{{#each groups}}
{{#if primary}}
{{#unless (or permissions display)}}
<li><a href="#" data-user="{{@key}}" class="group-add-user">{{@key}}</a></li>
<li><button data-action="add-user-specific-permission" data-user="{{@key}}">{{@key}}</button></li>
{{/unless}}
{{/if}}
{{/each}}

View file

@ -62,7 +62,7 @@
</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>
<a role="button" href="#/users/{{username}}/delete" class="btn btn-danger slide back"><span class="fa-trash-o"/> {{t 'delete'}}</a>
<button class="btn btn-danger" data-action="delete" data-user="{{username}}"><span class="fa-trash-o"/> {{t 'delete'}}</a>
</span>
</div>
</div>

120
tests/test_i18n_keys.py Normal file
View file

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
import os
import re
import glob
import json
import yaml
import subprocess
###############################################################################
# Find used keys in python code #
###############################################################################
def find_expected_string_keys():
# Try to find :
# y18n.t("foo" +) # (the real key is a concatenation of 'foo' with something else)
# y18n.t("foo") or y18n.t('foo', ...) # actual full key
js_p1 = re.compile(r'y18n\.t\(\s*[\"\'](\w+)[\"\']\s*[\,\)]')
js_p2 = re.compile(r'y18n\.t\(\s*[\"\'](\w+)[\"\']\s*\+')
js_files = glob.glob("../src/js/yunohost/controllers/*.js")
js_files.extend(glob.glob("../src/js/yunohost/*.js"))
for js_file in js_files:
content = open(js_file).read()
for m in js_p1.findall(content):
yield m
for m in js_p2.findall(content):
yield m
# In views we have stuff like {{t 'foo' arg}}
views_p1 = re.compile(r'{{t\s*[\"\'](\w+)[\"\']')
# Somes are inside {{ (t 'foo')))
views_p2 = re.compile(r'\(t\s*[\"\'](\w+)[\"\']\)')
views_p3 = re.compile(r't \(concat\s*[\"\'](\w+)[\"\']')
views_p4 = re.compile(r'data-y18n=[\"\'](\w+)[\"\']')
view_files = glob.glob("../src/*.html")
view_files.extend(glob.glob("../src/views/*.ms"))
view_files.extend(glob.glob("../src/views/*/*.ms"))
for view_file in view_files:
content = open(view_file).read()
for m in views_p1.findall(content):
yield m
for m in views_p2.findall(content):
yield m
for m in views_p3.findall(content):
yield m
for m in views_p4.findall(content):
yield m
# App maintenance state
for state in ['maintained', 'orphaned', 'request_adoption', 'request_help','unmaintained']:
yield state
yield state + "_details"
# Service states
for state in ['active', 'disabled', 'enabled', 'inactive']:
yield state
yield "confirm_cert_"
###############################################################################
# Load en locale json keys #
###############################################################################
def keys_defined_for_en():
return json.loads(open("../src/locales/en.json").read()).keys()
###############################################################################
# Compare keys used and keys defined #
###############################################################################
expected_string_keys = set(find_expected_string_keys())
keys_defined = set(keys_defined_for_en())
def test_undefined_i18n_keys():
undefined_keys = expected_string_keys.difference(keys_defined)
undefined_keys = sorted(undefined_keys)
undefined_keys = [k for k in undefined_keys if not k.endswith("_")]
return undefined_keys
if undefined_keys:
raise Exception("Those i18n keys should be defined in en.json:\n"
" - " + "\n - ".join(undefined_keys))
def test_unused_i18n_keys():
unused_keys = keys_defined.difference(expected_string_keys)
unused_keys = sorted(unused_keys)
partial_match = [k for k in unused_keys if any(k.startswith(m) for m in expected_string_keys if m.endswith("_")) ]
unused_keys_2 = []
for k in unused_keys:
if not any(k.startswith(m) for m in expected_string_keys if m.endswith("_")):
unused_keys_2.append(k)
return unused_keys_2
if unused_keys:
raise Exception("Those i18n keys appears unused:\n"
" - " + "\n - ".join(unused_keys))
print("--------- undefined")
print()
print(test_undefined_i18n_keys())
print("--------- unused")
print()
print(test_unused_i18n_keys())