Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
31a4d920
Commit
31a4d920
authored
Oct 19, 2014
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
SingleStatPanel: Added graph to single stat panel, #951
parent
69fdfd5c
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
234 additions
and
193 deletions
+234
-193
src/app/components/settings.js
+1
-0
src/app/directives/panelMenu.js
+2
-2
src/app/panels/stats/module.html
+3
-29
src/app/panels/stats/module.js
+27
-149
src/app/panels/stats/statsDirective.js
+170
-0
src/app/panels/stats/statsEditor.html
+21
-13
src/css/less/stats-panel.less
+10
-0
No files found.
src/app/components/settings.js
View file @
31a4d920
...
...
@@ -17,6 +17,7 @@ function (_, crypto) {
window_title_prefix
:
'Grafana - '
,
panels
:
{
'graph'
:
{
path
:
'panels/graph'
},
'stats'
:
{
path
:
'panels/stats'
},
'text'
:
{
path
:
'panels/text'
}
},
plugins
:
{},
...
...
src/app/directives/panelMenu.js
View file @
31a4d920
...
...
@@ -9,7 +9,7 @@ function (angular, $, _) {
angular
.
module
(
'grafana.directives'
)
.
directive
(
'panelMenu'
,
function
(
$compile
)
{
var
linkTemplate
=
'<
a class="panel-title drag-handle">{{panel.title | interpolateTemplateVars}}</a
>'
;
var
linkTemplate
=
'<
span class="panel-title drag-handle pointer">{{panel.title | interpolateTemplateVars}}</span
>'
;
function
createMenuTemplate
(
$scope
)
{
var
template
=
'<div class="panel-menu small">'
;
...
...
@@ -113,7 +113,7 @@ function (angular, $, _) {
dismiss
(
2500
);
};
if
(
$scope
.
panelMeta
.
titlePos
)
{
if
(
$scope
.
panelMeta
.
titlePos
&&
$scope
.
panel
.
title
)
{
elem
.
css
(
'text-align'
,
'left'
);
$link
.
css
(
'padding-left'
,
'10px'
);
}
...
...
src/app/panels/stats/module.html
View file @
31a4d920
<div
ng-controller=
'StatsCtrl'
>
<div
stats-panel
></div>
<!-- <div class="stats-panel-value-container"> -->
<!-- <span class="stats-panel-value">{{mainstat.value}}</span> -->
<!-- <span class="stats-panel-func">({{mainstat.func}})</span> -->
<!-- </div> -->
<!-- -->
<!-- <table class="stats-panel-table"> -->
<!-- <tr> -->
<!-- <th></th> -->
<!-- <th>avg</th> -->
<!-- <th>min</th> -->
<!-- <th>max</th> -->
<!-- <th>current</th> -->
<!-- <th>total</th> -->
<!-- </tr> -->
<!-- <tr class="stats-series-item" ng-repeat="series in series"> -->
<!-- <td> -->
<!-- <i class='icon-minus pointer' ng-style="{color: series.color}"></i> -->
<!-- {{series.info.alias}} -->
<!-- </td> -->
<!-- <td>{{series.info.avg}}</td> -->
<!-- <td>{{series.info.min}}</td> -->
<!-- <td>{{series.info.max}}</td> -->
<!-- <td>{{series.info.current}}</td> -->
<!-- <td>{{series.info.total}}</td> -->
<!-- </tr> -->
<!-- </table> -->
<div
class=
"stats-panel"
stats-panel
></div>
<div
class=
"clearfix"
></div>
<div
style=
"margin-top: 30px"
ng-if=
"editMode"
>
<div
class=
"dashboard-editor-header"
>
<div
class=
"dashboard-editor-title"
>
<i
class=
"icon icon-
bar-chart
"
></i>
Panel settings
<i
class=
"icon icon-
dashboard
"
></i>
Singlestat
</div>
<div
ng-model=
"editor.index"
bs-tabs
>
...
...
src/app/panels/stats/module.js
View file @
31a4d920
...
...
@@ -5,14 +5,15 @@ define([
'components/timeSeries'
,
'kbn'
,
'services/panelSrv'
,
'./statsDirective'
,
],
function
(
angular
,
app
,
_
,
TimeSeries
,
kbn
)
{
'use strict'
;
var
module
=
angular
.
module
(
'grafana.panels.stats'
,
[]
);
var
module
=
angular
.
module
(
'grafana.panels.stats'
);
app
.
useModule
(
module
);
module
.
controller
(
'StatsCtrl'
,
function
(
$scope
,
panelSrv
,
timeSrv
,
$rootScope
)
{
module
.
controller
(
'StatsCtrl'
,
function
(
$scope
,
panelSrv
,
timeSrv
)
{
$scope
.
panelMeta
=
{
titlePos
:
'left'
,
...
...
@@ -27,7 +28,7 @@ function (angular, app, _, TimeSeries, kbn) {
src
:
'app/partials/metrics.html'
},
{
title
:
'
Display Style
s'
,
title
:
'
Option
s'
,
src
:
'app/panels/stats/statsEditor.html'
}
],
...
...
@@ -39,25 +40,20 @@ function (angular, app, _, TimeSeries, kbn) {
targets
:
[{}],
cacheTimeout
:
null
,
format
:
'none'
,
stats
:
{
show
:
true
,
avg
:
true
,
template
:
'{{value}} {{func}}'
},
coloring
:
{
thresholds
:
''
,
background
:
false
,
value
:
false
,
colors
:
[
"rgba(245, 54, 54, 0.9)"
,
"rgba(237, 129, 40, 0.89)"
,
"rgba(50, 172, 45, 0.97)"
]
},
table
:
{
show
:
true
,
template
:
'{{avg}} !(avg)'
,
thresholds
:
''
,
colorBackground
:
false
,
colorValue
:
false
,
colors
:
[
"rgba(245, 54, 54, 0.9)"
,
"rgba(237, 129, 40, 0.89)"
,
"rgba(50, 172, 45, 0.97)"
],
sparkline
:
{
show
:
false
,
full
:
false
,
lineColor
:
'rgb(31, 120, 193)'
,
fillColor
:
'rgb(31, 120, 193)'
,
}
};
_
.
defaults
(
$scope
.
panel
,
_d
);
_
.
defaults
(
$scope
.
panel
.
stats
,
_d
.
stats
);
_
.
defaults
(
$scope
.
panel
.
coloring
,
_d
.
coloring
);
$scope
.
init
=
function
()
{
panelSrv
.
init
(
$scope
);
...
...
@@ -101,43 +97,33 @@ function (angular, app, _, TimeSeries, kbn) {
$scope
.
render
();
};
$scope
.
seriesHandler
=
function
(
seriesData
,
index
)
{
var
datapoints
=
seriesData
.
datapoints
;
var
alias
=
seriesData
.
target
;
var
color
=
$rootScope
.
colors
[
index
];
var
seriesInfo
=
{
alias
:
alias
,
enable
:
true
,
color
:
color
};
$scope
.
seriesHandler
=
function
(
seriesData
)
{
var
series
=
new
TimeSeries
({
datapoints
:
datapoints
,
info
:
seriesInfo
,
datapoints
:
seriesData
.
datapoints
,
info
:
{
alias
:
seriesData
.
target
}
,
});
series
.
points
=
series
.
getFlotPairs
(
'connected'
);
series
.
data
=
series
.
getFlotPairs
(
'connected'
);
return
series
;
};
$scope
.
setColoring
=
function
(
options
)
{
if
(
options
.
background
)
{
$scope
.
panel
.
color
ing
.
v
alue
=
false
;
$scope
.
panel
.
color
ing
.
color
s
=
[
'rgba(71, 212, 59, 0.4)'
,
'rgba(245, 150, 40, 0.73)'
,
'rgba(225, 40, 40, 0.59)'
];
$scope
.
panel
.
color
V
alue
=
false
;
$scope
.
panel
.
colors
=
[
'rgba(71, 212, 59, 0.4)'
,
'rgba(245, 150, 40, 0.73)'
,
'rgba(225, 40, 40, 0.59)'
];
}
else
{
$scope
.
panel
.
color
ing
.
b
ackground
=
false
;
$scope
.
panel
.
color
ing
.
color
s
=
[
'rgba(50, 172, 45, 0.97)'
,
'rgba(237, 129, 40, 0.89)'
,
'rgba(245, 54, 54, 0.9)'
];
$scope
.
panel
.
color
B
ackground
=
false
;
$scope
.
panel
.
colors
=
[
'rgba(50, 172, 45, 0.97)'
,
'rgba(237, 129, 40, 0.89)'
,
'rgba(245, 54, 54, 0.9)'
];
}
$scope
.
render
();
};
$scope
.
invertColorOrder
=
function
()
{
var
tmp
=
$scope
.
panel
.
color
ing
.
color
s
[
0
];
$scope
.
panel
.
color
ing
.
colors
[
0
]
=
$scope
.
panel
.
coloring
.
colors
[
2
];
$scope
.
panel
.
color
ing
.
color
s
[
2
]
=
tmp
;
var
tmp
=
$scope
.
panel
.
colors
[
0
];
$scope
.
panel
.
color
s
[
0
]
=
$scope
.
panel
.
colors
[
2
];
$scope
.
panel
.
colors
[
2
]
=
tmp
;
$scope
.
render
();
};
...
...
@@ -153,11 +139,11 @@ function (angular, app, _, TimeSeries, kbn) {
series
.
updateLegendValues
(
kbn
.
valueFormats
[
$scope
.
panel
.
format
],
2
,
-
7
);
}
data
.
thresholds
=
$scope
.
panel
.
coloring
.
thresholds
.
split
(
','
).
map
(
function
(
strVale
)
{
data
.
thresholds
=
$scope
.
panel
.
thresholds
.
split
(
','
).
map
(
function
(
strVale
)
{
return
Number
(
strVale
.
trim
());
});
data
.
colorMap
=
$scope
.
panel
.
color
ing
.
color
s
;
data
.
colorMap
=
$scope
.
panel
.
colors
;
$scope
.
data
=
data
;
$scope
.
$emit
(
'render'
);
...
...
@@ -165,112 +151,4 @@ function (angular, app, _, TimeSeries, kbn) {
$scope
.
init
();
});
module
.
directive
(
'statsPanel'
,
function
()
{
return
{
link
:
function
(
scope
,
elem
)
{
var
data
;
var
valueRegex
=
/
\{\{([
a-zA-Z
]
+
)\}\}
/g
;
var
smallValueTextRegex
=
/!
(\S
+
)
/g
;
scope
.
$on
(
'render'
,
function
()
{
data
=
scope
.
data
;
data
.
mainValue
=
null
;
if
(
!
data
||
data
.
series
.
length
===
0
)
{
elem
.
html
(
'no data'
);
return
;
}
render
();
});
function
applyColoringThresholds
(
value
,
valueString
)
{
if
(
!
scope
.
panel
.
coloring
.
value
)
{
return
valueString
;
}
var
color
=
getColorForValue
(
value
);
if
(
color
)
{
return
'<span style="color:'
+
color
+
'">'
+
valueString
+
'</span>'
;
}
return
valueString
;
}
function
getColorForValue
(
value
)
{
for
(
var
i
=
data
.
thresholds
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
value
>
data
.
thresholds
[
i
])
{
return
data
.
colorMap
[
i
];
}
}
return
null
;
}
function
valueTemplateReplaceFunc
(
match
,
statType
)
{
var
stats
=
data
.
series
[
0
].
stats
;
data
.
mainValue
=
stats
[
statType
];
var
valueFormated
=
scope
.
formatValue
(
data
.
mainValue
);
return
applyColoringThresholds
(
data
.
mainValue
,
valueFormated
);
}
function
smallValueTextReplaceFunc
(
match
,
text
)
{
return
'<span class="stats-panel-value-small">'
+
text
+
'</span>'
;
}
function
render
()
{
var
panel
=
scope
.
panel
;
var
body
=
''
;
var
i
,
series
;
if
(
panel
.
stats
.
show
)
{
body
+=
'<div class="stats-panel-value-container">'
;
body
+=
'<span class="stats-panel-value">'
;
var
valueHtml
=
panel
.
stats
.
template
.
replace
(
valueRegex
,
valueTemplateReplaceFunc
);
body
+=
valueHtml
.
replace
(
smallValueTextRegex
,
smallValueTextReplaceFunc
);
body
+=
'</div>'
;
body
+=
'</div>'
;
}
if
(
panel
.
coloring
.
background
&&
data
.
mainValue
)
{
var
color
=
getColorForValue
(
data
.
mainValue
);
if
(
color
)
{
elem
.
parents
(
'.panel-container'
).
css
(
'background-color'
,
color
);
if
(
scope
.
fullscreen
)
{
elem
.
css
(
'background-color'
,
color
);
}
else
{
elem
.
css
(
'background-color'
,
''
);
}
}
}
else
{
elem
.
parents
(
'.panel-container'
).
css
(
'background-color'
,
''
);
elem
.
css
(
'background-color'
,
''
);
}
if
(
panel
.
table
.
show
)
{
body
+=
'<table class="stats-panel-table">'
;
body
+=
'<tr>'
;
body
+=
'<th></th><th>avg</th><th>min</th><th>max</th><th>current</th><th>total</th>'
;
body
+=
'</tr>'
;
for
(
i
=
0
;
i
<
data
.
series
.
length
;
i
++
)
{
series
=
data
.
series
[
i
];
body
+=
'<tr>'
;
body
+=
'<td><i class="icon-minus pointer" style="color:'
+
series
.
color
+
'"></i> '
;
body
+=
series
.
info
.
alias
+
' </td>'
;
body
+=
'<td>'
+
series
.
info
.
avg
+
'</td>'
;
body
+=
'<td>'
+
series
.
info
.
min
+
'</td>'
;
body
+=
'<td>'
+
series
.
info
.
max
+
'</td>'
;
body
+=
'<td>'
+
series
.
info
.
total
+
'</td>'
;
body
+=
'<td>'
+
series
.
info
.
current
+
'</td>'
;
}
body
+=
'</table>'
;
}
elem
.
html
(
body
);
}
}
};
});
});
src/app/panels/stats/statsDirective.js
0 → 100644
View file @
31a4d920
define
([
'angular'
,
'app'
,
'lodash'
,
'kbn'
,
'jquery'
,
'jquery.flot'
,
'jquery.flot.time'
,
],
function
(
angular
,
app
,
_
,
kbn
,
$
)
{
'use strict'
;
var
module
=
angular
.
module
(
'grafana.panels.stats'
,
[]);
app
.
useModule
(
module
);
module
.
directive
(
'statsPanel'
,
function
()
{
return
{
link
:
function
(
scope
,
elem
)
{
var
data
;
var
valueRegex
=
/
\{\{([
a-zA-Z
]
+
)\}\}
/g
;
var
smallValueTextRegex
=
/!
(\S
+
)
/g
;
var
$panelContainer
=
elem
.
parents
(
'.panel-container'
);
scope
.
$on
(
'render'
,
function
()
{
data
=
scope
.
data
;
data
.
mainValue
=
null
;
if
(
!
data
||
data
.
series
.
length
===
0
)
{
elem
.
html
(
'no data'
);
return
;
}
render
();
});
function
setElementHeight
()
{
try
{
var
height
=
scope
.
height
||
scope
.
panel
.
height
||
scope
.
row
.
height
;
if
(
_
.
isString
(
height
))
{
height
=
parseInt
(
height
.
replace
(
'px'
,
''
),
10
);
}
height
-=
scope
.
panel
.
title
?
24
:
9
;
// subtract panel title bar
elem
.
css
(
'height'
,
height
+
'px'
);
return
true
;
}
catch
(
e
)
{
// IE throws errors sometimes
return
false
;
}
}
function
applyColoringThresholds
(
value
,
valueString
)
{
if
(
!
scope
.
panel
.
colorValue
)
{
return
valueString
;
}
var
color
=
getColorForValue
(
value
);
if
(
color
)
{
return
'<span style="color:'
+
color
+
'">'
+
valueString
+
'</span>'
;
}
return
valueString
;
}
function
getColorForValue
(
value
)
{
for
(
var
i
=
data
.
thresholds
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
value
>
data
.
thresholds
[
i
])
{
return
data
.
colorMap
[
i
];
}
}
return
null
;
}
function
valueTemplateReplaceFunc
(
match
,
statType
)
{
var
stats
=
data
.
series
[
0
].
stats
;
data
.
mainValue
=
stats
[
statType
];
var
valueFormated
=
scope
.
formatValue
(
data
.
mainValue
);
return
applyColoringThresholds
(
data
.
mainValue
,
valueFormated
);
}
function
smallValueTextReplaceFunc
(
match
,
text
)
{
return
'<span class="stats-panel-value-small">'
+
text
+
'</span>'
;
}
function
render
()
{
setElementHeight
();
var
panel
=
scope
.
panel
;
var
body
=
''
;
body
+=
'<div class="stats-panel-value-container">'
;
body
+=
'<span class="stats-panel-value">'
;
var
valueHtml
=
panel
.
template
.
replace
(
valueRegex
,
valueTemplateReplaceFunc
);
body
+=
valueHtml
.
replace
(
smallValueTextRegex
,
smallValueTextReplaceFunc
);
body
+=
'</div>'
;
body
+=
'</div>'
;
if
(
panel
.
colorBackground
&&
data
.
mainValue
)
{
var
color
=
getColorForValue
(
data
.
mainValue
);
if
(
color
)
{
$panelContainer
.
css
(
'background-color'
,
color
);
if
(
scope
.
fullscreen
)
{
elem
.
css
(
'background-color'
,
color
);
}
else
{
elem
.
css
(
'background-color'
,
''
);
}
}
}
else
{
$panelContainer
.
css
(
'background-color'
,
''
);
elem
.
css
(
'background-color'
,
''
);
}
var
width
=
elem
.
width
()
+
20
;
var
height
=
elem
.
height
()
||
100
;
var
plotCanvas
=
$
(
'<div></div>'
);
var
plotCss
=
{};
plotCss
.
position
=
'absolute'
;
if
(
panel
.
sparkline
.
full
)
{
plotCss
.
bottom
=
'5px'
;
plotCss
.
left
=
'-5px'
;
plotCss
.
width
=
(
width
-
10
)
+
'px'
;
plotCss
.
height
=
(
height
-
45
)
+
'px'
;
}
else
{
plotCss
.
bottom
=
"0px"
;
plotCss
.
left
=
"-5px"
;
plotCss
.
width
=
(
width
-
10
)
+
'px'
;
plotCss
.
height
=
Math
.
floor
(
height
*
0.3
)
+
"px"
;
}
plotCanvas
.
css
(
plotCss
);
var
options
=
{
legend
:
{
show
:
false
},
series
:
{
lines
:
{
show
:
true
,
fill
:
1
,
lineWidth
:
1
,
fillColor
:
panel
.
sparkline
.
fillColor
,
},
},
yaxes
:
{
show
:
false
},
xaxis
:
{
show
:
false
,
mode
:
"time"
,
min
:
scope
.
range
.
from
.
getTime
(),
max
:
scope
.
range
.
to
.
getTime
(),
},
grid
:
{
hoverable
:
false
,
show
:
false
},
};
elem
.
html
(
body
);
elem
.
append
(
plotCanvas
);
data
.
series
[
0
].
color
=
panel
.
sparkline
.
lineColor
;
setTimeout
(
function
()
{
$
.
plot
(
plotCanvas
,
[
data
.
series
[
0
]],
options
);
},
200
);
}
}
};
});
});
src/app/panels/stats/statsEditor.html
View file @
31a4d920
<div
class=
"editor-row"
>
<div
class=
"section"
>
<h5>
Main options
</h5>
<editor-opt-bool
text=
"Show table"
model=
"panel.table.show"
change=
"render()"
></editor-opt-bool>
<editor-opt-bool
text=
"Show big values"
model=
"panel.stats.show"
change=
"render()"
></editor-opt-bool>
</div>
<div
class=
"section"
ng-if=
"panel.stats"
>
<div
class=
"section"
>
<h5>
Big values
</h5>
<div
class=
"editor-option"
>
<label
class=
"small"
>
Template
</label>
<input
type=
"text"
class=
"input-
large"
ng-model=
"panel.stats
.template"
ng-blur=
"render()"
></input>
<input
type=
"text"
class=
"input-
xlarge"
ng-model=
"panel
.template"
ng-blur=
"render()"
></input>
</div>
</div>
<div
class=
"section"
>
...
...
@@ -20,17 +15,17 @@
</div>
<div
class=
"section"
>
<h5>
Coloring
</h5>
<editor-opt-bool
text=
"Background"
model=
"panel.color
ing.b
ackground"
change=
"setColoring({background: true})"
></editor-opt-bool>
<editor-opt-bool
text=
"Value"
model=
"panel.color
ing.v
alue"
change=
"setColoring({value: true})"
></editor-opt-bool>
<editor-opt-bool
text=
"Background"
model=
"panel.color
B
ackground"
change=
"setColoring({background: true})"
></editor-opt-bool>
<editor-opt-bool
text=
"Value"
model=
"panel.color
V
alue"
change=
"setColoring({value: true})"
></editor-opt-bool>
<div
class=
"editor-option"
>
<label
class=
"small"
>
Thresholds
</label>
<input
type=
"text"
class=
"input-large"
ng-model=
"panel.
coloring.
thresholds"
ng-blur=
"render()"
></input>
<input
type=
"text"
class=
"input-large"
ng-model=
"panel.thresholds"
ng-blur=
"render()"
></input>
</div>
<div
class=
"editor-option"
>
<label
class=
"small"
>
Color
</label>
<spectrum-picker
ng-model=
"panel.color
ing.color
s[0]"
ng-change=
"render()"
></spectrum-picker>
<spectrum-picker
ng-model=
"panel.color
ing.color
s[1]"
ng-change=
"render()"
></spectrum-picker>
<spectrum-picker
ng-model=
"panel.color
ing.color
s[2]"
ng-change=
"render()"
></spectrum-picker>
<spectrum-picker
ng-model=
"panel.colors[0]"
ng-change=
"render()"
></spectrum-picker>
<spectrum-picker
ng-model=
"panel.colors[1]"
ng-change=
"render()"
></spectrum-picker>
<spectrum-picker
ng-model=
"panel.colors[2]"
ng-change=
"render()"
></spectrum-picker>
<a
class=
"pointer"
ng-click=
"invertColorOrder()"
>
invert order
</a>
</div>
</div>
...
...
@@ -38,6 +33,19 @@
<div
class=
"editor-row"
>
<div
class=
"section"
>
<h5>
Spark lines
</h5>
</div>
<editor-opt-bool
text=
"Spark line"
model=
"panel.sparkline.show"
change=
"render()"
></editor-opt-bool>
<editor-opt-bool
text=
"Background mode"
model=
"panel.sparkline.full"
change=
"render()"
></editor-opt-bool>
<div
class=
"editor-option"
>
<label
class=
"small"
>
Line color
</label>
<spectrum-picker
ng-model=
"panel.sparkline.lineColor"
ng-change=
"render()"
></spectrum-picker>
<spectrum-picker
ng-model=
"panel.sparkline.fillColor"
ng-change=
"render()"
></spectrum-picker>
</div>
</div>
<div
class=
"editor-row"
>
<div
class=
"section"
>
<h5>
Series options
</h5>
<div
class=
"grafana-target"
ng-repeat=
"series in data.series"
>
<div
class=
"grafana-target-inner"
>
...
...
src/css/less/stats-panel.less
View file @
31a4d920
.stats-panel {
position: relative;
display: table;
width: 100%;
}
.stats-panel-value-container {
padding: 20px;
display: table-cell;
vertical-align: middle;
text-align: center;
position: relative;
z-index: 1;
}
.stats-panel-value {
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment