Clarify Tahesab self-signed TLS fix with one-click enable.

Health already failed on certificate; search/import need allowInsecureTls on the connection.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-05 09:52:27 +03:30
co-authored by Cursor
parent b42d39d756
commit 3277561e55
2 changed files with 82 additions and 7 deletions
@@ -132,9 +132,14 @@ export class TahesabHttpClient {
req.on('error', (err) => { req.on('error', (err) => {
this.logger.error(`Tahesab call failed: ${path}`, err); this.logger.error(`Tahesab call failed: ${path}`, err);
const raw = err.message || String(err);
const tlsHint =
/self-signed|certificate|UNABLE_TO_VERIFY|CERT_/i.test(raw)
? ' — در تنظیمات اتصال، گزینه «پذیرش گواهی SSL خودامضا» را فعال کنید.'
: '';
reject( reject(
new BadGatewayException( new BadGatewayException(
`اتصال به ته‌حساب ناموفق بود: ${err.message}`, `اتصال به ته‌حساب ناموفق بود: ${raw}${tlsHint}`,
), ),
); );
}); });
+76 -6
View File
@@ -70,6 +70,21 @@ function resetForm() {
form.enabled = true; form.enabled = true;
} }
function looksLikePrivateHttps(url: string) {
return /^https:\/\/(\d{1,3}\.){3}\d{1,3}(:\d+)?/i.test(url.trim());
}
function isTlsError(text: string | null | undefined) {
if (!text) return false;
return /self-signed|certificate|UNABLE_TO_VERIFY|CERT_|گواهی/i.test(text);
}
function onBaseUrlBlur() {
if (!editingId.value && looksLikePrivateHttps(form.baseUrl)) {
form.allowInsecureTls = true;
}
}
function startEdit(row: TahesabConnectionDto) { function startEdit(row: TahesabConnectionDto) {
editingId.value = row.id; editingId.value = row.id;
form.label = row.label; form.label = row.label;
@@ -162,12 +177,31 @@ async function testConn(id: string) {
`/integrations/tahesab/connections/${id}/test`, `/integrations/tahesab/connections/${id}/test`,
); );
if (data.ok) ok.value = 'اتصال سالم است.'; if (data.ok) ok.value = 'اتصال سالم است.';
else error.value = data.error ?? 'تست اتصال ناموفق بود.'; else {
error.value = data.error ?? 'تست اتصال ناموفق بود.';
if (isTlsError(data.error)) {
error.value +=
' → دکمه «فعال‌سازی SSL خودامضا» را بزنید یا در ویرایش اتصال تیک آن را بگذارید.';
}
}
await load(); await load();
} catch (e) { } catch (e) {
error.value = axios.isAxiosError(e) error.value = apiErr(e, 'تست ناموفق');
? String((e.response?.data as { message?: string })?.message ?? 'تست ناموفق') }
: 'تست ناموفق'; }
async function enableInsecureTls(id: string) {
error.value = '';
ok.value = '';
try {
await api.patch(`/integrations/tahesab/connections/${id}`, {
allowInsecureTls: true,
});
ok.value = 'پذیرش گواهی خودامضا فعال شد. در حال تست…';
await load();
await testConn(id);
} catch (e) {
error.value = apiErr(e, 'فعال‌سازی ناموفق بود');
} }
} }
@@ -332,6 +366,14 @@ onMounted(load);
<p v-if="error" class="error">{{ error }}</p> <p v-if="error" class="error">{{ error }}</p>
<p v-if="ok" class="ok">{{ ok }}</p> <p v-if="ok" class="ok">{{ ok }}</p>
<div
v-if="items.some((r) => !r.allowInsecureTls && isTlsError(r.lastError))"
class="banner warn"
>
اتصال شما به‌خاطر گواهی SSL خودامضا قطع است. روی همان کارت اتصال،
<strong>فعال‌سازی SSL خودامضا</strong> را بزنید؛ بعد دوباره «نمایش بازه» را امتحان کنید.
</div>
<div v-if="!loading && items.length" class="list"> <div v-if="!loading && items.length" class="list">
<article v-for="row in items" :key="row.id" class="card row"> <article v-for="row in items" :key="row.id" class="card row">
<div class="row-main"> <div class="row-main">
@@ -341,6 +383,7 @@ onMounted(load);
DB: {{ row.dbName }} · توکن: {{ row.tokenMasked }} DB: {{ row.dbName }} · توکن: {{ row.tokenMasked }}
<template v-if="row.isDefault"> · پیش‌فرض</template> <template v-if="row.isDefault"> · پیش‌فرض</template>
<template v-if="!row.enabled"> · غیرفعال</template> <template v-if="!row.enabled"> · غیرفعال</template>
<template v-if="row.allowInsecureTls"> · SSL خودامضا مجاز</template>
</span> </span>
<span <span
v-if="row.lastHealthAt" v-if="row.lastHealthAt"
@@ -353,6 +396,14 @@ onMounted(load);
</span> </span>
</div> </div>
<div class="row-actions"> <div class="row-actions">
<button
v-if="!row.allowInsecureTls && isTlsError(row.lastError)"
type="button"
class="btn primary"
@click="enableInsecureTls(row.id)"
>
فعال‌سازی SSL خودامضا
</button>
<button type="button" class="btn ghost" @click="startEdit(row)">ویرایش</button> <button type="button" class="btn ghost" @click="startEdit(row)">ویرایش</button>
<button type="button" class="btn ghost" @click="testConn(row.id)">تست</button> <button type="button" class="btn ghost" @click="testConn(row.id)">تست</button>
<button type="button" class="btn danger" @click="removeConn(row.id)">حذف</button> <button type="button" class="btn danger" @click="removeConn(row.id)">حذف</button>
@@ -373,6 +424,7 @@ onMounted(load);
required required
dir="ltr" dir="ltr"
placeholder="https://api-test.tahesab.ir یا https://IP:8081" placeholder="https://api-test.tahesab.ir یا https://IP:8081"
@blur="onBaseUrlBlur"
/> />
</div> </div>
<div class="field"> <div class="field">
@@ -401,9 +453,9 @@ onMounted(load);
<input v-model="form.enabled" type="checkbox" /> <input v-model="form.enabled" type="checkbox" />
فعال فعال
</label> </label>
<label class="check"> <label class="check highlight">
<input v-model="form.allowInsecureTls" type="checkbox" /> <input v-model="form.allowInsecureTls" type="checkbox" />
پذیرش گواهی SSL خودامضا (فقط شبکه داخلی) پذیرش گواهی SSL خودامضا (برای IP داخلی / https بدون گواهی معتبر — معمولاً لازم است)
</label> </label>
<div class="form-actions"> <div class="form-actions">
<button class="btn primary" type="submit" :disabled="saving"> <button class="btn primary" type="submit" :disabled="saving">
@@ -638,6 +690,24 @@ select {
color: #0f172a; color: #0f172a;
} }
.check.highlight {
padding: 0.65rem 0.75rem;
border-radius: 12px;
background: #fff7ed;
border: 1px solid #fdba74;
font-weight: 700;
color: #9a3412;
}
.banner.warn {
padding: 0.85rem 1rem;
border-radius: 14px;
background: #fff7ed;
border: 1px solid #fdba74;
color: #9a3412;
line-height: 1.7;
}
.form-actions { .form-actions {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;