close #1671: implement mobile UI for quarantine

this patch implements a UI for the Quarantine, designed to
be looked at on mobile phones

for this we use Framework7 instead of extjs, since it has much more
features and looks more native on phones

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
This commit is contained in:
Dominik Csapak 2019-02-18 13:50:50 +01:00 committed by Dietmar Maurer
parent 80ccc3425b
commit 43f0b189d9
11 changed files with 888 additions and 6 deletions

View File

@ -17,7 +17,7 @@ IMAGES= \
logo-128.png \ logo-128.png \
proxmox_logo.png proxmox_logo.png
CSSFILES = ext6-pmg.css CSSFILES = ext6-pmg.css ext6-pmg-mobile.css
all: all:
@ -31,12 +31,17 @@ deb ${DEB}:
js/pmgmanagerlib.js: js/pmgmanagerlib.js:
make -C js pmgmanagerlib.js make -C js pmgmanagerlib.js
install: pmg-index.html.tt js/pmgmanagerlib.js js/pmgmanagerlib-mobile.js:
make -C js pmgmanagerlib-mobile.js
install: pmg-index.html.tt js/pmgmanagerlib.js js/pmgmanagerlib-mobile.js
install -d -m 755 ${WWWCSSDIR} install -d -m 755 ${WWWCSSDIR}
install -d -m 755 ${WWWIMAGESDIR} install -d -m 755 ${WWWIMAGESDIR}
install -d -m 755 ${WWWJSDIR} install -d -m 755 ${WWWJSDIR}
install -m 0644 pmg-index.html.tt ${WWWBASEDIR} install -m 0644 pmg-index.html.tt ${WWWBASEDIR}
install -m 0644 pmg-mobile-index.html.tt ${WWWBASEDIR}
install -m 0644 js/pmgmanagerlib.js ${WWWJSDIR} install -m 0644 js/pmgmanagerlib.js ${WWWJSDIR}
install -m 0644 js/pmgmanagerlib-mobile.js ${WWWJSDIR}
for i in ${IMAGES}; do install -m 0644 images/$$i ${WWWIMAGESDIR}; done for i in ${IMAGES}; do install -m 0644 images/$$i ${WWWIMAGESDIR}; done
for i in ${CSSFILES}; do install -m 0644 css/$$i ${WWWCSSDIR}; done for i in ${CSSFILES}; do install -m 0644 css/$$i ${WWWCSSDIR}; done

46
css/ext6-pmg-mobile.css Normal file
View File

@ -0,0 +1,46 @@
.item-title .item-header {
white-space: inherit;
overflow: hidden;
text-overflow: ellipsis;
}
.empty {
padding: 1em;
color: var(--f7-label-text-color);
}
img.logo {
padding: 0 10px;
vertical-align: middle;
height: 64px;
}
img.logo-navbar {
padding: 0 10px;
height: 32;
}
.settings-form {
position: absolute;
bottom: calc(var(--f7-fab-margin) + var(--f7-safe-area-bottom));
right: calc(var(--f7-fab-margin) + var(--f7-safe-area-right));
z-index: 1500;
width: 200px;
background-color: var(--f7-list-bg-color);
}
.button.subscription i.icon {
display: inline;
}
@media only screen and (max-width: 500px) {
.login-screen-title {
font-size: 6vw;
}
}
@media only screen and (min-width: 500px) {
.login-screen-title {
font-size: 32px;
}
}

12
debian/control vendored
View File

@ -2,12 +2,20 @@ Source: pmg-gui
Section: perl Section: perl
Priority: optional Priority: optional
Maintainer: Proxmox Support Team <support@proxmox.com> Maintainer: Proxmox Support Team <support@proxmox.com>
Build-Depends: debhelper (>= 9), perl (>= 5.10.0-19), libtemplate-perl Build-Depends: debhelper (>= 9),
libtemplate-perl,
perl (>= 5.10.0-19),
Standards-Version: 3.9.5 Standards-Version: 3.9.5
Homepage: http://www.proxmox.com Homepage: http://www.proxmox.com
Package: pmg-gui Package: pmg-gui
Architecture: all Architecture: all
Depends: ${perl:Depends}, libtemplate-perl, libjs-extjs (>= 6.0.1), fonts-font-awesome, pmg-i18n, proxmox-widget-toolkit Depends: fonts-font-awesome,
libjs-extjs (>= 6.0.1),
libjs-framework7,
libtemplate-perl,
pmg-i18n,
proxmox-widget-toolkit,
${perl:Depends},
Description: Proxmox Mail Gateway GUI Description: Proxmox Mail Gateway GUI
Graphical user interface for Proxmox Mail Gateway. Graphical user interface for Proxmox Mail Gateway.

View File

@ -84,6 +84,15 @@ JSSRC= \
SpamContextMenu.js \ SpamContextMenu.js \
Application.js Application.js
# caution: order is important
MOBILESRC= \
mobile/component.js \
mobile/loginscreen.js \
mobile/mailview.js \
mobile/quarantineview.js \
mobile/utils.js \
mobile/app.js \
OnlineHelpInfo.js: /usr/bin/asciidoc-pmg OnlineHelpInfo.js: /usr/bin/asciidoc-pmg
/usr/bin/asciidoc-pmg scan-extjs ${JSSRC} >$@.tmp /usr/bin/asciidoc-pmg scan-extjs ${JSSRC} >$@.tmp
mv $@.tmp $@ mv $@.tmp $@
@ -95,12 +104,16 @@ pmgmanagerlib.js: OnlineHelpInfo.js ${JSSRC}
cat OnlineHelpInfo.js ${JSSRC} >$@.tmp cat OnlineHelpInfo.js ${JSSRC} >$@.tmp
mv $@.tmp $@ mv $@.tmp $@
all: pmgmanagerlib.js pmgmanagerlib-mobile.js: ${MOBILESRC}
cat ${MOBILESRC} >$@.tmp
mv $@.tmp $@
all: pmgmanagerlib.js pmgmanagerlib-mobile.js
.PHONY: clean .PHONY: clean
clean: clean:
find . -name '*~' -exec rm {} ';' find . -name '*~' -exec rm {} ';'
rm -rf pmgmanagerlib.js OnlineHelpInfo.js rm -rf pmgmanagerlib.js pmgmanagerlib-mobile.js OnlineHelpInfo.js

80
js/mobile/app.js Normal file
View File

@ -0,0 +1,80 @@
var $$ = Dom7;
var app = new Framework7({
root: '#app',
init: false,
name: 'Proxmox Mail Gateway',
routes: [
{
path: '/:path/:subpath?',
async: function(routeTo, routeFrom, resolve, reject) {
if (routeTo.params.path === 'mail') {
let mail = new MailView();
resolve({
template: mail.getTpl()
},{
context: {
mailid: routeTo.params.subpath
}
});
} else {
reject();
}
}
},
{
path: '/mail/:mailid/:action',
async: function(routeTo, routeFrom, resolve, reject) {
let action = routeTo.params.action;
let mailid = routeTo.params.mailid;
let confirmText = gettext('')
app.dialog.confirm(
`${action}: ${mailid}`,
gettext('Confirm'),
() => {
let loader = app.dialog.preloader();
app.request({
method: 'POST',
url: '/api2/json/quarantine/content/',
data: {
action: action,
id: mailid
},
headers: {
CSRFPreventionToken: Proxmox.CSRFPreventionToken
},
success: (data, status, xhr) => {
loader.close();
app.dialog.alert(
`Action '${action}' successful`,
gettext("Info"),
() => {
if (action === 'delete' ||
action === 'deliver')
{
// refresh the main list when a mail
// got deleted or delivered
app.ptr.refresh();
}
}
);
reject();
},
error: xhr => {
loader.close();
PMG.Utils.showError(xhr);
reject();
}
})
},
() => {
reject();
}
);
}
}
]
});
let quarlist = new QuarantineView();
app.init();

26
js/mobile/component.js Normal file
View File

@ -0,0 +1,26 @@
class Component {
constructor(config = {}) {
var me = this;
me.config = config;
me.data = config.data || {};
me.tpl = me.config.tpl || '<div class="component"></div>';
}
getTpl() {
var me = this;
if (!me._compiledtpl) {
me._compiledtpl = Template7.compile(me.tpl);
}
return me._compiledtpl;
}
getEl(data) {
var me = this;
if (data === undefined && me._el) {
return me._el;
} else if (data !== undefined) {
me.data =data;
}
me._el = Dom7(me.getTpl()(me.data));
return me._el;
}
}

114
js/mobile/loginscreen.js Normal file
View File

@ -0,0 +1,114 @@
class LoginScreen extends Component {
constructor(config = {}) {
config.tpl = `
<div class="login-screen">
<div class="view">
<div class="page">
<div class="page-content login-screen-content">
<div class="login-screen-title">
<img class="logo" src="pve2/images/logo-128.png" />
Proxmox Mail Gateway
</div>
<form action="/api2/json/access/ticket" method="POST" class="form-ajax-submit">
<div class="list">
<ul>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-title item-label">Username</div>
<div class="item-input-wrap">
<input type="text" name="username" placeholder="{{gettext 'Username'}}" required validate>
<span class="input-clear-button"></span>
</div>
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-title item-label">Password</div>
<div class="item-input-wrap">
<input type="password" name="password" placeholder="{{gettext 'Password'}}" required validate>
<span class="input-clear-button"></span>
</div>
</div>
</li>
</ul>
</div>
<div class="list">
<ul>
<li>
<input type="submit" class="button" value='{{gettext "Log In"}}'>
</li>
</ul>
</div>
</form>
</div>
</div>
</div>
</div>
`;
super(config);
var me = this;
me._screen = app.loginScreen.create({
content: me.getEl(),
});
let login = config.loginInfo;
me._form = me.getEl().find('form');
if (login.username && login.ticket) {
app.form.fillFromData(me._form, {
username: login.username,
password: login.ticket,
});
me._autoLogin = true;
} else if (PMG.Utils.authOK()) {
app.form.fillFromData(me._form, {
username: Proxmox.UserName,
password: decodeURIComponent(PMG.Utils.getCookie('PMGAuthCookie')),
});
me._autoLogin = true;
}
}
open(onLogin) {
var me = this;
return new Promise(function(resolve, reject) {
me._form.on('formajax:beforesend', (el, data, xhr) => {
me.loader = app.dialog.preloader();
});
me._form.on('formajax:success', (el, data, xhr) => {
let json;
try {
json = JSON.parse(xhr.responseText);
} catch (err) {
xhr.error = err;
PMG.Utils.showError(xhr);
return;
}
resolve(json);
});
me._form.on('formajax:error', (el, data, xhr) => {
me.loader.close();
PMG.Utils.showError(xhr);
});
if (me._autoLogin) {
delete me._autoLogin;
me._screen.on('open', () => {
me._form.trigger('submit');
})
}
me._screen.open();
});
}
close() {
var me = this;
if (me.loader) {
me.loader.close();
}
me._screen.close(false);
}
}

62
js/mobile/mailview.js Normal file
View File

