Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
61f55674
Commit
61f55674
authored
Jan 10, 2026
by
yangjianbo
Browse files
Merge branch 'main' of
https://github.com/mt21625457/aicodex2api
parents
eeb1282f
7d1fe818
Changes
103
Hide whitespace changes
Inline
Side-by-side
frontend/src/views/auth/RegisterView.vue
View file @
61f55674
...
...
@@ -11,6 +11,9 @@
<
/p
>
<
/div
>
<!--
LinuxDo
Connect
OAuth
登录
-->
<
LinuxDoOAuthSection
v
-
if
=
"
linuxdoOAuthEnabled
"
:
disabled
=
"
isLoading
"
/>
<!--
Registration
Disabled
Message
-->
<
div
v
-
if
=
"
!registrationEnabled && settingsLoaded
"
...
...
@@ -181,6 +184,7 @@ import { ref, reactive, onMounted } from 'vue'
import
{
useRouter
}
from
'
vue-router
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
AuthLayout
}
from
'
@/components/layout
'
import
LinuxDoOAuthSection
from
'
@/components/auth/LinuxDoOAuthSection.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
TurnstileWidget
from
'
@/components/TurnstileWidget.vue
'
import
{
useAuthStore
,
useAppStore
}
from
'
@/stores
'
...
...
@@ -207,6 +211,7 @@ const emailVerifyEnabled = ref<boolean>(false)
const
turnstileEnabled
=
ref
<
boolean
>
(
false
)
const
turnstileSiteKey
=
ref
<
string
>
(
''
)
const
siteName
=
ref
<
string
>
(
'
Sub2API
'
)
const
linuxdoOAuthEnabled
=
ref
<
boolean
>
(
false
)
// Turnstile
const
turnstileRef
=
ref
<
InstanceType
<
typeof
TurnstileWidget
>
|
null
>
(
null
)
...
...
@@ -233,6 +238,7 @@ onMounted(async () => {
turnstileEnabled
.
value
=
settings
.
turnstile_enabled
turnstileSiteKey
.
value
=
settings
.
turnstile_site_key
||
''
siteName
.
value
=
settings
.
site_name
||
'
Sub2API
'
linuxdoOAuthEnabled
.
value
=
settings
.
linuxdo_oauth_enabled
}
catch
(
error
)
{
console
.
error
(
'
Failed to load public settings:
'
,
error
)
}
finally
{
...
...
frontend/src/views/user/KeysView.vue
View file @
61f55674
...
...
@@ -46,8 +46,17 @@
</div>
</
template
>
<
template
#cell-name=
"{ value }"
>
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
<
template
#cell-name=
"{ value, row }"
>
<div
class=
"flex items-center gap-1.5"
>
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
<Icon
v-if=
"row.ip_whitelist?.length > 0 || row.ip_blacklist?.length > 0"
name=
"shield"
size=
"sm"
class=
"text-blue-500"
:title=
"t('keys.ipRestrictionEnabled')"
/>
</div>
</
template
>
<
template
#cell-group=
"{ row }"
>
...
...
@@ -278,6 +287,52 @@
:placeholder=
"t('keys.selectStatus')"
/>
</div>
<!-- IP Restriction Section -->
<div
class=
"space-y-3"
>
<div
class=
"flex items-center justify-between"
>
<label
class=
"input-label mb-0"
>
{{ t('keys.ipRestriction') }}
</label>
<button
type=
"button"
@
click=
"formData.enable_ip_restriction = !formData.enable_ip_restriction"
:class=
"[
'relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none',
formData.enable_ip_restriction ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
]"
>
<span
:class=
"[
'pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
formData.enable_ip_restriction ? 'translate-x-4' : 'translate-x-0'
]"
/>
</button>
</div>
<div
v-if=
"formData.enable_ip_restriction"
class=
"space-y-4 pt-2"
>
<div>
<label
class=
"input-label"
>
{{ t('keys.ipWhitelist') }}
</label>
<textarea
v-model=
"formData.ip_whitelist"
rows=
"3"
class=
"input font-mono text-sm"
:placeholder=
"t('keys.ipWhitelistPlaceholder')"
/>
<p
class=
"input-hint"
>
{{ t('keys.ipWhitelistHint') }}
</p>
</div>
<div>
<label
class=
"input-label"
>
{{ t('keys.ipBlacklist') }}
</label>
<textarea
v-model=
"formData.ip_blacklist"
rows=
"3"
class=
"input font-mono text-sm"
:placeholder=
"t('keys.ipBlacklistPlaceholder')"
/>
<p
class=
"input-hint"
>
{{ t('keys.ipBlacklistHint') }}
</p>
</div>
</div>
</div>
</form>
<
template
#footer
>
<div
class=
"flex justify-end gap-3"
>
...
...
@@ -528,7 +583,10 @@ const formData = ref({
group_id
:
null
as
number
|
null
,
status
:
'
active
'
as
'
active
'
|
'
inactive
'
,
use_custom_key
:
false
,
custom_key
:
''
custom_key
:
''
,
enable_ip_restriction
:
false
,
ip_whitelist
:
''
,
ip_blacklist
:
''
})
// 自定义Key验证
...
...
@@ -664,12 +722,16 @@ const handlePageSizeChange = (pageSize: number) => {
const
editKey
=
(
key
:
ApiKey
)
=>
{
selectedKey
.
value
=
key
const
hasIPRestriction
=
(
key
.
ip_whitelist
?.
length
>
0
)
||
(
key
.
ip_blacklist
?.
length
>
0
)
formData
.
value
=
{
name
:
key
.
name
,
group_id
:
key
.
group_id
,
status
:
key
.
status
,
use_custom_key
:
false
,
custom_key
:
''
custom_key
:
''
,
enable_ip_restriction
:
hasIPRestriction
,
ip_whitelist
:
(
key
.
ip_whitelist
||
[]).
join
(
'
\n
'
),
ip_blacklist
:
(
key
.
ip_blacklist
||
[]).
join
(
'
\n
'
)
}
showEditModal
.
value
=
true
}
...
...
@@ -751,14 +813,26 @@ const handleSubmit = async () => {
}
}
// Parse IP lists only if IP restriction is enabled
const
parseIPList
=
(
text
:
string
):
string
[]
=>
text
.
split
(
'
\n
'
).
map
(
ip
=>
ip
.
trim
()).
filter
(
ip
=>
ip
.
length
>
0
)
const
ipWhitelist
=
formData
.
value
.
enable_ip_restriction
?
parseIPList
(
formData
.
value
.
ip_whitelist
)
:
[]
const
ipBlacklist
=
formData
.
value
.
enable_ip_restriction
?
parseIPList
(
formData
.
value
.
ip_blacklist
)
:
[]
submitting
.
value
=
true
try
{
if
(
showEditModal
.
value
&&
selectedKey
.
value
)
{
await
keysAPI
.
update
(
selectedKey
.
value
.
id
,
formData
.
value
)
await
keysAPI
.
update
(
selectedKey
.
value
.
id
,
{
name
:
formData
.
value
.
name
,
group_id
:
formData
.
value
.
group_id
,
status
:
formData
.
value
.
status
,
ip_whitelist
:
ipWhitelist
,
ip_blacklist
:
ipBlacklist
})
appStore
.
showSuccess
(
t
(
'
keys.keyUpdatedSuccess
'
))
}
else
{
const
customKey
=
formData
.
value
.
use_custom_key
?
formData
.
value
.
custom_key
:
undefined
await
keysAPI
.
create
(
formData
.
value
.
name
,
formData
.
value
.
group_id
,
customKey
)
await
keysAPI
.
create
(
formData
.
value
.
name
,
formData
.
value
.
group_id
,
customKey
,
ipWhitelist
,
ipBlacklist
)
appStore
.
showSuccess
(
t
(
'
keys.keyCreatedSuccess
'
))
// Only advance tour if active, on submit step, and creation succeeded
if
(
onboardingStore
.
isCurrentStep
(
'
[data-tour="key-form-submit"]
'
))
{
...
...
@@ -805,7 +879,10 @@ const closeModals = () => {
group_id
:
null
,
status
:
'
active
'
,
use_custom_key
:
false
,
custom_key
:
''
custom_key
:
''
,
enable_ip_restriction
:
false
,
ip_whitelist
:
''
,
ip_blacklist
:
''
}
}
...
...
frontend/src/views/user/UsageView.vue
View file @
61f55674
...
...
@@ -273,19 +273,6 @@
</div>
</
template
>
<
template
#cell-billing_type=
"{ row }"
>
<span
class=
"inline-flex items-center rounded px-2 py-0.5 text-xs font-medium"
:class=
"
row.billing_type === 1
? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'
: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-200'
"
>
{{
row
.
billing_type
===
1
?
t
(
'
usage.subscription
'
)
:
t
(
'
usage.balance
'
)
}}
</span>
</
template
>
<
template
#cell-first_token=
"{ row }"
>
<span
v-if=
"row.first_token_ms != null"
...
...
@@ -482,7 +469,6 @@ const columns = computed<Column[]>(() => [
{
key
:
'
stream
'
,
label
:
t
(
'
usage.type
'
),
sortable
:
false
},
{
key
:
'
tokens
'
,
label
:
t
(
'
usage.tokens
'
),
sortable
:
false
},
{
key
:
'
cost
'
,
label
:
t
(
'
usage.cost
'
),
sortable
:
false
},
{
key
:
'
billing_type
'
,
label
:
t
(
'
usage.billingType
'
),
sortable
:
false
},
{
key
:
'
first_token
'
,
label
:
t
(
'
usage.firstToken
'
),
sortable
:
false
},
{
key
:
'
duration
'
,
label
:
t
(
'
usage.duration
'
),
sortable
:
false
},
{
key
:
'
created_at
'
,
label
:
t
(
'
usage.time
'
),
sortable
:
true
},
...
...
@@ -745,7 +731,6 @@ const exportToCSV = async () => {
'
Rate Multiplier
'
,
'
Billed Cost
'
,
'
Original Cost
'
,
'
Billing Type
'
,
'
First Token (ms)
'
,
'
Duration (ms)
'
]
...
...
@@ -762,7 +747,6 @@ const exportToCSV = async () => {
log
.
rate_multiplier
,
log
.
actual_cost
.
toFixed
(
8
),
log
.
total_cost
.
toFixed
(
8
),
log
.
billing_type
===
1
?
'
Subscription
'
:
'
Balance
'
,
log
.
first_token_ms
??
''
,
log
.
duration_ms
].
map
(
escapeCSVValue
)
...
...
Prev
1
2
3
4
5
6
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment