<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '../../app');
include_once "base_controller.php";
include_once "utils.php";
class ServiceConfig extends BaseController {
protected function signinRequired() {
return true;
}
protected function adminRequired() {
return true;
}
protected function applyHTTPSCerts() {
$response = array();
$certs_dir = "/etc/nginx/certs";
//check if there is uploaded certificates
if (!file_exists(build_file_path($certs_dir, "uploaded/cert"))
|| !file_exists(build_file_path($certs_dir, "uploaded/key")))
$this->renderAjaxError($response, "please upload certificate and key first");
//run nginx conf test against uploaded cert and key
$output = rtrim(`(cd $certs_dir && ln -nsf uploaded live && nginx -q -t && echo OK) || echo ERROR`)
if (endsWith($output, 'OK')) {
//try to apply the cert and key
$output = rtrim(`(cd $certs_dir && mkdir -p prod && mv uploaded/cert uploaded/key prod/ && ln -nsf prod live && nginx -s reload && echo OK) || echo ERROR`)
if (endsWith($output, 'OK'))
$this->renderAjaxSuccess2($response);
else //fall back to default cert and key if it fails
`cd $certs_dir && ln -nsf default live && nginx -s reload`
} else {
//restore to production cert and key, if that is missing, fall back to default
$restore_to = file_exists(build_file_path($certs_dir, 'prod')) ? 'prod' : 'default';
`cd $certs_dir && ln -nsf $restore_to live`
}
$this->renderAjaxError($response, "failed to apply uploaded cert and key");
}
protected function downloadHTTPSCerts() {
$response = array();
$certs_dir = "/etc/nginx/certs/prod";
if (!file_exists($certs_dir)
|| !file_exists(build_file_path($certs_dir, "cert"))
|| !file_exists(build_file_path($certs_dir, "key")) )
$this->renderAjaxError($response, "certificate are not available");
$url = 'service_config.php?action=dlHTTPSCerts';
$this->redirect($url);
}
public function doAjaxPost() {
$response = array();
$action = null;
if (isset($_POST['action']))
$action = $_POST['action'];
if (is_null($action))
$this->renderAjaxError($response, "invalid action: " . $action);
if ($action == "apply_https_certs")
$this->applyHTTPSCerts();
else if ($action == "download_https_cert")
$this->downloadHTTPSCerts();
else
$this->renderAjaxError($response, "invalid action: " . $action);
}
public function doGet() {
if (!isset($_GET['action']))
return;
$action = $_GET['action'];
if ($action == 'dlHTTPSCerts') {
$certs_dir = "/etc/nginx/certs/prod";
$content = `cd $certs_dir && tar -cf - cert key`
$this->genDownloadResp($content, "certs.tar");
}
}
}
$controller = new ServiceConfig();
$controller->run();
?>
<!DOCTYPE html>
<html>
<head>
<eta charset="utf-8" />
<title>Service Config</title>
<eta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../../css/bootstrap.min.css" media="screen" />
<link rel="stylesheet" href="../../css/dropzone.css" media="screen" />
<link rel="stylesheet" href="../../css/flatpickr.min.css" media="screen" />
<link rel="stylesheet" href="css/service_config.css" media="screen" />
</head>
<body>
<div class="container" id="service_config_content">
<div class="navbar">
<div class="navbar-inner">
<a class="brand" href="/">EasyIO</a>
<ul class="nav">
<li class="divider-vertical"></li>
<li v-for="sec in sections" :class="{active:isSectionActive(sec)}"><a href="#" @click="active_sec_id=sec.sec_id">{{ sec.name }}</a></li>
</ul>
</div>
</div>
<div class="row">
<alert></alert>
<keep-alive>
<component :is="active_sec_id"></component>
</keep-alive>
</div>
</div>
<!-- component templates -->
<script type="text/x-template" id="alert-template">
<div class="span12">
<div class="alert" :class="label_class" v-show="show">
<button type="button" class="close" @click="show=false">×</button>
<strong>{{ title }}</strong> {{ msg }}
</div>
</div>
</script>
<?php include "partials/modal_tmpl.php"; ?>
<!-- app component -->
<script type="text/x-template" id="https_cert_upload_template">
<div class="dropzone needsclick dz-clickable">
<div class="dz-message needsclick">
<slot></slot>
</div>
</div>
</script>
<script type="text/x-template" id="web_config_template">
<div class="span12">
<div class="well">
<div class="page-header" style="margin-bottom:0">
<h4>HTTPS Certificate</h4>
</div>
<!--
<div class="alert">
<strong>Warning:</strong> The uploaded certificate will take effect after controller reboot. Make sure the certificate is a valid, otherwise the web server may stop working after reboot.
</div>
-->
<h3>Certificate File:</h3>
<https-cert-upload server_url="file_upload.php" accepted_files=".pem,.crt,.cer" param_name="cert">
<span>Drop <strong>certificate(.pem,.crt,.cer)</strong> file here or click to select.</span>
</https-cert-upload>
<h3>Key File:</h3>
<https-cert-upload server_url="file_upload.php" accepted_files=".pem,.key" param_name="key">
<span>Drop <strong>key(.pem,.key)</strong> file here or click to select.</span>
</https-cert-upload>
<div class="row" style="margin-top: 20px">
<button type="button" class="btn btn-primary btn-large pull-right" @click="applyHTTPSCert">Apply</button>
<button type="button" class="btn btn-large" style="margin-left: 20px;" @click="downloadHTTPSCert">Download</button>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="openvpn_server_template">
<div class="row">
<div class="span12">
<form class="form-horizontal" @submit.prevent="saveOpenVPNConfig">
<div class="control-group" :class="{error: caError}">
<div class="control-label">Certificate Authority:</div>
<div class="controls">
<template v-if="ca">
<span class="label label-success">READY</span>
</template>
<template v-else>
<button class="btn btn-warning" type="button" @click="collectCAData">Generate CA</button>
<span class="help-inline" v-show="caError">Must generate certificate authority before openvpn server can work.</span>
</template>
</div>
</div>
<div class="control-group" :class="{error: serverCertError}">
<div class="control-label">Server Certificate:</div>
<div class="controls">
<template v-if="serverCert">
<span class="label label-success">READY</span>
</template>
<template v-else>
<button class="btn btn-warning" type="button" @click="collectServerCertData">Generate Server Certificate</button>
<span class="help-inline" v-show="serverCertError">Server certificate is not generated yet.</span>
</template>
</div>
</div>
<div class="control-group">
<label class="control-label">Protocol:</label>
<div class="controls">
<select v-model="proto">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
</div>
</div>
<div class="control-group" :class="{error:portError}">
<div class="control-label" for="inputPort">Port:</div>
<div class="controls">
<input id="inputPort" type="number" v-model.number="port" >
<span class="help-inline" v-show="portError">Invalid port value</span>
</div>
</div>
<div class="control-group" :class="{error:ipError}">
<div class="control-label" for="inputIPAddress">VPN Subnet:</div>
<div class="controls">
<input id="inputIPAddress" type="text" v-model.trim="ip">
<span class="help-inline" v-show="ipError">Invalid Subnet Address</span>
</div>
</div>
<div class="control-group" :class="{error:netmaskError}">
<div class="control-label" for="inputNetMask">VPN Network Mask:</div>
<div class="controls">
<input id="inputNetMask" type="text" v-model.trim="netmask">
<span class="help-inline" v-show="netmaskError">Invalid VPN Network Mask</span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn">Save</button>
</div>
</div>
</form>
</div>
<odalComp ref="gen_ca_modal" @confirmed="generateCA">
<template slot="header">Generate CA</template>
<div class="alert">
<strong>Warning!</strong> After CA is created, you can not modify it anymore, please make sure the value of 'Common Name' field is correct.
</div>
<form class="form-horizontal" @submit.prevent>
<div class="control-group" :class="{error: caCNError}">
<div class="control-label" for="inputCN">Common Name:</div>
<div class="controls">
<input id="inputCN" type="text" v-model.trim="caCN">
<span class="help-inline" v-show="caCNError">Invalid CA Common Name</span>
</div>
</div>
</form>
</modalComp>
<odalComp ref="gen_cert_modal" @confirmed="generateServerCert">
<template slot="header">Generate Certificate</template>
<form class="form-horizontal" @submit.prevent>
<div class="control-group" :class="{error: serverCertCNError}">
<div class="control-label" for="inputCertCN">Common Name:</div>
<div class="controls">
<input id="inputCertCN" type="text" v-model.trim="serverCertCN">
<span class="help-inline" v-show="serverCertCNError">Invalid Certificate Common Name</span>
</div>
</div>
</form>
</modalComp>
</div>
</script>
<script type="text/x-template" id="openvpn_client_ovpn_generation_template">
<div class='row'>
<div class="span12">
<form class="form-horizontal" @submit.prevent="generateClientOVPN">
<div class="control-group" :class="{error:clientNameError}">
<div class="control-label" for="inputClientName">Client Name:</div>
<div class="controls">
<input id="inputClientName" type="text" v-model.trim="clientName">
<span class="help-inline" v-show="!clientNameError">Unique client name</span>
<span class="help-inline" v-show="clientNameError">Invalid client name</span>
</div>
</div>
<div class="control-group" :class="{error:ipError}">
<div class="control-label" for="inputIPAddress">Remote Server IP:</div>
<div class="controls">
<input id="inputIPAddress" type="text" v-model.trim="ip">
<span class="help-inline" v-show="!ipError">The IP address used to connect to the VPN server, normally should be the router's IP.</span>
<span class="help-inline" v-show="ipError">Invalid IP address</span>
</div>
</div>
<div class="control-group" :class="{error:portError}">
<div class="control-label" for="inputPort">Port:</div>
<div class="controls">
<input id="inputPort" type="number" v-model.number="port" >
<span class="help-inline" v-show="!portError">Normally should be router's port that forwards data to above VPN server's port</span>
<span class="help-inline" v-show="portError">Invalid port value</span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn">Generate & Download</button>
</div>
</div>
</form>
</div>
</div>
</script>
<script type="text/x-template" id="openvpn_config_template">
<div class="span12">
<div class="well well-small">
<div class="page-header"> <h4>Server Config</h4> </div>
<openvpn-server />
</div>
<div class="well well-small">
<div class="page-header"> <h4>Generate & Download Client Config(.ovpn)</h4> </div>
<div class="alert alert-info">
<button type="button" class="close" data-dismiss="alert">×</button>
<strong>NOTE:</strong> Generate & Download OpenVPN client config file(.ovpn) that can connect to OpenVPN server configured by above settings. Please make sure above server settings are correct and work well.
</div>
<openvpn-client-ovpn-generator />
</div>
</div>
</script>
<script type="text/x-template" id="auth_key_editor_template">
<tr v-show='editing'>
<td colspan="4">
<form class="form-inline" style="margin-bottom:0px; margin-left:28px;" @submit.prevent="saveModification">
<div class="control-group input-append" style="display: inline;" :class="{error: this.expired_at.length <= 0}">
<input type="text" placeholder="<?php echo L('Expiry Date') ?>" v-model="expired_at" readonly>
<span class="add-on" @click="showHideCalendar"><i class="icon-calendar"></i></span>
</div>
<div class="control-group" style="display:inline" :class="{error: note_error}">
<input type="text" placeholder="<?php echo L('Note') ?>" v-model="note" style="margin-left:10px; margin-right:10px;" >
</div>
<button type="submit" class="btn btn-mini btn-primary"><?php echo L('Save') ?></button>
<button class="btn btn-mini" @click.prevent="$emit('editorCancelled')"><?php echo ('Cancel') ?></button>
</form>
</td>
</tr>
</script>
<script type="text/x-template" id="auth_key_config_template">
<div class="row">
<div class="alert hide"></div>
<div class="span12">
<form class="form-horizontal span12" @submit.prevent="createAuthKey">
<div class="control-group" :class="{error: expired_at_error}">
<label class="control-label" for="inputAuthKeyExpiration"><?php echo L('Expiry Date') ?></label>
<div class="controls input-append" id="authKeyExpiration" style="display: block;">
<input type='text' id="inputAuthKeyExpiration" readonly :value="expired_at"></input>
<span class="add-on" @click="showCalendar">
<i class="icon-calendar"></i>
</span>
<span class="help-block" v-show="expired_at_error" style="font-size: 14px;">Invalid Expiry Date</span>
</div>
</div>
<div class="control-group" :class="{error: note_error}">
<label class="control-label" for="inputAuthKeyNote"><?php echo L('Note') ?></label>
<div class="controls">
<input id="inputAuthKeyNote" type="text" placeholder="<?php echo L('Note for this AuthKey') ?>" focus v-model="note">
<span class="help-block" v-show="note_error" style="font-size: 14px;">Note can not be longer than 64 characters.</span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button id="save_auth_key_btn" type="submit" class="btn btn-primary"><?php echo L('Create') ?></button>
</div>
</div>
</form>
</div>
<hr/>
<div class="span12" style="width:920px;">
<table class="table table-striped table-hover">
<thead>
<tr>
<th @click="sortBy('key')"><?php echo L('Key') ?></th>
<th @click="sortBy('expired_at')"><?php echo L('Expiry Date') ?></th>
<th @click="sortBy('note')"><?php echo L('Note') ?></th>
<th></th>
</tr>
</thead>
<tbody>
<template v-for="(key, index) in keys">
<tr>
<td><i class="icon-certificate"></i> {{key.key}}</td>
<td>{{key.expired_at}}</td>
<td>{{key.note}}</td>
<td>
<button class="btn btn-mini" @click="toggleAuthKeyEditor(key)"><?php echo L('Edit') ?></button>
<button class="btn btn-mini btn-danger" @click="delAuthKey(key, index)"><?php echo L('Delete') ?></button>
</td>
</tr>
<auth-key-editor :editing="key.editing" :initial_note="key.note" :initial_expired_at="key.expired_at" @editCompleted="function(n, e) { onEditCompleted(key, n, e); }" @editorCancelled="key.editing = false" />
</template>
</tbody>
</table>
</div>
</div>
</script>
<script type="text/x-template" id="data_api_config_template">
<div class="span12">
<div class="well well-small">
<div class="page-header">
<h4>Authentication Key</h4>
</div>
<auth-key-config />
</div>
</div>
</script>
<script language="javascript" type="text/javascript">
window.iot_platform = '<?php echo platformName() ?>';
</script>
<script type="text/javascript" src="../../js/jquery-1.10.0.min.js"></script>
<script type="text/javascript" src="../../js/bootstrap.min.js"></script>
<script type="text/javascript" src="../../js/underscore-min.js"></script>
<script type="text/javascript" src="../../js/dropzone.js"></script>
<script type="text/javascript" src="../../js/spin.min.js"></script>
<script type="text/javascript" src="../../js/flatpickr.min.js"></script>
<?php
if (isset($_GET["dev"])) {
?>
<script type="text/javascript" src="../../js/vue.js"></script>
<?php
} else {
?>
<script type="text/javascript" src="../../js/vue.min.js"></script>
<?php
}
?>
<script type="text/javascript" src="../../js/plugin-utils.js"></script>
<script type="text/javascript" src="../../js/vue-alert-component.js"></script>
<script type="text/javascript" src="../../js/vue-modal-component.js"></script>
<script type="text/javascript" src="js/service_config.js"></script>
</body>
</html>