@ -0,0 +1,62 @@
class MailView extends Component {
constructor(config = {}) {
config.tpl = `
<div class="page">
<div class="navbar sliding">
<div class="navbar-inner">
<div class="left">
<a href="#" class="link back">
<i class="icon icon-back"></i>
<span class="ios-only">{{gettext "Back"}}</span>
</a>
</div>
<div class="title">Preview</div>
</div>
</div>
<div class="fab fab-right-bottom">
<a href="#">
<i class="icon f7-icons ios-only">menu</i>
<i class="icon f7-icons ios-only">close</i>
<i class="icon material-icons md-only">menu</i>
<i class="icon material-icons md-only">close</i>
</a>
<div class="fab-buttons fab-buttons-top">
<a href="/mail/{{mailid}}/blacklist" class="fab-label-button fab-close">
<span>
<i class="icon f7-icons ios-only">close</i>
<i class="icon material-icons md-only">close</i>
</span>
<span class="fab-label">{{gettext "Blacklist"}}</span>
</a>
<a href="/mail/{{mailid}}/whitelist" class="fab-label-button fab-close">
<span>
<i class="icon f7-icons ios-only">check</i>
<i class="icon material-icons md-only">check</i>
</span>
<span class="fab-label">{{gettext "Whitelist"}}</span>
</a>
<a href="/mail/{{mailid}}/delete" class="fab-label-button fab-close">
<span>
<i class="icon f7-icons ios-only">trash</i>
<i class="icon material-icons md-only">delete</i>
</span>
<span class="fab-label">{{gettext "Delete"}}</span>
</a>
<a href="/mail/{{mailid}}/deliver" class="fab-label-button fab-close">
<span>
<i class="icon f7-icons ios-only">paper_plane</i>
<i class="icon material-icons md-only">send</i>
</span>
<span class="fab-label">{{gettext "Deliver"}}</span>
</a>
</div>
</div>
<div class="page-content">
<iframe frameborder=0 width="100%" height="100%" sandbox="allow-same-origin" src="/api2/htmlmail/quarantine/content?id={{mailid}}"></iframe>
</div>
</div>
`;
super(config);
}
}

329
js/mobile/quarantineview.js Normal file
View File

