[Notice] Announcing the End of Demo Server [Read me]

Added UI for webhook form.
@ab24aeb723050d454125c8678d485f8753a7ed42
--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -604,7 +604,7 @@ |
604 | 604 |
color: #2cb400; |
605 | 605 |
} |
606 | 606 |
} |
607 |
- |
|
607 |
+ |
|
608 | 608 |
.d2-program:hover { |
609 | 609 |
background-color:#5f5f5f; |
610 | 610 |
text-decoration:none; |
... | ... | @@ -2140,7 +2140,7 @@ |
2140 | 2140 |
&.over { color:#f36c22; } |
2141 | 2141 |
} |
2142 | 2142 |
} |
2143 |
- |
|
2143 |
+ |
|
2144 | 2144 |
.progress-wrap { |
2145 | 2145 |
overflow: hidden; |
2146 | 2146 |
color: #999; |
... | ... | @@ -3069,7 +3069,7 @@ |
3069 | 3069 |
min-height: 140px; |
3070 | 3070 |
padding: 15px 20px; |
3071 | 3071 |
text-align: left; |
3072 |
- clear: both; |
|
3072 |
+ clear: both; |
|
3073 | 3073 |
} |
3074 | 3074 |
|
3075 | 3075 |
div.markdown-preview { |
... | ... | @@ -3482,9 +3482,9 @@ |
3482 | 3482 |
text-decoration: none; |
3483 | 3483 |
} |
3484 | 3484 |
|
3485 |
- &.name { |
|
3485 |
+ &.name { |
|
3486 | 3486 |
color : #333; |
3487 |
- |
|
3487 |
+ |
|
3488 | 3488 |
&:hover { |
3489 | 3489 |
color:#333; |
3490 | 3490 |
} |
... | ... | @@ -3534,7 +3534,7 @@ |
3534 | 3534 |
&.item-icon { |
3535 | 3535 |
background-color:#F7F7F7; |
3536 | 3536 |
color:#3592b5; |
3537 |
- font-size: 9px; |
|
3537 |
+ font-size: 9px; |
|
3538 | 3538 |
padding-top:2px; |
3539 | 3539 |
line-height: 12px; |
3540 | 3540 |
border-left:1px solid #EEE; |
... | ... | @@ -3542,7 +3542,7 @@ |
3542 | 3542 |
&:first-child { |
3543 | 3543 |
border-left:none; |
3544 | 3544 |
} |
3545 |
- |
|
3545 |
+ |
|
3546 | 3546 |
&.strong { |
3547 | 3547 |
color:@primary; |
3548 | 3548 |
} |
... | ... | @@ -3686,7 +3686,7 @@ |
3686 | 3686 |
min-height:30px; |
3687 | 3687 |
} |
3688 | 3688 |
|
3689 |
- |
|
3689 |
+ |
|
3690 | 3690 |
.write-comment-box { padding:0; margin-top:20px; } |
3691 | 3691 |
} |
3692 | 3692 |
|
... | ... | @@ -5665,7 +5665,7 @@ |
5665 | 5665 |
text-decoration:none; |
5666 | 5666 |
background-color:#fafafa; |
5667 | 5667 |
} |
5668 |
- |
|
5668 |
+ |
|
5669 | 5669 |
&.open { |
5670 | 5670 |
.box-shadow(inset 5px 0px 0px @state-open); |
5671 | 5671 |
} |
... | ... | @@ -5849,7 +5849,7 @@ |
5849 | 5849 |
margin-top: 15px; |
5850 | 5850 |
font-weight: normal; |
5851 | 5851 |
padding:0 15px; |
5852 |
- |
|
5852 |
+ |
|
5853 | 5853 |
strong { |
5854 | 5854 |
color:#f36c22; |
5855 | 5855 |
} |
... | ... | @@ -5956,7 +5956,7 @@ |
5956 | 5956 |
margin-top:10px; |
5957 | 5957 |
font-size: 13px; |
5958 | 5958 |
color:#999; |
5959 |
- padding-left:20px; |
|
5959 |
+ padding-left:20px; |
|
5960 | 5960 |
|
5961 | 5961 |
&.np { |
5962 | 5962 |
padding-left:0 !important; |
... | ... | @@ -5974,7 +5974,7 @@ |
5974 | 5974 |
} |
5975 | 5975 |
} |
5976 | 5976 |
|
5977 |
- .user-link { |
|
5977 |
+ .user-link { |
|
5978 | 5978 |
color: #3592b5 !important; |
5979 | 5979 |
font-weight: bold; |
5980 | 5980 |
} |
... | ... | @@ -6130,6 +6130,81 @@ |
6130 | 6130 |
} |
6131 | 6131 |
} |
6132 | 6132 |
|
6133 |
+.webhook-editor-wrap { |
|
6134 |
+ .new-webhook-wrap { |
|
6135 |
+ margin:30px auto; |
|
6136 |
+ |
|
6137 |
+ .form-legend { |
|
6138 |
+ display:block; |
|
6139 |
+ margin-bottom:10px; |
|
6140 |
+ } |
|
6141 |
+ .form-wrap { |
|
6142 |
+ position:relative; |
|
6143 |
+ display:inline-block; |
|
6144 |
+ vertical-align:top; |
|
6145 |
+ } |
|
6146 |
+ .input-webhook-payload { |
|
6147 |
+ width:355px; |
|
6148 |
+ } |
|
6149 |
+ .input-webhook-secret { |
|
6150 |
+ width:214px; |
|
6151 |
+ } |
|
6152 |
+ .btn-submit { |
|
6153 |
+ vertical-align: top; |
|
6154 |
+ min-width:100px; |
|
6155 |
+ } |
|
6156 |
+ } |
|
6157 |
+ |
|
6158 |
+ .webhook-list-wrap { |
|
6159 |
+ margin:0 auto; |
|
6160 |
+ |
|
6161 |
+ .list-head { |
|
6162 |
+ border:1px solid #ddd; |
|
6163 |
+ border-width:2px 0px 0px; |
|
6164 |
+ background: #fafafa; |
|
6165 |
+ |
|
6166 |
+ .payload-url { |
|
6167 |
+ padding-right:18px; |
|
6168 |
+ line-height:30px; |
|
6169 |
+ text-align:right; |
|
6170 |
+ } |
|
6171 |
+ .secret { |
|
6172 |
+ padding-left:8px; |
|
6173 |
+ line-height:30px; |
|
6174 |
+ } |
|
6175 |
+ } |
|
6176 |
+ .list-item { |
|
6177 |
+ border-top:1px solid #ddd; |
|
6178 |
+ |
|
6179 |
+ .table { |
|
6180 |
+ .webhook-secret { |
|
6181 |
+ display: block; |
|
6182 |
+ max-width: 50%; |
|
6183 |
+ font-size: 12px; |
|
6184 |
+ } |
|
6185 |
+ tr { |
|
6186 |
+ border-bottom: 1px solid #ddd; |
|
6187 |
+ &:last-of-type { |
|
6188 |
+ border-bottom: none; |
|
6189 |
+ } |
|
6190 |
+ } |
|
6191 |
+ td { |
|
6192 |
+ border-top: none; |
|
6193 |
+ &.info { |
|
6194 |
+ width: 150px; |
|
6195 |
+ text-align: right; |
|
6196 |
+ } |
|
6197 |
+ &.actions { |
|
6198 |
+ width: 150px; |
|
6199 |
+ text-align: right; |
|
6200 |
+ padding-right: 1px; |
|
6201 |
+ } |
|
6202 |
+ } |
|
6203 |
+ } |
|
6204 |
+ } |
|
6205 |
+ } |
|
6206 |
+} |
|
6207 |
+ |
|
6133 | 6208 |
.category-exclusive { |
6134 | 6209 |
margin-right:5px; |
6135 | 6210 |
vertical-align: middle; |
... | ... | @@ -6234,4 +6309,3 @@ |
6234 | 6309 |
.watch-btn > button { |
6235 | 6310 |
padding: 4px 10px; |
6236 | 6311 |
} |
6237 |
- |
--- app/controllers/ProjectApp.java
+++ app/controllers/ProjectApp.java
... | ... | @@ -1156,9 +1156,13 @@ |
1156 | 1156 |
@Transactional |
1157 | 1157 |
@IsAllowed(Operation.UPDATE) |
1158 | 1158 |
public static Result newWebhook(String ownerId, String projectName) { |
1159 |
- if(HttpUtil.isJSONPreferred(request())){ |
|
1160 |
- return ok("{}"); |
|
1161 |
- } |
|
1159 |
+ Form<Webhook> addWebhookForm = form(Webhook.class).bindFromRequest(); |
|
1160 |
+ Project project = Project.findByOwnerAndProjectName(ownerId, projectName); |
|
1161 |
+ |
|
1162 |
+ Webhook.create(project.id, |
|
1163 |
+ addWebhookForm.field("payload_url").value(), |
|
1164 |
+ addWebhookForm.field("secret").value()); |
|
1165 |
+ |
|
1162 | 1166 |
return redirect(routes.ProjectApp.webhooks(ownerId, projectName)); |
1163 | 1167 |
} |
1164 | 1168 |
|
--- app/models/Webhook.java
+++ app/models/Webhook.java
... | ... | @@ -107,9 +107,20 @@ |
107 | 107 |
return find.where().eq("project.id", projectId).findList(); |
108 | 108 |
} |
109 | 109 |
|
110 |
- public static List<Webhook> findWebhookListByProject(Long projectId) { |
|
111 |
- return find.fetch("webhook").fetch("payload_url", "secret").where() |
|
112 |
- .eq("project.id", projectId).findList(); |
|
110 |
+ public static void create(Long projectId, String payloadUrl, String secret) { |
|
111 |
+ Webhook webhook = new Webhook(projectId, payloadUrl, secret); |
|
112 |
+ webhook.save(); |
|
113 |
+ } |
|
114 |
+ |
|
115 |
+ public static void delete(Long webhookId, Long projectId) { |
|
116 |
+ Webhook.findByIds(webhookId, projectId).delete(); |
|
117 |
+ } |
|
118 |
+ |
|
119 |
+ public static Webhook findByIds(Long webhookId, Long projectId) { |
|
120 |
+ return find.where() |
|
121 |
+ .eq("webhook.id", webhookId) |
|
122 |
+ .eq("project.id", projectId) |
|
123 |
+ .findUnique(); |
|
113 | 124 |
} |
114 | 125 |
|
115 | 126 |
/** |
+++ app/views/project/partial_webhooks_list.scala.html
... | ... | @@ -0,0 +1,69 @@ |
1 | +@** | |
2 | +* Yobi, Project Hosting SW | |
3 | +* | |
4 | +* Copyright 2015 NAVER Corp. | |
5 | +* http://yobi.io | |
6 | +* | |
7 | +* @author Jihwan Chun | |
8 | +* | |
9 | +* Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | +* you may not use this file except in compliance with the License. | |
11 | +* You may obtain a copy of the License at | |
12 | +* | |
13 | +* http://www.apache.org/licenses/LICENSE-2.0 | |
14 | +* | |
15 | +* Unless required by applicable law or agreed to in writing, software | |
16 | +* distributed under the License is distributed on an "AS IS" BASIS, | |
17 | +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | +* See the License for the specific language governing permissions and | |
19 | +* limitations under the License. | |
20 | +**@ | |
21 | +@(project:Project, webhooks: List[models.Webhook]) | |
22 | + | |
23 | +@import utils.TemplateHelper._ | |
24 | +@import utils.AccessControl._ | |
25 | + | |
26 | +@if(webhooks.isEmpty){ | |
27 | + <div class="error-wrap"> | |
28 | + <i class="ico ico-err1"></i> | |
29 | + <p>@Messages("project.webhook.list.empty")</p> | |
30 | + </div> | |
31 | +} else { | |
32 | + <div class="row-fluid list-head"> | |
33 | + <div class="span8 payload-url"> | |
34 | + <strong>@Messages("project.webhook.payloadUrl")</strong> | |
35 | + </div> | |
36 | + <div class="span4 secret"> | |
37 | + <strong>@Messages("project.webhook.secret")</strong> | |
38 | + </div> | |
39 | + </div> | |
40 | + | |
41 | + @webhooks.map { webhook => | |
42 | + <div class="row-fluid list-item" data-webhook-id="@webhook.id"> | |
43 | + <div class="span8"> | |
44 | + <h6 class="right-txt mr20"> | |
45 | + @webhook.payloadUrl | |
46 | + </h6> | |
47 | + </div> | |
48 | + <div class="span4"> | |
49 | + <table class="table nm"> | |
50 | + <tr> | |
51 | + <td> | |
52 | + <span class="webhook-secret"> | |
53 | + @webhook.secret | |
54 | + </span> | |
55 | + </td> | |
56 | + <td class="actions"> | |
57 | + <button type="button" | |
58 | + class="ybtn ybtn-danger ybtn-small" | |
59 | + data-webhook-id="@webhook.id" | |
60 | + data-delete-uri="@routes.ProjectApp.deleteWebhook(project.owner, project.name, webhook.id)"> | |
61 | + @Messages("button.delete") | |
62 | + </button> | |
63 | + </td> | |
64 | + </tr> | |
65 | + </table> | |
66 | + </div> | |
67 | + </div> | |
68 | + } | |
69 | +} |
--- app/views/project/webhooks.scala.html
+++ app/views/project/webhooks.scala.html
... | ... | @@ -4,7 +4,7 @@ |
4 | 4 |
* Copyright 2015 NAVER Corp. |
5 | 5 |
* http://yobi.io |
6 | 6 |
* |
7 |
-* @author Jihwan Chun @ KAIST School of Computing |
|
7 |
+* @author Jihwan Chun |
|
8 | 8 |
* |
9 | 9 |
* Licensed under the Apache License, Version 2.0 (the "License"); |
10 | 10 |
* you may not use this file except in compliance with the License. |
... | ... | @@ -18,18 +18,42 @@ |
18 | 18 |
* See the License for the specific language governing permissions and |
19 | 19 |
* limitations under the License. |
20 | 20 |
**@ |
21 |
-@(message: String, webhooks: List[Webhook], project:Project) |
|
21 |
+@(message: String, webhooks: List[models.Webhook], project:Project) |
|
22 | 22 |
|
23 |
-@import helper._ |
|
23 |
+@import utils.TemplateHelper._ |
|
24 |
+@import utils.AccessControl._ |
|
24 | 25 |
|
25 | 26 |
@projectLayout(message, project, utils.MenuType.PROJECT_SETTING) { |
26 |
-@projectMenu(project, utils.MenuType.PROJECT_SETTING, "") |
|
27 |
-<div class="page-wrap-outer"> |
|
28 |
- <div class="project-page-wrap"> |
|
29 |
- @partial_settingmenu(project) |
|
27 |
+ @projectMenu(project, utils.MenuType.PROJECT_SETTING, "") |
|
28 |
+ <div class="page-wrap-outer"> |
|
29 |
+ <div class="project-page-wrap webhook-editor-wrap"> |
|
30 |
+ @partial_settingmenu(project) |
|
30 | 31 |
|
31 |
- Hello world! |
|
32 |
-</div> |
|
33 |
-<link rel="stylesheet" type="text/css" media="screen" href="/assets/javascripts/lib/mentionjs/mention.css"> |
|
32 |
+ @if(isProjectResourceCreatable(UserApp.currentUser, project, ResourceType.WEBHOOK)) { |
|
33 |
+ <form id="formNewWebhook" action="@routes.ProjectApp.newWebhook(project.owner, project.name)" method="post" class="new-webhook-wrap"> |
|
34 |
+ <strong class="form-legend">@Messages("project.webhook.new")</strong> |
|
35 |
+ <div class="form-wrap"> |
|
36 |
+ <div> |
|
37 |
+ <input type="text" name="payload_url" class="input-webhook-payload" maxlength="2048" autocomplete="off" placeholder="@Messages("project.webhook.payloadUrl")"> |
|
38 |
+ <input type="text" name="secret" class="input-webhook-secret" maxlength="250" autocomplete="off" placeholder="@Messages("project.webhook.secret")"> |
|
39 |
+ </div> |
|
40 |
+ </div> |
|
41 |
+ <button type="submit" class="ybtn ybtn-primary btn-submit">@Messages("project.webhook.add")</button> |
|
42 |
+ </form> |
|
43 |
+ } |
|
34 | 44 |
|
45 |
+ <div id="webhooksList" class="webhook-list-wrap"> |
|
46 |
+ @partial_webhooks_list(project, webhooks) |
|
47 |
+ </div> |
|
48 |
+ </div> |
|
49 |
+ </div> |
|
50 |
+ |
|
51 |
+ <script type="text/javascript"> |
|
52 |
+ $(function(){ |
|
53 |
+ $yobi.loadModule("project.Webhook", { |
|
54 |
+ "form": "#formNewWebhook", |
|
55 |
+ "list": "#webhooksList" |
|
56 |
+ }); |
|
57 |
+ }); |
|
58 |
+ </script> |
|
35 | 59 |
} |
--- conf/messages
+++ conf/messages
... | ... | @@ -617,6 +617,11 @@ |
617 | 617 |
project.watcher.number = number of watcher |
618 | 618 |
project.watching = Watching |
619 | 619 |
project.webhook = Webhooks |
620 |
+project.webhook.new = Create new webhook (Invoked by git push) |
|
621 |
+project.webhook.payloadUrl = Payload URL |
|
622 |
+project.webhook.secret = Secret token |
|
623 |
+project.webhook.add = Add webhook |
|
624 |
+project.webhook.list.empty = No webhook exists. |
|
620 | 625 |
project.you.are.not.watching = You are not watching {0} project. |
621 | 626 |
project.you.are.watching = You are watching {0} project. |
622 | 627 |
project.you.may.want.to.be.a.member = You can send a sign-up request for {0} project. |
--- conf/messages.ja-JP
+++ conf/messages.ja-JP
... | ... | @@ -404,6 +404,7 @@ |
404 | 404 |
project.watch = 見守る |
405 | 405 |
project.watching = 見守っている |
406 | 406 |
project.webhook = Webフック |
407 |
+project.webhook.add = Webフック追加 |
|
407 | 408 |
project.you.are.not.watching = {0} プロジェクトを見守っていません |
408 | 409 |
project.you.are.watching = {0} プロジェクトを見守っています |
409 | 410 |
project.you.may.want.to.be.a.member = {0} プロジェクトのメンバーに追加の申請が出来ません |
--- conf/messages.ko-KR
+++ conf/messages.ko-KR
... | ... | @@ -617,6 +617,11 @@ |
617 | 617 |
project.watcher.number = 지켜보는 사람 수 |
618 | 618 |
project.watching = 지켜보기 중 |
619 | 619 |
project.webhook = 웹후크 |
620 |
+project.webhook = 새 웹후크 생성 (Git push 시에 실행됩니다) |
|
621 |
+project.webhook.payloadUrl = 전송할 주소 |
|
622 |
+project.webhook.secret = 보안 토큰 |
|
623 |
+project.webhook.add = 웹후크 추가 |
|
624 |
+project.webhook.list.empty = 등록된 웹후크가 없습니다. |
|
620 | 625 |
project.you.are.not.watching = {0} 프로젝트를 지켜보고 있지 않습니다. |
621 | 626 |
project.you.are.watching = {0} 프로젝트를 지켜보는 중입니다. |
622 | 627 |
project.you.may.want.to.be.a.member = {0} 프로젝트 멤버로 등록 요청을 할 수 있습니다. |
+++ public/javascripts/service/yobi.project.Webhook.js
... | ... | @@ -0,0 +1,99 @@ |
1 | +/** | |
2 | + * Yobi, Project Hosting SW | |
3 | + * | |
4 | + * Copyright 2015 NAVER Corp. | |
5 | + * http://yobi.io | |
6 | + * | |
7 | + * @author Jihwan Chun | |
8 | + * | |
9 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
10 | + * you may not use this file except in compliance with the License. | |
11 | + * You may obtain a copy of the License at | |
12 | + * | |
13 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
14 | + * | |
15 | + * Unless required by applicable law or agreed to in writing, software | |
16 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
17 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | + * See the License for the specific language governing permissions and | |
19 | + * limitations under the License. | |
20 | + */ | |
21 | + | |
22 | +(function(ns){ | |
23 | + | |
24 | + var oNS = $yobi.createNamespace(ns); | |
25 | + oNS.container[oNS.name] = function(options){ | |
26 | + | |
27 | + var elements = {}; | |
28 | + | |
29 | + /** | |
30 | + * Initialize | |
31 | + * @param options | |
32 | + * @private | |
33 | + */ | |
34 | + function _init(options){ | |
35 | + _initElement(); | |
36 | + _attachEvent(); | |
37 | + } | |
38 | + | |
39 | + /** | |
40 | + * Initialize element variables | |
41 | + * @param options | |
42 | + * @private | |
43 | + */ | |
44 | + function _initElement(){ | |
45 | + elements.webhookListWrap = $(".webhook-list-wrap"); | |
46 | + } | |
47 | + | |
48 | + /** | |
49 | + * Attach event handlers | |
50 | + * @private | |
51 | + */ | |
52 | + function _attachEvent(){ | |
53 | + elements.webhookListWrap.on("click", "[data-delete-uri]", _onClickBtnDeleteWebhook); | |
54 | + } | |
55 | + | |
56 | + /** | |
57 | + * "Click" event handler of webhook row delete button | |
58 | + * Send request to remove webhook. | |
59 | + * | |
60 | + * @param event | |
61 | + * @private | |
62 | + */ | |
63 | + function _onClickBtnDeleteWebhook(evt){ | |
64 | + // TODO: Decide whether to show confirm modal or not | |
65 | + _requestRemoveWebhook(evt.target); | |
66 | + } | |
67 | + | |
68 | + /** | |
69 | + * Send AJAX request to remove webhook with specified delete button | |
70 | + * | |
71 | + * @param target | |
72 | + * @private | |
73 | + */ | |
74 | + function _requestRemoveWebhook(target){ | |
75 | + var targetButton = $(target); | |
76 | + | |
77 | + $.ajax(targetButton.data("deleteUri"), { | |
78 | + "method": "post", | |
79 | + "data" : {"_method": "delete"} | |
80 | + }) | |
81 | + .done(function(){ | |
82 | + _removeWebhookFromView(targetButton.data("webhookId")); | |
83 | + }); | |
84 | + } | |
85 | + | |
86 | + /** | |
87 | + * Remove specified webhook from webhook view | |
88 | + * | |
89 | + * @param webhookId | |
90 | + * @private | |
91 | + */ | |
92 | + function _removeWebhookFromView(webhookId){ | |
93 | + $('div[data-webhook-id="' + webhookId + '"]').remove(); | |
94 | + } | |
95 | + | |
96 | + _init(options || {}); | |
97 | + }; | |
98 | + | |
99 | +})("yobi.project.Webhook"); |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?