@takagaki/cortex-decisions-viewer 0.12.21 → 0.12.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/render.js +381 -134
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -756,10 +756,10 @@ function sanitizeHarnessShelf(v) {
|
|
|
756
756
|
* 写し忘れても赤くならなかったので、突き合わせは viewer/test/state-vocab-parity.test.mjs。
|
|
757
757
|
*/
|
|
758
758
|
const STATE_STATUSES = {
|
|
759
|
-
reachable: ["ok", "not_in_channel", "missing_scope", "no_token", "unreachable", "unknown", "not_in_space", "no_credentials"],
|
|
759
|
+
reachable: ["ok", "not_in_channel", "missing_scope", "no_token", "unreachable", "unknown", "not_in_space", "no_credentials", "no_consent", "transcripts_api_disabled", "no_access_policy", "speaker_attribution_off", "folder_not_granted"],
|
|
760
760
|
ingested: ["ok", "failed", "skipped"],
|
|
761
761
|
};
|
|
762
|
-
const STATE_ROW_FIELDS = ["cap", "ref", "workspace", "question", "status", "at", "by", "needed", "lastOkAt", "lastFailAt"];
|
|
762
|
+
const STATE_ROW_FIELDS = ["cap", "ref", "workspace", "question", "status", "at", "by", "needed", "detail", "lastOkAt", "lastFailAt"];
|
|
763
763
|
/** stateNow はライブ専用キーで build.ts の網にかからない。画面側でも許可リストを通す */
|
|
764
764
|
function sanitizeStateNow(v) {
|
|
765
765
|
if (!Array.isArray(v))
|
|
@@ -820,6 +820,11 @@ function sanitizeStateNow(v) {
|
|
|
820
820
|
* 書いた行だけスコープ名が消え、「権限が足りません」とだけ出て何を直せばよいか
|
|
821
821
|
* 分からない状態に戻る。上書きするときは空でも必ず載せる——夜間の古い needed が
|
|
822
822
|
* 新しい判定の横に残ると、直したはずのスコープ名がいつまでも出る
|
|
823
|
+
*
|
|
824
|
+
* **detail(最後に測った理由)も載せる(#346)。** 夜間が書く理由文はここを通らないと
|
|
825
|
+
* 画面に届かない(fleet-status.json には焼き込まれないので、ライブが届かない案件では出ない
|
|
826
|
+
* ——それは意図。古いスナップショットの理由を出すより、出さないほうが嘘にならない)。
|
|
827
|
+
* needed と同じく、上書きするときは空でも必ず載せる
|
|
823
828
|
*/
|
|
824
829
|
function overlayStateOnFleet(view, rows) {
|
|
825
830
|
const v = view || {};
|
|
@@ -855,6 +860,7 @@ function overlayStateOnFleet(view, rows) {
|
|
|
855
860
|
gateAt: hit.at,
|
|
856
861
|
gateBy: hit.by,
|
|
857
862
|
needed: hit.needed || "",
|
|
863
|
+
detail: hit.detail || "",
|
|
858
864
|
});
|
|
859
865
|
});
|
|
860
866
|
return changed ? Object.assign({}, v, { externalSources: externals }) : v;
|
|
@@ -937,6 +943,13 @@ function gchatRowsToAdd(externals, rows) {
|
|
|
937
943
|
*
|
|
938
944
|
* **種別(issues / discussions)は登録ファイルの宣言をそのまま使う。** 無いときだけ
|
|
939
945
|
* Issues に倒す——ここで決め打ちすると、Discussions だけを登録した案件のラベルが嘘になる。
|
|
946
|
+
*
|
|
947
|
+
* 🔴 **`origin: "explicit"` を付ける(#354)。** 「登録から削除」は `origin === "explicit"` の
|
|
948
|
+
* 行にだけ出る(導出リポに出すと押しても翌日戻る)。ここで足す行は `dev-sources.json` の
|
|
949
|
+
* `sources`(人が登録簿に書いたもの)からしか来ないので、明示登録であることは事実。
|
|
950
|
+
* 付けないと、**登録した直後の行だけ「登録から削除」が無い**(夜間のまとめ表が追いつく
|
|
951
|
+
* 翌朝まで、読めていない主役の行を外す手段が画面に無い——cortex-test で実際に踏んだ)。
|
|
952
|
+
* Slack の同じ穴を #221 で `chatRowsToAdd` が塞いだのと同型。
|
|
940
953
|
*/
|
|
941
954
|
function devRowsToAdd(externals, rows) {
|
|
942
955
|
const have = {};
|
|
@@ -951,7 +964,7 @@ function devRowsToAdd(externals, rows) {
|
|
|
951
964
|
continue;
|
|
952
965
|
if (have[String(r.ref)])
|
|
953
966
|
continue;
|
|
954
|
-
out.push({ type: r.type || "github-issues", name: r.ref, ref: r.ref });
|
|
967
|
+
out.push({ type: r.type || "github-issues", name: r.ref, ref: r.ref, origin: "explicit" });
|
|
955
968
|
}
|
|
956
969
|
return out;
|
|
957
970
|
}
|
|
@@ -1621,7 +1634,7 @@ function keyMissingText(cap) {
|
|
|
1621
1634
|
*/
|
|
1622
1635
|
const BAD_STATE_STATUSES = {
|
|
1623
1636
|
ingested: ["failed", "skipped"],
|
|
1624
|
-
reachable: ["not_in_channel", "missing_scope", "no_token", "unreachable", "not_in_space", "no_credentials"],
|
|
1637
|
+
reachable: ["not_in_channel", "missing_scope", "no_token", "unreachable", "not_in_space", "no_credentials", "no_consent", "transcripts_api_disabled", "no_access_policy", "speaker_attribution_off", "folder_not_granted"],
|
|
1625
1638
|
};
|
|
1626
1639
|
/**
|
|
1627
1640
|
* タイル1行(と接続グラフの1ノード)の状態を、**正本から導出する**(0.12.0)。
|
|
@@ -3320,6 +3333,56 @@ const CLIENT_JS = `
|
|
|
3320
3333
|
var SITE = JSON.parse(document.getElementById("site-data").textContent);
|
|
3321
3334
|
var ADAPTER_ICONS = ${JSON.stringify(ADAPTER_ICONS)};
|
|
3322
3335
|
|
|
3336
|
+
// ---- intake(操作の受け口)への共通の呼び出し(#349)----
|
|
3337
|
+
//
|
|
3338
|
+
// **全 op はここを通す。** 名乗り券(ポータルが出す Cookie の cortex-actor)を body に添えるのは
|
|
3339
|
+
// この1箇所だけ。fetch を直書きすると、その op だけ操作者が記録されない。
|
|
3340
|
+
// 券は document.cookie から**1回だけ**読んでメモリに持つ。site-data にも localStorage にも書かない
|
|
3341
|
+
// (ビルド時の値ではなく、開いた人ごとに違う値なので)。
|
|
3342
|
+
//
|
|
3343
|
+
// **1回だけ読むのは仕様。** ポータルからログアウトしても、開いたままのタブは券の期限まで
|
|
3344
|
+
// 名乗り続ける——署名付き Cookie と同じ性質で、券の寿命がそのまま名乗りの寿命になる。
|
|
3345
|
+
var ACTOR_COOKIE = "cortex-actor";
|
|
3346
|
+
var actorTicketCache;
|
|
3347
|
+
function actorTicket() {
|
|
3348
|
+
if (actorTicketCache !== undefined) return actorTicketCache;
|
|
3349
|
+
actorTicketCache = null;
|
|
3350
|
+
try {
|
|
3351
|
+
var parts = String(document.cookie || "").split(";");
|
|
3352
|
+
for (var i = 0; i < parts.length; i++) {
|
|
3353
|
+
var kv = parts[i].trim();
|
|
3354
|
+
if (kv.indexOf(ACTOR_COOKIE + "=") === 0) { actorTicketCache = kv.slice(ACTOR_COOKIE.length + 1) || null; break; }
|
|
3355
|
+
}
|
|
3356
|
+
} catch (e) { actorTicketCache = null; }
|
|
3357
|
+
return actorTicketCache;
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* intake の op を1回呼ぶ。POST は JSON の body に名乗り券(あれば)を actor として足す。
|
|
3361
|
+
* GET(opts.method === "GET" のとき)はカスタムヘッダも body も付けない(preflight を増やさないため・#198)。
|
|
3362
|
+
* 戻り値は fetch の Promise そのもの(呼び出し側の .then / await / res.json() はそのまま使える)。
|
|
3363
|
+
*/
|
|
3364
|
+
function callIntake(op, body, opts) {
|
|
3365
|
+
var intake = SITE.intake;
|
|
3366
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
3367
|
+
var url = pfx + "op=" + encodeURIComponent(op) + "&t=" + encodeURIComponent(intake.token);
|
|
3368
|
+
var o = opts || {};
|
|
3369
|
+
if (o.method === "GET") return fetch(url, { signal: o.signal });
|
|
3370
|
+
var payload = body && typeof body === "object" ? body : {};
|
|
3371
|
+
var ticket = actorTicket();
|
|
3372
|
+
if (ticket) {
|
|
3373
|
+
var withActor = {};
|
|
3374
|
+
for (var k in payload) if (Object.prototype.hasOwnProperty.call(payload, k)) withActor[k] = payload[k];
|
|
3375
|
+
withActor.actor = ticket;
|
|
3376
|
+
payload = withActor;
|
|
3377
|
+
}
|
|
3378
|
+
return fetch(url, {
|
|
3379
|
+
method: "POST",
|
|
3380
|
+
headers: { "content-type": "application/json" },
|
|
3381
|
+
body: JSON.stringify(payload),
|
|
3382
|
+
signal: o.signal,
|
|
3383
|
+
});
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3323
3386
|
// ---- ビルド時TSと共有する判定(唯一の正本は上の関数定義) ----
|
|
3324
3387
|
//
|
|
3325
3388
|
// **写経しない。** 接続マップ(ビルド時にSVGを描く)と画面(ブラウザで一覧を描く)が
|
|
@@ -3682,14 +3745,9 @@ ${deriveCapTile.toString()}
|
|
|
3682
3745
|
var endBusy = beginBusy(btn, "確認中…");
|
|
3683
3746
|
setStatus("", false);
|
|
3684
3747
|
var who = String(name || "").trim();
|
|
3685
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
3686
3748
|
var res, j;
|
|
3687
3749
|
try {
|
|
3688
|
-
res = await
|
|
3689
|
-
method: "POST",
|
|
3690
|
-
headers: { "content-type": "application/json" },
|
|
3691
|
-
body: JSON.stringify({ projectKey: intake.projectKey, path: path, submitter: who }),
|
|
3692
|
-
});
|
|
3750
|
+
res = await callIntake("promote", { projectKey: intake.projectKey, path: path, submitter: who });
|
|
3693
3751
|
j = await res.json().catch(function () { return {}; });
|
|
3694
3752
|
} catch (e) {
|
|
3695
3753
|
endBusy();
|
|
@@ -4400,6 +4458,44 @@ ${deriveCapTile.toString()}
|
|
|
4400
4458
|
cls: "ds-gate-muted",
|
|
4401
4459
|
help: "管理者の設定が済むと読み取りが始まります。",
|
|
4402
4460
|
},
|
|
4461
|
+
// --- ここから Microsoft 365(#335)。**値の正本は infra 側の語彙表**
|
|
4462
|
+
// (slack-gate.mjs の SLACK_GATE_VALUES と connection-state-keys.mjs の
|
|
4463
|
+
// REACHABLE_STATUSES)で、1対1の一致を infra のテストと
|
|
4464
|
+
// viewer/test/state-vocab-parity.test.mjs が見張る。
|
|
4465
|
+
// **判定を返すのは engine の夜間実測(1-E 以降)**——画面は語彙と文面だけを先に持つ
|
|
4466
|
+
// (判定器そのものは infra/lambda/shared の teams-gate.mjs / sharepoint-gate.mjs /
|
|
4467
|
+
// teams-chat-gate.mjs に既にある)。
|
|
4468
|
+
//
|
|
4469
|
+
// **バッジは短語・次の一手は help に分ける**(DS_GATE_NO_TOKEN と同じ作り。
|
|
4470
|
+
// バッジに長文を入れると行が折り返す)。**内部語を出さない**——証明書・トークン・
|
|
4471
|
+
// テナント・サービスプリンシパルは、読む人が自分の画面で探せない言葉
|
|
4472
|
+
"no_consent": {
|
|
4473
|
+
text: "管理者の承認が必要です",
|
|
4474
|
+
cls: "ds-gate-warn",
|
|
4475
|
+
help: "貴社の Microsoft 365 管理者が承認リンクを開いて許可すると読み取りが始まります。承認を取り消した場合もこの表示になります。",
|
|
4476
|
+
},
|
|
4477
|
+
"transcripts_api_disabled": {
|
|
4478
|
+
text: "文字起こしの外部取得がオフです",
|
|
4479
|
+
cls: "ds-gate-warn",
|
|
4480
|
+
help: "Teams 管理センターの会議設定で「文字起こしへの API アクセス」をオンにしてください。",
|
|
4481
|
+
},
|
|
4482
|
+
"no_access_policy": {
|
|
4483
|
+
text: "会議へのアクセス許可が未設定です",
|
|
4484
|
+
cls: "ds-gate-warn",
|
|
4485
|
+
help: "管理者がこの主催者にアクセス ポリシーを割り当てると読み取りが始まります(反映まで最大30分)。",
|
|
4486
|
+
},
|
|
4487
|
+
// **承認済み・API アクセスもオンなのに、話者名のトグルだけがオフ**という状態が実在する。
|
|
4488
|
+
// transcripts_api_disabled に寄せると、既にオンにしてある設定をもう一度案内することになる
|
|
4489
|
+
"speaker_attribution_off": {
|
|
4490
|
+
text: "話者名付きの文字起こしがオフです",
|
|
4491
|
+
cls: "ds-gate-warn",
|
|
4492
|
+
help: "Teams 管理センターの会議設定で話者名付き文字起こしをオンにしてください。オフのままだと取り込みません。",
|
|
4493
|
+
},
|
|
4494
|
+
"folder_not_granted": {
|
|
4495
|
+
text: "フォルダへのアクセス許可がありません",
|
|
4496
|
+
cls: "ds-gate-warn",
|
|
4497
|
+
help: "SharePoint 管理者がこのフォルダに読み取り許可を付与すると取り込みが始まります。",
|
|
4498
|
+
},
|
|
4403
4499
|
"unknown": { text: "不明", cls: "ds-gate-muted" },
|
|
4404
4500
|
};
|
|
4405
4501
|
/* ---------------------------------------------------------------------------
|
|
@@ -4472,6 +4568,126 @@ ${deriveCapTile.toString()}
|
|
|
4472
4568
|
cls: "ds-gate-muted",
|
|
4473
4569
|
},
|
|
4474
4570
|
};
|
|
4571
|
+
/**
|
|
4572
|
+
* 休止状態の行に、状態の語に添えて**押す前から**出す定型の補足(#346)と、
|
|
4573
|
+
* 「接続を確認」を押した結果に添える定型の1文(#356)。
|
|
4574
|
+
* 鍵は「アダプタ種別(helpTypeOf)× 状態」。値は { resting, probed } で、どちらも文の
|
|
4575
|
+
* パーツの配列。文字列はテキスト、{ link: <リンクの文字列> } は GitHub App の
|
|
4576
|
+
* インストール設定へのリンク(GITHUB_APP_INSTALL_URL)。
|
|
4577
|
+
*
|
|
4578
|
+
* ## 文の長さ(#356・オーナー却下を受けた原則)
|
|
4579
|
+
* **帯は症状だけ・行は具体的な一手だけ・説明を繰り返さない。** 0.12.22 では帯と行と
|
|
4580
|
+
* 押した結果に同じ admin の4文が3回出ており、オーナーが「長すぎ・冗長」と却下した。
|
|
4581
|
+
* - resting … **1文+リンクだけ**(誰が・どこで。「何が起きているか」は帯が言う)
|
|
4582
|
+
* - probed … **状態に応じた1文だけ**(1行目のサーバの needed が言わないことだけ)。
|
|
4583
|
+
* 押した結果に resting を繰り返さない——resting は押した瞬間に隠れ、needed が
|
|
4584
|
+
* 同じ操作(Configure から追加)を宛先つきで言う
|
|
4585
|
+
* 「admin」の語は画面に1回だけ(github-state-reason.test.mjs が押す前・押した後で数える)。
|
|
4586
|
+
*
|
|
4587
|
+
* ## なぜ画面の表で持つのか
|
|
4588
|
+
* サーバの needed / detail は「何が起きているか」と「どこを開くか」までを持つが、
|
|
4589
|
+
* **誰がその操作をできるか**(リポジトリの admin)と **request の意味**(組織オーナーの
|
|
4590
|
+
* 承認待ち)は、夜間(engine)とボタン(Lambda)の2つの書き手が別々に文を作るので、
|
|
4591
|
+
* サーバ側で足すと片方だけ古くなる。判定と URL はサーバ、定型の補足はここ、という役割分担。
|
|
4592
|
+
*
|
|
4593
|
+
* 🔴 **DS_GATE には入れない**(実測が返す語彙の表で、突き合わせテストの対象)。
|
|
4594
|
+
* DS_GATE_NO_TOKEN と同じく、仕組みはアダプタ非依存・データだけがアダプタ固有。
|
|
4595
|
+
*
|
|
4596
|
+
* 🔴 **DS_GATE[状態] が help を持つ状態にはエントリを置かない。** 置くと行の補足が2段になる
|
|
4597
|
+
* (github-state-reason.test.mjs の「行の補足は最大1段」が見張る)。
|
|
4598
|
+
*
|
|
4599
|
+
* 🔴 **URL を文字列で書き写さない**(帯・押した結果と同じ定数を使う。写経すると張り替えで
|
|
4600
|
+
* 片方だけ古くなる)。
|
|
4601
|
+
*
|
|
4602
|
+
* エントリがある状態では**この文だけ**を出し、サーバの needed / detail は出さない
|
|
4603
|
+
* ——理由文と補足が issues / discussions の2行 × N リポに並ぶと、一覧が案内で埋まる
|
|
4604
|
+
* (DS_GATE_NO_TOKEN.github の項と同じ判断)。needed は押した結果が持つ。
|
|
4605
|
+
*
|
|
4606
|
+
* ⚠ **「移動・改名・削除の可能性 → 登録から外してください」は出さない。** App のトークン
|
|
4607
|
+
* では「未選択」と「消えた・改名」がどちらも 404 で**区別できない**(github-gate.mjs)。
|
|
4608
|
+
* 状態で出し分ける材料が無いので書かない。消えたリポジトリの逃げ道は、行に常に出る
|
|
4609
|
+
* 「登録から削除」(#354)が担う。区別する信号ができたら probed に足す
|
|
4610
|
+
*/
|
|
4611
|
+
var DS_GATE_RESTING_HELP = {
|
|
4612
|
+
github: {
|
|
4613
|
+
unreachable: {
|
|
4614
|
+
// 誰が(admin)・どこで(Configure の Repository access)だけ。理由は帯、宛先はリンクが持つ。
|
|
4615
|
+
// 着地点(Repository access)を名指しするのは、押す前の画面からこの語が消えないため
|
|
4616
|
+
//(設計レビュー②)。「admin が…してください」はオーナーの原文の形
|
|
4617
|
+
resting: [
|
|
4618
|
+
"このリポジトリの admin(管理者権限を持つ人)が、Configure の Repository access に追加してください。",
|
|
4619
|
+
{ link: "インストール設定を開く" },
|
|
4620
|
+
],
|
|
4621
|
+
// 2026-09-02 の実測: admin でないリポジトリを Configure で選ぶと「request」タグが付き、
|
|
4622
|
+
// 組織オーナーの承認待ちになる("Okay, updated" と出るのに対象に入らない)。
|
|
4623
|
+
// 1行目(サーバの needed)はこれを言わないので、ここだけが持つ
|
|
4624
|
+
probed: ["GitHub 側で request と表示された場合は、組織オーナーの承認待ちです。"],
|
|
4625
|
+
},
|
|
4626
|
+
},
|
|
4627
|
+
};
|
|
4628
|
+
/**
|
|
4629
|
+
* 表を引くときのアダプタ種別。**probeTypeOf を使わない**——あちらは「op=probe の受け口」の
|
|
4630
|
+
* 語彙で、Google Chat に空文字を返す(実測の口が無いため)。ここで使うと Google Chat の
|
|
4631
|
+
* エントリを永久に置けない
|
|
4632
|
+
*/
|
|
4633
|
+
function helpTypeOf(type) {
|
|
4634
|
+
var t = String(type || "");
|
|
4635
|
+
return t.indexOf("github") === 0 ? "github" : t;
|
|
4636
|
+
}
|
|
4637
|
+
function restingHelpEntryOf(type, gate) {
|
|
4638
|
+
var byType = DS_GATE_RESTING_HELP[helpTypeOf(type)];
|
|
4639
|
+
return (byType && byType[gate]) || null;
|
|
4640
|
+
}
|
|
4641
|
+
/** 休止状態の行に出すパーツ(無ければ null) */
|
|
4642
|
+
function restingHelpPartsOf(type, gate) {
|
|
4643
|
+
var e = restingHelpEntryOf(type, gate);
|
|
4644
|
+
return (e && e.resting) || null;
|
|
4645
|
+
}
|
|
4646
|
+
/** 押した結果に添えるパーツ(無ければ null)。行と同じ表から引く(写経しない) */
|
|
4647
|
+
function probedHelpPartsOf(type, gate) {
|
|
4648
|
+
var e = restingHelpEntryOf(type, gate);
|
|
4649
|
+
return (e && e.probed) || null;
|
|
4650
|
+
}
|
|
4651
|
+
/** パーツを1段落にする。包む要素の class は置き場所で違う(行は ds-gate-help・押した結果は ds-probe-help) */
|
|
4652
|
+
function restingHelpNode(parts, cls) {
|
|
4653
|
+
var p = el("p", { class: cls });
|
|
4654
|
+
parts.forEach(function (part) {
|
|
4655
|
+
if (typeof part === "string") p.appendChild(document.createTextNode(part));
|
|
4656
|
+
else if (part && part.link) p.appendChild(el("a", { href: GITHUB_APP_INSTALL_URL, target: "_blank", rel: "noopener", text: part.link }));
|
|
4657
|
+
});
|
|
4658
|
+
return p;
|
|
4659
|
+
}
|
|
4660
|
+
/**
|
|
4661
|
+
* 休止状態の行に出す「最後に測った理由」(表にエントリが無い状態のフォールバック)。
|
|
4662
|
+
* needed(次の一手・URL 込み)を優先し、無ければ detail。
|
|
4663
|
+
*
|
|
4664
|
+
* 🔴 **書き手が button の detail は出さない。** ボタンは detail に判定の語そのもの
|
|
4665
|
+
* ("unreachable")や生のエラー("ratelimited")を書く——lastFailDetail を空で潰さない
|
|
4666
|
+
* ための痕跡で、表示用の値ではない(intake の op=probe のコメント)。人向けの文を持つのは
|
|
4667
|
+
* needed の側。夜間(gold)の detail は人向けの文なので出す
|
|
4668
|
+
*/
|
|
4669
|
+
function stateReasonOf(s) {
|
|
4670
|
+
if (s.needed) return String(s.needed);
|
|
4671
|
+
if (s.detail && s.gateBy !== "button") return String(s.detail);
|
|
4672
|
+
return "";
|
|
4673
|
+
}
|
|
4674
|
+
/**
|
|
4675
|
+
* この行に、押す前から理由・補足を出してよいか(#346)。**語の列挙で持たない**——
|
|
4676
|
+
* 別の作業が DS_GATE に語や help を足した日に、黙って漏れたり2段になったりしないよう
|
|
4677
|
+
* 導出で決める:
|
|
4678
|
+
* - 状態がある(未測定の行には出すものが無い)
|
|
4679
|
+
* - ok でない
|
|
4680
|
+
* - missing_scope でない(スコープ名の札 .ds-probe-scope が既に持つ)
|
|
4681
|
+
* - no_token でない(GitHub は組織の帯が導線を1つだけ持つ設計・Slack は DS_GATE_NO_TOKEN の help)
|
|
4682
|
+
* - not_in_channel でない(help は無いが、**状態の語そのものが操作の指示**
|
|
4683
|
+
* 「Slackで招待してください」。その下に夜間の「読めませんでした: not_in_channel」を
|
|
4684
|
+
* 並べても情報が増えず、招待という具体的な指示を薄める。help の有無とは別の軸)
|
|
4685
|
+
* - dsGateOf(s).help が無い(あれば DS_GATE の固定文が次の一手を持っている)
|
|
4686
|
+
*/
|
|
4687
|
+
function showsRestingReason(s) {
|
|
4688
|
+
if (!s.gate || s.gate === "ok" || s.gate === "missing_scope" || s.gate === "no_token" || s.gate === "not_in_channel") return false;
|
|
4689
|
+
return !dsGateOf(s).help;
|
|
4690
|
+
}
|
|
4475
4691
|
/**
|
|
4476
4692
|
* Slack のチャンネルが社内ワークスペースのものか。**workspace の有無だけで決まる**
|
|
4477
4693
|
* (省略=社内。#104 でこの値が画面まで届いていることは slack-workspace.test.mjs が見張る)。
|
|
@@ -4796,12 +5012,7 @@ ${deriveCapTile.toString()}
|
|
|
4796
5012
|
msg.className = "ds-toggle-msg";
|
|
4797
5013
|
msg.textContent = "承認画面を準備しています…";
|
|
4798
5014
|
try {
|
|
4799
|
-
var
|
|
4800
|
-
var res = await fetch(pfx + "op=slack-oauth-start&t=" + encodeURIComponent(intake.token), {
|
|
4801
|
-
method: "POST",
|
|
4802
|
-
headers: { "content-type": "application/json" },
|
|
4803
|
-
body: "{}",
|
|
4804
|
-
});
|
|
5015
|
+
var res = await callIntake("slack-oauth-start", {});
|
|
4805
5016
|
var j = await res.json().catch(function () { return {}; });
|
|
4806
5017
|
if (!res.ok || !j.url) {
|
|
4807
5018
|
// サーバの文言をそのまま出す(未設定のときは「準備中です」が返る)
|
|
@@ -4860,7 +5071,6 @@ ${deriveCapTile.toString()}
|
|
|
4860
5071
|
orgResolveDone = true;
|
|
4861
5072
|
var intake = SITE.intake;
|
|
4862
5073
|
if (!intake) return;
|
|
4863
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
4864
5074
|
// **描画の途中で投げない。** ここは行を組み立てている最中に呼ばれるので、
|
|
4865
5075
|
// 例外が出ると**画面そのものが描かれない**(fetch が無い環境で実際にそうなった)。
|
|
4866
5076
|
// 判定し直しは「できたら直る」性質のもので、失敗しても行は
|
|
@@ -4872,11 +5082,7 @@ ${deriveCapTile.toString()}
|
|
|
4872
5082
|
// fetch が存在しない(ReferenceError)と、呼び出しが投げる
|
|
4873
5083
|
if (typeof fetch !== "function") return;
|
|
4874
5084
|
try {
|
|
4875
|
-
|
|
4876
|
-
method: "POST",
|
|
4877
|
-
headers: { "content-type": "application/json" },
|
|
4878
|
-
body: "{}",
|
|
4879
|
-
})
|
|
5085
|
+
callIntake("slack-resolve", {})
|
|
4880
5086
|
.then(function (res) { return res.ok ? res.json() : null; })
|
|
4881
5087
|
.then(function (j) {
|
|
4882
5088
|
if (!j || !j.resolved) return;
|
|
@@ -5017,9 +5223,14 @@ ${deriveCapTile.toString()}
|
|
|
5017
5223
|
var owners = githubOwnersView();
|
|
5018
5224
|
var out = [];
|
|
5019
5225
|
groupGithubByOwner(items, owners).forEach(function (g) {
|
|
5020
|
-
// **行ごとに meta を引く。** issues と discussions
|
|
5226
|
+
// **行ごとに meta を引く。** issues と discussions でアイコンとラベルが違う。
|
|
5227
|
+
// Issues と Discussions は同じ接続先(同じ dev 行)なので、理由・補足は1本目だけが
|
|
5228
|
+
// 持つ(#346)。同じ長文が2行に並ぶと一覧が案内で埋まる
|
|
5229
|
+
var seenRef = {};
|
|
5021
5230
|
var cards = g.items.map(function (x) {
|
|
5022
|
-
|
|
5231
|
+
var again = !!(x.ref && seenRef[x.ref]);
|
|
5232
|
+
if (x.ref) seenRef[x.ref] = 1;
|
|
5233
|
+
return extSourceCard(x, DS_TYPE[x.type] || { icon: "🔗", label: x.type || "その他" }, { quietHelp: again });
|
|
5023
5234
|
});
|
|
5024
5235
|
if (g.state === "warn") {
|
|
5025
5236
|
out.push(orgNoteBox(githubNotConnectedNodes(g), cards));
|
|
@@ -5082,34 +5293,58 @@ ${deriveCapTile.toString()}
|
|
|
5082
5293
|
var repos = o.repos != null ? o.repos : g.repos != null ? g.repos : g.items.length;
|
|
5083
5294
|
var unread = o.unreadable != null ? o.unreadable : g.unread != null ? g.unread : g.items.length;
|
|
5084
5295
|
var installed = o.appInstalled;
|
|
5085
|
-
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " はまだ接続されていません" }));
|
|
5086
5296
|
// **読める件数を出す。** 総数だけ出すと、同じ画面の集計と食い違う(#101 で3度直した形)
|
|
5087
5297
|
var head = repos + "リポジトリ中 " + unread + "件が読めていません。";
|
|
5298
|
+
// 🔴 **見出しは 「installed」 の分岐の中で出す(#346)。** ここで1つに畳んでいたので、
|
|
5299
|
+
// App 導入済みの組織にも「まだ接続されていません」と出て、**同じ帯の本文と矛盾**した
|
|
5300
|
+
// (実機でオーナーが踏み、利用者が「組織の接続からやり直し」と誤解した)
|
|
5088
5301
|
if (installed === true) {
|
|
5302
|
+
// 🔴 **帯は症状だけ(#356・オーナー却下を受けた原則)。** 0.12.22 ではここに9文
|
|
5303
|
+
// (App の説明・Configure の手順・admin の4文・移動改名削除・お客様の組織)を入れ、
|
|
5304
|
+
// 同じ説明が行と押した結果にも出て3回繰り返された。見出しは件数だけ・本文は2文だけ。
|
|
5305
|
+
// **組織名は見出しに入れない**——各行のバッジ「{組織}(GitHub App)」が持っている。
|
|
5306
|
+
// 組織が複数ある画面でも、組織ごとの帯のまま・見出しは同じ形。
|
|
5307
|
+
//
|
|
5308
|
+
// **見出しでは「読み取り対象に選ばれていません」と言い切らない。** 「appInstalled」 が
|
|
5309
|
+
// 分けられるのは「①未インストール」と「②未選択/③消えた・改名」までで、**②と③は
|
|
5310
|
+
// 分かれない**(App のトークンではどちらも 404)。見出しは事実だけを言う
|
|
5311
|
+
out.push(el("div", {
|
|
5312
|
+
class: "ds-orgnote-title",
|
|
5313
|
+
text: "⚠️ " + unread + "件のリポジトリを読み取れていません",
|
|
5314
|
+
}));
|
|
5089
5315
|
// App は入っている。**インストールをもう一度促さないこと**——済んでいる操作を促すのは
|
|
5090
|
-
//
|
|
5316
|
+
// 「設定する必要が無いものに設定を促す」偽の警告と同じで、しかも直らない。
|
|
5317
|
+
//
|
|
5318
|
+
// 「誰が・どこで」は各行の1文(DS_GATE_RESTING_HELP)が持つ。ここで繰り返さない。
|
|
5319
|
+
// 「移動・改名・削除の可能性 → 登録から外して」も言わない——②と③を区別する材料が
|
|
5320
|
+
// 無く、外す手段は行の「登録から削除」(#354)が常に持つ。
|
|
5321
|
+
// 「お客様の組織では、組織のオーナーに承認を依頼」も外した——request(承認待ち)の
|
|
5322
|
+
// 1文は押した結果が持つ(同じ表の probed)。
|
|
5091
5323
|
//
|
|
5092
|
-
//
|
|
5093
|
-
//
|
|
5094
|
-
//
|
|
5095
|
-
//
|
|
5324
|
+
// ⚠ **件数の文 「head」(「{N}リポジトリ中 {M}件が読めていません。」)もこの枝では
|
|
5325
|
+
// 出さない(#356)。** #101 で3度直した「読める件数を出す」を意図して覆した——見出しが
|
|
5326
|
+
// 件数を持つので、本文で二重に言うと帯が3文になる。分母(登録数)は画面から消えるが、
|
|
5327
|
+
// 箱の中に行が全部並んでいるので数えられる。false / null の枝は従来どおり head を出す。
|
|
5096
5328
|
//
|
|
5097
|
-
//
|
|
5098
|
-
//
|
|
5099
|
-
//
|
|
5100
|
-
//
|
|
5329
|
+
// **「各行の案内」ではなく「アクセス不可の行の案内」と言う。** 帯の箱には読めている行も
|
|
5330
|
+
// 一緒に入る(unread が1件でもあれば組織ごと warn)。案内を持つのは「アクセス不可」の
|
|
5331
|
+
// 行だけなので、行に出ている状態の語そのもので指す(設計レビュー②の指摘)。
|
|
5332
|
+
//
|
|
5333
|
+
// **「読み取り対象に入っていません」はオーナーの文(#356)。** 2026-08-21 の
|
|
5334
|
+
// 「②未選択と③消えた・改名を区別できないので断定しない」は、帯の本文については
|
|
5335
|
+
// この文で覆した(見出しは引き続き断定しない)。③の逃げ道は「登録から削除」。
|
|
5336
|
+
//
|
|
5337
|
+
// **設定画面の宛先は App 個別ページ(#346)。** いまの宛先と、そこに着ける人の条件は
|
|
5338
|
+
// 「appSettingsLink」 の項にある
|
|
5101
5339
|
out.push(el("p", {
|
|
5102
5340
|
class: "ds-group-note",
|
|
5103
|
-
text:
|
|
5104
|
-
+ "インストール設定を開いて、対象のリポジトリを追加してください。"
|
|
5105
|
-
+ "設定は App を入れた方の画面に出ます。見当たらない場合は、この組織で App を入れた方にご依頼ください。"
|
|
5106
|
-
+ "リポジトリ自体が一覧に見当たらない場合は、移動・改名・削除されている可能性があるので、登録から外してください。"
|
|
5107
|
-
+ "お客様の組織では、組織のオーナーに承認を依頼してください。",
|
|
5341
|
+
text: "GitHub App の読み取り対象に入っていません。アクセス不可の行の案内に従って追加してください。",
|
|
5108
5342
|
}));
|
|
5109
5343
|
out.push(appSettingsLink("インストール設定を開く →"));
|
|
5110
5344
|
return out;
|
|
5111
5345
|
}
|
|
5112
5346
|
if (installed === false) {
|
|
5347
|
+
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " はまだ接続されていません" }));
|
|
5113
5348
|
out.push(el("p", {
|
|
5114
5349
|
class: "ds-group-note",
|
|
5115
5350
|
text: head + "この組織に Cortex の GitHub App を入れると読めるようになります(Issues の読み取りだけ)。"
|
|
@@ -5120,7 +5355,13 @@ ${deriveCapTile.toString()}
|
|
|
5120
5355
|
}
|
|
5121
5356
|
// 「null」(確かめられなかった)と、この値を知らない古い fleet-status.json。
|
|
5122
5357
|
// **導線を出さない。** どちらが原因か分からないので、押させると外れたほうを
|
|
5123
|
-
//
|
|
5358
|
+
// 押させることになる(済んでいるインストールをもう一度、あるいは効かない設定画面へ)。
|
|
5359
|
+
//
|
|
5360
|
+
// 見出しは件数を言わない(件数は本文だけが持つ・見出しと二重に言わない)。
|
|
5361
|
+
// 「githubUnsureNodes」 の「接続を確認できませんでした」(=その夜、確かめる処理自体が
|
|
5362
|
+
// 走らなかった)とは意味が違う——こちらは**読めなかった事実はあるが、App の導入状況が
|
|
5363
|
+
// 分からない**ので、語を分ける
|
|
5364
|
+
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " のリポジトリが読めていません(原因は確かめられていません)" }));
|
|
5124
5365
|
out.push(el("p", {
|
|
5125
5366
|
class: "ds-group-note",
|
|
5126
5367
|
text: head + "原因(GitHub App が入っていない/入っているがこれらのリポジトリが選ばれていない)は、"
|
|
@@ -5291,14 +5532,8 @@ ${deriveCapTile.toString()}
|
|
|
5291
5532
|
btn.disabled = true;
|
|
5292
5533
|
msg.className = "ds-toggle-msg";
|
|
5293
5534
|
msg.textContent = "実行中…";
|
|
5294
|
-
var intake = SITE.intake;
|
|
5295
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
5296
5535
|
try {
|
|
5297
|
-
var res = await
|
|
5298
|
-
method: "POST",
|
|
5299
|
-
headers: { "content-type": "application/json" },
|
|
5300
|
-
body: JSON.stringify({ kind: kind }),
|
|
5301
|
-
});
|
|
5536
|
+
var res = await callIntake("sync", { kind: kind });
|
|
5302
5537
|
var j = await res.json().catch(function () { return {}; });
|
|
5303
5538
|
// **起こせなかったときも「失敗」と言わない。** 定期実行が拾うので、
|
|
5304
5539
|
// 押した人にできることは無い。何が起きるかだけを伝える
|
|
@@ -5360,29 +5595,37 @@ ${deriveCapTile.toString()}
|
|
|
5360
5595
|
/**
|
|
5361
5596
|
* すでに入っている App の**インストール設定**(対象リポジトリの追加)へ行くリンク。
|
|
5362
5597
|
*
|
|
5363
|
-
*
|
|
5364
|
-
*
|
|
5365
|
-
*
|
|
5366
|
-
*
|
|
5367
|
-
*
|
|
5368
|
-
*
|
|
5369
|
-
*
|
|
5370
|
-
*
|
|
5371
|
-
*
|
|
5372
|
-
*
|
|
5373
|
-
*
|
|
5374
|
-
*
|
|
5375
|
-
*
|
|
5376
|
-
*
|
|
5377
|
-
*
|
|
5378
|
-
*
|
|
5598
|
+
* **宛先は App 個別ページ(「GITHUB_APP_INSTALL_URL」 と同じ定数・#346)。**
|
|
5599
|
+
* 「infra/lambda/shared/github-gate.mjs」 の 「githubConfigureUrl」 とも同じ宛先なので、
|
|
5600
|
+
* **押す前(この帯・行の補足)と押した後(確認結果の文中)でリンク先が割れない**。
|
|
5601
|
+
* 関数は残してある——呼び手が「入れる」ではなく「設定を開く」意図で呼ぶ場所だから。
|
|
5602
|
+
*
|
|
5603
|
+
* ## なぜ他の2つの宛先を使わないか
|
|
5604
|
+
*
|
|
5605
|
+
* - 個人の一覧 「github.com/settings/installations」 は**「個人アカウントに入れた App」の
|
|
5606
|
+
* 画面**で、組織のインストールは別立て
|
|
5607
|
+
* (https://docs.github.com/en/apps/using-github-apps/reviewing-and-modifying-installed-github-apps )。
|
|
5608
|
+
* 押した本人が入れた App しか出ないので、**別の人が入れた組織では空**になる。
|
|
5609
|
+
* 0.12.21 まではこちらを指しており、着いた先が空の人が詰まった(#346)
|
|
5610
|
+
* - 組織の設定ページ 「github.com/organizations/<組織>/settings/installations」 は
|
|
5611
|
+
* **owner 以外 404**(2026-09-01 オーナーが踏んだ実害)。読み取り対象の組織の
|
|
5612
|
+
* オーナーが Cortex の利用者とは限らない(お客様の組織がまさにそれ)
|
|
5613
|
+
*
|
|
5614
|
+
* ⚠ **App 個別ページが「導入済みの組織で Configure に着く」ことは、GitHub の文書に
|
|
5615
|
+
* 記述が無い。根拠は 2026-09-02 のオーナーの実測**(cortex-test と GitHub の Configure
|
|
5616
|
+
* 画面で、admin のリポジトリは即追加・admin でないリポジトリには 「request」 タグ)。
|
|
5617
|
+
* 文書化されている一次ソースは次の2つで、**着いた先の画面そのものは書かれていない**:
|
|
5618
|
+
* - インストール URL と repository admin の設置条件
|
|
5619
|
+
* https://docs.github.com/en/apps/using-github-apps/installing-a-github-app-from-a-third-party
|
|
5620
|
+
* - 非オーナーの申請は組織オーナーへ通知される
|
|
5621
|
+
* https://docs.github.com/en/apps/using-github-apps/requesting-a-github-app-from-your-organization-owner
|
|
5622
|
+
* **GitHub 側の画面が変わったらここが最初に嘘になる。** 宛先を変えるときは
|
|
5623
|
+
* 「githubConfigureUrl」(サーバ)と揃えること。
|
|
5379
5624
|
*/
|
|
5380
|
-
var GITHUB_APP_SETTINGS_URL = "https://github.com/settings/installations";
|
|
5381
|
-
|
|
5382
5625
|
function appSettingsLink(label) {
|
|
5383
5626
|
return el("a", {
|
|
5384
5627
|
class: "ds-owner-install",
|
|
5385
|
-
href:
|
|
5628
|
+
href: GITHUB_APP_INSTALL_URL,
|
|
5386
5629
|
target: "_blank", rel: "noopener",
|
|
5387
5630
|
text: label || "インストール設定を開く →",
|
|
5388
5631
|
});
|
|
@@ -6975,11 +7218,9 @@ ${deriveCapTile.toString()}
|
|
|
6975
7218
|
var giveUp = ctrl ? setTimeout(function () { ctrl.abort(); }, LIVE_CONFIRM_TIMEOUT_MS) : null;
|
|
6976
7219
|
// **成功・失敗の両方で外す。** 残すと、window を閉じないテストで node --test が終わらない
|
|
6977
7220
|
var clearGiveUp = function () { if (giveUp != null) { clearTimeout(giveUp); giveUp = null; } };
|
|
6978
|
-
|
|
6979
|
-
// GET
|
|
6980
|
-
|
|
6981
|
-
signal: ctrl ? ctrl.signal : undefined,
|
|
6982
|
-
})
|
|
7221
|
+
// GET で取る。**カスタムヘッダは付けない**(preflight を1往復増やさないため)。
|
|
7222
|
+
// 名乗り券は body に載せる仕組みなので、GET のこの op には付かない(#349)
|
|
7223
|
+
callIntake("harness-catalog", null, { method: "GET", signal: ctrl ? ctrl.signal : undefined })
|
|
6983
7224
|
.then(function (res) {
|
|
6984
7225
|
clearGiveUp();
|
|
6985
7226
|
if (!res.ok) throw new Error("http " + res.status);
|
|
@@ -8216,9 +8457,13 @@ ${deriveCapTile.toString()}
|
|
|
8216
8457
|
* 「<a>」 の組み立てだけで作る(この画面の他の表示と同じ流儀)。
|
|
8217
8458
|
*
|
|
8218
8459
|
* **URL 以外は素のまま出す。** 言い換えると検索できないので、サーバの文言を削らない。
|
|
8460
|
+
*
|
|
8461
|
+
* **行に置くときは 「ds-gate-help」 を付ける**(第2引数)。「.ds-card」 は横並びの flex で、
|
|
8462
|
+
* 自分の行を占めるのは 「flex: 1 1 100%」 の 「.ds-gate-help」 だけ。押した結果で置き換える
|
|
8463
|
+
* とき 「probeSection」 が 「.ds-gate-help」 をまとめて隠すので、この class が要る。
|
|
8219
8464
|
*/
|
|
8220
|
-
function neededNode(text) {
|
|
8221
|
-
var p = el("p", { class: "ds-probe-help" });
|
|
8465
|
+
function neededNode(text, cls) {
|
|
8466
|
+
var p = el("p", { class: cls || "ds-probe-help" });
|
|
8222
8467
|
splitNeededParts(text).forEach(function (part) {
|
|
8223
8468
|
if (part.url) p.appendChild(el("a", { href: part.text, target: "_blank", rel: "noopener", text: part.text }));
|
|
8224
8469
|
else p.appendChild(document.createTextNode(part.text));
|
|
@@ -8233,10 +8478,23 @@ ${deriveCapTile.toString()}
|
|
|
8233
8478
|
* 「App が入っていない」と「App は入っているがこのリポジトリが選ばれていない」の
|
|
8234
8479
|
* 言い分けも、設定ページの URL もあちらが決める——**画面で分岐を書き足さない**
|
|
8235
8480
|
* (書いた瞬間に判定が2箇所になり、夜間の実測とボタンでずれる)。
|
|
8481
|
+
* **ただし状態に応じた定型の1文だけは画面の表(DS_GATE_RESTING_HELP の probed)から引く**
|
|
8482
|
+
* (#346 → #356)——「request は組織オーナーの承認待ち」のような GitHub 側の意味は、
|
|
8483
|
+
* サーバの2つの書き手が別々に文を作るので、片方だけ古くなる。
|
|
8484
|
+
*
|
|
8485
|
+
* 🔴 **行の常時表示(resting)を押した結果に繰り返さない(#356)。** 0.12.22 では admin の
|
|
8486
|
+
* 4文が行と押した結果の両方に出ていた。行の1文は押した瞬間に隠れ、needed が同じ操作
|
|
8487
|
+
* (Configure から追加)を宛先つきで言うので、ここは needed が言わない1文だけ
|
|
8236
8488
|
*/
|
|
8237
8489
|
function githubProbeHelpNodes(r) {
|
|
8238
8490
|
if (r.gate === "ok") return [];
|
|
8239
|
-
if (r.needed)
|
|
8491
|
+
if (r.needed) {
|
|
8492
|
+
var nodes = [neededNode(r.needed)];
|
|
8493
|
+
// 状態に応じた1文は**行と同じ表から引く**(ここに文を直書きすると表と割れる)
|
|
8494
|
+
var probed = probedHelpPartsOf("github", r.gate);
|
|
8495
|
+
if (probed) nodes.push(restingHelpNode(probed, "ds-probe-help"));
|
|
8496
|
+
return nodes;
|
|
8497
|
+
}
|
|
8240
8498
|
// needed が無いのは gate に落とし込めなかったとき。**生のエラーは隠さない**
|
|
8241
8499
|
return [el("p", { class: "ds-probe-help", text: "GitHub の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。" })];
|
|
8242
8500
|
}
|
|
@@ -8380,12 +8638,7 @@ ${deriveCapTile.toString()}
|
|
|
8380
8638
|
if (!intake) return;
|
|
8381
8639
|
var payload = { op: "refresh-channel-name", ref: s.ref };
|
|
8382
8640
|
if (s.workspace) payload.workspace = s.workspace;
|
|
8383
|
-
|
|
8384
|
-
fetch(pfx + "op=config&t=" + encodeURIComponent(intake.token), {
|
|
8385
|
-
method: "POST",
|
|
8386
|
-
headers: { "content-type": "application/json" },
|
|
8387
|
-
body: JSON.stringify(payload),
|
|
8388
|
-
}).then(function (res) {
|
|
8641
|
+
callIntake("config", payload).then(function (res) {
|
|
8389
8642
|
if (!res.ok) return;
|
|
8390
8643
|
// 一覧は夜間のスナップショットなので、その場では変わらない。行だけ追いつかせる
|
|
8391
8644
|
var nameEl = card.querySelector(".ds-name");
|
|
@@ -8446,13 +8699,8 @@ ${deriveCapTile.toString()}
|
|
|
8446
8699
|
// ワークスペースは Slack にしか無い概念なので、他アダプタでは載せない
|
|
8447
8700
|
var payload = { type: probeTypeOf(s.type), ref: s.ref };
|
|
8448
8701
|
if (s.workspace) payload.workspace = s.workspace;
|
|
8449
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
8450
8702
|
try {
|
|
8451
|
-
var res = await
|
|
8452
|
-
method: "POST",
|
|
8453
|
-
headers: { "content-type": "application/json" },
|
|
8454
|
-
body: JSON.stringify(payload),
|
|
8455
|
-
});
|
|
8703
|
+
var res = await callIntake("probe", payload);
|
|
8456
8704
|
var j = await res.json().catch(function () { return {}; });
|
|
8457
8705
|
if (!res.ok) {
|
|
8458
8706
|
// サーバの文言をそのまま出す(回数制限の案内・未対応のアダプタの説明を含んでいる)
|
|
@@ -8485,9 +8733,12 @@ ${deriveCapTile.toString()}
|
|
|
8485
8733
|
at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
|
|
8486
8734
|
probeHelpNodes(s, j).forEach(function (n) { body.appendChild(n); });
|
|
8487
8735
|
// **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が
|
|
8488
|
-
// 下に残ると、同じ行に2
|
|
8489
|
-
|
|
8490
|
-
|
|
8736
|
+
// 下に残ると、同じ行に2つのことが書いてある状態になる。
|
|
8737
|
+
// **1つ目だけでなく全部隠す(#346)。** 理由文(.ds-gate-reason)・定型補足
|
|
8738
|
+
// (.ds-gate-resting)・DS_GATE の help のどれが付いていても、押した結果と
|
|
8739
|
+
// 食い違う案内を残さない
|
|
8740
|
+
var stale = card.querySelectorAll(".ds-gate-help");
|
|
8741
|
+
for (var si = 0; si < stale.length; si++) stale[si].hidden = true;
|
|
8491
8742
|
// 表示名を Slack の実名へ自動で揃える(差分表示も更新ボタンも出さない)。
|
|
8492
8743
|
// **GitHub は揃えない。** 行の名前は owner/repo そのもので、書き換える口
|
|
8493
8744
|
//(op=config の refresh-channel-name)は channels.json 専用
|
|
@@ -9784,17 +10035,12 @@ ${deriveCapTile.toString()}
|
|
|
9784
10035
|
async function sendConfig(payload, msg, btn, onDone, hooks) {
|
|
9785
10036
|
var intake = SITE.intake;
|
|
9786
10037
|
if (!intake) return;
|
|
9787
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
9788
10038
|
// 押している間はボタンごと「処理中…」にする(#222・beginBusy の項)
|
|
9789
10039
|
var endBusy = beginBusy(btn);
|
|
9790
10040
|
msg.className = "ds-toggle-msg";
|
|
9791
10041
|
msg.textContent = "保存中…";
|
|
9792
10042
|
try {
|
|
9793
|
-
var res = await
|
|
9794
|
-
method: "POST",
|
|
9795
|
-
headers: { "content-type": "application/json" },
|
|
9796
|
-
body: JSON.stringify(payload),
|
|
9797
|
-
});
|
|
10043
|
+
var res = await callIntake("config", payload);
|
|
9798
10044
|
var j = await res.json().catch(function () { return {}; });
|
|
9799
10045
|
// 一覧が添えられていれば、断られていても先に画面を追いつかせる(#127)
|
|
9800
10046
|
var raced = hooks && hooks.onTargets && Array.isArray(j.targets) ? hooks.onTargets(j.targets) : false;
|
|
@@ -10278,7 +10524,11 @@ ${deriveCapTile.toString()}
|
|
|
10278
10524
|
}
|
|
10279
10525
|
|
|
10280
10526
|
|
|
10281
|
-
|
|
10527
|
+
/**
|
|
10528
|
+
* 接続先1件の行。「opts.quietHelp」 が真なら休止状態の補足(#346)を出さない
|
|
10529
|
+
* ——同じリポジトリの2本目の行(Discussions)用で、同じ長文が2行に並ぶのを避ける
|
|
10530
|
+
*/
|
|
10531
|
+
function extSourceCard(s, meta, opts) {
|
|
10282
10532
|
var badges = el("div", { class: "ds-badges" });
|
|
10283
10533
|
// **Gold 対象かのバッジは持たない。** 一覧に並ぶ Slack チャンネルは例外なく全て対象で、
|
|
10284
10534
|
// 行ごとに「対象です」と書いても情報が増えない。
|
|
@@ -10458,6 +10708,20 @@ ${deriveCapTile.toString()}
|
|
|
10458
10708
|
// **ボタンより後ろに置く。** カードは横並びの flex なので、間に挟むと
|
|
10459
10709
|
// 「登録を解除」が案内文の下の行へ落ちて、既存案件の行の形が変わる
|
|
10460
10710
|
if (gate.help) card.appendChild(el("p", { class: "ds-gate-help", text: gate.help }));
|
|
10711
|
+
// **最後に測った理由と直し方を、押す前から出す(#346)。** 「アクセス不可 8時間前に確認」
|
|
10712
|
+
// だけでは、押さない人(初見の PM 等)が止まる。表にエントリがある状態は定型の補足
|
|
10713
|
+
// (リンク込み)だけ、無い状態は needed → detail のフォールバック。どちらも ds-gate-help
|
|
10714
|
+
// を付ける——押した結果で置き換えるとき probeSection がまとめて隠す。
|
|
10715
|
+
// **同じリポジトリの2本目の行(Discussions)には出さない**(opts.quietHelp)。
|
|
10716
|
+
// 同じ長文が2行に並ぶと一覧が案内で埋まる。URL のリンク化は neededNode(innerHTML は使わない)
|
|
10717
|
+
if (!(opts && opts.quietHelp) && showsRestingReason(s)) {
|
|
10718
|
+
var restingParts = restingHelpPartsOf(s.type, s.gate);
|
|
10719
|
+
if (restingParts) card.appendChild(restingHelpNode(restingParts, "ds-gate-help ds-gate-resting"));
|
|
10720
|
+
else {
|
|
10721
|
+
var reason = stateReasonOf(s);
|
|
10722
|
+
if (reason) card.appendChild(neededNode(reason, "ds-gate-help ds-gate-reason"));
|
|
10723
|
+
}
|
|
10724
|
+
}
|
|
10461
10725
|
// 「接続を確認」(2026-08-19)。**押せる人を絞らない**——読むだけで設定を変えないので、
|
|
10462
10726
|
// 気づいた人がその場で確かめられるほうがよい(オーナー判断)。書き込みになる
|
|
10463
10727
|
// 「表示名を更新する」は、押した結果の中から別のボタンとして出る(probeSection)。
|
|
@@ -10796,17 +11060,15 @@ ${deriveCapTile.toString()}
|
|
|
10796
11060
|
var giveUp = ctrl ? setTimeout(function () { ctrl.abort(); }, LIVE_CONFIRM_TIMEOUT_MS) : null;
|
|
10797
11061
|
/** 見切りのタイマを外す。**応答が来たら必ず呼ぶ**(残すと次の取得を巻き込んで中断する) */
|
|
10798
11062
|
var clearGiveUp = function () { if (giveUp != null) { clearTimeout(giveUp); giveUp = null; } };
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
body: JSON.stringify(useFresh ? { caps: ALL_STATE_CAPS, fresh: true } : { caps: ALL_STATE_CAPS }),
|
|
10809
|
-
})
|
|
11063
|
+
// **常に全能力を要求する(ALL_STATE_CAPS の項)。** 知らない値は Lambda 側が黙って
|
|
11064
|
+
// 捨てる(断らない)ので、画面と Lambda の版が食い違っても接続状況の表示は消えない
|
|
11065
|
+
// **保存直後だけ据え置きを迂回させる。** これが無いと、サーバが保存前の答えを返し、
|
|
11066
|
+
// 足りない行が0件になって取り直しも張られない=手で再読み込みするまで追いつかない
|
|
11067
|
+
callIntake(
|
|
11068
|
+
"state",
|
|
11069
|
+
useFresh ? { caps: ALL_STATE_CAPS, fresh: true } : { caps: ALL_STATE_CAPS },
|
|
11070
|
+
{ signal: ctrl ? ctrl.signal : undefined },
|
|
11071
|
+
)
|
|
10810
11072
|
// **非2xxも失敗として扱う。** ビューアとLambdaの版は独立に進むので、
|
|
10811
11073
|
// op=state 未対応のLambdaは 400 を返す。通信エラーだけを見ていると空表示になる。
|
|
10812
11074
|
.then(function (res) {
|
|
@@ -11313,18 +11575,13 @@ ${deriveCapTile.toString()}
|
|
|
11313
11575
|
msg.className = "ds-toggle-msg";
|
|
11314
11576
|
msg.textContent = "保存中…";
|
|
11315
11577
|
var intake = SITE.intake;
|
|
11316
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
11317
11578
|
try {
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
// **押した時点で読む。** 組み立て時に固めると、接続先を登録した直後に
|
|
11325
|
-
// 貼った鍵が「スペースを特定できません」で断られる
|
|
11326
|
-
body: JSON.stringify(secretPayload(intake, kind, value, cardSpace)),
|
|
11327
|
-
});
|
|
11579
|
+
// **シークレット名は送らない。** どの連携かだけを送り、書き込み先はサーバが案件から決める。
|
|
11580
|
+
// **Backlog はスペースだけ名乗る(#138)。** 名前を組み立てるのはサーバで、
|
|
11581
|
+
// こちらが渡すのは「どのスペースの鍵か」という材料だけ。
|
|
11582
|
+
// **押した時点で読む。** 組み立て時に固めると、接続先を登録した直後に
|
|
11583
|
+
// 貼った鍵が「スペースを特定できません」で断られる
|
|
11584
|
+
var res = await callIntake("secret", secretPayload(intake, kind, value, cardSpace));
|
|
11328
11585
|
var j = await res.json().catch(function () { return {}; });
|
|
11329
11586
|
if (!res.ok) {
|
|
11330
11587
|
msg.className = "ds-toggle-msg is-error";
|
|
@@ -12073,7 +12330,6 @@ ${deriveCapTile.toString()}
|
|
|
12073
12330
|
if (!entries || !entries.length) { setStatus("⚠ ファイルを選択してください。", true); return; }
|
|
12074
12331
|
if (isT && !meeting) { setStatus("⚠ 会議名を選んでください。", true); return; }
|
|
12075
12332
|
submitBtn.disabled = true;
|
|
12076
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
12077
12333
|
function fail(msg) { setStatus("✗ " + msg, true); submitBtn.disabled = false; }
|
|
12078
12334
|
try {
|
|
12079
12335
|
var uploaded = [];
|
|
@@ -12082,10 +12338,8 @@ ${deriveCapTile.toString()}
|
|
|
12082
12338
|
setStatus("アップロード中… (" + (i + 1) + "/" + entries.length + ") " + f.name, false);
|
|
12083
12339
|
var pres;
|
|
12084
12340
|
try {
|
|
12085
|
-
pres = await
|
|
12086
|
-
|
|
12087
|
-
body: JSON.stringify({ projectKey: intake.projectKey, filename: f.name, contentType: f.type || "application/octet-stream" }),
|
|
12088
|
-
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12341
|
+
pres = await callIntake("presign", { projectKey: intake.projectKey, filename: f.name, contentType: f.type || "application/octet-stream" })
|
|
12342
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12089
12343
|
} catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
12090
12344
|
if (!pres.ok || !pres.j.url) return fail((pres.j && pres.j.message) || "アップロードURLの取得に失敗しました。対応していないファイル形式かもしれません。");
|
|
12091
12345
|
var put;
|
|
@@ -12103,10 +12357,8 @@ ${deriveCapTile.toString()}
|
|
|
12103
12357
|
try {
|
|
12104
12358
|
var payload = { projectKey: intake.projectKey, kind: kind, files: uploaded, submitter: submitter.trim() };
|
|
12105
12359
|
if (isT) payload.meeting = meeting;
|
|
12106
|
-
sub = await
|
|
12107
|
-
|
|
12108
|
-
body: JSON.stringify(payload),
|
|
12109
|
-
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12360
|
+
sub = await callIntake("submit", payload)
|
|
12361
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12110
12362
|
} catch (net3) { return fail("サーバーに接続できませんでした。もう一度お試しください。"); }
|
|
12111
12363
|
if (!sub.ok) return fail((sub.j && sub.j.message) || "取り込みに失敗しました。時間をおいて再度お試しください。");
|
|
12112
12364
|
status.className = "modal-status";
|
|
@@ -12464,15 +12716,10 @@ ${deriveCapTile.toString()}
|
|
|
12464
12716
|
var endBusy = beginBusy(btn, "削除中…");
|
|
12465
12717
|
msg.className = "record-delete-msg";
|
|
12466
12718
|
msg.textContent = "";
|
|
12467
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
12468
12719
|
var res, j;
|
|
12469
12720
|
try {
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
headers: { "content-type": "application/json" },
|
|
12473
|
-
// 名乗りは確認済み昇格で覚えたものを流用する(削除のためだけに名前を聞かない)
|
|
12474
|
-
body: JSON.stringify({ projectKey: intake.projectKey, path: path, submitter: rememberedVerifier() }),
|
|
12475
|
-
});
|
|
12721
|
+
// 名乗りは確認済み昇格で覚えたものを流用する(削除のためだけに名前を聞かない)
|
|
12722
|
+
res = await callIntake("delete-record", { projectKey: intake.projectKey, path: path, submitter: rememberedVerifier() });
|
|
12476
12723
|
j = await res.json().catch(function () { return {}; });
|
|
12477
12724
|
} catch (e) {
|
|
12478
12725
|
endBusy();
|
package/package.json
CHANGED