Compare commits

...

2 Commits

Author SHA1 Message Date
cb99b03c87 1 2025-09-09 20:47:29 +08:00
8e941d6fbd 1 2025-08-12 14:22:42 +08:00
3 changed files with 563 additions and 225 deletions

View File

@ -36,6 +36,7 @@
<script src="static/js/countdown.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.4.4/build/qrcode.min.js"></script>
<script src="static/js/common.js?20250717"></script>
<link rel="stylesheet" href="static/css/cli-traffic.css">
</head>
<body>
<div id="app">
@ -116,6 +117,14 @@
长效号码
</vs-sidebar-item>
</vs-sidebar-group>
<vs-sidebar-group open title="API管理">
<vs-sidebar-item
index="5"
@click="handleActiveMenu(5)"
icon="vpn_key"
>API密钥</vs-sidebar-item
>
</vs-sidebar-group>
</div>
@ -181,8 +190,25 @@
</div>
<div class="content-conter-header" v-if="activeMenu === 3||activeMenu===4">
<vs-select
v-model="smsQuery.platform"
autocomplete
clearable
class="selectExample"
placeholder="平台"
label="平台"
@change="handlePlatformChange"
>
<vs-select-item v-for="item in smsPlatformList"
:key="'sms_platform_'+item.value"
:value="item.value"
:text="item.label"
>
</vs-select-item>
</vs-select>
<vs-select
autocomplete
clearable
v-model="smsQuery.serviceCode"
autocomplete
class="selectExample"
@ -194,7 +220,7 @@
:key="'sms_service_'+item.code"
:value="item.code"
:text="item.name"
@click="handleSmsServiceChange(item)"
@click="handleFormServiceChange(item)"
></vs-select-item>
</vs-select>
@ -375,6 +401,7 @@
<vs-row v-else-if="activeMenu===3" class="content-conter-container">
<vs-table class="tablex" :data="smsList">
<template slot="thead">
<vs-th> 通道 </vs-th>
<vs-th> 服务 </vs-th>
<vs-th> Phone </vs-th>
<vs-th> 验证码 </vs-th>
@ -385,6 +412,10 @@
<template slot-scope="{data}">
<vs-tr :key="indextr" v-for="(tr, indextr) in data">
<vs-td :data="data[indextr].platformName">
{{data[indextr].platformName}}
</vs-td>
<vs-td :data="data[indextr].service">
{{data[indextr].service}}
</vs-td>
@ -435,11 +466,13 @@
<vs-row v-else-if="activeMenu===4" class="content-conter-container">
<vs-table class="tablex" :data="smsList">
<template slot="thead">
<vs-th> 通道 </vs-th>
<vs-th> 服务 </vs-th>
<vs-th> Phone </vs-th>
<vs-th> 期限(月)</vs-th>
<vs-th> 验证码 </vs-th>
<vs-th> 状态 </vs-th>
<vs-th> 说明 </vs-th>
<vs-th> 过期时间 </vs-th>
<vs-th> 自动续期 </vs-th>
<vs-th> 操作 </vs-th>
@ -447,6 +480,9 @@
<template slot-scope="{data}">
<vs-tr :key="indextr" v-for="(tr, indextr) in data">
<vs-td :data="data[indextr].platformName">
{{data[indextr].platformName}}
</vs-td>
<vs-td :data="data[indextr].service">
{{data[indextr].service}}
</vs-td>
@ -461,12 +497,31 @@
{{data[indextr].code}}
</vs-td>
<vs-td :data="data[indextr].status">
<vs-chip v-if="data[indextr].status===0" transparent color="primary">未唤醒</vs-chip>
<vs-chip v-else-if="data[indextr].status===1" transparent color="primary">等待中</vs-chip>
<vs-chip v-else-if="data[indextr].status===2" transparent color="success">已使用</vs-chip>
<vs-chip v-else-if="data[indextr].status===3" transparent color="warning">过期</vs-chip>
<!-- 唤醒倒计时当前时间小于startTime时显示 -->
<vs-chip v-if="data[indextr].status===1&&data[indextr].startTime && new Date() < new Date(data[indextr].startTime)" transparent color="info">
唤醒中
<countdown :time="new Date(data[indextr].startTime).getTime() - new Date().getTime()" v-slot="timeObj">
{{ timeObj.minutes.toString().padStart(2, '0') }}:{{ timeObj.seconds.toString().padStart(2, '0') }}
</countdown>
</vs-chip>
<!-- 接码中倒计时当前时间在startTime和endTime之间时显示 -->
<vs-chip v-else-if="data[indextr].status===1&&data[indextr].startTime && data[indextr].endTime && new Date() >= new Date(data[indextr].startTime) && new Date() < new Date(data[indextr].endTime)" transparent color="warning">
接码中
<countdown :time="new Date(data[indextr].endTime).getTime() - new Date().getTime()" v-slot="timeObj">
{{ timeObj.minutes.toString().padStart(2, '0') }}:{{ timeObj.seconds.toString().padStart(2, '0') }}
</countdown>
</vs-chip>
<!-- 原有状态显示 -->
<div v-else>
<vs-chip v-if="data[indextr].status===0" transparent color="primary">未唤醒</vs-chip>
<vs-chip v-else-if="data[indextr].status===1" transparent color="primary">等待中</vs-chip>
<vs-chip v-else-if="data[indextr].status===2" transparent color="success">已使用</vs-chip>
<vs-chip v-else-if="data[indextr].status===3" transparent color="warning">过期</vs-chip>
</div>
</vs-td>
<vs-td :data="data[indextr].remark">
{{data[indextr].remark}}
</vs-td>
<vs-td :data="data[indextr].expireTime">
{{formatDate(data[indextr].expireTime)}}
</vs-td>
@ -497,7 +552,49 @@
:page-size="smsQuery.pageSize"
@change="getSmsList"
></vs-pagination>
</vs-row>
<!--API管理-->
<vs-row v-else-if="activeMenu===5" class="content-conter-container">
<div class="api-management">
<div class="api-section">
<h3>API密钥管理</h3>
<div class="api-key-container">
<div class="api-key-item">
<label>API Key:</label>
<div class="api-key-display">
<vs-input
:type="showApiKey ? 'text' : 'password'"
:value="apiKey"
readonly
placeholder="暂无API Key"
></vs-input>
<vs-button
icon="visibility"
type="flat"
size="small"
@click="toggleApiKeyVisibility"
>
{{ showApiKey ? '隐藏' : '显示' }}
</vs-button>
</div>
</div>
<div class="api-status-item">
<label>API状态:</label>
<div class="api-status-display">
<vs-switch
v-model="apiStatus"
@input="handleApiStatusChange"
/>
<span class="status-text">
{{ apiStatus ? '开启' : '关闭' }}
</span>
</div>
</div>
</div>
</div>
</div>
</vs-row>
</div>
@ -700,6 +797,20 @@
<!--获取号码-->
<vs-popup title="租赁号码" :active.sync="showGetSms" :loading="loading">
<div>
<vs-select
autocomplete
v-model="smsForm.platform"
autocomplete
class="selectExample"
placeholder="请选择平台"
label="平台"
@change="handlePlatformChange">
<vs-select-item
v-for="item in smsPlatformList"
:key="'form_platform'+item.value"
:value="item.value"
:text="item.label"></vs-select-item>
</vs-select>
<vs-select
autocomplete
v-model="smsForm.serviceCode"
@ -707,7 +818,7 @@
class="selectExample"
placeholder="请选择服务"
label="服务"
>
@change="handleSmsServiceChange">
<vs-select-item
v-for="item in smsServices"
:key="'form_service'+item.code"
@ -720,10 +831,10 @@
<vs-input class="inputx" placeholder="租赁月份" label="租赁月份" v-model="smsForm.period"/>
</div>
<div class="form-item" style="text-align: right;">
价格U: {{ this.smsForm.type===1&&this.smsForm.period>0? this.smsPrice*this.smsForm.period :this.smsPrice}}
价格U: {{ totalSmsPrice}}
</div>
<div class="form-item" style="text-align: right">
<vs-button size="small" @click="getNumber"
<div class="form-item" v-if="totalSmsPrice>0" style="text-align: right">
<vs-button size="small" @click="getNumber" v-if="smsPrice"
>确认租赁</vs-button>
</div>
</vs-popup>
@ -786,15 +897,23 @@
pageSize:10,
total:0,
serviceCode:"",
platform:"",
},
//短信列表
smsList:[],
showGetSms:false,
smsForm:{},
smsPlatformList:[],
checkSmsCodeTimer:null,
longSmsStatusTimer:null,
smsPrice:undefined,
//Ip单价
price:undefined,
//API管理相关
apiId:undefined,
apiKey: '',
showApiKey: false,
apiStatus: false,
};
},
mounted() {
@ -815,10 +934,22 @@
this.getMyProxy();
this.getTraffictList();
this.getTrafficServer();
this.getSmsServices();
// this.getSmsServices();
this.getSmsPlatformList();
//定时器
this.createCheckSmsCodeTimer();
this.createLongSmsStatusTimer();
},
computed:{
totalSmsPrice(){
// 使用 parseFloat() 将值转换为浮点数,如果无法转换则默认为 0
const period = parseFloat(this.smsForm.period) || 1;
const price = parseFloat(this.smsPrice) || 0;
// 确保在执行乘法或 toFixed() 前price 和 period 都是数字
return period > 0 ? (price * period).toFixed(2) : price.toFixed(2);
}
},
watch: {
showGetProxy(val, oldVal) {
@ -858,7 +989,7 @@
},
},
methods: {
getSmsServices(){
getSmsServices(platform){
fetch(requestApi + "/sms-services/list", {
headers:{
Authorization: `Bearer ${this.token}`,
@ -868,9 +999,10 @@
})
.then((res) => res.json())
.then((response) => {
console.log('getSmsServices',response);
if (response.code === 200) {
this.smsServices = response.data;
let datas= response.data.filter(x=>x.platformCode===platform)
console.log("getSmsServices:",datas)
this.smsServices = datas?datas:[];
}
})
.catch((error) => {
@ -1265,8 +1397,9 @@
this.smsForm.type=0;
this.smsForm.serviceCode="";
this.smsForm.period=undefined;
this.smsForm.platform="";
this.reloadSmsList();
this.handleGetPrice(this.smsForm.type);
// this.handleGetPrice(this.smsForm.type);
break;
case 4:
this.smsQuery.page=1;
@ -1274,8 +1407,12 @@
this.smsForm.type=1;
this.smsForm.serviceCode="";
this.smsForm.period=undefined;
this.smsForm.platform="";
this.reloadSmsList();
this.handleGetPrice(this.smsForm.type);
// this.handleGetPrice(this.smsForm.type);
break;
case 5:
this.getApiKey();
break;
}
},
@ -1631,10 +1768,17 @@
},
//sms service切换
handleSmsServiceChange(item){
this.smsQuery.serviceCode=item.code;
let service=this.smsServices.find(x=>x.code===item);
if(service&&this.smsForm.type===1){
this.smsPrice=service.longPrice;
}else if(service&&this.smsForm.type===0){
this.smsPrice=service.price;
}
},
//重置sms列表
handleResetSmsList(){
this.smsQuery.platform="";
this.smsQuery.serviceCode="";
this.smsQuery.page=1;
@ -1658,6 +1802,7 @@
type:type,
pageIndex: this.smsQuery.page,
pageSize: this.smsQuery.pageSize,
platformCode:this.smsQuery.platform,
serviceCode: this.smsQuery.serviceCode,
};
@ -1691,6 +1836,7 @@
handleShowGetSms(){
this.smsForm.period=undefined;
this.smsForm.serviceCode="";
this.smsForm.platform="";
this.showGetSms = true;
},
resetSmsForm(){
@ -1700,6 +1846,7 @@
type = 1;
}
this.smsForm={
platform: "",
serviceCode: "",
period:undefined,
type:type,
@ -1728,6 +1875,7 @@
}
let data = JSON.stringify({
platformCode: this.smsForm.platform,
serviceCode: this.smsForm.serviceCode,
type: this.smsForm.type,
period: Number(this.smsForm.period),
@ -1802,14 +1950,14 @@
codesMap.set(item.activationId, item);
});
console.log(codesMap);
this.smsList.forEach(item => {
const newItem = codesMap.get(item.activationId); // 尝试从 Map 中获取新 code
if (newItem!==undefined&&newItem!==null&&newItem.status===2) { // 如果找到了匹配的 activationId
item.code = newItem.code; // 替换 code
item.status = newItem.status; // 修改 status 为 2
}else if (newItem!==undefined&&newItem!==null&&newItem.status===3){
item.status=newItem.status
}})
}
})
@ -1930,23 +2078,25 @@
this.hiddenLoading();
});
},
handleGetPrice(type){
fetch(requestApi + "/sms-services/price?type=" + type, {
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
Accept: "application/json",
}
})
.then((res) => res.json())
.then((response) => {
if (response.code === 200) {
this.smsPrice = response.data;
handleGetPrice(type,platformCode,servicesCode){
if (type&&platformCode&&servicesCode) {
fetch(`${requestApi}/sms-services/price?type=${type}&platformCode=${platformCode}&serviceCode=${servicesCode}`, {
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
Accept: "application/json",
}
})
.catch((error) => {
console.log(error);
});
})
.then((res) => res.json())
.then((response) => {
if (response.code === 200) {
this.smsPrice = response.data;
}
})
.catch((error) => {
console.log(error);
});
}
},
//取消sms
handleCancelSms(row){
@ -2244,156 +2394,182 @@
this.hiddenLoading();
});
},
getSmsPlatformList(){
fetch(requestApi + "/sms-services/platform", {
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
Accept: "application/json",
}
})
.then((res) => res.json())
.then((response) => {
console.log(response);
if (response.code === 200) {
this.smsPlatformList = response.data;
}
})
.catch((error) => {
console.log(error);
});
},
handlePlatformChange(item){
console.log("handlePlatformChange:",item);
this.smsQuery.serviceCode="";
this.smsForm.serviceCode="";
this.getSmsServices(item);
},
handleFormServiceChange(serviceCode){
this.handleGetPrice(this.smsForm.type,this.smsForm.platformCode,serviceCode);
},
// API管理相关方法
getApiKey() {
console.log("",requestApi+"/api-info");
fetch(requestApi + "/api-info", {
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((res) => res.json())
.then((response) => {
if (response.code === 200) {
this.apiId = response.data.id
this.apiKey = response.data.api || '';
this.apiStatus = response.data.status === 1;
}
})
.catch((error) => {
console.log(error);
});
},
toggleApiKeyVisibility() {
this.showApiKey = !this.showApiKey;
},
handleApiStatusChange(status) {
this.showLoading();
const data = JSON.stringify({
id:this.apiId,
status: status ? 1 : 2
});
fetch(requestApi + "/api-info", {
method: "PUT",
headers: {
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: data,
})
.then((res) => res.json())
.then((response) => {
if (response.code === 200) {
this.$vs.notify({
title: "提示",
color: "success",
text: `API已${status ? '开启' : '关闭'}`,
position: "top-right",
});
} else {
// 如果请求失败,恢复原状态
this.apiStatus = !status;
this.$vs.notify({
title: "提示",
color: "danger",
text: response.msg || "操作失败",
position: "top-right",
});
}
})
.catch((error) => {
// 如果请求失败,恢复原状态
this.apiStatus = !status;
console.log(error);
this.$vs.notify({
title: "提示",
color: "danger",
text: "网络错误,请稍后重试",
position: "top-right",
});
})
.finally(() => {
this.hiddenLoading();
});
},
/**
* 创建长效号码状态检查定时器
* 用于定期检查和更新长效号码的倒计时状态
*/
createLongSmsStatusTimer() {
if (this.longSmsStatusTimer) {
clearInterval(this.longSmsStatusTimer);
}
// 每秒检查一次状态更新
this.longSmsStatusTimer = setInterval(() => {
this.updateLongSmsStatus();
}, 1000);
},
/**
* 清理长效号码状态检查定时器
*/
cleanLongSmsStatusTimer() {
if (this.longSmsStatusTimer) {
clearInterval(this.longSmsStatusTimer);
this.longSmsStatusTimer = null;
}
},
/**
* 更新长效号码状态
* 检查倒计时是否结束,并触发相应的状态更新
*/
updateLongSmsStatus() {
if (this.activeMenu !== 4 || !this.smsList.length) {
return;
}
const now = new Date();
let needRefresh = false;
this.smsList.forEach(item => {
// 检查唤醒倒计时是否结束
if (item.startTime && now >= new Date(item.startTime) &&
item.endTime && now < new Date(item.endTime)) {
// 唤醒倒计时结束,进入接码中状态
if (!item._inReceivingState) {
item._inReceivingState = true;
needRefresh = true;
}
}
// 检查接码中倒计时是否结束
if (item.endTime && now >= new Date(item.endTime)) {
// 接码中倒计时结束,恢复原有逻辑
if (item._inReceivingState) {
item._inReceivingState = false;
needRefresh = true;
}
}
});
// 如果有状态变化,强制更新视图
if (needRefresh) {
this.$forceUpdate();
}
},
},
beforeDestroy() {
// 清理定时器,防止内存泄漏
this.cleanLongSmsStatusTimer();
if (this.checkSmsCodeTimer) {
clearInterval(this.checkSmsCodeTimer);
}
if (this.checkOrderTimer) {
clearInterval(this.checkOrderTimer);
}
},
});
</script>
<style>
html,
body,
#app {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
font-family: DMSans-Regular;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
background-color: #fff;
border-bottom: 1px solid #eee;
padding: 0 20px;
}
.parentx-static {
overflow: hidden;
height: 500px;
position: relative;
}
.logo {
font-weight: bold;
font-size: 20px;
padding: 15px 0px 15px 15px;
height: 100%;
cursor: pointer;
}
.main-area {
display: flex;
height: calc(100vh - 60px); /* 减去 header 高度 */
}
.sidebar {
width: 200px;
background-color: #ffffff;
border-right: 1px solid #eee;
}
.sidebar .vs-content-sidebar .vs-sidebar {
background-color: unset !important;
}
.content {
flex: 1;
padding: 8px;
overflow: auto;
background: #eeeeee;
}
.content-conter {
background-color: white;
padding: 16px;
height: 100%;
}
/* .user-dropdown{
color: #000;
} */
.dropdown-menu {
width: 100px;
}
.tablex {
width: 100%;
padding-top: 10px;
}
.popup-content {
padding-bottom: 15px;
}
.popup-bottom {
text-align: right;
padding-top: 10px;
}
.qr-code {
height: 155px;
}
.success-contianer {
height: 100%;
line-height: 155px;
}
.success-contianer img {
height: 40px;
}
.content-conter-header {
display: flex;
padding-bottom: 5px;
}
/* .content-conter-header > *:not(:first-child) {
margin-left: 10px;
} */
.balance-btn[data-v-d585ebde] {
padding: 0 10px;
box-sizing: border-box;
max-width: max-content;
height: 30px;
line-height: 30px;
background: #ff916f33;
border-radius: 4px;
font-weight: 700;
font-size: 14px;
color: #ff916f;
margin-right: 28px;
cursor: pointer;
}
.table-cell-center {
text-align: left;
}
.btn-group {
display: flex;
gap: 10px;
place-items: end;
padding-left: 10px;
}
.operat-btn{
margin-top: 10px;
}
.form-item{
padding-top: 10px
}
.content-conter-container{
height: calc(100% - 45px);
overflow-y: auto;
}
.content-conter-footer{
height: 45px;
}
</style>
</html>

View File

@ -1446,51 +1446,10 @@
</div>
</div>
<div class="footer_right">
<!-- <div>
<h2>Solutions</h2>
<a href="download.html">Download</a>
<a href="socks5-ip.html">ISP S5</a>
<a href="enterprise-socks5-ip.html">Enterprise ISP S5</a>
<a href="iso-code.html">ISO Code</a>
</div>
<div>
<h2>Pricing</h2>
<a href="isp-socks5.html">Residential (socks5) proxies</a>
<a href="residential-proxies.html"
>Residential proxies - Traffic Plan</a
>
<a href="static-isp.html">Static ISP - Long time</a>
<a href="unlimit-proxies.html">Unlimited residential proxies</a>
</div>
<div>
<h2>Proxy Services</h2>
<a href="terms-service.html">Terms of Service</a>
<a href="privacy-policy.html">Privacy Policy</a>
<a href="refund-policy.html">Refund Policy</a>
<a href="anti-fraud.html">Anti-Money Laundering Policy</a>
</div> -->
<!-- <div>
<h2>Use Cases</h2>
<a href="price-monitoring.html">Price Monitoring</a>
<a href="advertising-verificat.html">Advertising Verificat</a>
<a href="brand-protection.html">Brand Protection</a>
<a href="market-research.html">Market Research</a>
<a href="travel-fare-summary.html">Travel Fare Summary</a>
<a href="serp-seo-proxies.html">SERP&SEO</a>
</div> -->
</div>
</div>
</footer>
<!-- <script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?c4dddce310908c98ca8617f0056e59e9";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script> -->
<script data-cfasync="false" src="static/js/email-decode.min.js"></script>
<script>

