Toggle navigation
Toggle navigation
This project
Loading...
Sign in
ops
/
monitor-ui
·
Commits
Go to a project
GitLab
Go to group
Project
Activity
Files
Commits
Pipelines
0
Builds
0
Graphs
Milestones
Issues
0
Merge Requests
0
Members
Labels
Wiki
Forks
Network
Create a new issue
Download as
Email Patches
Plain Diff
Browse Files
Authored by
qinchao
7 years ago
Commit
38047db81f47e3b70a50ebadb9c81370300af6ef
1 parent
b75593ac
docker回滚
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
263 additions
and
115 deletions
monitor-ui-ctrl/src/main/java/com/ui/ctrl/DockerBuildCtrl.java
monitor-ui-web/src/main/webapp/jsp/project/docker_build.jsp
monitor-ui-web/src/main/webapp/jsp/project/docker_project.jsp
monitor-ui-ctrl/src/main/java/com/ui/ctrl/DockerBuildCtrl.java
View file @
38047db
...
...
@@ -45,28 +45,40 @@ public class DockerBuildCtrl {
}
@RequestMapping
(
"/toBuildView"
)
public
ModelAndView
toBuildView
(
Model
model
,
String
project_id
,
String
project_name
,
String
environment_name
,
String
operate_name
,
String
branch_name
,
String
rollbackfile_name
,
String
workid_name
,
String
buildIds
,
String
failMsg
,
HttpSession
session
)
{
public
ModelAndView
toBuildView
(
Model
model
,
String
project_id
,
String
project_name
,
String
environment_name
,
String
operate_name
,
String
branch_name
,
String
rollbackImageStore
,
String
workid_name
,
String
buildIds
,
String
failMsg
,
HttpSession
session
)
{
List
<
Map
<
String
,
String
>>
buildInfoList
=
new
ArrayList
<>();
Map
mapIds
=
new
HashMap
<>();
mapIds
.
put
(
"ids"
,
project_id
);
BaseResponse
response
=
httpRestClient
.
defaultGet
(
"/dockerProject/getDockerProjectMapByIds"
,
BaseResponse
.
class
,
mapIds
);
Map
projectMap
=
JSON
.
parseObject
((
String
)
response
.
getData
(),
Map
.
class
);
List
<
Map
<
String
,
String
>>
buildInfoList
=
new
ArrayList
<>();
for
(
String
buildIdWithProjectId:
buildIds
.
split
(
","
)){
int
index
=
buildIdWithProjectId
.
indexOf
(
"-"
);
String
projectId
=
buildIdWithProjectId
.
substring
(
0
,
index
);
String
buildId
=
buildIdWithProjectId
.
substring
(
index
+
1
);
if
(
"deploy"
.
equalsIgnoreCase
(
operate_name
)){
for
(
String
buildIdWithProjectId:
buildIds
.
split
(
","
)){
int
index
=
buildIdWithProjectId
.
indexOf
(
"-"
);
String
projectId
=
buildIdWithProjectId
.
substring
(
0
,
index
);
String
buildId
=
buildIdWithProjectId
.
substring
(
index
+
1
);
Map
map
=
new
HashMap
();
map
.
put
(
"projectId"
,
projectId
);
map
.
put
(
"buildId"
,
buildId
);
map
.
put
(
"projectName"
,
buildId
.
substring
(
0
,
buildId
.
lastIndexOf
(
"-"
)));
map
.
put
(
"projectInfo"
,
projectMap
.
get
(
Integer
.
parseInt
(
projectId
)));
buildInfoList
.
add
(
map
);
}
}
else
{
String
projectId
=
project_id
;
Map
map
=
new
HashMap
();
map
.
put
(
"projectId"
,
projectId
);
map
.
put
(
"buildId"
,
buildId
);
map
.
put
(
"projectName"
,
buildId
.
substring
(
0
,
buildId
.
lastIndexOf
(
"-"
)));
map
.
put
(
"projectName"
,
project_name
);
map
.
put
(
"projectInfo"
,
projectMap
.
get
(
Integer
.
parseInt
(
projectId
)));
buildInfoList
.
add
(
map
);
}
model
.
addAttribute
(
"environment_name"
,
environment_name
);
model
.
addAttribute
(
"operate_name"
,
operate_name
);
model
.
addAttribute
(
"branch_name"
,
branch_name
);
model
.
addAttribute
(
"workid_name"
,
workid_name
);
model
.
addAttribute
(
"rollbackfile_name"
,
rollbackImageStore
);
model
.
addAttribute
(
"buildIds"
,
buildIds
);
model
.
addAttribute
(
"failMsg"
,
failMsg
);
model
.
addAttribute
(
"buildInfoList"
,
JSONArray
.
toJSON
(
buildInfoList
));
...
...
@@ -92,6 +104,31 @@ public class DockerBuildCtrl {
}
}
@RequestMapping
(
value
=
"/getRollbackList"
)
@ResponseBody
public
String
getRollbackList
(
String
projectId
,
String
environment
)
{
try
{
Map
<
String
,
String
>
map
=
new
HashMap
<>();
map
.
put
(
"projectId"
,
projectId
);
map
.
put
(
"environment"
,
environment
);
return
httpRestClient
.
defaultGet
(
"/dockerProject/getRollbackList"
,
String
.
class
,
map
);
}
catch
(
Exception
ex
)
{
return
"failed"
;
}
}
/**
* rollback项目
**/
@RequestMapping
(
value
=
"/rollback"
)
@ResponseBody
public
BaseResponse
rollback
(
String
project_id
,
String
environment_name
,
String
rollbackImageStore
){
Map
<
String
,
String
>
map
=
new
HashMap
<>();
map
.
put
(
"projectId"
,
project_id
);
map
.
put
(
"environment"
,
environment_name
);
map
.
put
(
"rollbackImageStore"
,
rollbackImageStore
);
return
httpRestClient
.
defaultGet
(
"/dockerProject/rollback"
,
BaseResponse
.
class
,
map
);
}
/**
* deploy项目
*
...
...
monitor-ui-web/src/main/webapp/jsp/project/docker_build.jsp
View file @
38047db
...
...
@@ -99,6 +99,12 @@
readonly=
"readonly"
value=
"${branch_name}"
/>
</div>
<div
class=
"input-group"
style=
"float: left;"
>
<span
class=
"input-group-addon"
>
回滚:
</span>
<input
type=
"text"
id=
"rollbackfile_name"
name=
"rollbackfile_name"
class=
"form-control"
readonly=
"readonly"
value=
"${rollbackfile_name}"
/>
</div>
<div
class=
"input-group"
style=
"float: left;"
>
<span
class=
"input-group-addon"
>
工单号:
</span>
<input
type=
"text"
id=
"workid_name"
name=
"workid_name"
class=
"form-control"
readonly=
"readonly"
value=
"${workid_name}"
/>
...
...
@@ -132,11 +138,11 @@
<c:forEach
items=
"${buildInfoList }"
var=
"buildInfo"
>
<div
style=
"float: left;height: 600px;"
>
<hr>
<div>
当前项目:
<input
type=
"text"
id=
"currentProject_${buildInfo.
build
Id}"
value=
"${buildInfo.projectName}"
readonly=
"readonly"
>
<div>
当前项目:
<input
type=
"text"
id=
"currentProject_${buildInfo.
project
Id}"
value=
"${buildInfo.projectName}"
readonly=
"readonly"
>
</div>
<
%--
<div
style=
"height: 50px"
class=
"alert alert-warning"
id=
"task-info-div_${dockerProject.id}"
>
</div>
--%>
<textarea
id=
"resultArea_${buildInfo.
build
Id}"
rows=
"25"
cols=
"100"
<textarea
id=
"resultArea_${buildInfo.
project
Id}"
rows=
"25"
cols=
"100"
style=
"background-color: black; color: white"
readonly=
"readonly"
></textarea>
</div>
</c:forEach>
...
...
@@ -158,20 +164,40 @@
var
buildInfoList
=
'${buildInfoList}'
;
$
(
function
()
{
if
(
buildInfoList
!=
null
&&
buildInfoList
.
length
>
0
){
var
buildInfoListJson
=
JSON
.
parse
(
buildInfoList
);
//docek发布
for
(
var
i
=
0
;
i
<
buildInfoListJson
.
length
;
i
++
){
buildDocker
(
buildInfoListJson
[
i
]);
if
(
"${operate_name}"
.
toUpperCase
()
==
"DEPLOY"
){
if
(
buildInfoList
!=
null
&&
buildInfoList
.
length
>
0
){
var
buildInfoListJson
=
JSON
.
parse
(
buildInfoList
);
//docek发布
for
(
var
i
=
0
;
i
<
buildInfoListJson
.
length
;
i
++
){
buildDocker
(
buildInfoListJson
[
i
]);
}
}
}
else
{
if
(
buildInfoList
!=
null
&&
buildInfoList
.
length
>
0
){
var
buildInfoListJson
=
JSON
.
parse
(
buildInfoList
);
//docek回滚
for
(
var
i
=
0
;
i
<
buildInfoListJson
.
length
;
i
++
){
rollbackDocker
(
buildInfoListJson
[
i
]);
}
}
}
});
function
rollbackDocker
(
buildInfo
){
var
textareaId
=
buildInfo
.
projectId
;
writeToText
(
textareaId
,
"开始查询腾讯云发布结果,请等待.........."
);
//开始查询腾讯云deploy状态,延迟60秒再开始查询
setTimeout
(
function
(){
builderDocker_queryServiceStatus
(
buildInfo
,
textareaId
);
},
6000
);
}
function
buildDocker
(
buildInfo
){
//镜像的制作状态
console
.
log
(
buildInfo
);
builderDocker_queryJenkinsStatus
(
buildInfo
,
buildInfo
.
buildId
);
var
textareaId
=
buildInfo
.
projectId
;
builderDocker_queryJenkinsStatus
(
buildInfo
,
textareaId
);
}
...
...
@@ -299,75 +325,6 @@
return
frontParam
;
}
/* function builderDocker_deploy(dockerProject){
writeToText(dockerProject.id,"开始docker发布....");
var url= contextPath + 'dockerBuild/deployMirror';
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data:getDockerRequestParam(dockerProject),
async: false,
success: function (data) {
if (!data || data.code != 200) {
writeToText(dockerProject.id,"....docker发布请求失败!");
responseError(dockerProject.id,data);
}else{
writeToText(dockerProject.id,"....docker发布请求成功");
builderDocker_queryServiceStatus(dockerProject);
}
},
error: function (e) {
writeToText(dockerProject.id,"....docker发布请求失败,发生异常!");
requestError(dockerProject.id,url);
}
});
}*/
/*function builderDocker_createMirror(dockerProject){
writeToText(dockerProject.id,"开始jenkins制作镜像....");
var url= contextPath + 'dockerBuild/createMirror';
var mirrorParam= {
projectName:dockerProject.projectName,
projectGitGroup:dockerProject.projectGitGroup,
deployName:dockerProject.dockerProjectName,
projectJobType:dockerProject.projectJobType,
projectPort:dockerProject.port,
nodeEnv:$("#environment_name").val(),
branch:$("#branch_name").val(),
releaseWorkId:$("#workid_name").val()
};
if($("#environment_name").val().toUpperCase().indexOf("GRAY")>=0){
mirrorParam.imageSource=dockerProject.dockerGrayImageStore;
}else{
mirrorParam.imageSource=dockerProject.dockerOnlineImageStore;
}
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data:mirrorParam,
async: false,
success: function (data) {
if (!data || data.code != 200) {
writeToText(dockerProject.id,"....jenkins制作镜像请求失败!");
responseError(dockerProject.id,data);
}else{
writeToText(dockerProject.id,"....jenkins制作镜像请求成功,buildId is "+data.data);
builderDocker_queryJenkinsStatus(data.data,dockerProject);
}
},
error: function (e) {
writeToText(dockerProject.id,"....jenkins制作镜像请求失败,发生异常!");
requestError(dockerProject.id,url);
}
});
}*/
//写入消息
function
writeToText
(
id
,
msg
){
var
d
=
$
(
"#resultArea_"
+
id
).
val
();
...
...
monitor-ui-web/src/main/webapp/jsp/project/docker_project.jsp
View file @
38047db
...
...
@@ -43,14 +43,14 @@
<div
class=
"col-sm-8"
style=
"display: inline"
>
<div
class=
"rdio rdio-default"
>
<input
type=
"radio"
name=
"operate"
id=
"operatedeploy"
value=
"Deploy"
checked=
"checked"
/>
<input
type=
"radio"
name=
"operate"
id=
"operatedeploy"
value=
"Deploy"
checked=
"checked"
onclick=
"operateChange()"
/>
<label
for=
"operatedeploy"
>
发布
</label>
</div>
<
%--
<
div
class=
"rdio rdio-default"
>
<div
class=
"rdio rdio-default"
>
<input
type=
"radio"
name=
"operate"
value=
"Rollback"
id=
"operateback"
onclick=
"operateChange()"
/>
<label
for=
"operateback"
>
回滚
</label>
</div>
--%>
</div>
</div>
</div>
...
...
@@ -109,6 +109,14 @@
</div>
</div>
<div
class=
"form-group"
id=
"rollback-div"
style=
"display: none"
>
<label
class=
"col-sm-1 control-label"
>
回滚版本选择
</label>
<div
class=
"col-sm-8"
id=
"select-rollbackList-div"
>
</div>
</div>
<div
class=
"form-group"
id=
"workid-div"
>
<label
class=
"col-sm-1 control-label"
>
关联工单
</label>
<div
class=
"col-sm-8"
>
...
...
@@ -118,13 +126,7 @@
</div>
</div>
<div
class=
"form-group"
id=
"rollback-div"
style=
"display: none"
>
<label
class=
"col-sm-1 control-label"
>
回滚版本选择
</label>
<div
class=
"col-sm-8"
id=
"select-rollbackList-div"
>
</div>
</div>
<div
class=
"form-group"
>
<label
class=
"col-sm-1 control-label"
>
确认操作
</label>
...
...
@@ -181,10 +183,13 @@
<input
type=
"text"
name=
"workid_name"
class=
"form-control"
readonly=
"readonly"
>
</div>
<br>
<div
class=
"input-group"
>
<span
class=
"input-group-addon"
>
回滚文件
</span>
<input
type=
"text"
name=
"rollbackfile_name"
class=
"form-control"
readonly=
"readonly"
>
</div>
<div
class=
"modal-footer"
>
<button
id=
"confirmBtn"
type=
"button"
class=
"btn btn-primary"
onclick=
"
mirrorAndDeploy
()"
>
确认
</button>
<button
id=
"confirmBtn"
type=
"button"
class=
"btn btn-primary"
onclick=
"
deployOrRollback
()"
>
确认
</button>
<button
id=
"cancelBtn"
type=
"button"
class=
"btn btn-default"
data-dismiss=
"modal"
>
取消
</button>
</div>
</form>
...
...
@@ -220,9 +225,31 @@
});
/**
* 操作变更
*/
function
operateChange
()
{
getProjects
();
var
operate
=
$
(
"input[name='operate']:checked"
).
val
();
if
(
"Rollback"
==
operate
)
{
//隐藏Branch输入框
document
.
getElementById
(
"branch-div"
).
style
.
display
=
"none"
;
//展示 回滚选择框
document
.
getElementById
(
"rollback-div"
).
style
.
display
=
""
;
}
else
{
//展示Branch输入框
document
.
getElementById
(
"branch-div"
).
style
.
display
=
""
;
//隐藏 回滚选择框
document
.
getElementById
(
"rollback-div"
).
style
.
display
=
"none"
;
}
}
/**
* 根据操作获取当前的项目列表
*/
function
getProjects
()
{
var
operate
=
$
(
"input[name='operate']:checked"
).
val
();
$
.
ajax
({
type
:
'post'
,
url
:
contextPath
+
"dockerBuild/getProjects"
,
...
...
@@ -232,27 +259,51 @@
dataType
:
'json'
,
success
:
function
(
data
)
{
var
obj
=
eval
(
"("
+
data
+
")"
);
var
order1HTML
=
""
;
var
order1num
=
1
;
for
(
var
i
=
0
;
i
<
obj
.
length
;
i
++
)
{
var
projectId
=
obj
[
i
].
id
;
var
name
=
obj
[
i
].
projectName
;
var
checkstr
=
""
;
if
(
default_releaseWorkJob_project
!=
null
&&
default_releaseWorkJob_project
.
indexOf
(
name
+
","
)
>=
0
){
checkstr
=
" checked "
;
if
(
"Deploy"
==
operate
)
{
//发布多选框
var
order1HTML
=
""
;
var
order1num
=
1
;
for
(
var
i
=
0
;
i
<
obj
.
length
;
i
++
)
{
var
projectId
=
obj
[
i
].
id
;
var
name
=
obj
[
i
].
projectName
;
var
checkstr
=
""
;
if
(
default_releaseWorkJob_project
!=
null
&&
default_releaseWorkJob_project
.
indexOf
(
name
+
","
)
>=
0
){
checkstr
=
" checked "
;
}
order1HTML
+=
"<div class='ckbox ckbox-primary' style='display: inline'>"
;
order1HTML
+=
" <input name='project1' type='checkbox' "
+
checkstr
;
order1HTML
+=
" id='"
+
name
+
"' value='"
+
projectId
+
"'/>"
;
order1HTML
+=
"<label for='"
+
name
+
"' style='width: 200px'>"
+
name
+
"</label></div>"
;
if
(
order1num
%
4
==
0
)
{
order1HTML
+=
"</br>"
;
}
order1num
+=
1
;
}
order1HTML
+=
"<div class='ckbox ckbox-primary' style='display: inline'>"
;
order1HTML
+=
" <input name='project1' type='checkbox' "
+
checkstr
;
order1HTML
+=
" id='"
+
name
+
"' value='"
+
projectId
+
"'/>"
;
order1HTML
+=
"<label for='"
+
name
+
"' style='width: 200px'>"
+
name
+
"</label></div>"
;
if
(
order1num
%
4
==
0
)
{
order1HTML
+=
"</br>"
;
document
.
getElementById
(
"project-div"
).
innerHTML
=
order1HTML
;
branchdefault
();
workiddefault
();
}
else
{
var
order1HTML
=
""
;
var
order1num
=
1
;
for
(
var
i
=
0
;
i
<
obj
.
length
;
i
++
)
{
var
projectId
=
obj
[
i
].
id
;
var
name
=
obj
[
i
].
projectName
;
var
checkstr
=
""
;
if
(
i
==
0
){
checkstr
=
" checked "
;
}
order1HTML
+=
"<div class='rdio rdio-primary' style='display: inline'>"
;
order1HTML
+=
" <input name='project' type='radio' "
+
checkstr
;
order1HTML
+=
" id='"
+
name
+
"' value='"
+
projectId
+
"'/>"
;
order1HTML
+=
"<label for='"
+
name
+
"' style='width: 200px'>"
+
name
+
"</label></div>"
;
if
(
order1num
%
4
==
0
)
{
order1HTML
+=
"</br>"
;
}
order1num
+=
1
;
}
order1num
+=
1
;
document
.
getElementById
(
"project-div"
).
innerHTML
=
order1HTML
;
getRollbackList
();
}
document
.
getElementById
(
"project-div"
).
innerHTML
=
order1HTML
;
branchdefault
();
workiddefault
();
if
(
default_releaseWorkJob_enviCount
!=
null
&&
default_releaseWorkJob_enviCount
>=
2
){
alert
(
"提醒:工单的发布环境为"
+
default_releaseWorkJob_environment
+
"当前只选中一个!"
);
...
...
@@ -264,6 +315,38 @@
});
}
function
getRollbackList
(){
var
projectId
=
$
(
"input[name='project']:checked"
).
val
();
var
environment
=
$
(
"input[name='environments']:checked"
).
val
();
$
.
ajax
({
url
:
contextPath
+
'dockerBuild/getRollbackList?projectId='
+
projectId
+
"&environment="
+
environment
,
type
:
'POST'
,
dataType
:
'json'
,
success
:
function
(
data
)
{
document
.
getElementById
(
"select-rollbackList-div"
).
innerHTML
=
""
;
if
(
"failed"
==
data
)
{
return
;
}
var
list
=
eval
(
"("
+
data
+
")"
);
//动态生成rollbackList的select组件
var
innerHTMLContext
=
"<select id='rollbackList' data-placeholder='Choose One' style='width:350px;'>"
;
for
(
var
i
=
0
;
i
<
list
.
length
;
i
++
)
{
innerHTMLContext
+=
"<option value='"
+
list
[
i
]
+
"'>"
+
list
[
i
]
+
"</option>"
;
}
innerHTMLContext
+=
"</select>"
;
document
.
getElementById
(
"select-rollbackList-div"
).
innerHTML
=
innerHTMLContext
;
jQuery
(
'#rollbackList'
).
select2
({
minimumResultsForSearch
:
-
1
});
},
error
:
function
(
e
)
{
alert
(
"该项目暂无备份,无法回滚"
);
}
});
}
function
branchdefault
()
{
var
operate
=
$
(
"input[name='operate']:checked"
).
val
();
if
(
operate
==
"Deploy"
)
{
...
...
@@ -320,9 +403,79 @@
$
(
"input[name='branch_name']"
).
val
(
branch
);
$
(
"input[name='workid_name']"
).
val
(
workid
);
$
(
'#confirmSubmitDivId'
).
modal
(
'show'
);
}
else
{
var
project
=
$
(
"input[name='project']:checked"
).
val
();
var
projectName
=
$
(
"input[name='project']:checked"
).
next
().
text
();
var
rollbackfile
=
$
(
"#rollbackList option:selected"
).
val
();
if
(
project
==
""
)
{
alert
(
"请选择回滚项目"
);
return
;
}
var
workid
=
$
(
"input[name='workid']"
).
val
();
if
(
workid
==
""
)
{
alert
(
"请填写关联工单号"
);
return
;
}
if
(
typeof
(
rollbackfile
)
==
"undefined"
||
rollbackfile
==
""
)
{
alert
(
"请选择回滚版本"
);
return
;
}
$
(
"input[name='operate_name']"
).
val
(
operate
);
$
(
"input[name='project_id']"
).
val
(
project
);
$
(
"input[name='project_name']"
).
val
(
projectName
);
$
(
"input[name='environment_name']"
).
val
(
environment
);
$
(
"input[name='rollbackfile_name']"
).
val
(
rollbackfile
);
$
(
"input[name='branch_name']"
).
val
(
""
);
$
(
"input[name='workid_name']"
).
val
(
workid
);
$
(
'#confirmSubmitDivId'
).
modal
(
'show'
);
}
}
function
deployOrRollback
()
{
var
operate
=
$
(
"input[name='operate']:checked"
).
val
();
if
(
"Deploy"
==
operate
)
{
//发布
mirrorAndDeploy
();
}
else
{
rollback
();
}
}
function
rollback
(){
$
(
"#confirmBtn"
).
attr
(
"disabled"
,
"disabled"
);
$
(
"#cancelBtn"
).
attr
(
"disabled"
,
"disabled"
);
var
param
=
{};
param
.
operate_name
=
$
(
"input[name='operate_name']"
).
val
();
param
.
project_id
=
$
(
"input[name='project_id']"
).
val
();
param
.
project_name
=
$
(
"input[name='project_name']"
).
val
();
param
.
environment_name
=
$
(
"input[name='environment_name']"
).
val
();
param
.
rollbackImageStore
=
$
(
"input[name='rollbackfile_name']"
).
val
();
param
.
workid_name
=
$
(
"input[name='workid_name']"
).
val
();
$
.
ajax
({
url
:
contextPath
+
"dockerBuild/rollback"
,
type
:
"post"
,
dataType
:
"json"
,
data
:
param
,
success
:
function
(
response
)
{
if
(
response
!=
null
&&
(
response
.
code
==
200
)){
GobalStandardPost
(
contextPath
+
"dockerBuild/toBuildView"
,
param
);
}
else
{
//失败
if
(
response
==
null
){
mirrorFail
(
"回滚失败!"
);
}
else
{
mirrorFail
(
response
.
message
);
}
}
},
error
:
function
(
e
)
{
mirrorFail
(
e
);
}
});
}
function
mirrorAndDeploy
()
{
$
(
"#confirmBtn"
).
attr
(
"disabled"
,
"disabled"
);
$
(
"#cancelBtn"
).
attr
(
"disabled"
,
"disabled"
);
...
...
@@ -362,9 +515,10 @@
}
function
mirrorFail
(
msg
)
{
console
.
log
(
msg
);
$
(
"#confirmBtn"
).
removeAttr
(
"disabled"
);
$
(
"#cancelBtn"
).
removeAttr
(
"disabled"
);
localAlert
(
"msg"
);
alert
(
JSON
.
stringify
(
msg
)
);
}
</script>
\ No newline at end of file
...
...
Please
register
or
login
to post a comment