Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.toolbox
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kirill Smelkov
slapos.toolbox
Commits
c5eaa61f
Commit
c5eaa61f
authored
Feb 07, 2012
by
Alain Takoudjou
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allowing to calculate MD5 sum of a file in the git repository
parent
3928ec49
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
122 additions
and
28 deletions
+122
-28
slapos/runner/static/css/styles.css
slapos/runner/static/css/styles.css
+11
-5
slapos/runner/static/images/cloud_bg.jpg
slapos/runner/static/images/cloud_bg.jpg
+0
-0
slapos/runner/static/js/scripts/editor.js
slapos/runner/static/js/scripts/editor.js
+27
-1
slapos/runner/static/js/scripts/softwareFolder.js
slapos/runner/static/js/scripts/softwareFolder.js
+36
-8
slapos/runner/templates/projectFolder.html
slapos/runner/templates/projectFolder.html
+2
-2
slapos/runner/templates/softwareFolder.html
slapos/runner/templates/softwareFolder.html
+3
-2
slapos/runner/templates/updateInstanceProfile.html
slapos/runner/templates/updateInstanceProfile.html
+2
-1
slapos/runner/templates/updateSoftwareProfile.html
slapos/runner/templates/updateSoftwareProfile.html
+2
-1
slapos/runner/utils.py
slapos/runner/utils.py
+26
-5
slapos/runner/views.py
slapos/runner/views.py
+13
-3
No files found.
slapos/runner/static/css/styles.css
View file @
c5eaa61f
...
@@ -44,7 +44,7 @@ textarea {
...
@@ -44,7 +44,7 @@ textarea {
}
}
body
{
body
{
background
:
#
0271BF
;
background
:
#
7195c2
url('../images/cloud_bg.jpg')
repeat-x
;
font
:
13px
'Helvetica Neue'
,
Helvetica
,
Arial
,
sans-serif
;
font
:
13px
'Helvetica Neue'
,
Helvetica
,
Arial
,
sans-serif
;
color
:
#000000
;
color
:
#000000
;
}
}
...
@@ -217,6 +217,13 @@ body {
...
@@ -217,6 +217,13 @@ body {
font-weight
:
normal
;
font-weight
:
normal
;
font-size
:
18px
;
font-size
:
18px
;
}
}
.content
h2
a
{
text-decoration
:
none
;
color
:
#4DA0C6
;
}
.content
h2
a
:hover
{
color
:
#19485C
;
}
.main_foot
{
.main_foot
{
height
:
15px
;
height
:
15px
;
...
@@ -631,9 +638,8 @@ a.lshare img{
...
@@ -631,9 +638,8 @@ a.lshare img{
.error_message
{
background
:
url(../images/exit.png)
center
no-repeat
;
height
:
26px
;}
.error_message
{
background
:
url(../images/exit.png)
center
no-repeat
;
height
:
26px
;}
.confirm_message
{
background
:
url(../images/confirm.png)
center
no-repeat
;
height
:
26px
;}
.confirm_message
{
background
:
url(../images/confirm.png)
center
no-repeat
;
height
:
26px
;}
.info_message
{
background
:
url(../images/info.png)
center
no-repeat
;
height
:
26px
;}
.info_message
{
background
:
url(../images/info.png)
center
no-repeat
;
height
:
26px
;}
#error
p
{
#error
p
{
fon-size
:
13px
;
color
:
#4A131F
;}
fon-size
:
13px
;
color
:
#000
;
}
#pClose
{
background
:
url(../images/close.png)
no-repeat
0px
0px
;
display
:
block
;
width
:
22px
;
height
:
22px
;
cursor
:
pointer
}
#pClose
{
background
:
url(../images/close.png)
no-repeat
0px
0px
;
display
:
block
;
width
:
22px
;
height
:
22px
;
cursor
:
pointer
}
#pClose
:hover
{
background
:
url(../images/close_hover.png)
no-repeat
0px
0px
;}
#pClose
:hover
{
background
:
url(../images/close_hover.png)
no-repeat
0px
0px
;}
.md5sum
{
margin
:
10px
;
font-size
:
15px
;}
.title
{
background
:
#e4e4e4
;
width
:
100%
;
height
:
25px
;
padding-top
:
2px
;
text-indent
:
5px
;
color
:
#737373
;
text-shadow
:
0px
1px
#FFF
;}
\ No newline at end of file
slapos/runner/static/images/cloud_bg.jpg
0 → 100644
View file @
c5eaa61f
431 Bytes
slapos/runner/static/js/scripts/editor.js
View file @
c5eaa61f
...
@@ -10,6 +10,7 @@ $(document).ready( function() {
...
@@ -10,6 +10,7 @@ $(document).ready( function() {
var
file
=
$
(
"
input#profile
"
).
val
();
var
file
=
$
(
"
input#profile
"
).
val
();
var
edit
=
false
;
var
edit
=
false
;
var
send
=
false
;
selectFile
(
file
);
selectFile
(
file
);
$
(
"
#save
"
).
click
(
function
(){
$
(
"
#save
"
).
click
(
function
(){
...
@@ -17,7 +18,8 @@ $(document).ready( function() {
...
@@ -17,7 +18,8 @@ $(document).ready( function() {
$
(
"
#error
"
).
Popup
(
"
Can not load your file, please make sure that you have selected a Software Release
"
,
{
type
:
'
alert
'
,
duration
:
5000
});
$
(
"
#error
"
).
Popup
(
"
Can not load your file, please make sure that you have selected a Software Release
"
,
{
type
:
'
alert
'
,
duration
:
5000
});
return
false
;
return
false
;
}
}
send
=
false
;
if
(
send
)
return
;
send
=
true
$
.
ajax
({
$
.
ajax
({
type
:
"
POST
"
,
type
:
"
POST
"
,
url
:
$SCRIPT_ROOT
+
'
/saveFileContent
'
,
url
:
$SCRIPT_ROOT
+
'
/saveFileContent
'
,
...
@@ -34,6 +36,10 @@ $(document).ready( function() {
...
@@ -34,6 +36,10 @@ $(document).ready( function() {
});
});
return
false
;
return
false
;
});
});
$
(
"
#getmd5
"
).
click
(
function
(){
getmd5sum
();
return
false
;
});
function
selectFile
(
file
){
function
selectFile
(
file
){
edit
=
false
;
edit
=
false
;
...
@@ -53,4 +59,24 @@ $(document).ready( function() {
...
@@ -53,4 +59,24 @@ $(document).ready( function() {
});
});
return
;
return
;
}
}
function
getmd5sum
(){
if
(
send
)
return
;
send
=
true
$
.
ajax
({
type
:
"
POST
"
,
url
:
$SCRIPT_ROOT
+
'
/getmd5sum
'
,
data
:
{
file
:
file
},
success
:
function
(
data
){
if
(
data
.
code
==
1
){
$
(
"
#md5sum
"
).
empty
();
$
(
"
#md5sum
"
).
append
(
'
md5sum : <span>
'
+
data
.
result
+
'
</span>
'
);
}
else
{
$
(
"
#error
"
).
Popup
(
data
.
result
,
{
type
:
'
error
'
,
duration
:
5000
});
}
send
=
false
;
}
});
}
});
});
\ No newline at end of file
slapos/runner/static/js/scripts/softwareFolder.js
View file @
c5eaa61f
...
@@ -74,15 +74,14 @@ $(document).ready( function() {
...
@@ -74,15 +74,14 @@ $(document).ready( function() {
$
(
"
#error
"
).
Popup
(
"
Please select the file to edit
"
,
{
type
:
'
alert
'
,
duration
:
3000
});
$
(
"
#error
"
).
Popup
(
"
Please select the file to edit
"
,
{
type
:
'
alert
'
,
duration
:
3000
});
return
false
;
return
false
;
}
}
send
=
false
;
if
(
send
)
return
false
;
send
=
true
;
$
.
ajax
({
$
.
ajax
({
type
:
"
POST
"
,
type
:
"
POST
"
,
url
:
$SCRIPT_ROOT
+
'
/saveFileContent
'
,
url
:
$SCRIPT_ROOT
+
'
/saveFileContent
'
,
data
:
{
file
:
$
(
"
input#subfolder
"
).
val
(),
content
:
editor
.
getSession
().
getValue
()},
data
:
{
file
:
$
(
"
input#subfolder
"
).
val
(),
content
:
editor
.
getSession
().
getValue
()},
success
:
function
(
data
){
success
:
function
(
data
){
if
(
data
.
code
==
1
){
if
(
data
.
code
==
1
){
$
(
"
#flash
"
).
fadeOut
(
'
normal
'
);
$
(
"
#flash
"
).
empty
();
$
(
"
#error
"
).
Popup
(
"
File saved succefuly!
"
,
{
type
:
'
confirm
'
,
duration
:
3000
});
$
(
"
#error
"
).
Popup
(
"
File saved succefuly!
"
,
{
type
:
'
confirm
'
,
duration
:
3000
});
}
}
else
{
else
{
...
@@ -108,9 +107,34 @@ $(document).ready( function() {
...
@@ -108,9 +107,34 @@ $(document).ready( function() {
$
(
"
#info
"
).
empty
();
$
(
"
#info
"
).
empty
();
$
(
"
#info
"
).
append
(
"
Select parent directory or nothing for root...
"
);
$
(
"
#info
"
).
append
(
"
Select parent directory or nothing for root...
"
);
$
(
"
input#subfolder
"
).
val
(
""
);
$
(
"
input#subfolder
"
).
val
(
""
);
$
(
"
#edit_info
"
).
empty
();
$
(
"
#edit_info
"
).
append
(
"
No file selected
"
);
editor
.
getSession
().
setValue
(
""
);
$
(
"
#md5sum
"
).
empty
();
return
false
;
return
false
;
});
});
function
getmd5sum
(){
var
file
=
$
(
"
input#subfolder
"
).
val
();
if
(
send
)
return
;
send
=
true
$
.
ajax
({
type
:
"
POST
"
,
url
:
$SCRIPT_ROOT
+
'
/getmd5sum
'
,
data
:
{
file
:
$
(
"
input#subfolder
"
).
val
()},
success
:
function
(
data
){
if
(
data
.
code
==
1
){
$
(
"
#md5sum
"
).
empty
();
$
(
"
#md5sum
"
).
append
(
'
md5sum : <span>
'
+
data
.
result
+
'
</span>
'
);
}
else
{
$
(
"
#error
"
).
Popup
(
data
.
result
,
{
type
:
'
error
'
,
duration
:
5000
});
}
send
=
false
;
}
});
}
function
switchContent
(){
function
switchContent
(){
var
root
=
projectDir
;
var
root
=
projectDir
;
if
(
!
softwareDisplay
){
if
(
!
softwareDisplay
){
...
@@ -149,6 +173,7 @@ $(document).ready( function() {
...
@@ -149,6 +173,7 @@ $(document).ready( function() {
$
(
"
#info
"
).
empty
();
$
(
"
#info
"
).
empty
();
$
(
"
#info
"
).
append
(
relativeFile
);
$
(
"
#info
"
).
append
(
relativeFile
);
$
(
"
input#subfolder
"
).
val
(
file
);
$
(
"
input#subfolder
"
).
val
(
file
);
$
(
"
#md5sum
"
).
empty
();
path
=
""
;
path
=
""
;
send
=
false
;
send
=
false
;
edit
=
false
;
edit
=
false
;
...
@@ -159,14 +184,17 @@ $(document).ready( function() {
...
@@ -159,14 +184,17 @@ $(document).ready( function() {
data
:
"
file=
"
+
file
,
data
:
"
file=
"
+
file
,
success
:
function
(
data
){
success
:
function
(
data
){
if
(
data
.
code
==
1
){
if
(
data
.
code
==
1
){
$
(
"
#flash
"
).
fadeOut
(
'
normal
'
);
md5link
=
"
<a href='#' id='getmd5' title='Show or Update md5sum value'>[md5]</a>
"
$
(
"
#flash
"
).
empty
();
$
(
"
#edit_info
"
).
empty
();
$
(
"
#edit_info
"
).
empty
();
var
name
=
file
.
split
(
'
/
'
);
var
name
=
file
.
split
(
'
/
'
);
$
(
"
#edit_info
"
).
append
(
"
Edit selected
file:
"
+
$
(
"
#edit_info
"
).
append
(
"
Current
file:
"
+
relativeFile
);
relativeFile
+
md5link
);
editor
.
getSession
().
setValue
(
data
.
result
);
editor
.
getSession
().
setValue
(
data
.
result
);
setEditMode
(
name
[
name
.
length
-
1
]);
setEditMode
(
name
[
name
.
length
-
1
]);
$
(
"
#getmd5
"
).
click
(
function
(){
getmd5sum
();
return
false
;
});
edit
=
true
;
edit
=
true
;
}
}
else
{
else
{
...
@@ -178,7 +206,7 @@ $(document).ready( function() {
...
@@ -178,7 +206,7 @@ $(document).ready( function() {
}
}
else
{
else
{
$
(
"
#edit_info
"
).
empty
();
$
(
"
#edit_info
"
).
empty
();
$
(
"
#edit_info
"
).
append
(
"
Edit your selected file
"
);
$
(
"
#edit_info
"
).
append
(
"
No file selected
"
);
editor
.
getSession
().
setValue
(
""
);
editor
.
getSession
().
setValue
(
""
);
}
}
return
;
return
;
...
...
slapos/runner/templates/projectFolder.html
View file @
c5eaa61f
...
@@ -14,7 +14,7 @@
...
@@ -14,7 +14,7 @@
<input
type=
"hidden"
name=
"method"
id=
"method"
value=
"{{method}}"
/>
<input
type=
"hidden"
name=
"method"
id=
"method"
value=
"{{method}}"
/>
{% if method == "new" %}
{% if method == "new" %}
<div
id=
"addsoftware"
>
<div
id=
"addsoftware"
>
<h2>
What is the name of your software?
</h2>
<h2>
What is the name of your software
release
?
</h2>
<label
for=
'software'
>
Name:
</label>
<label
for=
'software'
>
Name:
</label>
<input
type=
"text"
name=
"software"
id=
"software"
size=
'30'
value=
"Enter software name..."
/>
<input
type=
"text"
name=
"software"
id=
"software"
size=
'30'
value=
"Enter software name..."
/>
<br/><br/>
<br/><br/>
...
@@ -28,7 +28,7 @@
...
@@ -28,7 +28,7 @@
</div>
</div>
{% elif method == "open" %}
{% elif method == "open" %}
<div
id=
"openSoftware"
>
<div
id=
"openSoftware"
>
<h2>
Select the folder of your software into the box
</h2>
<h2>
Select the folder of your software
release
into the box
</h2>
<div
id=
"fileTree"
class=
"file_tree"
></div>
<div
id=
"fileTree"
class=
"file_tree"
></div>
<div
id=
"file_info"
class=
"file_info"
>
<div
id=
"file_info"
class=
"file_info"
>
<img
src=
"{{ url_for('static', filename='images/check.png') }}"
class=
"check"
id=
"check"
alt=
""
/>
<img
src=
"{{ url_for('static', filename='images/check.png') }}"
class=
"check"
id=
"check"
alt=
""
/>
...
...
slapos/runner/templates/softwareFolder.html
View file @
c5eaa61f
...
@@ -18,7 +18,7 @@
...
@@ -18,7 +18,7 @@
<input
type=
"hidden"
name=
"workdir"
id=
"workdir"
value=
"{{workDir}}"
/>
<input
type=
"hidden"
name=
"workdir"
id=
"workdir"
value=
"{{workDir}}"
/>
<input
type=
"hidden"
name=
"subfolder"
id=
"subfolder"
value=
""
/>
<input
type=
"hidden"
name=
"subfolder"
id=
"subfolder"
value=
""
/>
<div
id=
"software_folder"
>
<div
id=
"software_folder"
>
<h2
class=
"hight show"
id=
"details_head"
>
Current software file content
</h2>
<h2
class=
"hight show"
id=
"details_head"
>
Current software
release
file content
</h2>
<div
class=
"software_details"
id=
"details_box"
>
<div
class=
"software_details"
id=
"details_box"
>
<div
id=
"fileTree"
class=
"file_tree_short"
></div>
<div
id=
"fileTree"
class=
"file_tree_short"
></div>
<div
class=
"box_software"
>
<div
class=
"box_software"
>
...
@@ -37,7 +37,8 @@
...
@@ -37,7 +37,8 @@
<div
class=
"clear"
></div>
<div
class=
"clear"
></div>
</div>
</div>
<div
id=
"code"
style=
"margin-top:10px"
>
<div
id=
"code"
style=
"margin-top:10px"
>
<h2
id=
"edit_info"
>
Edit your selected file
</h2>
<h2
id=
"edit_info"
class=
'title'
>
No file selected
</h2>
<div
class=
'md5sum'
id=
'md5sum'
></div>
<div
class=
"main_content"
>
<div
class=
"main_content"
>
<pre
id=
"editor"
>
<pre
id=
"editor"
>
...
...
slapos/runner/templates/updateInstanceProfile.html
View file @
c5eaa61f
...
@@ -10,7 +10,8 @@
...
@@ -10,7 +10,8 @@
{% block body %}
{% block body %}
<form
method=
post
class=
add-entry
>
<form
method=
post
class=
add-entry
>
<dl>
<dl>
<dd><h2>
Instance Profile:
</h2></dd>
<dd><h2>
Edit instance profile
<a
href=
'#'
id=
'getmd5'
title=
'Show or Update md5sum value'
>
[md5]
</a>
:
</h2></dd>
<dd><div
class=
'md5sum'
id=
'md5sum'
></div></dd>
<dd>
<dd>
<div
class=
"main_content"
>
<div
class=
"main_content"
>
<pre
id=
"editor"
></pre>
<pre
id=
"editor"
></pre>
...
...
slapos/runner/templates/updateSoftwareProfile.html
View file @
c5eaa61f
...
@@ -10,7 +10,8 @@
...
@@ -10,7 +10,8 @@
{% block body %}
{% block body %}
<form
method=
post
class=
add-entry
>
<form
method=
post
class=
add-entry
>
<dl>
<dl>
<dd><h2>
Software Profile:
</h2></dd>
<dd><h2>
Edit software profile
<a
href=
'#'
id=
'getmd5'
title=
'Show or Update md5sum value'
>
[md5]
</a>
:
</h2></dd>
<dd><div
class=
'md5sum'
id=
'md5sum'
></div></dd>
<dd>
<dd>
<div
class=
"main_content"
>
<div
class=
"main_content"
>
<pre
id=
"editor"
></pre>
<pre
id=
"editor"
></pre>
...
...
slapos/runner/utils.py
View file @
c5eaa61f
...
@@ -8,6 +8,7 @@ import urllib
...
@@ -8,6 +8,7 @@ import urllib
from
flask
import
jsonify
from
flask
import
jsonify
import
shutil
import
shutil
import
string
import
string
import
hashlib
class
Popen
(
subprocess
.
Popen
):
class
Popen
(
subprocess
.
Popen
):
...
@@ -142,11 +143,15 @@ def runSoftwareWithLock(config):
...
@@ -142,11 +143,15 @@ def runSoftwareWithLock(config):
for
val
in
data
:
for
val
in
data
:
if
val
[
'md5'
]
==
path
:
if
val
[
'md5'
]
==
path
:
exist
=
True
exist
=
True
conf
=
os
.
path
.
join
(
config
[
'runner_workdir'
],
".project"
)
if
not
exist
:
#save this compile software folder
if
not
exist
:
#save this compile software folder
if
os
.
path
.
exists
(
conf
):
data
.
append
({
"title"
:
getProjectTitle
(
config
),
"md5"
:
path
,
data
.
append
({
"title"
:
getProjectTitle
(
config
),
"md5"
:
path
,
"path"
:
open
(
os
.
path
.
join
(
config
[
'runner_workdir'
],
"path"
:
open
(
os
.
path
.
join
(
config
[
'runner_workdir'
],
".project"
),
'r'
).
read
()})
".project"
),
'r'
).
read
()})
writeSoftwareData
(
config
[
'runner_workdir'
],
data
)
writeSoftwareData
(
config
[
'runner_workdir'
],
data
)
else
:
shutil
.
rmtree
(
os
.
path
.
join
(
config
[
'software_root'
],
path
))
break
break
return
True
return
True
return
False
return
False
...
@@ -435,3 +440,19 @@ def isText(file):
...
@@ -435,3 +440,19 @@ def isText(file):
return not is_binary_string(open(file).read(1024))
return not is_binary_string(open(file).read(1024))
except:
except:
return False
return False
def md5sum(file):
"""Compute md5sum of `file` and return hexdigest value"""
if os.path.isdir(file):
return False
try:
fh = open(file, 'rb')
m = hashlib.md5()
while True:
data = fh.read(8192)
if not data:
break
m.update(data)
return m.hexdigest()
except:
return False
\ No newline at end of file
slapos/runner/views.py
View file @
c5eaa61f
...
@@ -3,6 +3,7 @@ from flask import Flask, request, redirect, url_for, \
...
@@ -3,6 +3,7 @@ from flask import Flask, request, redirect, url_for, \
from
utils
import
*
from
utils
import
*
import
os
import
os
import
shutil
import
shutil
import
md5
from
gittools
import
cloneRepo
,
gitStatus
,
switchBranch
,
createBranch
,
getDiff
,
\
from
gittools
import
cloneRepo
,
gitStatus
,
switchBranch
,
createBranch
,
getDiff
,
\
gitPush
,
gitPull
gitPush
,
gitPull
...
@@ -48,12 +49,13 @@ def inspectSoftware():
...
@@ -48,12 +49,13 @@ def inspectSoftware():
@
app
.
route
(
'/removeSoftware'
)
@
app
.
route
(
'/removeSoftware'
)
def
removeSoftware
():
def
removeSoftware
():
file_config
=
os
.
path
.
join
(
app
.
config
[
'runner_workdir'
],
".softdata"
)
if
isSoftwareRunning
(
app
.
config
)
or
isInstanceRunning
(
app
.
config
):
if
isSoftwareRunning
(
app
.
config
)
or
isInstanceRunning
(
app
.
config
):
flash
(
'Software installation or instantiation in progress, cannot remove'
)
flash
(
'Software installation or instantiation in progress, cannot remove'
)
elif
os
.
path
.
exists
(
app
.
config
[
'software_root'
]
):
elif
os
.
path
.
exists
(
file_config
):
svcStopAll
(
app
.
config
)
svcStopAll
(
app
.
config
)
shutil
.
rmtree
(
app
.
config
[
'software_root'
])
shutil
.
rmtree
(
app
.
config
[
'software_root'
])
os
.
remove
(
os
.
path
.
join
(
config
[
'runner_workdir'
],
".softdata"
))
os
.
remove
(
os
.
path
.
join
(
app
.
config
[
'runner_workdir'
],
".softdata"
))
flash
(
'Software removed'
)
flash
(
'Software removed'
)
return
redirect
(
url_for
(
'inspectSoftware'
))
return
redirect
(
url_for
(
'inspectSoftware'
))
...
@@ -289,3 +291,11 @@ def checkFileType():
...
@@ -289,3 +291,11 @@ def checkFileType():
return
jsonify
(
code
=
1
,
result
=
"text"
)
return
jsonify
(
code
=
1
,
result
=
"text"
)
else
:
else
:
return
jsonify
(
code
=
0
,
result
=
"You can only open text files!"
)
return
jsonify
(
code
=
0
,
result
=
"You can only open text files!"
)
@
app
.
route
(
"/getmd5sum"
,
methods
=
[
'POST'
])
def
getmd5sum
():
md5
=
md5sum
(
request
.
form
[
'file'
])
if
md5
:
return
jsonify
(
code
=
1
,
result
=
md5
)
else
:
return
jsonify
(
code
=
0
,
result
=
"Can not get md5sum for this file!"
)
\ No newline at end of file
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