Skip to content

Commit a090461

Browse files
committed
feat: add web auth settings and redirect/retry buttons to ConnectionModal and operations component
1 parent 1121c4a commit a090461

File tree

2 files changed

+123
-2
lines changed

2 files changed

+123
-2
lines changed

app/components/Header/ConnectorModal.vue

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,70 @@
11
<script setup lang="ts">
2-
const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } =
3-
useConnector()
2+
const {
3+
isConnected,
4+
isConnecting,
5+
npmUser,
6+
error,
7+
hasOperations,
8+
operations,
9+
connect,
10+
disconnect,
11+
refreshState,
12+
} = useConnector()
13+
14+
const { settings } = useSettings()
15+
16+
const authUrl = computed(() => {
17+
const op = operations.value.find(o => o.status === 'running' && o.authUrl)
18+
return op?.authUrl ?? null
19+
})
20+
21+
const AUTH_POLL_INTERVAL = 20_000
22+
const AUTH_POLL_COUNT = 3
23+
let authPollTimer: ReturnType<typeof setInterval> | null = null
24+
25+
function startAuthPolling() {
26+
stopAuthPolling()
27+
let remaining = AUTH_POLL_COUNT
28+
authPollTimer = setInterval(async () => {
29+
await refreshState()
30+
remaining--
31+
if (remaining <= 0) {
32+
stopAuthPolling()
33+
}
34+
}, AUTH_POLL_INTERVAL)
35+
}
36+
37+
function stopAuthPolling() {
38+
if (authPollTimer) {
39+
clearInterval(authPollTimer)
40+
authPollTimer = null
41+
}
42+
}
43+
44+
onUnmounted(stopAuthPolling)
45+
46+
function handleOpenAuthUrl() {
47+
if (authUrl.value) {
48+
window.open(authUrl.value, '_blank', 'noopener,noreferrer')
49+
startAuthPolling()
50+
}
51+
}
452
553
const tokenInput = shallowRef('')
654
const portInput = shallowRef('31415')
755
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
856
957
const hasAttemptedConnect = shallowRef(false)
1058
59+
watch(
60+
() => settings.value.connector.webAuth,
61+
webAuth => {
62+
if (!webAuth) {
63+
settings.value.connector.autoOpenURL = false
64+
}
65+
},
66+
)
67+
1168
watch(isConnected, connected => {
1269
if (!connected) {
1370
tokenInput.value = ''
@@ -61,13 +118,40 @@ function handleDisconnect() {
61118
</div>
62119
</div>
63120

121+
<!-- Connector preferences -->
122+
<div class="flex flex-col gap-2">
123+
<SettingsToggle
124+
:label="$t('connector.modal.web_auth')"
125+
v-model="settings.connector.webAuth"
126+
/>
127+
<SettingsToggle
128+
:label="$t('connector.modal.auto_open_url')"
129+
v-model="settings.connector.autoOpenURL"
130+
:class="!settings.connector.webAuth ? 'opacity-50 pointer-events-none' : ''"
131+
:aria-disabled="!settings.connector.webAuth"
132+
/>
133+
</div>
134+
135+
<div class="border-t border-border my-3" />
136+
64137
<!-- Operations Queue -->
65138
<OrgOperationsQueue />
66139

67140
<div v-if="!hasOperations" class="text-sm text-fg-muted">
68141
{{ $t('connector.modal.connected_hint') }}
69142
</div>
70143

144+
<!-- Web auth link -->
145+
<button
146+
v-if="authUrl"
147+
type="button"
148+
class="flex items-center justify-center gap-2 w-full px-4 py-2 font-mono text-sm text-accent bg-accent/10 border border-accent/30 rounded-md transition-colors duration-200 hover:bg-accent/20 focus-visible:outline-accent/70"
149+
@click="handleOpenAuthUrl"
150+
>
151+
<span class="i-carbon:launch w-4 h-4" aria-hidden="true" />
152+
{{ $t('operations.queue.open_web_auth') }}
153+
</button>
154+
71155
<button
72156
type="button"
73157
class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-accent/70"
@@ -194,6 +278,20 @@ function handleDisconnect() {
194278
class="w-full"
195279
size="medium"
196280
/>
281+
282+
<div class="border-t border-border my-3" />
283+
<div class="flex flex-col gap-2">
284+
<SettingsToggle
285+
:label="$t('connector.modal.web_auth')"
286+
v-model="settings.connector.webAuth"
287+
/>
288+
<SettingsToggle
289+
:label="$t('connector.modal.auto_open_url')"
290+
v-model="settings.connector.autoOpenURL"
291+
:class="!settings.connector.webAuth ? 'opacity-50 pointer-events-none' : ''"
292+
:aria-disabled="!settings.connector.webAuth"
293+
/>
294+
</div>
197295
</div>
198296
</details>
199297
</div>

app/components/Org/OperationsQueue.vue

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const {
2121
refreshState,
2222
} = useConnector()
2323
24+
const { settings } = useSettings()
25+
2426
const isExecuting = shallowRef(false)
2527
const otpInput = shallowRef('')
2628
@@ -63,6 +65,18 @@ async function handleRetryWithOtp() {
6365
await handleExecute(otp)
6466
}
6567
68+
/** Retry all OTP-failed operations using web auth (no OTP needed) */
69+
async function handleRetryWithWebAuth() {
70+
const otpFailedOps = activeOperations.value.filter(
71+
(op: PendingOperation) => op.status === 'failed' && op.result?.requiresOtp,
72+
)
73+
for (const op of otpFailedOps) {
74+
await retryOperation(op.id)
75+
}
76+
77+
await handleExecute()
78+
}
79+
6680
async function handleClearAll() {
6781
await clearOperations()
6882
otpInput.value = ''
@@ -263,6 +277,15 @@ watch(isExecuting, executing => {
263277
{{ isExecuting ? $t('operations.queue.retrying') : $t('operations.queue.retry_otp') }}
264278
</button>
265279
</form>
280+
<button
281+
v-if="settings.connector.webAuth"
282+
type="button"
283+
:disabled="isExecuting"
284+
class="w-full mt-2 px-3 py-2 font-mono text-xs text-fg bg-bg-subtle border border-border rounded transition-all duration-200 hover:text-fg hover:border-border-hover disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-accent/70"
285+
@click="handleRetryWithWebAuth"
286+
>
287+
{{ isExecuting ? $t('operations.queue.retrying') : $t('operations.queue.retry_web_auth') }}
288+
</button>
266289
</div>
267290

268291
<!-- Action buttons -->

0 commit comments

Comments
 (0)