mirror of
https://github.com/optim-enterprises-bv/control-pane.git
synced 2025-11-01 02:17:52 +00:00
graph
This commit is contained in:
@@ -693,7 +693,8 @@ dialog.edit .edit {
|
|||||||
dialog.new p.new {
|
dialog.new p.new {
|
||||||
display:block;
|
display:block;
|
||||||
}
|
}
|
||||||
.dialog-close:before {
|
.dialog-close:before,
|
||||||
|
.dialog-fullscreen:before {
|
||||||
font-family:"clonos";
|
font-family:"clonos";
|
||||||
content:'\e820';
|
content:'\e820';
|
||||||
speak: none;
|
speak: none;
|
||||||
@@ -704,6 +705,9 @@ dialog.new p.new {
|
|||||||
text-transform: none;
|
text-transform: none;
|
||||||
margin:2px 6px;
|
margin:2px 6px;
|
||||||
}
|
}
|
||||||
|
.dialog-fullscreen:before {
|
||||||
|
content:'\f0b2';
|
||||||
|
}
|
||||||
dialog .panel {
|
dialog .panel {
|
||||||
background-color:white;
|
background-color:white;
|
||||||
}
|
}
|
||||||
@@ -711,6 +715,15 @@ dialog:not(input),
|
|||||||
dialog:not(textarea) {
|
dialog:not(textarea) {
|
||||||
user-select:none;
|
user-select:none;
|
||||||
}
|
}
|
||||||
|
dialog.fullscreen {
|
||||||
|
width:100% !important;
|
||||||
|
height:100% !important;
|
||||||
|
top:0;
|
||||||
|
}
|
||||||
|
dialog.fullscreen #vnc-iframe {
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
.window-box {
|
.window-box {
|
||||||
/*padding:0 30px 30px 30px;*/
|
/*padding:0 30px 30px 30px;*/
|
||||||
@@ -1455,4 +1468,31 @@ dialog#login input[type="password"] {
|
|||||||
width:50px;
|
width:50px;
|
||||||
height:50px;
|
height:50px;
|
||||||
margin:calc(50% - 25px) auto;
|
margin:calc(50% - 25px) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
div.smoothie-chart-tooltip {
|
||||||
|
background: #d0d0d0; /* #fcfccc; */
|
||||||
|
padding: 1em;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-family: consolas;
|
||||||
|
color: black;
|
||||||
|
font-size: 8pt;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 1px solid gray;
|
||||||
|
box-shadow:3px 3px 3px 0px rgba(0,0,0,0.1);
|
||||||
|
line-height: 120%;
|
||||||
|
}
|
||||||
|
.smoothie-chart-tooltip :nth-child(2):before {
|
||||||
|
content:'cpu: '
|
||||||
|
}
|
||||||
|
.smoothie-chart-tooltip :nth-child(4):before {
|
||||||
|
content:'mem: '
|
||||||
|
}
|
||||||
|
.smoothie-chart-tooltip :nth-child(2):after,
|
||||||
|
.smoothie-chart-tooltip :nth-child(4):after {
|
||||||
|
content:'%'
|
||||||
|
}
|
||||||
|
td.graph {
|
||||||
|
/*background-color:black;*/
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<dialog id="vnc">
|
<dialog id="vnc">
|
||||||
<div class="panel" style="text-align:right;">
|
<div class="panel" style="text-align:right;">
|
||||||
|
<span onclick="clonos.dialogFullscreen(this);" style="font-size:130%;cursor:pointer;"
|
||||||
|
<span class="dialog-fullscreen"></span>
|
||||||
|
</span>
|
||||||
<span onclick="clonos.dialogClose();" style="font-size:150%;font-weight:bold;cursor:pointer;">
|
<span onclick="clonos.dialogClose();" style="font-size:150%;font-weight:bold;cursor:pointer;">
|
||||||
<span class="dialog-close"></span>
|
<span class="dialog-close"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<dialog id="vnc">
|
<dialog id="vnc">
|
||||||
<div class="panel" style="text-align:right;">
|
<div class="panel" style="text-align:right;">
|
||||||
<span onclick="clonos.dialogClose();" style="font-size:150%;font-weight:bold;cursor:pointer;">
|
<span onclick="clonos.dialogClose();" style="font-size:150%;font-weight:bold;cursor:pointer;">
|
||||||
|
<span class="dialog-fullscreen"></span>
|
||||||
<span class="dialog-close"></span>
|
<span class="dialog-close"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ if(!$user_info['error'])
|
|||||||
<script src="/js/jquery.js" type="text/javascript"></script>
|
<script src="/js/jquery.js" type="text/javascript"></script>
|
||||||
<script src="/js/clonos.js" type="text/javascript"></script>
|
<script src="/js/clonos.js" type="text/javascript"></script>
|
||||||
<script src="/js/dmuploader.js" type="text/javascript"></script>
|
<script src="/js/dmuploader.js" type="text/javascript"></script>
|
||||||
|
<script src="/js/smoothie.js" type="text/javascript"></script>
|
||||||
<script src="/js/noty/packaged/jquery.noty.packaged.min.js" type="text/javascript"></script>
|
<script src="/js/noty/packaged/jquery.noty.packaged.min.js" type="text/javascript"></script>
|
||||||
<link type="text/css" href="/css/reset.css" rel="stylesheet" />
|
<link type="text/css" href="/css/reset.css" rel="stylesheet" />
|
||||||
<link type="text/css" href="/css/styles.css" rel="stylesheet" />
|
<link type="text/css" href="/css/styles.css" rel="stylesheet" />
|
||||||
|
|||||||
@@ -226,12 +226,12 @@ var clonos={
|
|||||||
|
|
||||||
if($('span.close-but',dlg).length==0)
|
if($('span.close-but',dlg).length==0)
|
||||||
$('h1',dlg).before('<span class="close-but">×</span>');
|
$('h1',dlg).before('<span class="close-but">×</span>');
|
||||||
|
/*
|
||||||
var wd=$(dlg).width();
|
var wd=$(dlg).width();
|
||||||
var hg=$(dlg).height();
|
var hg=$(dlg).height();
|
||||||
var mt=hg/2;
|
var mt=hg/2;
|
||||||
var ml=wd/2;
|
var ml=wd/2;
|
||||||
|
*/
|
||||||
var res=$(dlg).get(0).showModal;
|
var res=$(dlg).get(0).showModal;
|
||||||
if(typeof res=='function')
|
if(typeof res=='function')
|
||||||
{
|
{
|
||||||
@@ -243,6 +243,7 @@ var clonos={
|
|||||||
{
|
{
|
||||||
$('dialog#'+id).before('<div id="backdrop"></div>');
|
$('dialog#'+id).before('<div id="backdrop"></div>');
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
$('dialog#'+id).css({
|
$('dialog#'+id).css({
|
||||||
'display':'block',
|
'display':'block',
|
||||||
'top':'50%',
|
'top':'50%',
|
||||||
@@ -252,10 +253,37 @@ var clonos={
|
|||||||
'position':'fixed',
|
'position':'fixed',
|
||||||
'z-index':'100000',
|
'z-index':'100000',
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
this.dialogSetPosition(dlg);
|
||||||
$('div#backdrop').css('display','block');
|
$('div#backdrop').css('display','block');
|
||||||
}
|
}
|
||||||
$(dlg).find('input[type=text],textarea').filter(':visible:first').focus();
|
$(dlg).find('input[type=text],textarea').filter(':visible:first').focus();
|
||||||
},
|
},
|
||||||
|
dialogSetPosition:function(dialog)
|
||||||
|
{
|
||||||
|
var wd=$(dialog).width();
|
||||||
|
var hg=$(dialog).height();
|
||||||
|
var mt=hg/2;
|
||||||
|
var ml=wd/2;
|
||||||
|
|
||||||
|
$(dialog).css({
|
||||||
|
'display':'block',
|
||||||
|
'top':'50%',
|
||||||
|
'margin-top':'-'+mt+'px',
|
||||||
|
'left':'50%',
|
||||||
|
'margin-left':'-'+ml+'px',
|
||||||
|
'position':'fixed',
|
||||||
|
'z-index':'100000',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
dialogFullscreen:function(btn)
|
||||||
|
{
|
||||||
|
|
||||||
|
var dialog=$(btn).parents('dialog');
|
||||||
|
$(dialog).toggleClass('fullscreen');
|
||||||
|
if(!$(dialog).hasClass('fullscreen'))
|
||||||
|
this.dialogSetPosition(dialog);
|
||||||
|
},
|
||||||
dialogClose:function()
|
dialogClose:function()
|
||||||
{
|
{
|
||||||
var dialogs=$('dialog');
|
var dialogs=$('dialog');
|
||||||
@@ -957,6 +985,11 @@ var clonos={
|
|||||||
//debugger;
|
//debugger;
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
|
// Если мы в нужной таблице, то рисуем графики
|
||||||
|
if(data.id=='bhyveslist' || data.id=='jailslist')
|
||||||
|
{
|
||||||
|
clonos.createGraphs();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fillTab:function(data)
|
fillTab:function(data)
|
||||||
@@ -2701,6 +2734,8 @@ var clonos={
|
|||||||
}
|
}
|
||||||
if(status==2)
|
if(status==2)
|
||||||
{
|
{
|
||||||
|
var o=$('#'+this.dotEscape(id));
|
||||||
|
if(!o.length) his.addNewJail(data,cmd);
|
||||||
this.evtStatus2(id,status,data);
|
this.evtStatus2(id,status,data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -2718,6 +2753,8 @@ var clonos={
|
|||||||
{
|
{
|
||||||
this.enableRip(id);
|
this.enableRip(id);
|
||||||
window.setTimeout($.proxy(this.deleteItemsOk,this,id),2000);
|
window.setTimeout($.proxy(this.deleteItemsOk,this,id),2000);
|
||||||
|
if(cmd=='jremove' || cmd=='bremove')
|
||||||
|
this.deleteGraphById(id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'update':
|
case 'update':
|
||||||
@@ -2740,7 +2777,7 @@ var clonos={
|
|||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(typeof data.data['protected'])
|
if(typeof data.data['protected']!='undefined')
|
||||||
if(isset(this.tpl_protected,data.data['protected']))
|
if(isset(this.tpl_protected,data.data['protected']))
|
||||||
{
|
{
|
||||||
var table=$('table.tsimple').attr('id');
|
var table=$('table.tsimple').attr('id');
|
||||||
@@ -2832,6 +2869,8 @@ var clonos={
|
|||||||
}
|
}
|
||||||
status=true;
|
status=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.createGraphById(id);
|
||||||
},
|
},
|
||||||
|
|
||||||
formatBytes:function(bytes,decimals)
|
formatBytes:function(bytes,decimals)
|
||||||
@@ -3002,8 +3041,172 @@ var clonos={
|
|||||||
document.cookie="lang="+lang+";path=/;";
|
document.cookie="lang="+lang+";path=/;";
|
||||||
location.reload();
|
location.reload();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createGraphs:function()
|
||||||
|
{
|
||||||
|
var grs=$('td.graph');
|
||||||
|
for(n=0,nl=grs.length;n<nl;n++)
|
||||||
|
{
|
||||||
|
var gr=grs[n];
|
||||||
|
this.createGraphByGr(gr);
|
||||||
|
}
|
||||||
|
graphs.getMetrics();
|
||||||
|
},
|
||||||
|
createGraphByGr(gr)
|
||||||
|
{
|
||||||
|
$(gr).css({'padding':'1px 0','margin':0,'vertical-align':'middle','font-size':0});
|
||||||
|
var cl=$(gr).attr('class');
|
||||||
|
var width=$(gr).width();
|
||||||
|
var height=$(gr).height();
|
||||||
|
var res=cl.match(/g-([^ ]+)/);
|
||||||
|
if(res!=null)
|
||||||
|
{
|
||||||
|
var name=res[1];
|
||||||
|
var g=new graph(name,width,height,gr);
|
||||||
|
g.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
createGraphById:function(id)
|
||||||
|
{
|
||||||
|
var gr=$('td.graph.g-'+id)
|
||||||
|
this.createGraphByGr(gr);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteGraphById:function(id)
|
||||||
|
{
|
||||||
|
delete graphs.list[id];
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- GRAPH START --- */
|
||||||
|
graphs={
|
||||||
|
list:{},
|
||||||
|
|
||||||
|
listAdd:function(name)
|
||||||
|
{
|
||||||
|
this.list.push(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
getMetrics:function()
|
||||||
|
{
|
||||||
|
this.wsconnect();
|
||||||
|
},
|
||||||
|
|
||||||
|
wsconnect:function()
|
||||||
|
{
|
||||||
|
this.client_id=this.name;
|
||||||
|
this.socket = new WebSocket("ws://"+_server_name+":8024/graph/client-"+Math.random());
|
||||||
|
$(this.socket).on('open',$.proxy(this.wsopen,this))
|
||||||
|
.on('close',$.proxy(this.wsclose,this))
|
||||||
|
.on('error',$.proxy(this.wserror,this))
|
||||||
|
.on('message',$.proxy(this.wsmessage,this));
|
||||||
|
},
|
||||||
|
wsclose:function(event)
|
||||||
|
{
|
||||||
|
if(event.wasClean)
|
||||||
|
{
|
||||||
|
var msg_type='warning';
|
||||||
|
var msg='Сервер закрыл соединение!';
|
||||||
|
}else{
|
||||||
|
var msg_type='error';
|
||||||
|
var msg='Соединение с сервером разорвано аварийно! Перезагрузите страницу!';
|
||||||
|
}
|
||||||
|
this.connected=false;
|
||||||
|
setTimeout($.proxy(this.wsconnect,this),5000);
|
||||||
|
},
|
||||||
|
wsopen:function(event)
|
||||||
|
{
|
||||||
|
this.connected=true;
|
||||||
|
},
|
||||||
|
wserror:function(event)
|
||||||
|
{
|
||||||
|
this.connected=false;
|
||||||
|
},
|
||||||
|
wsmessage:function(event)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
var msg=JSON.parse(event.originalEvent.data);
|
||||||
|
}catch(e){ }
|
||||||
|
|
||||||
|
if(msg && typeof msg.cmd!='undefined')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var larr=[];
|
||||||
|
for(l in this.list)
|
||||||
|
larr.push(this.list[l].name)
|
||||||
|
|
||||||
|
if(typeof msg=='object')
|
||||||
|
{
|
||||||
|
for(n in msg)
|
||||||
|
{
|
||||||
|
var inf=msg[n];
|
||||||
|
var name=inf['name'];
|
||||||
|
var gr=this.list[name];
|
||||||
|
if(gr)
|
||||||
|
{
|
||||||
|
gr.line1.append(new Date().getTime(), inf.pcpu);
|
||||||
|
gr.line2.append(new Date().getTime(), inf.pmem);
|
||||||
|
var res=larr.indexOf(name);
|
||||||
|
if(res>-1) larr.splice(res,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(n=0,nl=larr.length;n<nl;n++)
|
||||||
|
{
|
||||||
|
this.list[larr[n]].line1.append(new Date().getTime(), 0);
|
||||||
|
this.list[larr[n]].line2.append(new Date().getTime(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function graph(name,width,height,el_parent)
|
||||||
|
{
|
||||||
|
this.name=name;
|
||||||
|
this.height=height;
|
||||||
|
this.width=width;
|
||||||
|
this.el_parent=el_parent;
|
||||||
|
this.is_init=false;
|
||||||
|
this.el_id=null;
|
||||||
|
this.line1=null;
|
||||||
|
this.line2=null;
|
||||||
|
this.connected=false;
|
||||||
|
}
|
||||||
|
graph.prototype.create=function()
|
||||||
|
{
|
||||||
|
|
||||||
|
var el_parent=$(this.el_parent);
|
||||||
|
if(typeof el_parent=='undefined')
|
||||||
|
{
|
||||||
|
el_parent=$('body');
|
||||||
|
}else{
|
||||||
|
this.el_id=el_parent;
|
||||||
|
this.is_init=true;
|
||||||
|
}
|
||||||
|
$(el_parent).append('<canvas id="g-'+this.name+'" width="'+this.width+'" height="'+this.height+' vertical-align="middle"></canvas>');
|
||||||
|
|
||||||
|
this.line1 = new TimeSeries({resetBounds:false,resetBoundsInterval:5000});
|
||||||
|
this.line2 = new TimeSeries({resetBounds:false,resetBoundsInterval:5000});
|
||||||
|
|
||||||
|
var back_color='rgba(0,0,0,0.02)';
|
||||||
|
var line1_color='rgb(17,125,187)'; //'blue'; //'rgb(0,0,255)';
|
||||||
|
var line1_fillStyle='rgba(17,125,187,0.05)'; //'rgba(241, 240, 255, 0.4)';
|
||||||
|
var line2_color='rgb(149,40,180)'; //'green'; //'rgb(0,255,0)';
|
||||||
|
var line2_fillStyle='rgba(149,40,180,0.05)'; //'rgba(222,245,222,0.4)';
|
||||||
|
|
||||||
|
this.smoothie = new SmoothieChart({interpolation:'bezier',grid:{fillStyle:back_color,sharpLines:true,strokeStyle:'transparent'},labels:{fontSize:8,disabled:true,fillStyle:'#000000',precision:0},millisPerPixel:1000,tooltip:true,maxValue:100,minValue:0});
|
||||||
|
|
||||||
|
this.smoothie.addTimeSeries(this.line1, { strokeStyle: line1_color, lineWidth:1, fillStyle:line1_fillStyle }); //, fillStyle: 'rgba(0, 255, 0, 0.4)'
|
||||||
|
this.smoothie.addTimeSeries(this.line2, { strokeStyle: line2_color, lineWidth: 1, fillStyle:line2_fillStyle }); //, fillStyle: 'rgba(255, 0, 255, 0.3)'
|
||||||
|
|
||||||
|
this.smoothie.streamTo(document.getElementById('g-'+this.name), 5000);
|
||||||
|
graphs.list[this.name]=this;
|
||||||
|
}
|
||||||
|
/* === GRAPH END === */
|
||||||
|
|
||||||
function isset(varr){for(a in arguments){if(typeof arguments[a]=='undefined')return false;}return true;}
|
function isset(varr){for(a in arguments){if(typeof arguments[a]=='undefined')return false;}return true;}
|
||||||
|
|
||||||
function ws_debug(){
|
function ws_debug(){
|
||||||
|
|||||||
1099
public/js/smoothie.js
Normal file
1099
public/js/smoothie.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
|||||||
<tr class="#nth-num##desktop##maintenance#" id="#jname#">
|
<tr class="#nth-num##desktop##maintenance#" id="#jname#">
|
||||||
<td class="node">#node#</td>
|
<td class="node">#node#</td>
|
||||||
<td class="txtleft jname">#jname#</td>
|
<td class="txtleft jname">#jname#</td>
|
||||||
|
<td class="graph g-#jname#"></td>
|
||||||
<td class="txtcenter vm_ram">#vm_ram#</td>
|
<td class="txtcenter vm_ram">#vm_ram#</td>
|
||||||
<td class="vm_cpus">#vm_cpus#</td>
|
<td class="vm_cpus">#vm_cpus#</td>
|
||||||
<td class="vm_os_type">#vm_os_type#</td>
|
<td class="vm_os_type">#vm_os_type#</td>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ $clonos->useDialogs(array(
|
|||||||
<thead>
|
<thead>
|
||||||
<th class="wdt-120">Имя сервера</th>
|
<th class="wdt-120">Имя сервера</th>
|
||||||
<th class="txtleft">Виртуальная машина</th>
|
<th class="txtleft">Виртуальная машина</th>
|
||||||
|
<th class="wdt-120">Нагрузка</th>
|
||||||
<th class="txtcenter wdt-70">RAM</th>
|
<th class="txtcenter wdt-70">RAM</th>
|
||||||
<th class="wdt-30">CPU</th>
|
<th class="wdt-30">CPU</th>
|
||||||
<th class="txtcenter wdt-100">Тип ОС</th>
|
<th class="txtcenter wdt-100">Тип ОС</th>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ if(!empty($jail_ids))
|
|||||||
{
|
{
|
||||||
$tasks=$this->getRunningTasks($jail_ids);
|
$tasks=$this->getRunningTasks($jail_ids);
|
||||||
}
|
}
|
||||||
|
//echo '<pre>';print_r($tasks);exit;
|
||||||
$html_tpl_1=str_replace(array("\n","\r","\t"),'',$hres[1]);
|
$html_tpl_1=str_replace(array("\n","\r","\t"),'',$hres[1]);
|
||||||
if($hres!==false)
|
if($hres!==false)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<tr class="#nth-num##desktop##maintenance#" id="#jname#">
|
<tr class="#nth-num##desktop##maintenance#" id="#jname#">
|
||||||
<td class="wordwreck node">#node#</td>
|
<td class="wordwreck node">#node#</td>
|
||||||
<td class="txtleft jname">#jname#</td>
|
<td class="txtleft jname">#jname#</td>
|
||||||
|
<td class="graph g-#jname#"></td>
|
||||||
<td class="txtleft wordwreck ip4_addr">#ip4_addr#</td>
|
<td class="txtleft wordwreck ip4_addr">#ip4_addr#</td>
|
||||||
<td class="jstatus">#jstatus#</td>
|
<td class="jstatus">#jstatus#</td>
|
||||||
<td class="ops" width="5"><span class="icon-cnt"><span class="icon-#icon#"></span></span></td>
|
<td class="ops" width="5"><span class="icon-cnt"><span class="icon-#icon#"></span></span></td>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ $clonos->useDialogs(array(
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="wdt-200 elastic">Имя сервера</th>
|
<th class="wdt-200 elastic">Имя сервера</th>
|
||||||
<th class="txtleft">Контейнер</th>
|
<th class="txtleft">Контейнер</th>
|
||||||
|
<th class="wdt-120">Нагрузка</th>
|
||||||
<th class="txtleft wdt-200">IP-адрес</th>
|
<th class="txtleft wdt-200">IP-адрес</th>
|
||||||
<th class="txtcenter wdt-120">Статус</th>
|
<th class="txtcenter wdt-120">Статус</th>
|
||||||
<th colspan="4" class="wdt-100">Действия</th>
|
<th colspan="4" class="wdt-100">Действия</th>
|
||||||
|
|||||||
Reference in New Issue
Block a user