diff --git a/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSProtocolHostPanelExtension.kt b/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSProtocolHostPanelExtension.kt index 5c29bed..7246562 100644 --- a/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSProtocolHostPanelExtension.kt +++ b/plugins/cos/src/main/kotlin/app/termora/plugins/cos/COSProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugins.cos +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -13,7 +14,7 @@ class COSProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt return COSProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return COSProtocolHostPanel() } } \ No newline at end of file diff --git a/plugins/obs/src/main/kotlin/app/termora/plugins/obs/OBSProtocolHostPanelExtension.kt b/plugins/obs/src/main/kotlin/app/termora/plugins/obs/OBSProtocolHostPanelExtension.kt index a109f4c..4ff0dd1 100644 --- a/plugins/obs/src/main/kotlin/app/termora/plugins/obs/OBSProtocolHostPanelExtension.kt +++ b/plugins/obs/src/main/kotlin/app/termora/plugins/obs/OBSProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugins.obs +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -13,7 +14,7 @@ class OBSProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt return OBSProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return OBSProtocolHostPanel() } } \ No newline at end of file diff --git a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanelExtension.kt b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanelExtension.kt index 41e5f86..4adebfc 100644 --- a/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanelExtension.kt +++ b/plugins/oss/src/main/kotlin/app/termora/plugins/oss/OSSProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugins.oss +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -13,7 +14,7 @@ class OSSProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt return OSSProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return OSSProtocolHostPanel() } } \ No newline at end of file diff --git a/plugins/s3/src/main/kotlin/app/termora/plugins/s3/S3ProtocolHostPanelExtension.kt b/plugins/s3/src/main/kotlin/app/termora/plugins/s3/S3ProtocolHostPanelExtension.kt index c214c9c..642b794 100644 --- a/plugins/s3/src/main/kotlin/app/termora/plugins/s3/S3ProtocolHostPanelExtension.kt +++ b/plugins/s3/src/main/kotlin/app/termora/plugins/s3/S3ProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugins.s3 +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -13,7 +14,7 @@ class S3ProtocolHostPanelExtension private constructor() : ProtocolHostPanelExte return S3ProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return S3ProtocolHostPanel() } } \ No newline at end of file diff --git a/plugins/smb/src/main/kotlin/app/termora/plugins/smb/SMBProtocolHostPanelExtension.kt b/plugins/smb/src/main/kotlin/app/termora/plugins/smb/SMBProtocolHostPanelExtension.kt index 1c6ebeb..d4ca8f5 100644 --- a/plugins/smb/src/main/kotlin/app/termora/plugins/smb/SMBProtocolHostPanelExtension.kt +++ b/plugins/smb/src/main/kotlin/app/termora/plugins/smb/SMBProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugins.smb +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -13,7 +14,7 @@ class SMBProtocolHostPanelExtension private constructor() : ProtocolHostPanelExt return SMBProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return SMBProtocolHostPanel() } } \ No newline at end of file diff --git a/plugins/webdav/src/main/kotlin/app/termora/plugins/webdav/WebDAVProtocolHostPanelExtension.kt b/plugins/webdav/src/main/kotlin/app/termora/plugins/webdav/WebDAVProtocolHostPanelExtension.kt index 0123481..11204ff 100644 --- a/plugins/webdav/src/main/kotlin/app/termora/plugins/webdav/WebDAVProtocolHostPanelExtension.kt +++ b/plugins/webdav/src/main/kotlin/app/termora/plugins/webdav/WebDAVProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugins.webdav +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -13,7 +14,7 @@ class WebDAVProtocolHostPanelExtension private constructor() : ProtocolHostPanel return WebDAVProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return WebDAVProtocolHostPanel() } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/NewHostDialogV2.kt b/src/main/kotlin/app/termora/NewHostDialogV2.kt index 34e919e..6974e51 100644 --- a/src/main/kotlin/app/termora/NewHostDialogV2.kt +++ b/src/main/kotlin/app/termora/NewHostDialogV2.kt @@ -1,5 +1,6 @@ package app.termora +import app.termora.account.AccountOwner import app.termora.actions.AnAction import app.termora.actions.AnActionEvent import app.termora.protocol.* @@ -18,7 +19,11 @@ import java.awt.Dimension import java.awt.Window import javax.swing.* -class NewHostDialogV2(owner: Window, private val editHost: Host? = null) : DialogWrapper(owner) { +class NewHostDialogV2( + owner: Window, + private val editHost: Host? = null, + private val accountOwner: AccountOwner, +) : DialogWrapper(owner) { private object Current { var card: ProtocolHostPanel? = null @@ -65,11 +70,11 @@ class NewHostDialogV2(owner: Window, private val editHost: Host? = null) : Dialo toolbar.add(Box.createHorizontalGlue()) val extensions = ProtocolHostPanelExtension.extensions - .filter { it.canCreateProtocolHostPanel() } + .filter { it.canCreateProtocolHostPanel(accountOwner) } for ((index, extension) in extensions.withIndex()) { val protocol = extension.getProtocolProvider().getProtocol() val icon = ScaleIcon(extension.getProtocolProvider().getIcon(), 22) - val hostPanel = extension.createProtocolHostPanel() + val hostPanel = extension.createProtocolHostPanel(accountOwner) val button = JToggleButton(protocol, icon).apply { buttonGroup.add(this) } button.setVerticalTextPosition(SwingConstants.BOTTOM) button.setHorizontalTextPosition(SwingConstants.CENTER) diff --git a/src/main/kotlin/app/termora/Scope.kt b/src/main/kotlin/app/termora/Scope.kt index f028e2a..3388eb5 100644 --- a/src/main/kotlin/app/termora/Scope.kt +++ b/src/main/kotlin/app/termora/Scope.kt @@ -33,7 +33,7 @@ open class Scope( return get(clazz) } - synchronized(clazz) { + synchronized(this) { if (beans.containsKey(clazz)) { return get(clazz) } diff --git a/src/main/kotlin/app/termora/TerminalTabbed.kt b/src/main/kotlin/app/termora/TerminalTabbed.kt index 8f9267f..a9925b3 100644 --- a/src/main/kotlin/app/termora/TerminalTabbed.kt +++ b/src/main/kotlin/app/termora/TerminalTabbed.kt @@ -1,6 +1,7 @@ package app.termora +import app.termora.account.AccountManager import app.termora.actions.* import app.termora.database.DatabaseChangedExtension import app.termora.database.DatabaseManager @@ -244,10 +245,15 @@ class TerminalTabbed( val edit = popupMenu.add(I18n.getString("termora.keymgr.edit")) edit.addActionListener(object : AnAction() { private val hostManager get() = HostManager.getInstance() + private val accountManager get() = AccountManager.getInstance() + override fun actionPerformed(evt: AnActionEvent) { if (tab is HostTerminalTab) { val host = hostManager.getHost(tab.host.id) ?: return - val dialog = NewHostDialogV2(evt.window, host) + val dialog = NewHostDialogV2( + evt.window, host, + accountManager.getOwners().first { it.id == host.ownerId }, + ) dialog.setLocationRelativeTo(evt.window) dialog.isVisible = true diff --git a/src/main/kotlin/app/termora/account/AccountHttp.kt b/src/main/kotlin/app/termora/account/AccountHttp.kt index 7d76988..a2a23f8 100644 --- a/src/main/kotlin/app/termora/account/AccountHttp.kt +++ b/src/main/kotlin/app/termora/account/AccountHttp.kt @@ -183,8 +183,8 @@ object AccountHttp { if (isRefreshing.compareAndSet(false, true)) { try { - // 刷新 token - accountManager.refreshToken() + // 刷新 token 和用户 + accountManager.refresh() } finally { lock.withLock { isRefreshing.set(false) diff --git a/src/main/kotlin/app/termora/account/AccountManager.kt b/src/main/kotlin/app/termora/account/AccountManager.kt index 12ef524..014375e 100644 --- a/src/main/kotlin/app/termora/account/AccountManager.kt +++ b/src/main/kotlin/app/termora/account/AccountManager.kt @@ -14,12 +14,15 @@ import okhttp3.RequestBody.Companion.toRequestBody import org.apache.commons.codec.binary.Base64 import org.apache.commons.io.IOUtils import org.apache.commons.lang3.StringUtils +import org.slf4j.LoggerFactory import java.security.PrivateKey import java.security.PublicKey import javax.swing.SwingUtilities class AccountManager private constructor() : ApplicationRunnerExtension { companion object { + private val log = LoggerFactory.getLogger(AccountManager::class.java) + fun getInstance(): AccountManager { return ApplicationScope.forApplicationScope() .getOrCreate(AccountManager::class) { AccountManager() } @@ -30,6 +33,7 @@ class AccountManager private constructor() : ApplicationRunnerExtension { } } + private val serverManager get() = ServerManager.getInstance() private var account = locally() private val accountProperties get() = AccountProperties.getInstance() @@ -48,10 +52,14 @@ class AccountManager private constructor() : ApplicationRunnerExtension { fun getAccessToken() = account.accessToken fun getRefreshToken() = account.refreshToken fun getOwnerIds() = account.teams.map { it.id }.toMutableList().apply { add(getAccountId()) }.toSet() - fun getOwners() = - account.teams.map { AccountOwner(it.id, it.name, OwnerType.Team) } - .toMutableList().apply { AccountOwner(getAccountId(), getEmail(), OwnerType.User) } - .toSet() + fun getOwners(): Set { + val owners = mutableSetOf() + owners.add(AccountOwner(getAccountId(), getEmail(), OwnerType.User)) + for (team in getTeams()) { + owners.add(AccountOwner(team.id, team.name, OwnerType.Team)) + } + return owners + } fun isFreePlan(): Boolean { return isLocally() || getSubscription().plan == SubscriptionPlan.Free @@ -126,37 +134,39 @@ class AccountManager private constructor() : ApplicationRunnerExtension { * 设置账户信息,可以多次调用,每次修改用户信息都要通过这个方法 */ internal fun login(account: Account) { + synchronized(this) { - val oldAccount = this.account + val oldAccount = this.account - this.account = account + this.account = account - // 立即保存到数据库 - val accountProperties = AccountProperties.getInstance() - accountProperties.id = account.id - accountProperties.server = account.server - accountProperties.email = account.email - accountProperties.teams = ohMyJson.encodeToString(account.teams) - accountProperties.subscriptions = ohMyJson.encodeToString(account.subscriptions) - accountProperties.accessToken = account.accessToken - accountProperties.refreshToken = account.refreshToken - accountProperties.secretKey = ohMyJson.encodeToString(account.secretKey) + // 立即保存到数据库 + val accountProperties = AccountProperties.getInstance() + accountProperties.id = account.id + accountProperties.server = account.server + accountProperties.email = account.email + accountProperties.teams = ohMyJson.encodeToString(account.teams) + accountProperties.subscriptions = ohMyJson.encodeToString(account.subscriptions) + accountProperties.accessToken = account.accessToken + accountProperties.refreshToken = account.refreshToken + accountProperties.secretKey = ohMyJson.encodeToString(account.secretKey) - // 如果变更账户了,那么同步时间从0开始 - if (oldAccount.id != account.id) { - accountProperties.nextSynchronizationSince = 0 + // 如果变更账户了,那么同步时间从0开始 + if (oldAccount.id != account.id) { + accountProperties.nextSynchronizationSince = 0 + } + + if (isLocally().not()) { + accountProperties.publicKey = Base64.encodeBase64String(account.publicKey.encoded) + accountProperties.privateKey = Base64.encodeBase64String(account.privateKey.encoded) + } else { + accountProperties.publicKey = StringUtils.EMPTY + accountProperties.privateKey = StringUtils.EMPTY + } + + // 通知变化 + notifyAccountChanged(oldAccount, account) } - - if (isLocally().not()) { - accountProperties.publicKey = Base64.encodeBase64String(account.publicKey.encoded) - accountProperties.privateKey = Base64.encodeBase64String(account.privateKey.encoded) - } else { - accountProperties.publicKey = StringUtils.EMPTY - accountProperties.privateKey = StringUtils.EMPTY - } - - // 通知变化 - notifyAccountChanged(oldAccount, account) } private fun notifyAccountChanged(oldAccount: Account, newAccount: Account) { @@ -220,7 +230,7 @@ class AccountManager private constructor() : ApplicationRunnerExtension { override fun ready() { if (isLocally().not()) { - swingCoroutineScope.launch(Dispatchers.IO) { refreshToken() } + swingCoroutineScope.launch(Dispatchers.IO) { refresh() } } } @@ -228,8 +238,34 @@ class AccountManager private constructor() : ApplicationRunnerExtension { /** * 刷新用户 */ - fun refresh(accessToken: String = getAccessToken()) { + fun refresh() { + runCatching { refreshToken() }.onSuccess { + refreshAccount() + }.onFailure { + if (log.isErrorEnabled) { + log.error(it.message, it) + } + } + } + fun refreshAccount() { + try { + val me = serverManager.callMe(account.server, getAccessToken()) + val teams = me.teams.map { + Team( + id = it.id, + name = it.name, + secretKey = RSA.decrypt(getPrivateKey(), Base64.decodeBase64(it.secretKey)), + role = it.role + ) + } + // 重新登录 + login(account.copy(teams = teams, subscriptions = me.subscriptions)) + } catch (e: Exception) { + if (log.isErrorEnabled) { + log.error(e.message, e) + } + } } class AccountApplicationRunnerExtension private constructor() : ApplicationRunnerExtension { diff --git a/src/main/kotlin/app/termora/account/AccountOption.kt b/src/main/kotlin/app/termora/account/AccountOption.kt index 8e7160e..baa7bbf 100644 --- a/src/main/kotlin/app/termora/account/AccountOption.kt +++ b/src/main/kotlin/app/termora/account/AccountOption.kt @@ -75,8 +75,10 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable { val subscription = accountManager.getSubscription() val isFreePlan = accountManager.isFreePlan() val isLocally = accountManager.isLocally() - val validTo = if (isFreePlan) "-" else if (subscription.endAt >= Long.MAX_VALUE) - I18n.getString("termora.settings.account.lifetime") else + val validTo = if (isFreePlan) "-" + else if (subscription.endAt >= Long.MAX_VALUE) + I18n.getString("termora.settings.account.lifetime") + else DateFormatUtils.format(Date(subscription.endAt), I18n.getString("termora.date-format")) val lastSynchronizationOn = if (isFreePlan) "-" else DateFormatUtils.format( @@ -158,9 +160,19 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable { if (isFreePlan.not()) { actions.add(JXHyperlink(object : AnAction(I18n.getString("termora.settings.account.sync-now")) { override fun actionPerformed(evt: AnActionEvent) { + // 全量同步 + accountProperties.nextSynchronizationSince = 0 + + // 拉取 PullService.getInstance().trigger() + + // 推送 PushService.getInstance().trigger() + swingCoroutineScope.launch(Dispatchers.IO) { + // 刷新账户 + accountManager.refreshAccount() + withContext(Dispatchers.Swing) { isEnabled = false lastSynchronizationOnLabel.text = DateFormatUtils.format( diff --git a/src/main/kotlin/app/termora/account/AccountOwner.kt b/src/main/kotlin/app/termora/account/AccountOwner.kt index 62c019e..430e7f5 100644 --- a/src/main/kotlin/app/termora/account/AccountOwner.kt +++ b/src/main/kotlin/app/termora/account/AccountOwner.kt @@ -2,5 +2,4 @@ package app.termora.account import app.termora.database.OwnerType -data class AccountOwner(val id: String, val name: String, val type: OwnerType) { -} \ No newline at end of file +data class AccountOwner(val id: String, val name: String, val type: OwnerType) \ No newline at end of file diff --git a/src/main/kotlin/app/termora/account/LoginServerDialog.kt b/src/main/kotlin/app/termora/account/LoginServerDialog.kt index 391c889..e28d7e7 100644 --- a/src/main/kotlin/app/termora/account/LoginServerDialog.kt +++ b/src/main/kotlin/app/termora/account/LoginServerDialog.kt @@ -48,6 +48,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { Server(I18n.getString("termora.settings.account.server-singapore"), "https://account.termora.app") private val chinaServer = Server(I18n.getString("termora.settings.account.server-china"), "https://account.termora.cn") + private val serverManager get() = ServerManager.getInstance() init { isModal = true @@ -359,7 +360,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) { val loginJob = swingCoroutineScope.launch(Dispatchers.IO) { try { - ServerManager.getInstance().login( + serverManager.login( server, usernameTextField.text, String(passwordField.password), mfaTextField.text.trim() ) diff --git a/src/main/kotlin/app/termora/account/PullService.kt b/src/main/kotlin/app/termora/account/PullService.kt index e2f90f5..03f5ae8 100644 --- a/src/main/kotlin/app/termora/account/PullService.kt +++ b/src/main/kotlin/app/termora/account/PullService.kt @@ -45,6 +45,15 @@ class PullService private constructor() : SyncService(), Disposable, Application lastChangeHash = StringUtils.EMPTY } + // 团队变了,全量同步 + if (oldAccount.id == newAccount.id) { + if (oldAccount.teams != newAccount.teams) { + accountProperties.nextSynchronizationSince = 0 + trigger() + return + } + } + if (oldAccount.isLocally && newAccount.isLocally.not()) { trigger() } @@ -213,7 +222,7 @@ class PullService private constructor() : SyncService(), Disposable, Application log.debug("拉取数据: {} 成功, 响应码: {}", id, response.code) } - if(response.isSuccessful.not()){ + if (response.isSuccessful.not()) { IOUtils.closeQuietly(response) } @@ -281,7 +290,7 @@ class PullService private constructor() : SyncService(), Disposable, Application ownerId = ownerId, ownerType = ownerType, type = type, - data = decryptData(id, data), + data = decryptData(id, data, ownerId), version = version, // 因为已经是拉取最新版本了,所以这里无需再同步了 synced = true, @@ -298,11 +307,8 @@ class PullService private constructor() : SyncService(), Disposable, Application if (log.isDebugEnabled) { log.debug("数据: {}, 类型: {} 云端已经删除,本地即将删除", id, type) } - databaseManager.delete( - id, type, - DatabaseChangedExtension.Source.Sync - ) + databaseManager.delete(id, type, DatabaseChangedExtension.Source.Sync) if (log.isInfoEnabled) { log.info("数据: {}, 类型: {} 已从本地删除", id, type) @@ -340,7 +346,7 @@ class PullService private constructor() : SyncService(), Disposable, Application ownerId = ownerId, ownerType = ownerType, type = type, - data = decryptData(id, data), + data = decryptData(id, data, ownerId), version = version, // 因为已经是拉取最新版本了,所以这里无需再同步了 synced = true, @@ -377,7 +383,7 @@ class PullService private constructor() : SyncService(), Disposable, Application pullChanges() // N 秒拉一次 - val result = withTimeoutOrNull(Random.nextInt(5, 15).seconds) { + val result = withTimeoutOrNull(Random.nextInt(3, 10).seconds) { channel.receiveCatching() } ?: continue if (result.isFailure) break diff --git a/src/main/kotlin/app/termora/account/PushService.kt b/src/main/kotlin/app/termora/account/PushService.kt index b2b7ad1..59a3413 100644 --- a/src/main/kotlin/app/termora/account/PushService.kt +++ b/src/main/kotlin/app/termora/account/PushService.kt @@ -4,6 +4,7 @@ import app.termora.* import app.termora.Application.ohMyJson import app.termora.database.Data import app.termora.database.DatabaseChangedExtension +import app.termora.database.OwnerType import app.termora.plugin.DispatchThread import app.termora.plugin.internal.extension.DynamicExtensionHandler import kotlinx.coroutines.Dispatchers @@ -106,7 +107,20 @@ class PushService private constructor() : SyncService(), Disposable, Application .delete() .build() - AccountHttp.execute(request = request) + try { + AccountHttp.execute(request = request) + } catch (e: Exception) { + if (e is ResponseException) { + if (e.code == 403) { + // 如果是 Team 发现没有权限,那么很有可能是被提出团队 + if (data.ownerType == OwnerType.Team.name) { + // 刷新用户 + accountManager.refreshAccount() + } + } + } + throw e + } // 修改为已经同步 updateData(data.id, synced = true) @@ -153,6 +167,12 @@ class PushService private constructor() : SyncService(), Disposable, Application } // 标记为已经同步 updateData(data.id, synced = true, version = data.version) + + // 如果是 Team 发现没有权限,那么很有可能是被提出团队 + if (data.ownerType == OwnerType.Team.name) { + // 刷新用户 + accountManager.refreshAccount() + } return } else if (response.code == 409) { // 版本冲突,一般来说是云端版本大于本地版本 val json = ohMyJson.decodeFromString(text) diff --git a/src/main/kotlin/app/termora/account/ServerManager.kt b/src/main/kotlin/app/termora/account/ServerManager.kt index f89c3b6..32a36f6 100644 --- a/src/main/kotlin/app/termora/account/ServerManager.kt +++ b/src/main/kotlin/app/termora/account/ServerManager.kt @@ -54,7 +54,7 @@ class ServerManager private constructor() { val loginResponse = callLogin(serverInfo, server, username, password, mfa) // call me - val meResponse = callMe(server, loginResponse.accessToken) + val meResponse = callMe(server.server, loginResponse.accessToken) // 解密 val salt = "${serverInfo.salt}:${username}".toByteArray() @@ -139,9 +139,9 @@ class ServerManager private constructor() { } - private fun callMe(server: Server, accessToken: String): MeResponse { + fun callMe(server: String, accessToken: String): MeResponse { val request = Request.Builder() - .url("${server.server}/v1/users/me") + .url("${server}/v1/users/me") .header("Authorization", "Bearer $accessToken") .build() val text = AccountHttp.execute(request = request) @@ -149,13 +149,13 @@ class ServerManager private constructor() { } @Serializable - private data class ServerInfo(val salt: String) + data class ServerInfo(val salt: String) @Serializable - private data class LoginResponse(val accessToken: String, val refreshToken: String) + data class LoginResponse(val accessToken: String, val refreshToken: String) @Serializable - private data class MeResponse( + data class MeResponse( val id: String, val email: String, val publicKey: String, @@ -167,5 +167,5 @@ class ServerManager private constructor() { @Serializable - private data class MeTeam(val id: String, val name: String, val role: TeamRole, val secretKey: String) + data class MeTeam(val id: String, val name: String, val role: TeamRole, val secretKey: String) } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/account/SyncService.kt b/src/main/kotlin/app/termora/account/SyncService.kt index 61996c8..31cf6f7 100644 --- a/src/main/kotlin/app/termora/account/SyncService.kt +++ b/src/main/kotlin/app/termora/account/SyncService.kt @@ -78,28 +78,23 @@ abstract class SyncService { protected fun encryptData(id: String, data: String, ownerId: String): String { val iv = DigestUtils.sha256(id).copyOf(12) - var secretKey = EMPTY_BYTE_ARRAY - if (ownerId != accountManager.getAccountId()) { - val team = accountManager.getTeams().firstOrNull { it.id == ownerId } - if (team == null) { - return StringUtils.EMPTY - } else { - secretKey = team.secretKey - } - } else if (ownerId == accountManager.getAccountId()) { - secretKey = accountManager.getSecretKey() - } + val secretKey = getSecretKey(ownerId) if (secretKey.isEmpty()) return StringUtils.EMPTY return Base64.encodeBase64String(AES.GCM.encrypt(secretKey, iv, data.toByteArray())) } - protected fun decryptData(id: String, data: String): String { + protected fun getSecretKey(ownerId: String): ByteArray { + if (ownerId == accountManager.getAccountId()) { + return accountManager.getSecretKey() + } + val team = accountManager.getTeams().firstOrNull { it.id == ownerId } + return team?.secretKey ?: EMPTY_BYTE_ARRAY + } + + protected fun decryptData(id: String, data: String, ownerId: String): String { val iv = DigestUtils.sha256(id).copyOf(12) - return String( - AES.GCM.decrypt( - accountManager.getSecretKey(), iv, - Base64.decodeBase64(data) - ) - ) + val secretKey = getSecretKey(ownerId) + if (secretKey.isEmpty()) throw IllegalStateException("根据 ownerId 无法获取对应密钥") + return String(AES.GCM.decrypt(secretKey, iv, Base64.decodeBase64(data))) } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/account/Team.kt b/src/main/kotlin/app/termora/account/Team.kt index 8f36aa1..afb5640 100644 --- a/src/main/kotlin/app/termora/account/Team.kt +++ b/src/main/kotlin/app/termora/account/Team.kt @@ -26,4 +26,22 @@ class Team( * 所属角色 */ val role: TeamRole, -) +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Team + + if (id != other.id) return false + if (name != other.name) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + name.hashCode() + return result + } +} diff --git a/src/main/kotlin/app/termora/actions/NewHostAction.kt b/src/main/kotlin/app/termora/actions/NewHostAction.kt index dbb277c..8d88aab 100644 --- a/src/main/kotlin/app/termora/actions/NewHostAction.kt +++ b/src/main/kotlin/app/termora/actions/NewHostAction.kt @@ -1,6 +1,7 @@ package app.termora.actions import app.termora.NewHostDialogV2 +import app.termora.account.AccountManager import app.termora.tree.HostTreeNode import javax.swing.tree.TreePath @@ -14,6 +15,8 @@ class NewHostAction : AnAction() { } + private val accountManager get() = AccountManager.getInstance() + override fun actionPerformed(evt: AnActionEvent) { val tree = evt.getData(DataProviders.Welcome.HostTree) ?: return var lastNode = (tree.lastSelectedPathComponent ?: tree.model.root) as? HostTreeNode ?: return @@ -27,7 +30,7 @@ class NewHostAction : AnAction() { } val lastHost = lastNode.host - val dialog = NewHostDialogV2(evt.window) + val dialog = NewHostDialogV2(evt.window, accountOwner = accountManager.getOwners().first { it.id == lastHost.ownerId }) dialog.setLocationRelativeTo(evt.window) dialog.isVisible = true val host = (dialog.host ?: return).copy( diff --git a/src/main/kotlin/app/termora/database/DatabaseManager.kt b/src/main/kotlin/app/termora/database/DatabaseManager.kt index 04cb781..665cfc1 100644 --- a/src/main/kotlin/app/termora/database/DatabaseManager.kt +++ b/src/main/kotlin/app/termora/database/DatabaseManager.kt @@ -11,7 +11,6 @@ import app.termora.highlight.KeywordHighlightManager import app.termora.keymap.KeymapManager import app.termora.keymgr.KeyManager import app.termora.macro.MacroManager -import app.termora.plugin.ExtensionManager import app.termora.plugin.internal.extension.DynamicExtensionHandler import app.termora.snippet.SnippetManager import app.termora.terminal.CursorStyle @@ -23,6 +22,7 @@ import org.jetbrains.exposed.v1.core.statements.StatementType import org.jetbrains.exposed.v1.jdbc.* import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager import org.jetbrains.exposed.v1.jdbc.transactions.transaction +import org.slf4j.Logger import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.locks.ReentrantLock @@ -32,11 +32,8 @@ import kotlin.reflect.KProperty class DatabaseManager private constructor() : Disposable { companion object { + val log: Logger = LoggerFactory.getLogger(DatabaseManager::class.java) - private const val DB_PASSWORD = "DB_PASSWORD" - private const val DB_SALT = "DB_SALT" - - val log = LoggerFactory.getLogger(DatabaseManager::class.java)!! fun getInstance(): DatabaseManager { return ApplicationScope.forApplicationScope() .getOrCreate(DatabaseManager::class) { DatabaseManager() } @@ -52,14 +49,6 @@ class DatabaseManager private constructor() : Disposable { val sftp by lazy { SFTP(this) } - @Volatile - internal var dbPassword = StringUtils.EMPTY - private set - - @Volatile - internal var dbSalt = StringUtils.EMPTY - private set - private val map = Collections.synchronizedMap(mutableMapOf()) private val accountManager get() = AccountManager.getInstance() @@ -101,11 +90,6 @@ class DatabaseManager private constructor() : Disposable { // 注册动态扩展 registerDynamicExtensions() - for (extension in ExtensionManager.getInstance().getExtensions(DatabaseReadyExtension::class.java)) { - extension.ready(this) - } - - } private fun registerDynamicExtensions() { @@ -308,6 +292,7 @@ class DatabaseManager private constructor() : Disposable { DataEntity.update({ DataEntity.id eq id }) { it[DataEntity.deleted] = true // 如果是本地用户,那么删除是不需要同步的,云端用户才需要同步 + // 云端用户也会判断,如果来源 Sync 那么默认同步了 it[DataEntity.synced] = accountManager.isLocally() it[DataEntity.data] = StringUtils.EMPTY } @@ -367,7 +352,6 @@ class DatabaseManager private constructor() : Disposable { private inner class AccountDataTransferExtension : AccountExtension { - private val hostManager get() = HostManager.getInstance() override fun onAccountChanged(oldAccount: Account, newAccount: Account) { if (oldAccount.isLocally && newAccount.isLocally) { return @@ -481,19 +465,17 @@ class DatabaseManager private constructor() : Disposable { return } + // 如果团队变更,那么删除所有旧的团队数据,静默删除 if (oldAccount.id == newAccount.id) { - return - } - - for (team in oldAccount.teams) { - // 如果被踢出团队,那么移除该团队的所有资产 - if (newAccount.teams.none { it.id == team.id }) { - lock.withLock { - transaction(database) { - DataEntity.deleteWhere { - DataEntity.ownerId.eq(team.id) and (DataEntity.ownerType.eq( - OwnerType.Team.name - )) + if (oldAccount.teams != newAccount.teams) { + for (team in oldAccount.teams) { + lock.withLock { + transaction(database) { + DataEntity.deleteWhere { + DataEntity.ownerId.eq(team.id) and (DataEntity.ownerType.eq( + OwnerType.Team.name + )) + } } } } diff --git a/src/main/kotlin/app/termora/database/DatabaseReadyExtension.kt b/src/main/kotlin/app/termora/database/DatabaseReadyExtension.kt deleted file mode 100644 index 4e06fd0..0000000 --- a/src/main/kotlin/app/termora/database/DatabaseReadyExtension.kt +++ /dev/null @@ -1,37 +0,0 @@ -package app.termora.database - -import app.termora.database.DatabaseManager.Companion.log -import app.termora.plugin.DispatchThread -import app.termora.plugin.Extension -import app.termora.plugin.ExtensionManager -import javax.swing.SwingUtilities - -interface DatabaseReadyExtension : Extension { - - companion object { - fun fireReady(databaseManager: DatabaseManager) { - if (SwingUtilities.isEventDispatchThread()) { - for (extension in ExtensionManager.getInstance().getExtensions(DatabaseReadyExtension::class.java)) { - try { - extension.ready(databaseManager) - } catch (e: Exception) { - if (log.isErrorEnabled) { - log.error(e.message, e) - } - } - } - } else { - SwingUtilities.invokeLater { fireReady(databaseManager) } - } - } - } - - /** - * 数据库初始化完成 - */ - fun ready(databaseManager: DatabaseManager) {} - - override fun getDispatchThread(): DispatchThread { - return DispatchThread.BGT - } -} \ No newline at end of file diff --git a/src/main/kotlin/app/termora/keymgr/KeyManagerDialog.kt b/src/main/kotlin/app/termora/keymgr/KeyManagerDialog.kt index 18e8561..1c254e0 100644 --- a/src/main/kotlin/app/termora/keymgr/KeyManagerDialog.kt +++ b/src/main/kotlin/app/termora/keymgr/KeyManagerDialog.kt @@ -19,6 +19,7 @@ class KeyManagerDialog( owner: Window, private val selectMode: Boolean = false, size: Dimension = Dimension(UIManager.getInt("Dialog.width"), UIManager.getInt("Dialog.height")), + private val accountOwner: AccountOwner? = null, ) : DialogWrapper(owner) { var ok: Boolean = false @@ -56,12 +57,40 @@ class KeyManagerDialog( tabbed.border = BorderFactory.createMatteBorder(1, 0, 0, 0, DynamicColor.BorderColor) tabbed.tabPlacement = JTabbedPane.TOP + if (accountOwner == null || accountOwner.type == OwnerType.User) { + tabbed.addTab( + I18n.getString("termora.keymgr.my-keys"), + Icons.user, + KeyManagerPanel( + AccountOwner( + accountManager.getAccountId(), + accountManager.getEmail(), + OwnerType.User + ) + ) + ) + } + + if (accountOwner != null && accountManager.hasTeamFeature()) { + for (team in accountManager.getTeams()) { + if (team.id == accountOwner.id) { + tabbed.addTab( + team.name, + Icons.cwmUsers, + KeyManagerPanel( + AccountOwner( + team.id, + team.name, + OwnerType.Team + ) + ) + ) + return tabbed + } + } + } + - tabbed.addTab( - I18n.getString("termora.keymgr.my-keys"), - Icons.user, - KeyManagerPanel(AccountOwner(accountManager.getAccountId(), accountManager.getEmail(), OwnerType.User)) - ) if (accountManager.hasTeamFeature()) { for (team in accountManager.getTeams()) { diff --git a/src/main/kotlin/app/termora/plugin/internal/local/LocalProtocolHostPanelExtension.kt b/src/main/kotlin/app/termora/plugin/internal/local/LocalProtocolHostPanelExtension.kt index d082f7c..7481278 100644 --- a/src/main/kotlin/app/termora/plugin/internal/local/LocalProtocolHostPanelExtension.kt +++ b/src/main/kotlin/app/termora/plugin/internal/local/LocalProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugin.internal.local +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -14,7 +15,7 @@ internal class LocalProtocolHostPanelExtension private constructor() : ProtocolH return LocalProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return LocalProtocolHostPanel() } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/plugin/internal/rdp/RDPProtocolHostPanelExtension.kt b/src/main/kotlin/app/termora/plugin/internal/rdp/RDPProtocolHostPanelExtension.kt index 9ad5942..79a2436 100644 --- a/src/main/kotlin/app/termora/plugin/internal/rdp/RDPProtocolHostPanelExtension.kt +++ b/src/main/kotlin/app/termora/plugin/internal/rdp/RDPProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugin.internal.rdp +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -14,7 +15,7 @@ internal class RDPProtocolHostPanelExtension private constructor() : ProtocolHos return RDPProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return RDPProtocolHostPanel() } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/plugin/internal/serial/SerialProtocolHostPanelExtension.kt b/src/main/kotlin/app/termora/plugin/internal/serial/SerialProtocolHostPanelExtension.kt index 5fdef3b..326daaf 100644 --- a/src/main/kotlin/app/termora/plugin/internal/serial/SerialProtocolHostPanelExtension.kt +++ b/src/main/kotlin/app/termora/plugin/internal/serial/SerialProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugin.internal.serial +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -14,7 +15,7 @@ internal class SerialProtocolHostPanelExtension private constructor() : Protocol return SerialProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return SerialProtocolHostPanel() } diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt index 5deaf8c..f827eb1 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHHostOptionsPane.kt @@ -1,6 +1,7 @@ package app.termora.plugin.internal.ssh import app.termora.* +import app.termora.account.AccountOwner import app.termora.keymgr.KeyManager import app.termora.keymgr.KeyManagerDialog import app.termora.plugin.internal.BasicProxyOption @@ -29,7 +30,7 @@ import javax.swing.table.DefaultTableModel import kotlin.math.max @Suppress("CascadeIf") -open class SSHHostOptionsPane : OptionsPane() { +open class SSHHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() { protected val tunnelingOption = TunnelingOption() protected val generalOption = GeneralOption() protected val proxyOption = BasicProxyOption() @@ -375,6 +376,7 @@ open class SSHHostOptionsPane : OptionsPane() { val dialog = KeyManagerDialog( owner, selectMode = true, + accountOwner = accountOwner, ) dialog.pack() dialog.setLocationRelativeTo(owner) @@ -383,7 +385,7 @@ open class SSHHostOptionsPane : OptionsPane() { val selectedItem = publicKeyComboBox.selectedItem publicKeyComboBox.removeAllItems() - for (keyPair in KeyManager.getInstance().getOhKeyPairs()) { + for (keyPair in KeyManager.getInstance().getOhKeyPairs(accountOwner.id)) { publicKeyComboBox.addItem(keyPair.id) } publicKeyComboBox.selectedItem = selectedItem @@ -465,7 +467,7 @@ open class SSHHostOptionsPane : OptionsPane() { if (authenticationTypeComboBox.selectedItem == AuthenticationType.PublicKey) { val selectedItem = publicKeyComboBox.selectedItem publicKeyComboBox.removeAllItems() - for (pair in KeyManager.getInstance().getOhKeyPairs()) { + for (pair in KeyManager.getInstance().getOhKeyPairs(accountOwner.id)) { publicKeyComboBox.addItem(pair.id) } publicKeyComboBox.selectedItem = selectedItem diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanel.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanel.kt index 731517e..4752c26 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanel.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanel.kt @@ -2,12 +2,13 @@ package app.termora.plugin.internal.ssh import app.termora.Disposer import app.termora.Host +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import java.awt.BorderLayout -class SSHProtocolHostPanel : ProtocolHostPanel() { +class SSHProtocolHostPanel(accountOwner: AccountOwner) : ProtocolHostPanel() { - private val pane = SSHHostOptionsPane() + private val pane = SSHHostOptionsPane(accountOwner) init { initView() diff --git a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanelExtension.kt b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanelExtension.kt index b004d68..a203960 100644 --- a/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanelExtension.kt +++ b/src/main/kotlin/app/termora/plugin/internal/ssh/SSHProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugin.internal.ssh +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -14,8 +15,8 @@ internal class SSHProtocolHostPanelExtension private constructor() : ProtocolHos return SSHProtocolProvider.instance } - override fun createProtocolHostPanel(): ProtocolHostPanel { - return SSHProtocolHostPanel() + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { + return SSHProtocolHostPanel(accountOwner) } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/plugin/internal/wsl/WSLProtocolHostPanelExtension.kt b/src/main/kotlin/app/termora/plugin/internal/wsl/WSLProtocolHostPanelExtension.kt index 1931e51..0e51cc1 100644 --- a/src/main/kotlin/app/termora/plugin/internal/wsl/WSLProtocolHostPanelExtension.kt +++ b/src/main/kotlin/app/termora/plugin/internal/wsl/WSLProtocolHostPanelExtension.kt @@ -1,5 +1,6 @@ package app.termora.plugin.internal.wsl +import app.termora.account.AccountOwner import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanelExtension import app.termora.protocol.ProtocolProvider @@ -14,11 +15,11 @@ internal class WSLProtocolHostPanelExtension private constructor() : ProtocolHos return WSLProtocolProvider.instance } - override fun canCreateProtocolHostPanel(): Boolean { + override fun canCreateProtocolHostPanel(accountOwner: AccountOwner): Boolean { return WSLSupport.isSupported } - override fun createProtocolHostPanel(): ProtocolHostPanel { + override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { return WSLProtocolHostPanel() } } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/protocol/ProtocolHostPanelExtension.kt b/src/main/kotlin/app/termora/protocol/ProtocolHostPanelExtension.kt index e194bd6..3a51055 100644 --- a/src/main/kotlin/app/termora/protocol/ProtocolHostPanelExtension.kt +++ b/src/main/kotlin/app/termora/protocol/ProtocolHostPanelExtension.kt @@ -1,8 +1,10 @@ package app.termora.protocol +import app.termora.account.AccountOwner import app.termora.plugin.Extension import app.termora.plugin.ExtensionManager + interface ProtocolHostPanelExtension : Extension { companion object { val extensions @@ -19,11 +21,23 @@ interface ProtocolHostPanelExtension : Extension { /** * 是否可以创建协议主机面板 */ + @Deprecated("Old stuff") fun canCreateProtocolHostPanel(): Boolean = true + /** + * 是否可以创建协议主机面板 + */ + fun canCreateProtocolHostPanel(accountOwner: AccountOwner) = canCreateProtocolHostPanel() + /** * 创建协议主机面板 */ - fun createProtocolHostPanel(): ProtocolHostPanel + @Deprecated("Old stuff") + fun createProtocolHostPanel(): ProtocolHostPanel = throw UnsupportedOperationException() + + /** + * 创建协议主机面板 + */ + fun createProtocolHostPanel(accountOwner: AccountOwner) = createProtocolHostPanel() } \ No newline at end of file diff --git a/src/main/kotlin/app/termora/transfer/TransportTabbed.kt b/src/main/kotlin/app/termora/transfer/TransportTabbed.kt index 7e42a58..35170d1 100644 --- a/src/main/kotlin/app/termora/transfer/TransportTabbed.kt +++ b/src/main/kotlin/app/termora/transfer/TransportTabbed.kt @@ -1,6 +1,7 @@ package app.termora.transfer import app.termora.* +import app.termora.account.AccountManager import app.termora.actions.AnAction import app.termora.actions.AnActionEvent import app.termora.database.DatabaseChangedExtension @@ -164,9 +165,13 @@ class TransportTabbed( // 编辑 val edit = popupMenu.add(I18n.getString("termora.keymgr.edit")) edit.addActionListener(object : AnAction() { + private val accountManager get() = AccountManager.getInstance() override fun actionPerformed(evt: AnActionEvent) { val window = evt.window - val dialog = NewHostDialogV2(window, panel.host) + val dialog = NewHostDialogV2( + window, + panel.host, + accountOwner = accountManager.getOwners().first { it.id == panel.host.ownerId }) dialog.setLocationRelativeTo(window) dialog.title = panel.host.name dialog.isVisible = true diff --git a/src/main/kotlin/app/termora/tree/NewHostTree.kt b/src/main/kotlin/app/termora/tree/NewHostTree.kt index e64fc54..324c0ce 100644 --- a/src/main/kotlin/app/termora/tree/NewHostTree.kt +++ b/src/main/kotlin/app/termora/tree/NewHostTree.kt @@ -2,6 +2,7 @@ package app.termora.tree import app.termora.* import app.termora.Application.ohMyJson +import app.termora.account.AccountManager import app.termora.actions.OpenHostAction import app.termora.database.DatabaseChangedExtension import app.termora.database.DatabaseManager @@ -295,8 +296,11 @@ class NewHostTree : SimpleTree(), Disposable { } } newHost.addActionListener(object : ActionListener { + private val accountManager get() = AccountManager.getInstance() override fun actionPerformed(e: ActionEvent) { - val dialog = NewHostDialogV2(owner) + val dialog = NewHostDialogV2( + owner, + accountOwner = accountManager.getOwners().first { it.id == lastHost.ownerId }) dialog.setLocationRelativeTo(owner) dialog.isVisible = true val host = (dialog.host ?: return).copy( @@ -311,8 +315,12 @@ class NewHostTree : SimpleTree(), Disposable { } }) property.addActionListener(object : ActionListener { + private val accountManager get() = AccountManager.getInstance() override fun actionPerformed(e: ActionEvent) { - val dialog = NewHostDialogV2(owner, lastHost) + val dialog = NewHostDialogV2( + owner, + lastHost, + accountOwner = accountManager.getOwners().first { it.id == lastHost.ownerId }) dialog.setLocationRelativeTo(owner) dialog.title = lastHost.name dialog.isVisible = true @@ -639,12 +647,12 @@ class NewHostTree : SimpleTree(), Disposable { ownerType = folder.host.ownerType, ownerId = folder.host.ownerId, ), - DatabaseChangedExtension.Source.Sync + DatabaseChangedExtension.Source.User ) for (host in node.getAllChildren().map { it.host }) { hostManager.addHost( host.copy(ownerType = folder.host.ownerType, ownerId = folder.host.ownerId), - DatabaseChangedExtension.Source.Sync + DatabaseChangedExtension.Source.User ) } } diff --git a/src/main/kotlin/app/termora/tree/NewHostTreeModel.kt b/src/main/kotlin/app/termora/tree/NewHostTreeModel.kt index ee3cfed..f67c3d4 100644 --- a/src/main/kotlin/app/termora/tree/NewHostTreeModel.kt +++ b/src/main/kotlin/app/termora/tree/NewHostTreeModel.kt @@ -148,6 +148,10 @@ class NewHostTreeModel private constructor() : SimpleTreeModel( } hostManager.removeHost(node.id) } + removeNodeFromParent0(node) + } + + private fun removeNodeFromParent0(node: MutableTreeNode?) { super.removeNodeFromParent(node) } @@ -232,7 +236,13 @@ class NewHostTreeModel private constructor() : SimpleTreeModel( private inner class MyAccountAccountExtension : AccountExtension { override fun onAccountChanged(oldAccount: Account, newAccount: Account) { - if (oldAccount.id != newAccount.id) reload(getRoot()) + if (oldAccount.id != newAccount.id) { + reload(getRoot()) + } else if (oldAccount.teams != newAccount.teams) { + val nodes = getRoot().children().toList().filterIsInstance() + nodes.forEach { removeNodeFromParent0(it) } + reload(getRoot()) + } } }