Feat: Sub-license count limit - LicenseLimit field in licenses.ini + context menu
- Add LicenseLimit field to LicenseInfo struct (0 = not set, unlimited) - Add GetLicenseLimit/SetLicenseLimit: read/write LicenseLimit key in licenses.ini - Append |lic:N to reserved field in TOKEN_AUTH response only when LicenseLimit > 0; absent |lic: means no limit (client defaults to 9999), so super admin authenticating to its own server is never falsely terminated - Add "Sub-license limit" item in CLicenseDlg right-click menu (1-9999, empty = clear limit); menu label shows current value in real time - Limit change takes effect when sub-client re-authenticates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,7 @@ BEGIN_MESSAGE_MAP(CLicenseDlg, CDialogEx)
|
||||
ON_COMMAND(ID_LICENSE_DELETE, &CLicenseDlg::OnLicenseDelete)
|
||||
ON_COMMAND(ID_LICENSE_AUTO_FRP, &CLicenseDlg::OnLicenseAutoFrp)
|
||||
ON_COMMAND(ID_LICENSE_REVOKE_FRP, &CLicenseDlg::OnLicenseRevokeFrp)
|
||||
ON_COMMAND(ID_LICENSE_SET_LIMIT, &CLicenseDlg::OnLicenseSetLimit)
|
||||
END_MESSAGE_MAP()
|
||||
|
||||
// 前向声明:实现位于本文件后段(Auto FRP 相关工具)
|
||||
@@ -110,6 +111,9 @@ std::vector<LicenseInfo> GetAllLicenses(int* activeNum)
|
||||
it = kv.find("PendingQuota");
|
||||
if (it != kv.end()) info.PendingQuota = atoi(it->second.c_str());
|
||||
|
||||
it = kv.find("LicenseLimit");
|
||||
if (it != kv.end()) info.LicenseLimit = atoi(it->second.c_str());
|
||||
|
||||
licenses.push_back(info);
|
||||
}
|
||||
|
||||
@@ -346,6 +350,15 @@ void CLicenseDlg::OnNMRClickLicenseList(NMHDR* pNMHDR, LRESULT* pResult)
|
||||
const auto& lic = m_Licenses[nIndex];
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_RENEWAL, _T("预设续期(&N)..."));
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_EDIT_REMARK, _T("编辑备注(&E)..."));
|
||||
{
|
||||
CString strLimitLabel;
|
||||
int curLimit = lic.LicenseLimit;
|
||||
if (curLimit > 0)
|
||||
strLimitLabel.FormatL("下级数量限制: %d (&L)...", curLimit);
|
||||
else
|
||||
strLimitLabel = _TR("下级数量限制 (&L)...");
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_SET_LIMIT, strLimitLabel);
|
||||
}
|
||||
menu.AppendMenuL(MF_STRING, ID_LICENSE_AUTO_FRP, _T("自动FRP(&F)..."));
|
||||
|
||||
// 仅当该授权已分配 FRPC 端口时,才显示"撤销FRP"
|
||||
@@ -1254,3 +1267,80 @@ void CLicenseDlg::OnLicenseRevokeFrp()
|
||||
lic.SerialNumber.c_str(), existingPort);
|
||||
MessageBox(msg, _TR("撤销成功"), MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
|
||||
// 写入下级数量上限到 licenses.ini(limit <= 0 表示清除,恢复默认)
|
||||
bool SetLicenseLimit(const std::string& deviceID, int limit)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||
std::string iniPath = GetLicensesPath();
|
||||
config cfg(iniPath);
|
||||
if (cfg.GetStr(deviceID, "Passcode", "").empty())
|
||||
return false;
|
||||
if (limit > 0)
|
||||
cfg.SetInt(deviceID, "LicenseLimit", limit);
|
||||
else
|
||||
cfg.SetStr(deviceID, "LicenseLimit", ""); // 清除字段,恢复默认 10
|
||||
return true;
|
||||
}
|
||||
|
||||
// 读取下级数量上限;返回 0 表示未设置(调用方视为默认 10)
|
||||
int GetLicenseLimit(const std::string& deviceID)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _lock(LicensesIniMutex());
|
||||
std::string iniPath = GetLicensesPath();
|
||||
config cfg(iniPath);
|
||||
return cfg.GetInt(deviceID, "LicenseLimit", 0);
|
||||
}
|
||||
|
||||
void CLicenseDlg::OnLicenseSetLimit()
|
||||
{
|
||||
int nItem = m_ListLicense.GetNextItem(-1, LVNI_SELECTED);
|
||||
if (nItem < 0)
|
||||
return;
|
||||
|
||||
size_t nIndex = (size_t)m_ListLicense.GetItemData(nItem);
|
||||
if (nIndex >= m_Licenses.size())
|
||||
return;
|
||||
|
||||
auto& lic = m_Licenses[nIndex];
|
||||
int curLimit = lic.LicenseLimit;
|
||||
|
||||
CInputDialog dlg(this);
|
||||
CString strTitle;
|
||||
strTitle.FormatL("下级数量限制 (%s)", lic.SerialNumber.c_str());
|
||||
dlg.Init(strTitle, _L("数量上限(1-9999):"));
|
||||
if (curLimit > 0)
|
||||
dlg.m_str = std::to_string(curLimit).c_str();
|
||||
|
||||
if (dlg.DoModal() != IDOK)
|
||||
return;
|
||||
|
||||
std::string input(dlg.m_str);
|
||||
// 去除首尾空格
|
||||
size_t s = input.find_first_not_of(" \t");
|
||||
if (s != std::string::npos) input = input.substr(s);
|
||||
size_t e = input.find_last_not_of(" \t");
|
||||
if (e != std::string::npos) input = input.substr(0, e + 1);
|
||||
|
||||
int newLimit = 0;
|
||||
if (!input.empty()) {
|
||||
newLimit = atoi(input.c_str());
|
||||
if (newLimit < 1 || newLimit > 9999) {
|
||||
MessageBoxL("请输入 1 到 9999 之间的整数。", "输入无效", MB_ICONWARNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SetLicenseLimit(lic.SerialNumber, newLimit))
|
||||
return;
|
||||
|
||||
lic.LicenseLimit = newLimit;
|
||||
CString confirm;
|
||||
if (newLimit > 0)
|
||||
confirm.FormatL("授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。",
|
||||
lic.SerialNumber.c_str(), newLimit);
|
||||
else
|
||||
confirm.FormatL("授权 %s 的下级数量限制已清除。\n下级重新认证后生效。",
|
||||
lic.SerialNumber.c_str());
|
||||
MessageBox(confirm, _TR("设置成功"), MB_OK | MB_ICONINFORMATION);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user