feat: Add guest mode support and permission controls

This commit is contained in:
hstyi
2026-02-24 17:43:55 +08:00
committed by GitHub
parent c0f3ea8556
commit 5375a2e42e
23 changed files with 234 additions and 110 deletions

View File

@@ -1,8 +1,10 @@
package app.termora package app.termora
import app.termora.account.AccountOwner import app.termora.account.AccountOwner
import app.termora.account.TeamRole
import app.termora.actions.AnAction import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.database.OwnerType
import app.termora.protocol.* import app.termora.protocol.*
import app.termora.transfer.ScaleIcon import app.termora.transfer.ScaleIcon
import com.formdev.flatlaf.extras.components.FlatToolBar import com.formdev.flatlaf.extras.components.FlatToolBar
@@ -13,9 +15,11 @@ import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.apache.commons.lang3.StringUtils import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.exception.ExceptionUtils import org.apache.commons.lang3.exception.ExceptionUtils
import org.jdesktop.swingx.SwingXUtilities
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.CardLayout import java.awt.CardLayout
import java.awt.Dimension import java.awt.Dimension
import java.awt.Graphics
import java.awt.Window import java.awt.Window
import javax.swing.* import javax.swing.*
@@ -144,10 +148,33 @@ class NewHostDialogV2(
val provider = extension.getProtocolProvider() val provider = extension.getProtocolProvider()
testConnectionBtn.isVisible = provider is ProtocolTester testConnectionBtn.isVisible = provider is ProtocolTester
preventImportantData()
}
private fun preventImportantData() {
if (visitorMode()) {
for (component in SwingUtils.getDescendantsOfType(JComponent::class.java, cardPanel)) {
if (component is OutlinePasswordField) {
component.styleMap = component.styleMap.toMutableMap().apply {
put("showRevealButton", false)
}
}
}
}
}
private fun visitorMode(): Boolean {
return accountOwner.isVisitorMode()
} }
override fun createActions(): List<AbstractAction> { override fun createActions(): List<AbstractAction> {
return listOf(createOkAction(), testConnectionAction, CancelAction()) val actions = mutableListOf<AbstractAction>()
if (visitorMode().not()) {
actions.add(createOkAction())
actions.add(testConnectionAction)
}
actions.add(CancelAction())
return actions
} }
override fun createJButtonForAction(action: Action): JButton { override fun createJButtonForAction(action: Action): JButton {
@@ -238,5 +265,4 @@ class NewHostDialogV2(
super.doOKAction() super.doOKAction()
} }
} }

View File

@@ -40,8 +40,8 @@ object AccountHttp {
throw ResponseException(response.code, response) throw ResponseException(response.code, response)
} }
val text = response.use { response.body.use { it?.string() } } val text = response.use { response.body.use { it.string() } }
if (text.isNullOrBlank()) { if (text.isBlank()) {
throw ResponseException(response.code, "response body is empty", response) throw ResponseException(response.code, "response body is empty", response)
} }

View File

