• ■ ■ ■ ■
    pom.xml
    skipped 8 lines
    9 9   <version>1.0.5</version>
    10 10   </parent>
    11 11   <artifactId>server</artifactId>
    12  - <version>4.3.5</version>
     12 + <version>4.4.0</version>
    13 13   <packaging>pom</packaging>
    14 14   <build>
    15 15   <finalName>${project.groupId}.${project.artifactId}-${project.version}</finalName>
    skipped 560 lines
  • ■ ■ ■ ■
    server-core/pom.xml
    skipped 6 lines
    7 7   <parent>
    8 8   <groupId>io.onedev</groupId>
    9 9   <artifactId>server</artifactId>
    10  - <version>4.3.5</version>
     10 + <version>4.4.0</version>
    11 11   </parent>
    12 12   <build>
    13 13   <plugins>
    skipped 311 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/CoreModule.java
    skipped 216 lines
    217 217  import io.onedev.server.persistence.annotation.Transactional;
    218 218  import io.onedev.server.persistence.dao.Dao;
    219 219  import io.onedev.server.persistence.dao.DefaultDao;
    220  -import io.onedev.server.rest.RestUtils;
     220 +import io.onedev.server.rest.RestConstants;
    221 221  import io.onedev.server.rest.jersey.DefaultServletContainer;
    222 222  import io.onedev.server.rest.jersey.JerseyConfigurator;
    223 223  import io.onedev.server.rest.jersey.ResourceConfigProvider;
    skipped 345 lines
    569 569  
    570 570   @Override
    571 571   public void configure(ResourceConfig resourceConfig) {
    572  - resourceConfig.packages(RestUtils.class.getPackage().getName());
     572 + resourceConfig.packages(RestConstants.class.getPackage().getName());
    573 573   }
    574 574  
    575 575   });
    skipped 248 lines
  • ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultBuildManager.java
    skipped 318 lines
    319 319   try {
    320 320   for (Project each: projects) {
    321 321   Collection<String> availableJobNames = jobNames.get(each.getId());
    322  - if (!availableJobNames.isEmpty()) {
     322 + if (availableJobNames != null && !availableJobNames.isEmpty()) {
    323 323   Collection<String> accessibleJobNames = getAccessibleJobNames(each);
    324 324   if (accessibleJobNames.containsAll(availableJobNames)) {
    325 325   projectCriterions.add(Restrictions.eq(Build.PROP_PROJECT, each));
    skipped 630 lines
  • ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/entitymanager/impl/DefaultSshKeyManager.java
    1 1  package io.onedev.server.entitymanager.impl;
    2 2   
    3  -import java.io.IOException;
    4  -import java.security.GeneralSecurityException;
    5  -import java.security.PublicKey;
    6 3  import java.util.Collection;
    7 4  import java.util.Date;
    8 5  import java.util.HashMap;
    skipped 2 lines
    11 8  import javax.inject.Inject;
    12 9  import javax.inject.Singleton;
    13 10   
    14  -import org.apache.sshd.common.config.keys.KeyUtils;
    15 11  import org.hibernate.criterion.Restrictions;
    16 12  import org.hibernate.criterion.SimpleExpression;
    17 13  import org.slf4j.Logger;
    skipped 10 lines
    28 24  import io.onedev.server.persistence.dao.BaseEntityManager;
    29 25  import io.onedev.server.persistence.dao.Dao;
    30 26  import io.onedev.server.persistence.dao.EntityCriteria;
    31  -import io.onedev.server.security.CipherUtils;
    32  -import io.onedev.server.ssh.SshKeyUtils;
    33 27   
    34 28  @Singleton
    35 29  public class DefaultSshKeyManager extends BaseEntityManager<SshKey> implements SshKeyManager {
    skipped 19 lines
    55 49   public void syncSshKeys(User user, Collection<String> sshKeys) {
    56 50   Map<String, SshKey> syncMap = new HashMap<>();
    57 51   for (String content: sshKeys) {
    58  - try {
    59  - PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(content);
    60  - String digest = KeyUtils.getFingerPrint(CipherUtils.DIGEST_FORMAT, pubEntry);
    61  -
    62  - SshKey sshKey = new SshKey();
    63  - sshKey.setDigest(digest);
    64  - sshKey.setContent(content);
    65  - sshKey.setOwner(user);
    66  - sshKey.setCreatedAt(new Date());
    67  - syncMap.put(content, sshKey);
    68  - } catch (IOException | GeneralSecurityException e) {
    69  - logger.error("Error parsing SSH key", e);
    70  - }
     52 + SshKey sshKey = new SshKey();
     53 + sshKey.setContent(content);
     54 + sshKey.setOwner(user);
     55 + sshKey.setCreatedAt(new Date());
     56 + sshKey.digest();
     57 + syncMap.put(content, sshKey);
    71 58   }
    72 59   
    73 60   Map<String, SshKey> currentMap = new HashMap<>();
    skipped 17 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/model/SshKey.java
    1 1  package io.onedev.server.model;
    2 2   
     3 +import java.io.IOException;
     4 +import java.security.GeneralSecurityException;
     5 +import java.security.PublicKey;
    3 6  import java.util.Date;
    4 7   
    5 8  import javax.annotation.Nullable;
    skipped 7 lines
    13 16  import javax.persistence.UniqueConstraint;
    14 17  import javax.validation.ConstraintValidatorContext;
    15 18   
     19 +import org.apache.sshd.common.config.keys.KeyUtils;
    16 20  import org.hibernate.validator.constraints.NotEmpty;
    17 21   
     22 +import com.fasterxml.jackson.annotation.JsonIgnore;
     23 + 
    18 24  import io.onedev.commons.utils.StringUtils;
     25 +import io.onedev.server.security.CipherUtils;
    19 26  import io.onedev.server.ssh.SshKeyUtils;
    20 27  import io.onedev.server.util.validation.Validatable;
    21 28  import io.onedev.server.util.validation.annotation.ClassValidating;
    skipped 12 lines
    34 41  
    35 42   private static final long serialVersionUID = 1L;
    36 43  
    37  - @Column(nullable=false, length = 5000)
     44 + @Column(nullable=false, length=5000)
    38 45   private String content;
    39 46  
     47 + @JsonIgnore
    40 48   @Column(nullable=false)
    41 49   private String digest;
    42 50   
     51 + @JsonIgnore
    43 52   @Column(nullable=false)
    44 53   private Date createdAt;
    45 54  
    skipped 44 lines
    90 99   return StringUtils.substringAfter(tempStr, " ");
    91 100   else
    92 101   return null;
     102 + }
     103 +
     104 + public void digest() {
     105 + try {
     106 + PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(content);
     107 + digest = KeyUtils.getFingerPrint(CipherUtils.DIGEST_FORMAT, pubEntry);
     108 + } catch (IOException | GeneralSecurityException e) {
     109 + throw new RuntimeException(e);
     110 + }
    93 111   }
    94 112  
    95 113   @Override
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/BuildQuerySettingResource.java
    skipped 43 lines
    44 44   return querySetting;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update build query setting of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull BuildQuerySetting querySetting) {
    50  - if (!SecurityUtils.canAccess(querySetting.getProject())
    51  - || !SecurityUtils.isAdministrator() && !querySetting.getUser().equals(SecurityUtils.getUser())) {
     49 + public Long createOrUpdate(@NotNull BuildQuerySetting querySetting) {
     50 + if (!SecurityUtils.isAdministrator() && !querySetting.getUser().equals(SecurityUtils.getUser()))
    52 51   throw new UnauthorizedException();
    53  - }
    54 52   querySettingManager.save(querySetting);
    55 53   return querySetting.getId();
    56 54   }
    skipped 14 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/BuildResource.java
    skipped 25 lines
    26 26  import io.onedev.server.search.entity.build.BuildQuery;
    27 27  import io.onedev.server.security.SecurityUtils;
    28 28   
    29  -@Api(order=4000)
     29 +@Api(order=4000, description="In most cases, build resource is operated with build id, which is different from build number. "
     30 + + "To get build id of a particular build number, use the <a href='/help/api/io.onedev.server.rest.BuildResource/queryBasicInfo'>Query Basic Info</a> operation with query for "
     31 + + "instance <code>&quot;Number&quot; is &quot;projectName#100&quot;</code>")
    30 32  @Path("/builds")
    31 33  @Consumes(MediaType.APPLICATION_JSON)
    32 34  @Produces(MediaType.APPLICATION_JSON)
    skipped 10 lines
    43 45   @Api(order=100)
    44 46   @Path("/{buildId}")
    45 47   @GET
    46  - public Build get(@PathParam("buildId") Long buildId) {
     48 + public Build getBasicInfo(@PathParam("buildId") Long buildId) {
    47 49   Build build = buildManager.load(buildId);
    48 50   if (!SecurityUtils.canAccess(build))
    49 51   throw new UnauthorizedException();
    skipped 42 lines
    92 94  
    93 95   @Api(order=600)
    94 96   @GET
    95  - public List<Build> query(@QueryParam("query") String query, @QueryParam("offset") int offset,
    96  - @QueryParam("count") int count) {
     97 + public List<Build> queryBasicInfo(
     98 + @QueryParam("query") @Api(description="Syntax of this query is the same as query box in <a href='/builds'>builds page</a>", example="\"Number\" is \"projectName#100\"") String query,
     99 + @QueryParam("offset") @Api(example="0") int offset,
     100 + @QueryParam("count") @Api(example="100") int count) {
    97 101  
    98  - if (count > RestUtils.MAX_PAGE_SIZE)
    99  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     102 + if (count > RestConstants.MAX_PAGE_SIZE)
     103 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    100 104   
    101 105   BuildQuery parsedQuery;
    102 106   try {
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/CodeCommentQuerySettingResource.java
    skipped 43 lines
    44 44   return querySetting;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update code comment query setting of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull CodeCommentQuerySetting querySetting) {
     49 + public Long createOrUpdate(@NotNull CodeCommentQuerySetting querySetting) {
    50 50   if (!SecurityUtils.canAccess(querySetting.getProject())
    51 51   || !SecurityUtils.isAdministrator() && !querySetting.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/CodeCommentReplyResource.java
    skipped 43 lines
    44 44   return reply;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update code comment reply of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull CodeCommentReply reply) {
     49 + public Long createOrUpdate(@NotNull CodeCommentReply reply) {
    50 50   if (!SecurityUtils.canReadCode(reply.getComment().getProject()) ||
    51 51   !SecurityUtils.isAdministrator() && !reply.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/CodeCommentResource.java
    skipped 53 lines
    54 54   @Api(order=100)
    55 55   @Path("/{commentId}")
    56 56   @GET
    57  - public CodeComment get(@PathParam("commentId") Long commentId) {
     57 + public CodeComment getBasicInfo(@PathParam("commentId") Long commentId) {
    58 58   CodeComment comment = commentManager.load(commentId);
    59 59   if (!SecurityUtils.canReadCode(comment.getProject()))
    60 60   throw new UnauthorizedException();
    skipped 12 lines
    73 73  
    74 74   @Api(order=300)
    75 75   @GET
    76  - public Collection<CodeComment> query(@QueryParam("projectId") Long projectId,
    77  - @QueryParam("pullRequestId") Long pullRequestId, @QueryParam("query") String query,
    78  - @QueryParam("offset") int offset, @QueryParam("count") int count) {
     76 + public Collection<CodeComment> queryBasicInfo(
     77 + @QueryParam("projectId") Long projectId,
     78 + @QueryParam("pullRequestId") Long pullRequestId,
     79 + @QueryParam("query") @Api(description="Syntax of this query is the same as query box in project code comments page", example="created by me") String query,
     80 + @QueryParam("offset") @Api(example="0") int offset, @QueryParam("count") @Api(example="100") int count) {
    79 81  
    80  - if (count > RestUtils.MAX_PAGE_SIZE)
    81  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     82 + if (count > RestConstants.MAX_PAGE_SIZE)
     83 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    82 84   
    83 85   Project project;
    84 86   if (projectId != null)
    skipped 29 lines
    114 116   return commentManager.query(project, pullRequest, parsedQuery, offset, count);
    115 117   }
    116 118  
    117  - @Api(order=400)
     119 + @Api(order=400, description="Update code comment of specified id in request body, or create new if id property not provided")
    118 120   @POST
    119  - public Long save(@NotNull CodeComment comment) {
     121 + public Long createOrUpdate(@NotNull CodeComment comment) {
    120 122   if (!SecurityUtils.canReadCode(comment.getProject()) ||
    121 123   !SecurityUtils.isAdministrator() && !comment.getUser().equals(SecurityUtils.getUser())) {
    122 124   throw new UnauthorizedException();
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/CommitQuerySettingResource.java
    skipped 43 lines
    44 44   return querySetting;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update commit query setting of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull CommitQuerySetting querySetting) {
     49 + public Long createOrUpdate(@NotNull CommitQuerySetting querySetting) {
    50 50   if (!SecurityUtils.canAccess(querySetting.getProject())
    51 51   || !SecurityUtils.isAdministrator() && !querySetting.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/GroupAuthorizationResource.java
    skipped 42 lines
    43 43   return authorizationManager.load(authorizationId);
    44 44   }
    45 45  
    46  - @Api(order=200)
     46 + @Api(order=200, description="Update group authorization of specified id in request body, or create new if id property not provided")
    47 47   @POST
    48  - public Long save(@NotNull GroupAuthorization authorization) {
     48 + public Long createOrUpdate(@NotNull GroupAuthorization authorization) {
    49 49   if (!SecurityUtils.isAdministrator())
    50 50   throw new UnauthorizedException();
    51 51   authorizationManager.save(authorization);
    skipped 15 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/GroupResource.java
    skipped 46 lines
    47 47   @Api(order=100)
    48 48   @Path("/{groupId}")
    49 49   @GET
    50  - public Group get(@PathParam("groupId") Long groupId) {
     50 + public Group getBasicInfo(@PathParam("groupId") Long groupId) {
    51 51   if (!SecurityUtils.isAdministrator())
    52 52   throw new UnauthorizedException();
    53 53   return groupManager.load(groupId);
    skipped 19 lines
    73 73  
    74 74   @Api(order=400)
    75 75   @GET
    76  - public List<Group> query(@QueryParam("name") String name, @QueryParam("offset") int offset,
    77  - @QueryParam("count") int count) {
     76 + public List<Group> queryBasicInfo(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset,
     77 + @QueryParam("count") @Api(example="100") int count) {
    78 78  
    79 79   if (!SecurityUtils.isAdministrator())
    80 80   throw new UnauthorizedException();
    81 81  
    82  - if (count > RestUtils.MAX_PAGE_SIZE)
    83  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     82 + if (count > RestConstants.MAX_PAGE_SIZE)
     83 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    84 84   
    85 85   EntityCriteria<Group> criteria = EntityCriteria.of(Group.class);
    86 86   if (name != null)
    skipped 2 lines
    89 89   return groupManager.query(criteria, offset, count);
    90 90   }
    91 91  
    92  - @Api(order=500)
     92 + @Api(order=500, description="Update group of specified id in request body, or create new if id property not provided")
    93 93   @POST
    94  - public Long save(@NotNull Group group) {
     94 + public Long createOrUpdate(@NotNull Group group) {
    95 95   if (!SecurityUtils.isAdministrator())
    96 96   throw new UnauthorizedException();
    97 97   groupManager.save(group, (String) group.getCustomData());
    skipped 15 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/IssueCommentResource.java
    skipped 43 lines
    44 44   return comment;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update issue comment of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull IssueComment comment) {
     49 + public Long createOrUpdate(@NotNull IssueComment comment) {
    50 50   if (!SecurityUtils.canAccess(comment.getIssue().getProject()) ||
    51 51   !SecurityUtils.isAdministrator() && !comment.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/IssueQuerySettingResource.java
    skipped 43 lines
    44 44   return querySetting;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update issue query setting of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull IssueQuerySetting querySetting) {
     49 + public Long createOrUpdate(@NotNull IssueQuerySetting querySetting) {
    50 50   if (!SecurityUtils.canAccess(querySetting.getProject())
    51 51   || !SecurityUtils.isAdministrator() && !querySetting.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/IssueResource.java
    skipped 44 lines
    45 45  import io.onedev.server.model.User;
    46 46  import io.onedev.server.model.support.issue.field.spec.FieldSpec;
    47 47  import io.onedev.server.rest.annotation.Api;
     48 +import io.onedev.server.rest.annotation.EntityCreate;
    48 49  import io.onedev.server.rest.annotation.EntityId;
    49 50  import io.onedev.server.rest.jersey.InvalidParamException;
    50 51  import io.onedev.server.search.entity.issue.IssueQuery;
    51 52  import io.onedev.server.security.SecurityUtils;
    52 53   
    53  -@Api(order=2000)
     54 +@Api(order=2000, description="In most cases, issue resource is operated with issue id, which is different from issue number. "
     55 + + "To get issue id of a particular issue number, use the <a href='/help/api/io.onedev.server.rest.IssueResource/queryBasicInfo'>Query Basic Info</a> operation with query for "
     56 + + "instance <code>&quot;Number&quot; is &quot;projectName#100&quot;</code>")
    54 57  @Path("/issues")
    55 58  @Consumes(MediaType.APPLICATION_JSON)
    56 59  @Produces(MediaType.APPLICATION_JSON)
    skipped 24 lines
    81 84   @Api(order=100)
    82 85   @Path("/{issueId}")
    83 86   @GET
    84  - public Issue get(@PathParam("issueId") Long issueId) {
     87 + public Issue getBasicInfo(@PathParam("issueId") Long issueId) {
    85 88   Issue issue = issueManager.load(issueId);
    86 89   if (!SecurityUtils.canAccess(issue.getProject()))
    87 90   throw new UnauthorizedException();
    skipped 72 lines
    160 163  
    161 164   @Api(order=900)
    162 165   @GET
    163  - public List<Issue> query(@QueryParam("query") String query, @QueryParam("offset") int offset,
    164  - @QueryParam("count") int count) {
     166 + public List<Issue> queryBasicInfo(
     167 + @QueryParam("query") @Api(description="Syntax of this query is the same as query box in <a href='/issues'>issues page</a>", example="\"Number\" is \"projectName#100\"") String query,
     168 + @QueryParam("offset") @Api(example="0") int offset,
     169 + @QueryParam("count") @Api(example="100") int count) {
    165 170  
    166  - if (count > RestUtils.MAX_PAGE_SIZE)
    167  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     171 + if (count > RestConstants.MAX_PAGE_SIZE)
     172 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    168 173   
    169 174   IssueQuery parsedQuery;
    170 175   try {
    skipped 7 lines
    178 183  
    179 184   @Api(order=1000)
    180 185   @POST
    181  - public Long open(@NotNull @Valid IssueOpenData data) {
     186 + public Long create(@NotNull @Valid IssueOpenData data) {
    182 187   User user = SecurityUtils.getUser();
    183 188  
    184 189   Project project = projectManager.load(data.getProjectId());
    skipped 151 lines
    336 341   return Response.ok().build();
    337 342   }
    338 343  
     344 + @EntityCreate(Issue.class)
    339 345   public static class IssueOpenData implements Serializable {
    340 346   
    341 347   private static final long serialVersionUID = 1L;
    skipped 109 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/IssueVoteResource.java
    skipped 43 lines
    44 44   return vote;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update issue vote of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull IssueVote vote) {
     49 + public Long createOrUpdate(@NotNull IssueVote vote) {
    50 50   if (!SecurityUtils.canAccess(vote.getIssue().getProject())
    51 51   || !SecurityUtils.isAdministrator() && !vote.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/IssueWatchResource.java
    skipped 43 lines
    44 44   return watch;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update issue watch of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull IssueWatch watch) {
     49 + public Long createOrUpdate(@NotNull IssueWatch watch) {
    50 50   if (!SecurityUtils.canAccess(watch.getIssue().getProject())
    51 51   || !SecurityUtils.isAdministrator() && !watch.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 19 lines
  • ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/JobRunResource.java
    skipped 30 lines
    31 31  import io.onedev.server.model.Project;
    32 32  import io.onedev.server.model.PullRequest;
    33 33  import io.onedev.server.rest.annotation.Api;
     34 +import io.onedev.server.rest.annotation.EntityCreate;
    34 35  import io.onedev.server.rest.annotation.EntityId;
    35 36  import io.onedev.server.security.SecurityUtils;
    36 37  import io.onedev.server.util.validation.annotation.CommitHash;
    skipped 24 lines
    61 62   
    62 63   @Api(order=100)
    63 64   @POST
    64  - public Long run(@NotNull @Valid JobRun jobRun) {
     65 + public Long runBuild(@NotNull @Valid JobRun jobRun) {
    65 66   Project project = projectManager.load(jobRun.projectId);
    66 67   if (!SecurityUtils.canRunJob(project, jobRun.jobName))
    67 68   throw new UnauthorizedException();
    skipped 24 lines
    92 93   }
    93 94   
    94 95   @Api(order=200)
    95  - @Path("/rerun")
     96 + @Path("/rebuild")
    96 97   @POST
    97  - public Response rerun(@NotNull @Valid JobRerun jobRerun) {
     98 + public Response rebuild(@NotNull @Valid JobRerun jobRerun) {
    98 99   Build build = buildManager.load(jobRerun.buildId);
    99 100   if (!SecurityUtils.canRunJob(build.getProject(), build.getJobName()))
    100 101   throw new UnauthorizedException();
    skipped 4 lines
    105 106   @Api(order=300)
    106 107   @Path("/{buildId}")
    107 108   @DELETE
    108  - public Response delete(@PathParam("buildId") Long buildId) {
     109 + public Response cancelBuild(@PathParam("buildId") Long buildId) {
    109 110   Build build = buildManager.load(buildId);
    110 111   if (!SecurityUtils.canRunJob(build.getProject(), build.getJobName()))
    111 112   throw new UnauthorizedException();
    skipped 2 lines
    114 115   return Response.ok().build();
    115 116   }
    116 117  
     118 + @EntityCreate(Build.class)
    117 119   public static class JobRun implements Serializable {
    118 120  
    119 121   private static final long serialVersionUID = 1L;
    skipped 5 lines
    125 127  
    126 128   private String jobName;
    127 129  
     130 + @Api(description="A map of param name to value list. Normally the value list contains only one "
     131 + + "param value. However in case the job param is defined as multi-valued in build spec, "
     132 + + "you can add multiple param values")
    128 133   private Map<String, List<String>> params = new HashMap<>();
    129 134  
    130 135   private String refName;
    skipped 72 lines
    203 208  
    204 209   private static final long serialVersionUID = 1L;
    205 210   
     211 + @EntityId(Build.class)
    206 212   private Long buildId;
    207 213  
     214 + @Api(description="A map of param name to value list. Normally the value list contains only one "
     215 + + "param value. However in case the job param is defined as multi-valued in build spec, "
     216 + + "you can add multiple param values")
    208 217   private Map<String, List<String>> params = new HashMap<>();
    209 218  
    210 219   private String reason;
    skipped 32 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/MembershipResource.java
    skipped 42 lines
    43 43   return membershipManager.load(membershipId);
    44 44   }
    45 45  
    46  - @Api(order=200)
     46 + @Api(order=200, description="Update membership of specified id in request body, or create new if id property not provided")
    47 47   @POST
    48  - public Long save(@NotNull Membership membership) {
     48 + public Long createOrUpdate(@NotNull Membership membership) {
    49 49   if (!SecurityUtils.isAdministrator())
    50 50   throw new UnauthorizedException();
    51 51   membershipManager.save(membership);
    skipped 15 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/MilestoneResource.java
    skipped 43 lines
    44 44   return milestone;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update milestone of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull Milestone milestone) {
     49 + public Long createOrUpdate(@NotNull Milestone milestone) {
    50 50   if (!SecurityUtils.canManageIssues(milestone.getProject()))
    51 51   throw new UnauthorizedException();
    52 52   milestoneManager.save(milestone);
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/ProjectResource.java
    skipped 2 lines
    3 3  import java.io.Serializable;
    4 4  import java.util.ArrayList;
    5 5  import java.util.Collection;
     6 +import java.util.Date;
    6 7  import java.util.List;
    7 8   
    8 9  import javax.inject.Inject;
    skipped 32 lines
    41 42  import io.onedev.server.rest.jersey.InvalidParamException;
    42 43  import io.onedev.server.search.entity.project.ProjectQuery;
    43 44  import io.onedev.server.security.SecurityUtils;
     45 +import io.onedev.server.util.DateUtils;
    44 46   
    45 47  @Api(order=1000)
    46 48  @Path("/projects")
    skipped 15 lines
    62 64   @Api(order=100)
    63 65   @Path("/{projectId}")
    64 66   @GET
    65  - public Project get(@Api(description="Internal id of project") @PathParam("projectId") Long projectId) {
     67 + public Project getBasicInfo(@PathParam("projectId") Long projectId) {
    66 68   Project project = projectManager.load(projectId);
    67 69   if (!SecurityUtils.canAccess(project))
    68 70   throw new UnauthorizedException();
    skipped 50 lines
    119 121  
    120 122   @Api(order=700)
    121 123   @GET
    122  - public List<Project> query(@QueryParam("query") String query, @QueryParam("offset") int offset,
    123  - @QueryParam("count") int count) {
     124 + public List<Project> queryBasicInfo(
     125 + @QueryParam("query") @Api(description="Syntax of this query is the same as query box in <a href='/projects'>projects page</a>", example="\"Name\" is \"projectName\"") String query,
     126 + @QueryParam("offset") @Api(example="0") int offset,
     127 + @QueryParam("count") @Api(example="100") int count) {
    124 128  
    125  - if (count > RestUtils.MAX_PAGE_SIZE)
    126  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     129 + if (count > RestConstants.MAX_PAGE_SIZE)
     130 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    127 131   
    128 132   ProjectQuery parsedQuery;
    129 133   try {
    skipped 9 lines
    139 143   @Path("/{projectId}/milestones")
    140 144   @GET
    141 145   public List<Milestone> queryMilestones(@PathParam("projectId") Long projectId, @QueryParam("name") String name,
    142  - @QueryParam("dueBefore") String dueBefore, @QueryParam("dueAfter") String dueAfter,
    143  - @QueryParam("closed") Boolean closed, @QueryParam("offset") int offset,
    144  - @QueryParam("count") int count) {
     146 + @QueryParam("dueBefore") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String dueBefore,
     147 + @QueryParam("dueAfter") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String dueAfter,
     148 + @QueryParam("closed") Boolean closed, @QueryParam("offset") @Api(example="0") int offset,
     149 + @QueryParam("count") @Api(example="100") int count) {
    145 150   Project project = projectManager.load(projectId);
    146 151   if (!SecurityUtils.canAccess(project))
    147 152   throw new UnauthorizedException();
    148 153   
    149  - if (count > RestUtils.MAX_PAGE_SIZE)
    150  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     154 + if (count > RestConstants.MAX_PAGE_SIZE)
     155 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    151 156  
    152 157   EntityCriteria<Milestone> criteria = EntityCriteria.of(Milestone.class);
    153 158   criteria.add(Restrictions.eq(Milestone.PROP_PROJECT, project));
    154 159   if (name != null)
    155 160   criteria.add(Restrictions.ilike(Milestone.PROP_NAME, name.replace('%', '*')));
    156 161   if (dueBefore != null)
    157  - criteria.add(Restrictions.le(Milestone.PROP_DUE_DATE, RestUtils.toDate(dueBefore)));
     162 + criteria.add(Restrictions.le(Milestone.PROP_DUE_DATE, DateUtils.parseISO8601Date(dueBefore)));
    158 163   if (dueAfter != null)
    159  - criteria.add(Restrictions.ge(Milestone.PROP_DUE_DATE, RestUtils.toDate(dueAfter)));
     164 + criteria.add(Restrictions.ge(Milestone.PROP_DUE_DATE, DateUtils.parseISO8601Date(dueAfter)));
    160 165   if (closed != null)
    161 166   criteria.add(Restrictions.eq(Milestone.PROP_CLOSED, closed));
    162 167  
    163 168   return milestoneManager.query(criteria, offset, count);
    164 169   }
    165 170  
    166  - @Api(order=800)
     171 + @SuppressWarnings("unused")
     172 + private static String getDateExample() {
     173 + return DateUtils.formatISO8601Date(new Date());
     174 + }
     175 +
     176 + @Api(order=800, description="Update project of specified id in request body, or create new if id property not provided")
    167 177   @POST
    168  - public Long save(@NotNull Project project) {
     178 + public Long createOrUpdate(@NotNull Project project) {
    169 179   if (project.isNew())
    170 180   projectManager.create(project);
    171 181   else if (!SecurityUtils.canManage(project))
    skipped 4 lines
    176 186   return project.getId();
    177 187   }
    178 188  
    179  - @Api(order=900)
     189 + @Api(order=900, description="Update project setting")
    180 190   @Path("/{projectId}/setting")
    181 191   @POST
    182  - public Response saveSetting(@PathParam("projectId") Long projectId, @NotNull ProjectSetting setting) {
     192 + public Response updateSetting(@PathParam("projectId") Long projectId, @NotNull ProjectSetting setting) {
    183 193   Project project = projectManager.load(projectId);
    184 194   if (!SecurityUtils.canManage(project))
    185 195   throw new UnauthorizedException();
    skipped 110 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/PullRequestAssignmentResource.java
    skipped 44 lines
    45 45   return assignment;
    46 46   }
    47 47  
    48  - @Api(order=200)
     48 + @Api(order=200, description="Update pull request assignment of specified id in request body, or create new if id property not provided")
    49 49   @POST
    50  - public Long save(PullRequestAssignment assignment) {
     50 + public Long createOrUpdate(PullRequestAssignment assignment) {
    51 51   PullRequest pullRequest = assignment.getRequest();
    52 52   if (!SecurityUtils.canReadCode(pullRequest.getProject()) || !SecurityUtils.canModify(pullRequest))
    53 53   throw new UnauthorizedException();
    skipped 26 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/PullRequestCommentResource.java
    skipped 43 lines
    44 44   return comment;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update pull request comment of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull PullRequestComment comment) {
     49 + public Long createOrUpdate(@NotNull PullRequestComment comment) {
    50 50   if (!SecurityUtils.canReadCode(comment.getProject()) ||
    51 51   !SecurityUtils.isAdministrator() && !comment.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/PullRequestQuerySettingResource.java
    skipped 43 lines
    44 44   return querySetting;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update pull request query setting of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull PullRequestQuerySetting querySetting) {
     49 + public Long createOrUpdate(@NotNull PullRequestQuerySetting querySetting) {
    50 50   if (!SecurityUtils.canAccess(querySetting.getProject())
    51 51   || !SecurityUtils.isAdministrator() && !querySetting.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/PullRequestResource.java
    skipped 43 lines
    44 44  import io.onedev.server.model.support.pullrequest.MergePreview;
    45 45  import io.onedev.server.model.support.pullrequest.MergeStrategy;
    46 46  import io.onedev.server.rest.annotation.Api;
     47 +import io.onedev.server.rest.annotation.EntityCreate;
    47 48  import io.onedev.server.rest.annotation.EntityId;
    48 49  import io.onedev.server.rest.jersey.InvalidParamException;
    49 50  import io.onedev.server.search.entity.pullrequest.PullRequestQuery;
    50 51  import io.onedev.server.security.SecurityUtils;
    51 52  import io.onedev.server.util.ProjectAndBranch;
    52 53   
    53  -@Api(order=3000)
     54 +@Api(order=3000, description="In most cases, pull request resource is operated with pull request id, which is different from pull request number. "
     55 + + "To get pull request id of a particular pull request number, use the <a href='/help/api/io.onedev.server.rest.PullRequestResource/queryBasicInfo'>Query Basic Info</a> operation with query for "
     56 + + "instance <code>&quot;Number&quot; is &quot;projectName#100&quot;</code>")
    54 57  @Path("/pull-requests")
    55 58  @Consumes(MediaType.APPLICATION_JSON)
    56 59  @Produces(MediaType.APPLICATION_JSON)
    skipped 17 lines
    74 77   @Api(order=100)
    75 78   @Path("/{requestId}")
    76 79   @GET
    77  - public PullRequest get(@PathParam("requestId") Long requestId) {
     80 + public PullRequest getBasicInfo(@PathParam("requestId") Long requestId) {
    78 81   PullRequest pullRequest = pullRequestManager.load(requestId);
    79 82   if (!SecurityUtils.canReadCode(pullRequest.getProject()))
    80 83   throw new UnauthorizedException();
    skipped 92 lines
    173 176  
    174 177   @Api(order=1100)
    175 178   @GET
    176  - public List<PullRequest> query(@QueryParam("query") String query, @QueryParam("offset") int offset,
    177  - @QueryParam("count") int count) {
     179 + public List<PullRequest> queryBasicInfo(
     180 + @QueryParam("query") @Api(description="Syntax of this query is the same as query box in <a href='/pull-requests'>pull requests page</a>", example="\"Number\" is \"projectName#100\"") String query,
     181 + @QueryParam("offset") @Api(example="0") int offset,
     182 + @QueryParam("count") @Api(example="100") int count) {
    178 183  
    179  - if (count > RestUtils.MAX_PAGE_SIZE)
    180  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     184 + if (count > RestConstants.MAX_PAGE_SIZE)
     185 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    181 186   
    182 187   PullRequestQuery parsedQuery;
    183 188   try {
    skipped 7 lines
    191 196   
    192 197   @Api(order=1200)
    193 198   @POST
    194  - public Long open(@NotNull PullRequestOpenData data) {
     199 + public Long create(@NotNull PullRequestOpenData data) {
    195 200   User user = SecurityUtils.getUser();
    196 201  
    197 202   ProjectAndBranch target = new ProjectAndBranch(data.getTargetProjectId(), data.getTargetBranch());
    skipped 193 lines
    391 396   return Response.ok().build();
    392 397   }
    393 398  
     399 + @EntityCreate(PullRequest.class)
    394 400   public static class PullRequestOpenData implements Serializable {
    395 401   
    396 402   private static final long serialVersionUID = 1L;
    skipped 104 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/PullRequestReviewResource.java
    skipped 46 lines
    47 47   return review;
    48 48   }
    49 49  
    50  - @Api(order=200)
     50 + @Api(order=200, description="Update pull request review of specified id in request body, or create new if id property not provided")
    51 51   @POST
    52  - public Long save(@NotNull PullRequestReview review) {
     52 + public Long createOrUpdate(@NotNull PullRequestReview review) {
    53 53   if (!SecurityUtils.canReadCode(review.getRequest().getProject())
    54 54   || !SecurityUtils.isAdministrator() && !review.getUser().equals(SecurityUtils.getUser())) {
    55 55   throw new UnauthorizedException();
    skipped 32 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/PullRequestWatchResource.java
    skipped 43 lines
    44 44   return watch;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update pull request watch of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull PullRequestWatch watch) {
     49 + public Long createOrUpdate(@NotNull PullRequestWatch watch) {
    50 50   if (!SecurityUtils.canReadCode(watch.getRequest().getProject())
    51 51   || !SecurityUtils.isAdministrator() && !watch.getUser().equals(SecurityUtils.getUser())) {
    52 52   throw new UnauthorizedException();
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/RestConstants.java
     1 +package io.onedev.server.rest;
     2 + 
     3 +public class RestConstants {
     4 + 
     5 + public static final int MAX_PAGE_SIZE = 100;
     6 + 
     7 +}
     8 + 
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/RestUtils.java
    1  -package io.onedev.server.rest;
    2  - 
    3  -import java.util.Date;
    4  - 
    5  -import org.joda.time.format.ISODateTimeFormat;
    6  - 
    7  -public class RestUtils {
    8  - 
    9  - public static final int MAX_PAGE_SIZE = 100;
    10  - 
    11  - public static Date toDate(String dateString) {
    12  - return ISODateTimeFormat.dateTimeParser().parseDateTime(dateString).toDate();
    13  - }
    14  -
    15  -}
    16  - 
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/RoleResource.java
    skipped 51 lines
    52 52   
    53 53   @Api(order=200)
    54 54   @GET
    55  - public List<Role> query(@QueryParam("name") String name, @QueryParam("offset") int offset,
    56  - @QueryParam("count") int count) {
     55 + public List<Role> query(@QueryParam("name") String name, @QueryParam("offset") @Api(example="0") int offset,
     56 + @QueryParam("count") @Api(example="100") int count) {
    57 57  
    58 58   if (!SecurityUtils.isAdministrator())
    59 59   throw new UnauthorizedException();
    60 60  
    61  - if (count > RestUtils.MAX_PAGE_SIZE)
    62  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     61 + if (count > RestConstants.MAX_PAGE_SIZE)
     62 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    63 63   
    64 64   EntityCriteria<Role> criteria = EntityCriteria.of(Role.class);
    65 65   if (name != null)
    skipped 2 lines
    68 68   return roleManager.query(criteria, offset, count);
    69 69   }
    70 70  
    71  - @Api(order=300)
     71 + @Api(order=300, description="Update role of specified id in request body, or create new if id property not provided")
    72 72   @POST
    73  - public Long save(@NotNull Role role) {
     73 + public Long createOrUpdate(@NotNull Role role) {
    74 74   if (!SecurityUtils.isAdministrator())
    75 75   throw new UnauthorizedException();
    76 76   roleManager.save(role, (String) role.getCustomData());
    skipped 15 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/SshKeyResource.java
    1 1  package io.onedev.server.rest;
    2 2   
     3 +import java.util.Date;
     4 + 
    3 5  import javax.inject.Inject;
    4 6  import javax.inject.Singleton;
    5 7  import javax.ws.rs.Consumes;
    6 8  import javax.ws.rs.DELETE;
    7 9  import javax.ws.rs.GET;
     10 +import javax.ws.rs.POST;
    8 11  import javax.ws.rs.Path;
    9 12  import javax.ws.rs.PathParam;
    10 13  import javax.ws.rs.Produces;
    skipped 29 lines
    40 43   if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getUser()))
    41 44   throw new UnauthorizedException();
    42 45   return sshKey;
     46 + }
     47 +
     48 + @Api(order=150, description="Update ssh key of specified id in request body, or create new if id property not provided")
     49 + @POST
     50 + public Long createOrUpdate(SshKey sshKey) {
     51 + if (!SecurityUtils.isAdministrator() && !sshKey.getOwner().equals(SecurityUtils.getUser()))
     52 + throw new UnauthorizedException();
     53 + if (sshKey.isNew())
     54 + sshKey.setCreatedAt(new Date());
     55 +
     56 + sshKey.digest();
     57 +
     58 + sshKeyManager.save(sshKey);
     59 + return sshKey.getId();
    43 60   }
    44 61  
    45 62   @Api(order=200)
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/UserAuthorizationResource.java
    skipped 43 lines
    44 44   return authorization;
    45 45   }
    46 46  
    47  - @Api(order=200)
     47 + @Api(order=200, description="Update user authorization of specified id in request body, or create new if id property not provided")
    48 48   @POST
    49  - public Long save(@NotNull UserAuthorization authorization) {
     49 + public Long createOrUpdate(@NotNull UserAuthorization authorization) {
    50 50   if (!SecurityUtils.canManage(authorization.getProject()))
    51 51   throw new UnauthorizedException();
    52 52   authorizationManager.save(authorization);
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/UserResource.java
    1 1  package io.onedev.server.rest;
    2 2   
    3  -import java.io.IOException;
    4 3  import java.io.Serializable;
    5  -import java.security.GeneralSecurityException;
    6  -import java.security.PublicKey;
    7 4  import java.util.ArrayList;
    8 5  import java.util.Collection;
    9 6  import java.util.Date;
    skipped 18 lines
    28 25  import org.apache.shiro.authc.credential.PasswordService;
    29 26  import org.apache.shiro.authz.UnauthenticatedException;
    30 27  import org.apache.shiro.authz.UnauthorizedException;
    31  -import org.apache.sshd.common.config.keys.KeyUtils;
    32 28  import org.hibernate.criterion.MatchMode;
    33 29  import org.hibernate.criterion.Restrictions;
    34 30  import org.hibernate.validator.constraints.NotEmpty;
    skipped 23 lines
    58 54  import io.onedev.server.persistence.dao.EntityCriteria;
    59 55  import io.onedev.server.rest.annotation.Api;
    60 56  import io.onedev.server.rest.jersey.InvalidParamException;
    61  -import io.onedev.server.security.CipherUtils;
    62 57  import io.onedev.server.security.SecurityUtils;
    63  -import io.onedev.server.ssh.SshKeyUtils;
    64 58   
    65 59  @Api(order=5000)
    66 60  @Path("/users")
    skipped 18 lines
    85 79   @Api(order=100)
    86 80   @Path("/{userId}")
    87 81   @GET
    88  - public User get(@PathParam("userId") Long userId) {
     82 + public User getBasicInfo(@PathParam("userId") Long userId) {
    89 83   User user = userManager.load(userId);
    90 84   if (!SecurityUtils.isAdministrator() && !user.equals(SecurityUtils.getUser()))
    91 85   throw new UnauthorizedException();
    skipped 3 lines
    95 89   @Api(order=200)
    96 90   @Path("/me")
    97 91   @GET
    98  - public User getMe() {
     92 + public User getMyBasicInfo() {
    99 93   User user = SecurityUtils.getUser();
    100 94   if (user == null)
    101 95   throw new UnauthenticatedException();
    skipped 161 lines
    263 257  
    264 258   @Api(order=1800)
    265 259   @GET
    266  - public List<User> query(@QueryParam("name") String name, @QueryParam("fullName") String fullName,
    267  - @QueryParam("email") String email, @QueryParam("offset") int offset,
    268  - @QueryParam("count") int count) {
     260 + public List<User> queryBasicInfo(@QueryParam("name") String name, @QueryParam("fullName") String fullName,
     261 + @QueryParam("email") String email, @QueryParam("offset") @Api(example="0") int offset,
     262 + @QueryParam("count") @Api(example="100") int count) {
    269 263  
    270 264   if (!SecurityUtils.isAdministrator())
    271 265   throw new UnauthorizedException();
    272 266  
    273  - if (count > RestUtils.MAX_PAGE_SIZE)
    274  - throw new InvalidParamException("Count should be less than " + RestUtils.MAX_PAGE_SIZE);
     267 + if (count > RestConstants.MAX_PAGE_SIZE)
     268 + throw new InvalidParamException("Count should be less than " + RestConstants.MAX_PAGE_SIZE);
    275 269   
    276 270   EntityCriteria<User> criteria = EntityCriteria.of(User.class);
    277 271   criteria.add(Restrictions.not(Restrictions.eq("id", User.SYSTEM_ID)));
    skipped 7 lines
    285 279   return userManager.query(criteria, offset, count);
    286 280   }
    287 281  
    288  - @Api(order=1900)
     282 + @Api(order=1900, description="Update user of specified id in request body, or create new if id property not provided")
    289 283   @POST
    290  - public Long save(@NotNull User user) {
     284 + public Long createOrUpdate(@NotNull User user) {
    291 285   if (user.isNew()) {
    292 286   if (!SecurityUtils.isAdministrator()) {
    293 287   throw new UnauthorizedException();
    skipped 75 lines
    369 363   sshKey.setContent(content);
    370 364   sshKey.setCreatedAt(new Date());
    371 365   sshKey.setOwner(user);
    372  -
    373  - try {
    374  - PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(content);
    375  - String fingerPrint = KeyUtils.getFingerPrint(CipherUtils.DIGEST_FORMAT, pubEntry);
    376  - sshKey.setDigest(fingerPrint);
    377  - } catch (IOException | GeneralSecurityException e) {
    378  - throw new RuntimeException(e);
    379  - }
     366 + sshKey.digest();
    380 367  
    381 368   sshKeyManager.save(sshKey);
    382 369   return sshKey.getId();
    skipped 45 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/annotation/Api.java
    skipped 12 lines
    13 13  
    14 14   String exampleProvider() default "";
    15 15  
     16 + String example() default "";
     17 +
    16 18   String description() default "";
    17 19  
    18 20   int order() default 100;
    19  -
    20  - String permission() default "";
    21 21  
    22 22  }
    23 23   
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/rest/annotation/EntityCreate.java
     1 +package io.onedev.server.rest.annotation;
     2 + 
     3 +import java.lang.annotation.ElementType;
     4 +import java.lang.annotation.Retention;
     5 +import java.lang.annotation.RetentionPolicy;
     6 +import java.lang.annotation.Target;
     7 + 
     8 +@Retention(RetentionPolicy.RUNTIME)
     9 +@Target({ElementType.TYPE})
     10 +public @interface EntityCreate {
     11 + 
     12 + Class<?> value();
     13 +
     14 +}
     15 + 
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/security/CipherUtils.java
    skipped 10 lines
    11 11  import org.apache.shiro.crypto.AesCipherService;
    12 12  import org.apache.sshd.common.digest.BaseDigest;
    13 13  import org.apache.sshd.common.digest.Digest;
    14  -import org.apache.sshd.common.digest.DigestUtils;
    15 14   
    16 15  import io.onedev.server.OneDev;
    17 16  import io.onedev.server.entitymanager.SettingManager;
    skipped 30 lines
    48 47  
    49 48   public static byte[] decrypt(byte[] data) {
    50 49   return cipherService.decrypt(data, getCipherKey()).getBytes();
    51  - }
    52  -
    53  - public static String digest(String content) {
    54  - try {
    55  - return DigestUtils.getFingerPrint(DIGEST_FORMAT, content);
    56  - } catch (Exception e) {
    57  - throw new RuntimeException(e);
    58  - }
    59 50   }
    60 51  
    61 52   private static class KeyPair {
    skipped 22 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/util/DateUtils.java
    skipped 6 lines
    7 7   
    8 8  import org.apache.commons.lang3.time.DurationFormatUtils;
    9 9  import org.joda.time.DateTime;
     10 +import org.joda.time.format.ISODateTimeFormat;
    10 11  import org.ocpsoft.prettytime.PrettyTime;
    11 12   
    12 13  import com.google.common.collect.Lists;
    skipped 36 lines
    49 50   durationMillis = 0;
    50 51   return DurationFormatUtils.formatDurationWords(durationMillis, true, true);
    51 52   }
     53 +
     54 + public static Date parseISO8601Date(String dateString) {
     55 + return ISODateTimeFormat.dateTimeParser().parseDateTime(dateString).toDate();
     56 + }
     57 +
     58 + public static String formatISO8601Date(Date date) {
     59 + return ISODateTimeFormat.dateTime().print(date.getTime());
     60 + }
     61 +
    52 62  }
  • ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/component/user/sshkey/InsertSshKeyPanel.java
    1 1  package io.onedev.server.web.component.user.sshkey;
    2 2   
    3  -import java.io.IOException;
    4  -import java.security.GeneralSecurityException;
    5  -import java.security.PublicKey;
    6 3  import java.util.Date;
    7 4   
    8  -import org.apache.sshd.common.config.keys.KeyUtils;
    9 5  import org.apache.wicket.ajax.AjaxRequestTarget;
    10 6  import org.apache.wicket.ajax.markup.html.AjaxLink;
    11 7  import org.apache.wicket.ajax.markup.html.form.AjaxButton;
    skipped 6 lines
    18 14  import io.onedev.server.entitymanager.SshKeyManager;
    19 15  import io.onedev.server.model.SshKey;
    20 16  import io.onedev.server.model.User;
    21  -import io.onedev.server.security.CipherUtils;
    22  -import io.onedev.server.ssh.SshKeyUtils;
    23 17  import io.onedev.server.util.Path;
    24 18  import io.onedev.server.util.PathNode;
    25 19  import io.onedev.server.web.editable.BeanContext;
    skipped 32 lines
    58 52  
    59 53   SshKeyManager sshKeyManager = OneDev.getInstance(SshKeyManager.class);
    60 54   SshKey sshKey = (SshKey) editor.getModelObject();
    61  -
    62  - try {
    63  - PublicKey pubEntry = SshKeyUtils.decodeSshPublicKey(sshKey.getContent());
    64  - String fingerPrint = KeyUtils.getFingerPrint(CipherUtils.DIGEST_FORMAT, pubEntry);
    65  - sshKey.setDigest(fingerPrint);
    66  - } catch (IOException | GeneralSecurityException e) {
    67  - throw new RuntimeException(e);
    68  - }
     55 + sshKey.digest();
    69 56  
    70 57   if (sshKeyManager.findByDigest(sshKey.getDigest()) != null) {
    71 58   editor.error(new Path(new PathNode.Named("content")), "This key is already in use");
    skipped 43 lines
  • ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ApiHelpPage.java
    skipped 1 lines
    2 2   
    3 3  import java.lang.reflect.Method;
    4 4   
     5 +import javax.annotation.Nullable;
     6 +import javax.ws.rs.GET;
     7 +import javax.ws.rs.POST;
     8 +import javax.ws.rs.PUT;
     9 + 
    5 10  import org.apache.commons.lang.StringUtils;
    6 11  import org.apache.wicket.Component;
    7 12  import org.apache.wicket.markup.head.CssHeaderItem;
    skipped 23 lines
    31 36   }
    32 37  
    33 38   protected String getResourceTitle(Class<?> resourceClass) {
    34  - return StringUtils.capitalize(
     39 + return WordUtils.capitalize(
    35 40   WordUtils.uncamel(
    36 41   StringUtils.substringBeforeLast(
    37  - resourceClass.getSimpleName(), "Resource")).toLowerCase());
     42 + resourceClass.getSimpleName(), "Resource")));
    38 43   }
    39 44  
     45 + @Nullable
    40 46   protected String getResourceDescription(Class<?> resourceClass) {
     47 + String description = "";
    41 48   Api api = resourceClass.getAnnotation(Api.class);
    42 49   if (api != null && api.description().length() != 0)
    43  - return api.description();
     50 + description = api.description();
     51 + if (description.length() != 0)
     52 + return description;
    44 53   else
    45  - return getResourceTitle(resourceClass);
     54 + return null;
    46 55   }
    47 56  
    48 57   protected String getMethodTitle(Method resourceMethod) {
    49  - return StringUtils.capitalize(WordUtils.uncamel(resourceMethod.getName()).toLowerCase());
     58 + return WordUtils.capitalize(WordUtils.uncamel(resourceMethod.getName()));
    50 59   }
    51 60   
     61 + @Nullable
    52 62   protected String getMethodDescription(Method resourceMethod) {
     63 + String description = "";
    53 64   Api api = resourceMethod.getAnnotation(Api.class);
    54 65   if (api != null && api.description().length() != 0)
    55  - return api.description();
     66 + description = api.description();
     67 + if (description.length() != 0)
     68 + return description;
     69 + else
     70 + return null;
     71 + }
     72 +
     73 + protected String getHttpMethod(Method resourceMethod) {
     74 + if (resourceMethod.getAnnotation(GET.class) != null)
     75 + return "GET";
     76 + else if (resourceMethod.getAnnotation(POST.class) != null)
     77 + return "POST";
     78 + else if (resourceMethod.getAnnotation(PUT.class) != null)
     79 + return "PUT";
    56 80   else
    57  - return getMethodTitle(resourceMethod);
     81 + return "DELETE";
    58 82   }
    59 83   
    60 84   @Override
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ApiHelpUtils.java
    1 1  package io.onedev.server.web.page.help;
    2 2   
     3 +import java.io.Serializable;
    3 4  import java.lang.reflect.Field;
    4 5  import java.lang.reflect.Method;
    5 6  import java.lang.reflect.Modifier;
    6 7  import java.lang.reflect.Type;
    7 8  import java.util.ArrayList;
    8 9  import java.util.Collection;
     10 +import java.util.Comparator;
    9 11  import java.util.Date;
    10 12  import java.util.EnumSet;
    11 13  import java.util.HashMap;
    skipped 22 lines
    34 36   
    35 37  public class ApiHelpUtils {
    36 38   
    37  - public static Object getExampleValue(Type valueType) {
     39 + public static Serializable getExampleValue(Type valueType) {
    38 40   return getExampleValue(valueType, Sets.newHashSet());
    39 41   }
    40 42  
    41 43   @SuppressWarnings({ "unchecked", "rawtypes" })
    42  - public static Object getExampleValue(Type valueType, Set<Class<?>> parsedTypes) {
     44 + public static Serializable getExampleValue(Type valueType, Set<Class<?>> parsedTypes) {
    43 45   Class<?> valueClass = ReflectionUtils.getClass(valueType);
    44 46   Object value = new ExampleProvider(valueClass, valueClass.getAnnotation(Api.class)).getExample();
    45 47   if (value == null) {
    skipped 26 lines
    72 74   }
    73 75   }
    74 76   collection.add(getExampleValue(collectionElementType, parsedTypes));
    75  - return collection;
     77 + return (Serializable) collection;
    76 78   } else if (Map.class.isAssignableFrom(valueClass)) {
    77 79   Type mapKeyType = ReflectionUtils.getMapKeyType(valueType);
    78 80   if (mapKeyType == null)
    skipped 15 lines
    94 96  
    95 97   map.put(getExampleValue(mapKeyType, parsedTypes), getExampleValue(mapValueType, parsedTypes));
    96 98  
    97  - return map;
     99 + return (Serializable) map;
    98 100   } else {
    99 101   try {
    100 102   if (parsedTypes.add(valueClass)) {
    101 103   Class<?> instantiationClass;
    102 104   if (Modifier.isAbstract(valueClass.getModifiers())) {
    103  - instantiationClass = OneDev.getInstance(ImplementationRegistry.class)
    104  - .getImplementations(valueClass).iterator().next();
     105 + List<Class<?>> implementations = new ArrayList<>(
     106 + OneDev.getInstance(ImplementationRegistry.class).getImplementations(valueClass));
     107 + Collections.sort(implementations, new Comparator<Class<?>>() {
     108 + 
     109 + @Override
     110 + public int compare(Class<?> o1, Class<?> o2) {
     111 + return o1.getName().compareTo(o2.getName());
     112 + }
     113 +
     114 + });
     115 + instantiationClass = implementations.iterator().next();
    105 116   } else {
    106 117   instantiationClass = valueClass;
    107 118   }
    skipped 22 lines
    130 141   }
    131 142   }
    132 143   }
    133  - return value;
     144 + return (Serializable) value;
    134 145   }
    135 146  
    136 147   public static List<Field> getJsonFields(Class<?> beanClass) {
    skipped 26 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ExampleProvider.java
    1 1  package io.onedev.server.web.page.help;
    2 2   
     3 +import java.io.Serializable;
     4 + 
    3 5  import javax.annotation.Nullable;
    4 6   
    5 7  import com.google.common.base.Preconditions;
    skipped 13 lines
    19 21   }
    20 22  
    21 23   @Nullable
    22  - public Object getExample() {
    23  - if (api != null && api.exampleProvider().length() != 0) {
    24  - return Preconditions.checkNotNull(ReflectionUtils.invokeStaticMethod(
    25  - apiDeclaringClass, api.exampleProvider()));
    26  - } else {
    27  - return null;
     24 + public Serializable getExample() {
     25 + if (api != null) {
     26 + if (api.example().length() != 0) {
     27 + return api.example();
     28 + } else if (api.exampleProvider().length() != 0) {
     29 + return (Serializable) Preconditions.checkNotNull(ReflectionUtils.invokeStaticMethod(
     30 + apiDeclaringClass, api.exampleProvider()));
     31 + }
    28 32   }
     33 + return null;
    29 34   }
    30 35  }
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ExampleValueChanged.java
     1 +package io.onedev.server.web.page.help;
     2 + 
     3 +import org.apache.wicket.ajax.AjaxRequestTarget;
     4 + 
     5 +import io.onedev.server.web.util.AjaxPayload;
     6 + 
     7 +public class ExampleValueChanged extends AjaxPayload {
     8 + 
     9 + public ExampleValueChanged(AjaxRequestTarget target) {
     10 + super(target);
     11 + }
     12 + 
     13 +}
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ExampleValuePanel.html
    skipped 35 lines
    36 36   </div>
    37 37   </wicket:fragment>
    38 38  
    39  - <wicket:fragment wicket:id="idOfCurrentEntityHintFrag">
    40  - Internal id of current object
     39 + <wicket:fragment wicket:id="idAsResultOfEntityCreateOrUpdateHintFrag">
     40 + Id of created/updated <a wicket:id="entity"><span wicket:id="label"></span></a>
    41 41   </wicket:fragment>
    42 42  
    43  - <wicket:fragment wicket:id="idOfCurrentEntityInRequestBodyHintFrag">
    44  - Update object with this id. Remove this property or set this property as <i>null</i> if you want to create new object
     43 + <wicket:fragment wicket:id="idAsResultOfEntityCreateHintFrag">
     44 + Id of created <a wicket:id="entity"><span wicket:id="label"></span></a>
    45 45   </wicket:fragment>
    46 46  
    47 47   <wicket:fragment wicket:id="entityIdHintFrag">
    48  - Internal id of <a wicket:id="entity"><span wicket:id="label"></span></a>
     48 + Id of <a wicket:id="entity"><span wicket:id="label"></span></a>
    49 49   </wicket:fragment>
    50 50  
    51 51   <wicket:fragment wicket:id="enumHintFrag">
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ExampleValuePanel.java
    1 1  package io.onedev.server.web.page.help;
    2 2   
     3 +import java.io.Serializable;
    3 4  import java.lang.reflect.Field;
    4 5  import java.lang.reflect.Method;
    5 6  import java.lang.reflect.Modifier;
    6 7  import java.lang.reflect.Type;
    7 8  import java.util.ArrayList;
    8 9  import java.util.Collection;
     10 +import java.util.Comparator;
    9 11  import java.util.Date;
    10 12  import java.util.EnumSet;
    11 13  import java.util.HashMap;
    12 14  import java.util.List;
    13 15  import java.util.Map;
     16 +import java.util.regex.Pattern;
    14 17   
    15 18  import javax.annotation.Nullable;
    16  -import javax.persistence.Id;
    17 19  import javax.persistence.ManyToOne;
     20 +import javax.ws.rs.GET;
    18 21  import javax.ws.rs.Path;
    19 22   
    20 23  import org.apache.wicket.Component;
    21 24  import org.apache.wicket.ajax.AjaxRequestTarget;
    22 25  import org.apache.wicket.ajax.markup.html.AjaxLink;
     26 +import org.apache.wicket.event.Broadcast;
    23 27  import org.apache.wicket.markup.html.WebMarkupContainer;
    24 28  import org.apache.wicket.markup.html.basic.Label;
    25 29  import org.apache.wicket.markup.html.link.Link;
    skipped 4 lines
    30 34  import org.apache.wicket.model.AbstractReadOnlyModel;
    31 35  import org.apache.wicket.model.IModel;
    32 36  import org.apache.wicket.model.LoadableDetachableModel;
     37 +import org.apache.wicket.model.Model;
    33 38  import org.glassfish.jersey.server.ResourceConfig;
    34 39   
    35 40  import com.fasterxml.jackson.annotation.JsonTypeInfo;
    36 41  import com.fasterxml.jackson.core.JsonProcessingException;
    37 42  import com.fasterxml.jackson.databind.ObjectMapper;
    38 43   
     44 +import edu.emory.mathcs.backport.java.util.Collections;
    39 45  import io.onedev.commons.launcher.loader.ImplementationRegistry;
    40 46  import io.onedev.commons.utils.StringUtils;
    41 47  import io.onedev.commons.utils.WordUtils;
    42 48  import io.onedev.server.OneDev;
    43 49  import io.onedev.server.model.AbstractEntity;
    44 50  import io.onedev.server.rest.annotation.Api;
     51 +import io.onedev.server.rest.annotation.EntityCreate;
    45 52  import io.onedev.server.rest.annotation.EntityId;
    46 53  import io.onedev.server.util.ReflectionUtils;
    47 54  import io.onedev.server.web.component.floating.FloatingPanel;
    skipped 7 lines
    55 62   
    56 63   private static final Map<Class<?>, Class<?>> resourceMap = new HashMap<>();
    57 64  
     65 + private static final Pattern GET_ENTITY_PATH = Pattern.compile("\\/\\{[a-zA-Z_\\-0-9]+\\}");
     66 +
    58 67   static {
    59 68   ResourceConfig config = OneDev.getInstance(ResourceConfig.class);
    60 69  
    skipped 1 lines
    62 71   if (clazz.getAnnotation(Path.class) != null) {
    63 72   Api api = clazz.getAnnotation(Api.class);
    64 73   if (api == null || !api.exclude()) {
    65  - try {
    66  - Method method = clazz.getMethod("get", Long.class);
    67  - if (AbstractEntity.class.isAssignableFrom(method.getReturnType()))
     74 + for (Method method: clazz.getMethods()) {
     75 + if (AbstractEntity.class.isAssignableFrom(method.getReturnType())
     76 + && method.getAnnotation(GET.class) != null
     77 + && method.getAnnotation(Path.class) != null
     78 + && GET_ENTITY_PATH.matcher(method.getAnnotation(Path.class).value()).matches()) {
    68 79   resourceMap.put(method.getReturnType(), clazz);
    69  - } catch (NoSuchMethodException | SecurityException e) {
     80 + }
    70 81   }
    71 82   }
    72 83   }
    73 84   }
    74 85   }
    75 86  
    76  - private final Object value;
     87 + private final IModel<Serializable> valueModel;
    77 88  
    78 89   private final IModel<ValueInfo> valueInfoModel;
    79 90  
    80  - private final boolean requestBody;
    81  -
    82  - public ExampleValuePanel(String id, Object value, boolean requestBody) {
    83  - this(id, value, newNullValueModel(), requestBody);
    84  - }
     91 + private final Class<?> requestBodyClass;
    85 92  
    86  - public ExampleValuePanel(String id, Object value, IModel<ValueInfo> valueInfoModel, boolean requestBody) {
     93 + public ExampleValuePanel(String id, IModel<Serializable> valueModel, IModel<ValueInfo> valueInfoModel,
     94 + @Nullable Class<?> requestBodyClass) {
    87 95   super(id);
    88  - this.value = value;
     96 + this.valueModel = valueModel;
    89 97   this.valueInfoModel = valueInfoModel;
    90  - this.requestBody = requestBody;
     98 + this.requestBodyClass = requestBodyClass;
    91 99   }
    92 100   
    93 101   @Override
    94 102   protected void onDetach() {
     103 + valueModel.detach();
    95 104   valueInfoModel.detach();
    96 105   super.onDetach();
    97 106   }
    98 107  
     108 + @Nullable
     109 + private Serializable getValue() {
     110 + return valueModel.getObject();
     111 + }
     112 + 
    99 113   @Override
    100 114   protected void onInitialize() {
    101 115   super.onInitialize();
     116 + setOutputMarkupId(true);
     117 + }
    102 118  
    103  - if (value == null || value.getClass() == String.class
    104  - || value.getClass() == boolean.class || value.getClass() == Boolean.class
    105  - || value.getClass() == int.class || value.getClass() == Integer.class
    106  - || value.getClass() == long.class || value.getClass() == Long.class
    107  - || value.getClass() == Date.class || value instanceof Enum) {
    108  - add(newScalarFragment(value));
     119 + @Override
     120 + protected void onBeforeRender() {
     121 + if (getValue() == null || getValue().getClass() == String.class
     122 + || getValue().getClass() == boolean.class || getValue().getClass() == Boolean.class
     123 + || getValue().getClass() == int.class || getValue().getClass() == Integer.class
     124 + || getValue().getClass() == long.class || getValue().getClass() == Long.class
     125 + || getValue().getClass() == Date.class || getValue() instanceof Enum) {
     126 + addOrReplace(newScalarFragment(getValue()));
    109 127   } else if (getField() != null && getField().getAnnotation(ManyToOne.class) != null) {
    110  - add(newScalarFragment(((AbstractEntity)value).getId()));
    111  - } else if (value instanceof Collection) {
    112  - add(newArrayFragment());
    113  - } else if (value instanceof Map) {
    114  - add(newMapFragment());
     128 + addOrReplace(newScalarFragment(((AbstractEntity)getValue()).getId()));
     129 + } else if (getValue() instanceof Collection) {
     130 + addOrReplace(newArrayFragment());
     131 + } else if (getValue() instanceof Map) {
     132 + addOrReplace(newMapFragment());
    115 133   } else {
    116  - add(newObjectFragment());
     134 + addOrReplace(newObjectFragment());
    117 135   }
    118 136  
    119 137   if (newValueHint("psuedoId") != null) {
    120  - add(new DropdownLink("hint") {
     138 + addOrReplace(new DropdownLink("hint") {
    121 139  
    122 140   @Override
    123 141   protected Component newContent(String id, FloatingPanel dropdown) {
    skipped 2 lines
    126 144  
    127 145   });
    128 146   } else {
    129  - add(new WebMarkupContainer("hint").setVisible(false));
     147 + addOrReplace(new WebMarkupContainer("hint").setVisible(false));
    130 148   }
    131 149  
    132  - setOutputMarkupId(true);
     150 + super.onBeforeRender();
    133 151   }
    134 152   
    135 153   @SuppressWarnings({ "unchecked", "rawtypes" })
    skipped 3 lines
    139 157  
    140 158   boolean hasHint = false;
    141 159  
    142  - if (value instanceof Date) {
     160 + if (getValue() instanceof Date) {
    143 161   fragment.add(new Fragment("typeHint", "dateHintFrag", this));
    144 162   hasHint = true;
    145  - } else if (value instanceof Enum && getDeclaredClass() != null) {
     163 + } else if (getValue() instanceof Enum && getDeclaredClass() != null) {
    146 164   Fragment typeHintFrag = new Fragment("typeHint", "enumHintFrag", this);
    147 165   List<String> possibleValues = new ArrayList<>();
    148 166   for (Object each: EnumSet.allOf((Class<Enum>) getDeclaredClass()))
    skipped 3 lines
    152 170   fragment.add(typeHintFrag);
    153 171   hasHint = true;
    154 172   } else if (getField() != null) {
    155  - if (getField().getAnnotation(ManyToOne.class) != null
    156  - || getField().getAnnotation(EntityId.class) != null) {
     173 + if (getField().getAnnotation(ManyToOne.class) != null || getField().getAnnotation(EntityId.class) != null) {
    157 174   Class<?> entityClass;
    158  - if (value instanceof AbstractEntity)
    159  - entityClass = value.getClass();
     175 + if (getValue() instanceof AbstractEntity)
     176 + entityClass = getValue().getClass();
    160 177   else
    161 178   entityClass = getField().getAnnotation(EntityId.class).value();
    162 179   Class<?> resourceClass = resourceMap.get(entityClass);
    skipped 1 lines
    164 181   Fragment typeHintFrag = new Fragment("typeHint", "entityIdHintFrag", this);
    165 182   Link<Void> link = new ViewStateAwarePageLink<Void>("entity", ResourceDetailPage.class,
    166 183   ResourceDetailPage.paramsOf(resourceClass));
    167  - link.add(new Label("label", WordUtils.uncamel(resourceClass.getSimpleName()).toLowerCase()));
     184 + link.add(new Label("label", WordUtils.uncamel(entityClass.getSimpleName()).toLowerCase()));
    168 185   typeHintFrag.add(link);
    169 186   fragment.add(typeHintFrag);
    170 187   hasHint = true;
    171 188   } else {
    172 189   fragment.add(new WebMarkupContainer("typeHint").setVisible(false));
    173 190   }
    174  - } else if (getField().getAnnotation(Id.class) != null) {
    175  - if (requestBody) {
    176  - fragment.add(new Fragment("typeHint", "idOfCurrentEntityInRequestBodyHintFrag", this));
     191 + } else {
     192 + fragment.add(new WebMarkupContainer("typeHint").setVisible(false));
     193 + }
     194 + } else if (getValue() instanceof Long
     195 + && getValueOrigin() == ValueInfo.Origin.RESPONSE_BODY
     196 + && requestBodyClass != null) {
     197 + if (AbstractEntity.class.isAssignableFrom(requestBodyClass)) {
     198 + Class<?> resourceClass = resourceMap.get(requestBodyClass);
     199 + if (resourceClass != null) {
     200 + Fragment typeHintFrag = new Fragment("typeHint", "idAsResultOfEntityCreateOrUpdateHintFrag", this);
     201 +
     202 + Link<Void> link = new ViewStateAwarePageLink<Void>("entity", ResourceDetailPage.class,
     203 + ResourceDetailPage.paramsOf(resourceClass));
     204 + String entityName = WordUtils.uncamel(requestBodyClass.getSimpleName()).toLowerCase();
     205 + link.add(new Label("label", entityName));
     206 + typeHintFrag.add(link);
     207 + fragment.add(typeHintFrag);
    177 208   hasHint = true;
    178 209   } else {
    179  - fragment.add(new Fragment("typeHint", "idOfCurrentEntityHintFrag", this));
     210 + fragment.add(new WebMarkupContainer("typeHint").setVisible(false));
     211 + }
     212 + } else if (requestBodyClass.getAnnotation(EntityCreate.class) != null) {
     213 + Class<?> entityClass = requestBodyClass.getAnnotation(EntityCreate.class).value();
     214 + Class<?> resourceClass = resourceMap.get(entityClass);
     215 + if (resourceClass != null) {
     216 + Fragment typeHintFrag = new Fragment("typeHint", "idAsResultOfEntityCreateHintFrag", this);
     217 + Link<Void> link = new ViewStateAwarePageLink<Void>("entity", ResourceDetailPage.class,
     218 + ResourceDetailPage.paramsOf(resourceClass));
     219 + String entityName = WordUtils.uncamel(entityClass.getSimpleName()).toLowerCase();
     220 + link.add(new Label("label", entityName));
     221 + typeHintFrag.add(link);
     222 + fragment.add(typeHintFrag);
    180 223   hasHint = true;
     224 + } else {
     225 + fragment.add(new WebMarkupContainer("typeHint").setVisible(false));
    181 226   }
    182 227   } else {
    183 228   fragment.add(new WebMarkupContainer("typeHint").setVisible(false));
    skipped 5 lines
    189 234   String description = "";
    190 235   if (getField() != null && getField().getAnnotation(Api.class) != null)
    191 236   description = getField().getAnnotation(Api.class).description();
    192  - if (description.length() == 0 && value != null
    193  - && value.getClass().getAnnotation(Api.class) != null) {
    194  - description = value.getClass().getAnnotation(Api.class).description();
     237 + if (description.length() == 0 && getValue() != null
     238 + && getValue().getClass().getAnnotation(Api.class) != null) {
     239 + description = getValue().getClass().getAnnotation(Api.class).description();
    195 240   }
    196 241   if (description.length() != 0) {
    197 242   hasHint = true;
    skipped 8 lines
    206 251   return null;
    207 252   }
    208 253  
    209  - private Fragment newScalarFragment(Object value) {
     254 + private Fragment newScalarFragment(Serializable value) {
    210 255   Fragment fragment = new Fragment("content", "scalarFrag", this);
    211  - fragment.add(new Label("value", toJson(value)));
     256 + if (getValueOrigin() == ValueInfo.Origin.REQUEST_BODY || getValueOrigin() == ValueInfo.Origin.RESPONSE_BODY)
     257 + fragment.add(new Label("value", toJson(value)));
     258 + else
     259 + fragment.add(new Label("value", String.valueOf(value)));
     260 +
    212 261   return fragment;
    213 262   }
    214 263  
    215  - private static IModel<ValueInfo> newNullValueModel() {
    216  - return new AbstractReadOnlyModel<ValueInfo>() {
     264 + private Fragment newArrayFragment() {
     265 + Fragment fragment = new Fragment("content", "arrayFrag", this);
     266 + fragment.add(new ListView<Serializable>("elements", new AbstractReadOnlyModel<List<Serializable>>() {
    217 267   
     268 + @SuppressWarnings("unchecked")
    218 269   @Override
    219  - public ValueInfo getObject() {
    220  - return null;
     270 + public List<Serializable> getObject() {
     271 + return new ArrayList<Serializable>((Collection<? extends Serializable>) getValue());
    221 272   }
    222 273  
    223  - };
    224  - }
    225  -
    226  - private Fragment newArrayFragment() {
    227  - Fragment fragment = new Fragment("content", "arrayFrag", this);
    228  - fragment.add(new ListView<Object>("elements", new ArrayList<Object>(((Collection<?>)value))) {
     274 + }) {
    229 275   
    230 276   @Override
    231  - protected void populateItem(ListItem<Object> item) {
    232  - IModel<ValueInfo> valueInfoModel;
    233  - if (getValueInfo() != null) {
    234  - valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     277 + protected void populateItem(ListItem<Serializable> item) {
     278 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
    235 279   
    236  - @Override
    237  - protected ValueInfo load() {
    238  - Field field = getField();
    239  - Type declaredType = ReflectionUtils.getCollectionElementType(getDeclaredType());
    240  - return new ValueInfo(field, declaredType);
    241  - }
     280 + @Override
     281 + protected ValueInfo load() {
     282 + Type declaredType = ReflectionUtils.getCollectionElementType(getDeclaredType());
     283 + return new ValueInfo(getValueOrigin(), declaredType, null);
     284 + }
    242 285  
    243  - };
    244  - } else {
    245  - valueInfoModel = newNullValueModel();
    246  - }
    247  - item.add(new ExampleValuePanel("element", item.getModelObject(), valueInfoModel, requestBody));
     286 + };
     287 + 
     288 + item.add(new ExampleValuePanel("element", new IModel<Serializable>() {
     289 + 
     290 + @Override
     291 + public void detach() {
     292 + }
     293 + 
     294 + @Override
     295 + public Serializable getObject() {
     296 + return item.getModelObject();
     297 + }
     298 + 
     299 + @SuppressWarnings({ "unchecked", "rawtypes" })
     300 + @Override
     301 + public void setObject(Serializable object) {
     302 + Collection collection = (Collection)getValue();
     303 + collection.remove(item.getModelObject());
     304 + collection.add(object);
     305 + }
     306 +
     307 + }, valueInfoModel, requestBodyClass));
     308 +
    248 309   item.add(new WebMarkupContainer("comma")
    249 310   .setVisible(item.getIndex() < getModelObject().size()-1));
    250 311   }
    skipped 7 lines
    258 319  
    259 320   fragment.add(new WebMarkupContainer("typeInfo").setVisible(false));
    260 321  
    261  - IModel<List<Map.Entry<?, ?>>> entriesModel = new LoadableDetachableModel<List<Map.Entry<?, ?>>>() {
     322 + IModel<List<Map.Entry<Serializable, Serializable>>> entriesModel =
     323 + new AbstractReadOnlyModel<List<Map.Entry<Serializable, Serializable>>>() {
    262 324   
     325 + @SuppressWarnings("unchecked")
    263 326   @Override
    264  - protected List<Map.Entry<?, ?>> load() {
    265  - return new ArrayList<Map.Entry<?, ?>>(((Map<?, ?>)value).entrySet());
     327 + public List<Map.Entry<Serializable, Serializable>> getObject() {
     328 + return new ArrayList<Map.Entry<Serializable, Serializable>>(((Map<Serializable, Serializable>)getValue()).entrySet());
    266 329   }
    267 330  
    268 331   };
    269 332  
    270  - fragment.add(new ListView<Map.Entry<?, ?>>("properties", entriesModel) {
     333 + fragment.add(new ListView<Map.Entry<Serializable, Serializable>>("properties", entriesModel) {
    271 334   
    272 335   @Override
    273  - protected void populateItem(ListItem<Map.Entry<?, ?>> item) {
    274  - IModel<ValueInfo> valueInfoModel;
    275  - if (getValueInfo() != null) {
    276  - valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     336 + protected void populateItem(ListItem<Map.Entry<Serializable, Serializable>> item) {
     337 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
    277 338   
    278  - @Override
    279  - protected ValueInfo load() {
    280  - Field field = getField();
    281  - Type declaredType = ReflectionUtils.getMapKeyType(getDeclaredType());
    282  - return new ValueInfo(field, declaredType);
    283  - }
     339 + @Override
     340 + protected ValueInfo load() {
     341 + Type declaredType = ReflectionUtils.getMapKeyType(getDeclaredType());
     342 + return new ValueInfo(getValueOrigin(), declaredType, null);
     343 + }
    284 344  
    285  - };
    286  - } else {
    287  - valueInfoModel = newNullValueModel();
    288  - }
    289  - item.add(new ExampleValuePanel("name", item.getModelObject().getKey(), valueInfoModel, requestBody));
     345 + };
     346 + item.add(new ExampleValuePanel("name", Model.of(item.getModelObject().getKey()), valueInfoModel, requestBodyClass));
    290 347  
    291  - if (getValueInfo() != null) {
    292  - valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     348 + valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
    293 349   
    294  - @Override
    295  - protected ValueInfo load() {
    296  - Field field = getField();
    297  - Type declaredType = ReflectionUtils.getMapValueType(getDeclaredType());
    298  - return new ValueInfo(field, declaredType);
    299  - }
     350 + @Override
     351 + protected ValueInfo load() {
     352 + Type declaredType = ReflectionUtils.getMapValueType(getDeclaredType());
     353 + return new ValueInfo(getValueOrigin(), declaredType, null);
     354 + }
    300 355  
    301  - };
    302  - } else {
    303  - valueInfoModel = newNullValueModel();
    304  - }
    305  - item.add(new ExampleValuePanel("value", item.getModelObject().getValue(), valueInfoModel, requestBody));
     356 + };
     357 + item.add(new ExampleValuePanel("value", new IModel<Serializable>() {
     358 + 
     359 + @Override
     360 + public void detach() {
     361 + }
     362 + 
     363 + @Override
     364 + public Serializable getObject() {
     365 + return item.getModelObject().getValue();
     366 + }
     367 + 
     368 + @SuppressWarnings("unchecked")
     369 + @Override
     370 + public void setObject(Serializable object) {
     371 + Map<Serializable, Serializable> map = (Map<Serializable, Serializable>)getValue();
     372 + map.put(item.getModelObject().getKey(), object);
     373 + }
     374 +
     375 + }, valueInfoModel, requestBodyClass));
     376 +
    306 377   item.add(new WebMarkupContainer("comma")
    307 378   .setVisible(item.getIndex() < getModelObject().size()-1));
    308 379   }
    skipped 9 lines
    318 389   
    319 390   @Override
    320 391   protected List<Field> load() {
    321  - return ApiHelpUtils.getJsonFields(value.getClass());
     392 + return ApiHelpUtils.getJsonFields(getValue().getClass());
    322 393   }
    323 394  
    324 395   };
    skipped 3 lines
    328 399   && !Collection.class.isAssignableFrom(getDeclaredClass())
    329 400   && !Map.class.isAssignableFrom(getDeclaredClass())) {
    330 401   Fragment typeInfoFragment = new Fragment("typeInfo", "typeInfoFrag", this);
    331  - typeInfoFragment.add(new ExampleValuePanel("name", JsonTypeInfo.Id.CLASS.getDefaultPropertyName(), requestBody));
    332  - typeInfoFragment.add(new ExampleValuePanel("value", value.getClass().getName(), requestBody));
     402 +
     403 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     404 + 
     405 + @Override
     406 + protected ValueInfo load() {
     407 + return new ValueInfo(getValueOrigin(), String.class, null);
     408 + }
     409 +
     410 + };
     411 + typeInfoFragment.add(new ExampleValuePanel("name", Model.of(JsonTypeInfo.Id.CLASS.getDefaultPropertyName()),
     412 + valueInfoModel, requestBodyClass));
     413 + typeInfoFragment.add(new ExampleValuePanel("value", new AbstractReadOnlyModel<Serializable>() {
     414 + 
     415 + @Override
     416 + public Serializable getObject() {
     417 + return getValue().getClass().getName();
     418 + }
     419 +
     420 + }, valueInfoModel, requestBodyClass));
    333 421  
    334 422   typeInfoFragment.add(new MenuLink("implementations") {
    335 423   
    336 424   @Override
    337 425   protected List<MenuItem> getMenuItems(FloatingPanel dropdown) {
    338 426   List<MenuItem> items = new ArrayList<>();
    339  - ImplementationRegistry registry = OneDev.getInstance(ImplementationRegistry.class);
    340  - for (Class<?> clazz: registry.getImplementations(getDeclaredClass())) {
     427 + List<Class<?>> implementations = new ArrayList<>(
     428 + OneDev.getInstance(ImplementationRegistry.class).getImplementations(getDeclaredClass()));
     429 + Collections.sort(implementations, new Comparator<Class<?>>() {
     430 + 
     431 + @Override
     432 + public int compare(Class<?> o1, Class<?> o2) {
     433 + return o1.getName().compareTo(o2.getName());
     434 + }
     435 +
     436 + });
     437 +
     438 + for (Class<?> clazz: implementations) {
    341 439   items.add(new MenuItem() {
    342 440   
    343 441   @Override
    skipped 7 lines
    351 449   
    352 450   @Override
    353 451   public void onClick(AjaxRequestTarget target) {
    354  - Object newValue = ApiHelpUtils.getExampleValue(clazz);
    355  - Component newExampleValuePanel = new ExampleValuePanel(
    356  - ExampleValuePanel.this.getId(), newValue, valueInfoModel, requestBody);
    357  - ExampleValuePanel.this.replaceWith(newExampleValuePanel);
    358  - target.add(newExampleValuePanel);
     452 + Serializable newValue = ApiHelpUtils.getExampleValue(clazz);
     453 + valueModel.setObject(newValue);
     454 + target.add(ExampleValuePanel.this);
     455 + send(getPage(), Broadcast.BREADTH, new ExampleValueChanged(target));
    359 456   }
    360 457  
    361 458   };
    skipped 18 lines
    380 477   @Override
    381 478   protected void populateItem(ListItem<Field> item) {
    382 479   Field field = item.getModelObject();
     480 +
     481 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     482 + 
     483 + @Override
     484 + protected ValueInfo load() {
     485 + return new ValueInfo(getValueOrigin(), String.class, null);
     486 + }
     487 +
     488 + };
    383 489   if (field.getAnnotation(ManyToOne.class) != null)
    384  - item.add(new ExampleValuePanel("name", field.getName() + "Id", requestBody));
     490 + item.add(new ExampleValuePanel("name", Model.of(field.getName() + "Id"), valueInfoModel, requestBodyClass));
    385 491   else
    386  - item.add(new ExampleValuePanel("name", field.getName(), requestBody));
     492 + item.add(new ExampleValuePanel("name", Model.of(field.getName()), valueInfoModel, requestBodyClass));
    387 493  
    388 494   field.setAccessible(true);
    389  - try {
    390  - IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     495 + valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
    391 496   
    392  - @Override
    393  - protected ValueInfo load() {
     497 + @Override
     498 + protected ValueInfo load() {
     499 + Field field = item.getModelObject();
     500 + return new ValueInfo(getValueOrigin(), field.getGenericType(), field);
     501 + }
     502 +
     503 + };
     504 + item.add(new ExampleValuePanel("value", new IModel<Serializable>() {
     505 + 
     506 + @Override
     507 + public void detach() {
     508 + }
     509 + 
     510 + @Override
     511 + public Serializable getObject() {
     512 + try {
     513 + Field field = item.getModelObject();
     514 + field.setAccessible(true);
     515 + return (Serializable) field.get(getValue());
     516 + } catch (IllegalArgumentException | IllegalAccessException e) {
     517 + throw new RuntimeException(e);
     518 + }
     519 + }
     520 + 
     521 + @Override
     522 + public void setObject(Serializable object) {
     523 + try {
    394 524   Field field = item.getModelObject();
    395  - return new ValueInfo(field, field.getGenericType());
     525 + field.setAccessible(true);
     526 + field.set(getValue(), object);
     527 + } catch (IllegalArgumentException | IllegalAccessException e) {
     528 + throw new RuntimeException(e);
    396 529   }
     530 + }
    397 531  
    398  - };
    399  - item.add(new ExampleValuePanel("value", field.get(value), valueInfoModel, requestBody));
    400  - } catch (IllegalArgumentException | IllegalAccessException e) {
    401  - throw new RuntimeException(e);
    402  - }
     532 + }, valueInfoModel, requestBodyClass));
     533 +
    403 534   item.add(new WebMarkupContainer("comma")
    404 535   .setVisible(item.getIndex() < getModelObject().size()-1));
    405 536   }
    skipped 2 lines
    408 539   return fragment;
    409 540   }
    410 541   
    411  - @Nullable
    412 542   private ValueInfo getValueInfo() {
    413 543   return valueInfoModel.getObject();
    414 544   }
    415 545  
    416 546   @Nullable
    417 547   private Field getField() {
    418  - return getValueInfo()!=null? getValueInfo().getField(): null;
     548 + return getValueInfo().getField();
    419 549   }
    420 550   
    421  - @Nullable
    422 551   private Type getDeclaredType() {
    423  - return getValueInfo()!=null? getValueInfo().getDeclaredType(): null;
     552 + return getValueInfo().getDeclaredType();
    424 553   }
    425 554  
    426  - @Nullable
    427 555   private Class<?> getDeclaredClass() {
    428  - if (getDeclaredType() != null)
    429  - return ReflectionUtils.getClass(getDeclaredType());
    430  - else
    431  - return null;
     556 + return ReflectionUtils.getClass(getDeclaredType());
     557 + }
     558 +
     559 + private ValueInfo.Origin getValueOrigin() {
     560 + return getValueInfo().getOrigin();
    432 561   }
    433 562  
    434 563   private String toJson(Object value) {
    skipped 4 lines
    439 568   }
    440 569   }
    441 570  
    442  - private static class ValueInfo {
    443  -
    444  - private final Field field;
    445  -
    446  - private final Type declaredType;
    447  -
    448  - public ValueInfo(Field field, Type declaredType) {
    449  - this.field = field;
    450  - this.declaredType = declaredType;
    451  - }
    452  - 
    453  - public Field getField() {
    454  - return field;
    455  - }
    456  - 
    457  - public Type getDeclaredType() {
    458  - return declaredType;
    459  - }
    460  -
    461  - }
    462 571  }
    463 572   
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/MethodDetailPage.html
    1 1  <wicket:extend>
    2  - <h5 wicket:id="title" class="mb-4"></h5>
    3  - <div class="method-detail">
     2 + <div wicket:id="title" class="title"></div>
     3 + <div wicket:id="description" class="alert alert-notice alert-light mb-4"></div>
     4 + <div class="method-detail border-top pt-3">
    4 5   <dl class="content">
    5  - <dt>Method</dt>
     6 + <dt>Http Method</dt>
    6 7   <dd wicket:id="method"></dd>
    7  - <dt>Path</dt>
    8  - <dd wicket:id="path"></dd>
    9  - <wicket:enclosure child="pathPlaceholders">
    10  - <dt>Path Placeholders</dt>
     8 + <dt>End Point</dt>
    11 9   <dd>
    12  - <dl wicket:id="pathPlaceholders">
    13  - <dt wicket:id="name"></dt>
    14  - <dd>
    15  - <dl>
    16  - <dt>Description</dt>
    17  - <dd wicket:id="description"></dd>
    18  - <dt>Example</dt>
    19  - <dd wicket:id="example"></dd>
    20  - </dl>
    21  - </dd>
    22  - </dl>
     10 + <div wicket:id="path"></div>
     11 + <wicket:enclosure child="pathPlaceholders">
     12 + <table class="table mt-4">
     13 + <thead>
     14 + <tr>
     15 + <td>Placeholder</td>
     16 + <td>Description</td>
     17 + <td>Example</td>
     18 + </tr>
     19 + </thead>
     20 + <tbody>
     21 + <tr wicket:id="pathPlaceholders">
     22 + <td wicket:id="name"></td>
     23 + <td wicket:id="description"></td>
     24 + <td wicket:id="example"></td>
     25 + </tr>
     26 + </tbody>
     27 + </table>
     28 + </wicket:enclosure>
    23 29   </dd>
    24  - </wicket:enclosure>
    25 30   <wicket:enclosure child="queryParams">
    26 31   <dt>Query Parameters</dt>
    27 32   <dd>
    28  - <dl wicket:id="queryParams">
    29  - <dt wicket:id="name"></dt>
    30  - <dd>
    31  - <dl>
    32  - <dt>Description</dt>
    33  - <dd wicket:id="description"></dd>
    34  - <dt>Required</dt>
    35  - <dd wicket:id="required"></dd>
    36  - <dt>Example</dt>
    37  - <dd wicket:id="example"></dd>
    38  - </dl>
    39  - </dd>
    40  - </dl>
     33 + <table class="table">
     34 + <thead>
     35 + <tr>
     36 + <td>Parameter</td>
     37 + <td>Description</td>
     38 + <td>Required</td>
     39 + <td>Example</td>
     40 + </tr>
     41 + </thead>
     42 + <tbody>
     43 + <tr wicket:id="queryParams">
     44 + <td wicket:id="name"></td>
     45 + <td wicket:id="description"></td>
     46 + <td wicket:id="required"></td>
     47 + <td wicket:id="example"></td>
     48 + </tr>
     49 + </tbody>
     50 + </table>
    41 51   </dd>
    42 52   </wicket:enclosure>
    43 53   <wicket:enclosure child="requestBodyExample">
    skipped 2 lines
    46 56   <dl>
    47 57   <dt>Content Type</dt>
    48 58   <dd>application/json</dd>
    49  - <dt>Example</dt>
     59 + <dt>Example <a wicket:id="copyRequestBodyExample" class="ml-1" title="Copy to clipboard"><wicket:svg href="copy" class="icon icon-sm"/></a></dt>
    50 60   <dd wicket:id="requestBodyExample"></dd>
    51 61   </dl>
    52 62   </dd>
    53 63   </wicket:enclosure>
    54  - <dt>Required Permission</dt>
    55  - <dd wicket:id="permission"></dd>
    56 64   <dt>Response</dt>
    57 65   <dd>
    58 66   <dl>
    skipped 17 lines
    76 84   </dd>
    77 85   </dl>
    78 86   </dd>
     87 + <dt>cURL Example <a wicket:id="copyCurlExample" class="ml-1" title="Copy to clipboard"><wicket:svg href="copy" class="icon icon-sm"/></a></dt>
     88 + <dd>
     89 + <code wicket:id="curlExample"></code>
     90 + </dd>
    79 91   </dl>
    80 92   </div>
    81 93   <wicket:fragment wicket:id="topbarTitleFrag">
    skipped 7 lines
    89 101   <dl>
    90 102   <dt>Content Type</dt>
    91 103   <dd>application/json</dd>
    92  - <dt>Example</dt>
     104 + <dt>Example <a wicket:id="copyExample" class="ml-1" title="Copy to clipboard"><wicket:svg href="copy" class="icon icon-sm"/></a></dt>
    93 105   <dd wicket:id="example"></dd>
    94 106   </dl>
    95 107   </wicket:fragment>
    skipped 1 lines
  • ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/MethodDetailPage.java
    1 1  package io.onedev.server.web.page.help;
    2 2   
     3 +import java.io.Serializable;
    3 4  import java.lang.reflect.Method;
    4 5  import java.lang.reflect.Parameter;
    5 6  import java.util.ArrayList;
     7 +import java.util.LinkedHashMap;
    6 8  import java.util.List;
     9 +import java.util.Map;
    7 10   
    8  -import javax.ws.rs.GET;
    9  -import javax.ws.rs.POST;
    10  -import javax.ws.rs.PUT;
     11 +import javax.annotation.Nullable;
    11 12  import javax.ws.rs.Path;
    12 13  import javax.ws.rs.PathParam;
    13 14  import javax.ws.rs.QueryParam;
    14 15  import javax.ws.rs.core.Response;
    15 16   
    16 17  import org.apache.wicket.Component;
     18 +import org.apache.wicket.event.IEvent;
    17 19  import org.apache.wicket.markup.html.WebMarkupContainer;
    18 20  import org.apache.wicket.markup.html.basic.Label;
    19 21  import org.apache.wicket.markup.html.link.Link;
    skipped 2 lines
    22 24  import org.apache.wicket.markup.html.panel.Fragment;
    23 25  import org.apache.wicket.model.IModel;
    24 26  import org.apache.wicket.model.LoadableDetachableModel;
     27 +import org.apache.wicket.model.Model;
    25 28  import org.apache.wicket.request.mapper.parameter.PageParameters;
     29 + 
     30 +import com.fasterxml.jackson.core.JsonProcessingException;
     31 +import com.fasterxml.jackson.databind.ObjectMapper;
    26 32   
    27 33  import io.onedev.commons.utils.ExplicitException;
    28 34  import io.onedev.commons.utils.StringUtils;
    29 35  import io.onedev.commons.utils.WordUtils;
     36 +import io.onedev.server.OneDev;
     37 +import io.onedev.server.entitymanager.SettingManager;
    30 38  import io.onedev.server.rest.annotation.Api;
    31 39  import io.onedev.server.rest.jersey.ParamCheckFilter;
    32 40  import io.onedev.server.web.component.link.ViewStateAwarePageLink;
     41 +import io.onedev.server.web.component.link.copytoclipboard.CopyToClipboardLink;
    33 42  import io.onedev.server.web.util.TextUtils;
    34 43   
    35 44  @SuppressWarnings("serial")
    skipped 44 lines
    80 89   return methodModel.getObject();
    81 90   }
    82 91   
     92 + @Nullable
     93 + private Parameter getRequestBodyParam() {
     94 + for (Parameter param: getResourceMethod().getParameters()) {
     95 + if (param.getAnnotation(PathParam.class) == null && param.getAnnotation(QueryParam.class) == null)
     96 + return param;
     97 + }
     98 + return null;
     99 + }
     100 +
    83 101   @Override
    84 102   protected void onInitialize() {
    85 103   super.onInitialize();
    86 104   
    87 105   Method method = getResourceMethod();
    88  - add(new Label("title", getMethodDescription(method)));
     106 +
     107 + add(new Label("title", getMethodTitle(getResourceMethod())));
     108 +
     109 + String description = getMethodDescription(method);
     110 + add(new Label("description", description).setVisible(description!=null));
     111 + 
     112 + String httpMethod = getHttpMethod(method);
     113 + add(new Label("method", httpMethod));
     114 +
     115 + Class<?> requestBodyClass;
     116 +
     117 + Parameter param = getRequestBodyParam();
     118 +
     119 + if (param != null) {
     120 + Serializable exampleValue = new ExampleProvider(resourceClass, param.getAnnotation(Api.class)).getExample();
     121 + if (exampleValue == null)
     122 + exampleValue = ApiHelpUtils.getExampleValue(param.getParameterizedType());
     123 + requestBodyClass = exampleValue.getClass();
     124 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     125 + 
     126 + @Override
     127 + protected ValueInfo load() {
     128 + return new ValueInfo(ValueInfo.Origin.REQUEST_BODY,
     129 + getRequestBodyParam().getParameterizedType(), null);
     130 + }
     131 +
     132 + };
     133 +
     134 + Model<Serializable> valueModel = Model.of(exampleValue);
     135 + add(new ExampleValuePanel("requestBodyExample", valueModel, valueInfoModel, requestBodyClass));
     136 + add(new CopyToClipboardLink("copyRequestBodyExample", new LoadableDetachableModel<String>() {
     137 + 
     138 + @Override
     139 + protected String load() {
     140 + return toJson(valueModel.getObject());
     141 + }
     142 +
     143 + }) {
     144 +
     145 + @Override
     146 + public void onEvent(IEvent<?> event) {
     147 + super.onEvent(event);
     148 + if (event.getPayload() instanceof ExampleValueChanged)
     149 + ((ExampleValueChanged)event.getPayload()).getHandler().add(this);
     150 + }
    89 151  
    90  - if (method.getAnnotation(GET.class) != null)
    91  - add(new Label("method", "GET"));
    92  - else if (method.getAnnotation(POST.class) != null)
    93  - add(new Label("method", "POST"));
    94  - else if (method.getAnnotation(PUT.class) != null)
    95  - add(new Label("method", "PUT"));
    96  - else
    97  - add(new Label("method", "DELETE"));
     152 + }.setOutputMarkupId(true));
     153 + } else {
     154 + requestBodyClass = null;
     155 + add(new WebMarkupContainer("requestBodyExample").setVisible(false));
     156 + }
    98 157  
    99 158   String resourcePathValue = resourceClass.getAnnotation(Path.class).value();
    100 159   Path methodPath = method.getAnnotation(Path.class);
     160 +
     161 + String endPoint;
    101 162   if (methodPath != null)
    102  - add(new Label("path", "/api" + resourcePathValue + methodPath.value()));
     163 + endPoint = "/api" + resourcePathValue + methodPath.value();
    103 164   else
    104  - add(new Label("path", "/api" + resourcePathValue));
     165 + endPoint = "/api" + resourcePathValue;
     166 +
     167 + add(new Label("path", endPoint));
    105 168  
    106 169   add(new ListView<Parameter>("pathPlaceholders", new LoadableDetachableModel<List<Parameter>>() {
    107 170   
    skipped 20 lines
    128 191   else
    129 192   item.add(new Label("description", StringUtils.capitalize(WordUtils.uncamel(pathParam.value()))));
    130 193  
    131  - Object exampleValue = new ExampleProvider(getResourceMethod().getDeclaringClass(), api).getExample();
     194 + Serializable exampleValue = new ExampleProvider(getResourceMethod().getDeclaringClass(), api).getExample();
    132 195   if (exampleValue == null)
    133 196   exampleValue = ApiHelpUtils.getExampleValue(param.getParameterizedType());
    134  - item.add(new ExampleValuePanel("example", exampleValue, false));
     197 +
     198 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     199 + 
     200 + @Override
     201 + protected ValueInfo load() {
     202 + return new ValueInfo(ValueInfo.Origin.PATH_PLACEHOLDER,
     203 + item.getModelObject().getParameterizedType(), null);
     204 + }
     205 +
     206 + };
     207 +
     208 + item.add(new ExampleValuePanel("example", Model.of(exampleValue), valueInfoModel, requestBodyClass));
     209 + }
     210 +
     211 + @Override
     212 + protected void onConfigure() {
     213 + super.onConfigure();
     214 + setVisible(!getModelObject().isEmpty());
    135 215   }
    136 216  
    137 217   });
    skipped 19 lines
    157 237   item.add(new Label("name", queryParam.value()));
    158 238   Api api = param.getAnnotation(Api.class);
    159 239   if (api != null && api.description().length() != 0)
    160  - item.add(new Label("description", api.description()));
     240 + item.add(new Label("description", api.description()).setEscapeModelStrings(false));
    161 241   else
    162  - item.add(new Label("description", StringUtils.capitalize(WordUtils.uncamel(queryParam.value()))));
     242 + item.add(new Label("description", StringUtils.capitalize(WordUtils.uncamel(queryParam.value()).toLowerCase())));
    163 243   item.add(new Label("required", TextUtils.describe(ParamCheckFilter.isRequired(param))));
    164 244   
    165  - Object exampleValue = new ExampleProvider(getResourceMethod().getDeclaringClass(), api).getExample();
     245 + Serializable exampleValue = new ExampleProvider(getResourceMethod().getDeclaringClass(), api).getExample();
    166 246   if (exampleValue == null)
    167 247   exampleValue = ApiHelpUtils.getExampleValue(param.getParameterizedType());
    168  - item.add(new ExampleValuePanel("example", exampleValue, false));
     248 +
     249 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     250 + 
     251 + @Override
     252 + protected ValueInfo load() {
     253 + return new ValueInfo(ValueInfo.Origin.QUERY_PARAM,
     254 + item.getModelObject().getParameterizedType(), null);
     255 + }
     256 +
     257 + };
     258 +
     259 + item.add(new ExampleValuePanel("example", Model.of(exampleValue), valueInfoModel, requestBodyClass));
    169 260   }
    170 261   
    171 262   @Override
    skipped 4 lines
    176 267  
    177 268   });
    178 269  
    179  - Parameter requestBodyParam = null;
    180  - for (Parameter param: method.getParameters()) {
    181  - if (param.getAnnotation(PathParam.class) == null && param.getAnnotation(QueryParam.class) == null) {
    182  - requestBodyParam = param;
    183  - break;
     270 + if (method.getReturnType() == Response.class) {
     271 + add(new Label("successResponseBody", "No response body if successful; error"));
     272 + } else {
     273 + Fragment fragment = new Fragment("successResponseBody", "hasResponseBodyFrag", MethodDetailPage.this);
     274 + Serializable exampleValue = new ExampleProvider(resourceClass, method.getAnnotation(Api.class)).getExample();
     275 + if (exampleValue == null)
     276 + exampleValue = ApiHelpUtils.getExampleValue(method.getGenericReturnType());
     277 +
     278 + IModel<ValueInfo> valueInfoModel = new LoadableDetachableModel<ValueInfo>() {
     279 + 
     280 + @Override
     281 + protected ValueInfo load() {
     282 + return new ValueInfo(ValueInfo.Origin.RESPONSE_BODY,
     283 + getResourceMethod().getGenericReturnType(), null);
     284 + }
     285 +
     286 + };
     287 +
     288 + IModel<Serializable> valueModel = Model.of(exampleValue);
     289 +
     290 + fragment.add(new ExampleValuePanel("example", valueModel, valueInfoModel, requestBodyClass));
     291 +
     292 + fragment.add(new CopyToClipboardLink("copyExample", new LoadableDetachableModel<String>() {
     293 + 
     294 + @Override
     295 + protected String load() {
     296 + return toJson(valueModel.getObject());
     297 + }
     298 +
     299 + }) {
     300 +
     301 + @Override
     302 + public void onEvent(IEvent<?> event) {
     303 + super.onEvent(event);
     304 + if (event.getPayload() instanceof ExampleValueChanged)
     305 + ((ExampleValueChanged)event.getPayload()).getHandler().add(this);
     306 + }
     307 +
     308 + }.setOutputMarkupId(true));
     309 + 
     310 + add(fragment);
     311 + }
     312 + 
     313 + for (Parameter pathParam: getResourceMethod().getParameters()) {
     314 + if (pathParam.getAnnotation(PathParam.class) != null) {
     315 + Api api = pathParam.getAnnotation(Api.class);
     316 + Serializable exampleValue = new ExampleProvider(getResourceMethod().getDeclaringClass(), api).getExample();
     317 + if (exampleValue == null)
     318 + exampleValue = ApiHelpUtils.getExampleValue(pathParam.getParameterizedType());
     319 + endPoint = endPoint.replace("{" + pathParam.getAnnotation(PathParam.class).value() + "}", String.valueOf(exampleValue));
    184 320   }
    185 321   }
    186 322  
    187  - if (requestBodyParam != null) {
    188  - Object exampleValue = new ExampleProvider(resourceClass, requestBodyParam.getAnnotation(Api.class)).getExample();
    189  - if (exampleValue == null) {
    190  - exampleValue = ApiHelpUtils.getExampleValue(requestBodyParam.getParameterizedType());
     323 + Map<String, String> queryParams = new LinkedHashMap<>();
     324 +
     325 + for (Parameter queryParam: getResourceMethod().getParameters()) {
     326 + if (queryParam.getAnnotation(QueryParam.class) != null) {
     327 + String paramName = queryParam.getAnnotation(QueryParam.class).value();
     328 +
     329 + Api api = queryParam.getAnnotation(Api.class);
     330 + Serializable exampleValue = new ExampleProvider(getResourceMethod().getDeclaringClass(), api).getExample();
     331 + if (exampleValue == null)
     332 + exampleValue = ApiHelpUtils.getExampleValue(queryParam.getParameterizedType());
     333 +
     334 + queryParams.put(paramName, String.valueOf(exampleValue));
    191 335   }
    192  - add(new ExampleValuePanel("requestBodyExample", exampleValue, true));
    193  - } else {
    194  - add(new WebMarkupContainer("requestBodyExample").setVisible(false));
     336 + }
     337 +
     338 + StringBuilder curlExample = new StringBuilder();
     339 + 
     340 + curlExample.append("$ curl -u <login name>:<password or access token> ");
     341 + if (!queryParams.isEmpty())
     342 + curlExample.append("-G ");
     343 +
     344 + switch (getHttpMethod(method)) {
     345 + case "DELETE":
     346 + curlExample.append("-X DELETE ");
     347 + break;
     348 + case "PUT":
     349 + curlExample.append("-X PUT -d@request-body.json -H \"Content-Type: application/json\" ");
     350 + break;
     351 + case "POST":
     352 + curlExample.append("-d@request-body.json -H \"Content-Type: application/json\" ");
     353 + break;
    195 354   }
    196 355  
    197  - Api api = method.getAnnotation(Api.class);
    198  - if (api != null && api.permission().length() != 0)
    199  - add(new Label("permission", api.permission()));
    200  - else
    201  - add(new Label("permission", "Anyone can perform this operation"));
     356 + curlExample.append(OneDev.getInstance(SettingManager.class).getSystemSetting().getServerUrl()).append(endPoint);
    202 357  
    203  - if (method.getReturnType() == Response.class) {
    204  - add(new Label("successResponseBody", "No response body if successful; error"));
    205  - } else {
    206  - Fragment fragment = new Fragment("successResponseBody", "hasResponseBodyFrag", MethodDetailPage.this);
    207  - Object exampleValue = new ExampleProvider(resourceClass, method.getAnnotation(Api.class)).getExample();
    208  - if (exampleValue == null)
    209  - exampleValue = ApiHelpUtils.getExampleValue(method.getGenericReturnType());
    210  - fragment.add(new ExampleValuePanel("example", exampleValue, false));
    211  - add(fragment);
     358 + for (Map.Entry<String, String> entry: queryParams.entrySet()) {
     359 + if (entry.getValue().contains(" "))
     360 + curlExample.append(" --data-urlencode '").append(entry.getKey()).append("=").append(entry.getValue()).append("'");
     361 + else
     362 + curlExample.append(" --data-urlencode ").append(entry.getKey()).append("=").append(entry.getValue());
    212 363   }
    213 364  
     365 + add(new Label("curlExample", curlExample));
     366 +
     367 + add(new CopyToClipboardLink("copyCurlExample", Model.of(curlExample.toString().substring(1))));
     368 + }
     369 + 
     370 + private String toJson(Object value) {
     371 + try {
     372 + return OneDev.getInstance(ObjectMapper.class).writeValueAsString(value);
     373 + } catch (JsonProcessingException e) {
     374 + throw new RuntimeException(e);
     375 + }
    214 376   }
    215 377  
    216 378   @Override
    skipped 20 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ResourceDetailPage.html
    1 1  <wicket:extend>
    2  - <h5 wicket:id="title" class="mb-4"></h5>
    3  - <table class="table resource-detail">
    4  - <tr wicket:id="methods" class="method">
    5  - <td>
    6  - <a wicket:id="link" class="title`"><span wicket:id="label"></span></a>
    7  - </td>
    8  - </tr>
     2 + <div wicket:id="title" class="title"></div>
     3 + <div wicket:id="description" class="alert alert-notice alert-light mb-5"></div>
     4 + <table class="table">
     5 + <thead>
     6 + <tr>
     7 + <td>Operation</td>
     8 + <td>Http Method</td>
     9 + <td>End Point</td>
     10 + </tr>
     11 + </thead>
     12 + <tbody>
     13 + <tr wicket:id="methods" class="method">
     14 + <td>
     15 + <a wicket:id="link" class="title`"><span wicket:id="label"></span></a>
     16 + </td>
     17 + <td wicket:id="httpMethod"></td>
     18 + <td wicket:id="path"></td>
     19 + </tr>
     20 + </tbody>
    9 21   </table>
    10 22   <wicket:fragment wicket:id="topbarTitleFrag">
    11 23   <a wicket:id="resources">Resources</a> <span class="dot"></span> <span wicket:id="resource"></span>
    skipped 2 lines
  • ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ResourceDetailPage.java
    skipped 7 lines
    8 8  import javax.ws.rs.GET;
    9 9  import javax.ws.rs.POST;
    10 10  import javax.ws.rs.PUT;
     11 +import javax.ws.rs.Path;
    11 12   
    12 13  import org.apache.wicket.Component;
    13 14  import org.apache.wicket.markup.html.basic.Label;
    skipped 27 lines
    41 42   protected void onInitialize() {
    42 43   super.onInitialize();
    43 44  
    44  - add(new Label("title", getResourceDescription(resourceClass)));
     45 + add(new Label("title", getResourceTitle(resourceClass)));
     46 +
     47 + String description = getResourceDescription(resourceClass);
     48 + add(new Label("description", description).setEscapeModelStrings(false).setVisible(description!=null));
    45 49  
    46 50   add(new ListView<Method>("methods", new LoadableDetachableModel<List<Method>>() {
    47 51   
    skipped 22 lines
    70 74  
    71 75   Link<Void> link = new ViewStateAwarePageLink<Void>("link", MethodDetailPage.class,
    72 76   MethodDetailPage.paramsOf(resourceClass, method.getName()));
    73  - link.add(new Label("label", getMethodDescription(method)));
     77 + link.add(new Label("label", getMethodTitle(method)));
    74 78  
    75 79   item.add(link);
     80 +
     81 + item.add(new Label("httpMethod", getHttpMethod(method)));
     82 + 
     83 + String resourcePathValue = resourceClass.getAnnotation(Path.class).value();
     84 + Path methodPath = method.getAnnotation(Path.class);
     85 + if (methodPath != null)
     86 + item.add(new Label("path", resourcePathValue + methodPath.value()));
     87 + else
     88 + item.add(new Label("path", resourcePathValue));
    76 89   }
    77 90  
    78 91   });
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ResourceListPage.html
    1 1  <wicket:extend>
    2  - <h5 class="mb-4">All RESTful Resources</h5>
    3  - <div class="alert alert-light-info">
     2 + <div class="title">All RESTful Resources</div>
     3 + <div class="alert alert-notice alert-light mb-5">
    4 4   In case anonymous access is disabled or anonymous user does not have enough permission for a resource operation,
    5 5   you will need to authenticate by providing user name and password (or access token) via http basic auth header
    6 6   </div>
    7 7   <table class="table resources">
    8  - <tr wicket:id="resources" class="resource">
    9  - <td>
    10  - <a wicket:id="link" class="title"><span wicket:id="label"></span></a>
    11  - </td>
    12  - </tr>
     8 + <thead>
     9 + <tr>
     10 + <td>Resource</td>
     11 + <td>End Point</td>
     12 + </tr>
     13 + </thead>
     14 + <tbody>
     15 + <tr wicket:id="resources" class="resource">
     16 + <td>
     17 + <a wicket:id="link" class="title"><span wicket:id="label"></span></a>
     18 + </td>
     19 + <td wicket:id="path"></td>
     20 + </tr>
     21 + </tbody>
    13 22   </table>
    14 23  </wicket:extend>
  • ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ResourceListPage.java
    skipped 54 lines
    55 55  
    56 56   Link<Void> link = new ViewStateAwarePageLink<Void>("link", ResourceDetailPage.class,
    57 57   ResourceDetailPage.paramsOf(clazz));
    58  - link.add(new Label("label", getResourceDescription(clazz)));
     58 + link.add(new Label("label", getResourceTitle(clazz)));
    59 59  
    60 60   item.add(link);
     61 +
     62 + item.add(new Label("path", clazz.getAnnotation(Path.class).value()));
    61 63   }
    62 64  
    63 65   });
    skipped 9 lines
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/ValueInfo.java
     1 +package io.onedev.server.web.page.help;
     2 + 
     3 +import java.lang.reflect.Field;
     4 +import java.lang.reflect.Type;
     5 + 
     6 +import javax.annotation.Nullable;
     7 + 
     8 +class ValueInfo {
     9 +
     10 + public enum Origin {REQUEST_BODY, RESPONSE_BODY, PATH_PLACEHOLDER, QUERY_PARAM};
     11 +
     12 + private final Origin origin;
     13 +
     14 + private final Type declaredType;
     15 +
     16 + private final Field field;
     17 +
     18 + public ValueInfo(Origin origin, Type declaredType, @Nullable Field field) {
     19 + this.origin = origin;
     20 + this.declaredType = declaredType;
     21 + this.field = field;
     22 + }
     23 + 
     24 + public Type getDeclaredType() {
     25 + return declaredType;
     26 + }
     27 + 
     28 + public Origin getOrigin() {
     29 + return origin;
     30 + }
     31 + 
     32 + @Nullable
     33 + public Field getField() {
     34 + return field;
     35 + }
     36 + 
     37 +}
  • ■ ■ ■ ■ ■ ■
    server-core/src/main/java/io/onedev/server/web/page/help/api-help.css
    skipped 1 lines
    2 2   margin-left: 2rem;
    3 3  }
    4 4  .api-help dt {
    5  - font-weight: 600;
     5 + font-weight: 500;
     6 + font-size: 1.1rem;
     7 + margin-bottom: 0.8rem;
     8 +}
     9 +.api-help dd {
     10 + margin-bottom: 1.6rem;
    6 11  }
    7 12  .api-help dl {
    8 13   margin-bottom: 0;
     14 +}
     15 + 
     16 +.api-help>.card-body>.title {
     17 + font-size: 18px;
     18 + font-weight: 500;
     19 + margin-bottom: 1.5rem;
    9 20  }
    10 21   
    11 22  .example-value-scalar, .example-value-brace,
    skipped 13 lines
    25 36   padding: 1rem;
    26 37  }
    27 38   
     39 + 
  • ■ ■ ■ ■
    server-plugin/pom.xml
    skipped 5 lines
    6 6   <parent>
    7 7   <groupId>io.onedev</groupId>
    8 8   <artifactId>server</artifactId>
    9  - <version>4.3.5</version>
     9 + <version>4.4.0</version>
    10 10   </parent>
    11 11   <dependencies>
    12 12   <dependency>
    skipped 21 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-archetype/pom.xml
    skipped 5 lines
    6 6   <parent>
    7 7   <groupId>io.onedev</groupId>
    8 8   <artifactId>server-plugin</artifactId>
    9  - <version>4.3.5</version>
     9 + <version>4.4.0</version>
    10 10   </parent>
    11 11   <build>
    12 12   <resources>
    skipped 44 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-authenticator-ldap/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.authenticator.ldap.LdapModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-buildspec-gradle/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.buildspec.gradle.GradleModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-buildspec-maven/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.buildspec.maven.MavenModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-buildspec-node/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.buildspec.node.NodePluginModule</moduleClass>
    skipped 4 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-executor-docker/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.docker.DockerModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-executor-kubernetes/pom.xml
    skipped 5 lines
    6 6   <parent>
    7 7   <groupId>io.onedev</groupId>
    8 8   <artifactId>server-plugin</artifactId>
    9  - <version>4.3.5</version>
     9 + <version>4.4.0</version>
    10 10   </parent>
    11 11   <properties>
    12 12   <moduleClass>io.onedev.server.plugin.executor.kubernetes.KubernetesModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-report-checkstyle/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.report.checkstyle.CheckstylePluginModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-report-clover/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.report.clover.CloverPluginModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-report-jest/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.report.jest.JestTestReportModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-report-markdown/pom.xml
    skipped 4 lines
    5 5   <parent>
    6 6   <groupId>io.onedev</groupId>
    7 7   <artifactId>server-plugin</artifactId>
    8  - <version>4.3.5</version>
     8 + <version>4.4.0</version>
    9 9   </parent>
    10 10   <properties>
    11 11   <moduleClass>io.onedev.server.plugin.report.markdown.MarkdownReportModule</moduleClass>
    skipped 3 lines
  • ■ ■ ■ ■
    server-plugin/server-plugin-sso-openid/pom.xml
    skipped 5 lines
    6 6   <parent>
    7 7   <groupId>io.onedev</groupId>
    8 8   <artifactId>server-plugin</artifactId>
    9  - <version>4.3.5</version>
     9 + <version>4.4.0</version>
    10 10   </parent>
    11 11   <dependencies>
    12 12   <dependency>
    skipped 10 lines
  • ■ ■ ■ ■
    server-product/pom.xml
    skipped 6 lines
    7 7   <parent>
    8 8   <groupId>io.onedev</groupId>
    9 9   <artifactId>server</artifactId>
    10  - <version>4.3.5</version>
     10 + <version>4.4.0</version>
    11 11   </parent>
    12 12   <dependencies>
    13 13   <dependency>
    skipped 66 lines
Please wait...
Page is in error, reload to recover