{% include 'instance_list_vnf.html' %}
{% elif type == 'pdu' %}
{% include 'instance_list_pdu.html' %}
+ {% elif type == 'nsi' %}
+ {% include 'instance_list_nsi.html' %}
{% endif %}
</div>
</div>
{% include 'modal/instance_create.html' %}
{% include 'modal/instance_create_pdu.html' %}
+ {% include 'modal/instance_create_nsi.html' %}
{% include 'modal/instance_show.html' %}
{% include 'modal/instance_new_action.html' %}
{% include 'modal/instance_new_alarm.html' %}
"targets": 5,
"orderable": false
}
+ ],
+ 'nsi': [
+ {
+ "render": function (data, type, row) {
+ return row["name"];
+ },
+ "targets": 0
+ },
+ {
+ "render": function (data, type, row) {
+ return row["_id"];
+ },
+ "targets": 1
+ },
+ {
+ "render": function (data, type, row) {
+ return row["nst-ref"];
+ },
+ "targets": 2
+ },
+ {
+ // "width": "5%",
+ "render": function (data, type, row) {
+ if(row["operational-status"] === 'failed')
+ return '<span class="label label-danger">'+ row["operational-status"] +'</span>';
+ if(row["operational-status"] === 'init')
+ return '<span class="label label-warning">'+ row["operational-status"] +'</span>';
+ if(row["operational-status"] === 'running')
+ return '<span class="label label-success">'+ row["operational-status"] +'</span>';
+ return ''+row["operational-status"]+'';
+ },
+ "targets": 3
+ },
+ {
+ // "width": "5%",
+ "render": function (data, type, row) {
+ if(row["config-status"] === 'failed')
+ return '<span class="label label-danger">'+ row["config-status"] +'</span>';
+ if(row["config-status"] === 'init')
+ return '<span class="label label-warning">'+ row["config-status"] +'</span>';
+ if(row["config-status"] === 'running')
+ return '<span class="label label-success">'+ row["config-status"] +'</span>';
+ if(row["config-status"] === 'configured')
+ return '<span class="label label-success">'+ row["config-status"] +'</span>';
+ return ''+row["operational-status"]+'';
+ },
+ "targets": 4
+ },
+ {
+ // "className": "ellipsis",
+ "render": function (data, type, row) {
+ return row["detailed-status"];
+ },
+ "targets": 5
+ },{
+ "width": "20%",
+ "render": function (data, type, row) {
+ var template = '<div class="btn-group">' +
+ ' <button type="button" class="btn btn-default"' +
+ ' onclick="javascript:showInstanceDetails(\''+instance_type+'\', \''+row["_id"]+'\')"' +
+ ' data-toggle="tooltip" data-placement="top" data-container="body" title="Show Info">' +
+ ' <i class="fa fa-info"></i>';
+ /* if (row["operational-status"] === "running") {
+ template += ' <button type="button" class="btn btn-default"' +
+ ' onclick="javascript:showTopology(\'' + instance_type + '\', \'' + row["_id"] + '\')"' +
+ ' data-toggle="tooltip" data-placement="top" data-container="body" title="Show Graph">' +
+ ' <i class="fa fa-sitemap"></i>' +
+ ' </button>';
+ }else{
+ template += ' <button type="button" disabled class="btn btn-default"' +
+ ' onclick="javascript:showTopology(\'' + instance_type + '\', \'' + row["_id"] + '\')"' +
+ ' data-toggle="tooltip" data-placement="top" data-container="body" title="Show Graph">' +
+ ' <i class="fa fa-sitemap"></i>' +
+ ' </button>';
+ } */
+ template += ' <button type="button" class="btn btn-default"' +
+ ' onclick="javascript:deleteNsi(\''+ row["name"] +'\', \''+row["_id"]+'\')"' +
+ ' data-toggle="tooltip" data-placement="top" data-container="body" title="Delete"><i' +
+ ' class="far fa-trash-alt"></i></button>' +
+ ' <button type="button" class="btn btn-default dropdown-toggle"' +
+ ' data-toggle="dropdown" aria-expanded="false">Actions' +
+ ' <span class="fa fa-caret-down"></span></button>' +
+ ' <ul class="dropdown-menu">' +
+ ' <li>' +
+ ' <a href="/instances/nsi/' +row["_id"] +'/operation">' +
+ ' <i class="fa fa-list"></i> History of operations</a></li>' +
+ ' <li class="divider"></li>' +
+
+ ' <li>' +
+ ' <a href="javascript:deleteNsi(\''+ row["name"] +'\', \''+row["_id"]+'\', true)">' +
+ ' <i class="far fa-trash-alt" style="color:red" ></i> Force delete</a></li>' +
+ ' </ul>' +
+ '</div>';
+ return template;
+ },
+ "targets": 5,
+ "orderable": false
+ },
+
]
};
$(document).ready(function () {
--- /dev/null
+{% load get %}
+<div class="box">
+ <div class="box-header with-border">
+ <h3 class="box-title">Network Slices Instances</h3>
+
+ <div class="box-tools">
+
+ <button type="button" class="btn btn-default" data-container="body"
+ data-toggle="tooltip" data-placement="top" title="Instantiate NSI"
+ onclick="javascript:openModalCreateNSI({ 'project_id':'{{ project_id }}','vim_list_url': '{% url "vims:list" %}', 'nst_list_url': '{% url "netslices:list_templates" %}'})">
+ <i class="fa fa-paper-plane"></i> <span> Create NSI</span></button>
+
+ </div>
+
+ </div>
+ <div class="box-body">
+ {% if alert_error %}
+ <div class="alert alert-danger alert-dismissible fade in">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+ <h4><i class="icon fa fa-ban"></i> Error</h4>
+ {{alert_error}}
+ </div>
+ {% endif %}
+ <table id="instances_table" class="table table-bordered table-striped responsive" style="width:100%">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Identifier</th>
+ <th>Nst name</th>
+ <th style="width:5%">Operational Status</th>
+ <th style="width:5%">Config Status</th>
+ <th>Detailed Status</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+
+ </tbody>
+ </table>
+ </div>
+</div>
--- /dev/null
+<div class="modal" id="modal_new_nsi" xmlns="http://www.w3.org/1999/html">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+ <span aria-hidden="true">×</span></button>
+ <h4 class="modal-title">New NSI</h4>
+ </div>
+ <form id="formCreateNS" action='{% url "instances:create" type='nsi' %}'
+ class="form-horizontal"
+ method="post" enctype="multipart/form-data">
+ {% csrf_token %}
+ <div class="modal-body" id="modal_new_instance_body">
+ <div class="form-group">
+ <label for="nsiName" class="col-sm-3 control-label">Name *</label>
+ <div class="col-sm-6">
+ <input class="form-control" id="nsiName" name="nsiName"
+ placeholder="Ns name" required>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="nsiDescription" class="col-sm-3 control-label">Description *</label>
+ <div class="col-sm-6">
+ <input class="form-control" id="nsiDescription" name="nsiDescription"
+ placeholder="Description" required>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="nstId" class="col-sm-3 control-label">Nst Id *</label>
+ <div class="col-sm-6">
+ <select required id="nstId" class="js-example-basic form-control" name="nstId">
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="vimAccountIdNSI" class="col-sm-3 control-label">Vim Account Id *</label>
+ <div class="col-sm-6">
+ <select required id="vimAccountIdNSI" class="js-example-basic form-control" name="vimAccountId">
+ </select>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="ssh_key_nsi" class="col-sm-3 control-label">SSH Key </label>
+ <div class="col-sm-6">
+ <textarea class="form-control" id="ssh_key_nsi" name="ssh_key"
+ placeholder="Paste your key here..." rows="4"></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="config_nsi" class="col-sm-3 control-label">Config </label>
+ <div class="col-sm-6">
+ <textarea class="form-control" id="config_nsi" name="config" placeholder="Yaml config"
+ rows="4"></textarea>
+ </div>
+ </div>
+
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
+ <button class="btn btn-primary"
+ data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> Creating..."
+ id="create_new_instance_nsi">Create
+ </button>
+
+ </div>
+ </form>
+ </div>
+ <!-- /.modal-content -->
+ </div>
+ <!-- /.modal-dialog -->
+</div>
<div class="modal" id="modal_show_instance" xmlns="http://www.w3.org/1999/html">
- <div class="modal-dialog">
+ <div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
from instancehandler import views
urlpatterns = [
- url(r'^(?P<type>[ns|vnf|pdu]+)/list/', views.list, name='list'),
- url(r'^(?P<type>[ns|pdu]+)/create/', views.create, name='create'),
- url(r'^(?P<type>[ns|vnf|pdu]+)/(?P<instance_id>[0-9a-z-]+)/delete$', views.delete, name='delete'),
+ url(r'^(?P<type>[ns|vnf|pdu|nsi]+)/list/', views.list, name='list'),
+ url(r'^(?P<type>[ns|pdu|nsi]+)/create/', views.create, name='create'),
+ url(r'^(?P<type>[ns|pdu|nsi]+)/(?P<instance_id>[0-9a-z-]+)/delete$', views.delete, name='delete'),
url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/topology', views.show_topology, name='show_topology'),
url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/action$', views.action, name='action'),
- url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/operation$', views.ns_operations, name='ns_operations'),
+ url(r'^(?P<type>[ns|vnf|nsi]+)/(?P<instance_id>[0-9a-z-]+)/operation$', views.ns_operations, name='ns_operations'),
url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/operation/(?P<op_id>[0-9a-z-]+)', views.ns_operation, name='ns_operation'),
url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/monitoring/alarm$', views.create_alarm, name='ns_create_alarm'),
url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/monitoring/metric$', views.export_metric, name='ns_export_metric'),
- url(r'^(?P<type>[ns|vnf|pdu]+)/(?P<instance_id>[0-9a-z-]+)', views.show, name='show'),
+ url(r'^(?P<type>[ns|vnf|pdu|nsi]+)/(?P<instance_id>[0-9a-z-]+)', views.show, name='show'),
]
#
from django.shortcuts import render, redirect
-#from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, JsonResponse
import yaml
import json
instance_list = client.vnf_list(user.get_token())
elif type == 'pdu':
instance_list = client.pdu_list(user.get_token())
+ elif type == 'nsi':
+ instance_list = client.nsi_list(user.get_token())
result['instances'] = instance_list['data'] if instance_list and instance_list['error'] is False else []
@login_required
def create(request, type=None):
result = {}
+ config_vim_account_id = {}
user = osmutils.get_user(request)
client = Client()
+
if type == 'ns':
try:
return __response_handler(request, {}, 'instances:list', to_redirect=True, type='ns', )
result = client.ns_create(user.get_token(), ns_data)
return __response_handler(request, result, 'instances:list', to_redirect=True, type='ns')
+
+ elif type == 'nsi':
+ try:
+ nsi_data = {
+ "nsiName": request.POST.get('nsiName', 'WithoutName'),
+ "nsiDescription": request.POST.get('nsiDescription', ''),
+ "nstId": request.POST.get('nstId', ''),
+ "vimAccountId": request.POST.get('vimAccountId', ''),
+ }
+
+ if 'ssh_key' in request.POST and request.POST.get('ssh_key') != '':
+ nsi_data["ssh_keys"] = [request.POST.get('ssh_key')]
+ except Exception as e:
+ request.session["OSM_ERROR"] = "Error creating the NSI; Invalid parameters provided."
+ return __response_handler(request, {}, 'instances:list', to_redirect=True, type=type)
+ result = client.nsi_create(user.get_token(), nsi_data)
+ return __response_handler(request, result, 'instances:list', to_redirect=True, type=type)
+
elif type == 'pdu':
interface_param_name = request.POST.getlist('interfaces_name')
interface_param_ip = request.POST.getlist('interfaces_ip')
user = osmutils.get_user(request)
project_id = user.project_id
- result = {'type': 'ns', 'project_id': project_id, 'instance_id': instance_id}
+ result = {'type': type, 'project_id': project_id, 'instance_id': instance_id}
raw_content_types = request.META.get('HTTP_ACCEPT', '*/*').split(',')
if 'application/json' not in raw_content_types:
return __response_handler(request, result, 'instance_operations_list.html')
client = Client()
- op_list = client.ns_op_list(user.get_token(), instance_id)
+ if type == 'ns':
+ op_list = client.ns_op_list(user.get_token(), instance_id)
+ elif type == 'nsi':
+ op_list = client.nsi_op_list(user.get_token(), instance_id)
result['operations'] = op_list['data'] if op_list and op_list['error'] is False else []
return __response_handler(request, result, 'instance_operations_list.html')
if result['error']:
return __response_handler(request, result['data'], url=None,
status=result['data']['status'] if 'status' in result['data'] else 500)
-
else:
return __response_handler(request, {}, url=None, status=200)
result = client.ns_delete(user.get_token(), instance_id, force)
elif type == 'pdu':
result = client.pdu_delete(user.get_token(), instance_id)
- print result
- return __response_handler(request, result, 'instances:list', to_redirect=True, type='ns')
+ elif type == 'nsi':
+ result = client.nsi_delete(user.get_token(), instance_id, force)
+
+ if result['error']:
+ return __response_handler(request, result['data'], url=None,
+ status=result['data']['status'] if 'status' in result['data'] else 500)
+ else:
+ return __response_handler(request, {}, url=None, status=200)
@login_required
def show_topology(request, instance_id=None, type=None):
result = client.vnf_get(user.get_token(), instance_id)
elif type == 'pdu':
result = client.pdu_get(user.get_token(), instance_id)
+ elif type == 'nsi':
+ result = client.nsi_get(user.get_token(), instance_id)
print result
return __response_handler(request, result)
return result
+ def nsi_list(self, token):
+ result = {'error': True, 'data': ''}
+ headers = {"Content-Type": "application/yaml", "accept": "application/json",
+ 'Authorization': 'Bearer {}'.format(token['id'])}
+ _url = "{0}/nsilcm/v1/netslice_instances".format(self._base_path)
+ try:
+ r = requests.get(_url, params=None, verify=False, stream=True, headers=headers)
+ except Exception as e:
+ log.exception(e)
+ result['data'] = str(e)
+ return result
+ if r.status_code == requests.codes.ok:
+ result['error'] = False
+ result['data'] = Util.json_loads_byteified(r.text)
+
+ return result
+
def ns_list(self, token):
result = {'error': True, 'data': ''}
headers = {"Content-Type": "application/yaml", "accept": "application/json",
return result
+ def nsi_create(self, token, nsi_data):
+ result = {'error': True, 'data': ''}
+ headers = {"Content-Type": "application/yaml", "accept": "application/json",
+ 'Authorization': 'Bearer {}'.format(token['id'])}
+
+ _url = "{0}/nsilcm/v1/netslice_instances_content".format(self._base_path)
+
+ try:
+ r = requests.post(_url, json=nsi_data, verify=False, headers=headers)
+ except Exception as e:
+ log.exception(e)
+ result['data'] = str(e)
+ return result
+ if r.status_code == requests.codes.ok:
+ result['error'] = False
+ result['data'] = Util.json_loads_byteified(r.text)
+ return result
+
def ns_create(self, token, ns_data):
result = {'error': True, 'data': ''}
headers = {"Content-Type": "application/yaml", "accept": "application/json",
return result
+ def nsi_op_list(self, token, id):
+ result = {'error': True, 'data': ''}
+ headers = {"Content-Type": "application/json", "accept": "application/json",
+ 'Authorization': 'Bearer {}'.format(token['id'])}
+ _url = "{0}/nsilcm/v1/nsi_lcm_op_occs/?nsInstanceId={1}".format(self._base_path, id)
+
+ try:
+ r = requests.get(_url, params=None, verify=False, stream=True, headers=headers)
+ except Exception as e:
+ log.exception(e)
+ result['data'] = str(e)
+ return result
+ if r.status_code == requests.codes.ok:
+ result['error'] = False
+ result['data'] = Util.json_loads_byteified(r.text)
+
+ return result
+
def ns_op(self, token, id):
result = {'error': True, 'data': ''}
headers = {"Content-Type": "application/json", "accept": "application/json",
result['data'] = Util.json_loads_byteified(r.text)
return result
+ def nsi_delete(self, token, id, force=None):
+ result = {'error': True, 'data': ''}
+ headers = {"Content-Type": "application/yaml", "accept": "application/json",
+ 'Authorization': 'Bearer {}'.format(token['id'])}
+ query_path = ''
+ if force:
+ query_path = '?FORCE=true'
+ _url = "{0}/nsilcm/v1/netslice_instances_content/{1}{2}".format(self._base_path, id, query_path)
+ try:
+ r = requests.delete(_url, params=None, verify=False, headers=headers)
+ except Exception as e:
+ log.exception(e)
+ result['data'] = str(e)
+ return result
+ if r:
+ result['error'] = False
+ if r.status_code != requests.codes.no_content:
+ result['data'] = Util.json_loads_byteified(r.text)
+ return result
+
def ns_delete(self, token, id, force=None):
result = {'error': True, 'data': ''}
headers = {"Content-Type": "application/yaml", "accept": "application/json",
result['data'] = Util.json_loads_byteified(r.text)
return result
+ def nsi_get(self, token, id):
+ result = {'error': True, 'data': ''}
+ headers = {"Content-Type": "application/json", "accept": "application/json",
+ 'Authorization': 'Bearer {}'.format(token['id'])}
+ _url = "{0}/nsilcm/v1/netslice_instances/{1}".format(self._base_path, id)
+
+ try:
+ r = requests.get(_url, params=None, verify=False, stream=True, headers=headers)
+ except Exception as e:
+ log.exception(e)
+ result['data'] = str(e)
+ return result
+ if r.status_code == requests.codes.ok:
+ result['error'] = False
+ result['data'] = Util.json_loads_byteified(r.text)
+ return result
+
def ns_get(self, token, id):
result = {'error': True, 'data': ''}
headers = {"Content-Type": "application/json", "accept": "application/json",
</li>
{% url "instances:list" type='ns' as instance_ns_list_url %}
{% url "instances:list" type='vnf' as instance_vnf_list_url %}
- {% url "instances:list" type='pdu' as instance_vnf_list_url %}
- <li {% if request.get_full_path == instance_ns_list_url or request.get_full_path == instance_vnf_list_url %}
+ {% url "instances:list" type='pdu' as instance_pdu_list_url %}
+ {% url "instances:list" type='nsi' as instance_nsi_list_url %}
+ <li {% if request.get_full_path == instance_ns_list_url or request.get_full_path == instance_vnf_list_url or request.get_full_path == instance_pdu_list_url or request.get_full_path == instance_nsi_list_url %}
class="active treeview menu-open" {% else %} class="treeview menu-open" {% endif %} >
<a href="#">
<i class="fa fa-paper-plane fa-fw"></i> <span>Instances</span>
<i class="far fa-hdd fa-fw"></i> <span>VNF Instances</span>
</a>
</li>
- {% url "instances:list" type='pdu' as instance_vnf_list_url %}
- <li {% if request.get_full_path == instance_vnf_list_url %} class="active" {% endif %} >
+ {% url "instances:list" type='pdu' as instance_pdu_list_url %}
+ <li {% if request.get_full_path == instance_pdu_list_url %} class="active" {% endif %} >
<a href='{% url "instances:list" type="pdu" %}'>
- <i class="far fa-hdd fa-fw"></i> <span>PDU</span>
+ <i class="far fa-hdd fa-fw"></i> <span>PDU Instances</span>
+ </a>
+ </li>
+ {% url "instances:list" type='nsi' as instance_nsi_list_url %}
+ <li {% if request.get_full_path == instance_nsi_list_url %} class="active" {% endif %} >
+ <a href='{% url "instances:list" type="nsi" %}'>
+ <i class="fas fa-layer-group fa-fw"></i> <span>NetSlice Instances</span>
</a>
</li>
</ul>
}
$('#modal_new_instance').modal('show');
+}
+function openModalCreateNSI(args) {
+ // load vim account list
+ select2_groups = $('#vimAccountIdNSI').select2({
+ placeholder: 'Select VIM',
+ width: '100%',
+ ajax: {
+ url: args.vim_list_url,
+ dataType: 'json',
+ processResults: function (data) {
+ vims = [];
+ if (data['datacenters']) {
+ for (d in data['datacenters']) {
+ var datacenter = data['datacenters'][d];
+ vims.push({ id: datacenter['_id'], text: datacenter['name'] })
+ }
+ }
+
+ return {
+ results: vims
+ };
+ }
+ }
+ });
+
+ // load nsd list
+ select2_groups = $('#nstId').select2({
+ placeholder: 'Select NST',
+ width: '100%',
+ ajax: {
+ url: args.nst_list_url,
+ dataType: 'json',
+ processResults: function (data) {
+ nst_list = [];
+
+ if (data['templates']) {
+ for (d in data['templates']) {
+ var nst = data['templates'][d];
+ nst_list.push({ id: nst['_id'], text: nst['name'] })
+ }
+ }
+
+ return {
+ results: nst_list
+ };
+ }
+ }
+ });
+
+ if (args.descriptor_id) {
+ // Set the value, creating a new option if necessary
+ if ($('#nstId').find("option[value='" + args.descriptor_id + "']").length) {
+ $('#nstId').val(args.descriptor_id).trigger('change');
+ } else {
+ // Create a DOM Option and pre-select by default
+ var newOption = new Option(args.descriptor_name, args.descriptor_id, true, true);
+ // Append it to the select
+ $('#nstId').append(newOption).trigger('change');
+ }
+ }
+
+ $('#modal_new_nsi').modal('show');
}
\ No newline at end of file
location.reload();
}
},
- error: function (error) {
+ error: function (result) {
dialog.modal('hide');
- bootbox.alert("An error occurred.");
+ var data = result.responseJSON;
+ var title = "Error " + (data && data.code ? data.code : 'unknown');
+ var message = data && data.detail ? data.detail : 'No detail available.';
+ bootbox.alert({
+ title: title,
+ message: message
+ });
+ }
+ });
+ }
+ })
+}
+function deleteNsi(instance_name, instance_id, force) {
+ var url = '/instances/nsi/'+instance_id+'/delete';
+ bootbox.confirm("Are you sure want to delete " + instance_name + "?", function (result) {
+ if (result) {
+ if (force)
+ url = url + '?force=true';
+ var dialog = bootbox.dialog({
+ message: '<div class="text-center"><i class="fa fa-spin fa-spinner"></i> Loading...</div>',
+ closeButton: true
+ });
+ $.ajax({
+ url: url,
+ type: 'GET',
+ dataType: "json",
+ contentType: "application/json;charset=utf-8",
+ success: function (result) {
+ console.log(result)
+ if (result['error'] == true){
+ dialog.modal('hide');
+ var data = result.responseJSON;
+ var title = "Error " + (data && data.code ? data.code : 'unknown');
+ var message = data && data.detail ? data.detail : 'No detail available.';
+ bootbox.alert({
+ title: title,
+ message: message
+ });
+ }
+ else {
+ dialog.modal('hide');
+ location.reload();
+ }
+ },
+ error: function (result) {
+ dialog.modal('hide');
+ var data = result.responseJSON;
+ var title = "Error " + (data && data.code ? data.code : 'unknown');
+ var message = data && data.detail ? data.detail : 'No detail available.';
+ bootbox.alert({
+ title: title,
+ message: message
+ });
}
});
}