mirror of
https://github.com/theonedev/onedev.git
synced 2025-12-08 18:26:30 +00:00
wip: AI user
This commit is contained in:
parent
9fdbf390b3
commit
b9ee6f987b
5
pom.xml
5
pom.xml
@ -627,6 +627,11 @@
|
||||
<artifactId>fastexcel</artifactId>
|
||||
<version>0.15.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-core</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
|
||||
@ -383,6 +383,10 @@
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<kotlin.version>1.9.23</kotlin.version>
|
||||
|
||||
@ -0,0 +1,327 @@
|
||||
package dev.langchain4j.internal;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dev.langchain4j.Internal;
|
||||
import dev.langchain4j.exception.LangChain4jException;
|
||||
import dev.langchain4j.exception.NonRetriableException;
|
||||
import io.onedev.server.exception.ExceptionUtils;
|
||||
|
||||
/**
|
||||
* Utility class for retrying actions.
|
||||
*/
|
||||
@Internal
|
||||
public final class RetryUtils {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private RetryUtils() {}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RetryUtils.class);
|
||||
|
||||
/**
|
||||
* This method returns a RetryPolicy.Builder.
|
||||
*
|
||||
* @return A RetryPolicy.Builder.
|
||||
*/
|
||||
public static RetryPolicy.Builder retryPolicyBuilder() {
|
||||
return new RetryPolicy.Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* This class encapsulates a retry policy.
|
||||
*/
|
||||
public static final class RetryPolicy {
|
||||
|
||||
/**
|
||||
* This class encapsulates a retry policy builder.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private int maxRetries = 2;
|
||||
private int delayMillis = 1000;
|
||||
private double jitterScale = 0.2;
|
||||
private double backoffExp = 1.5;
|
||||
|
||||
/**
|
||||
* Construct a RetryPolicy.Builder.
|
||||
*/
|
||||
public Builder() {}
|
||||
|
||||
/**
|
||||
* Sets the default maximum number of retries.
|
||||
*
|
||||
* @param maxRetries The maximum number of retries.
|
||||
* The action can be executed up to {@code maxRetries + 1} times.
|
||||
* @return {@code this}
|
||||
*/
|
||||
public Builder maxRetries(int maxRetries) {
|
||||
this.maxRetries = maxRetries;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base delay in milliseconds.
|
||||
*
|
||||
* <p>The delay is calculated as follows:
|
||||
* <ol>
|
||||
* <li>Calculate the raw delay in milliseconds as
|
||||
* {@code delayMillis * Math.pow(backoffExp, retry)}.</li>
|
||||
* <li>Calculate the jitter delay in milliseconds as
|
||||
* {@code rawDelayMs + rand.nextInt((int) (rawDelayMs * jitterScale))}.</li>
|
||||
* <li>Sleep for the jitter delay in milliseconds.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param delayMillis The delay in milliseconds.
|
||||
* @return {@code this}
|
||||
*/
|
||||
public Builder delayMillis(int delayMillis) {
|
||||
this.delayMillis = delayMillis;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the jitter scale.
|
||||
*
|
||||
* <p>The jitter delay in milliseconds is calculated as
|
||||
* {@code rawDelayMs + rand.nextInt((int) (rawDelayMs * jitterScale))}.
|
||||
*
|
||||
* @param jitterScale The jitter scale.
|
||||
* @return {@code this}
|
||||
*/
|
||||
public Builder jitterScale(double jitterScale) {
|
||||
this.jitterScale = jitterScale;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the backoff exponent.
|
||||
*
|
||||
* @param backoffExp The backoff exponent.
|
||||
* @return {@code this}
|
||||
*/
|
||||
public Builder backoffExp(double backoffExp) {
|
||||
this.backoffExp = backoffExp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a RetryPolicy.
|
||||
*
|
||||
* @return A RetryPolicy.
|
||||
*/
|
||||
public RetryPolicy build() {
|
||||
return new RetryPolicy(maxRetries, delayMillis, jitterScale, backoffExp);
|
||||
}
|
||||
}
|
||||
|
||||
private final int maxRetries;
|
||||
private final int delayMillis;
|
||||
private final double jitterScale;
|
||||
private final double backoffExp;
|
||||
|
||||
/**
|
||||
* Construct a RetryPolicy.
|
||||
*
|
||||
* @param maxRetries The maximum number of retries.
|
||||
* The action can be executed up to {@code maxRetries + 1} times.
|
||||
* @param delayMillis The delay in milliseconds.
|
||||
* @param jitterScale The jitter scale.
|
||||
* @param backoffExp The backoff exponent.
|
||||
*/
|
||||
public RetryPolicy(int maxRetries, int delayMillis, double jitterScale, double backoffExp) {
|
||||
this.maxRetries = maxRetries;
|
||||
this.delayMillis = delayMillis;
|
||||
this.jitterScale = jitterScale;
|
||||
this.backoffExp = backoffExp;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the raw delay in milliseconds after a given retry.
|
||||
*
|
||||
* @param retry The retry number.
|
||||
* @return The raw delay in milliseconds.
|
||||
*/
|
||||
public double rawDelayMs(int retry) {
|
||||
return delayMillis * Math.pow(backoffExp, retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the jitter delay in milliseconds after a given retry.
|
||||
*
|
||||
* @param retry The retry number.
|
||||
* @return The jitter delay in milliseconds.
|
||||
*/
|
||||
public int jitterDelayMillis(int retry) {
|
||||
double delay = rawDelayMs(retry);
|
||||
double jitter = delay * jitterScale;
|
||||
return (int) (delay + RANDOM.nextInt((int) jitter));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sleeps after a given retry.
|
||||
*
|
||||
* @param retry The retry number.
|
||||
*/
|
||||
@JacocoIgnoreCoverageGenerated
|
||||
public void sleep(int retry) {
|
||||
try {
|
||||
Thread.sleep(jitterDelayMillis(retry));
|
||||
} catch (InterruptedException ignored) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to 3 times with an exponential backoff.
|
||||
* If the action fails on all attempts, it throws a RuntimeException.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param <T> The type of the result of the action.
|
||||
* @return The result of the action if it is successful.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public <T> T withRetry(Callable<T> action) {
|
||||
return withRetry(action, maxRetries);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to a specified number of times with an exponential backoff.
|
||||
* If the action fails on all attempts, it throws a RuntimeException.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param maxRetries The maximum number of retries.
|
||||
* The action can be executed up to {@code maxRetries + 1} times.
|
||||
* @param <T> The type of the result of the action.
|
||||
* @return The result of the action if it is successful.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public <T> T withRetry(Callable<T> action, int maxRetries) {
|
||||
int retry = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return action.call();
|
||||
} catch (NonRetriableException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
if (retry >= maxRetries || ExceptionUtils.find(e, InterruptedException.class) != null) {
|
||||
throw e instanceof RuntimeException re ? re : new LangChain4jException(e);
|
||||
}
|
||||
|
||||
log.warn(
|
||||
"A retriable exception occurred. Remaining retries: %s of %s"
|
||||
.formatted(maxRetries - retry, maxRetries),
|
||||
e);
|
||||
|
||||
sleep(retry);
|
||||
}
|
||||
retry++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default retry policy used by {@link #withRetry(Callable)}.
|
||||
*/
|
||||
public static final RetryPolicy DEFAULT_RETRY_POLICY = retryPolicyBuilder()
|
||||
.maxRetries(2)
|
||||
.delayMillis(500)
|
||||
.jitterScale(0.2)
|
||||
.backoffExp(1.5)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to 3 times with an exponential backoff.
|
||||
* If the action fails on all attempts, it throws a RuntimeException.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param <T> The type of the result of the action.
|
||||
* @return The result of the action if it is successful.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public static <T> T withRetry(Callable<T> action) {
|
||||
return DEFAULT_RETRY_POLICY.withRetry(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to a specified number of times with an exponential backoff.
|
||||
* If the action fails on all attempts, it throws a RuntimeException.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param maxRetries The maximum number of retries.
|
||||
* The action can be executed up to {@code maxRetries + 1} times.
|
||||
* @param <T> The type of the result of the action.
|
||||
* @return The result of the action if it is successful.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public static <T> T withRetry(Callable<T> action, int maxRetries) {
|
||||
return DEFAULT_RETRY_POLICY.withRetry(action, maxRetries);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to a specified number of times with an exponential backoff.
|
||||
* If the action fails on all attempts, it throws a RuntimeException.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param maxRetries The maximum number of retries.
|
||||
* The action can be executed up to {@code maxRetries + 1} times.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public static void withRetry(Runnable action, int maxRetries) {
|
||||
DEFAULT_RETRY_POLICY.withRetry(
|
||||
() -> {
|
||||
action.run();
|
||||
return null;
|
||||
},
|
||||
maxRetries);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to 3 times with an exponential backoff.
|
||||
* If the action fails, the Exception causing the failure will be mapped with the default {@link ExceptionMapper}.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param <T> The type of the result of the action.
|
||||
* @return The result of the action if it is successful.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public static <T> T withRetryMappingExceptions(Callable<T> action) {
|
||||
return withRetry(() -> ExceptionMapper.DEFAULT.withExceptionMapper(action));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to a specified number of times with an exponential backoff.
|
||||
* If the action fails, the Exception causing the failure will be mapped with the default {@link ExceptionMapper}.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param maxRetries The maximum number of retries.
|
||||
* The action can be executed up to {@code maxRetries + 1} times.
|
||||
* @param <T> The type of the result of the action.
|
||||
* @return The result of the action if it is successful.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public static <T> T withRetryMappingExceptions(Callable<T> action, int maxRetries) {
|
||||
return withRetryMappingExceptions(action, maxRetries, ExceptionMapper.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to execute a given action up to a specified number of times with an exponential backoff.
|
||||
* If the action fails, the Exception causing the failure will be mapped with the provided {@link ExceptionMapper}.
|
||||
*
|
||||
* @param action The action to be executed.
|
||||
* @param maxRetries The maximum number of retries.
|
||||
* The action can be executed up to {@code maxRetries + 1} times.
|
||||
* @param exceptionMapper The ExceptionMapper used to translate the exception that caused the failure of the action invocation.
|
||||
* @param <T> The type of the result of the action.
|
||||
* @return The result of the action if it is successful.
|
||||
* @throws RuntimeException if the action fails on all attempts.
|
||||
*/
|
||||
public static <T> T withRetryMappingExceptions(
|
||||
Callable<T> action, int maxRetries, ExceptionMapper exceptionMapper) {
|
||||
return withRetry(() -> exceptionMapper.withExceptionMapper(action), maxRetries);
|
||||
}
|
||||
}
|
||||
@ -197,6 +197,7 @@ import io.onedev.server.service.BuildMetricService;
|
||||
import io.onedev.server.service.BuildParamService;
|
||||
import io.onedev.server.service.BuildQueryPersonalizationService;
|
||||
import io.onedev.server.service.BuildService;
|
||||
import io.onedev.server.service.ChatService;
|
||||
import io.onedev.server.service.CodeCommentMentionService;
|
||||
import io.onedev.server.service.CodeCommentQueryPersonalizationService;
|
||||
import io.onedev.server.service.CodeCommentReplyService;
|
||||
@ -212,6 +213,7 @@ import io.onedev.server.service.EmailAddressService;
|
||||
import io.onedev.server.service.GitLfsLockService;
|
||||
import io.onedev.server.service.GpgKeyService;
|
||||
import io.onedev.server.service.GroupAuthorizationService;
|
||||
import io.onedev.server.service.GroupEntitlementService;
|
||||
import io.onedev.server.service.GroupService;
|
||||
import io.onedev.server.service.IssueAuthorizationService;
|
||||
import io.onedev.server.service.IssueChangeService;
|
||||
@ -243,6 +245,7 @@ import io.onedev.server.service.PackLabelService;
|
||||
import io.onedev.server.service.PackQueryPersonalizationService;
|
||||
import io.onedev.server.service.PackService;
|
||||
import io.onedev.server.service.PendingSuggestionApplyService;
|
||||
import io.onedev.server.service.ProjectEntitlementService;
|
||||
import io.onedev.server.service.ProjectLabelService;
|
||||
import io.onedev.server.service.ProjectLastEventDateService;
|
||||
import io.onedev.server.service.ProjectService;
|
||||
@ -269,6 +272,7 @@ import io.onedev.server.service.SsoAccountService;
|
||||
import io.onedev.server.service.SsoProviderService;
|
||||
import io.onedev.server.service.StopwatchService;
|
||||
import io.onedev.server.service.UserAuthorizationService;
|
||||
import io.onedev.server.service.UserEntitlementService;
|
||||
import io.onedev.server.service.UserInvitationService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.service.impl.DefaultAccessTokenAuthorizationService;
|
||||
@ -285,6 +289,7 @@ import io.onedev.server.service.impl.DefaultBuildMetricService;
|
||||
import io.onedev.server.service.impl.DefaultBuildParamService;
|
||||
import io.onedev.server.service.impl.DefaultBuildQueryPersonalizationService;
|
||||
import io.onedev.server.service.impl.DefaultBuildService;
|
||||
import io.onedev.server.service.impl.DefaultChatService;
|
||||
import io.onedev.server.service.impl.DefaultCodeCommentMentionService;
|
||||
import io.onedev.server.service.impl.DefaultCodeCommentQueryPersonalizationService;
|
||||
import io.onedev.server.service.impl.DefaultCodeCommentReplyService;
|
||||
@ -300,6 +305,7 @@ import io.onedev.server.service.impl.DefaultEmailAddressService;
|
||||
import io.onedev.server.service.impl.DefaultGitLfsLockService;
|
||||
import io.onedev.server.service.impl.DefaultGpgKeyService;
|
||||
import io.onedev.server.service.impl.DefaultGroupAuthorizationService;
|
||||
import io.onedev.server.service.impl.DefaultGroupEntitlementService;
|
||||
import io.onedev.server.service.impl.DefaultGroupService;
|
||||
import io.onedev.server.service.impl.DefaultIssueAuthorizationService;
|
||||
import io.onedev.server.service.impl.DefaultIssueChangeService;
|
||||
@ -331,6 +337,7 @@ import io.onedev.server.service.impl.DefaultPackLabelService;
|
||||
import io.onedev.server.service.impl.DefaultPackQueryPersonalizationService;
|
||||
import io.onedev.server.service.impl.DefaultPackService;
|
||||
import io.onedev.server.service.impl.DefaultPendingSuggestionApplyService;
|
||||
import io.onedev.server.service.impl.DefaultProjectEntitlementService;
|
||||
import io.onedev.server.service.impl.DefaultProjectLabelService;
|
||||
import io.onedev.server.service.impl.DefaultProjectLastEventDateService;
|
||||
import io.onedev.server.service.impl.DefaultProjectService;
|
||||
@ -357,6 +364,7 @@ import io.onedev.server.service.impl.DefaultSsoAccountService;
|
||||
import io.onedev.server.service.impl.DefaultSsoProviderService;
|
||||
import io.onedev.server.service.impl.DefaultStopwatchService;
|
||||
import io.onedev.server.service.impl.DefaultUserAuthorizationService;
|
||||
import io.onedev.server.service.impl.DefaultUserEntitlementService;
|
||||
import io.onedev.server.service.impl.DefaultUserInvitationService;
|
||||
import io.onedev.server.service.impl.DefaultUserService;
|
||||
import io.onedev.server.ssh.CommandCreator;
|
||||
@ -393,6 +401,7 @@ import io.onedev.server.web.DefaultUrlService;
|
||||
import io.onedev.server.web.DefaultWicketFilter;
|
||||
import io.onedev.server.web.DefaultWicketServlet;
|
||||
import io.onedev.server.web.ResourcePackScopeContribution;
|
||||
import io.onedev.server.web.SessionListener;
|
||||
import io.onedev.server.web.UrlService;
|
||||
import io.onedev.server.web.WebApplication;
|
||||
import io.onedev.server.web.avatar.AvatarService;
|
||||
@ -600,7 +609,11 @@ public class CoreModule extends AbstractPluginModule {
|
||||
bind(PullRequestDescriptionRevisionService.class).to(DefaultPullRequestDescriptionRevisionService.class);
|
||||
bind(SsoProviderService.class).to(DefaultSsoProviderService.class);
|
||||
bind(SsoAccountService.class).to(DefaultSsoAccountService.class);
|
||||
bind(ChatService.class).to(DefaultChatService.class);
|
||||
bind(BaseAuthorizationService.class).to(DefaultBaseAuthorizationService.class);
|
||||
bind(GroupEntitlementService.class).to(DefaultGroupEntitlementService.class);
|
||||
bind(UserEntitlementService.class).to(DefaultUserEntitlementService.class);
|
||||
bind(ProjectEntitlementService.class).to(DefaultProjectEntitlementService.class);
|
||||
|
||||
bind(WebHookManager.class);
|
||||
|
||||
@ -719,6 +732,9 @@ public class CoreModule extends AbstractPluginModule {
|
||||
contributeFromPackage(ExceptionHandler.class, ConstraintViolationExceptionHandler.class);
|
||||
contributeFromPackage(ExceptionHandler.class, PageExpiredExceptionHandler.class);
|
||||
contributeFromPackage(ExceptionHandler.class, WebApplicationExceptionHandler.class);
|
||||
|
||||
contribute(SessionListener.class, DefaultChatService.class);
|
||||
contribute(SessionListener.class, DefaultWebSocketService.class);
|
||||
|
||||
bind(UrlService.class).to(DefaultUrlService.class);
|
||||
bind(CodeCommentEventBroadcaster.class);
|
||||
|
||||
@ -2,7 +2,7 @@ package io.onedev.server.data;
|
||||
|
||||
import static com.google.common.base.Throwables.getStackTraceAsString;
|
||||
import static io.onedev.server.model.User.PROP_NOTIFY_OWN_EVENTS;
|
||||
import static io.onedev.server.model.User.PROP_SERVICE_ACCOUNT;
|
||||
import static io.onedev.server.model.User.PROP_TYPE;
|
||||
import static io.onedev.server.model.support.administration.SystemSetting.PROP_CURL_LOCATION;
|
||||
import static io.onedev.server.model.support.administration.SystemSetting.PROP_DISABLE_AUTO_UPDATE_CHECK;
|
||||
import static io.onedev.server.model.support.administration.SystemSetting.PROP_GIT_LOCATION;
|
||||
@ -92,7 +92,7 @@ import io.onedev.server.model.Role;
|
||||
import io.onedev.server.model.Setting;
|
||||
import io.onedev.server.model.Setting.Key;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.model.support.administration.AISetting;
|
||||
import io.onedev.server.model.support.administration.AiSetting;
|
||||
import io.onedev.server.model.support.administration.AgentSetting;
|
||||
import io.onedev.server.model.support.administration.AlertSetting;
|
||||
import io.onedev.server.model.support.administration.AuditSetting;
|
||||
@ -674,7 +674,7 @@ public class DefaultDataService implements DataService, Serializable {
|
||||
if (validator.validate(bean).isEmpty()) {
|
||||
createRoot(bean);
|
||||
} else {
|
||||
manualConfigs.add(new ManualConfig("Create Administrator Account", null, bean, Sets.newHashSet(PROP_SERVICE_ACCOUNT, PROP_NOTIFY_OWN_EVENTS)) {
|
||||
manualConfigs.add(new ManualConfig("Create Administrator Account", null, bean, Sets.newHashSet(PROP_TYPE, PROP_NOTIFY_OWN_EVENTS)) {
|
||||
|
||||
@Override
|
||||
public void complete() {
|
||||
@ -884,7 +884,7 @@ public class DefaultDataService implements DataService, Serializable {
|
||||
|
||||
setting = settingService.findSetting(Key.AI);
|
||||
if (setting == null)
|
||||
settingService.saveAISetting(new AISetting());
|
||||
settingService.saveAiSetting(new AiSetting());
|
||||
|
||||
if (roleService.get(Role.OWNER_ID) == null) {
|
||||
Role owner = new Role();
|
||||
|
||||
@ -8382,4 +8382,19 @@ public class DataMigrator {
|
||||
private void migrate214(File dataDir, Stack<Integer> versions) {
|
||||
}
|
||||
|
||||
private void migrate215(File dataDir, Stack<Integer> versions) {
|
||||
for (File file : dataDir.listFiles()) {
|
||||
if (file.getName().startsWith("Users.xml")) {
|
||||
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
|
||||
for (Element element : dom.getRootElement().elements()) {
|
||||
Element serviceAccountElement = element.element("serviceAccount");
|
||||
boolean isServiceAccount = Boolean.parseBoolean(serviceAccountElement.getTextTrim());
|
||||
serviceAccountElement.detach();
|
||||
element.addElement("type").setText(isServiceAccount ? "SERVICE" : "ORDINARY");
|
||||
}
|
||||
dom.writeToFile(file, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ public class IssueChanged extends IssueEvent implements CommitAware {
|
||||
private final String comment;
|
||||
|
||||
public IssueChanged(IssueChange change, @Nullable String comment) {
|
||||
this(change, comment, !change.getUser().isServiceAccount());
|
||||
this(change, comment, change.getUser().getType() != User.Type.SERVICE);
|
||||
}
|
||||
|
||||
public IssueChanged(IssueChange change, @Nullable String comment, boolean sendNotifications) {
|
||||
|
||||
@ -22,7 +22,7 @@ public abstract class IssueEvent extends ProjectEvent {
|
||||
private final boolean sendNotifications;
|
||||
|
||||
public IssueEvent(User user, Date date, Issue issue) {
|
||||
this(user, date, issue, !user.isServiceAccount());
|
||||
this(user, date, issue, user.getType() != User.Type.SERVICE);
|
||||
}
|
||||
|
||||
public IssueEvent(User user, Date date, Issue issue, boolean sendNotifications) {
|
||||
|
||||
116
server-core/src/main/java/io/onedev/server/model/Chat.java
Normal file
116
server-core/src/main/java/io/onedev/server/model/Chat.java
Normal file
@ -0,0 +1,116 @@
|
||||
package io.onedev.server.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@Entity
|
||||
@Table(indexes={@Index(columnList="o_user_id"), @Index(columnList="o_ai_id")})
|
||||
public class Chat extends AbstractEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int MAX_TITLE_LEN = 255;
|
||||
|
||||
public static final String PROP_USER = "user";
|
||||
|
||||
public static final String PROP_AI = "ai";
|
||||
|
||||
public static final String PROP_DATE = "date";
|
||||
|
||||
public static final String PROP_TITLE = "title";
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private User user;
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private User ai;
|
||||
|
||||
@Column(nullable=false)
|
||||
private Date date;
|
||||
|
||||
@Column(nullable=false, length=MAX_TITLE_LEN)
|
||||
private String title = "New chat";
|
||||
|
||||
@OneToMany(mappedBy="chat", cascade=CascadeType.REMOVE)
|
||||
private Collection<ChatMessage> messages = new ArrayList<>();
|
||||
|
||||
private transient List<ChatMessage> sortedMessages;
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(Date date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public User getAi() {
|
||||
return ai;
|
||||
}
|
||||
|
||||
public void setAi(User ai) {
|
||||
this.ai = ai;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = StringUtils.abbreviate(title, MAX_TITLE_LEN);
|
||||
}
|
||||
|
||||
public Collection<ChatMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void setMessages(Collection<ChatMessage> messages) {
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public List<ChatMessage> getSortedMessages() {
|
||||
if (sortedMessages == null) {
|
||||
sortedMessages = new ArrayList<>(messages);
|
||||
sortedMessages.sort(Comparator.comparing(ChatMessage::getId));
|
||||
}
|
||||
return sortedMessages;
|
||||
}
|
||||
|
||||
public static String getChangeObservable(Long chatId) {
|
||||
return "chat:" + chatId;
|
||||
}
|
||||
|
||||
public static String getPartialResponseObservable(Long chatId) {
|
||||
return "chat:" + chatId + ":partialResponse";
|
||||
}
|
||||
|
||||
public static String getNewMessagesObservable(Long chatId) {
|
||||
return "chat:" + chatId + ":newMessages";
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package io.onedev.server.model;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.Lob;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
|
||||
@Entity
|
||||
@Table(indexes={@Index(columnList="o_chat_id")})
|
||||
public class ChatMessage extends AbstractEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final int MAX_CONTENT_LEN = 100000;
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private Chat chat;
|
||||
|
||||
private boolean error;
|
||||
|
||||
private boolean request;
|
||||
|
||||
@Lob
|
||||
@Column(nullable=false, length=MAX_CONTENT_LEN)
|
||||
private String content;
|
||||
|
||||
@Lob
|
||||
@Column(nullable=false, length=65535)
|
||||
private LinkedHashMap<String, String> attachments = new LinkedHashMap<>();
|
||||
|
||||
public Chat getChat() {
|
||||
return chat;
|
||||
}
|
||||
|
||||
public void setChat(Chat chat) {
|
||||
this.chat = chat;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(boolean error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public boolean isRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public void setRequest(boolean request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = StringUtils.abbreviate(content, MAX_CONTENT_LEN);
|
||||
}
|
||||
|
||||
public LinkedHashMap<String, String> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public void setAttachments(LinkedHashMap<String, String> attachments) {
|
||||
this.attachments = attachments;
|
||||
}
|
||||
|
||||
}
|
||||
@ -59,6 +59,10 @@ public class Group extends AbstractEntity implements BasePermission {
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<Membership> memberships = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="group", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<GroupEntitlement> entitlements = new ArrayList<>();
|
||||
|
||||
private transient Collection<User> members;
|
||||
|
||||
@Editable(order=100)
|
||||
@ -133,6 +137,14 @@ public class Group extends AbstractEntity implements BasePermission {
|
||||
this.memberships = memberships;
|
||||
}
|
||||
|
||||
public Collection<GroupEntitlement> getEntitlements() {
|
||||
return entitlements;
|
||||
}
|
||||
|
||||
public void setEntitlements(Collection<GroupEntitlement> groupEntitlements) {
|
||||
this.entitlements = groupEntitlements;
|
||||
}
|
||||
|
||||
public Collection<User> getMembers() {
|
||||
if (members == null) {
|
||||
members = new HashSet<>();
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package io.onedev.server.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
indexes={@Index(columnList="o_ai_id"), @Index(columnList="o_group_id")},
|
||||
uniqueConstraints={@UniqueConstraint(columnNames={"o_ai_id", "o_group_id"})
|
||||
})
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
public class GroupEntitlement extends AbstractEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String PROP_AI = "ai";
|
||||
|
||||
public static final String PROP_GROUP = "group";
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private User ai;
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private Group group;
|
||||
|
||||
public User getAi() {
|
||||
return ai;
|
||||
}
|
||||
|
||||
public void setAi(User ai) {
|
||||
this.ai = ai;
|
||||
}
|
||||
|
||||
public Group getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(Group group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
}
|
||||
@ -367,6 +367,10 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<Iteration> iterations = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="project", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<ProjectEntitlement> entitlements = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="project", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<Audit> audits = new ArrayList<>();
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package io.onedev.server.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
indexes={@Index(columnList="o_ai_id"), @Index(columnList="o_project_id")},
|
||||
uniqueConstraints={@UniqueConstraint(columnNames={"o_ai_id", "o_project_id"})
|
||||
})
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
public class ProjectEntitlement extends AbstractEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static String PROP_AI = "ai";
|
||||
|
||||
public static String PROP_PROJECT = "project";
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private Project project;
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private User ai;
|
||||
|
||||
public Project getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
public void setProject(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public User getAi() {
|
||||
return ai;
|
||||
}
|
||||
|
||||
public void setAi(User ai) {
|
||||
this.ai = ai;
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,12 +2,14 @@ package io.onedev.server.model;
|
||||
|
||||
import static io.onedev.server.model.User.PROP_FULL_NAME;
|
||||
import static io.onedev.server.model.User.PROP_NAME;
|
||||
import static io.onedev.server.model.User.Type.AI;
|
||||
import static io.onedev.server.model.User.Type.SERVICE;
|
||||
import static io.onedev.server.security.SecurityUtils.asPrincipals;
|
||||
import static io.onedev.server.security.SecurityUtils.asUserPrincipal;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@ -15,7 +17,6 @@ import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
@ -31,6 +32,7 @@ import org.apache.shiro.subject.Subject;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.common.base.MoreObjects;
|
||||
@ -41,11 +43,8 @@ import io.onedev.server.OneDev;
|
||||
import io.onedev.server.annotation.DependsOn;
|
||||
import io.onedev.server.annotation.Editable;
|
||||
import io.onedev.server.annotation.Password;
|
||||
import io.onedev.server.annotation.SubscriptionRequired;
|
||||
import io.onedev.server.annotation.UserName;
|
||||
import io.onedev.server.service.EmailAddressService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.model.support.AiSetting;
|
||||
import io.onedev.server.model.support.NamedProjectQuery;
|
||||
import io.onedev.server.model.support.QueryPersonalization;
|
||||
import io.onedev.server.model.support.TwoFactorAuthentication;
|
||||
@ -54,10 +53,12 @@ import io.onedev.server.model.support.issue.NamedIssueQuery;
|
||||
import io.onedev.server.model.support.pack.NamedPackQuery;
|
||||
import io.onedev.server.model.support.pullrequest.NamedPullRequestQuery;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.service.EmailAddressService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.util.facade.UserFacade;
|
||||
import io.onedev.server.util.watch.QuerySubscriptionSupport;
|
||||
import io.onedev.server.util.watch.QueryWatchSupport;
|
||||
import io.onedev.server.web.util.WicketUtils;
|
||||
|
||||
@Entity
|
||||
@Table(indexes={@Index(columnList=PROP_NAME), @Index(columnList=PROP_FULL_NAME)})
|
||||
@ -66,6 +67,8 @@ import io.onedev.server.web.util.WicketUtils;
|
||||
public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public enum Type {ORDINARY, SERVICE, AI};
|
||||
|
||||
public static final Long UNKNOWN_ID = -2L;
|
||||
|
||||
@ -76,6 +79,8 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
public static final String SYSTEM_NAME = "OneDev";
|
||||
|
||||
public static final String SYSTEM_EMAIL_ADDRESS = "system@onedev";
|
||||
|
||||
public static final String AI_EMAIL_ADDRESS = "ai@onedev";
|
||||
|
||||
public static final String UNKNOWN_NAME = "unknown";
|
||||
|
||||
@ -83,7 +88,7 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
|
||||
public static final String PROP_FULL_NAME = "fullName";
|
||||
|
||||
public static final String PROP_SERVICE_ACCOUNT = "serviceAccount";
|
||||
public static final String PROP_TYPE = "type";
|
||||
|
||||
public static final String PROP_DISABLED = "disabled";
|
||||
|
||||
@ -93,7 +98,8 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
|
||||
private static ThreadLocal<Stack<User>> stack = ThreadLocal.withInitial(() -> new Stack<>());
|
||||
|
||||
private boolean serviceAccount;
|
||||
@Column(nullable=false)
|
||||
private Type type = Type.ORDINARY;
|
||||
|
||||
private boolean disabled;
|
||||
|
||||
@ -109,6 +115,10 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
private String fullName;
|
||||
|
||||
private boolean notifyOwnEvents;
|
||||
|
||||
@Lob
|
||||
@Column(length=65535)
|
||||
private AiSetting aiSetting = new AiSetting();
|
||||
|
||||
@JsonIgnore
|
||||
@Lob
|
||||
@ -229,6 +239,30 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
@OneToMany(mappedBy=ReviewedDiff.PROP_USER, cascade=CascadeType.REMOVE)
|
||||
private Collection<ReviewedDiff> reviewedDiffs = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="ai", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<ProjectEntitlement> projectEntitlements = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="ai", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<GroupEntitlement> groupEntitlements = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="ai", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<UserEntitlement> userEntitlements = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="user", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<UserEntitlement> aiEntitlements = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="ai", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<Chat> aiChats = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy="user", cascade=CascadeType.REMOVE)
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
private Collection<Chat> userChats = new ArrayList<>();
|
||||
|
||||
@JsonIgnore
|
||||
@Lob
|
||||
@Column(nullable=false, length=65535)
|
||||
@ -283,6 +317,8 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
private transient Optional<EmailAddress> gitEmailAddress;
|
||||
|
||||
private transient Optional<EmailAddress> publicEmailAddress;
|
||||
|
||||
private transient List<User> entitledAis;
|
||||
|
||||
public QueryPersonalization<NamedProjectQuery> getProjectQueryPersonalization() {
|
||||
return new QueryPersonalization<NamedProjectQuery>() {
|
||||
@ -531,14 +567,16 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
return SecurityUtils.asSubject(getPrincipals());
|
||||
}
|
||||
|
||||
@Editable(order=50, name="Service Account", descriptionProvider = "getServiceAccountDescription")
|
||||
@SubscriptionRequired
|
||||
public boolean isServiceAccount() {
|
||||
return serviceAccount;
|
||||
@Editable(order=50, description = "" +
|
||||
"Ordinary: Normal account<br>" +
|
||||
"Service: Service account does not have password and email addresses, and will not generate notifications for its activities<br>" +
|
||||
"AI: AI account (working in progress)")
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setServiceAccount(boolean serviceAccount) {
|
||||
this.serviceAccount = serviceAccount;
|
||||
public void setType(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public boolean isDisabled() {
|
||||
@ -549,20 +587,6 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static String getServiceAccountDescription() {
|
||||
if (!WicketUtils.isSubscriptionActive()) {
|
||||
return _T(""
|
||||
+ "Whether or not to create as a service account for task automation purpose. Service account does not have password and email addresses, and will not generate "
|
||||
+ "notifications for its activities. <b class='text-warning'>NOTE:</b> Service account is an enterprise feature. "
|
||||
+ "<a href='https://onedev.io/pricing' target='_blank'>Try free</a> for 30 days");
|
||||
} else {
|
||||
return _T(""
|
||||
+ "Whether or not to create as a service account for task automation purpose. Service account does not have password and email addresses, and will not generate "
|
||||
+ "notifications for its activities");
|
||||
}
|
||||
}
|
||||
|
||||
@Editable(name="Login Name", order=100)
|
||||
@UserName
|
||||
@NotEmpty
|
||||
@ -580,7 +604,7 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
* time
|
||||
*/
|
||||
@Editable(order=150)
|
||||
@DependsOn(property="serviceAccount", value="false")
|
||||
@DependsOn(property="type", value="ORDINARY")
|
||||
@Password(checkPolicy=true, autoComplete="new-password")
|
||||
@NotEmpty
|
||||
@Nullable
|
||||
@ -614,9 +638,9 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
|
||||
@Editable(order=400, name="Notify Own Events", description = "Whether or not to send notifications for events generated by yourself")
|
||||
@DependsOn(property="serviceAccount", value="false")
|
||||
@DependsOn(property="type", value="ORDINARY")
|
||||
public boolean isNotifyOwnEvents() {
|
||||
return notifyOwnEvents;
|
||||
}
|
||||
@ -625,6 +649,14 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
this.notifyOwnEvents = sendOwnEvents;
|
||||
}
|
||||
|
||||
public AiSetting getAiSetting() {
|
||||
return aiSetting;
|
||||
}
|
||||
|
||||
public void setAiSetting(AiSetting aiSetting) {
|
||||
this.aiSetting = aiSetting;
|
||||
}
|
||||
|
||||
public Collection<AccessToken> getAccessTokens() {
|
||||
return accessTokens;
|
||||
}
|
||||
@ -666,7 +698,13 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
}
|
||||
|
||||
public PersonIdent asPerson() {
|
||||
if (isSystem()) {
|
||||
if (getType() == SERVICE) {
|
||||
throw new ExplicitException("Service account does not have git identity");
|
||||
} else if (getType() == AI) {
|
||||
return new PersonIdent(getName(), User.AI_EMAIL_ADDRESS);
|
||||
} else if (isUnknown()) {
|
||||
throw new ExplicitException("Unknown user does not have git identity");
|
||||
} else if (isSystem()) {
|
||||
return new PersonIdent(User.SYSTEM_NAME, User.SYSTEM_EMAIL_ADDRESS);
|
||||
} else {
|
||||
EmailAddress emailAddress = getGitEmailAddress();
|
||||
@ -708,6 +746,38 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
this.projectAuthorizations = projectAuthorizations;
|
||||
}
|
||||
|
||||
public Collection<ProjectEntitlement> getProjectEntitlements() {
|
||||
return projectEntitlements;
|
||||
}
|
||||
|
||||
public void setProjectEntitlements(Collection<ProjectEntitlement> projectEntitlements) {
|
||||
this.projectEntitlements = projectEntitlements;
|
||||
}
|
||||
|
||||
public Collection<GroupEntitlement> getGroupEntitlements() {
|
||||
return groupEntitlements;
|
||||
}
|
||||
|
||||
public void setGroupEntitlements(Collection<GroupEntitlement> groupEntitlements) {
|
||||
this.groupEntitlements = groupEntitlements;
|
||||
}
|
||||
|
||||
public Collection<UserEntitlement> getUserEntitlements() {
|
||||
return userEntitlements;
|
||||
}
|
||||
|
||||
public void setUserEntitlements(Collection<UserEntitlement> userEntitlements) {
|
||||
this.userEntitlements = userEntitlements;
|
||||
}
|
||||
|
||||
public Collection<UserEntitlement> getAiEntitlements() {
|
||||
return aiEntitlements;
|
||||
}
|
||||
|
||||
public void setAiEntitlements(Collection<UserEntitlement> aiEntitlements) {
|
||||
this.aiEntitlements = aiEntitlements;
|
||||
}
|
||||
|
||||
public Collection<IssueAuthorization> getIssueAuthorizations() {
|
||||
return issueAuthorizations;
|
||||
}
|
||||
@ -1106,8 +1176,37 @@ public class User extends AbstractEntity implements AuthenticationInfo {
|
||||
getEmailAddresses().add(emailAddress);
|
||||
}
|
||||
|
||||
public List<User> getEntitledAis() {
|
||||
if (entitledAis == null) {
|
||||
var userService = OneDev.getInstance(UserService.class);
|
||||
var userCache = userService.cloneCache();
|
||||
var aiUserFacades = userCache.values().stream()
|
||||
.filter(it -> it.getType() == AI && !it.isDisabled() && !it.getId().equals(getId()))
|
||||
.collect(Collectors.toList());
|
||||
if (aiUserFacades.stream().allMatch(it->it.isEntitleToAll())) {
|
||||
entitledAis = aiUserFacades.stream()
|
||||
.sorted(Comparator.comparing(UserFacade::getDisplayName))
|
||||
.map(it -> userService.load(it.getId()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
var entitledAiSet = aiUserFacades.stream()
|
||||
.filter(it->it.isEntitleToAll())
|
||||
.map(it -> userService.load(it.getId()))
|
||||
.collect(Collectors.toSet());
|
||||
getAiEntitlements().stream().forEach(it -> entitledAiSet.add(it.getAI()));
|
||||
|
||||
for (var group: getGroups()) {
|
||||
group.getEntitlements().stream().forEach(it -> entitledAiSet.add(it.getAi()));
|
||||
}
|
||||
entitledAis = new ArrayList<User>(entitledAiSet);
|
||||
entitledAis.sort(Comparator.comparing(User::getDisplayName));
|
||||
}
|
||||
}
|
||||
return entitledAis;
|
||||
}
|
||||
|
||||
public UserFacade getFacade() {
|
||||
return new UserFacade(getId(), getName(), getFullName(), isServiceAccount(), isDisabled());
|
||||
return new UserFacade(getId(), getName(), getFullName(), getType(), isDisabled(), getAiSetting().isEntitleToAll());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package io.onedev.server.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.UniqueConstraint;
|
||||
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
indexes={@Index(columnList="o_ai_id"), @Index(columnList="o_user_id")},
|
||||
uniqueConstraints={@UniqueConstraint(columnNames={"o_ai_id", "o_user_id"})
|
||||
})
|
||||
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
|
||||
public class UserEntitlement extends AbstractEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String PROP_AI = "ai";
|
||||
|
||||
public static final String PROP_USER = "user";
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private User ai;
|
||||
|
||||
@ManyToOne(fetch=FetchType.LAZY)
|
||||
@JoinColumn(nullable=false)
|
||||
private User user;
|
||||
|
||||
public User getAI() {
|
||||
return ai;
|
||||
}
|
||||
|
||||
public void setAI(User ai) {
|
||||
this.ai = ai;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,19 +19,21 @@ import org.slf4j.LoggerFactory;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
|
||||
import io.onedev.server.annotation.ChoiceProvider;
|
||||
import io.onedev.server.annotation.Editable;
|
||||
import io.onedev.server.util.EditContext;
|
||||
|
||||
@Editable(order=100)
|
||||
public class AIModelSetting implements Serializable {
|
||||
public class AiModelSetting implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final int TIMEOUT_SECONDS = 30;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AIModelSetting.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(AiModelSetting.class);
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
@ -128,4 +130,13 @@ public class AIModelSetting implements Serializable {
|
||||
.build();
|
||||
}
|
||||
|
||||
public StreamingChatModel getStreamingChatModel() {
|
||||
return OpenAiStreamingChatModel.builder()
|
||||
.apiKey(apiKey)
|
||||
.baseUrl(baseUrl)
|
||||
.modelName(name)
|
||||
.timeout(Duration.ofSeconds(TIMEOUT_SECONDS))
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package io.onedev.server.model.support;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class AiSetting implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private AiModelSetting modelSetting;
|
||||
|
||||
private boolean entitleToAll = true;
|
||||
|
||||
public AiModelSetting getModelSetting() {
|
||||
return modelSetting;
|
||||
}
|
||||
|
||||
public void setModelSetting(AiModelSetting modelSetting) {
|
||||
this.modelSetting = modelSetting;
|
||||
}
|
||||
|
||||
public boolean isEntitleToAll() {
|
||||
return entitleToAll;
|
||||
}
|
||||
|
||||
public void setEntitleToAll(boolean entitleToAll) {
|
||||
this.entitleToAll = entitleToAll;
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,24 +6,24 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import io.onedev.server.annotation.Editable;
|
||||
import io.onedev.server.model.support.AIModelSetting;
|
||||
import io.onedev.server.model.support.AiModelSetting;
|
||||
|
||||
@Editable
|
||||
public class AISetting implements Serializable {
|
||||
public class AiSetting implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String PROP_LITE_MODEL_SETTING = "liteModelSetting";
|
||||
|
||||
private AIModelSetting liteModelSetting;
|
||||
private AiModelSetting liteModelSetting;
|
||||
|
||||
@Editable(order=100)
|
||||
@Nullable
|
||||
public AIModelSetting getLiteModelSetting() {
|
||||
public AiModelSetting getLiteModelSetting() {
|
||||
return liteModelSetting;
|
||||
}
|
||||
|
||||
public void setLiteModelSetting(AIModelSetting liteModelSetting) {
|
||||
public void setLiteModelSetting(AiModelSetting liteModelSetting) {
|
||||
this.liteModelSetting = liteModelSetting;
|
||||
}
|
||||
|
||||
|
||||
@ -205,13 +205,13 @@ public class GlobalIssueSetting implements Serializable {
|
||||
|
||||
var branchUpdatedSpec = new BranchUpdatedSpec();
|
||||
branchUpdatedSpec.setToState("Closed");
|
||||
branchUpdatedSpec.setBranches("main");
|
||||
branchUpdatedSpec.setBranches("main master");
|
||||
branchUpdatedSpec.setIssueQuery("fixed in current commit");
|
||||
transitionSpecs.add(branchUpdatedSpec);
|
||||
|
||||
var pullRequestOpenedSpec = new PullRequestOpenedSpec();
|
||||
pullRequestOpenedSpec.setToState("In Review");
|
||||
pullRequestOpenedSpec.setBranches("main");
|
||||
pullRequestOpenedSpec.setBranches("main master");
|
||||
pullRequestOpenedSpec.setIssueQuery("fixed in current pull request");
|
||||
transitionSpecs.add(pullRequestOpenedSpec);
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ public class BuildNotificationManager {
|
||||
@Sessional
|
||||
@Listen
|
||||
public void on(BuildEvent event) {
|
||||
if (!(event instanceof BuildUpdated) && (event.getUser() == null || !event.getUser().isServiceAccount())) {
|
||||
if (!(event instanceof BuildUpdated) && (event.getUser() == null || event.getUser().getType() != User.Type.SERVICE)) {
|
||||
Project project = event.getProject();
|
||||
Map<User, Collection<String>> subscribedQueryStrings = new HashMap<>();
|
||||
for (BuildQueryPersonalization personalization: project.getBuildQueryPersonalizations()) {
|
||||
|
||||
@ -72,7 +72,7 @@ public abstract class ChannelNotificationManager<T extends ChannelNotificationSe
|
||||
@Sessional
|
||||
@Listen
|
||||
public void on(PullRequestEvent event) {
|
||||
if (!event.isMinor() && (event.getUser() == null || !event.getUser().isServiceAccount())) {
|
||||
if (!event.isMinor() && (event.getUser() == null || event.getUser().getType() != User.Type.SERVICE)) {
|
||||
PullRequest request = event.getRequest();
|
||||
User user = event.getUser();
|
||||
|
||||
@ -91,7 +91,7 @@ public abstract class ChannelNotificationManager<T extends ChannelNotificationSe
|
||||
@Sessional
|
||||
@Listen
|
||||
public void on(BuildEvent event) {
|
||||
if (event.getUser() == null || !event.getUser().isServiceAccount()) {
|
||||
if (event.getUser() == null || event.getUser().getType() != User.Type.SERVICE) {
|
||||
Build build = event.getBuild();
|
||||
|
||||
var status = StringUtils.capitalize(build.getStatus().toString().toLowerCase());
|
||||
@ -107,7 +107,7 @@ public abstract class ChannelNotificationManager<T extends ChannelNotificationSe
|
||||
@Sessional
|
||||
@Listen
|
||||
public void on(PackEvent event) {
|
||||
if (!event.getUser().isServiceAccount()) {
|
||||
if (event.getUser().getType() != User.Type.SERVICE) {
|
||||
Pack pack = event.getPack();
|
||||
var title = format("[%s %s] Package published", pack.getType(), pack.getReference(true));
|
||||
postIfApplicable(title, event);
|
||||
@ -134,7 +134,7 @@ public abstract class ChannelNotificationManager<T extends ChannelNotificationSe
|
||||
@Sessional
|
||||
@Listen
|
||||
public void on(CodeCommentEvent event) {
|
||||
if (!(event instanceof CodeCommentEdited) && !event.getUser().isServiceAccount()) {
|
||||
if (!(event instanceof CodeCommentEdited) && event.getUser().getType() != User.Type.SERVICE) {
|
||||
CodeComment comment = event.getComment();
|
||||
|
||||
String title = format("[Code Comment %s:%s] %s %s",
|
||||
|
||||
@ -41,7 +41,7 @@ public class CodeCommentNotificationManager {
|
||||
@Listen
|
||||
public void on(CodeCommentEvent event) {
|
||||
CodeComment comment = event.getComment();
|
||||
if (comment.getCompareContext().getPullRequest() == null && !event.getUser().isServiceAccount()) {
|
||||
if (comment.getCompareContext().getPullRequest() == null && event.getUser().getType() != User.Type.SERVICE) {
|
||||
MarkdownText markdown = (MarkdownText) event.getCommentText();
|
||||
|
||||
Collection<User> notifyUsers = new HashSet<>();
|
||||
|
||||
@ -177,7 +177,7 @@ public class IssueNotificationManager {
|
||||
if (user != null) {
|
||||
if (!user.isNotifyOwnEvents() || isNotified(notifiedEmailAddresses, user))
|
||||
notifiedUsers.add(user);
|
||||
if (!user.isSystem() && !user.isServiceAccount())
|
||||
if (!user.isSystem() && user.getType() != User.Type.SERVICE)
|
||||
watchService.watch(issue, user, true);
|
||||
}
|
||||
|
||||
@ -208,7 +208,7 @@ public class IssueNotificationManager {
|
||||
}
|
||||
|
||||
for (User member: entry.getValue().getMembers()) {
|
||||
if (!member.isServiceAccount())
|
||||
if (member.getType() != User.Type.SERVICE)
|
||||
watchService.watch(issue, member, true);
|
||||
authorizationService.authorize(issue, member);
|
||||
}
|
||||
@ -237,7 +237,7 @@ public class IssueNotificationManager {
|
||||
}
|
||||
|
||||
for (User each: entry.getValue()) {
|
||||
if (!each.isServiceAccount())
|
||||
if (each.getType() != User.Type.SERVICE)
|
||||
watchService.watch(issue, each, true);
|
||||
authorizationService.authorize(issue, each);
|
||||
}
|
||||
@ -250,7 +250,7 @@ public class IssueNotificationManager {
|
||||
User mentionedUser = userService.findByName(userName);
|
||||
if (mentionedUser != null) {
|
||||
mentionService.mention(issue, mentionedUser);
|
||||
if (!mentionedUser.isServiceAccount())
|
||||
if (mentionedUser.getType() != User.Type.SERVICE)
|
||||
watchService.watch(issue, mentionedUser, true);
|
||||
authorizationService.authorize(issue, mentionedUser);
|
||||
if (!isNotified(notifiedEmailAddresses, mentionedUser)) {
|
||||
|
||||
@ -83,7 +83,7 @@ public class PackNotificationManager {
|
||||
@Sessional
|
||||
@Listen
|
||||
public void on(PackEvent event) {
|
||||
if (!event.getUser().isServiceAccount()) {
|
||||
if (event.getUser().getType() != User.Type.SERVICE) {
|
||||
Project project = event.getProject();
|
||||
Map<User, Collection<String>> subscribedQueryStrings = new HashMap<>();
|
||||
for (PackQueryPersonalization personalization: project.getPackQueryPersonalizations()) {
|
||||
|
||||
@ -63,7 +63,7 @@ public class PullRequestNotificationManager {
|
||||
@Transactional
|
||||
@Listen
|
||||
public void on(PullRequestEvent event) {
|
||||
if (event.getUser() == null || !event.getUser().isServiceAccount()) {
|
||||
if (event.getUser() == null || event.getUser().getType() != User.Type.SERVICE) {
|
||||
PullRequest request = event.getRequest();
|
||||
User user = event.getUser();
|
||||
|
||||
@ -131,7 +131,7 @@ public class PullRequestNotificationManager {
|
||||
if (user != null) {
|
||||
if (!user.isNotifyOwnEvents() || isNotified(notifiedEmailAddresses, user))
|
||||
notifiedUsers.add(user);
|
||||
if (!user.isSystem() && !user.isServiceAccount())
|
||||
if (!user.isSystem() && user.getType() != User.Type.SERVICE)
|
||||
watchService.watch(request, user, true);
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ public class PullRequestNotificationManager {
|
||||
notifiedUsers.add(committer);
|
||||
}
|
||||
for (User each : committers) {
|
||||
if (!each.isSystem() && !each.isServiceAccount())
|
||||
if (!each.isSystem() && each.getType() != User.Type.SERVICE)
|
||||
watchService.watch(request, each, true);
|
||||
}
|
||||
}
|
||||
@ -206,7 +206,7 @@ public class PullRequestNotificationManager {
|
||||
}
|
||||
|
||||
for (User assignee : assignees) {
|
||||
if (!assignee.isServiceAccount())
|
||||
if (assignee.getType() != User.Type.SERVICE)
|
||||
watchService.watch(request, assignee, true);
|
||||
if (!notifiedUsers.contains(assignee)) {
|
||||
String subject = String.format(
|
||||
@ -232,7 +232,7 @@ public class PullRequestNotificationManager {
|
||||
}
|
||||
|
||||
for (User reviewer : reviewers) {
|
||||
if (!reviewer.isServiceAccount())
|
||||
if (reviewer.getType() != User.Type.SERVICE)
|
||||
watchService.watch(request, reviewer, true);
|
||||
if (!notifiedUsers.contains(reviewer)) {
|
||||
String subject = String.format(
|
||||
@ -264,7 +264,7 @@ public class PullRequestNotificationManager {
|
||||
User mentionedUser = userService.findByName(userName);
|
||||
if (mentionedUser != null) {
|
||||
mentionService.mention(request, mentionedUser);
|
||||
if (!mentionedUser.isServiceAccount())
|
||||
if (mentionedUser.getType() != User.Type.SERVICE)
|
||||
watchService.watch(request, mentionedUser, true);
|
||||
if (!isNotified(notifiedEmailAddresses, mentionedUser)) {
|
||||
String subject = String.format(
|
||||
|
||||
@ -19,12 +19,13 @@ import javax.ws.rs.core.Response;
|
||||
import org.apache.shiro.authz.UnauthorizedException;
|
||||
|
||||
import io.onedev.commons.utils.ExplicitException;
|
||||
import io.onedev.server.model.EmailAddress;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.rest.annotation.Api;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.EmailAddressService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.model.EmailAddress;
|
||||
import io.onedev.server.rest.annotation.Api;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
|
||||
@Path("/email-addresses")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@ -73,8 +74,8 @@ public class EmailAddressResource {
|
||||
throw new UnauthorizedException();
|
||||
else if (owner.isDisabled())
|
||||
throw new ExplicitException("Can not set email address for disabled user");
|
||||
else if (owner.isServiceAccount())
|
||||
throw new ExplicitException("Can not set email address for service account");
|
||||
else if (owner.getType() != User.Type.ORDINARY)
|
||||
throw new ExplicitException("Can not set email address for service or ai user");
|
||||
else if (emailAddressService.findByValue(emailAddress.getValue()) != null)
|
||||
throw new ExplicitException("This email address is already used by another user");
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package io.onedev.server.rest.resource;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.model.User.Type.SERVICE;
|
||||
import static io.onedev.server.security.SecurityUtils.getAuthUser;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
@ -101,10 +103,10 @@ public class UserResource {
|
||||
private UserData getData(User user) {
|
||||
var data = new UserData();
|
||||
data.setDisabled(user.isDisabled());
|
||||
data.setServiceAccount(user.isServiceAccount());
|
||||
data.setType(user.getType());
|
||||
data.setName(user.getName());
|
||||
data.setFullName(user.getFullName());
|
||||
if (!user.isServiceAccount())
|
||||
if (user.getType() != SERVICE)
|
||||
data.setNotifyOwnEvents(user.isNotifyOwnEvents());
|
||||
return data;
|
||||
}
|
||||
@ -333,14 +335,14 @@ public class UserResource {
|
||||
|
||||
if (userService.findByName(data.getName()) != null)
|
||||
throw new ExplicitException("Login name is already used by another user");
|
||||
if (!data.isServiceAccount() && emailAddressService.findByValue(data.getEmailAddress()) != null)
|
||||
if (data.getType() == ORDINARY && emailAddressService.findByValue(data.getEmailAddress()) != null)
|
||||
throw new ExplicitException("Email address is already used by another user");
|
||||
|
||||
User user = new User();
|
||||
user.setServiceAccount(data.isServiceAccount());
|
||||
user.setType(data.getType());
|
||||
user.setName(data.getName());
|
||||
user.setFullName(data.getFullName());
|
||||
if (data.isServiceAccount()) {
|
||||
if (data.getType() != ORDINARY) {
|
||||
userService.create(user);
|
||||
} else {
|
||||
user.setNotifyOwnEvents(data.isNotifyOwnEvents());
|
||||
@ -383,7 +385,7 @@ public class UserResource {
|
||||
String oldName = user.getName();
|
||||
user.setName(data.getName());
|
||||
user.setFullName(data.getFullName());
|
||||
if (!user.isServiceAccount())
|
||||
if (user.getType() != SERVICE)
|
||||
user.setNotifyOwnEvents(data.isNotifyOwnEvents());
|
||||
userService.update(user, oldName);
|
||||
|
||||
@ -462,9 +464,9 @@ public class UserResource {
|
||||
auditService.audit(null, "changed password of account \"" + user.getName() + "\" via RESTful API", null, null);
|
||||
return Response.ok().build();
|
||||
} else if (user.isDisabled()) {
|
||||
throw new ExplicitException("Can not set password for disabled user");
|
||||
} else if (user.isServiceAccount()) {
|
||||
throw new ExplicitException("Can not set password for service account");
|
||||
throw new ExplicitException("Can not set password for disabled account");
|
||||
} else if (user.getType() != ORDINARY) {
|
||||
throw new ExplicitException("Can not set password for service or AI account");
|
||||
} else if (user.equals(getAuthUser())) {
|
||||
if (user.getPassword() == null) {
|
||||
throw new ExplicitException("The user is currently authenticated via external system, "
|
||||
@ -488,9 +490,9 @@ public class UserResource {
|
||||
|
||||
User user = userService.load(userId);
|
||||
if (user.isDisabled()) {
|
||||
throw new ExplicitException("Can not reset two factor authentication for disabled user");
|
||||
} else if (user.isServiceAccount()) {
|
||||
throw new ExplicitException("Can not reset two factor authentication for service account");
|
||||
throw new ExplicitException("Can not reset two factor authentication for disabled account");
|
||||
} else if (user.getType() != ORDINARY) {
|
||||
throw new ExplicitException("Can not reset two factor authentication for service or AI account");
|
||||
} else {
|
||||
user.setTwoFactorAuthentication(null);
|
||||
userService.update(user, null);
|
||||
@ -509,8 +511,8 @@ public class UserResource {
|
||||
|
||||
if (user.isDisabled())
|
||||
throw new ExplicitException("Can not set queries and watches for disabled user");
|
||||
else if (user.isServiceAccount())
|
||||
throw new ExplicitException("Can not set queries and watches for service account");
|
||||
else if (user.getType() != ORDINARY)
|
||||
throw new ExplicitException("Can not set queries and watches for service or ai account");
|
||||
|
||||
var oldAuditContent = VersionedXmlDoc.fromBean(getQueriesAndWatches(user)).toXML();
|
||||
|
||||
@ -587,8 +589,8 @@ public class UserResource {
|
||||
@Api(order=10, description="Whether or not the user is disabled")
|
||||
private boolean disabled;
|
||||
|
||||
@Api(order=50, description="Whether or not the user is a service account")
|
||||
private boolean serviceAccount;
|
||||
@Api(order=50, description="Type of the user")
|
||||
private User.Type type;
|
||||
|
||||
@Api(order=100, description="Login name of the user")
|
||||
private String name;
|
||||
@ -607,12 +609,12 @@ public class UserResource {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
public boolean isServiceAccount() {
|
||||
return serviceAccount;
|
||||
public User.Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setServiceAccount(boolean serviceAccount) {
|
||||
this.serviceAccount = serviceAccount;
|
||||
public void setType(User.Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@ -645,8 +647,8 @@ public class UserResource {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Api(order=50, exampleProvider="getServiceAccountExample", description="Create user as service account")
|
||||
private boolean serviceAccount;
|
||||
@Api(order=50, exampleProvider = "getTypeExample", description="Specify account type")
|
||||
private User.Type type;
|
||||
|
||||
@Api(order=100, description="Login name of the user")
|
||||
private String name;
|
||||
@ -662,17 +664,17 @@ public class UserResource {
|
||||
@Api(order=400, description = "Whether or not to notify user on own events. Only required if not created as service account")
|
||||
private boolean notifyOwnEvents;
|
||||
|
||||
public boolean isServiceAccount() {
|
||||
return serviceAccount;
|
||||
public User.Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setServiceAccount(boolean serviceAccount) {
|
||||
this.serviceAccount = serviceAccount;
|
||||
public void setType(User.Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static boolean getServiceAccountExample() {
|
||||
return false;
|
||||
private static User.Type getTypeExample() {
|
||||
return ORDINARY;
|
||||
}
|
||||
|
||||
@UserName
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.security.realm;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.validation.validator.UserNameValidator.normalizeUserName;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
@ -145,8 +146,8 @@ public class PasswordAuthenticatingRealm extends AuthenticatingRealm {
|
||||
if (user != null) {
|
||||
if (user.isDisabled())
|
||||
throw new DisabledAccountException(_T("Account is disabled"));
|
||||
else if (user.isServiceAccount())
|
||||
throw new DisabledAccountException(_T("Service account not allowed to login"));
|
||||
else if (user.getType() != ORDINARY)
|
||||
throw new DisabledAccountException(_T("Service or AI account not allowed to login"));
|
||||
if (user.getPassword() == null) {
|
||||
var authenticator = settingService.getAuthenticator();
|
||||
if (authenticator != null) {
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package io.onedev.server.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import io.onedev.server.model.Chat;
|
||||
import io.onedev.server.model.ChatMessage;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.service.support.ChatResponding;
|
||||
|
||||
public interface ChatService extends EntityService<Chat> {
|
||||
|
||||
List<Chat> query(User user, User ai, String term, int count);
|
||||
|
||||
void createOrUpdate(Chat chat);
|
||||
|
||||
void sendRequest(String sessionId, ChatMessage request);
|
||||
|
||||
@Nullable
|
||||
ChatResponding getResponding(String sessionId, Chat chat);
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package io.onedev.server.service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.onedev.server.model.GroupEntitlement;
|
||||
import io.onedev.server.model.User;
|
||||
|
||||
public interface GroupEntitlementService extends EntityService<GroupEntitlement> {
|
||||
|
||||
void syncEntitlements(User ai, Collection<GroupEntitlement> entitlements);
|
||||
|
||||
void create(GroupEntitlement entitlement);
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package io.onedev.server.service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.onedev.server.model.ProjectEntitlement;
|
||||
import io.onedev.server.model.User;
|
||||
|
||||
public interface ProjectEntitlementService extends EntityService<ProjectEntitlement> {
|
||||
|
||||
void syncEntitlements(User ai, Collection<ProjectEntitlement> entitlements);
|
||||
|
||||
void create(ProjectEntitlement entitlement);
|
||||
|
||||
}
|
||||
@ -9,7 +9,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import io.onedev.server.annotation.NoDBAccess;
|
||||
import io.onedev.server.model.Setting;
|
||||
import io.onedev.server.model.Setting.Key;
|
||||
import io.onedev.server.model.support.administration.AISetting;
|
||||
import io.onedev.server.model.support.administration.AiSetting;
|
||||
import io.onedev.server.model.support.administration.AgentSetting;
|
||||
import io.onedev.server.model.support.administration.AlertSetting;
|
||||
import io.onedev.server.model.support.administration.AuditSetting;
|
||||
@ -173,9 +173,9 @@ public interface SettingService extends EntityService<Setting> {
|
||||
|
||||
PerformanceSetting getPerformanceSetting();
|
||||
|
||||
AISetting getAISetting();
|
||||
AiSetting getAiSetting();
|
||||
|
||||
void saveAISetting(AISetting aiSetting);
|
||||
void saveAiSetting(AiSetting aiSetting);
|
||||
|
||||
void saveSshSetting(SshSetting sshSetting);
|
||||
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
package io.onedev.server.service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.model.UserEntitlement;
|
||||
|
||||
public interface UserEntitlementService extends EntityService<UserEntitlement> {
|
||||
|
||||
void syncEntitlements(User ai, Collection<UserEntitlement> entitlements);
|
||||
|
||||
void create(UserEntitlement entitlement);
|
||||
|
||||
}
|
||||
@ -0,0 +1,293 @@
|
||||
package io.onedev.server.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.hibernate.criterion.Order;
|
||||
import org.hibernate.criterion.Restrictions;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import dev.langchain4j.data.message.AiMessage;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
import dev.langchain4j.data.message.UserMessage;
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import dev.langchain4j.model.chat.response.PartialResponse;
|
||||
import dev.langchain4j.model.chat.response.PartialResponseContext;
|
||||
import dev.langchain4j.model.chat.response.PartialThinking;
|
||||
import dev.langchain4j.model.chat.response.PartialThinkingContext;
|
||||
import dev.langchain4j.model.chat.response.PartialToolCall;
|
||||
import dev.langchain4j.model.chat.response.PartialToolCallContext;
|
||||
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.exception.ExceptionUtils;
|
||||
import io.onedev.server.model.Chat;
|
||||
import io.onedev.server.model.ChatMessage;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.persistence.TransactionService;
|
||||
import io.onedev.server.persistence.annotation.Sessional;
|
||||
import io.onedev.server.persistence.annotation.Transactional;
|
||||
import io.onedev.server.persistence.dao.EntityCriteria;
|
||||
import io.onedev.server.service.ChatService;
|
||||
import io.onedev.server.service.support.ChatResponding;
|
||||
import io.onedev.server.web.SessionListener;
|
||||
import io.onedev.server.web.websocket.WebSocketService;
|
||||
|
||||
@Singleton
|
||||
public class DefaultChatService extends BaseEntityService<Chat> implements ChatService, SessionListener {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultChatService.class);
|
||||
|
||||
private static final int MAX_HISTORY_MESSAGES = 25;
|
||||
|
||||
private static final int MAX_HISTORY_MESSAGE_LEN = 1024;
|
||||
|
||||
private static final int PARTIAL_RESPONSE_NOTIFICATION_INTERVAL = 1000;
|
||||
|
||||
@Inject
|
||||
private ExecutorService executorService;
|
||||
|
||||
@Inject
|
||||
private TransactionService transactionService;
|
||||
|
||||
@Inject
|
||||
private WebSocketService webSocketService;
|
||||
|
||||
private final Map<String, Map<Long, ChatRespondingImpl>> respondings = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public List<Chat> query(User user, User ai, String term, int count) {
|
||||
EntityCriteria<Chat> criteria = EntityCriteria.of(Chat.class);
|
||||
criteria.add(Restrictions.eq(Chat.PROP_USER, user));
|
||||
criteria.add(Restrictions.eq(Chat.PROP_AI, ai));
|
||||
criteria.add(Restrictions.ilike(Chat.PROP_TITLE, "%" + term + "%"));
|
||||
criteria.addOrder(Order.desc(Chat.PROP_DATE));
|
||||
return query(criteria);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createOrUpdate(Chat chat) {
|
||||
dao.persist(chat);
|
||||
}
|
||||
|
||||
@Sessional
|
||||
@Override
|
||||
public ChatResponding getResponding(String sessionId, Chat chat) {
|
||||
return getResponding(sessionId, chat.getId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void sendRequest(String sessionId, ChatMessage request) {
|
||||
var requestId = request.getId();
|
||||
var chatId = request.getChat().getId();
|
||||
var modelSetting = request.getChat().getAi().getAiSetting().getModelSetting();
|
||||
var messages = request.getChat().getSortedMessages()
|
||||
.stream()
|
||||
.filter(it->!it.isError())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (messages.size() > MAX_HISTORY_MESSAGES)
|
||||
messages = messages.subList(messages.size()-MAX_HISTORY_MESSAGES, messages.size());
|
||||
|
||||
var langchain4jMessages = messages.stream()
|
||||
.map(it -> {
|
||||
var content = it.getContent();
|
||||
if (!it.equals(request))
|
||||
content = StringUtils.abbreviate(content, MAX_HISTORY_MESSAGE_LEN);
|
||||
if (it.isRequest())
|
||||
return new UserMessage(content);
|
||||
else
|
||||
return new AiMessage(content);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
var future = executorService.submit(() -> {
|
||||
var latch = new CountDownLatch(1);
|
||||
var handler = new StreamingChatResponseHandler() {
|
||||
|
||||
private long lastPartialResponseNotificationTime = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void onPartialResponse(PartialResponse partialResponse, PartialResponseContext context) {
|
||||
if (latch.getCount() == 0) {
|
||||
context.streamingHandle().cancel();
|
||||
} else {
|
||||
var responding = getResponding(sessionId, chatId, requestId);
|
||||
if (responding != null) {
|
||||
var content = responding.getContent();
|
||||
if (content == null)
|
||||
content = "";
|
||||
content += partialResponse.text();
|
||||
responding.content = content;
|
||||
if (System.currentTimeMillis() - lastPartialResponseNotificationTime > PARTIAL_RESPONSE_NOTIFICATION_INTERVAL) {
|
||||
lastPartialResponseNotificationTime = System.currentTimeMillis();
|
||||
webSocketService.notifyObservableChange(Chat.getPartialResponseObservable(chatId), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartialThinking(PartialThinking partialThinking, PartialThinkingContext context) {
|
||||
if (latch.getCount() == 0)
|
||||
context.streamingHandle().cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartialToolCall(PartialToolCall partialToolCall, PartialToolCallContext context) {
|
||||
if (latch.getCount() == 0)
|
||||
context.streamingHandle().cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleteResponse(ChatResponse completeResponse) {
|
||||
try {
|
||||
createResponseIfNecessary(sessionId, chatId, requestId, completeResponse.aiMessage().text(), null);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
try {
|
||||
createResponseIfNecessary(sessionId, chatId, requestId, "Error getting chat response, check server log for details", error);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
try {
|
||||
modelSetting.getStreamingChatModel().chat(langchain4jMessages, handler);
|
||||
transactionService.run(() -> {
|
||||
var chat = load(chatId);
|
||||
var requests = chat.getMessages().stream().filter(it->it.isRequest()).collect(Collectors.toList());
|
||||
if (requests.size() == 1) {
|
||||
var systemPrompt = String.format("""
|
||||
Summarize provided message to get a compact title with below requirements:
|
||||
1. It should be within %d characters
|
||||
2. Only title is returned, no other text or comments
|
||||
""", Chat.MAX_TITLE_LEN);
|
||||
var title = modelSetting.getChatModel().chat(new SystemMessage(systemPrompt), new UserMessage(requests.get(0).getContent())).aiMessage().text();
|
||||
chat.setTitle(title);
|
||||
webSocketService.notifyObservableChange(Chat.getChangeObservable(chatId), null);
|
||||
}
|
||||
});
|
||||
latch.await();
|
||||
} catch (Exception e) {
|
||||
if (ExceptionUtils.find(e, InterruptedException.class) == null) {
|
||||
logger.error("Error getting chat response", e);
|
||||
String errorMessage = e.getMessage();
|
||||
if (errorMessage == null)
|
||||
errorMessage = "Error getting chat response, check server log for details";
|
||||
createResponseIfNecessary(sessionId, chatId, requestId, errorMessage, e);
|
||||
} else {
|
||||
var responding = getResponding(sessionId, chatId, requestId);
|
||||
if (responding != null && responding.getContent() != null)
|
||||
createResponseIfNecessary(sessionId, chatId, requestId, responding.getContent(), null);
|
||||
}
|
||||
} finally {
|
||||
latch.countDown();
|
||||
var respondingsOfSession = respondings.get(sessionId);
|
||||
if (respondingsOfSession != null) {
|
||||
var responding = respondingsOfSession.get(chatId);
|
||||
if (responding != null) {
|
||||
respondingsOfSession.computeIfPresent(chatId, (k, v)-> {
|
||||
if (v.requestId.equals(requestId))
|
||||
return null;
|
||||
else
|
||||
return v;
|
||||
});
|
||||
webSocketService.notifyObservableChange(Chat.getPartialResponseObservable(chatId), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var previousResponding = respondings.computeIfAbsent(sessionId, it->new ConcurrentHashMap<>()).put(chatId, new ChatRespondingImpl(requestId, future));
|
||||
if (previousResponding != null)
|
||||
previousResponding.cancel();
|
||||
}
|
||||
|
||||
private ChatRespondingImpl getResponding(String sessionId, Long chatId) {
|
||||
var respondingsOfSession = respondings.get(sessionId);
|
||||
if (respondingsOfSession != null) {
|
||||
return respondingsOfSession.get(chatId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ChatRespondingImpl getResponding(String sessionId, Long chatId, Long requestId) {
|
||||
var responding = getResponding(sessionId, chatId);
|
||||
if (responding != null && responding.requestId.equals(requestId))
|
||||
return responding;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private void createResponseIfNecessary(String sessionId, Long chatId, Long requestId, String content, @Nullable Throwable throwable) {
|
||||
var responding = getResponding(sessionId, chatId, requestId);
|
||||
if (responding != null) {
|
||||
transactionService.run(() -> {
|
||||
var chat = load(chatId);
|
||||
if (throwable != null)
|
||||
logger.error("Error getting chat response", throwable);
|
||||
var response = new ChatMessage();
|
||||
response.setChat(chat);
|
||||
response.setError(throwable != null);
|
||||
response.setContent(content);
|
||||
dao.persist(response);
|
||||
webSocketService.notifyObservableChange(Chat.getNewMessagesObservable(chatId), null);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionCreated(String sessionId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionDestroyed(String sessionId) {
|
||||
var respondingsOfSession = respondings.remove(sessionId);
|
||||
if (respondingsOfSession != null) {
|
||||
for (var responding : respondingsOfSession.values())
|
||||
responding.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChatRespondingImpl implements ChatResponding {
|
||||
|
||||
private final Long requestId;
|
||||
|
||||
private final Future<?> future;
|
||||
|
||||
private volatile String content;
|
||||
|
||||
ChatRespondingImpl(Long requestId, Future<?> future) {
|
||||
this.requestId = requestId;
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package io.onedev.server.service.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import io.onedev.server.model.GroupEntitlement;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.persistence.annotation.Transactional;
|
||||
import io.onedev.server.service.GroupEntitlementService;
|
||||
|
||||
@Singleton
|
||||
public class DefaultGroupEntitlementService extends BaseEntityService<GroupEntitlement>
|
||||
implements GroupEntitlementService {
|
||||
|
||||
@Override
|
||||
public List<GroupEntitlement> query() {
|
||||
return query(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return count(true);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void syncEntitlements(User ai, Collection<GroupEntitlement> entitlements) {
|
||||
var newGroups = entitlements.stream()
|
||||
.map(GroupEntitlement::getGroup)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
ai.getGroupEntitlements().removeIf(it -> {
|
||||
if (!newGroups.contains(it.getGroup())) {
|
||||
delete(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
var existingGroups = ai.getGroupEntitlements().stream()
|
||||
.map(GroupEntitlement::getGroup)
|
||||
.collect(Collectors.toSet());
|
||||
entitlements.stream()
|
||||
.filter(it -> !existingGroups.contains(it.getGroup()))
|
||||
.forEach(it -> {
|
||||
ai.getGroupEntitlements().add(it);
|
||||
dao.persist(it);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void create(GroupEntitlement entitlement) {
|
||||
Preconditions.checkState(entitlement.isNew());
|
||||
dao.persist(entitlement);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.service.impl;
|
||||
|
||||
import static io.onedev.server.model.User.Type.SERVICE;
|
||||
import static java.lang.Integer.MAX_VALUE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -170,7 +171,7 @@ public class DefaultIssueChangeService extends BaseEntityService<IssueChange>
|
||||
@Transactional
|
||||
@Override
|
||||
public void create(IssueChange change, @Nullable String note) {
|
||||
create(change, note, !change.getUser().isServiceAccount());
|
||||
create(change, note, change.getUser().getType() != SERVICE);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@ -317,7 +318,7 @@ public class DefaultIssueChangeService extends BaseEntityService<IssueChange>
|
||||
@Transactional
|
||||
@Override
|
||||
public void addSchedule(User user, Issue issue, Iteration iteration) {
|
||||
addSchedule(user, issue, iteration, !user.isServiceAccount());
|
||||
addSchedule(user, issue, iteration, user.getType() != SERVICE);
|
||||
}
|
||||
|
||||
protected void addSchedule(User user, Issue issue, Iteration iteration, boolean sendNotifications) {
|
||||
@ -348,7 +349,7 @@ public class DefaultIssueChangeService extends BaseEntityService<IssueChange>
|
||||
@Transactional
|
||||
@Override
|
||||
public void removeSchedule(User user, Issue issue, Iteration iteration) {
|
||||
removeSchedule(user, issue, iteration, !user.isServiceAccount());
|
||||
removeSchedule(user, issue, iteration, user.getType() != SERVICE);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
package io.onedev.server.service.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import io.onedev.server.model.ProjectEntitlement;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.persistence.annotation.Transactional;
|
||||
import io.onedev.server.service.ProjectEntitlementService;
|
||||
|
||||
@Singleton
|
||||
public class DefaultProjectEntitlementService extends BaseEntityService<ProjectEntitlement>
|
||||
implements ProjectEntitlementService {
|
||||
|
||||
@Override
|
||||
public List<ProjectEntitlement> query() {
|
||||
return query(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return count(true);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void syncEntitlements(User ai, Collection<ProjectEntitlement> entitlements) {
|
||||
var newProjects = entitlements.stream()
|
||||
.map(ProjectEntitlement::getProject)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
ai.getProjectEntitlements().removeIf(it -> {
|
||||
if (!newProjects.contains(it.getProject())) {
|
||||
delete(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
var existingProjects = ai.getProjectEntitlements().stream()
|
||||
.map(ProjectEntitlement::getProject)
|
||||
.collect(Collectors.toSet());
|
||||
entitlements.stream()
|
||||
.filter(it -> !existingProjects.contains(it.getProject()))
|
||||
.forEach(it -> {
|
||||
ai.getProjectEntitlements().add(it);
|
||||
dao.persist(it);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void create(ProjectEntitlement entitlement) {
|
||||
Preconditions.checkState(entitlement.isNew());
|
||||
dao.persist(entitlement);
|
||||
}
|
||||
|
||||
}
|
||||
@ -30,7 +30,7 @@ import io.onedev.server.event.entity.EntityPersisted;
|
||||
import io.onedev.server.event.system.SystemStarting;
|
||||
import io.onedev.server.model.Setting;
|
||||
import io.onedev.server.model.Setting.Key;
|
||||
import io.onedev.server.model.support.administration.AISetting;
|
||||
import io.onedev.server.model.support.administration.AiSetting;
|
||||
import io.onedev.server.model.support.administration.AgentSetting;
|
||||
import io.onedev.server.model.support.administration.AlertSetting;
|
||||
import io.onedev.server.model.support.administration.AuditSetting;
|
||||
@ -168,8 +168,8 @@ public class DefaultSettingService extends BaseEntityService<Setting> implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public AISetting getAISetting() {
|
||||
return (AISetting) getSettingValue(Key.AI);
|
||||
public AiSetting getAiSetting() {
|
||||
return (AiSetting) getSettingValue(Key.AI);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -304,7 +304,7 @@ public class DefaultSettingService extends BaseEntityService<Setting> implements
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void saveAISetting(AISetting aiSetting) {
|
||||
public void saveAiSetting(AiSetting aiSetting) {
|
||||
saveSetting(Key.AI, aiSetting);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
package io.onedev.server.service.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.model.UserEntitlement;
|
||||
import io.onedev.server.persistence.annotation.Transactional;
|
||||
import io.onedev.server.service.UserEntitlementService;
|
||||
|
||||
@Singleton
|
||||
public class DefaultUserEntitlementService extends BaseEntityService<UserEntitlement>
|
||||
implements UserEntitlementService {
|
||||
|
||||
@Override
|
||||
public List<UserEntitlement> query() {
|
||||
return query(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return count(true);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void syncEntitlements(User ai, Collection<UserEntitlement> entitlements) {
|
||||
var newUsers = entitlements.stream()
|
||||
.map(UserEntitlement::getUser)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
ai.getUserEntitlements().removeIf(it -> {
|
||||
if (!newUsers.contains(it.getUser())) {
|
||||
delete(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
var existingUsers = ai.getUserEntitlements().stream()
|
||||
.map(UserEntitlement::getUser)
|
||||
.collect(Collectors.toSet());
|
||||
entitlements.stream()
|
||||
.filter(it -> !existingUsers.contains(it.getUser()))
|
||||
.forEach(it -> {
|
||||
ai.getUserEntitlements().add(it);
|
||||
dao.persist(it);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void create(UserEntitlement entitlement) {
|
||||
Preconditions.checkState(entitlement.isNew());
|
||||
dao.persist(entitlement);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package io.onedev.server.service.impl;
|
||||
|
||||
import static io.onedev.server.model.User.Type.SERVICE;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@ -18,13 +20,11 @@ import org.hibernate.query.Query;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.hazelcast.core.HazelcastInstance;
|
||||
|
||||
import io.onedev.server.SubscriptionService;
|
||||
import io.onedev.server.cluster.ClusterService;
|
||||
import io.onedev.server.event.Listen;
|
||||
import io.onedev.server.event.entity.EntityPersisted;
|
||||
import io.onedev.server.event.entity.EntityRemoved;
|
||||
import io.onedev.server.event.system.SystemStarting;
|
||||
import io.onedev.server.exception.NoSubscriptionException;
|
||||
import io.onedev.server.model.AbstractEntity;
|
||||
import io.onedev.server.model.EmailAddress;
|
||||
import io.onedev.server.model.Project;
|
||||
@ -68,9 +68,6 @@ public class DefaultUserService extends BaseEntityService<User> implements UserS
|
||||
|
||||
@Inject
|
||||
private ClusterService clusterService;
|
||||
|
||||
@Inject
|
||||
private SubscriptionService subscriptionService;
|
||||
|
||||
private volatile UserCache cache;
|
||||
|
||||
@ -115,8 +112,6 @@ public class DefaultUserService extends BaseEntityService<User> implements UserS
|
||||
@Override
|
||||
public void create(User user) {
|
||||
Preconditions.checkState(user.isNew());
|
||||
if (user.isServiceAccount() && !subscriptionService.isSubscriptionActive())
|
||||
throw new NoSubscriptionException("Service account");
|
||||
user.setName(user.getName().toLowerCase());
|
||||
dao.persist(user);
|
||||
}
|
||||
@ -385,7 +380,7 @@ public class DefaultUserService extends BaseEntityService<User> implements UserS
|
||||
|
||||
user.setPassword(null);
|
||||
user.setPasswordResetCode(null);
|
||||
user.setServiceAccount(true);
|
||||
user.setType(SERVICE);
|
||||
|
||||
dao.persist(user);
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
package io.onedev.server.service.support;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public interface ChatResponding {
|
||||
|
||||
@Nullable
|
||||
String getContent();
|
||||
|
||||
void cancel();
|
||||
|
||||
}
|
||||
@ -6,12 +6,14 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.util.MapProxy;
|
||||
import io.onedev.server.util.Similarities;
|
||||
|
||||
@ -53,19 +55,23 @@ public class UserCache extends MapProxy<Long, UserFacade> {
|
||||
public UserCache clone() {
|
||||
return new UserCache(new HashMap<>(delegate));
|
||||
}
|
||||
|
||||
public Collection<User> getUsers(boolean includeDisabled) {
|
||||
|
||||
public Collection<User> getUsers(Function<UserFacade, Boolean> filter) {
|
||||
UserService userService = OneDev.getInstance(UserService.class);
|
||||
return entrySet().stream()
|
||||
.filter(it -> includeDisabled || !it.getValue().isDisabled())
|
||||
.filter(it -> filter.apply(it.getValue()))
|
||||
.map(it -> userService.load(it.getKey()))
|
||||
.collect(toSet());
|
||||
}
|
||||
|
||||
public Collection<User> getUsers() {
|
||||
return getUsers(false);
|
||||
return getUsers(it->true);
|
||||
}
|
||||
|
||||
public Comparator<User> comparingDisplayName() {
|
||||
return comparingDisplayName(Set.of());
|
||||
}
|
||||
|
||||
public Comparator<User> comparingDisplayName(Collection<User> topUsers) {
|
||||
return (o1, o2) -> {
|
||||
if (topUsers.contains(o1)) {
|
||||
|
||||
@ -12,16 +12,20 @@ public class UserFacade extends EntityFacade {
|
||||
|
||||
private final String fullName;
|
||||
|
||||
private final boolean serviceAccount;
|
||||
private final User.Type type;
|
||||
|
||||
private final boolean disabled;
|
||||
|
||||
private final boolean entitleToAll;
|
||||
|
||||
public UserFacade(Long id, String name, @Nullable String fullName, boolean serviceAccount, boolean disabled) {
|
||||
public UserFacade(Long id, String name, @Nullable String fullName, User.Type type,
|
||||
boolean disabled, boolean entitleToAll) {
|
||||
super(id);
|
||||
this.name = name;
|
||||
this.fullName = fullName;
|
||||
this.serviceAccount = serviceAccount;
|
||||
this.type = type;
|
||||
this.disabled = disabled;
|
||||
this.entitleToAll = entitleToAll;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@ -58,12 +62,16 @@ public class UserFacade extends EntityFacade {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isServiceAccount() {
|
||||
return serviceAccount;
|
||||
public User.Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isDisabled() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public boolean isEntitleToAll() {
|
||||
return entitleToAll;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package io.onedev.server.web;
|
||||
|
||||
public interface SessionListener {
|
||||
|
||||
void sessionCreated(String sessionId);
|
||||
|
||||
void sessionDestroyed(String sessionId);
|
||||
|
||||
}
|
||||
@ -14,6 +14,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.wicket.Application;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.DefaultExceptionMapper;
|
||||
import org.apache.wicket.ISessionListener;
|
||||
import org.apache.wicket.Page;
|
||||
import org.apache.wicket.RuntimeConfigurationType;
|
||||
import org.apache.wicket.Session;
|
||||
@ -82,8 +83,8 @@ import io.onedev.server.web.translation.TranslationResolver;
|
||||
import io.onedev.server.web.translation.TranslationStringResourceLoader;
|
||||
import io.onedev.server.web.translation.TranslationTagHandler;
|
||||
import io.onedev.server.web.util.AbsoluteUrlRenderer;
|
||||
import io.onedev.server.web.websocket.WebSocketService;
|
||||
import io.onedev.server.web.websocket.WebSocketMessages;
|
||||
import io.onedev.server.web.websocket.WebSocketService;
|
||||
|
||||
@Singleton
|
||||
public class WebApplication extends org.apache.wicket.protocol.http.WebApplication {
|
||||
@ -173,6 +174,17 @@ public class WebApplication extends org.apache.wicket.protocol.http.WebApplicati
|
||||
}
|
||||
});
|
||||
|
||||
getSessionListeners().add(new ISessionListener() {
|
||||
|
||||
@Override
|
||||
public void onCreated(Session session) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbound(String sessionId) {
|
||||
}
|
||||
|
||||
});
|
||||
getAjaxRequestTargetListeners().add(new AjaxRequestTarget.IListener() {
|
||||
|
||||
@Override
|
||||
|
||||
@ -35,6 +35,12 @@ public class WebSession extends org.apache.wicket.protocol.http.WebSession {
|
||||
private Map<Class<?>, String> redirectUrlsAfterDelete = new ConcurrentHashMap<>();
|
||||
|
||||
private Set<Long> expandedProjectIds = new ConcurrentHashSet<>();
|
||||
|
||||
private boolean chatVisible;
|
||||
|
||||
private Long activeChatId;
|
||||
|
||||
private String chatInput;
|
||||
|
||||
public WebSession(Request request) {
|
||||
super(request);
|
||||
@ -118,6 +124,32 @@ public class WebSession extends org.apache.wicket.protocol.http.WebSession {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public boolean isChatVisible() {
|
||||
return chatVisible;
|
||||
}
|
||||
|
||||
public void setChatVisible(boolean chatVisible) {
|
||||
this.chatVisible = chatVisible;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getActiveChatId() {
|
||||
return activeChatId;
|
||||
}
|
||||
|
||||
public void setActiveChatId(Long activeChatId) {
|
||||
this.activeChatId = activeChatId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getChatInput() {
|
||||
return chatInput;
|
||||
}
|
||||
|
||||
public void setChatInput(String chatInput) {
|
||||
this.chatInput = chatInput;
|
||||
}
|
||||
|
||||
public static WebSession from(HttpSession session) {
|
||||
String attributeName = "wicket:" + OneDev.getInstance(WicketServlet.class).getServletName() + ":session";
|
||||
return (WebSession) session.getAttribute(attributeName);
|
||||
|
||||
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763890408372" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6809" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M619.776 543.936a39.552 39.552 0 0 1 21.696 21.888l144.96 362.752a34.88 34.88 0 1 1-64.832 25.92l-40.704-101.76H525.632l-41.856 102.08a34.88 34.88 0 0 1-44.8 19.328l-0.768-0.32a34.88 34.88 0 0 1-19.008-45.568l148.928-362.752a39.552 39.552 0 0 1 51.648-21.568z m229.76-3.2c19.2 0 34.88 15.616 34.816 34.88l-0.576 364.48a34.88 34.88 0 0 1-69.76-0.128l0.512-364.416c0-19.264 15.68-34.88 35.008-34.88zM378.496 55.424L459.2 121.6c21.12-3.968 38.272-6.016 52.096-6.144h1.152c13.76 0 31.36 1.92 53.12 5.824l66.368-63.36A39.552 39.552 0 0 1 678.4 51.584l0.768 0.448 114.56 66.496a39.552 39.552 0 0 1 19.392 38.784L800 269.952c9.984 13.76 17.344 25.088 22.528 34.432l0.512 0.96c4.928 9.216 9.984 20.736 15.104 34.752l113.088 53.248c12.288 5.76 20.16 18.048 20.16 31.616V492.8a34.88 34.88 0 0 1-69.824 0v-45.632l-106.048-49.472-13.504-6.4-4.8-14.08a269.632 269.632 0 0 0-15.68-38.72 338.88 338.88 0 0 0-25.664-37.952l-8.448-11.136 1.664-13.888 12.48-106.496-77.632-45.12-62.464 60.224-13.248 12.8-17.92-3.712c-26.432-5.376-46.016-8-57.856-8-11.648 0-30.336 2.56-55.168 7.936l-16.448 3.52-12.992-10.624-77.568-63.488-68.48 38.016 21.76 99.648 4.224 19.392-14.528 13.568c-17.792 16.64-30.016 29.952-36.288 39.168a185.728 185.728 0 0 0-19.2 41.152l-5.44 15.36-15.296 5.696-93.632 34.816v101.632l94.464 37.76 16.064 6.4 4.608 16.768c4.8 17.536 9.728 30.528 14.272 38.592 4.416 7.744 12.032 17.088 22.912 27.648l13.44 12.992-3.392 18.368-19.072 104.384 77.376 49.92c16 10.24 20.736 31.36 10.88 47.552l-0.448 0.64a34.88 34.88 0 0 1-48.256 10.432l-94.4-60.8a39.552 39.552 0 0 1-17.472-40.32l19.52-106.816-0.896-0.96a172.928 172.928 0 0 1-20.096-27.2L192 664.96c-5.696-10.048-10.88-22.4-15.616-37.056l-0.192-0.576-97.792-39.04a39.616 39.616 0 0 1-24.896-35.968V408.32c0-16.512 10.304-31.36 25.792-37.12l98.432-36.544c6.592-16.256 13.44-29.568 20.672-40.512l0.896-1.28c7.296-10.624 17.728-22.656 31.424-36.416l-21.952-103.04a39.552 39.552 0 0 1 18.688-42.56l0.768-0.384 106.048-58.88a39.552 39.552 0 0 1 44.288 3.904z m225.856 605.632l-50.048 121.792h98.688l-48.64-121.792z m-87.68-350.848c79.808 0 149.76 51.328 174.528 125.952a34.88 34.88 0 0 1-66.304 21.952 114.112 114.112 0 1 0-157.312 138.88 34.88 34.88 0 1 1-30.08 63.04 183.872 183.872 0 0 1 79.168-349.824z" p-id="6810"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1759963509092" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11021" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1013.775951 516.722584a12.8 12.8 0 0 1-9.322496 9.322495l-198.487702 49.599298a320 320 0 0 0-232.836121 232.836121l-49.644553 198.532957a12.8 12.8 0 0 1-24.844904-0.045255l-49.599298-198.487702a320 320 0 0 0-232.83612-232.836121L17.626545 525.95457a12.8 12.8 0 0 1 0-24.799649l198.532957-49.644553a320 320 0 0 0 232.836121-232.836121L498.640175 20.2318a12.8 12.8 0 0 1 24.844904-0.045255l49.644553 198.532957a320 320 0 0 0 232.836121 232.836121l198.487702 49.599298a12.8 12.8 0 0 1 9.322496 15.567663zM713.374363 513.554745l-9.956064-4.525483a409.792 409.792 0 0 1-187.807561-187.807561l-4.525483-9.956064-4.615993 9.956064a409.792 409.792 0 0 1-187.807561 187.807561l-9.956064 4.525483 10.001319 4.661248a409.792 409.792 0 0 1 187.762306 187.762306l4.570738 9.910809 4.615993-9.865554a409.792 409.792 0 0 1 187.807561-187.807561l9.910809-4.661248z" p-id="11022"></path><path d="M887.453079 38.673217l5.656855 22.763181c11.087434 44.576011 45.888402 79.376979 90.509668 90.509668l22.763181 5.656855a12.8 12.8 0 0 1-0.045255 24.844904l-22.717926 5.702109a124.416 124.416 0 0 0-90.509668 90.509668l-5.702109 22.717926a12.8 12.8 0 0 1-24.79965 0l-5.702109-22.717926a124.416 124.416 0 0 0-90.509668-90.509668l-22.717926-5.702109a12.8 12.8 0 0 1-0.045255-24.844904l22.763181-5.656855c44.576011-11.177944 79.376979-45.978911 90.509668-90.509668l5.656855-22.763181a12.8 12.8 0 0 1 24.890158 0z" p-id="11023"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1763871574824" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1900" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M618.666667 149.333333a42.666667 42.666667 0 0 0-42.666667-42.666666h-128a42.666667 42.666667 0 1 0 0 85.333333h21.333333v42.666667h-149.333333a170.666667 170.666667 0 0 0-170.666667 170.666666v320a170.666667 170.666667 0 0 0 170.666667 170.666667h384a170.666667 170.666667 0 0 0 170.666667-170.666667V405.333333a170.666667 170.666667 0 0 0-170.666667-170.666666h-149.333333V192h21.333333a42.666667 42.666667 0 0 0 42.666667-42.666667zM320 810.666667a85.333333 85.333333 0 0 1-85.333333-85.333334V405.333333a85.333333 85.333333 0 0 1 85.333333-85.333333h384a85.333333 85.333333 0 0 1 85.333333 85.333333v320a85.333333 85.333333 0 0 1-85.333333 85.333334H320zM64 448a42.666667 42.666667 0 0 0-42.666667 42.666667v149.333333a42.666667 42.666667 0 1 0 85.333334 0v-149.333333a42.666667 42.666667 0 0 0-42.666667-42.666667z m896 0a42.666667 42.666667 0 0 0-42.666667 42.666667v149.333333a42.666667 42.666667 0 1 0 85.333334 0v-149.333333a42.666667 42.666667 0 0 0-42.666667-42.666667z m-512 117.333333a53.333333 53.333333 0 1 0-106.666667 0 53.333333 53.333333 0 0 0 106.666667 0z m234.666667 0a53.333333 53.333333 0 1 0-106.666667 0 53.333333 53.333333 0 0 0 106.666667 0z" p-id="1901"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.5 KiB |
@ -186,14 +186,14 @@ public class AgentQueryBehavior extends ANTLRAssistBehavior {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getSettingService().getAISetting().getLiteModelSetting() == null)
|
||||
if (getSettingService().getAiSetting().getLiteModelSetting() == null)
|
||||
hints.add(_T("<a href='/~administration/settings/lite-ai-model' target='_blank'>Set up AI</a> to query with natural language</a>"));
|
||||
return hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
|
||||
var liteModel = getSettingService().getAISetting().getLiteModel();
|
||||
var liteModel = getSettingService().getAiSetting().getLiteModel();
|
||||
if (liteModel != null) {
|
||||
return new NaturalLanguageTranslator(liteModel) {
|
||||
|
||||
|
||||
@ -256,14 +256,14 @@ public class BuildQueryBehavior extends ANTLRAssistBehavior {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getSettingService().getAISetting().getLiteModelSetting() == null)
|
||||
if (getSettingService().getAiSetting().getLiteModelSetting() == null)
|
||||
hints.add(_T("<a href='/~administration/settings/lite-ai-model' target='_blank'>Set up AI</a> to query with natural language"));
|
||||
return hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
|
||||
var liteModel = getSettingService().getAISetting().getLiteModel();
|
||||
var liteModel = getSettingService().getAiSetting().getLiteModel();
|
||||
if (liteModel != null) {
|
||||
return new NaturalLanguageTranslator(liteModel) {
|
||||
|
||||
|
||||
@ -159,7 +159,7 @@ public class CommitQueryBehavior extends ANTLRAssistBehavior {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getSettingService().getAISetting().getLiteModelSetting() == null)
|
||||
if (getSettingService().getAiSetting().getLiteModelSetting() == null)
|
||||
hints.add(_T("<a href='/~administration/settings/lite-ai-model' target='_blank'>Set up AI</a> to query with natural language</a>"));
|
||||
return hints;
|
||||
}
|
||||
@ -201,7 +201,7 @@ public class CommitQueryBehavior extends ANTLRAssistBehavior {
|
||||
|
||||
@Override
|
||||
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
|
||||
var liteModel = getSettingService().getAISetting().getLiteModel();
|
||||
var liteModel = getSettingService().getAiSetting().getLiteModel();
|
||||
if (liteModel != null) {
|
||||
return new NaturalLanguageTranslator(liteModel) {
|
||||
|
||||
|
||||
@ -416,7 +416,7 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getSettingService().getAISetting().getLiteModelSetting() == null)
|
||||
if (getSettingService().getAiSetting().getLiteModelSetting() == null)
|
||||
hints.add(_T("<a href='/~administration/settings/lite-ai-model' target='_blank'>Set up AI</a> to query with natural language</a>"));
|
||||
return hints;
|
||||
}
|
||||
@ -429,7 +429,7 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
|
||||
|
||||
@Override
|
||||
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
|
||||
var liteModel = getSettingService().getAISetting().getLiteModel();
|
||||
var liteModel = getSettingService().getAiSetting().getLiteModel();
|
||||
if (liteModel != null) {
|
||||
return new NaturalLanguageTranslator(liteModel) {
|
||||
|
||||
|
||||
@ -226,14 +226,14 @@ public class PackQueryBehavior extends ANTLRAssistBehavior {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getSettingService().getAISetting().getLiteModelSetting() == null)
|
||||
if (getSettingService().getAiSetting().getLiteModelSetting() == null)
|
||||
hints.add(_T("<a href='/~administration/settings/lite-ai-model' target='_blank'>Set up AI</a> to query with natural language</a>"));
|
||||
return hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
|
||||
var liteModel = getSettingService().getAISetting().getLiteModel();
|
||||
var liteModel = getSettingService().getAiSetting().getLiteModel();
|
||||
if (liteModel != null) {
|
||||
return new NaturalLanguageTranslator(liteModel) {
|
||||
|
||||
|
||||
@ -225,14 +225,14 @@ public class ProjectQueryBehavior extends ANTLRAssistBehavior {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getSettingService().getAISetting().getLiteModelSetting() == null)
|
||||
if (getSettingService().getAiSetting().getLiteModelSetting() == null)
|
||||
hints.add(_T("<a href='/~administration/settings/lite-ai-model' target='_blank'>Set up AI</a> to query with natural language</a>"));
|
||||
return hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
|
||||
var liteModel = getSettingService().getAISetting().getLiteModel();
|
||||
var liteModel = getSettingService().getAiSetting().getLiteModel();
|
||||
if (liteModel != null) {
|
||||
return new NaturalLanguageTranslator(liteModel) {
|
||||
|
||||
|
||||
@ -261,7 +261,7 @@ public class PullRequestQueryBehavior extends ANTLRAssistBehavior {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getSettingService().getAISetting().getLiteModelSetting() == null)
|
||||
if (getSettingService().getAiSetting().getLiteModelSetting() == null)
|
||||
hints.add(_T("<a href='/~administration/settings/lite-ai-model' target='_blank'>Set up AI</a> to query with natural language</a>"));
|
||||
return hints;
|
||||
}
|
||||
@ -274,7 +274,7 @@ public class PullRequestQueryBehavior extends ANTLRAssistBehavior {
|
||||
|
||||
@Override
|
||||
protected NaturalLanguageTranslator getNaturalLanguageTranslator() {
|
||||
var liteModel = getSettingService().getAISetting().getLiteModel();
|
||||
var liteModel = getSettingService().getAiSetting().getLiteModel();
|
||||
if (liteModel != null) {
|
||||
return new NaturalLanguageTranslator(liteModel) {
|
||||
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
<wicket:panel>
|
||||
<div class="ui-resizable-handle ui-resizable-w"></div>
|
||||
<div class="head d-flex align-items-center justify-content-between px-4 py-3 border-bottom">
|
||||
<h5 class="card-title mb-0"><wicket:t>Chat with</wicket:t> <a wicket:id="aiSelector" class="ml-2"><img wicket:id="avatar"></img> <span wicket:id="name"></span> <wicket:svg href="arrow" class="icon rotate-90"></wicket:svg></a></h5>
|
||||
<a wicket:id="close" t:data-tippy-content="Close" class="btn btn-xs btn-icon btn-light btn-hover-primary"><wicket:svg href="times" class="icon"/></a>
|
||||
</div>
|
||||
<div class="body d-flex flex-column flex-grow-1 p-4 position-relative" style="overflow: hidden;">
|
||||
<div wicket:id="chatSelectorContainer" class="chat-selector d-flex flex-shrink-0 mb-3">
|
||||
<input wicket:id="chatSelector" t:placeholder="New chat" type="hidden" class="form-control">
|
||||
<a wicket:id="newChat" t:data-tippy-content="New chat" class="btn btn-light btn-hover-primary btn-icon flex-shrink-0"><wicket:svg href="plus" class="icon icon-lg"></wicket:svg></a>
|
||||
<a wicket:id="deleteChat" t:data-tippy-content="Delete chat" class="btn btn-light btn-hover-danger btn-icon flex-shrink-0"><wicket:svg href="trash" class="icon icon-lg"></wicket:svg></a>
|
||||
</div>
|
||||
<ul class="messages list-unstyled mb-2 flex-grow-1 overflow-auto">
|
||||
<li wicket:id="messages" class="message">
|
||||
<div wicket:id="content"></div>
|
||||
</li>
|
||||
<li wicket:id="responding" class="responding">
|
||||
<div class="breathing-dot"></div>
|
||||
<div wicket:id="content" class="mt-2"></div>
|
||||
</li>
|
||||
</ul>
|
||||
<form wicket:id="send" class="send flex-shrink-0 position-relative">
|
||||
<textarea wicket:id="input" class="form-control" t:placeholder="Type your message here"></textarea>
|
||||
<a wicket:id="submit" class="submit position-absolute btn btn-primary btn-icon btn-sm" t:data-tippy-content="Send"><wicket:svg href="arrow2" class="icon rotate-270"></wicket:svg></a>
|
||||
<a wicket:id="stop" class="stop position-absolute btn btn-primary btn-icon btn-sm" t:data-tippy-content="Stop"><wicket:svg href="stop" class="icon"></wicket:svg></a>
|
||||
</form>
|
||||
</div>
|
||||
</wicket:panel>
|
||||
@ -0,0 +1,524 @@
|
||||
package io.onedev.server.web.component.ai.chat;
|
||||
|
||||
import static io.onedev.server.security.SecurityUtils.getUser;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
|
||||
import org.apache.wicket.ajax.markup.html.AjaxLink;
|
||||
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
|
||||
import org.apache.wicket.behavior.AttributeAppender;
|
||||
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
|
||||
import org.apache.wicket.markup.head.IHeaderResponse;
|
||||
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
|
||||
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
|
||||
import org.apache.wicket.markup.html.WebMarkupContainer;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.form.TextArea;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.markup.repeater.RepeatingView;
|
||||
import org.apache.wicket.model.AbstractReadOnlyModel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.cycle.RequestCycle;
|
||||
import org.apache.wicket.request.http.WebRequest;
|
||||
import org.apache.wicket.request.http.WebResponse;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONWriter;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import io.onedev.server.model.Chat;
|
||||
import io.onedev.server.model.ChatMessage;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.persistence.dao.Dao;
|
||||
import io.onedev.server.service.ChatService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.service.support.ChatResponding;
|
||||
import io.onedev.server.web.WebConstants;
|
||||
import io.onedev.server.web.WebSession;
|
||||
import io.onedev.server.web.behavior.ChangeObserver;
|
||||
import io.onedev.server.web.behavior.OnTypingDoneBehavior;
|
||||
import io.onedev.server.web.component.MultilineLabel;
|
||||
import io.onedev.server.web.component.floating.FloatingPanel;
|
||||
import io.onedev.server.web.component.markdown.MarkdownViewer;
|
||||
import io.onedev.server.web.component.menu.MenuItem;
|
||||
import io.onedev.server.web.component.menu.MenuLink;
|
||||
import io.onedev.server.web.component.select2.ChoiceProvider;
|
||||
import io.onedev.server.web.component.select2.ResponseFiller;
|
||||
import io.onedev.server.web.component.select2.Select2Choice;
|
||||
import io.onedev.server.web.component.user.UserAvatar;
|
||||
|
||||
public class ChatPanel extends Panel {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String COOKIE_ACTIVE_AI = "active-ai";
|
||||
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
@Inject
|
||||
private ChatService chatService;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
private Long activeAiId;
|
||||
|
||||
private RepeatingView messagesView;
|
||||
|
||||
private WebMarkupContainer respondingContainer;
|
||||
|
||||
public ChatPanel(String componentId) {
|
||||
super(componentId);
|
||||
|
||||
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
|
||||
Cookie cookie = request.getCookie(COOKIE_ACTIVE_AI);
|
||||
if (cookie != null)
|
||||
activeAiId = Long.valueOf(cookie.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
var aiSelector = new MenuLink("aiSelector") {
|
||||
|
||||
@Override
|
||||
protected List<MenuItem> getMenuItems(FloatingPanel dropdown) {
|
||||
var activeAI = getActiveAI();
|
||||
var menuItems = new ArrayList<MenuItem>();
|
||||
for (var ai : getUser().getEntitledAis()) {
|
||||
menuItems.add(new MenuItem() {
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return ai.getDisplayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebMarkupContainer newLink(String id) {
|
||||
return new AjaxLink<Void>(id) {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
setActiveAI(ai);
|
||||
WebSession.get().setActiveChatId(null);
|
||||
target.add(ChatPanel.this);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return ai.equals(activeAI);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBeforeRender() {
|
||||
addOrReplace(new UserAvatar("avatar", getActiveAI()));
|
||||
addOrReplace(new Label("name", getActiveAI().getDisplayName()));
|
||||
super.onBeforeRender();
|
||||
}
|
||||
};
|
||||
add(aiSelector);
|
||||
|
||||
add(new AjaxLink<Void>("close") {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
hide(target);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var chatSelectorContainer = new WebMarkupContainer("chatSelectorContainer");
|
||||
chatSelectorContainer.setOutputMarkupId(true);
|
||||
chatSelectorContainer.add(new Select2Choice<Chat>("chatSelector", new IModel<Chat>() {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chat getObject() {
|
||||
return getActiveChat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setObject(Chat object) {
|
||||
WebSession.get().setActiveChatId(object.getId());
|
||||
}
|
||||
|
||||
}, new ChoiceProvider<Chat>() {
|
||||
|
||||
@Override
|
||||
public void query(String term, int page, io.onedev.server.web.component.select2.Response<Chat> response) {
|
||||
var count = (page+1) * WebConstants.PAGE_SIZE + 1;
|
||||
var chats = chatService.query(getUser(), getActiveAI(), term, count);
|
||||
new ResponseFiller<>(response).fill(chats, page, WebConstants.PAGE_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(Chat choice, JSONWriter writer) throws JSONException {
|
||||
writer.key("id").value(choice.getId()).key("text").value(choice.getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Chat> toChoices(Collection<String> ids) {
|
||||
return ids.stream().map(it->chatService.load(Long.valueOf(it))).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}).add(new AjaxFormComponentUpdatingBehavior("change") {
|
||||
|
||||
@Override
|
||||
protected void onUpdate(AjaxRequestTarget target) {
|
||||
target.add(ChatPanel.this);
|
||||
}
|
||||
|
||||
}));
|
||||
chatSelectorContainer.add(new ChangeObserver() {
|
||||
|
||||
@Override
|
||||
protected Collection<String> findObservables() {
|
||||
var chat = getActiveChat();
|
||||
if (chat != null)
|
||||
return Collections.singleton(Chat.getChangeObservable(chat.getId()));
|
||||
else
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public void onObservableChanged(IPartialPageRequestHandler handler, Collection<String> changedObservables) {
|
||||
handler.add(chatSelectorContainer);
|
||||
}
|
||||
|
||||
});
|
||||
chatSelectorContainer.add(new ChangeObserver() {
|
||||
|
||||
@Override
|
||||
protected Collection<String> findObservables() {
|
||||
var chat = getActiveChat();
|
||||
if (chat != null)
|
||||
return Collections.singleton(Chat.getNewMessagesObservable(chat.getId()));
|
||||
else
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObservableChanged(IPartialPageRequestHandler handler, Collection<String> changedObservables) {
|
||||
showNewMessages(handler);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
chatSelectorContainer.add(new AjaxLink<Void>("newChat") {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
WebSession.get().setActiveChatId(null);
|
||||
target.add(ChatPanel.this);
|
||||
}
|
||||
|
||||
});
|
||||
chatSelectorContainer.add(new AjaxLink<Void>("deleteChat") {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
getSession().success(_T("Chat deleted"));
|
||||
chatService.delete(getActiveChat());
|
||||
WebSession.get().setActiveChatId(null);
|
||||
target.add(ChatPanel.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(getActiveChat() != null);
|
||||
}
|
||||
|
||||
});
|
||||
add(chatSelectorContainer);
|
||||
|
||||
respondingContainer = new WebMarkupContainer("responding") {
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(getResponding() != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHead(IHeaderResponse response) {
|
||||
super.renderHead(response);
|
||||
response.render(OnDomReadyHeaderItem.forScript(String.format("""
|
||||
setTimeout(() => {
|
||||
var $responding = $('#%s');
|
||||
if ($responding.is(":visible"))
|
||||
$responding[0].scrollIntoView({ block: "end" });
|
||||
}, 0);
|
||||
""", getMarkupId())));
|
||||
}
|
||||
|
||||
};
|
||||
respondingContainer.add(new MarkdownViewer("content", new AbstractReadOnlyModel<String>() {
|
||||
|
||||
@Override
|
||||
public String getObject() {
|
||||
var responding = getResponding();
|
||||
if (responding != null)
|
||||
return responding.getContent();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
}, null));
|
||||
respondingContainer.add(new ChangeObserver() {
|
||||
|
||||
@Override
|
||||
protected Collection<String> findObservables() {
|
||||
var chat = getActiveChat();
|
||||
if (chat != null)
|
||||
return Collections.singleton(Chat.getPartialResponseObservable(chat.getId()));
|
||||
else
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
respondingContainer.setOutputMarkupPlaceholderTag(true);
|
||||
add(respondingContainer);
|
||||
|
||||
var form = new Form<Void>("send");
|
||||
form.add(new TextArea<String>("input", new IModel<String>() {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getObject() {
|
||||
return WebSession.get().getChatInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setObject(String object) {
|
||||
WebSession.get().setChatInput(object);
|
||||
}
|
||||
|
||||
}).add(new OnTypingDoneBehavior() {
|
||||
|
||||
@Override
|
||||
protected void onTypingDone(AjaxRequestTarget target) {
|
||||
}
|
||||
|
||||
}));
|
||||
form.setOutputMarkupId(true);
|
||||
|
||||
form.add(new AjaxButton("submit") {
|
||||
|
||||
@Override
|
||||
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
|
||||
var input = WebSession.get().getChatInput().trim();
|
||||
var chat = getActiveChat();
|
||||
if (chat == null) {
|
||||
chat = new Chat();
|
||||
chat.setUser(getUser());
|
||||
chat.setAi(getActiveAI());
|
||||
chat.setTitle(_T("New chat"));
|
||||
chat.setDate(new Date());
|
||||
chatService.createOrUpdate(chat);
|
||||
WebSession.get().setActiveChatId(chat.getId());
|
||||
target.add(chatSelectorContainer);
|
||||
}
|
||||
var request = new ChatMessage();
|
||||
request.setChat(chat);
|
||||
request.setRequest(true);
|
||||
request.setContent(input);
|
||||
chat.getMessages().add(request);
|
||||
dao.persist(request);
|
||||
chatService.sendRequest(getSession().getId(), request);
|
||||
|
||||
showNewMessages(target);
|
||||
target.add(respondingContainer);
|
||||
|
||||
WebSession.get().setChatInput(null);
|
||||
target.appendJavaScript("""
|
||||
var $send = $(".chat>.body>.send");
|
||||
$send.find("textarea").val("");
|
||||
$send.find("a.submit").attr("disabled", "disabled");
|
||||
""");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
form.add(new AjaxLink<Void>("stop") {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
var responding = getResponding();
|
||||
if (responding != null)
|
||||
responding.cancel();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
add(form);
|
||||
|
||||
add(AttributeAppender.append("class", "chat d-flex flex-column"));
|
||||
setOutputMarkupPlaceholderTag(true);
|
||||
}
|
||||
|
||||
private User getActiveAI() {
|
||||
if (activeAiId != null) {
|
||||
var ai = userService.get(activeAiId);
|
||||
if (ai != null && getUser().getEntitledAis().contains(ai))
|
||||
return ai;
|
||||
}
|
||||
return getUser().getEntitledAis().get(0);
|
||||
}
|
||||
|
||||
private void setActiveAI(User ai) {
|
||||
activeAiId = ai.getId();
|
||||
|
||||
WebResponse response = (WebResponse) RequestCycle.get().getResponse();
|
||||
Cookie cookie = new Cookie(COOKIE_ACTIVE_AI, activeAiId.toString());
|
||||
cookie.setMaxAge(Integer.MAX_VALUE);
|
||||
cookie.setPath("/");
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Chat getActiveChat() {
|
||||
var activeChatId = WebSession.get().getActiveChatId();
|
||||
if (activeChatId != null) {
|
||||
var chat = chatService.get(activeChatId);
|
||||
if (chat != null && chat.getAi().equals(getActiveAI()))
|
||||
return chat;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ChatResponding getResponding() {
|
||||
var chat = getActiveChat();
|
||||
if (chat != null)
|
||||
return chatService.getResponding(getSession().getId(), chat);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<ChatMessage> getMessages() {
|
||||
var chat = getActiveChat();
|
||||
if (chat != null)
|
||||
return chat.getSortedMessages();
|
||||
else
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void showNewMessages(IPartialPageRequestHandler handler) {
|
||||
long lastMessageId;
|
||||
if (messagesView.size() != 0)
|
||||
lastMessageId = (Long) messagesView.get(messagesView.size() - 1).getDefaultModelObject();
|
||||
else
|
||||
lastMessageId = 0;
|
||||
getMessages().stream().filter(it -> it.getId() > lastMessageId).forEach(it -> {
|
||||
var messageContainer = newMessageContainer(messagesView.newChildId(), it);
|
||||
messagesView.add(messageContainer);
|
||||
handler.prependJavaScript(String.format("""
|
||||
$('#%s').before($("<li class='message' id='%s'></li>"));
|
||||
""", respondingContainer.getMarkupId(), messageContainer.getMarkupId()));
|
||||
handler.add(messageContainer);
|
||||
});
|
||||
var lastMessage = messagesView.get(messagesView.size() - 1);
|
||||
handler.appendJavaScript(String.format("""
|
||||
$('#%s')[0].scrollIntoView({ block: "end" });
|
||||
""", lastMessage.getMarkupId()));
|
||||
}
|
||||
|
||||
private Component newMessageContainer(String containerId, ChatMessage message) {
|
||||
var messageContainer = new WebMarkupContainer(containerId, Model.of(message.getId()));
|
||||
if (message.isError() || message.isRequest()) {
|
||||
messageContainer.add(new MultilineLabel("content", message.getContent()));
|
||||
} else {
|
||||
messageContainer.add(new MarkdownViewer("content", Model.of(message.getContent()), null));
|
||||
}
|
||||
|
||||
if (message.isError())
|
||||
messageContainer.add(AttributeAppender.append("class", "error"));
|
||||
else if (message.isRequest())
|
||||
messageContainer.add(AttributeAppender.append("class", "request"));
|
||||
else
|
||||
messageContainer.add(AttributeAppender.append("class", "response"));
|
||||
|
||||
messageContainer.setOutputMarkupId(true);
|
||||
|
||||
return messageContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBeforeRender() {
|
||||
messagesView = new RepeatingView("messages");
|
||||
for (var message: getMessages()) {
|
||||
messagesView.add(newMessageContainer(messagesView.newChildId(), message));
|
||||
}
|
||||
addOrReplace(messagesView);
|
||||
|
||||
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
|
||||
Cookie cookie = request.getCookie("chat.width");
|
||||
if (cookie != null)
|
||||
add(AttributeAppender.replace("style", "width:" + cookie.getValue() + "px"));
|
||||
else
|
||||
add(AttributeAppender.replace("style", "width:400px"));
|
||||
super.onBeforeRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(WebSession.get().isChatVisible());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHead(IHeaderResponse response) {
|
||||
super.renderHead(response);
|
||||
response.render(JavaScriptHeaderItem.forReference(new ChatResourceReference()));
|
||||
response.render(OnDomReadyHeaderItem.forScript("onedev.server.chat.onDomReady();"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return WebSession.get().isChatVisible();
|
||||
}
|
||||
|
||||
public void show(AjaxRequestTarget target) {
|
||||
if (!isVisible()) {
|
||||
WebSession.get().setChatVisible(true);
|
||||
WebSession.get().setActiveChatId(null);
|
||||
target.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void hide(AjaxRequestTarget target) {
|
||||
WebSession.get().setChatVisible(false);
|
||||
target.add(this);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package io.onedev.server.web.component.ai.chat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.wicket.markup.head.CssHeaderItem;
|
||||
import org.apache.wicket.markup.head.HeaderItem;
|
||||
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
|
||||
|
||||
import io.onedev.server.web.asset.jqueryui.JQueryUIResourceReference;
|
||||
import io.onedev.server.web.page.base.BaseDependentCssResourceReference;
|
||||
import io.onedev.server.web.page.base.BaseDependentResourceReference;
|
||||
|
||||
public class ChatResourceReference extends BaseDependentResourceReference {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public ChatResourceReference() {
|
||||
super(ChatResourceReference.class, "chat.js");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeaderItem> getDependencies() {
|
||||
List<HeaderItem> dependencies = super.getDependencies();
|
||||
dependencies.add(JavaScriptHeaderItem.forReference(new JQueryUIResourceReference()));
|
||||
dependencies.add(CssHeaderItem.forReference(new BaseDependentCssResourceReference(getScope(), "chat.css")));
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
.chat {
|
||||
position: fixed;
|
||||
top: calc(var(--topbar-height) + 1rem);
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
border-radius: 0.42rem 0 0 0;
|
||||
background: white;
|
||||
box-shadow: 0 0 12px rgba(0,0,0,0.2), 0 -2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.chat>.head {
|
||||
border-radius: 0.42rem 0 0 0;
|
||||
}
|
||||
.dark-mode .chat {
|
||||
background-color: var(--dark-mode-dark);
|
||||
box-shadow: 0 0 20px rgb(0 0 0 / 50%), 0 -2px 15px rgb(0 0 0 / 30%);
|
||||
}
|
||||
|
||||
.chat .ui-resizable-handle {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0.42rem;
|
||||
bottom: 0;
|
||||
width: 3px;
|
||||
cursor: col-resize;
|
||||
z-index: 10;
|
||||
background: var(--light) url(/~icon/grip3.svg) no-repeat scroll center center;
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
|
||||
.dark-mode .chat .ui-resizable-handle {
|
||||
background: var(--dark-mode-light-dark) url(/~icon/dark-grip3.svg) no-repeat scroll center center;
|
||||
background-size: 18px 18px;
|
||||
}
|
||||
|
||||
.chat>.body>.chat-selector {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.chat>.body>.chat-selector>:first-child {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.chat>.body>.send textarea {
|
||||
max-height: 160px;
|
||||
}
|
||||
.chat>.body>.send a {
|
||||
border-radius: 50px;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
right: 6px;
|
||||
bottom: 6px;
|
||||
}
|
||||
.chat>.body>.send a.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat>.body>.messages>li {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.chat>.body>.messages>li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat>.body>.messages>.request {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.chat>.body>.messages>.response+.responding {
|
||||
display: none;
|
||||
}
|
||||
.chat>.body>.messages>.response, .chat>.body>.messages>.error {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.chat>.body>.messages>.response>div, .chat>.body>.messages>.error>div {
|
||||
margin-right: 3rem;
|
||||
}
|
||||
.chat>.body>.messages>.request>div {
|
||||
background: var(--light);
|
||||
padding: 0.7rem;
|
||||
border-radius: 0.42rem;
|
||||
margin-left: 3rem;
|
||||
}
|
||||
.dark-mode .chat>.body>.messages>.request>div {
|
||||
background: var(--dark-mode-light-dark);
|
||||
}
|
||||
|
||||
.chat>.body>.messages>.error>div {
|
||||
background: var(--light-danger);
|
||||
padding: 0.7rem;
|
||||
border-radius: 0.42rem;
|
||||
}
|
||||
.dark-mode .chat>.body>.messages>.error>div {
|
||||
background: #3a2434;
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.chat>.body>.messages>.responding .breathing-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--gray-dark);
|
||||
animation: breathing 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.dark-mode .chat>.body>.messages>.responding .breathing-dot {
|
||||
background-color: var(--dark-mode-gray);
|
||||
}
|
||||
|
||||
@keyframes breathing {
|
||||
0%, 100% {
|
||||
transform: scale(0.6);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.chat>.body:has(.messages>.responding:not(.response + .responding))>.send .submit {
|
||||
display: none;
|
||||
}
|
||||
.chat>.body:not(:has(.messages>.responding:not(.response + .responding)))>.send .stop {
|
||||
display: none;
|
||||
}
|
||||
.chat>.body>.send .stop svg {
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
onedev.server.chat = {
|
||||
onDomReady: function() {
|
||||
var $chat = $(".chat");
|
||||
var $resizeHandle = $chat.children(".ui-resizable-handle");
|
||||
$chat.resizable({
|
||||
autoHide: false,
|
||||
handles: {"w": $resizeHandle},
|
||||
minWidth: 300,
|
||||
stop: function(e, ui) {
|
||||
Cookies.set("chat.width", ui.size.width, {expires: Infinity});
|
||||
}
|
||||
});
|
||||
|
||||
var $textarea = $chat.find(">.body>.send textarea");
|
||||
var $submit = $chat.find(">.body>.send a.submit");
|
||||
|
||||
function updateSubmit() {
|
||||
var $responding = $chat.find(">.body>.messages>.responding");
|
||||
var isEmpty = $textarea.val().trim() === "";
|
||||
$submit.toggleClass("disabled", isEmpty);
|
||||
if (isEmpty && !$responding.is(":visible")) {
|
||||
$submit.attr("disabled", "disabled");
|
||||
} else {
|
||||
$submit.removeAttr("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
updateSubmit();
|
||||
|
||||
$textarea.on("input", updateSubmit);
|
||||
|
||||
$textarea.keydown(function(e) {
|
||||
if (e.keyCode == 13 && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
if ($textarea.val().trim() !== "" && !$submit.hasClass("disabled")) {
|
||||
$submit.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1373,6 +1373,19 @@ public abstract class RevisionDiffPanel extends Panel {
|
||||
private WebMarkupContainer newNavigationContainer() {
|
||||
WebMarkupContainer navigationContainer = new WebMarkupContainer("navigation") {
|
||||
|
||||
@Override
|
||||
protected void onBeforeRender() {
|
||||
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
|
||||
Cookie cookie = request.getCookie(COOKIE_NAVIGATION_WIDTH);
|
||||
// cookie will not be sent for websocket request
|
||||
if (cookie != null)
|
||||
add(AttributeAppender.replace("style", "width:" + cookie.getValue() + "px"));
|
||||
else
|
||||
add(AttributeAppender.replace("style", "width:360px"));
|
||||
|
||||
super.onBeforeRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHead(IHeaderResponse response) {
|
||||
super.renderHead(response);
|
||||
@ -1380,15 +1393,6 @@ public abstract class RevisionDiffPanel extends Panel {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
float navigationWidth = 360;
|
||||
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
|
||||
Cookie cookie = request.getCookie(COOKIE_NAVIGATION_WIDTH);
|
||||
// cookie will not be sent for websocket request
|
||||
if (cookie != null)
|
||||
navigationWidth = Float.parseFloat(cookie.getValue());
|
||||
|
||||
navigationContainer.add(AttributeAppender.append("style", "width:" + navigationWidth + "px"));
|
||||
|
||||
var changes = new TreeMap<String, BlobChange>();
|
||||
var treeState = new HashSet<String>();
|
||||
@ -1551,6 +1555,19 @@ public abstract class RevisionDiffPanel extends Panel {
|
||||
private WebMarkupContainer newCommentContainer() {
|
||||
WebMarkupContainer commentContainer = new WebMarkupContainer("comment", Model.of((Mark)null)) {
|
||||
|
||||
@Override
|
||||
protected void onBeforeRender() {
|
||||
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
|
||||
Cookie cookie = request.getCookie(COOKIE_COMMENT_WIDTH);
|
||||
// cookie will not be sent for websocket request
|
||||
if (cookie != null)
|
||||
add(AttributeAppender.replace("style", "width:" + cookie.getValue() + "px"));
|
||||
else
|
||||
add(AttributeAppender.replace("style", "width:360px"));
|
||||
|
||||
super.onBeforeRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderHead(IHeaderResponse response) {
|
||||
super.renderHead(response);
|
||||
@ -1560,15 +1577,6 @@ public abstract class RevisionDiffPanel extends Panel {
|
||||
};
|
||||
commentContainer.setOutputMarkupPlaceholderTag(true);
|
||||
|
||||
float commentWidth = 360;
|
||||
WebRequest request = (WebRequest) RequestCycle.get().getRequest();
|
||||
Cookie cookie = request.getCookie(COOKIE_COMMENT_WIDTH);
|
||||
// cookie will not be sent for websocket request
|
||||
if (cookie != null)
|
||||
commentWidth = Float.parseFloat(cookie.getValue());
|
||||
|
||||
commentContainer.add(AttributeAppender.append("style", "width:" + commentWidth + "px"));
|
||||
|
||||
WebMarkupContainer head = new WebMarkupContainer("head");
|
||||
head.setOutputMarkupId(true);
|
||||
commentContainer.add(head);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.component.entity.watches;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -109,7 +110,7 @@ public abstract class EntityWatchesPanel extends Panel {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(SecurityUtils.getAuthUser() != null && !SecurityUtils.getAuthUser().isServiceAccount());
|
||||
setVisible(SecurityUtils.getAuthUser() != null && SecurityUtils.getAuthUser().getType() == ORDINARY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.component.issue.list;
|
||||
|
||||
import static io.onedev.server.model.User.Type.SERVICE;
|
||||
import static io.onedev.server.web.component.issue.list.BuiltInFieldsBean.NAME_CONFIDENTIAL;
|
||||
import static io.onedev.server.web.component.issue.list.BuiltInFieldsBean.NAME_ITERATION;
|
||||
import static io.onedev.server.web.component.issue.list.BuiltInFieldsBean.NAME_STATE;
|
||||
@ -20,7 +21,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import javax.validation.ValidationException;
|
||||
|
||||
import org.apache.wicket.AttributeModifier;
|
||||
@ -39,14 +39,13 @@ import org.apache.wicket.markup.html.list.ListView;
|
||||
import org.apache.wicket.markup.html.panel.Panel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.PropertyModel;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.buildspecmodel.inputspec.InputContext;
|
||||
import io.onedev.server.buildspecmodel.inputspec.InputSpec;
|
||||
import io.onedev.server.service.IssueChangeService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.model.Issue;
|
||||
import io.onedev.server.model.Iteration;
|
||||
import io.onedev.server.model.Project;
|
||||
@ -55,6 +54,8 @@ import io.onedev.server.model.support.issue.field.FieldUtils;
|
||||
import io.onedev.server.model.support.issue.field.spec.FieldSpec;
|
||||
import io.onedev.server.search.entity.issue.IssueQuery;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.service.IssueChangeService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.web.ajaxlistener.DisableGlobalAjaxIndicatorListener;
|
||||
import io.onedev.server.web.behavior.RunTaskBehavior;
|
||||
import io.onedev.server.web.component.comment.CommentInput;
|
||||
@ -267,7 +268,7 @@ abstract class BatchEditPanel extends Panel implements InputContext {
|
||||
});
|
||||
|
||||
form.add(new CheckBox("sendNotifications", new PropertyModel<>(this, "sendNotifications"))
|
||||
.setVisible(!SecurityUtils.getUser().isServiceAccount()));
|
||||
.setVisible(SecurityUtils.getUser().getType() != SERVICE));
|
||||
|
||||
form.add(new AjaxButton("save") {
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package io.onedev.server.web.component.issue.list;
|
||||
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.search.entity.EntitySort.Direction.ASCENDING;
|
||||
import static io.onedev.server.search.entity.issue.IssueQuery.merge;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
@ -20,8 +21,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.Predicate;
|
||||
import org.apache.commons.csv.CSVFormat;
|
||||
@ -70,6 +69,7 @@ import org.apache.wicket.request.cycle.RequestCycle;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
import org.apache.wicket.request.resource.AbstractResource;
|
||||
import org.dhatim.fastexcel.Workbook;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
@ -78,12 +78,6 @@ import io.onedev.commons.utils.ExplicitException;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.buildspecmodel.inputspec.Input;
|
||||
import io.onedev.server.data.migration.VersionedXmlDoc;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.IssueLinkService;
|
||||
import io.onedev.server.service.IssueService;
|
||||
import io.onedev.server.service.IssueWatchService;
|
||||
import io.onedev.server.service.ProjectService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.imports.IssueImporter;
|
||||
import io.onedev.server.imports.IssueImporterContribution;
|
||||
import io.onedev.server.model.Issue;
|
||||
@ -105,6 +99,12 @@ import io.onedev.server.search.entity.issue.IssueQuery;
|
||||
import io.onedev.server.search.entity.issue.IssueQueryParseOption;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.security.permission.AccessProject;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.IssueLinkService;
|
||||
import io.onedev.server.service.IssueService;
|
||||
import io.onedev.server.service.IssueWatchService;
|
||||
import io.onedev.server.service.ProjectService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.timetracking.TimeTrackingService;
|
||||
import io.onedev.server.util.DateUtils;
|
||||
import io.onedev.server.util.LinkDescriptor;
|
||||
@ -1575,7 +1575,7 @@ public abstract class IssueListPanel extends Panel {
|
||||
});
|
||||
}
|
||||
|
||||
if (!SecurityUtils.getAuthUser().isServiceAccount()) {
|
||||
if (SecurityUtils.getAuthUser().getType() == ORDINARY) {
|
||||
menuItems.add(new MenuItem() {
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,27 +1,11 @@
|
||||
package io.onedev.server.web.component.markdown;
|
||||
|
||||
import io.onedev.commons.loader.AppLoader;
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.service.*;
|
||||
import io.onedev.server.entityreference.BuildReference;
|
||||
import io.onedev.server.entityreference.EntityReference;
|
||||
import io.onedev.server.entityreference.IssueReference;
|
||||
import io.onedev.server.entityreference.PullRequestReference;
|
||||
import io.onedev.server.markdown.MarkdownService;
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.util.ColorUtils;
|
||||
import io.onedev.server.util.DateUtils;
|
||||
import io.onedev.server.web.asset.emoji.Emojis;
|
||||
import io.onedev.server.web.asset.lozad.LozadResourceReference;
|
||||
import io.onedev.server.web.avatar.AvatarService;
|
||||
import io.onedev.server.web.behavior.AbstractPostAjaxBehavior;
|
||||
import io.onedev.server.web.component.build.status.BuildStatusIcon;
|
||||
import io.onedev.server.web.component.svg.SpriteImage;
|
||||
import io.onedev.server.web.page.project.ProjectPage;
|
||||
import io.onedev.server.web.page.project.blob.render.BlobRenderContext;
|
||||
import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.wicket.ajax.AjaxRequestTarget;
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.markup.ComponentTag;
|
||||
@ -38,13 +22,35 @@ import org.apache.wicket.request.cycle.RequestCycle;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.hibernate.StaleStateException;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.unbescape.html.HtmlEscape;
|
||||
import org.unbescape.javascript.JavaScriptEscape;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
|
||||
import io.onedev.commons.utils.StringUtils;
|
||||
import io.onedev.server.entityreference.BuildReference;
|
||||
import io.onedev.server.entityreference.EntityReference;
|
||||
import io.onedev.server.entityreference.IssueReference;
|
||||
import io.onedev.server.entityreference.PullRequestReference;
|
||||
import io.onedev.server.markdown.MarkdownService;
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.service.BuildService;
|
||||
import io.onedev.server.service.IssueService;
|
||||
import io.onedev.server.service.ProjectService;
|
||||
import io.onedev.server.service.PullRequestService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.util.ColorUtils;
|
||||
import io.onedev.server.util.DateUtils;
|
||||
import io.onedev.server.web.asset.emoji.Emojis;
|
||||
import io.onedev.server.web.asset.lozad.LozadResourceReference;
|
||||
import io.onedev.server.web.avatar.AvatarService;
|
||||
import io.onedev.server.web.behavior.AbstractPostAjaxBehavior;
|
||||
import io.onedev.server.web.component.build.status.BuildStatusIcon;
|
||||
import io.onedev.server.web.component.svg.SpriteImage;
|
||||
import io.onedev.server.web.page.project.ProjectPage;
|
||||
import io.onedev.server.web.page.project.blob.render.BlobRenderContext;
|
||||
|
||||
public class MarkdownViewer extends GenericPanel<String> {
|
||||
|
||||
@ -67,6 +73,30 @@ public class MarkdownViewer extends GenericPanel<String> {
|
||||
private AbstractPostAjaxBehavior referenceBehavior;
|
||||
|
||||
private AbstractPostAjaxBehavior suggestionBehavior;
|
||||
|
||||
@Inject
|
||||
private MarkdownService markdownService;
|
||||
|
||||
@Inject
|
||||
private IssueService issueService;
|
||||
|
||||
@Inject
|
||||
private PullRequestService pullRequestService;
|
||||
|
||||
@Inject
|
||||
private SettingService settingService;
|
||||
|
||||
@Inject
|
||||
private BuildService buildService;
|
||||
|
||||
@Inject
|
||||
private AvatarService avatarService;
|
||||
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
@Inject
|
||||
private ProjectService projectService;
|
||||
|
||||
private final IModel<String> renderedModel = new LoadableDetachableModel<String>() {
|
||||
|
||||
@ -74,8 +104,7 @@ public class MarkdownViewer extends GenericPanel<String> {
|
||||
protected String load() {
|
||||
String markdown = getModelObject();
|
||||
if (markdown != null) {
|
||||
MarkdownService manager = AppLoader.getInstance(MarkdownService.class);
|
||||
return manager.process(manager.render(markdown), getProject(),
|
||||
return markdownService.process(markdownService.render(markdown), getProject(),
|
||||
getRenderContext(), getSuggestionSupport(), false);
|
||||
} else {
|
||||
return null;
|
||||
@ -183,10 +212,10 @@ public class MarkdownViewer extends GenericPanel<String> {
|
||||
switch (referenceType) {
|
||||
case "issue":
|
||||
EntityReference reference = IssueReference.of(referenceId, null);
|
||||
var issue = OneDev.getInstance(IssueService.class).find(reference.getProject(), reference.getNumber());
|
||||
var issue = issueService.find(reference.getProject(), reference.getNumber());
|
||||
// check permission here as issue project may not be the same as current project
|
||||
if (issue != null && SecurityUtils.canAccessIssue(issue)) {
|
||||
String color = OneDev.getInstance(SettingService.class).getIssueSetting().getStateSpec(issue.getState()).getColor();
|
||||
String color = settingService.getIssueSetting().getStateSpec(issue.getState()).getColor();
|
||||
String script = String.format("onedev.server.markdown.renderIssueTooltip('%s', '%s', '%s', '%s');",
|
||||
Emojis.getInstance().apply(JavaScriptEscape.escapeJavaScript(issue.getTitle())),
|
||||
JavaScriptEscape.escapeJavaScript(issue.getState()),
|
||||
@ -198,7 +227,7 @@ public class MarkdownViewer extends GenericPanel<String> {
|
||||
break;
|
||||
case "pull request":
|
||||
reference = PullRequestReference.of(referenceId, null);
|
||||
var request = OneDev.getInstance(PullRequestService.class).find(reference.getProject(), reference.getNumber());
|
||||
var request = pullRequestService.find(reference.getProject(), reference.getNumber());
|
||||
// check permission here as target project may not be the same as current project
|
||||
if (request != null && SecurityUtils.canReadCode(request.getTargetProject())) {
|
||||
String status = request.getStatus().toString();
|
||||
@ -224,7 +253,7 @@ public class MarkdownViewer extends GenericPanel<String> {
|
||||
break;
|
||||
case "build":
|
||||
reference = BuildReference.of(referenceId, null);
|
||||
var build = OneDev.getInstance(BuildService.class).find(reference.getProject(), reference.getNumber());
|
||||
var build = buildService.find(reference.getProject(), reference.getNumber());
|
||||
// check permission here as build project may not be the same as current project
|
||||
if (build != null && SecurityUtils.canAccessBuild(build)) {
|
||||
String iconHref = SpriteImage.getVersionedHref(BuildStatusIcon.getIconHref(build.getStatus()));
|
||||
@ -241,9 +270,9 @@ public class MarkdownViewer extends GenericPanel<String> {
|
||||
}
|
||||
break;
|
||||
case "user":
|
||||
User user = OneDev.getInstance(UserService.class).findByName(referenceId);
|
||||
User user = userService.findByName(referenceId);
|
||||
if (user != null) {
|
||||
String avatarUrl = OneDev.getInstance(AvatarService.class).getUserAvatarUrl(user.getId());
|
||||
String avatarUrl = avatarService.getUserAvatarUrl(user.getId());
|
||||
String script = String.format("onedev.server.markdown.renderUserTooltip('%s', '%s')",
|
||||
JavaScriptEscape.escapeJavaScript(avatarUrl),
|
||||
JavaScriptEscape.escapeJavaScript(user.getDisplayName()));
|
||||
@ -254,8 +283,7 @@ public class MarkdownViewer extends GenericPanel<String> {
|
||||
Project commitProject = getProject();
|
||||
String commitHash;
|
||||
if (referenceId.contains(":")) {
|
||||
commitProject = OneDev.getInstance(ProjectService.class)
|
||||
.findByPath(StringUtils.substringBefore(referenceId, ":"));
|
||||
commitProject = projectService.findByPath(StringUtils.substringBefore(referenceId, ":"));
|
||||
commitHash = StringUtils.substringAfter(referenceId, ":");
|
||||
} else {
|
||||
commitHash = referenceId;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package io.onedev.server.web.component.pullrequest.list;
|
||||
|
||||
import static io.onedev.server.entityreference.ReferenceUtils.transformReferences;
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.search.entity.pullrequest.PullRequestQuery.merge;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
@ -13,8 +14,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.Session;
|
||||
@ -52,17 +51,13 @@ import org.apache.wicket.model.IModel;
|
||||
import org.apache.wicket.model.LoadableDetachableModel;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.cycle.RequestCycle;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import io.onedev.commons.utils.ExplicitException;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.data.migration.VersionedXmlDoc;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.ProjectService;
|
||||
import io.onedev.server.service.PullRequestService;
|
||||
import io.onedev.server.service.PullRequestReviewService;
|
||||
import io.onedev.server.service.PullRequestWatchService;
|
||||
import io.onedev.server.entityreference.LinkTransformer;
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.model.PullRequest;
|
||||
@ -78,6 +73,11 @@ import io.onedev.server.search.entity.pullrequest.FuzzyCriteria;
|
||||
import io.onedev.server.search.entity.pullrequest.PullRequestQuery;
|
||||
import io.onedev.server.security.SecurityUtils;
|
||||
import io.onedev.server.security.permission.ReadCode;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.ProjectService;
|
||||
import io.onedev.server.service.PullRequestReviewService;
|
||||
import io.onedev.server.service.PullRequestService;
|
||||
import io.onedev.server.service.PullRequestWatchService;
|
||||
import io.onedev.server.util.DateUtils;
|
||||
import io.onedev.server.util.watch.WatchStatus;
|
||||
import io.onedev.server.web.WebConstants;
|
||||
@ -271,7 +271,7 @@ public abstract class PullRequestListPanel extends Panel {
|
||||
protected List<MenuItem> getMenuItems(FloatingPanel dropdown) {
|
||||
List<MenuItem> menuItems = new ArrayList<>();
|
||||
|
||||
if (!SecurityUtils.getAuthUser().isServiceAccount()) {
|
||||
if (SecurityUtils.getAuthUser().getType() == ORDINARY) {
|
||||
menuItems.add(new MenuItem() {
|
||||
|
||||
@Override
|
||||
@ -474,7 +474,7 @@ public abstract class PullRequestListPanel extends Panel {
|
||||
});
|
||||
}
|
||||
|
||||
if (!SecurityUtils.getAuthUser().isServiceAccount()) {
|
||||
if (SecurityUtils.getAuthUser().getType() == ORDINARY) {
|
||||
menuItems.add(new MenuItem() {
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
package io.onedev.server.web.component.savedquery;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -31,6 +32,7 @@ import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.cycle.RequestCycle;
|
||||
import org.apache.wicket.request.http.WebRequest;
|
||||
import org.apache.wicket.request.http.WebResponse;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import io.onedev.server.model.Project;
|
||||
import io.onedev.server.model.support.NamedQuery;
|
||||
@ -310,7 +312,7 @@ public abstract class SavedQueriesPanel<T extends NamedQuery> extends Panel {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(!SecurityUtils.getAuthUser().isServiceAccount() && getQueryPersonalization().getQueryWatchSupport() != null);
|
||||
setVisible(SecurityUtils.getAuthUser().getType() == ORDINARY && getQueryPersonalization().getQueryWatchSupport() != null);
|
||||
}
|
||||
|
||||
});
|
||||
@ -336,7 +338,7 @@ public abstract class SavedQueriesPanel<T extends NamedQuery> extends Panel {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(!SecurityUtils.getAuthUser().isServiceAccount() && getQueryPersonalization().getQuerySubscriptionSupport() != null);
|
||||
setVisible(SecurityUtils.getAuthUser().getType() == ORDINARY && getQueryPersonalization().getQuerySubscriptionSupport() != null);
|
||||
}
|
||||
|
||||
});
|
||||
@ -392,7 +394,7 @@ public abstract class SavedQueriesPanel<T extends NamedQuery> extends Panel {
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(SecurityUtils.getAuthUser() != null
|
||||
&& !SecurityUtils.getAuthUser().isServiceAccount()
|
||||
&& SecurityUtils.getAuthUser().getType() == ORDINARY
|
||||
&& getQueryPersonalization() != null
|
||||
&& getQueryPersonalization().getQueryWatchSupport() != null);
|
||||
}
|
||||
@ -422,7 +424,7 @@ public abstract class SavedQueriesPanel<T extends NamedQuery> extends Panel {
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(SecurityUtils.getAuthUser() != null
|
||||
&& !SecurityUtils.getAuthUser().isServiceAccount()
|
||||
&& SecurityUtils.getAuthUser().getType() == ORDINARY
|
||||
&& getQueryPersonalization() != null
|
||||
&& getQueryPersonalization().getQuerySubscriptionSupport() != null);
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ public abstract class SymbolTooltipPanel extends Panel {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(settingService.getAISetting().getLiteModelSetting() == null && symbolHits.size() > 1);
|
||||
setVisible(settingService.getAiSetting().getLiteModelSetting() == null && symbolHits.size() > 1);
|
||||
}
|
||||
|
||||
});
|
||||
@ -311,7 +311,7 @@ public abstract class SymbolTooltipPanel extends Panel {
|
||||
target.add(content);
|
||||
|
||||
CharSequence callback;
|
||||
if (settingService.getAISetting().getLiteModelSetting() != null && symbolHits.size() > 1)
|
||||
if (settingService.getAiSetting().getLiteModelSetting() != null && symbolHits.size() > 1)
|
||||
callback = getCallbackFunction(explicit("action"));
|
||||
else
|
||||
callback = "undefined";
|
||||
@ -326,7 +326,7 @@ public abstract class SymbolTooltipPanel extends Panel {
|
||||
}
|
||||
target.appendJavaScript(script);
|
||||
} else {
|
||||
var liteModel = settingService.getAISetting().getLiteModel();
|
||||
var liteModel = settingService.getAiSetting().getLiteModel();
|
||||
int index;
|
||||
try {
|
||||
ObjectMapper mapperCopy = objectMapper.copy();
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
package io.onedev.server.web.component.user.aisetting;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.annotation.DependsOn;
|
||||
import io.onedev.server.annotation.Editable;
|
||||
import io.onedev.server.annotation.GroupChoice;
|
||||
import io.onedev.server.annotation.ProjectChoice;
|
||||
import io.onedev.server.annotation.UserChoice;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.service.UserService;
|
||||
|
||||
@Editable
|
||||
public class EntitlementEditBean implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private boolean entitleToAll;
|
||||
|
||||
private List<String> entitledProjects;
|
||||
|
||||
private List<String> entitledGroups;
|
||||
|
||||
private List<String> entitledUsers;
|
||||
|
||||
@Editable(order=100, name="Entitle to All Users")
|
||||
public boolean isEntitleToAll() {
|
||||
return entitleToAll;
|
||||
}
|
||||
|
||||
public void setEntitleToAll(boolean entitleToAll) {
|
||||
this.entitleToAll = entitleToAll;
|
||||
}
|
||||
|
||||
@Editable(order=200)
|
||||
@ProjectChoice
|
||||
@DependsOn(property="entitleToAll", value="false")
|
||||
public List<String> getEntitledProjects() {
|
||||
return entitledProjects;
|
||||
}
|
||||
|
||||
public void setEntitledProjects(List<String> entitledProjects) {
|
||||
this.entitledProjects = entitledProjects;
|
||||
}
|
||||
|
||||
@Editable(order=300)
|
||||
@GroupChoice
|
||||
@DependsOn(property="entitleToAll", value="false")
|
||||
public List<String> getEntitledGroups() {
|
||||
return entitledGroups;
|
||||
}
|
||||
|
||||
public void setEntitledGroups(List<String> entitledGroups) {
|
||||
this.entitledGroups = entitledGroups;
|
||||
}
|
||||
|
||||
@Editable(order=300)
|
||||
@UserChoice("getUsers")
|
||||
@DependsOn(property="entitleToAll", value="false")
|
||||
public List<String> getEntitledUsers() {
|
||||
return entitledUsers;
|
||||
}
|
||||
|
||||
public void setEntitledUsers(List<String> entitledUsers) {
|
||||
this.entitledUsers = entitledUsers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static List<User> getUsers() {
|
||||
var cache = OneDev.getInstance(UserService.class).cloneCache();
|
||||
return cache.getUsers(it->!it.isDisabled() && it.getType() == ORDINARY)
|
||||
.stream()
|
||||
.sorted(cache.comparingDisplayName())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
<wicket:panel>
|
||||
<div class="alert alert-light">
|
||||
<wicket:t>Specifies who can access this AI service</wicket:t>
|
||||
</div>
|
||||
<form wicket:id="form" class="leave-confirm">
|
||||
<div wicket:id="editor" class="mb-4"></div>
|
||||
<input type="submit" class="btn btn-primary dirty-aware" t:value="Save Settings">
|
||||
</form>
|
||||
</wicket:panel>
|
||||
@ -0,0 +1,132 @@
|
||||
package io.onedev.server.web.component.user.aisetting;
|
||||
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
|
||||
import io.onedev.server.data.migration.VersionedXmlDoc;
|
||||
import io.onedev.server.model.GroupEntitlement;
|
||||
import io.onedev.server.model.ProjectEntitlement;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.model.UserEntitlement;
|
||||
import io.onedev.server.persistence.TransactionService;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.GroupEntitlementService;
|
||||
import io.onedev.server.service.GroupService;
|
||||
import io.onedev.server.service.ProjectEntitlementService;
|
||||
import io.onedev.server.service.ProjectService;
|
||||
import io.onedev.server.service.UserEntitlementService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.web.editable.BeanContext;
|
||||
|
||||
public class EntitlementSettingPanel extends GenericPanel<User> {
|
||||
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
@Inject
|
||||
private AuditService auditService;
|
||||
|
||||
@Inject
|
||||
private TransactionService transactionService;
|
||||
|
||||
@Inject
|
||||
private ProjectEntitlementService projectEntitlementService;
|
||||
|
||||
@Inject
|
||||
private GroupEntitlementService groupEntitlementService;
|
||||
|
||||
@Inject
|
||||
private UserEntitlementService userEntitlementService;
|
||||
|
||||
@Inject
|
||||
private ProjectService projectService;
|
||||
|
||||
@Inject
|
||||
private GroupService groupService;
|
||||
|
||||
public EntitlementSettingPanel(String id, IModel<User> model) {
|
||||
super(id, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
var bean = new EntitlementEditBean();
|
||||
bean.setEntitleToAll(getUser().getAiSetting().isEntitleToAll());
|
||||
bean.setEntitledProjects(getUser().getProjectEntitlements().stream()
|
||||
.map(it->it.getProject().getPath())
|
||||
.sorted()
|
||||
.collect(Collectors.toList()));
|
||||
bean.setEntitledGroups(getUser().getGroupEntitlements().stream()
|
||||
.map(it->it.getGroup().getName())
|
||||
.sorted()
|
||||
.collect(Collectors.toList()));
|
||||
bean.setEntitledUsers(getUser().getUserEntitlements().stream()
|
||||
.map(it->it.getUser().getName())
|
||||
.sorted()
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
var oldAuditContent = VersionedXmlDoc.fromBean(bean).toXML();
|
||||
|
||||
Form<?> form = new Form<Void>("form") {
|
||||
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
super.onSubmit();
|
||||
|
||||
transactionService.run(() -> {
|
||||
var newAuditContent = VersionedXmlDoc.fromBean(bean).toXML();
|
||||
getUser().getAiSetting().setEntitleToAll(bean.isEntitleToAll());
|
||||
|
||||
var projectEntitlements = bean.getEntitledProjects().stream().map(it->{
|
||||
var entitlement = new ProjectEntitlement();
|
||||
entitlement.setProject(projectService.findByPath(it));
|
||||
entitlement.setAi(getUser());
|
||||
return entitlement;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
var groupEntitlements = bean.getEntitledGroups().stream().map(it->{
|
||||
var entitlement = new GroupEntitlement();
|
||||
entitlement.setGroup(groupService.find(it));
|
||||
entitlement.setAi(getUser());
|
||||
return entitlement;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
var userEntitlements = bean.getEntitledUsers().stream().map(it->{
|
||||
var entitlement = new UserEntitlement();
|
||||
entitlement.setUser(userService.findByName(it));
|
||||
entitlement.setAI(getUser());
|
||||
return entitlement;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
userService.update(getUser(), null);
|
||||
projectEntitlementService.syncEntitlements(getUser(), projectEntitlements);
|
||||
groupEntitlementService.syncEntitlements(getUser(), groupEntitlements);
|
||||
userEntitlementService.syncEntitlements(getUser(), userEntitlements);
|
||||
|
||||
auditService.audit(null, "changed AI entitlement settings", oldAuditContent, newAuditContent);
|
||||
});
|
||||
|
||||
getSession().success(_T("AI entitlement settings have been saved"));
|
||||
setResponsePage(getPage().getClass(), getPage().getPageParameters());
|
||||
}
|
||||
|
||||
};
|
||||
form.add(BeanContext.edit("editor", bean));
|
||||
|
||||
add(form);
|
||||
}
|
||||
|
||||
private User getUser() {
|
||||
return getModelObject();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
<wicket:panel>
|
||||
<form wicket:id="form" class="leave-confirm">
|
||||
<div wicket:id="editor" class="mb-4"></div>
|
||||
<input type="submit" class="btn btn-primary dirty-aware" t:value="Save Settings">
|
||||
</form>
|
||||
</wicket:panel>
|
||||
@ -0,0 +1,60 @@
|
||||
package io.onedev.server.web.component.user.aisetting;
|
||||
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.markup.html.panel.GenericPanel;
|
||||
import org.apache.wicket.model.IModel;
|
||||
|
||||
import io.onedev.server.data.migration.VersionedXmlDoc;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.model.support.AiModelSetting;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.web.editable.BeanContext;
|
||||
|
||||
public class ModelSettingPanel extends GenericPanel<User> {
|
||||
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
@Inject
|
||||
private AuditService auditService;
|
||||
|
||||
public ModelSettingPanel(String id, IModel<User> model) {
|
||||
super(id, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
AiModelSetting setting = getUser().getAiSetting().getModelSetting();
|
||||
var oldAuditContent = VersionedXmlDoc.fromBean(setting).toXML();
|
||||
|
||||
Form<?> form = new Form<Void>("form") {
|
||||
|
||||
@Override
|
||||
protected void onSubmit() {
|
||||
super.onSubmit();
|
||||
var newAuditContent = VersionedXmlDoc.fromBean(setting).toXML();
|
||||
getUser().getAiSetting().setModelSetting(setting);
|
||||
userService.update(getUser(), null);
|
||||
auditService.audit(null, "changed AI model settings", oldAuditContent, newAuditContent);
|
||||
getSession().success(_T("AI model settings have been saved"));
|
||||
setResponsePage(getPage().getClass(), getPage().getPageParameters());
|
||||
}
|
||||
|
||||
};
|
||||
form.add(BeanContext.edit("editor", setting));
|
||||
|
||||
add(form);
|
||||
}
|
||||
|
||||
private User getUser() {
|
||||
return getModelObject();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,10 +1,13 @@
|
||||
package io.onedev.server.web.component.user.basicsetting;
|
||||
|
||||
import static io.onedev.server.model.User.PROP_NOTIFY_OWN_EVENTS;
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.wicket.Session;
|
||||
import org.apache.wicket.feedback.FencedFeedbackPanel;
|
||||
import org.apache.wicket.markup.html.form.Form;
|
||||
@ -13,11 +16,10 @@ import org.apache.wicket.model.IModel;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.data.migration.VersionedXmlDoc;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.service.AuditService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.util.Path;
|
||||
import io.onedev.server.util.PathNode;
|
||||
import io.onedev.server.web.editable.BeanContext;
|
||||
@ -26,6 +28,12 @@ import io.onedev.server.web.page.user.UserPage;
|
||||
|
||||
public class BasicSettingPanel extends GenericPanel<User> {
|
||||
|
||||
@Inject
|
||||
private UserService userService;
|
||||
|
||||
@Inject
|
||||
private AuditService auditService;
|
||||
|
||||
private BeanEditor editor;
|
||||
|
||||
private String oldName;
|
||||
@ -42,8 +50,8 @@ public class BasicSettingPanel extends GenericPanel<User> {
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
var excludeProperties = Sets.newHashSet("password", "serviceAccount");
|
||||
if (getUser().isServiceAccount() || getUser().isDisabled())
|
||||
var excludeProperties = Sets.newHashSet("password", "type", "aiModelSetting");
|
||||
if (getUser().getType() != ORDINARY || getUser().isDisabled())
|
||||
excludeProperties.add(PROP_NOTIFY_OWN_EVENTS);
|
||||
editor = BeanContext.editModel("editor", new IModel<Serializable>() {
|
||||
|
||||
@ -75,7 +83,7 @@ public class BasicSettingPanel extends GenericPanel<User> {
|
||||
|
||||
User user = getUser();
|
||||
|
||||
User userWithSameName = getUserService().findByName(user.getName());
|
||||
User userWithSameName = userService.findByName(user.getName());
|
||||
if (userWithSameName != null && !userWithSameName.equals(user)) {
|
||||
editor.error(new Path(new PathNode.Named(User.PROP_NAME)),
|
||||
_T("Login name already used by another account"));
|
||||
@ -83,9 +91,9 @@ public class BasicSettingPanel extends GenericPanel<User> {
|
||||
|
||||
if (editor.isValid()) {
|
||||
var newAuditContent = VersionedXmlDoc.fromBean(editor.getPropertyValues()).toXML();
|
||||
getUserService().update(user, oldName);
|
||||
userService.update(user, oldName);
|
||||
if (getPage() instanceof UserPage)
|
||||
getAuditService().audit(null, "changed basic settings of account \"" + user.getName() + "\"", oldAuditContent, newAuditContent);
|
||||
auditService.audit(null, "changed basic settings of account \"" + user.getName() + "\"", oldAuditContent, newAuditContent);
|
||||
Session.get().success(_T("Basic settings updated"));
|
||||
setResponsePage(getPage().getClass(), getPage().getPageParameters());
|
||||
}
|
||||
@ -98,13 +106,5 @@ public class BasicSettingPanel extends GenericPanel<User> {
|
||||
|
||||
add(form);
|
||||
}
|
||||
|
||||
private UserService getUserService() {
|
||||
return OneDev.getInstance(UserService.class);
|
||||
}
|
||||
|
||||
private AuditService getAuditService() {
|
||||
return OneDev.getInstance(AuditService.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -55,12 +55,18 @@
|
||||
<wicket:fragment wicket:id="serviceAccountFrag">
|
||||
<div class="alert alert-light p-2 font-size-sm"><wicket:t>This is a service account for task automation purpose</wicket:t></div>
|
||||
</wicket:fragment>
|
||||
<wicket:fragment wicket:id="aiAccountFrag">
|
||||
<div class="alert alert-light p-2 font-size-sm"><wicket:t>This is an AI account</wicket:t></div>
|
||||
</wicket:fragment>
|
||||
<wicket:fragment wicket:id="disabledFrag">
|
||||
<div class="alert alert-light-warning p-2 font-size-sm"><wicket:t>This account is disabled</wicket:t></div>
|
||||
</wicket:fragment>
|
||||
<wicket:fragment wicket:id="disabledServiceAccountFrag">
|
||||
<div class="alert alert-light-warning p-2 font-size-sm"><wicket:t>This is a disabled service account</wicket:t></div>
|
||||
</wicket:fragment>
|
||||
<wicket:fragment wicket:id="disabledAIAccountFrag">
|
||||
<div class="alert alert-light-warning p-2 font-size-sm"><wicket:t>This is a disabled AI account</wicket:t></div>
|
||||
</wicket:fragment>
|
||||
<wicket:fragment wicket:id="dateFrag">
|
||||
<h6 class="mt-4 mb-3 d-flex align-items-center">
|
||||
<wicket:svg href="calendar" class="icon mr-2"/> <span wicket:id="date"></span>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package io.onedev.server.web.component.user.profile;
|
||||
|
||||
import static io.onedev.server.model.User.Type.AI;
|
||||
import static io.onedev.server.model.User.Type.SERVICE;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
@ -219,13 +221,17 @@ public abstract class UserProfilePanel extends GenericPanel<User> {
|
||||
}
|
||||
|
||||
WebMarkupContainer noteContainer;
|
||||
if (user.isDisabled() && WicketUtils.isSubscriptionActive()) {
|
||||
if (user.isServiceAccount())
|
||||
if (user.isDisabled()) {
|
||||
if (user.getType() == SERVICE)
|
||||
noteContainer = new Fragment("note", "disabledServiceAccountFrag", this);
|
||||
else if (user.getType() == AI)
|
||||
noteContainer = new Fragment("note", "disabledAIAccountFrag", this);
|
||||
else
|
||||
noteContainer = new Fragment("note", "disabledFrag", this);
|
||||
} else if (user.isServiceAccount() && WicketUtils.isSubscriptionActive()) {
|
||||
} else if (user.getType() == SERVICE) {
|
||||
noteContainer = new Fragment("note", "serviceAccountFrag", this);
|
||||
} else if (user.getType() == AI) {
|
||||
noteContainer = new Fragment("note", "aiAccountFrag", this);
|
||||
} else if (user.getPassword() != null) {
|
||||
noteContainer = new Fragment("note", "authViaInternalDatabaseFrag", this);
|
||||
var actions = new WebMarkupContainer("actions");
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<wicket:panel>
|
||||
<input wicket:id="input" type="hidden"></input>
|
||||
<input wicket:id="input" type="hidden">
|
||||
</wicket:panel>
|
||||
@ -1,3 +1,3 @@
|
||||
<wicket:panel>
|
||||
<input wicket:id="input" type="hidden"></input>
|
||||
<input wicket:id="input" type="hidden">
|
||||
</wicket:panel>
|
||||
@ -77,6 +77,8 @@ import io.onedev.server.web.page.help.ResourceDetailPage;
|
||||
import io.onedev.server.web.page.help.ResourceListPage;
|
||||
import io.onedev.server.web.page.issues.IssueListPage;
|
||||
import io.onedev.server.web.page.my.accesstoken.MyAccessTokensPage;
|
||||
import io.onedev.server.web.page.my.aisetting.MyEntitlementSettingPage;
|
||||
import io.onedev.server.web.page.my.aisetting.MyModelSettingPage;
|
||||
import io.onedev.server.web.page.my.avatar.MyAvatarPage;
|
||||
import io.onedev.server.web.page.my.basicsetting.MyBasicSettingPage;
|
||||
import io.onedev.server.web.page.my.emailaddresses.MyEmailAddressesPage;
|
||||
@ -163,6 +165,8 @@ import io.onedev.server.web.page.security.SsoProcessPage;
|
||||
import io.onedev.server.web.page.serverinit.ServerInitPage;
|
||||
import io.onedev.server.web.page.test.TestPage;
|
||||
import io.onedev.server.web.page.user.accesstoken.UserAccessTokensPage;
|
||||
import io.onedev.server.web.page.user.aisetting.UserEntitlementSettingPage;
|
||||
import io.onedev.server.web.page.user.aisetting.UserModelSettingPage;
|
||||
import io.onedev.server.web.page.user.avatar.UserAvatarPage;
|
||||
import io.onedev.server.web.page.user.basicsetting.UserBasicSettingPage;
|
||||
import io.onedev.server.web.page.user.emailaddresses.UserEmailAddressesPage;
|
||||
@ -227,6 +231,8 @@ public class BaseUrlMapper extends CompoundRequestMapper {
|
||||
add(new BasePageMapper("~my/email-addresses", MyEmailAddressesPage.class));
|
||||
add(new BasePageMapper("~my/avatar", MyAvatarPage.class));
|
||||
add(new BasePageMapper("~my/password", MyPasswordPage.class));
|
||||
add(new BasePageMapper("~my/ai-model-setting", MyModelSettingPage.class));
|
||||
add(new BasePageMapper("~my/ai-entitlement-setting", MyEntitlementSettingPage.class));
|
||||
add(new BasePageMapper("~my/ssh-keys", MySshKeysPage.class));
|
||||
add(new BasePageMapper("~my/gpg-keys", MyGpgKeysPage.class));
|
||||
add(new BasePageMapper("~my/access-tokens", MyAccessTokensPage.class));
|
||||
@ -286,6 +292,8 @@ public class BaseUrlMapper extends CompoundRequestMapper {
|
||||
io.onedev.server.web.page.user.authorization.UserAuthorizationsPage.class));
|
||||
add(new BasePageMapper("~users/${user}/avatar", UserAvatarPage.class));
|
||||
add(new BasePageMapper("~users/${user}/password", UserPasswordPage.class));
|
||||
add(new BasePageMapper("~users/${user}/ai-model-setting", UserModelSettingPage.class));
|
||||
add(new BasePageMapper("~users/${user}/ai-entitlement-setting", UserEntitlementSettingPage.class));
|
||||
add(new BasePageMapper("~users/${user}/ssh-keys", UserSshKeysPage.class));
|
||||
add(new BasePageMapper("~users/${user}/gpg-keys", UserGpgKeysPage.class));
|
||||
add(new BasePageMapper("~users/${user}/access-tokens", UserAccessTokensPage.class));
|
||||
|
||||
@ -53,4 +53,13 @@
|
||||
}
|
||||
.branding-setting .dark-mode>.logo-preview>div {
|
||||
background-color: var(--dark-mode-dark) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.property-aiModelSetting>.value {
|
||||
margin-top: 1rem;
|
||||
padding: 0 0 0 1rem;
|
||||
border-left: 3px solid var(--light-gray);
|
||||
}
|
||||
.dark-mode .property-aiModelSetting>.value {
|
||||
border-color: var(--dark-mode-light-dark);
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import org.apache.wicket.markup.html.form.Form;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import io.onedev.server.data.migration.VersionedXmlDoc;
|
||||
import io.onedev.server.model.support.administration.AISetting;
|
||||
import io.onedev.server.model.support.administration.AiSetting;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.web.editable.PropertyContext;
|
||||
import io.onedev.server.web.page.admin.AdministrationPage;
|
||||
@ -28,7 +28,7 @@ public class LiteModelPage extends AdministrationPage {
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
AISetting aiSetting = settingService.getAISetting();
|
||||
AiSetting aiSetting = settingService.getAiSetting();
|
||||
var oldAuditContent = VersionedXmlDoc.fromBean(aiSetting).toXML();
|
||||
|
||||
Form<?> form = new Form<Void>("form") {
|
||||
@ -37,7 +37,7 @@ public class LiteModelPage extends AdministrationPage {
|
||||
protected void onSubmit() {
|
||||
super.onSubmit();
|
||||
var newAuditContent = VersionedXmlDoc.fromBean(aiSetting).toXML();
|
||||
settingService.saveAISetting(aiSetting);
|
||||
settingService.saveAiSetting(aiSetting);
|
||||
auditService.audit(null, "changed AI settings", oldAuditContent, newAuditContent);
|
||||
getSession().success(_T("Lite AI model settings have been saved"));
|
||||
|
||||
@ -45,7 +45,7 @@ public class LiteModelPage extends AdministrationPage {
|
||||
}
|
||||
|
||||
};
|
||||
form.add(PropertyContext.edit("editor", aiSetting, AISetting.PROP_LITE_MODEL_SETTING));
|
||||
form.add(PropertyContext.edit("editor", aiSetting, AiSetting.PROP_LITE_MODEL_SETTING));
|
||||
|
||||
add(form);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.page.admin.usermanagement;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import org.apache.shiro.authc.credential.PasswordService;
|
||||
@ -18,15 +19,16 @@ import com.google.common.collect.Sets;
|
||||
import io.onedev.commons.loader.AppLoader;
|
||||
import io.onedev.server.OneDev;
|
||||
import io.onedev.server.data.migration.VersionedXmlDoc;
|
||||
import io.onedev.server.service.EmailAddressService;
|
||||
import io.onedev.server.service.MembershipService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.model.EmailAddress;
|
||||
import io.onedev.server.model.Group;
|
||||
import io.onedev.server.model.Membership;
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.model.support.AiSetting;
|
||||
import io.onedev.server.persistence.TransactionService;
|
||||
import io.onedev.server.service.EmailAddressService;
|
||||
import io.onedev.server.service.MembershipService;
|
||||
import io.onedev.server.service.SettingService;
|
||||
import io.onedev.server.service.UserService;
|
||||
import io.onedev.server.util.Path;
|
||||
import io.onedev.server.util.PathNode;
|
||||
import io.onedev.server.web.editable.BeanContext;
|
||||
@ -63,7 +65,7 @@ public class NewUserPage extends AdministrationPage {
|
||||
_T("User name already used by another account"));
|
||||
}
|
||||
|
||||
if (!bean.isServiceAccount() && getEmailAddressService().findByValue(bean.getEmailAddress()) != null) {
|
||||
if (bean.getType() == ORDINARY && getEmailAddressService().findByValue(bean.getEmailAddress()) != null) {
|
||||
editor.error(new Path(new PathNode.Named(NewUserBean.PROP_EMAIL_ADDRESS)),
|
||||
_T("Email address already used by another user"));
|
||||
}
|
||||
@ -71,9 +73,12 @@ public class NewUserPage extends AdministrationPage {
|
||||
User user = new User();
|
||||
user.setName(bean.getName());
|
||||
user.setFullName(bean.getFullName());
|
||||
user.setServiceAccount(bean.isServiceAccount());
|
||||
user.setType(bean.getType());
|
||||
var aiSetting = new AiSetting();
|
||||
aiSetting.setModelSetting(bean.getAiModelSetting());
|
||||
user.setAiSetting(aiSetting);
|
||||
var defaultLoginGroup = getSettingService().getSecuritySetting().getDefaultGroup();
|
||||
if (user.isServiceAccount()) {
|
||||
if (user.getType() != ORDINARY) {
|
||||
getTransactionService().run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
@ -24,7 +24,8 @@
|
||||
<a wicket:id="link">
|
||||
<img wicket:id="avatar">
|
||||
<span wicket:id="name" class="name"></span>
|
||||
<span wicket:id="service" class="badge badge-info badge-sm ml-1"><wicket:t>service</wicket:t></span>
|
||||
<span wicket:id="service" class="badge badge-info badge-sm ml-1">service</span>
|
||||
<span wicket:id="ai" class="badge badge-info badge-sm ml-1">AI</span>
|
||||
<span wicket:id="disabled" class="badge badge-warning badge-sm ml-1"><wicket:t>disabled</wicket:t></span>
|
||||
</a>
|
||||
</wicket:fragment>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
package io.onedev.server.web.page.admin.usermanagement;
|
||||
|
||||
import static io.onedev.server.model.User.Type.AI;
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.model.User.Type.SERVICE;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -89,7 +92,7 @@ public class UserListPage extends AdministrationPage {
|
||||
var emailAddresses = new HashMap<Long, Collection<String>>();
|
||||
for (var emailAddress: emailAddressCache.values())
|
||||
emailAddresses.computeIfAbsent(emailAddress.getOwnerId(), key -> new HashSet<>()).add(emailAddress.getValue());
|
||||
List<User> users = new ArrayList<>(userCache.getUsers(state.includeDisabled));
|
||||
List<User> users = new ArrayList<>(userCache.getUsers(it->state.includeDisabled?true:!it.isDisabled()));
|
||||
users.sort(userCache.comparingDisplayName(Sets.newHashSet()));
|
||||
users = new Similarities<>(users) {
|
||||
|
||||
@ -852,7 +855,8 @@ public class UserListPage extends AdministrationPage {
|
||||
};
|
||||
link.add(new UserAvatar("avatar", user));
|
||||
link.add(new Label("name", user.getName()));
|
||||
link.add(new WebMarkupContainer("service").setVisible(user.isServiceAccount() && WicketUtils.isSubscriptionActive()));
|
||||
link.add(new WebMarkupContainer("service").setVisible(user.getType() == SERVICE));
|
||||
link.add(new WebMarkupContainer("ai").setVisible(user.getType() == AI));
|
||||
link.add(new WebMarkupContainer("disabled").setVisible(user.isDisabled() && WicketUtils.isSubscriptionActive()));
|
||||
fragment.add(link);
|
||||
cellItem.add(fragment);
|
||||
@ -879,7 +883,7 @@ public class UserListPage extends AdministrationPage {
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<User>> cellItem, String componentId,
|
||||
IModel<User> rowModel) {
|
||||
if (rowModel.getObject().isServiceAccount()) {
|
||||
if (rowModel.getObject().getType() != ORDINARY) {
|
||||
cellItem.add(new Label(componentId, "<i>N/A</i>").setEscapeModelStrings(false));
|
||||
} else {
|
||||
EmailAddress emailAddress = rowModel.getObject().getPrimaryEmailAddress();
|
||||
@ -906,7 +910,7 @@ public class UserListPage extends AdministrationPage {
|
||||
|
||||
@Override
|
||||
public void populateItem(Item<ICellPopulator<User>> cellItem, String componentId, IModel<User> rowModel) {
|
||||
if (rowModel.getObject().isServiceAccount() || rowModel.getObject().isDisabled()) {
|
||||
if (rowModel.getObject().getType() != ORDINARY || rowModel.getObject().isDisabled()) {
|
||||
cellItem.add(new Label(componentId, "<i>" + _T("N/A") + "</i>").setEscapeModelStrings(false));
|
||||
} else {
|
||||
cellItem.add(new Label(componentId, _T(rowModel.getObject().getAuthSource())));
|
||||
|
||||
@ -216,7 +216,7 @@ onedev.server = {
|
||||
onedev.server.focus.$components = null;
|
||||
},
|
||||
|
||||
doFocus: function($containers) {
|
||||
doFocus: function($containers) {
|
||||
/*
|
||||
* Do focus with a timeout as otherwise it will not work in a panel replaced
|
||||
* via Wicket
|
||||
@ -249,19 +249,24 @@ onedev.server = {
|
||||
$containers.find(focusibleSelector).addBack(focusibleSelector).filter(":visible").each(function() {
|
||||
var $this = $(this);
|
||||
if ($this.closest(".no-autofocus").length == 0) {
|
||||
var focused = false;
|
||||
if ($this.hasClass("CodeMirror") && $this[0].CodeMirror.options.readOnly == false) {
|
||||
$this[0].CodeMirror.focus();
|
||||
focused = true;
|
||||
} else if ($this.attr("readonly") != "readonly" && $this.attr("disabled") != "disabled") {
|
||||
if ($this.closest(".select2-container").length != 0) {
|
||||
if ($this.closest(".inplace-property-edit").length != 0) {
|
||||
$this.focus();
|
||||
focused = true;
|
||||
$this.closest(".select2-container").next("input").select2("open");
|
||||
}
|
||||
} else {
|
||||
$this.focus();
|
||||
focused = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (focused)
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
<div class="topbar-right text-nowrap">
|
||||
<a wicket:id="alerts" t:data-tippy-content="Alerts" class="text-warning topbar-link"><wicket:svg href="exclamation-circle-o" class="icon fade-in-out"></wicket:svg></a>
|
||||
<a wicket:id="newVersionStatus" t:data-tippy-content="New version available. Red for security/critical update, yellow for bug fix, blue for feature update. Click to show changes. Disable in system setting" class="new-version-status topbar-link"><img wicket:id="icon"/></a>
|
||||
<a wicket:id="showChat" t:data-tippy-content="Chat with AI" class="topbar-link"><wicket:svg href="ai" class="icon"/></a>
|
||||
<a wicket:id="darkMode" t:data-tippy-content="Toggle dark mode" class="topbar-link"><svg wicket:id="icon" class="icon"/></a>
|
||||
<a wicket:id="showCommandPalette" class="topbar-link command-palette"></a>
|
||||
<a wicket:id="languageSelector" t:data-tippy-content="Language" class="topbar-link language-selector"><wicket:svg href="globe" class="icon"></wicket:svg></a>
|
||||
@ -87,6 +88,10 @@
|
||||
<wicket:svg href="password" class="icon mr-2"></wicket:svg>
|
||||
<wicket:t>Password</wicket:t>
|
||||
</a>
|
||||
<a wicket:id="myAISetting" class="dropdown-item">
|
||||
<wicket:svg href="ai-setting" class="icon mr-2"></wicket:svg>
|
||||
<wicket:t>AI Settings</wicket:t>
|
||||
</a>
|
||||
<a wicket:id="mySshKeys" class="dropdown-item">
|
||||
<wicket:svg href="key" class="icon mr-2"></wicket:svg>
|
||||
<wicket:t>SSH Keys</wicket:t>
|
||||
@ -117,6 +122,7 @@
|
||||
</div>
|
||||
<div class="main autofit d-flex flex-column resize-aware">
|
||||
<wicket:child></wicket:child>
|
||||
<div wicket:id="chat"></div>
|
||||
</div>
|
||||
|
||||
<wicket:fragment wicket:id="menuHeaderFrag">
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package io.onedev.server.web.page.layout;
|
||||
|
||||
import static io.onedev.server.model.Alert.PROP_DATE;
|
||||
import static io.onedev.server.model.User.Type.AI;
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
import static org.apache.wicket.ajax.attributes.CallbackParameter.explicit;
|
||||
|
||||
@ -76,6 +78,7 @@ import io.onedev.server.util.DateUtils;
|
||||
import io.onedev.server.web.WebConstants;
|
||||
import io.onedev.server.web.behavior.AbstractPostAjaxBehavior;
|
||||
import io.onedev.server.web.behavior.ChangeObserver;
|
||||
import io.onedev.server.web.component.ai.chat.ChatPanel;
|
||||
import io.onedev.server.web.component.brandlogo.BrandLogoPanel;
|
||||
import io.onedev.server.web.component.commandpalette.CommandPalettePanel;
|
||||
import io.onedev.server.web.component.datatable.DefaultDataTable;
|
||||
@ -151,6 +154,7 @@ import io.onedev.server.web.page.base.BasePage;
|
||||
import io.onedev.server.web.page.help.IncompatibilitiesPage;
|
||||
import io.onedev.server.web.page.my.MyPage;
|
||||
import io.onedev.server.web.page.my.accesstoken.MyAccessTokensPage;
|
||||
import io.onedev.server.web.page.my.aisetting.MyModelSettingPage;
|
||||
import io.onedev.server.web.page.my.avatar.MyAvatarPage;
|
||||
import io.onedev.server.web.page.my.basicsetting.MyBasicSettingPage;
|
||||
import io.onedev.server.web.page.my.emailaddresses.MyEmailAddressesPage;
|
||||
@ -807,6 +811,24 @@ public abstract class LayoutPage extends BasePage {
|
||||
|
||||
});
|
||||
|
||||
var chat = new ChatPanel("chat");
|
||||
add(chat);
|
||||
|
||||
topbar.add(new AjaxLink<Void>("showChat") {
|
||||
|
||||
@Override
|
||||
public void onClick(AjaxRequestTarget target) {
|
||||
chat.show(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
super.onConfigure();
|
||||
setVisible(getLoginUser() != null && !getLoginUser().getEntitledAis().isEmpty());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
topbar.add(new MenuLink("languageSelector") {
|
||||
|
||||
private void switchLanguage(Locale locale) {
|
||||
@ -1061,7 +1083,7 @@ public abstract class LayoutPage extends BasePage {
|
||||
if (loginUser != null) {
|
||||
userInfo.add(new UserAvatar("avatar", loginUser));
|
||||
userInfo.add(new Label("name", loginUser.getDisplayName()));
|
||||
if (loginUser.isServiceAccount() || loginUser.isDisabled()) {
|
||||
if (loginUser.getType() != ORDINARY || loginUser.isDisabled()) {
|
||||
userInfo.add(new WebMarkupContainer("hasUnverifiedLink").setVisible(false));
|
||||
userInfo.add(new WebMarkupContainer("noPrimaryAddressLink").setVisible(false));
|
||||
} else if (loginUser.getEmailAddresses().isEmpty()) {
|
||||
@ -1088,7 +1110,7 @@ public abstract class LayoutPage extends BasePage {
|
||||
if (getPage() instanceof MyBasicSettingPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
|
||||
if (getLoginUser() != null && !getLoginUser().isServiceAccount()) {
|
||||
if (getLoginUser() != null && getLoginUser().getType() == ORDINARY) {
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myEmailSetting", MyEmailAddressesPage.class));
|
||||
if (getPage() instanceof MyEmailAddressesPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
@ -1100,7 +1122,7 @@ public abstract class LayoutPage extends BasePage {
|
||||
if (getPage() instanceof MyAvatarPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
|
||||
if (loginUser != null && loginUser.getPassword() != null && !loginUser.isServiceAccount() && !loginUser.isDisabled()) {
|
||||
if (loginUser != null && loginUser.getPassword() != null && loginUser.getType() == ORDINARY && !loginUser.isDisabled()) {
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myPassword", MyPasswordPage.class));
|
||||
if (getPage() instanceof MyPasswordPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
@ -1108,6 +1130,14 @@ public abstract class LayoutPage extends BasePage {
|
||||
userInfo.add(new WebMarkupContainer("myPassword").setVisible(false));
|
||||
}
|
||||
|
||||
if (loginUser != null && loginUser.getType() == AI && !loginUser.isDisabled()) {
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myAISetting", MyModelSettingPage.class));
|
||||
if (getPage() instanceof MyModelSettingPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
} else {
|
||||
userInfo.add(new WebMarkupContainer("myAISetting").setVisible(false));
|
||||
}
|
||||
|
||||
if (OneDev.getInstance(ServerConfig.class).getSshPort() != 0 && loginUser != null && !loginUser.isDisabled()) {
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("mySshKeys", MySshKeysPage.class));
|
||||
if (getPage() instanceof MySshKeysPage)
|
||||
@ -1132,7 +1162,7 @@ public abstract class LayoutPage extends BasePage {
|
||||
userInfo.add(new WebMarkupContainer("myAccessTokens").setVisible(false));
|
||||
}
|
||||
|
||||
if (getLoginUser() != null && !getLoginUser().isServiceAccount() && !getLoginUser().isDisabled()) {
|
||||
if (getLoginUser() != null && getLoginUser().getType() == ORDINARY && !getLoginUser().isDisabled()) {
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myTwoFactorAuthentication", MyTwoFactorAuthenticationPage.class) {
|
||||
@Override
|
||||
protected void onConfigure() {
|
||||
@ -1146,7 +1176,7 @@ public abstract class LayoutPage extends BasePage {
|
||||
userInfo.add(new WebMarkupContainer("myTwoFactorAuthentication").setVisible(false));
|
||||
}
|
||||
|
||||
if (loginUser != null && !loginUser.isDisabled() && !loginUser.isServiceAccount()) {
|
||||
if (loginUser != null && !loginUser.isDisabled() && loginUser.getType() == ORDINARY) {
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("mySsoAccounts", MySsoAccountsPage.class));
|
||||
if (getPage() instanceof MySsoAccountsPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
@ -1154,7 +1184,7 @@ public abstract class LayoutPage extends BasePage {
|
||||
userInfo.add(new WebMarkupContainer("mySsoAccounts").setVisible(false));
|
||||
}
|
||||
|
||||
if (getLoginUser() != null && !getLoginUser().isServiceAccount() && !getLoginUser().isDisabled()) {
|
||||
if (getLoginUser() != null && getLoginUser().getType() == ORDINARY && !getLoginUser().isDisabled()) {
|
||||
userInfo.add(item = new ViewStateAwarePageLink<Void>("myQueryWatches", MyQueryWatchesPage.class));
|
||||
if (getPage() instanceof MyQueryWatchesPage)
|
||||
item.add(AttributeAppender.append("class", "active"));
|
||||
@ -1218,6 +1248,7 @@ public abstract class LayoutPage extends BasePage {
|
||||
getUpdateCheckService().cacheNewVersionStatus(newVersionStatus);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private SubscriptionService getSubscriptionService() {
|
||||
|
||||
@ -306,6 +306,8 @@
|
||||
.topbar-right .new-version-status>img {
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.topbar-right a.topbar-link.active,
|
||||
.topbar-right a.topbar-link:hover,
|
||||
.topbar-right .active>a.topbar-link,
|
||||
.topbar-right .show>a.topbar-link {
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
<wicket:extend>
|
||||
<ul wicket:id="aiSettingTabs" class="tabs nav nav-bold nav-tabs nav-tabs-line text-weight-bold mb-4"></ul>
|
||||
<wicket:child></wicket:child>
|
||||
</wicket:extend>
|
||||
@ -0,0 +1,43 @@
|
||||
package io.onedev.server.web.page.my.aisetting;
|
||||
|
||||
import static io.onedev.server.model.User.Type.AI;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
import org.apache.wicket.markup.html.basic.Label;
|
||||
import org.apache.wicket.model.Model;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import io.onedev.server.web.component.tabbable.PageTab;
|
||||
import io.onedev.server.web.component.tabbable.Tabbable;
|
||||
import io.onedev.server.web.page.my.MyPage;
|
||||
|
||||
public abstract class MyAiSettingPage extends MyPage {
|
||||
|
||||
public MyAiSettingPage(PageParameters params) {
|
||||
super(params);
|
||||
if (getUser().isDisabled() || getUser().getType() != AI)
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
List<PageTab> tabs = new ArrayList<>();
|
||||
|
||||
tabs.add(new PageTab(Model.of(_T("Model")), null, MyModelSettingPage.class, new PageParameters()));
|
||||
tabs.add(new PageTab(Model.of(_T("Entitlement")), null, MyEntitlementSettingPage.class, new PageParameters()));
|
||||
|
||||
add(new Tabbable("aiSettingTabs", tabs));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Component newTopbarTitle(String componentId) {
|
||||
return new Label(componentId, _T("My AI Settings"));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
<wicket:extend>
|
||||
<div wicket:id="content"></div>
|
||||
</wicket:extend>
|
||||
@ -0,0 +1,29 @@
|
||||
package io.onedev.server.web.page.my.aisetting;
|
||||
|
||||
import org.apache.wicket.model.AbstractReadOnlyModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.user.aisetting.EntitlementSettingPanel;
|
||||
|
||||
public class MyEntitlementSettingPage extends MyAiSettingPage {
|
||||
|
||||
public MyEntitlementSettingPage(PageParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
add(new EntitlementSettingPanel("content", new AbstractReadOnlyModel<User>() {
|
||||
|
||||
@Override
|
||||
public User getObject() {
|
||||
return getUser();
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
<wicket:extend>
|
||||
<div wicket:id="content"></div>
|
||||
</wicket:extend>
|
||||
@ -0,0 +1,29 @@
|
||||
package io.onedev.server.web.page.my.aisetting;
|
||||
|
||||
import org.apache.wicket.model.AbstractReadOnlyModel;
|
||||
import org.apache.wicket.request.mapper.parameter.PageParameters;
|
||||
|
||||
import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.user.aisetting.ModelSettingPanel;
|
||||
|
||||
public class MyModelSettingPage extends MyAiSettingPage {
|
||||
|
||||
public MyModelSettingPage(PageParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInitialize() {
|
||||
super.onInitialize();
|
||||
|
||||
add(new ModelSettingPanel("content", new AbstractReadOnlyModel<User>() {
|
||||
|
||||
@Override
|
||||
public User getObject() {
|
||||
return getUser();
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.page.my.emailaddresses;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -15,7 +16,7 @@ public class MyEmailAddressesPage extends MyPage {
|
||||
|
||||
public MyEmailAddressesPage(PageParameters params) {
|
||||
super(params);
|
||||
if (getUser().isServiceAccount())
|
||||
if (getUser().getType() != ORDINARY)
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.user.passwordedit.PasswordEditPanel;
|
||||
import io.onedev.server.web.page.my.MyPage;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -16,7 +17,7 @@ public class MyPasswordPage extends MyPage {
|
||||
|
||||
public MyPasswordPage(PageParameters params) {
|
||||
super(params);
|
||||
if (getUser().isServiceAccount() || getUser().isDisabled())
|
||||
if (getUser().getType() != ORDINARY || getUser().isDisabled())
|
||||
throw new IllegalStateException();
|
||||
if (getUser().getPassword() == null)
|
||||
throw new ExplicitException(_T("Unable to change password as you are authenticating via external system"));
|
||||
@ -28,12 +29,12 @@ public class MyPasswordPage extends MyPage {
|
||||
|
||||
add(new PasswordEditPanel("content", new AbstractReadOnlyModel<User>() {
|
||||
|
||||
@Override
|
||||
public User getObject() {
|
||||
return getUser();
|
||||
}
|
||||
|
||||
}));
|
||||
@Override
|
||||
public User getObject() {
|
||||
return getUser();
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.page.my.querywatch;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -20,7 +21,7 @@ public class MyQueryWatchesPage extends MyPage {
|
||||
|
||||
public MyQueryWatchesPage(PageParameters params) {
|
||||
super(params);
|
||||
if (getUser().isServiceAccount() || getUser().isDisabled())
|
||||
if (getUser().getType() != ORDINARY || getUser().isDisabled())
|
||||
throw new IllegalStateException();
|
||||
tabName = params.get(PARAM_TAB).toString(QueryWatchesPanel.TAB_ISSUE);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package io.onedev.server.web.page.my.ssoaccounts;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -15,7 +16,7 @@ public class MySsoAccountsPage extends MyPage {
|
||||
|
||||
public MySsoAccountsPage(PageParameters params) {
|
||||
super(params);
|
||||
if (getUser().isDisabled() || getUser().isServiceAccount())
|
||||
if (getUser().isDisabled() || getUser().getType() != ORDINARY)
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import io.onedev.server.model.User;
|
||||
import io.onedev.server.web.component.user.twofactorauthentication.TwoFactorAuthenticationStatusPanel;
|
||||
import io.onedev.server.web.page.my.MyPage;
|
||||
|
||||
import static io.onedev.server.model.User.Type.ORDINARY;
|
||||
import static io.onedev.server.web.translation.Translation._T;
|
||||
|
||||
import org.apache.wicket.Component;
|
||||
@ -15,7 +16,7 @@ public class MyTwoFactorAuthenticationPage extends MyPage {
|
||||
|
||||
public MyTwoFactorAuthenticationPage(PageParameters params) {
|
||||
super(params);
|
||||
if (getUser().isServiceAccount() || getUser().isDisabled())
|
||||
if (getUser().getType() != ORDINARY || getUser().isDisabled())
|
||||
throw new IllegalStateException();
|
||||
else if (!getUser().isEnforce2FA())
|
||||
throw new ExplicitException(_T("Two-factor authentication not enabled"));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user