@ -0,0 +1,329 @@
class QuarantineView extends Component {
constructor(config = {}) {
config.tpl = config.tpl || `
<div class="view view-quarantine">
<div data-name="quarantine-list" class="page">
<div class="navbar">
<div class="navbar-inner">
<div class="left">
<img class="logo-navbar" style="padding: 0 10px" src="pve2/images/logo-128.png" height=32 />
</div>
<div class="title">Mail Gateway</div>
</div>
</div>
<div class="settings-form elevation-5 fab-morph-target">
<div class="block-title block-title-medium">{{gettext "Range"}}</div>
<div class="list no-hairlines-md">
<ul>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-title item-label">{{gettext "From"}}</div>
<div class="item-input-wrap">
<input type="date" name="from" placeholder="from" required validate>
</div>
</div>
</li>
<li class="item-content item-input">
<div class="item-inner">
<div class="item-title item-label">{{gettext "To"}}</div>
<div class="item-input-wrap">
<input type="date" name="to" placeholder="to" required validate>
</div>
</div>
</li>
</ul>
<a class="button fab-close range-form">{{gettext "OK"}}</a>
</div>
</div>
<div class="fab fab-morph fab-right-bottom" data-morph-to=".settings-form">
<a href="#">
<i class="icon f7-icons ios-only">calendar</i>
<i class="icon material-icons md-only">date_range</i>
</a>
</div>
<div class="toolbar subscription toolbar-hidden toolbar-bottom">
<div class="toolbar-inner">
<a class="button subscription">
<i class="icon f7-icons ios-only color-yellow">alert</i>
<i class="icon material-icons md-only color-yellow">warning</i>
<span class="subscription-text">
{{gettext "No valid subscription"}}
</span>
</a>
</div>
</div>
<div class="page-content ptr-content">
<div class="ptr-preloader">
<div class="preloader"></div>
<div class="ptr-arrow"></div>
</div>
<div class="list virtual-list"></div>
</div>
</div>
</div>`;
config.itemTemplate = config.itemTemplate || `
<li class="swipeout">
<div class="swipeout-content">
<a href="/mail/{{id}}/" class="item-link item-content">
<div class="item-inner">
<div class="item-title">
<div class="item-header">{{escape from}}</div>
{{escape subject}}
</div>
<div class="item-after">Score: {{js "this.spamlevel || 0"}}</div>
</div>
</a>
</div>
<div class="swipeout-actions-left">
<a href="/mail/{{id}}/deliver" class="color-green swipeout-close">
<i class="icon f7-icons ios-only">paper_plane</i>
<i class="icon material-icons md-only">send</i>
&nbsp;{{gettext "Deliver"}}
</a>
<a href="/mail/{{id}}/whitelist" class="swipeout-close">
<i class="icon f7-icons ios-only">check</i>
<i class="icon material-icons md-only">check</i>
&nbsp;{{gettext "Whitelist"}}
</a>
</div>
<div class="swipeout-actions-right">
<a href="/mail/{{id}}/blacklist" class="color-orange swipeout-close">
<i class="icon f7-icons ios-only">close</i>
<i class="icon material-icons md-only">close</i>
&nbsp;{{gettext "Blacklist"}}
</a>
<a href="/mail/{{id}}/delete" class="color-red swipeout-close">
<i class="icon f7-icons ios-only">trash</i>
<i class="icon material-icons md-only">delete</i>
&nbsp;{{gettext "Delete"}}
</a>
</div>
</li>`;
config.dividerTemplate = config.dividerTemplate ||
'<li class="item-divider">{{group}}</li>';
super(config);
var me = this;
me._compiledItemTemplate = Template7.compile(me.config.itemTemplate);
me._compiledDividerTemplate = Template7.compile(me.config.dividerTemplate);
me.skelTpl = `
<li class="skeleton-text skeleton-effect-fade">
<a href="#" class="item-content item-link">
<div class="item-inner">
<div class="item-title">
<div class="item-header">_______________________</div>
____ ______ __ _______ ____ _______ _______ ___
</div>
<div class="item-after">Score: 15</div>
</div>
</a>
</li>`;
me.skelDividerTpl = '<li class="item-divider skeleton-text">____-__-__</li>';
me.setEndtime(new Date());
let startdate = new Date();
startdate.setDate(startdate.getDate() - 7);
me.setStarttime(startdate);
// add to dom
$$(me.config.target || '#app').append(me.getEl());
$$(document).on('page:init', '.page[data-name=quarantine-list]', (e, page) => {
me.vList = app.virtualList.create({
el: '.virtual-list',
items: [],
renderItem: function(item) {
return me._renderItem(item);
},
emptyTemplate: '<div class="empty">No data in database</div>'
});
// setup pull to refresh
$$('.ptr-content').on('ptr:refresh', (e) => {
me.setItems([
{ skel: true, divider: true },
{ skel: true },
{ skel: true },
{ skel: true },
{ skel: true, divider: true },
{ skel: true },
{ skel: true },
{ skel: true },
{ skel: true },
{ skel: true },
{ skel: true },
{ skel: true },
]);
me.load().then(data => {
me.setItems(data, {
sorter: {
property: 'time',
numeric: true,
direction: 'DESC'
},
grouperFn: (val) => PMG.Utils.unixToIso(val['time'])
});
}).catch(PMG.Utils.showError).then(() => {
e.detail();
});
});
// process query parameters
let { mail, action, date, username, ticket } = PMG.Utils.extractParams();
if (date) {
me.setStarttime(date);
}
// setup range form
$$('input[name=from]').val(PMG.Utils.unixToIso(me.starttime));
$$('input[name=to]').val(PMG.Utils.unixToIso(me.endtime));
$$('.fab').on('fab:close', () => {
let fromChanged = me.setStarttime($$('input[name=from]').val());
let toChanged = me.setEndtime($$('input[name=to]').val());
if (fromChanged || toChanged) {
app.ptr.refresh();
}
});
// check login
let loginInfo = { username, ticket };
let showPopup = (username && ticket) || !PMG.Utils.authOK();
me._loginScreen = new LoginScreen({ loginInfo });
me._loginScreen.open().then(data => {
me._loginScreen.close();
PMG.Utils.setLoginInfo(data);
return PMG.Utils.getSubscriptionInfo();
}).then(data => {
return PMG.Utils.checkSubscription(data, showPopup);
}).then(data => {
app.ptr.refresh();
if (mail) {
let url = "/mail/" + mail + "/" + (action || "");
me._view.router.navigate(url);
}
}).catch(PMG.Utils.showError);
});
me._view = app.views.create('.view-quarantine', {
main: me.config.mainView !== undefined ? me.config.mainView : true,
url: '/',
pushState: true,
pushStateAnimateOnLoad: true
});
}
setStarttime(starttime) {
var me = this;
let date = starttime;
if (!(starttime instanceof Date)) {
// we assume an ISO string
if (starttime == '') {
return;
}
date = new Date(PMG.Utils.isoToUnix(starttime)*1000);
}
// starttime is at beginning of date
date.setHours(0,0,0,0);
let result = Math.round(date.getTime()/1000);
if (result !== me.starttime) {
me.starttime = result;
return true;
}
return false
}
setEndtime(endtime) {
var me = this;
let date = endtime;
if (!(endtime instanceof Date)) {
if (endtime == '') {
return;
}
// we assume an ISO string
date = new Date(PMG.Utils.isoToUnix(endtime)*1000);
}
// endtime is at the end of the day
date.setHours(23, 59, 59);
let result = Math.round(date.getTime()/1000);
if (result !== me.endtime) {
me.endtime = result;
return true;
}
return false;
}
_renderItem(item) {
var me = this;
if(typeof item === 'object') {
if (item.skel) {
return item.divider? me.skelDividerTpl : me.skelTpl;
} else if (item.divider) {
return me._compiledDividerTemplate(item);
} else {
return me._compiledItemTemplate(item);
}
}
return item.toString();
}
setItems(items, options) {
var me = this;
if (options && options.sorter) {
if (options.sorter.sorterFn) {
items.sort(options.sorter.sorterFn);
} else {
let prop = options.sorter.property;
let numeric = options.sorter.numeric;
let dir = options.sorter.direction === "ASC" ? 1 : -1;
items.sort((a,b) => {
let result;
if (numeric) {
result = a[prop] - b[prop];
} else {
result = a[prop] === b[prop] ? 0 : (a[prop] < b[prop] ? 1 : -1);
}
return result * dir;
});
}
}
me.vList.replaceAllItems(items);
if (options && options.grouperFn) {
let lastgroup;
let offset = 0;
for (let i = 0; i+offset < items.length; i++) {
let item = items[i+offset];
let curgroup = options.grouperFn(item);
if (curgroup != lastgroup) {
me.vList.insertItemBefore(i+(offset++), {
divider: true,
group: curgroup
});
lastgroup = curgroup;
}
}
}
}
load() {
var me = this;
return new Promise(function(resolve, reject) {
app.request({
url: '/api2/json/quarantine/spam',
data: {
starttime: me.starttime,
endtime: me.endtime
},
dataType: 'json',
success: (response, status, xhr) => {
resolve(response.data);
},
error: xhr => {
reject(xhr);
}
});
});
}
}

163
js/mobile/utils.js Normal file
View File

@ -0,0 +1,163 @@
Template7.registerHelper('gettext', function(value) {
return gettext(value);
});
var PMG = {
Utils: {
getCookie(name) {
let cookies = document.cookie.split(/;\s*/);
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i].split('=');
if (cookie[0] === name && cookie.length > 1) {
return cookie[1];
}
}
return undefined;
},
setCookie(name, value, expires) {
value = encodeURIComponent(value);
let cookie = `${name}=${value}`;
if (expires) {
cookie += `; expires=${expires}`;
}
document.cookie = cookie;
},
deleteCookie(name) {
PMG.Utils.setCookie(name, "", "Thu, 01 Jan 1970 00:00:00 UTC");
},
authOK(options) {
var authCookie = PMG.Utils.getCookie('PMGAuthCookie') || "";
return (authCookie.substr(0,7) === 'PMGQUAR' && Proxmox.UserName !== '');
},
isoToUnix(iso) {
let fields = iso.split('-').map((field) => parseInt(field, 10));
// monthIndex starts at 0
let date = new Date(fields[0],fields[1]-1, fields[2]);
return Math.round(date.getTime()/1000);
},
unixToIso(unix) {
let date = new Date(unix*1000);
let year = date.getFullYear().toString();
let month = (date.getMonth()+1).toString().padStart(2, "0");
let day = date.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
},
showError(xhr) {
let statusText = "", errorText = "";
if (xhr instanceof Error) {
statusText = gettext("Error");
errorText = xhr.message;
} else if (xhr.error instanceof Error) {
statusText = gettext("Error");
errorText = xhr.error.message;
} else {
statusText = xhr.status.toString() + ' ' + xhr.statusText;
try {
let errorObj = JSON.parse(xhr.responseText);
if (errorObj.errors) {
let errors = Object.keys(errorObj.errors).map((key) => key + ": " + errorObj.errors[key]);
errorText = errors.join('<br>');
}
} catch (e) {
statusText = gettext("Error");
errorText = e.message;
}
}
app.toast.show({
text: `Error:<br>
${statusText}<br>
${errorText}
`,
closeButton: true,
destroyOnClose: true
});
},
extractParams() {
let queryObj = app.utils.parseUrlQuery(location.search);
let mail, action, date, username, ticket;
if (queryObj.ticket) {
let tocheck = decodeURIComponent(queryObj.ticket);
let match = tocheck.match(/^PMGQUAR:([^\s\:]+):/);
if (match) {
ticket = tocheck;
username = match[1];
}
delete queryObj.ticket;
}
if (queryObj.date) {
date =queryObj.date;
delete queryObj.date;
}
if (queryObj.cselect) {
mail = queryObj.cselect;
action = queryObj.action;
delete queryObj.cselect;
delete queryObj.action;
}
if (mail || action || date || ticket) {
let queryString = app.utils.serializeObject(queryObj);
window.history.replaceState(
window.history.state,
document.title,
location.pathname + (queryString? "?" + queryString : '')
);
}
return { mail, action, date, username, ticket };
},
setLoginInfo(result) {
PMG.Utils.setCookie('PMGAuthCookie', result.data.ticket);
Proxmox.CSRFPreventionToken = result.data.CSRFPreventionToken;
},
getSubscriptionInfo() {
return new Promise(function(resolve, reject) {
app.request({
url: '/api2/json/nodes/localhost/subscription',
dataType: 'json',
success: (result, status, xhr) => {
resolve(result.data);
},
error: (xhr, status) => {
reject(xhr);
}
});
});
},
checkSubscription(data, showPopup) {
return new Promise(function(resolve, reject) {
if (data.status !== 'Active') {
let url = data.url || 'https://wwww.proxmox.com';
let err = `You do not have a valid subscription for this server.
Please visit
<a target="_blank" href="${url}">www.proxmox.com</a>
to get a list of available options.`;
app.toolbar.show('.toolbar.subscription');
$$('.button.subscription').on('click', () => {
app.dialog.alert(
err,
gettext("No valid subscription"),
);
});
if (showPopup) {
app.dialog.alert(
err,
gettext("No valid subscription"),
() => {
resolve(data);
}
);
} else {
resolve();
}
} else {
app.toolbar.hide('.toolbar.subscription');
resolve();
}
});
}
}
};

36
pmg-mobile-index.html.tt Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#2196f3">
<title>Proxmox Mail Gateway - Quarantine</title>
<link rel="stylesheet" href="framework7/css/framework7.bundle.min.css">
<link rel="stylesheet" href="framework7/css/framework7-icons.css">
<link rel="stylesheet" href="framework7/css/material-icons.css">
<link rel="stylesheet" href="pve2/css/ext6-pmg-mobile.css">
[% IF langfile %]
<script type='text/javascript' src='/pve2/locale/pmg-lang-[% lang %].js'></script>
[% ELSE %]
<script type='text/javascript'> function gettext(buf) { return buf; } </script>
[%- END %]
<script type="text/javascript">
Proxmox = {
UserName: '[% username %]',
CSRFPreventionToken: '[% token %]'
};
</script>
</head>
<body>
<div id="app">
<div class="statusbar"></div>
</div>
[% IF debug %]
<script type="text/javascript" src="/framework7/js/framework7.bundle.js"></script>
[% ELSE %]
<script type="text/javascript" src="/framework7/js/framework7.bundle.min.js"></script>
[% END %]
<script type="text/javascript" src="/pve2/js/pmgmanagerlib-mobile.js"></script>
</body>
</html>