@@ -54,24 +54,24 @@ class AccountManager private constructor() : ApplicationRunnerExtension {
fun getOwnerIds() = account.teams.map { it.id }.toMutableList().apply { add(getAccountId()) }.toSet() fun getOwnerIds() = account.teams.map { it.id }.toMutableList().apply { add(getAccountId()) }.toSet()
fun getOwners(): Set<AccountOwner> { fun getOwners(): Set<AccountOwner> {
val owners = mutableSetOf<AccountOwner>() val owners = mutableSetOf<AccountOwner>()
owners.add(AccountOwner(getAccountId(), getEmail(), OwnerType.User)) owners.add(AccountOwner(getAccountId(), getEmail(), OwnerType.User, StringUtils.EMPTY))
for (team in getTeams()) { for (team in getTeams()) {
owners.add(AccountOwner(team.id, team.name, OwnerType.Team)) owners.add(AccountOwner(team.id, team.name, OwnerType.Team, team.role))
} }
return owners return owners
} }
fun isFreePlan(): Boolean { fun isFreePlan(): Boolean {
return isLocally() || getSubscription().plan == SubscriptionPlan.Free return isLocally() || getSubscription().plan == SubscriptionPlan.Free.name
} }
fun getSubscription(): Subscription { fun getSubscription(): Subscription {
if (isLocally().not()) { if (isLocally().not()) {
val subscriptions = getSubscriptions() val subscriptions = getSubscriptions()
val enterprises = getSubscriptions().filter { it.plan == SubscriptionPlan.Enterprise } val enterprises = getSubscriptions().filter { it.plan == SubscriptionPlan.Enterprise.name }
val teams = subscriptions.filter { it.plan == SubscriptionPlan.Team } val teams = subscriptions.filter { it.plan == SubscriptionPlan.Team.name }
val pros = subscriptions.filter { it.plan == SubscriptionPlan.Pro } val pros = subscriptions.filter { it.plan == SubscriptionPlan.Pro.name }
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
if (enterprises.any { it.endAt > now }) { if (enterprises.any { it.endAt > now }) {
@@ -83,16 +83,9 @@ class AccountManager private constructor() : ApplicationRunnerExtension {
} }
} }
return Subscription(id = "0", plan = SubscriptionPlan.Free, startAt = 0, endAt = 0) return Subscription(id = "0", plan = SubscriptionPlan.Free.name, startAt = 0, endAt = 0)
} }
fun hasTeamFeature(): Boolean {
if (accountProperties.signed.not()) return false
val plan = getSubscription().plan
return SubscriptionPlan.Team == plan || SubscriptionPlan.Enterprise == plan
}
/** /**
* 刷新 Token * 刷新 Token
*/ */

View File

@@ -123,7 +123,7 @@ class AccountOption : JPanel(BorderLayout()), OptionsPane.Option, Disposable {
} }
val planBox = Box.createHorizontalBox() val planBox = Box.createHorizontalBox()
planBox.add(JLabel(if (isLocally) "-" else subscription.plan.name)) planBox.add(JLabel(if (isLocally) "-" else subscription.plan))
if (isFreePlan && isLocally.not()) { if (isFreePlan && isLocally.not()) {
planBox.add(Box.createHorizontalStrut(16)) planBox.add(Box.createHorizontalStrut(16))
val upgrade = JXHyperlink(object : AnAction(I18n.getString("termora.settings.account.upgrade")) { val upgrade = JXHyperlink(object : AnAction(I18n.getString("termora.settings.account.upgrade")) {

View File

@@ -1,5 +1,12 @@
package app.termora.account package app.termora.account
import app.termora.database.OwnerType import app.termora.database.OwnerType
import org.apache.commons.lang3.StringUtils
data class AccountOwner(val id: String, val name: String, val type: OwnerType) data class AccountOwner(val id: String, val name: String, val type: OwnerType, val role: String) {
constructor(id: String, name: String, type: OwnerType) : this(id, name, type, StringUtils.EMPTY)
fun isVisitorMode(): Boolean {
return type == OwnerType.Team && role == TeamRole.Visitor.name
}
}

View File

@@ -159,5 +159,5 @@ class ServerManager private constructor() {
@Serializable @Serializable
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: String, val secretKey: String)
} }

View File

@@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Subscription( data class Subscription(
val id: String, val id: String,
val plan: SubscriptionPlan, val plan: String,
val startAt: Long, val startAt: Long,
val endAt: Long, val endAt: Long,
) )

View File

@@ -25,7 +25,7 @@ class Team(
/** /**
* 所属角色 * 所属角色
*/ */
val role: TeamRole, val role: String,
) { ) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true

View File

@@ -3,4 +3,5 @@ package app.termora.account
enum class TeamRole { enum class TeamRole {
Member, Member,
Owner, Owner,
Visitor,
} }

View File

@@ -409,7 +409,8 @@ class DatabaseManager private constructor() : Disposable {
val accountOwner = AccountOwner( val accountOwner = AccountOwner(
id = account.id, id = account.id,
name = account.email, name = account.email,
type = OwnerType.User type = OwnerType.User,
role = StringUtils.EMPTY,
) )
for (host in hostManager.hosts()) { for (host in hostManager.hosts()) {

View File

@@ -5,6 +5,7 @@ import app.termora.account.AccountManager
import app.termora.account.AccountOwner import app.termora.account.AccountOwner
import app.termora.database.OwnerType import app.termora.database.OwnerType
import com.formdev.flatlaf.extras.components.FlatTabbedPane import com.formdev.flatlaf.extras.components.FlatTabbedPane
import org.apache.commons.lang3.StringUtils
import java.awt.Dimension import java.awt.Dimension
import java.awt.Window import java.awt.Window
import javax.swing.BorderFactory import javax.swing.BorderFactory
@@ -45,12 +46,13 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
AccountOwner( AccountOwner(
accountManager.getAccountId(), accountManager.getAccountId(),
accountManager.getEmail(), accountManager.getEmail(),
OwnerType.User OwnerType.User,
StringUtils.EMPTY,
) )
).apply { Disposer.register(disposable, this) } ).apply { Disposer.register(disposable, this) }
) )
if (accountManager.hasTeamFeature()) { // if (accountManager.hasTeamFeature()) {
for (team in accountManager.getTeams()) { for (team in accountManager.getTeams()) {
tabbed.addTab( tabbed.addTab(
team.name, team.name,
@@ -59,11 +61,12 @@ class KeywordHighlightDialog(owner: Window) : DialogWrapper(owner) {
AccountOwner( AccountOwner(
team.id, team.id,
team.name, team.name,
OwnerType.Team OwnerType.Team,
team.role,
) )
).apply { Disposer.register(disposable, this) }) ).apply { Disposer.register(disposable, this) })
} }
} // }
return tabbed return tabbed

View File

@@ -248,6 +248,9 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
table.addMouseListener(object : MouseAdapter() { table.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { override fun mouseClicked(e: MouseEvent) {
if (accountOwner.isVisitorMode()) {
return
}
if (SwingUtilities.isLeftMouseButton(e)) { if (SwingUtilities.isLeftMouseButton(e)) {
val row = table.rowAtPoint(e.point) val row = table.rowAtPoint(e.point)
val column = table.columnAtPoint(e.point) val column = table.columnAtPoint(e.point)
@@ -299,7 +302,8 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
if (keywordHighlight.backgroundColor in 0..16) { if (keywordHighlight.backgroundColor in 0..16) {
if (keywordHighlight.backgroundColor == 0) { if (keywordHighlight.backgroundColor == 0) {
dialog.backgroundColor.background = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND)) dialog.backgroundColor.background =
Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
dialog.backgroundColor.colorIndex = -1 dialog.backgroundColor.colorIndex = -1
} else { } else {
dialog.backgroundColor.color = dialog.backgroundColor.color =
@@ -402,7 +406,7 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
val panel = JPanel(BorderLayout()) val panel = JPanel(BorderLayout())
panel.add(JScrollPane(table).apply { panel.add(JScrollPane(table).apply {
border = BorderFactory.createCompoundBorder( border = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(8, 8, 8, 0), BorderFactory.createEmptyBorder(8, 8, 8, if (accountOwner.isVisitorMode()) 8 else 0),
BorderFactory.createMatteBorder(1, 1, 1, 1, DynamicColor.BorderColor) BorderFactory.createMatteBorder(1, 1, 1, 1, DynamicColor.BorderColor)
) )
}, BorderLayout.CENTER) }, BorderLayout.CENTER)
@@ -414,6 +418,7 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
"default:grow", "default:grow",
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref" "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
) )
if (accountOwner.isVisitorMode().not()) {
panel.add( panel.add(
FormBuilder.create().layout(layout) FormBuilder.create().layout(layout)
.border(BorderFactory.createEmptyBorder(8, 8, 8, 8)) .border(BorderFactory.createEmptyBorder(8, 8, 8, 8))
@@ -424,7 +429,7 @@ class KeywordHighlightPanel(private val accountOwner: AccountOwner) : JPanel(Bor
.add(exportBtn).xy(1, rows).apply { rows += step } .add(exportBtn).xy(1, rows).apply { rows += step }
.build(), .build(),
BorderLayout.EAST) BorderLayout.EAST)
}
return panel return panel
} }
} }

View File

@@ -8,6 +8,7 @@ import app.termora.account.AccountManager
import app.termora.account.AccountOwner import app.termora.account.AccountOwner
import app.termora.database.OwnerType import app.termora.database.OwnerType
import com.formdev.flatlaf.extras.components.FlatTabbedPane import com.formdev.flatlaf.extras.components.FlatTabbedPane
import org.apache.commons.lang3.StringUtils
import java.awt.Dimension import java.awt.Dimension
import java.awt.Window import java.awt.Window
import javax.swing.BorderFactory import javax.swing.BorderFactory
@@ -65,13 +66,14 @@ class KeyManagerDialog(
AccountOwner( AccountOwner(
accountManager.getAccountId(), accountManager.getAccountId(),
accountManager.getEmail(), accountManager.getEmail(),
OwnerType.User OwnerType.User,
StringUtils.EMPTY,
) )
) )
) )
} }
if (accountOwner != null && accountManager.hasTeamFeature()) { if (accountOwner != null) {
for (team in accountManager.getTeams()) { for (team in accountManager.getTeams()) {
if (team.id == accountOwner.id) { if (team.id == accountOwner.id) {
tabbed.addTab( tabbed.addTab(
@@ -81,7 +83,8 @@ class KeyManagerDialog(
AccountOwner( AccountOwner(
team.id, team.id,
team.name, team.name,
OwnerType.Team OwnerType.Team,
team.role,
) )
) )
) )
@@ -91,8 +94,7 @@ class KeyManagerDialog(
} }
// if (accountManager.hasTeamFeature()) {
if (accountManager.hasTeamFeature()) {
for (team in accountManager.getTeams()) { for (team in accountManager.getTeams()) {
tabbed.addTab( tabbed.addTab(
team.name, team.name,
@@ -101,12 +103,13 @@ class KeyManagerDialog(
AccountOwner( AccountOwner(
team.id, team.id,
team.name, team.name,
OwnerType.Team OwnerType.Team,
team.role,
) )
) )
) )
} }
} // }
return tabbed return tabbed

View File

@@ -2,8 +2,10 @@ package app.termora.keymgr
import app.termora.* import app.termora.*
import app.termora.account.AccountOwner import app.termora.account.AccountOwner
import app.termora.account.TeamRole
import app.termora.actions.AnAction import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.database.OwnerType
import app.termora.plugin.internal.ssh.SSHProtocolProvider import app.termora.plugin.internal.ssh.SSHProtocolProvider
import app.termora.tree.Filter import app.termora.tree.Filter
import app.termora.tree.HostTreeNode import app.termora.tree.HostTreeNode
@@ -56,6 +58,7 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
init { init {
initView() initView()
initEvents() initEvents()
preventImportantData()
} }
@@ -89,6 +92,8 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
add(JScrollPane(keyPairTable).apply { add(JScrollPane(keyPairTable).apply {
border = BorderFactory.createMatteBorder(1, 1, 1, 1, DynamicColor.BorderColor) border = BorderFactory.createMatteBorder(1, 1, 1, 1, DynamicColor.BorderColor)
}, BorderLayout.CENTER) }, BorderLayout.CENTER)
if (accountOwner.type == OwnerType.User || (accountOwner.type == OwnerType.Team && accountOwner.role != TeamRole.Visitor.name)) {
add( add(
FormBuilder.create().layout(layout).padding(EmptyBorder(0, 12, 0, 0)) FormBuilder.create().layout(layout).padding(EmptyBorder(0, 12, 0, 0))
.add(generateBtn).xy(1, rows).apply { rows += step } .add(generateBtn).xy(1, rows).apply { rows += step }
@@ -98,6 +103,8 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
.add(deleteBtn).xy(1, rows).apply { rows += step } .add(deleteBtn).xy(1, rows).apply { rows += step }
.add(sshCopyIdBtn).xy(1, rows).apply { rows += step } .add(sshCopyIdBtn).xy(1, rows).apply { rows += step }
.build(), BorderLayout.EAST) .build(), BorderLayout.EAST)
}
border = BorderFactory.createEmptyBorder(12, 12, 12, 12) border = BorderFactory.createEmptyBorder(12, 12, 12, 12)
} }
@@ -199,6 +206,17 @@ class KeyManagerPanel(private val accountOwner: AccountOwner) : JPanel(BorderLay
} }
} }
private fun preventImportantData() {
if (accountOwner.isVisitorMode()) {
generateBtn.isVisible = false
editBtn.isVisible = false
deleteBtn.isVisible = false
importBtn.isVisible = false
exportBtn.isVisible = false
sshCopyIdBtn.isVisible = false
}
}
private fun sshCopyId(evt: AnActionEvent) { private fun sshCopyId(evt: AnActionEvent) {
val keyPairs = keyPairTable.selectedRows.map { keyPairTableModel.getOhKeyPair(it) } val keyPairs = keyPairTable.selectedRows.map { keyPairTableModel.getOhKeyPair(it) }
val publicKeys = mutableListOf<Pair<String, String>>() val publicKeys = mutableListOf<Pair<String, String>>()

View File

@@ -1,6 +1,7 @@
package app.termora.plugin.internal.rdp package app.termora.plugin.internal.rdp
import app.termora.* import app.termora.*
import app.termora.account.AccountOwner
import app.termora.plugin.internal.BasicProxyOption import app.termora.plugin.internal.BasicProxyOption
import com.formdev.flatlaf.FlatClientProperties import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.extras.components.FlatComboBox import com.formdev.flatlaf.extras.components.FlatComboBox
@@ -17,7 +18,7 @@ import java.awt.event.ComponentEvent
import java.awt.event.ItemEvent import java.awt.event.ItemEvent
import javax.swing.* import javax.swing.*
internal open class RDPHostOptionsPane : OptionsPane() { internal open class RDPHostOptionsPane(private val accountOwner: AccountOwner) : OptionsPane() {
protected val generalOption = GeneralOption() protected val generalOption = GeneralOption()
protected val proxyOption = BasicProxyOption() protected val proxyOption = BasicProxyOption()
protected val owner: Window get() = SwingUtilities.getWindowAncestor(this) protected val owner: Window get() = SwingUtilities.getWindowAncestor(this)

View File

@@ -2,11 +2,12 @@ package app.termora.plugin.internal.rdp
import app.termora.Disposer import app.termora.Disposer
import app.termora.Host import app.termora.Host
import app.termora.account.AccountOwner
import app.termora.protocol.ProtocolHostPanel import app.termora.protocol.ProtocolHostPanel
import java.awt.BorderLayout import java.awt.BorderLayout
class RDPProtocolHostPanel : ProtocolHostPanel() { class RDPProtocolHostPanel(private val accountOwner: AccountOwner) : ProtocolHostPanel() {
private val pane = RDPHostOptionsPane() private val pane = RDPHostOptionsPane(accountOwner)
init { init {
initView() initView()

View File

@@ -16,7 +16,7 @@ internal class RDPProtocolHostPanelExtension private constructor() : ProtocolHos
} }
override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel { override fun createProtocolHostPanel(accountOwner: AccountOwner): ProtocolHostPanel {
return RDPProtocolHostPanel() return RDPProtocolHostPanel(accountOwner)
} }
override fun ordered(): Long { override fun ordered(): Long {

View File

@@ -46,12 +46,12 @@ class TagDialog(owner: Window, private val accountOwnerId: String = StringUtils.
AccountOwner( AccountOwner(
accountManager.getAccountId(), accountManager.getAccountId(),
accountManager.getEmail(), accountManager.getEmail(),
OwnerType.User OwnerType.User,
StringUtils.EMPTY,
) )
).apply { Disposer.register(disposable, this) } ).apply { Disposer.register(disposable, this) }
) )
if (accountManager.hasTeamFeature()) {
for (team in accountManager.getTeams()) { for (team in accountManager.getTeams()) {
tabbed.addTab( tabbed.addTab(
team.name, team.name,
@@ -60,7 +60,8 @@ class TagDialog(owner: Window, private val accountOwnerId: String = StringUtils.
AccountOwner( AccountOwner(
team.id, team.id,
team.name, team.name,
OwnerType.Team OwnerType.Team,
team.role,
) )
).apply { Disposer.register(disposable, this) }) ).apply { Disposer.register(disposable, this) })
@@ -68,7 +69,6 @@ class TagDialog(owner: Window, private val accountOwnerId: String = StringUtils.
tabbed.selectedIndex = tabbed.tabCount - 1 tabbed.selectedIndex = tabbed.tabCount - 1
} }
} }
}
return tabbed return tabbed
} }

View File

@@ -2,6 +2,8 @@ package app.termora.tag
import app.termora.* import app.termora.*
import app.termora.account.AccountOwner import app.termora.account.AccountOwner
import app.termora.account.TeamRole
import app.termora.database.OwnerType
import com.jgoodies.forms.builder.FormBuilder import com.jgoodies.forms.builder.FormBuilder
import com.jgoodies.forms.layout.FormLayout import com.jgoodies.forms.layout.FormLayout
import java.awt.BorderLayout import java.awt.BorderLayout
@@ -9,7 +11,7 @@ import java.awt.Component
import javax.swing.* import javax.swing.*
import javax.swing.border.EmptyBorder import javax.swing.border.EmptyBorder
class TagPanel(accountOwner: AccountOwner) : JPanel(BorderLayout()), Disposable { class TagPanel(private val accountOwner: AccountOwner) : JPanel(BorderLayout()), Disposable {
private val owner get() = SwingUtilities.getWindowAncestor(this) private val owner get() = SwingUtilities.getWindowAncestor(this)
@@ -23,6 +25,7 @@ class TagPanel(accountOwner: AccountOwner) : JPanel(BorderLayout()), Disposable
init { init {
initView() initView()
initEvents() initEvents()
preventImportantData()
} }
private fun initView() { private fun initView() {
@@ -108,6 +111,14 @@ class TagPanel(accountOwner: AccountOwner) : JPanel(BorderLayout()), Disposable
} }
private fun preventImportantData() {
if (accountOwner.isVisitorMode()) {
addBtn.isVisible = false
editBtn.isVisible = false
deleteBtn.isVisible = false
}
}
private fun createCenterPanel(): JComponent { private fun createCenterPanel(): JComponent {
val panel = JPanel(BorderLayout()) val panel = JPanel(BorderLayout())

View File

@@ -3,6 +3,8 @@ package app.termora.tree
import app.termora.* import app.termora.*
import app.termora.Application.ohMyJson import app.termora.Application.ohMyJson
import app.termora.account.AccountManager import app.termora.account.AccountManager
import app.termora.account.Team
import app.termora.account.TeamRole
import app.termora.actions.AnAction import app.termora.actions.AnAction
import app.termora.actions.AnActionEvent import app.termora.actions.AnActionEvent
import app.termora.actions.OpenHostAction import app.termora.actions.OpenHostAction
@@ -455,6 +457,33 @@ class NewHostTree : SimpleTree(), Disposable {
}) })
popupMenu.addPopupMenuListener(object : PopupMenuListener {
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent?) {
for (node in nodes) {
val team = TeamTreeNode.parentTeam(node) ?: continue
if (team.role == TeamRole.Visitor.name) {
copy.isEnabled = false
remove.isEnabled = false
rename.isEnabled = false
importMenu.isEnabled = false
newMenu.isEnabled = false
tagsMenu.isEnabled = false
break
}
}
}
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) {
}
override fun popupMenuCanceled(e: PopupMenuEvent?) {
}
})
val mnemonics = mapOf( val mnemonics = mapOf(
refresh to KeyEvent.VK_R, refresh to KeyEvent.VK_R,
newMenu to KeyEvent.VK_W, newMenu to KeyEvent.VK_W,

View File

@@ -61,11 +61,11 @@ class NewHostTreeModel private constructor() : SimpleTreeModel<Host>(
// 如果是根,需要引入团队功能 // 如果是根,需要引入团队功能
if (parent == getRoot()) { if (parent == getRoot()) {
if (accountManager.hasTeamFeature()) { // if (accountManager.hasTeamFeature()) {
for (team in accountManager.getTeams()) { for (team in accountManager.getTeams()) {
nodes[team.id] = TeamTreeNode(team) nodes[team.id] = TeamTreeNode(team)
} }
} // }
nodes[accountManager.getAccountId()] = HostTreeNode( nodes[accountManager.getAccountId()] = HostTreeNode(
Host( Host(
@@ -93,11 +93,11 @@ class NewHostTreeModel private constructor() : SimpleTreeModel<Host>(
} }
if (parent == getRoot()) { if (parent == getRoot()) {
if (accountManager.hasTeamFeature()) { // if (accountManager.hasTeamFeature()) {
for (team in accountManager.getTeams()) { for (team in accountManager.getTeams()) {
parent.add(nodes.getValue(team.id)) parent.add(nodes.getValue(team.id))
} }
} // }
parent.add(nodes.getValue(accountManager.getAccountId())) parent.add(nodes.getValue(accountManager.getAccountId()))
} else { } else {
for (node in nodes.values) { for (node in nodes.values) {

View File

@@ -1,6 +1,7 @@
package app.termora.tree package app.termora.tree
import app.termora.OutlineTextField import app.termora.OutlineTextField
import app.termora.account.TeamRole
import com.formdev.flatlaf.ui.FlatTreeUI import com.formdev.flatlaf.ui.FlatTreeUI
import org.jdesktop.swingx.JXTree import org.jdesktop.swingx.JXTree
import java.awt.Dimension import java.awt.Dimension
@@ -134,6 +135,13 @@ open class SimpleTree : JXTree() {
} }
} }
for (node in nodes) {
val team = TeamTreeNode.parentTeam(node) ?: continue
if (team.role == TeamRole.Visitor.name) {
return null
}
}
return MoveNodeTransferable(nodes) return MoveNodeTransferable(nodes)
} }
@@ -153,9 +161,14 @@ open class SimpleTree : JXTree() {
if (nodes.isEmpty()) return false if (nodes.isEmpty()) return false
if (!node.isFolder) return false if (!node.isFolder) return false
val team = TeamTreeNode.parentTeam(node)
if (team?.role == TeamRole.Visitor.name) {
return false
}
for (e in nodes) { for (e in nodes) {
// 禁止拖拽到自己的子下面 // 禁止拖拽到自己的子下面
if (path.equals(TreePath(e.path)) || TreePath(e.path).isDescendant(path)) { if (path == TreePath(e.path) || TreePath(e.path).isDescendant(path)) {
return false return false
} }
@@ -333,7 +346,7 @@ open class SimpleTree : JXTree() {
private inner class MyTreeUI : FlatTreeUI() { private inner class MyTreeUI : FlatTreeUI() {
override fun createNodeDimensions(): AbstractLayoutCache.NodeDimensions? { override fun createNodeDimensions(): AbstractLayoutCache.NodeDimensions {
return object : NodeDimensionsHandler() { return object : NodeDimensionsHandler() {
override fun getNodeDimensions( override fun getNodeDimensions(
value: Any?, row: Int, depth: Int, expanded: Boolean, value: Any?, row: Int, depth: Int, expanded: Boolean,

View File

@@ -26,4 +26,16 @@ class TeamTreeNode(val team: Team) : HostTreeNode(
override fun toString(): String { override fun toString(): String {
return team.name return team.name
} }
companion object {
fun parentTeam(node: SimpleTreeNode<*>?): Team? {
if (node is TeamTreeNode) {
return node.team
} else if (node == null) {
return null
}
return parentTeam(node.parent)
}
}
} }