-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAccessTokenList.vue
More file actions
170 lines (156 loc) · 8.3 KB
/
AccessTokenList.vue
File metadata and controls
170 lines (156 loc) · 8.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
<!-- App list page -->
<template>
<div class="min-h-screen bg-slate-50 flex flex-col font-sans" :id="'vue-content-'+Math.random().toString(36).substring(2)">
<nav-bar />
<main class="max-w-[1600px] w-full mx-auto p-6 flex flex-col lg:flex-row gap-6 flex-1">
<account-sidebar :is-access-token-active="true" />
<section class="flex-1 flex flex-col">
<access-token-delete ref="accessTokenDelete" v-if="isShowDelete"
:token="token"
@closeDelete="closeDelete"
/>
<!-- Table Card -->
<div class="bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden flex-1 flex flex-col min-h-[600px]">
<!-- Header -->
<div class="px-6 py-5 border-b border-slate-100 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 class="text-lg font-medium text-slate-800">Access Tokens</h2>
<p class="text-sm text-slate-500 mt-1">Generate and manage API tokens to authenticate with third-party integrations.</p>
</div>
</div>
<!-- Table Content -->
<div class="w-full overflow-x-auto">
<table class="w-full text-left text-sm text-slate-600">
<thead class="bg-slate-50/50 border-b border-slate-200 text-slate-500 text-xs tracking-wider font-medium">
<tr>
<th class="px-6 py-4">Application</th>
<th class="px-6 py-4">Token</th>
<th class="px-6 py-4">Created</th>
<th class="px-6 py-4">Expires</th>
<th class="px-6 py-4 text-right w-16"></th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
<tr v-for="(token, index) in tokens" :key="index" class="hover:bg-primary-50 transition-colors">
<td class="px-6 py-4 font-medium text-slate-800">{{ token.application }}</td>
<td class="px-6 py-4 font-mono text-xs text-slate-500 break-all">{{ token.token }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ token.created }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ token.expires }}</td>
<td class="px-6 py-4 text-right whitespace-nowrap">
<button @click="showDelete(index)" class="text-slate-400 hover:text-red-600 hover:bg-red-50 p-2 rounded-md transition-colors" title="Delete token">
<Trash2 class="w-4 h-4" />
</button>
</td>
</tr>
<tr v-if="!tokens || tokens.length === 0">
<td colspan="5" class="px-6 py-16">
<div class="flex flex-col items-center justify-center text-center">
<div class="w-16 h-16 bg-slate-50 border border-slate-100 rounded-full flex items-center justify-center mb-4">
<div class="w-10 h-10 border border-slate-200 bg-white rounded-full flex items-center justify-center shadow-sm">
<div class="w-4 h-[2px] bg-slate-300 rounded-full"></div>
</div>
</div>
<h3 class="text-lg font-medium text-slate-800 mb-1">No access tokens</h3>
<p class="text-sm text-slate-500 max-w-sm">You haven't generated any access tokens yet. Create one via Drycc CLI to authenticate API requests.</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- Right Sidebar (Token Info & Help) -->
<aside class="hidden xl:flex flex-col w-[280px] flex-shrink-0 gap-5">
<!-- About Tokens -->
<div class="bg-white rounded border border-slate-200 overflow-hidden">
<div class="px-5 py-3.5 border-b border-slate-100 bg-white">
<h3 class="text-[13px] font-semibold text-slate-700">Security Guidance</h3>
</div>
<div class="p-5">
<p class="text-[12px] text-slate-500 leading-relaxed mb-4">
Treat your tokens like passwords. Never share them or hardcode them directly into your application source code.
</p>
<a href="https://oauth.net/security/" target="_blank" class="text-[12px] font-medium text-primary hover:text-primary-600 flex items-center gap-1 transition-colors">
Learn more about SSO
</a>
</div>
</div>
<!-- Need Help? -->
<div class="bg-white rounded border border-slate-200 overflow-hidden">
<div class="px-5 py-3.5 border-b border-slate-100 bg-white">
<h3 class="text-[13px] font-semibold text-slate-700">Need Help?</h3>
</div>
<div class="p-0">
<ul class="divide-y divide-slate-100">
<li>
<a href="https://www.drycc.cc" target="_blank" rel="noopener noreferrer" class="px-5 py-3 text-[12px] text-slate-600 hover:text-slate-900 hover:bg-slate-50 flex items-center justify-between transition-colors w-full">
<span>API Documentation</span>
<i data-lucide="external-link" class="w-3.5 h-3.5 text-slate-400"></i>
</a>
</li>
<li>
<a href="https://drycc.slack.com/" target="_blank" rel="noopener noreferrer" class="px-5 py-3 text-[12px] text-slate-600 hover:text-slate-900 hover:bg-slate-50 flex items-center justify-between transition-colors w-full">
<span>Community Support</span>
<i data-lucide="external-link" class="w-3.5 h-3.5 text-slate-400"></i>
</a>
</li>
</ul>
</div>
</div>
</aside>
</main>
<main-footer />
</div>
</template>
<script lang="ts">
import NavBar from "../components/NavBar.vue";
import {onBeforeMount, reactive, toRefs} from 'vue'
import AccountSidebar from "../components/AccountSidebar.vue";
import MainFooter from "../components/MainFooter.vue";
import AccessTokenDelete from "../components/AccessTokenDelete.vue"
import { Trash2 } from "lucide-vue-next"
import {dealAccessTokenList, getAccessTokenList, deleteAccessToken} from "../services/tokens";
export default {
name: "AccessTokenList",
components: {
'nav-bar': NavBar,
'account-sidebar': AccountSidebar,
'main-footer': MainFooter,
'access-token-delete': AccessTokenDelete,
Trash2
},
setup() {
const state = reactive({
tokens: [],
token: Object,
isShowDelete: false,
})
onBeforeMount(async () => {
await fetchAccessTokenList()
})
const fetchAccessTokenList = (async () => {
let res = await getAccessTokenList()
state.tokens = res.data ? dealAccessTokenList(res) : []
})
const showDelete = (index) => {
state.isShowDelete = true
state.token = state.tokens.slice(index, index + 1)[0];
}
const closeDelete = (param) => {
state.isShowDelete = false
if (param.hasAccessTokenDeleted) {
fetchAccessTokenList()
}
}
return {
...toRefs(state),
showDelete,
closeDelete
}
},
}
</script>
<style scoped>
/* Styles removed in favor of Tailwind CSS framework classes */
</style>