203
static/css/cli-traffic.css Normal file
View File

@ -0,0 +1,203 @@
/* CLI Traffic 页面样式 */
html,
body,
#app {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
font-family: DMSans-Regular;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
background-color: #fff;
border-bottom: 1px solid #eee;
padding: 0 20px;
}
.parentx-static {
overflow: hidden;
height: 500px;
position: relative;
}
.logo {
font-weight: bold;
font-size: 20px;
padding: 15px 0px 15px 15px;
height: 100%;
cursor: pointer;
}
.main-area {
display: flex;
height: calc(100vh - 60px); /* 减去 header 高度 */
}
.sidebar {
width: 200px;
background-color: #ffffff;
border-right: 1px solid #eee;
}
.sidebar .vs-content-sidebar .vs-sidebar {
background-color: unset !important;
}
.content {
flex: 1;
padding: 8px;
overflow: auto;
background: #eeeeee;
}
.content-conter {
background-color: white;
padding: 16px;
height: 100%;
}
/* .user-dropdown{
color: #000;
} */
.dropdown-menu {
width: 100px;
}
.tablex {
width: 100%;
padding-top: 10px;
}
.popup-content {
padding-bottom: 15px;
}
.popup-bottom {
text-align: right;
padding-top: 10px;
}
.qr-code {
height: 155px;
}
.success-contianer {
height: 100%;
line-height: 155px;
}
.success-contianer img {
height: 40px;
}
.content-conter-header {
display: flex;
padding-bottom: 5px;
}
/* .content-conter-header > *:not(:first-child) {
margin-left: 10px;
} */
.balance-btn[data-v-d585ebde] {
padding: 0 10px;
box-sizing: border-box;
max-width: max-content;
height: 30px;
line-height: 30px;
background: #ff916f33;
border-radius: 4px;
font-weight: 700;
font-size: 14px;
color: #ff916f;
margin-right: 28px;
cursor: pointer;
}
.table-cell-center {
text-align: left;
}
.btn-group {
display: flex;
gap: 10px;
place-items: end;
padding-left: 10px;
}
.operat-btn {
margin-top: 10px;
}
.form-item {
padding-top: 10px;
}
.content-conter-container {
height: calc(100% - 45px);
overflow-y: auto;
}
.content-conter-footer {
height: 45px;
}
/* API管理样式 */
.api-management {
padding: 20px;
}
.api-section h3 {
margin-bottom: 20px;
color: #333;
font-size: 18px;
font-weight: 600;
}
.api-key-container {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.api-key-item, .api-status-item {
margin-bottom: 20px;
}
.api-key-item:last-child, .api-status-item:last-child {
margin-bottom: 0;
}
.api-key-item label, .api-status-item label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #495057;
}
.api-key-display {
display: flex;
align-items: center;
gap: 10px;
}
.api-key-display .vs-input {
flex: 1;
}
.api-status-display {
display: flex;
align-items: center;
gap: 10px;
}
.status-text {
font-weight: 500;
color: #495057;
}