Dynamic app pages

This commit is contained in:
Taylor Helsper 2022-07-02 16:54:04 -05:00
parent a1b15aca5a
commit ff61dd6e7b
11 changed files with 882 additions and 0 deletions

View File

@ -0,0 +1,46 @@
<!DOCTYPE html lang="en">
<head>
<title>{{ title }}</title>
{% include 'includes/head.html' %}
</head>
<body>
{% include 'includes/logo_header.html' %}
<div class="mynode_back_div">
<a class="ui-button ui-widget ui-corner-all mynode_back" href="/"><span class="ui-icon ui-icon-home"></span>home&nbsp;</a>
</div>
<div class="main_header">{{app.name}} (custom page - remove)</div>
<br/>
<div class="app_tile_row">
<div class="info_tile">
<div class="info_tile_header">Status</div>
<div class="info_tile_contents">
<table class="info_table">
<tr>
<th>Status</th>
<td>{{app_status}}</td>
</tr>
<tr>
<th>Actions</th>
<td>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 100px;" href="#">Open</a>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 100px;" href="#">Button A</a>
<a class="ui-button ui-widget ui-corner-all mynode_button_small" style="width: 100px;" href="#">Button B</a>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="instructions">
<div class="instructions-header">Instructions</div>
<ol class="instructions-steps">
<li>Custom instructions?</li>
</ol>
</div>
</body>
</html>

View File

@ -0,0 +1,3 @@
FOLDERS AND FILES IN THIS FOLDER ARE DYNAMICALLY ADDED
Do not edit files within this folder. They will be overwritten during app install process.

View File

@ -0,0 +1,32 @@
from flask import Blueprint, render_template, redirect, request, url_for
from flask import current_app
from user_management import check_logged_in
from device_info import *
from application_info import *
import subprocess
import re
import os
mynode_generic_app = Blueprint('mynode_generic_app',__name__)
# This is the generic app page handler. Specific ones can override this.
@mynode_generic_app.route('/app/<name>/info')
def app_generic_info_page(name):
check_logged_in()
app = get_application(name)
if not is_application_valid(name) or app == None:
flash("Application is invalid", category="error")
return redirect("/apps")
app_status = get_application_status(name)
# Load page
templateData = {
"title": "myNode - " + app["name"],
"ui_settings": read_ui_settings(),
"app_status": app_status,
"app": app
}
return render_template('/app/generic_app.html', **templateData)

View File

@ -0,0 +1,58 @@
from flask import Blueprint, render_template, redirect, request
from user_management import check_logged_in
from device_info import *
from application_info import *
import subprocess
import re
import os
mynode_marketplace = Blueprint('mynode_marketplace',__name__)
### Page functions
@mynode_marketplace.route("/marketplace")
def marketplace_page():
check_logged_in()
t1 = get_system_time_in_ms()
apps = get_all_applications(order_by="alphabetic")
t2 = get_system_time_in_ms()
categories = [{"name": "bitcoin_app", "title": "Bitcoin Apps"},
{"name": "lightning_app", "title": "Lightning Apps"},
{"name": "uncategorized", "title": "Uncategorized"}
]
# Load page
templateData = {
"title": "myNode Marketplace",
"ui_settings": read_ui_settings(),
"load_time": t2-t1,
"product_key_skipped": skipped_product_key(),
"categories": categories,
"apps": apps,
"has_customized_app_versions": has_customized_app_versions(),
}
return render_template('marketplace.html', **templateData)
@mynode_marketplace.route("/marketplace/<app_name>")
def marketplace_app_page(app_name):
check_logged_in()
app = get_application(app_name)
if not is_application_valid(app_name) or app == None:
flash("Application is invalid", category="error")
return redirect("/marketplace")
app_status = get_application_status(app_name)
# Load page
templateData = {
"title": "myNode - " + app["name"],
"ui_settings": read_ui_settings(),
"product_key_skipped": skipped_product_key(),
"app_status": app_status,
"app": app
}
return render_template('/marketplace_app.html', **templateData)

View File

@ -0,0 +1,9 @@
/*!
HesGallery v1.5.1
Copyright (c) 2018-2019 Artur Medrygal <medrygal.artur@gmail.com>
Product under MIT licence
*/#hgallery{display:block;content:'';position:fixed;top:0;left:0;width:100%;height:100vh;background-color:rgba(0,0,0,.9);visibility:hidden;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;opacity:0;-webkit-transition:.3s;transition:.3s;z-index:99999}.hg-disable-scrolling{overflow:hidden!important}#hg-bg{position:absolute;top:0;left:0;width:100%;height:100vh;z-index:1}#hg-bg::after{content:'';position:absolute;display:block;top:20px;right:20px;width:30px;height:30px;background-image:url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRkZGRkZGIiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxwYXRoIGQ9Ik0xOSA2LjQxTDE3LjU5IDUgMTIgMTAuNTkgNi40MSA1IDUgNi40MSAxMC41OSAxMiA1IDE3LjU5IDYuNDEgMTkgMTIgMTMuNDEgMTcuNTkgMTkgMTkgMTcuNTkgMTMuNDEgMTJ6Ii8+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+Cjwvc3ZnPg==);background-position:center;background-size:contain;cursor:pointer;opacity:.8}#hg-bg::after:hover{background-color:#fff}#hgallery.open{visibility:visible!important;opacity:1}#hg-pic-cont{max-width:calc(70% - 40px);max-height:90vh;cursor:default;z-index:12;position:relative;background-color:#fff;-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;-webkit-transform:scale(1);transform:scale(1)}#hg-pic-cont.hg-transition{-webkit-transform:scale(.1);transform:scale(.1)}#hg-subtext{color:#ddd;font-size:14px;position:absolute;display:block;left:5px;top:calc(100% + 6px)}#hg-howmany{color:#aaa;font-size:14px;position:absolute;display:block;right:5px;bottom:-20px}#hg-pic{width:auto;height:auto;min-height:100px;min-width:100px;max-width:100%;max-height:90vh;-webkit-box-sizing:border-box;box-sizing:border-box;display:block;cursor:default;-o-object-fit:contain;object-fit:contain;margin:0}#hg-pic:hover{-webkit-transform:none;transform:none;-webkit-box-shadow:none;box-shadow:none}#hgallery button{position:absolute;display:block;margin:auto 0;width:60px;height:60px;z-index:11;cursor:pointer;background-color:transparent;border:0;outline:0;opacity:0;-webkit-transition:opacity .3s,visibility .3s;transition:opacity .3s,visibility .3s;visibility:hidden}#hgallery button img{width:100%;height:100%;-o-object-fit:contain;object-fit:contain}#hgallery.open button{visibility:visible;opacity:.7}#hgallery button:hover{opacity:1}#hgallery button#hg-prev{left:10px;-webkit-transform:rotate(180deg);transform:rotate(180deg)}#hgallery button#hg-prev:active{left:7px}#hgallery button#hg-next{right:10px}#hgallery button#hg-next:active{right:7px}#hgallery #hg-next-onpic,#hgallery #hg-prev-onpic{position:absolute;top:0;left:0;width:34%;height:100%;cursor:pointer}#hgallery #hg-next-onpic{right:0;left:auto;width:66%}.hg-unvisible{opacity:0!important;visibility:hidden}@media (max-width:1100px){#hg-pic-cont{max-width:calc(100% - 40px)}}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,98 @@
//
// These functions are used both by the manage_apps page and individual app pages
//
// ==========================================
// Manage running apps
// ==========================================
function restart(name, short_name) {
if ( confirm("Are you sure you want to restart "+name+"?\n\nRestarting services like Bitcoin or LND may have side effects. If so, restart the device.") ) {
$('#loading_spinner_message').html("Restarting...");
$('#loading_spinner_overlay').fadeIn();
window.location.href='/apps/restart-app?app='+short_name;
}
}
// ==========================================
// Manage app installations
// ==========================================
function upgrade(name, short_name) {
if ( confirm("Are you sure you want to upgrade "+name+"? This will reboot your device.") ) {
$('#loading_spinner_message').html("Upgrading...");
$('#loading_spinner_overlay').fadeIn();
window.location.href='/settings/reinstall-app?app='+short_name;
}
}
function reinstall(name, short_name) {
if ( confirm("Are you sure you want to re-install "+name+"? This will reboot your device.") ) {
$('#loading_spinner_message').html("Re-installing...");
$('#loading_spinner_overlay').fadeIn();
window.location.href='/settings/reinstall-app?app='+short_name;
}
}
function install(name, short_name) {
if ( confirm("Are you sure you want to install "+name+"? This will reboot your device.") ) {
$('#loading_spinner_message').html("Installing...");
$('#loading_spinner_overlay').fadeIn();
window.location.href='/settings/reinstall-app?app='+short_name;
}
}
function uninstall(name, short_name) {
if ( confirm("Are you sure you want to uninstall "+name+"? ") ) {
$('#loading_spinner_message').html("Uninstalling...");
$('#loading_spinner_overlay').fadeIn();
window.location.href='/settings/uninstall-app?app='+short_name;
}
}
// ==========================================
// Toggle enable/disable functions
// ==========================================
function get_custom_enable_message(short_name) {
message = "";
if (short_name == "electrs") {
message = "Enabling Electrum Server will take several days to fully sync for \
the first time. Your myNode may run slowly during this period.";
} else if (short_name == "vpn") {
message = "Enabling VPN will set your IP to a static IP rather than a dynamic one via DHCP. \
The initial setup may take about an hour.";
} else if (short_name == "dojo") {
message = "Enabling Dojo for the first time will reboot your device and install Dojo.";
}
if (message != "") {
message += "<br/><br/>";
}
return message;
}
function toggleEnabled(short_name, full_name, is_enabled) {
//enabled = application_data[short_name]["is_enabled"];
//full_name = application_data[short_name]["name"];
if ( is_enabled ) {
// Disabling
openConfirmDialog("confirm-dialog",
"Disable "+full_name,
"Are you sure you want to disable "+full_name+"?",
function(){
$( this ).dialog( "close" );
$('#loading_spinner_overlay').fadeIn();
window.location.href="/toggle-enabled?app="+short_name
});
} else {
custom_message = "";
// Enabling
openConfirmDialog("confirm-dialog",
"Enable "+full_name,
get_custom_enable_message(short_name) +
"Are you sure you want to enable "+full_name+"?",
function(){
$( this ).dialog( "close" );
$('#loading_spinner_overlay').fadeIn();
window.location.href="/toggle-enabled?app="+short_name
});
}
}

View File

@ -0,0 +1,3 @@
FOLDERS AND FILES IN THIS FOLDER ARE DYNAMICALLY ADDED
Do not edit files within this folder. They will be overwritten during app install process.

View File

@ -0,0 +1,255 @@
<!DOCTYPE html lang="en">
<head>
<title>myNode - {{app.name}}</title>
{% include 'includes/head.html' %}
<script src="{{ url_for('static', filename='js/manage_apps.js')}}"></script>
<style>
.hes-gallery {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 10px;
padding: 10px;
columns: 4;
}
.hes-gallery img {
width: 100%;
object-fit: cover;
transition: 0.3s;
cursor: pointer;
}
.hes-gallery img:hover {
transform: scale(1.04);
box-shadow: 2px 2px 6px #555;
}
.app_page_container {
margin: auto;
width: 1000px;
}
.app_page_block_header {
width: 1000px;
height: 12px;
padding: 0px 20px 0px 20px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
background-color: orange;
}
.app_page_block_contents {
width: 1000px;
padding: 20px 20px 20px 20px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
background: #f0f0f0;
overflow: auto;
}
.app_page_block_contents_left {
width: 150px;
float: left;
}
.app_page_block_contents_right {
width: 820px;
float: right;
margin-left: 30px;
}
.app_page_icon {
margin: auto;
display: block;
width: 150px;
}
.app_page_button {
margin: auto;
display: block;
width: 150px;
margin-bottom: 10px;
font-size: 14px !important;
}
.app_page_block_contents_heading {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
}
.app_page_block_contents_text {
font-size: 14px;
margin-bottom: 30px;
}
</style>
<!-- Hes Gallery -->
<link href="{{ url_for('static', filename='css/hes-gallery.min.css')}}" rel="stylesheet">
<script src="{{ url_for('static', filename='js/hes-gallery.min.js')}}"></script>
<script>
function restart_app_via_api(name, short_name) {
if ( confirm("Are you sure you want to restart "+name+"?") ) {
$('#loading_spinner_message').html("Restarting...");
$('#loading_spinner_overlay').fadeIn();
$.getJSON('/api/restart_app?app='+short_name, function( data ) {
alert(data)
if (data != "OK") {
alert("Error restarting app: "+data)
}
});
}
}
$(document).ready(function() {
HesGallery.init({
disableScrolling: false,
wrapAround: true,
animations: true,
keyboardControl: true,
showImageCount: true,
});
});
</script>
</head>
<body>
{% include 'includes/logo_header.html' %}
<div class="mynode_back_div">
<a class="ui-button ui-widget ui-corner-all mynode_back" href="/"><span class="ui-icon ui-icon-home"></span>home&nbsp;</a>
</div>
<div class="main_header">{{app.name}}</div>
<br/>
<div class="app_page_container">
<div class="app_page_block_header">&nbsp;</div>
<div class="app_page_block_contents">
<div class="app_page_block_contents_left">
<img class="app_page_icon" src="{{ url_for('static', filename="images/app_icons/")}}{{app.short_name}}.png"/>
<p style="font-size: 14px; text-align: center;">{{app_status}}</p>
<br/>
{% if not app.is_installed %}
<!-- Install -->
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button install_button" onclick="install('{{ app.name }}', '{{ app.short_name }}');">Install</button>
{% else %}
<!-- Open, Enable / Disable-->
<!-- TODO: ADD ENABLE / DISABLE BUTTON -->
{% if app.is_enabled %}
{% if app.http_port != "" or app.https_port != "" %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="open_app_in_new_tab('{{app.http_port}}', '{{app.https_port}}', false, '{APP_TOR_ADDRESS}')">Open</button>
{% endif %}
{% endif %}
<div class="divider"></div>
<!-- Manage App: Restart, Reset Data, Etc... -->
{% if app.is_enabled %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="restart_app_via_api('{{ app.name }}', '{{ app.short_name }}');">Restart</button>
{% for btn in app.app_page_additional_buttons %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button"
{% if btn.href is defined and btn.href != "" %}
onclick="window.location='{{btn.href}}'"
{% elif btn.onclick is defined and btn.onclick != "" %}
onclick="{{btn.onclick|safe}}"
{% endif %}
>{{btn.title}}</button>
{% endfor %}
<div class="divider"></div>
{% endif %}
<!-- Upgrade / Re-install / Uninstall -->
{% if app.current_version != app.latest_version %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="upgrade('{{ app.name }}', '{{ app.short_name }}');">Upgrade</button>
{% endif %}
{% if app.can_reinstall %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="reinstall('{{ app.name }}', '{{ app.short_name }}');">Reinstall</button>
{% endif %}
{% if app.can_uninstall %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button uninstall_button" onclick="uninstall('{{ app.name }}', '{{ app.short_name }}');">Uninstall</button>
{% endif %}
{% endif %}
</div>
<div class="app_page_block_contents_right">
<div class="app_page_block_contents_heading">
<div class="info-page-block">Info</div>
</div>
<div class="app_page_block_contents_text">
<table class="info_table" style="font-size: 12px; margin-left: 10px">
<tr>
<th>Installed Version</th>
<td>
{% if app.is_installed %}
{{app.current_version}}
{% else %}
Not Installed
{% endif %}
</td>
</tr>
<tr>
<th>Latest Version</th>
<td>{{app.latest_version}}</td>
</tr>
{% if app.author.name is defined %}
<tr>
<th>Author</th>
<td>
{% if app.author.link is defined and app.author.link != "" %}
<a href="{{app.author.link}}" target="_blank">{{app.author.name}}</a>
{% else %}
{{app.author.name}}
{% endif %}
</td>
</tr>
{% endif %}
{% if app.website.name is defined and app.website.link is defined %}
<tr>
<th>Website</th>
<td>
<a href="{{app.website.link}}" target="_blank">{{app.website.name}}</a>
</td>
</tr>
{% endif %}
</table>
</div>
<div class="app_page_block_contents_heading">
<div class="info-page-block">Description</div>
</div>
<div class="app_page_block_contents_text">
{% if app.description is defined and app.description|length > 0 %}
{% for parapraph in app.description %}
<p>{{parapraph}}</p>
{% endfor %}
{% else %}
No description available.
{% endif %}
</div>
{% if app.screenshots is defined and app.screenshots|length > 0 %}
<div class="app_page_block_contents_heading">
<div class="info-page-block">Screenshots</div>
</div>
<div class="app_page_block_contents_text">
<div class="hes-gallery">
{% for screenshot in app.screenshots %}
<img src="{{ url_for('static', filename="images/screenshots/")}}{{app.short_name}}/{{screenshot}}" alt="{{app.name}} Image" data-subtext=""/>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div id="loading_spinner_overlay" class="loading_spinner_overlay" style="display:none;">
<img id="loading_spinner" class="loading_image" src="{{ url_for('static', filename="images/loading.gif")}}"/>
<br/>
<span id="loading_spinner_message">Loading...</span>
</div>
</body>
</html>

View File

@ -0,0 +1,148 @@
<!DOCTYPE html lang="en">
<head>
<title>{{ title }}</title>
{% include 'includes/head.html' %}
<script src="{{ url_for('static', filename='js/manage_apps.js')}}"></script>
<script>
$(document).ready(function() {
$(".marketplace_app_tile").on("click", function() {
shortname = $(this).data("shortname");
window.location = "/marketplace/" + shortname;
});
});
</script>
<style>
.marketplace_category_header {
color: #555555;
text-align: center;
font-size: 26px;
margin-bottom: 5px;
}
.marketplace_category_container {
width: 1020px;
display: flex;
margin: auto;
margin-bottom: 20px;
flex-wrap:wrap;
flex-direction: row;
}
.marketplace_app_tile {
width: 320px;
height: 80px;
margin: 10px;
display: inline-block;
position: relative;
background-color: #f9f9f9;
border-radius: 10px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.marketplace_app_tile:hover {
cursor: pointer;
box-shadow: 0 4px 8px 0 rgba(180, 117, 0, 0.2), 0 6px 20px 0 rgba(180, 117, 0, 0.19);
}
.marketplace_app_tile_left {
float: left;
width: 80px;
height: 80px;
}
.marketplace_app_tile_icon {
float: left;
width: 60px;
margin-top: 10px;
margin-left: 10px;
}
.marketplace_app_tile_right {
float: right;
width: 230px;
height: 80px;
font-size: 11px;
padding-top: 10px;
}
.marketplace_app_tile_name {
display: inline-block;
width: 100%;
margin-bottom: 4px;
font-weight: bold;
font-size: 14px;
}
.marketplace_app_tile_description {
display: inline-block;
width: 100%;
margin-bottom: 10px;
font-size: 12px;
}
.marketplace_app_tile_version {
display: inline-block;
width: 100%;
font-size: 12px;
}
.marketplace_app_tile_installed {
position: absolute;
width: 18px;
top: 10px;
right: 10px;
}
</style>
</head>
<body>
{% include 'includes/logo_header.html' %}
<div class="mynode_back_div">
<a class="ui-button ui-widget ui-corner-all mynode_back" href="/"><span class="ui-icon ui-icon-home"></span>home&nbsp;</a>
</div>
<div class="main_header">Marketplace</div>
{% include 'includes/message_display.html' %}
</br>
<!-- <br/>{{ load_time }} ms -->
<br/><br/>
{% for category in categories %}
<div class="marketplace_category_header">{{category.title}}</div>
<div class="marketplace_category_container">
{% for app in apps %}
{% if app.show_on_application_page and app.category == category.name %}
<div class="marketplace_app_tile" data-shortname="{{app.short_name}}">
<div class="marketplace_app_tile_left">
<img class="marketplace_app_tile_icon" src="{{ url_for('static', filename="images/app_icons/")}}{{app.short_name}}.png"/>
</div>
<div class="marketplace_app_tile_right">
<div class="marketplace_app_tile_name">{{app.name}}</div>
<div class="marketplace_app_tile_description">{{app.short_description}}</div>
{% if not product_key_skipped or product_key_skipped and not app.is_premium %}
<div class="marketplace_app_tile_version">Version {{app.latest_version}}</div>
{% else %}
<div class="marketplace_app_tile_version"><i>Premium Feature</i></div>
{% endif %}
{% if app.is_installed %}
<img class="marketplace_app_tile_installed" title="Installed" src="{{ url_for('static', filename="images/")}}app_installed.png"/>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
<div id="loading_spinner_overlay" class="loading_spinner_overlay" style="display:none;">
<img id="loading_spinner" class="loading_image" src="{{ url_for('static', filename="images/loading.gif")}}"/>
<br/>
<span id="loading_spinner_message">Loading...</span>
</div>
<br/><br/>
{% include 'includes/footer.html' %}
</body>
</html>

View File

@ -0,0 +1,220 @@
<!DOCTYPE html lang="en">
<head>
<title>myNode - {{app.name}}</title>
{% include 'includes/head.html' %}
<script src="{{ url_for('static', filename='js/manage_apps.js')}}"></script>
<style>
.hes-gallery {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 10px;
padding: 10px;
columns: 4;
}
.hes-gallery img {
width: 100%;
object-fit: cover;
transition: 0.3s;
cursor: pointer;
}
.hes-gallery img:hover {
transform: scale(1.04);
box-shadow: 2px 2px 6px #555;
}
.app_page_container {
margin: auto;
width: 1000px;
}
.app_page_block_header {
width: 1000px;
height: 12px;
padding: 0px 20px 0px 20px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.app_page_block_contents {
width: 1000px;
padding: 20px 20px 20px 20px;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
overflow: auto;
}
.app_page_block_contents_left {
width: 150px;
float: left;
padding: 20px;
background-color: #f9f9f9;
border-radius: 10px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.app_page_block_contents_right {
width: 760px;
float: right;
margin-left: 30px;
}
.app_page_icon {
margin: auto;
display: block;
width: 150px;
}
.app_page_button {
margin: auto;
display: block;
width: 150px;
margin-bottom: 10px;
font-size: 14px !important;
}
.app_page_block_contents_heading {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
}
.app_page_block_contents_text {
font-size: 14px;
margin-bottom: 30px;
}
</style>
<!-- Hes Gallery -->
<link href="{{ url_for('static', filename='css/hes-gallery.min.css')}}" rel="stylesheet">
<script src="{{ url_for('static', filename='js/hes-gallery.min.js')}}"></script>
<script>
$(document).ready(function() {
HesGallery.init({
disableScrolling: false,
wrapAround: true,
animations: true,
keyboardControl: true,
showImageCount: true,
});
});
</script>
</head>
<body>
{% include 'includes/logo_header.html' %}
<div class="mynode_back_div">
<a class="ui-button ui-widget ui-corner-all mynode_back" href="/marketplace"><span class="ui-icon ui-icon-arrowthick-1-w"></span>back&nbsp;</a>
</div>
<div class="main_header">{{app.name}}</div>
<br/>
<div class="app_page_container">
<div class="app_page_block_header">&nbsp;</div>
<div class="app_page_block_contents">
<div class="app_page_block_contents_left">
<img class="app_page_icon" src="{{ url_for('static', filename="images/app_icons/")}}{{app.short_name}}.png"/>
<p style="font-size: 14px; text-align: center;">{{app.short_description}}</p>
<br/>
{% if not product_key_skipped or product_key_skipped and not app.is_premium %}
<!-- On Marketplace, only show install / uninstall (app pages show others) -->
{% if not app.is_installed %}
<!-- Install -->
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button install_button" onclick="install('{{ app.name }}', '{{ app.short_name }}');">Install</button>
{% else %}
<!-- Upgrade / Re-install / Uninstall -->
{% if app.current_version != app.latest_version %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button install_button" onclick="upgrade('{{ app.name }}', '{{ app.short_name }}');">Upgrade</button>
{% endif %}
{% if app.can_reinstall %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button" onclick="reinstall('{{ app.name }}', '{{ app.short_name }}');">Reinstall</button>
{% endif %}
{% if app.can_uninstall %}
<button class="ui-button ui-widget ui-corner-all mynode_button app_page_button uninstall_button" onclick="uninstall('{{ app.name }}', '{{ app.short_name }}');">Uninstall</button>
{% endif %}
{% endif %}
{% else %}
<p style="font-size: 14px; text-align: center;"><i>Premium Feature</i></p>
{% endif %}
</div>
<div class="app_page_block_contents_right">
<div class="app_page_block_contents_heading">
<div class="info-page-block">Info</div>
</div>
<div class="app_page_block_contents_text">
<table class="info_table" style="font-size: 12px; margin-left: 10px">
<tr>
<th>Installed Version</th>
<td>
{% if app.is_installed %}
{{app.current_version}}
{% else %}
Not Installed
{% endif %}
</td>
</tr>
<tr>
<th>Latest Version</th>
<td>{{app.latest_version}}</td>
</tr>
{% if app.author.name is defined %}
<tr>
<th>Author</th>
<td>
{% if app.author.link is defined and app.author.link != "" %}
<a href="{{app.author.link}}" target="_blank">{{app.author.name}}</a>
{% else %}
{{app.author.name}}
{% endif %}
</td>
</tr>
{% endif %}
{% if app.website.name is defined and app.website.link is defined %}
<tr>
<th>Website</th>
<td>
<a href="{{app.website.link}}" target="_blank">{{app.website.name}}</a>
</td>
</tr>
{% endif %}
</table>
</div>
<div class="app_page_block_contents_heading">
<div class="info-page-block">Description</div>
</div>
<div class="app_page_block_contents_text">
{% if app.description is defined and app.description|length > 0 %}
{% for parapraph in app.description %}
<p>{{parapraph}}</p>
{% endfor %}
{% else %}
No description available.
{% endif %}
</div>
{% if app.screenshots is defined and app.screenshots|length > 0 %}
<div class="app_page_block_contents_heading">
<div class="info-page-block">Screenshots</div>
</div>
<div class="app_page_block_contents_text">
<div class="hes-gallery">
{% for screenshot in app.screenshots %}
<img src="{{ url_for('static', filename="images/screenshots/")}}{{app.short_name}}/{{screenshot}}" alt="{{app.name}} Image" data-subtext=""/>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div id="loading_spinner_overlay" class="loading_spinner_overlay" style="display:none;">
<img id="loading_spinner" class="loading_image" src="{{ url_for('static', filename="images/loading.gif")}}"/>
<br/>
<span id="loading_spinner_message">Loading...</span>
</div>
</body>
</html>