Merge pull request #279 from YunoHost/app-categories

WIP : App categories
This commit is contained in:
Alexandre Aubin 2019-12-12 19:11:13 +01:00 committed by GitHub
commit 1f47ce7927
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 246 additions and 115 deletions

View file

@ -631,8 +631,12 @@ input[type='radio'].nice-radio {
background-color: darkorchid; background-color: darkorchid;
} }
.app-category-card {
text-align: center;
}
// only one card for small screens // only one card for small screens
.app-card { .app-card, .app-category-card {
width: 100%; width: 100%;
.btn-group { .btn-group {
width: 100%; width: 100%;
@ -653,13 +657,16 @@ input[type='radio'].nice-radio {
} }
} }
.app-state {
font-size: 10px;
}
.app-title { .app-title, .app-category-title {
margin-top: 5px; margin-top: 5px;
font-weight: 600; font-weight: 600;
} }
.app-card-desc { .app-card-desc, .app-category-card-desc {
height: 6rem; height: 6rem;
overflow: hidden; overflow: hidden;
} }
@ -670,6 +677,7 @@ input[type='radio'].nice-radio {
margin-bottom: 3px; margin-bottom: 3px;
margin-right: 7px; margin-right: 7px;
margin-top: -5px; margin-top: -5px;
height: 18px;
} }
.auto-width { .auto-width {
@ -686,6 +694,7 @@ input[type='radio'].nice-radio {
.app-card .panel-body { .app-card .panel-body {
padding: 1.5rem; padding: 1.5rem;
padding-bottom: 0.5rem;
h3 { h3 {
margin-top: 0; margin-top: 0;
@ -697,6 +706,33 @@ 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 **/ /** Groups View **/
#view-groups { #view-groups {
@ -806,7 +842,7 @@ input[type='radio'].nice-radio {
} }
// display 2 cards between 640 and 992px // display 2 cards between 640 and 992px
.app-card { .app-card, .app-category-card {
width: 47.9%; width: 47.9%;
margin: 1%; margin: 1%;
} }
@ -908,7 +944,7 @@ input[type='radio'].nice-radio {
// bootstrap breakpoint for large screen is 992px // bootstrap breakpoint for large screen is 992px
@media screen and (min-width: 992px) { @media screen and (min-width: 992px) {
.app-card { .app-card, .app-category-card {
// display 3 cards by row // display 3 cards by row
width: 31.3%; width: 31.3%;
margin: 1%; margin: 1%;

View file

@ -10,7 +10,7 @@
// List installed apps // List installed apps
app.get('#/apps', function (c) { app.get('#/apps', function (c) {
c.api('GET', '/apps?installed', {}, function(data) { c.api('GET', '/apps?full', {}, function(data) {
var apps = data['apps']; var apps = data['apps'];
c.arraySortById(apps); c.arraySortById(apps);
c.view('app/app_list', {apps: apps}); c.view('app/app_list', {apps: apps});
@ -70,7 +70,7 @@
} }
else else
{ {
return 'success'; return 'info';
} }
} }
@ -107,101 +107,145 @@
} }
} }
// List available apps
app.get('#/apps/install', function (c) {
c.api('GET', '/apps', {}, function (data) {
c.api('GET', '/apps?raw', {}, function (dataraw) {
var apps = []
$.each(data['apps'], function(k, v) {
app = dataraw[v['id']];
app.level = parseInt(app.level);
if (app.high_quality && app.level > 7) // Display catalog home page where users chooses to browse a specific category
{ app.get('#/apps/catalog', function (c) {
app.state = "high-quality"; c.api('GET', '/appscatalog?full&with_categories', {}, function (data) {
} c.view('app/app_catalog_home', {categories: data["categories"]}, function() {
if ( app.maintained === false ) // Configure layout / rendering for app-category-cards
{ $('#category-selector').isotope({
app.maintained = "orphaned"; itemSelector: '.app-category-card',
} layoutMode: 'fitRows',
else if ( app.maintained === true ) transitionDuration: 200
{ });
app.maintained = "maintained"; });
} });
});
app.manifest.maintainer = extractMaintainer(app.manifest); // Display app catalog for a specific category
var isWorking = (app.state === 'working' || app.state === "high-quality") && app.level > 0; 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 // Ignore not working apps
if (!v['id'].match(/__[0-9]{1,5}$/) && (app.state !== 'notworking')) { if (app.state === 'notworking') { return; }
app.installable = (!v.installed || app.manifest.multi_instance) // Ignore apps not in this category
app.levelFormatted = isNaN(app.level) ? '?' : app.level; if ((category_id !== "all") && (app.category !== category_id)) { return; }
app.levelColor = levelToColor(app.level); app.id = app.manifest.id;
app.stateColor = stateToColor(app.state); app.level = parseInt(app.level);
app.maintainedColor = maintainedStateToColor(app.maintained);
app.installColor = combineColors(app.stateColor, app.levelColor);
app.updateDate = app.lastUpdate * 1000 || 0; if (app.high_quality && app.level > 7)
app.isSafe = (app.installColor !== 'danger'); {
app.isWorking = isWorking ? "isworking" : "notFullyWorking"; app.state = "high-quality";
app.isHighQuality = (app.state === "high-quality") ? "isHighQuality" : ""; }
app.decentQuality = (app.level > 4)?"decentQuality":"badQuality"; if ( app.maintained === false )
{
app.maintained = "orphaned";
}
else if ( app.maintained === true )
{
app.maintained = "maintained";
}
jQuery.extend(app, v); app.manifest.maintainer = extractMaintainer(app.manifest);
apps.push(app); var isWorking = (app.state === 'working' || app.state === "high-quality") && app.level > 0;
}
app.installable = (!app.installed || app.manifest.supports_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 // Default filter is 'decent quality apps'
c.arraySortById(apps); cardGrid.isotope({ filter: '.decentQuality' });
// setup filtering of apps once the view is loaded $(".subtag-selector button").on("click", function() {
function setupFilterEvents () { var selector = $(this).parent();
// Uses plugin isotope to filter apps (we could had ordering to) $("button", selector).removeClass("active");
var cardGrid = jQuery('.grid').isotope({ $(this).addClass("active");
itemSelector: '.app-card', cardGrid.isotope({ filter: filterApps });
layoutMode: 'fitRows', });
transitionDuration: 200
});
filterByClassAndName = function () { filterApps = function () {
var input = jQuery("#filter-app-cards").val().toLowerCase();
var inputMatch = (jQuery(this).find('.app-title').text().toLowerCase().indexOf(input) > -1);
var filterClass = jQuery("#dropdownFilter").attr("data-filter"); // Check text search
var classMatch = (filterClass === '*') ? true : jQuery(this).hasClass(filterClass); var input = jQuery("#filter-app-cards").val().toLowerCase();
return inputMatch && classMatch; if (jQuery(this).find('.app-title').text().toLowerCase().indexOf(input) <= -1) return false;
},
// Default filter is 'decent quality apps' // Check subtags
cardGrid.isotope({ filter: '.decentQuality' }); 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() { // Check quality criteria
// change dropdown label var class_ = jQuery("#dropdownFilter").data("filter");
jQuery('#app-cards-list-filter-text').text(jQuery(this).find('.menu-item').text()); if ((class_ !== '*') && (! jQuery(this).hasClass(class_))) return false;
// change filter attribute
jQuery('#dropdownFilter').attr("data-filter", jQuery(this).attr("data-filter"));
// filter !
cardGrid.isotope({ filter: filterByClassAndName });
});
jQuery("#filter-app-cards").on("keyup", function() { return true;
cardGrid.isotope({ filter: filterByClassAndName }); },
});
};
// render jQuery('.dropdownFilter').on('click', function() {
c.view('app/app_list_install', {apps: apps}, setupFilterEvents); // 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 });
});
};
// render
c.view('app/app_catalog_category', {apps: apps, category: category}, setupFilterEvents);
});
}); });
}); });
// Get app information // Get app information
app.get('#/apps/:app', function (c) { app.get('#/apps/:app', function (c) {
c.api('GET', '/apps/'+c.params['app']+'?raw', {}, function(data) { c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(data) {
c.api('GET', '/users/permissions', {}, function(data_permissions) { c.api('GET', '/users/permissions', {}, function(data_permissions) {
// Permissions // Permissions
@ -506,9 +550,9 @@
// App installation form // App installation form
app.get('#/apps/install/:app', function (c) { app.get('#/apps/install/:app', function (c) {
c.api('GET', '/apps?raw', {}, function(data) { c.api('GET', '/appscatalog?full', {}, function(data) {
var app_name = c.params["app"]; var app_name = c.params["app"];
var app_infos = data[app_name]; var app_infos = data["apps"][app_name];
if (app_infos['state'] === "validated") if (app_infos['state'] === "validated")
{ {
app_infos['state'] = "official"; app_infos['state'] = "official";
@ -535,7 +579,7 @@
{ {
c.appInstallForm( c.appInstallForm(
c.params['app'], c.params['app'],
data[c.params['app']].manifest, app_infos.manifest,
c.params c.params
); );
} }
@ -622,7 +666,7 @@
// Get app change label page // Get app change label page
app.get('#/apps/:app/changelabel', function (c) { app.get('#/apps/:app/changelabel', function (c) {
c.api('GET', '/apps/'+c.params['app']+'?raw', {}, function(app_data) { c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(app_data) {
data = { data = {
id: c.params['app'], id: c.params['app'],
label: app_data.settings.label, label: app_data.settings.label,
@ -641,7 +685,7 @@
// Get app change URL page // Get app change URL page
app.get('#/apps/:app/changeurl', function (c) { app.get('#/apps/:app/changeurl', function (c) {
c.api('GET', '/apps/'+c.params['app']+'?raw', {}, function(app_data) { c.api('GET', '/apps/'+c.params['app']+'?full', {}, function(app_data) {
c.api('GET', '/domains', {}, function(domain_data) { c.api('GET', '/domains', {}, function(domain_data) {
// Display a list of available domains // Display a list of available domains

View file

@ -5,6 +5,7 @@
"advanced": "Advanced", "advanced": "Advanced",
"remove": "Remove", "remove": "Remove",
"administration_password": "Administration password", "administration_password": "Administration password",
"all": "All",
"all_apps": "All apps", "all_apps": "All apps",
"api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?", "api_not_responding": "The YunoHost API is not responding. Maybe 'yunohost-api' is down or got restarted?",
"app_change_label": "Change Label", "app_change_label": "Change Label",
@ -23,10 +24,12 @@
"app_no_actions": "This application doesn't have any actions", "app_no_actions": "This application doesn't have any actions",
"app_repository": "Application origin: ", "app_repository": "Application origin: ",
"app_state": "Application state: ", "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_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": "not working",
"app_state_notworking_explanation": "This maintainer of this app declared it as 'not working'. IT WILL BREAK YOUR SYSTEM!", "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": "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_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": "working",
@ -51,6 +54,7 @@
"begin": "Begin", "begin": "Begin",
"both": "Both", "both": "Both",
"cancel": "Cancel", "cancel": "Cancel",
"catalog": "Catalog",
"check": "Check", "check": "Check",
"close": "Close", "close": "Close",
"configuration": "Configuration", "configuration": "Configuration",
@ -218,8 +222,9 @@
"only_decent_quality_apps": "Only decent quality apps", "only_decent_quality_apps": "Only decent quality apps",
"open": "Open", "open": "Open",
"operations": "Operations", "operations": "Operations",
"orphaned": "not maintained", "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!", "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",
"password": "Password", "password": "Password",
"password_confirmation": "Password confirmation", "password_confirmation": "Password confirmation",
"password_description": "Password must be at least %s characters long.", "password_description": "Password must be at least %s characters long.",

View file

@ -1,16 +1,20 @@
<div class="btn-breadcrumb"> <div class="btn-breadcrumb">
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a> <a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/apps">{{t 'applications'}}</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>
<div class="separator"></div> <div class="separator"></div>
<div class="input-group" id="app-filter-input"> <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"/> <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"> <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> <span id="app-cards-list-filter-text">{{t 'only_decent_quality_apps'}}</span> <span class="caret"></span>
</button> </button>
<ul id="dropdownFilter" class="dropdown-menu" data-filter="decentQuality" role="menu"> <ul id="dropdownFilter" class="dropdown-menu" data-filter="decentQuality" role="menu">
@ -24,32 +28,51 @@
<div class="separator"></div> <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}} {{#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"> <div class="panel-body">
<h2 class="app-title">{{name}}</h2> <h2 class="app-title">
<div class="category"> {{manifest.name}}
<span class="label label-{{stateColor}} label-as-badge app-state" title="{{t (concat 'app_state_' state '_explanation') }}">{{t (concat 'app_state_' state) }}</span> {{#if (eq state 'working') }}
<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> {{#if (eq decentQuality 'badQuality')}}
<span class="label label-{{maintainedColor}} label-as-badge maintained-status" title="{{t (concat maintained '_details') }}"> {{t maintained}}</span> <span class="label label-warning label-as-badge app-state" title="{{t 'app_state_low_quality_explanation' }}">{{t 'app_state_low_quality' }}</span>
</div> {{/if}}
<div class="app-card-desc">{{description}}</div> {{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>
<div class="app-card-date-maintainer"> <div class="app-card-date-maintainer">
<i class="fa-refresh"></i> {{formatDate updateDate day="numeric" month="long" year="numeric"}} - {{#if (eq maintainedColor 'danger') }}
<span title="{{t 'current_maintainer_title'}}" class="maintained"></span><i class="fa-user"></i> {{manifest.maintainer}}</span> <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>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<a href="{{git.url}}" target="_BLANK" type="button" role="button" class="btn btn-default col-xs-4"> <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>
<a href="{{git.url}}/blob/master/README.md" target="_BLANK" type="button" role="button" class="btn btn-default col-xs-4"> <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> </a>
{{#installable}} {{#installable}}
<a href="#/apps/install/{{id}}" type="button" role="button" class="btn btn-{{installColor}} col-xs-4 active"> <a href="#/apps/install/{{manifest.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}} <i class="fa-fw fa-plus"></i> {{t 'install'}}{{^isSafe}} <i class="fa-fw fa-warning"></i>{{/isSafe}}
</a> </a>
{{/installable}} {{/installable}}
{{^installable}} {{^installable}}
@ -66,7 +89,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p class="alert alert-warning"> <p class="alert alert-warning">
<span class="fa-warning"></span> <span class="fa-fw fa-warning"></span>
{{t 'confirm_install_custom_app'}} {{t 'confirm_install_custom_app'}}
</p> </p>
<form action="#/apps/install/custom" method="POST" class="form-horizontal"> <form action="#/apps/install/custom" method="POST" class="form-horizontal">
@ -75,7 +98,7 @@
<div class="col-sm-12"> <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-_.]+[/]?$"> <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"> <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> </p>
</div> </div>
</div> </div>

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">
{{#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}}
<a class="app-category-card" href="#/apps/catalog/all">
<div class="panel-body">
<h2 class="app-category-title" style="padding-top: 3em'"><span class="fa-fw fa-search"></span> {{t 'all_apps'}}</h2>
</div>
</a>
</div>

View file

@ -22,7 +22,7 @@
<dt>{{t 'version'}}</dt> <dt>{{t 'version'}}</dt>
<dd>{{version}}</dd> <dd>{{version}}</dd>
<dt>{{t 'multi_instance'}}</dt> <dt>{{t 'multi_instance'}}</dt>
<dd>{{manifest.multi_instance}}</dd> <dd>{{supports_multi_instance}}</dd>
<dt>{{t 'install_time'}}</dt> <dt>{{t 'install_time'}}</dt>
<dd>{{formatTime install_time day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}</dd> <dd>{{formatTime install_time day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}</dd>
{{#if settings.domain}} {{#if settings.domain}}
@ -64,7 +64,7 @@
<hr> <hr>
<div class="container"> <div class="container">
<p>{{t 'app_info_changeurl_desc' settings.domain}}</p> <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"> <a href="#/apps/{{settings.id}}/changeurl" role="button" class="btn btn-info slide">
<span class="fa-exchange"></span> {{t 'app_change_url'}} <span class="fa-exchange"></span> {{t 'app_change_url'}}
</a> </a>

View file

@ -1,8 +1,8 @@
<div class="btn-breadcrumb"> <div class="btn-breadcrumb">
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a> <a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/apps">{{t 'applications'}}</a> <a href="#/apps">{{t 'applications'}}</a>
<a href="#/apps/install">{{t 'install'}}</a> <a href="#/apps/catalog">{{t 'catalog'}}</a>
<a href="#/apps/install/{{id}}">{{manifest.name}}</a> <a href="#/apps/install/{{id}}">{{t 'install_name' manifest.name}}</a>
</div> </div>
<div class="separator"></div> <div class="separator"></div>
@ -18,7 +18,7 @@
<dt>{{t 'id'}}</dt> <dt>{{t 'id'}}</dt>
<dd>{{id}}</dd> <dd>{{id}}</dd>
<dt>{{t 'description'}}</dt> <dt>{{t 'description'}}</dt>
<dd>{{description}}</dd> <dd>{{manifest.description}}</dd>
{{#displayLicense}} {{#displayLicense}}
<dt>{{t 'license'}}</dt> <dt>{{t 'license'}}</dt>
<dd>{{manifest.license}}</dd> <dd>{{manifest.license}}</dd>

View file

@ -4,7 +4,7 @@
</div> </div>
<div class="actions-group"> <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'}} <span class="fa-plus"></span> {{t 'install'}}
</a> </a>
</div> </div>
@ -16,7 +16,7 @@
<a href="#/apps/{{id}}" class="list-group-item slide" title="{{t 'infos'}}"> <a href="#/apps/{{id}}" class="list-group-item slide" title="{{t 'infos'}}">
<span class="fa-chevron-right pull-right"></span> <span class="fa-chevron-right pull-right"></span>
<h2 class="list-group-item-heading"> <h2 class="list-group-item-heading">
{{label}} <small>{{name}}</small> {{settings.label}} <small>{{name}}</small>
</h2> </h2>
<p class="list-group-item-text">{{description}}</p> <p class="list-group-item-text">{{description}}</p>
</a> </a>