Compare commits
5 Commits
198732239a
...
17d52e1e45
| Author | SHA1 | Date | |
|---|---|---|---|
| 17d52e1e45 | |||
| efd1b48df0 | |||
| a1ed86ff88 | |||
| e160b01bb0 | |||
| baa03cb63e |
@@ -384,6 +384,9 @@ type ui struct {
|
|||||||
disableAPIToken widget.Clickable
|
disableAPIToken widget.Clickable
|
||||||
revokeAPIToken widget.Clickable
|
revokeAPIToken widget.Clickable
|
||||||
deleteAPIToken widget.Clickable
|
deleteAPIToken widget.Clickable
|
||||||
|
useCurrentGroupForPolicy widget.Clickable
|
||||||
|
useSelectedEntryForPolicy widget.Clickable
|
||||||
|
clearAPIPolicyTarget widget.Clickable
|
||||||
addAPIPolicyRule widget.Clickable
|
addAPIPolicyRule widget.Clickable
|
||||||
phoneSplit widget.Float
|
phoneSplit widget.Float
|
||||||
splitDrag gesture.Drag
|
splitDrag gesture.Drag
|
||||||
@@ -396,6 +399,7 @@ type ui struct {
|
|||||||
copyIcon *widget.Icon
|
copyIcon *widget.Icon
|
||||||
expandMoreIcon *widget.Icon
|
expandMoreIcon *widget.Icon
|
||||||
expandLessIcon *widget.Icon
|
expandLessIcon *widget.Icon
|
||||||
|
chevronRightIcon *widget.Icon
|
||||||
chevronDownIcon *widget.Icon
|
chevronDownIcon *widget.Icon
|
||||||
settingsIcon *widget.Icon
|
settingsIcon *widget.Icon
|
||||||
menuIcon *widget.Icon
|
menuIcon *widget.Icon
|
||||||
@@ -615,6 +619,7 @@ func newUIWithState(mode string, sess appstate.CurrentSession, paths statePaths)
|
|||||||
u.copyIcon, _ = widget.NewIcon(icons.ContentContentCopy)
|
u.copyIcon, _ = widget.NewIcon(icons.ContentContentCopy)
|
||||||
u.expandMoreIcon, _ = widget.NewIcon(icons.NavigationExpandMore)
|
u.expandMoreIcon, _ = widget.NewIcon(icons.NavigationExpandMore)
|
||||||
u.expandLessIcon, _ = widget.NewIcon(icons.NavigationExpandLess)
|
u.expandLessIcon, _ = widget.NewIcon(icons.NavigationExpandLess)
|
||||||
|
u.chevronRightIcon, _ = widget.NewIcon(icons.NavigationChevronRight)
|
||||||
u.chevronDownIcon, _ = widget.NewIcon(icons.NavigationArrowDropDown)
|
u.chevronDownIcon, _ = widget.NewIcon(icons.NavigationArrowDropDown)
|
||||||
u.settingsIcon, _ = widget.NewIcon(icons.ActionSettings)
|
u.settingsIcon, _ = widget.NewIcon(icons.ActionSettings)
|
||||||
u.menuIcon, _ = widget.NewIcon(icons.NavigationMenu)
|
u.menuIcon, _ = widget.NewIcon(icons.NavigationMenu)
|
||||||
@@ -645,13 +650,21 @@ func (u *ui) filter() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
u.visible = visible
|
u.visible = visible
|
||||||
if len(u.entryClicks) < len(u.visible) {
|
u.ensureEntryClicks()
|
||||||
u.entryClicks = make([]widget.Clickable, len(u.visible))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) ensureEntryClicks() {
|
||||||
|
if len(u.entryClicks) >= len(u.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := make([]widget.Clickable, len(u.visible))
|
||||||
|
copy(next, u.entryClicks)
|
||||||
|
u.entryClicks = next
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) visibleEntrySnapshot() ([]entry, []*widget.Clickable) {
|
func (u *ui) visibleEntrySnapshot() ([]entry, []*widget.Clickable) {
|
||||||
visible := append([]entry(nil), u.visible...)
|
visible := append([]entry(nil), u.visible...)
|
||||||
|
u.ensureEntryClicks()
|
||||||
clicks := make([]*widget.Clickable, len(visible))
|
clicks := make([]*widget.Clickable, len(visible))
|
||||||
for i := range visible {
|
for i := range visible {
|
||||||
clicks[i] = &u.entryClicks[i]
|
clicks[i] = &u.entryClicks[i]
|
||||||
@@ -659,6 +672,19 @@ func (u *ui) visibleEntrySnapshot() ([]entry, []*widget.Clickable) {
|
|||||||
return visible, clicks
|
return visible, clicks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) searchPlaceholder() string {
|
||||||
|
switch u.state.Section {
|
||||||
|
case appstate.SectionAPITokens:
|
||||||
|
return "Search API tokens"
|
||||||
|
case appstate.SectionAPIAudit:
|
||||||
|
return "Search audit log"
|
||||||
|
case appstate.SectionRecycleBin:
|
||||||
|
return "Search recycle bin"
|
||||||
|
default:
|
||||||
|
return "Search vault"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func defaultStatePaths(stateDir string) statePaths {
|
func defaultStatePaths(stateDir string) statePaths {
|
||||||
baseDir := strings.TrimSpace(stateDir)
|
baseDir := strings.TrimSpace(stateDir)
|
||||||
if baseDir == "" {
|
if baseDir == "" {
|
||||||
@@ -2582,7 +2608,7 @@ func (u *ui) listEmptyState() emptyState {
|
|||||||
case appstate.SectionAPITokens:
|
case appstate.SectionAPITokens:
|
||||||
return emptyState{
|
return emptyState{
|
||||||
Title: "No matching API tokens",
|
Title: "No matching API tokens",
|
||||||
Body: fmt.Sprintf("No API tokens match %q. Clear or refine Search vault to find a token by name, client, or expiration.", query),
|
Body: fmt.Sprintf("No API tokens match %q. Clear or refine Search API tokens to find a token by name, client, or expiration.", query),
|
||||||
}
|
}
|
||||||
case appstate.SectionAPIAudit:
|
case appstate.SectionAPIAudit:
|
||||||
return emptyState{
|
return emptyState{
|
||||||
@@ -2654,7 +2680,7 @@ func (u *ui) detailPlaceholderMessage() string {
|
|||||||
case appstate.SectionAPITokens:
|
case appstate.SectionAPITokens:
|
||||||
return "Select an API token, issue a new one, or search to narrow the list."
|
return "Select an API token, issue a new one, or search to narrow the list."
|
||||||
case appstate.SectionAPIAudit:
|
case appstate.SectionAPIAudit:
|
||||||
return "Select an audit event to inspect it, or use Search vault or the quick filters above."
|
return "Select an audit event to inspect it, or use Search audit log or the quick filters above."
|
||||||
case appstate.SectionTemplates:
|
case appstate.SectionTemplates:
|
||||||
return "Select a template or start a reusable entry."
|
return "Select a template or start a reusable entry."
|
||||||
case appstate.SectionRecycleBin:
|
case appstate.SectionRecycleBin:
|
||||||
@@ -2986,6 +3012,15 @@ func (u *ui) layout(gtx layout.Context) layout.Dimensions {
|
|||||||
for u.addAPIPolicyRule.Clicked(gtx) {
|
for u.addAPIPolicyRule.Clicked(gtx) {
|
||||||
u.runAction("add API policy rule", u.addAPIPolicyRuleAction)
|
u.runAction("add API policy rule", u.addAPIPolicyRuleAction)
|
||||||
}
|
}
|
||||||
|
for u.useCurrentGroupForPolicy.Clicked(gtx) {
|
||||||
|
u.runAction("use current group for API policy", u.useCurrentGroupForPolicyAction)
|
||||||
|
}
|
||||||
|
for u.useSelectedEntryForPolicy.Clicked(gtx) {
|
||||||
|
u.runAction("use selected entry for API policy", u.useSelectedEntryForPolicyAction)
|
||||||
|
}
|
||||||
|
for u.clearAPIPolicyTarget.Clicked(gtx) {
|
||||||
|
u.runAction("clear API policy target", u.clearAPIPolicyTargetAction)
|
||||||
|
}
|
||||||
for i := range u.apiPolicyRemoves {
|
for i := range u.apiPolicyRemoves {
|
||||||
for u.apiPolicyRemoves[i].Clicked(gtx) {
|
for u.apiPolicyRemoves[i].Clicked(gtx) {
|
||||||
index := i
|
index := i
|
||||||
@@ -4159,7 +4194,7 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
|
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
|
||||||
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||||
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions {
|
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions {
|
||||||
editor := material.Editor(u.theme, &u.search, "Search vault")
|
editor := material.Editor(u.theme, &u.search, u.searchPlaceholder())
|
||||||
editor.Color = u.theme.Palette.Fg
|
editor.Color = u.theme.Palette.Fg
|
||||||
editor.HintColor = mutedColor
|
editor.HintColor = mutedColor
|
||||||
return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout)
|
return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout)
|
||||||
@@ -4176,12 +4211,6 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !u.isVaultLocked() && u.state.Section == appstate.SectionRecycleBin {
|
|
||||||
rows = append(rows, u.recycleBinSectionNotice)
|
|
||||||
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
|
|
||||||
return layout.Spacer{Height: spacing}.Layout(gtx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if !u.isVaultLocked() && (u.state.Section == appstate.SectionEntries || u.state.Section == appstate.SectionRecycleBin) {
|
if !u.isVaultLocked() && (u.state.Section == appstate.SectionEntries || u.state.Section == appstate.SectionRecycleBin) {
|
||||||
rows = append(rows, u.pathBar)
|
rows = append(rows, u.pathBar)
|
||||||
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
|
rows = append(rows, func(gtx layout.Context) layout.Dimensions {
|
||||||
@@ -4245,18 +4274,6 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
return u.navigationHeader(gtx)
|
return u.navigationHeader(gtx)
|
||||||
}),
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
|
layout.Rigid(layout.Spacer{Height: spacing}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
||||||
if u.isVaultLocked() || u.state.Section != appstate.SectionRecycleBin {
|
|
||||||
return layout.Dimensions{}
|
|
||||||
}
|
|
||||||
return u.recycleBinSectionNotice(gtx)
|
|
||||||
}),
|
|
||||||
layout.Rigid(layout.Spacer{Height: func() unit.Dp {
|
|
||||||
if u.isVaultLocked() || u.state.Section != appstate.SectionRecycleBin {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return spacing
|
|
||||||
}()}.Layout),
|
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
if u.isVaultLocked() || (u.state.Section != appstate.SectionEntries && u.state.Section != appstate.SectionRecycleBin) {
|
if u.isVaultLocked() || (u.state.Section != appstate.SectionEntries && u.state.Section != appstate.SectionRecycleBin) {
|
||||||
return layout.Dimensions{}
|
return layout.Dimensions{}
|
||||||
@@ -4283,7 +4300,7 @@ func (u *ui) listPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||||
}
|
}
|
||||||
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions {
|
return u.outlinedFieldState(gtx, u.isFocused(focusSearch), func(gtx layout.Context) layout.Dimensions {
|
||||||
editor := material.Editor(u.theme, &u.search, "Search vault")
|
editor := material.Editor(u.theme, &u.search, u.searchPlaceholder())
|
||||||
editor.Color = u.theme.Palette.Fg
|
editor.Color = u.theme.Palette.Fg
|
||||||
editor.HintColor = mutedColor
|
editor.HintColor = mutedColor
|
||||||
return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout)
|
return layout.UniformInset(unit.Dp(10)).Layout(gtx, editor.Layout)
|
||||||
@@ -5741,10 +5758,6 @@ func recyclePathCard(gtx layout.Context, th *material.Theme, title, body string)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ui) recycleBinSectionNotice(gtx layout.Context) layout.Dimensions {
|
|
||||||
return recyclePathCard(gtx, u.theme, "Recycle Bin", "Deleted entries are separated from normal browsing so you can review or restore them safely.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func recycleDetailTitle(gtx layout.Context, th *material.Theme, title string) layout.Dimensions {
|
func recycleDetailTitle(gtx layout.Context, th *material.Theme, title string) layout.Dimensions {
|
||||||
return layout.Background{}.Layout(gtx, fill(color.NRGBA{R: 247, G: 239, B: 231, A: 255}), func(gtx layout.Context) layout.Dimensions {
|
return layout.Background{}.Layout(gtx, fill(color.NRGBA{R: 247, G: 239, B: 231, A: 255}), func(gtx layout.Context) layout.Dimensions {
|
||||||
return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
return layout.UniformInset(unit.Dp(10)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
|||||||
+101
-1
@@ -849,6 +849,28 @@ func TestUIVisibleEntrySnapshotIsStableAfterVisibleMutation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUIVisibleEntrySnapshotRegrowsClickableState(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
u := newUIWithModel("phone", vault.Model{
|
||||||
|
Entries: []vault.Entry{
|
||||||
|
{ID: "1", Title: "Alpha", Path: []string{"Joe", "Internet"}},
|
||||||
|
{ID: "2", Title: "Beta", Path: []string{"Joe", "Internet"}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
u.state.NavigateToPath([]string{"Joe", "Internet"})
|
||||||
|
u.filter()
|
||||||
|
u.entryClicks = u.entryClicks[:1]
|
||||||
|
|
||||||
|
visible, clicks := u.visibleEntrySnapshot()
|
||||||
|
if len(visible) != 2 || len(clicks) != 2 {
|
||||||
|
t.Fatalf("snapshot lengths = (%d, %d), want (2, 2)", len(visible), len(clicks))
|
||||||
|
}
|
||||||
|
if clicks[1] == nil {
|
||||||
|
t.Fatal("regrown click pointer = nil, want usable clickable state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUIPhoneBackReturnsFromSubscreenToEntries(t *testing.T) {
|
func TestUIPhoneBackReturnsFromSubscreenToEntries(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -865,6 +887,20 @@ func TestUIPhoneBackReturnsFromSubscreenToEntries(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUIPhoneBackClosesSettingsDialog(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
u := newUIWithModel("phone", vault.Model{})
|
||||||
|
u.securityDialogOpen = true
|
||||||
|
|
||||||
|
if !u.handlePhoneBack() {
|
||||||
|
t.Fatal("handlePhoneBack() = false, want true for open settings dialog")
|
||||||
|
}
|
||||||
|
if u.securityDialogOpen {
|
||||||
|
t.Fatal("securityDialogOpen = true after back, want false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUISecurityDialogContentDoesNotPanicWithSmallViewport(t *testing.T) {
|
func TestUISecurityDialogContentDoesNotPanicWithSmallViewport(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -951,7 +987,7 @@ func TestUIAPIAuditMessagesGuideQuickFilters(t *testing.T) {
|
|||||||
if got := u.listEmptyState(); got.Title != "No API audit events yet" || got.Body != "Connect a trusted client, respond to approval prompts, or issue a token to start recording activity." {
|
if got := u.listEmptyState(); got.Title != "No API audit events yet" || got.Body != "Connect a trusted client, respond to approval prompts, or issue a token to start recording activity." {
|
||||||
t.Fatalf("listEmptyState() = %#v, want updated API audit guidance", got)
|
t.Fatalf("listEmptyState() = %#v, want updated API audit guidance", got)
|
||||||
}
|
}
|
||||||
if got := u.detailPlaceholderMessage(); got != "Select an audit event to inspect it, or use Search vault or the quick filters above." {
|
if got := u.detailPlaceholderMessage(); got != "Select an audit event to inspect it, or use Search audit log or the quick filters above." {
|
||||||
t.Fatalf("detailPlaceholderMessage() = %q, want quick-filter guidance", got)
|
t.Fatalf("detailPlaceholderMessage() = %q, want quick-filter guidance", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5195,6 +5231,70 @@ func TestUIListEmptyStateProvidesSectionSpecificGuidance(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUISearchPlaceholderIsContextual(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
u := newUIWithModel("desktop", vault.Model{})
|
||||||
|
if got := u.searchPlaceholder(); got != "Search vault" {
|
||||||
|
t.Fatalf("default searchPlaceholder() = %q, want %q", got, "Search vault")
|
||||||
|
}
|
||||||
|
u.showRecycleBinSection()
|
||||||
|
if got := u.searchPlaceholder(); got != "Search recycle bin" {
|
||||||
|
t.Fatalf("recycle searchPlaceholder() = %q, want %q", got, "Search recycle bin")
|
||||||
|
}
|
||||||
|
u.showAPITokensSection()
|
||||||
|
if got := u.searchPlaceholder(); got != "Search API tokens" {
|
||||||
|
t.Fatalf("api token searchPlaceholder() = %q, want %q", got, "Search API tokens")
|
||||||
|
}
|
||||||
|
u.showAPIAuditSection()
|
||||||
|
if got := u.searchPlaceholder(); got != "Search audit log" {
|
||||||
|
t.Fatalf("api audit searchPlaceholder() = %q, want %q", got, "Search audit log")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUIAPIPolicyTargetActionsUseCurrentContext(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
u := newUIWithModel("desktop", vault.Model{
|
||||||
|
Entries: []vault.Entry{
|
||||||
|
{ID: "lights", Title: "Home Assistant", Path: []string{"Joe", "codex"}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
u.state.NavigateToPath([]string{"Joe", "codex"})
|
||||||
|
u.filter()
|
||||||
|
u.state.SelectedEntryID = "lights"
|
||||||
|
|
||||||
|
if err := u.useCurrentGroupForPolicyAction(); err != nil {
|
||||||
|
t.Fatalf("useCurrentGroupForPolicyAction() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := u.apiPolicyPath.Text(); got != "codex" {
|
||||||
|
t.Fatalf("apiPolicyPath.Text() = %q, want %q", got, "codex")
|
||||||
|
}
|
||||||
|
if !u.apiPolicyGroupScopeW.Value {
|
||||||
|
t.Fatal("apiPolicyGroupScopeW.Value = false, want true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.useSelectedEntryForPolicyAction(); err != nil {
|
||||||
|
t.Fatalf("useSelectedEntryForPolicyAction() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := u.apiPolicyEntryID.Text(); got != "lights" {
|
||||||
|
t.Fatalf("apiPolicyEntryID.Text() = %q, want %q", got, "lights")
|
||||||
|
}
|
||||||
|
if u.apiPolicyGroupScopeW.Value {
|
||||||
|
t.Fatal("apiPolicyGroupScopeW.Value = true, want false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.clearAPIPolicyTargetAction(); err != nil {
|
||||||
|
t.Fatalf("clearAPIPolicyTargetAction() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := u.apiPolicyPath.Text(); got != "" {
|
||||||
|
t.Fatalf("apiPolicyPath.Text() = %q, want empty", got)
|
||||||
|
}
|
||||||
|
if got := u.apiPolicyEntryID.Text(); got != "" {
|
||||||
|
t.Fatalf("apiPolicyEntryID.Text() = %q, want empty", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUIVisibleBreadcrumbsCompressesAggressivelyOnPhone(t *testing.T) {
|
func TestUIVisibleBreadcrumbsCompressesAggressivelyOnPhone(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -471,7 +471,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
|||||||
<uses-sdk android:minSdkVersion="{{.MinSDK}}" android:targetSdkVersion="{{.TargetSDK}}" />
|
<uses-sdk android:minSdkVersion="{{.MinSDK}}" android:targetSdkVersion="{{.TargetSDK}}" />
|
||||||
{{range .Permissions}} <uses-permission android:name="{{.}}"/>
|
{{range .Permissions}} <uses-permission android:name="{{.}}"/>
|
||||||
{{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/>
|
{{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/>
|
||||||
{{end}}{{.ManifestSnip}} <application {{.IconSnip}} android:label="{{.AppName}}">
|
{{end}}{{.ManifestSnip}} <application {{.IconSnip}} android:label="{{.AppName}}" android:enableOnBackInvokedCallback="false">
|
||||||
{{.AppSnip}}
|
{{.AppSnip}}
|
||||||
<activity android:name="org.gioui.GioActivity"
|
<activity android:name="org.gioui.GioActivity"
|
||||||
android:label="{{.AppName}}"
|
android:label="{{.AppName}}"
|
||||||
|
|||||||
@@ -369,6 +369,57 @@ func (u *ui) addAPIPolicyRuleAction() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *ui) apiPolicyGroupPathSummary() string {
|
||||||
|
path := parsePath(u.apiPolicyPath.Text())
|
||||||
|
if len(path) == 0 {
|
||||||
|
return "No group selected"
|
||||||
|
}
|
||||||
|
return strings.Join(path, " / ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) apiPolicyEntrySummary() string {
|
||||||
|
id := strings.TrimSpace(u.apiPolicyEntryID.Text())
|
||||||
|
if id == "" {
|
||||||
|
return "No entry selected"
|
||||||
|
}
|
||||||
|
if item, ok := u.selectedEntry(); ok && item.ID == id {
|
||||||
|
if strings.TrimSpace(item.Title) != "" {
|
||||||
|
return item.Title + " (" + id + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) useCurrentGroupForPolicyAction() error {
|
||||||
|
u.syncCurrentPath()
|
||||||
|
path := u.displayPath()
|
||||||
|
if len(path) == 0 {
|
||||||
|
return fmt.Errorf("navigate to a group first")
|
||||||
|
}
|
||||||
|
u.apiPolicyGroupScope = true
|
||||||
|
u.apiPolicyGroupScopeW.Value = true
|
||||||
|
u.apiPolicyPath.SetText(strings.Join(path, " / "))
|
||||||
|
u.apiPolicyEntryID.SetText("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) useSelectedEntryForPolicyAction() error {
|
||||||
|
item, ok := u.selectedEntry()
|
||||||
|
if !ok || strings.TrimSpace(item.ID) == "" {
|
||||||
|
return fmt.Errorf("select an entry first")
|
||||||
|
}
|
||||||
|
u.apiPolicyGroupScope = false
|
||||||
|
u.apiPolicyGroupScopeW.Value = false
|
||||||
|
u.apiPolicyEntryID.SetText(item.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *ui) clearAPIPolicyTargetAction() error {
|
||||||
|
u.apiPolicyPath.SetText("")
|
||||||
|
u.apiPolicyEntryID.SetText("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *ui) removeAPIPolicyRuleAction(index int) error {
|
func (u *ui) removeAPIPolicyRuleAction(index int) error {
|
||||||
token, ok := u.selectedAPIToken()
|
token, ok := u.selectedAPIToken()
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -1002,9 +1053,44 @@ func (u *ui) apiTokenDetailPanel(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "Operation", "Valid operations: "+strings.Join(stringOps(apiOperations()), ", "), &u.apiPolicyOperation, false)),
|
layout.Rigid(labeledEditorHelp(u.theme, "Operation", "Valid operations: "+strings.Join(stringOps(apiOperations()), ", "), &u.apiPolicyOperation, false)),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "Group Path", "Used when group scope is enabled.", &u.apiPolicyPath, false)),
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
if u.apiPolicyGroupScopeW.Value {
|
||||||
layout.Rigid(labeledEditorHelp(u.theme, "Entry ID", "Used when group scope is disabled.", &u.apiPolicyEntryID, false)),
|
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(detailLine(u.theme, "Group Path", u.apiPolicyGroupPathSummary())),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.useCurrentGroupForPolicy, "Use Current Group")
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.clearAPIPolicyTarget, "Clear")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return compactCard(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(detailLine(u.theme, "Entry", u.apiPolicyEntrySummary())),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.useSelectedEntryForPolicy, "Use Selected Entry")
|
||||||
|
}),
|
||||||
|
layout.Rigid(layout.Spacer{Width: unit.Dp(6)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return tonedButton(gtx, u.theme, &u.clearAPIPolicyTarget, "Clear")
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}),
|
||||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
return tonedButton(gtx, u.theme, &u.addAPIPolicyRule, "Add Rule")
|
return tonedButton(gtx, u.theme, &u.addAPIPolicyRule, "Add Rule")
|
||||||
|
|||||||
+1
-1
@@ -909,7 +909,7 @@ func (u *ui) groupControlsDisclosure(gtx layout.Context) layout.Dimensions {
|
|||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
icon := u.expandLessIcon
|
icon := u.expandLessIcon
|
||||||
if u.groupControlsHidden {
|
if u.groupControlsHidden {
|
||||||
icon = u.expandMoreIcon
|
icon = u.chevronRightIcon
|
||||||
}
|
}
|
||||||
if icon == nil {
|
if icon == nil {
|
||||||
lbl := material.Label(u.theme, unit.Sp(16), ">")
|
lbl := material.Label(u.theme, unit.Sp(16), ">")
|
||||||
|
|||||||
Reference in New Issue
Block a user