[add] 設定settings/actions/secrets
This commit is contained in:
parent
3f363c9a3c
commit
91f314d297
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/vendor
|
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
// 使用 IntelliSense 以得知可用的屬性。
|
||||
// 暫留以檢視現有屬性的描述。
|
||||
// 如需詳細資訊,請瀏覽: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Listen for XDebug",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
// "hostname": "jianmiau.tk",
|
||||
"port": 9000,
|
||||
"pathMappings": {
|
||||
"web/MyWeb/GiteaRepoManager": "${workspaceRoot}",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Launch currently open script",
|
||||
"type": "php",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"cwd": "${fileDirname}",
|
||||
"port": 9003,
|
||||
"pathMappings": {
|
||||
"web/MyWeb/GiteaRepoManager": "${workspaceRoot}",
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
44
actions_settings.json
Normal file
44
actions_settings.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"project_settings": {
|
||||
"secrets": {
|
||||
"PROJECT_NAME": "excel:1082812249:$B",
|
||||
"PLATFORM": "excel:1082812249:$C"
|
||||
}
|
||||
},
|
||||
"repos": [
|
||||
{
|
||||
"name": "Resource-B2B",
|
||||
"secrets": {
|
||||
"ENV": "excel:1659610869:$C",
|
||||
"ACTION": "excel:1082812249:$D",
|
||||
"POS": "B4",
|
||||
"REPO": "eI4Z6Iuhuf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Resource-Out",
|
||||
"secrets": {
|
||||
"ENV": "excel:1866603134:$C",
|
||||
"POS": "D4",
|
||||
"REPO": "eI4Z6Iuhuf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Official-B2B",
|
||||
"secrets": {
|
||||
"ENV": "excel:1659610869:$C",
|
||||
"ACTION": "excel:1082812249:$D",
|
||||
"POS": "D4",
|
||||
"REPO": "xZcvkubN1o"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Official-Out",
|
||||
"secrets": {
|
||||
"ENV": "excel:1866603134:$C",
|
||||
"POS": "D4",
|
||||
"REPO": "xZcvkubN1o"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
17
composer.json
Normal file
17
composer.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "user/gitearepomanager",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"User\\GiteaRepoManager\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "JianMiau",
|
||||
"email": "bir840124@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"google/apiclient": "^2.14"
|
||||
}
|
||||
}
|
1281
composer.lock
generated
Normal file
1281
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
favicon.svg
Normal file
1
favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
14
google-service-key.json
Normal file
14
google-service-key.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "fresh-strategy-369406",
|
||||
"private_key_id": "a816afe3911c78b8618ca1846f703aefba3519b2",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDXzAYyDqjMNbSM\noGMuFKxagDhJH9TKoV2yyj4AAJZ2wGYOTKar5pJ78KW7GCwPr+w3Oom2YJX6QDXe\nxnrguikmku9bPUmHkoVb7ZK5R8aWqR6tjn+czRHU5wlU5FVv2Bn/uxI94vnfRb48\nZmKP6BG6RcLdoBBE7TFb8Kny+D0O7w3Q63r7hbJFssnfm/YBXyPpCOnUuB30GJmQ\n0rPNT37+BfbuLDH6mX9KCAfz4VTKPNQbje3r8b+7Il/woPs9DtLmJtkiHDMbxkjt\nFJCqwyUwjjAFE5Youepjv5Tpm5Q1IWSCKhE9rQ/XeuD0pezeF8wzaQMHfUPM9WxS\nYrVYP0odAgMBAAECggEAFNdqE8SRreNT9C77VTR+7uCqTvmpigZqr71Tnplv7rkn\nQiNKB5kltZ2oy/iKLNuvQyg+q6QJaBlyenkN3g1sswKG5nd1VggjJB0+SfGyLtPX\nmCiGj6TIn5jOsGm7DKnA3Q96tAprWpJ4TIoQ49gkeiqJpvDyEU4dMcV9DG/IKdxk\nKs99yw5dIcMOAq8o1C6ZM7eEJGcG9iyshlB86Bg0hPY8EfEIuP1dF/kTGELhBNKz\nbku7UQsBJdeBGb31Qa361Qx9tbifPK2zl1gpjG6dZmgTHgxYb2xEEAcUdzyKsWyR\n18D3PRmGHKLsCxYFmj9jzwYaxaEarYx9ap2VHPS+wQKBgQD6EWrN56oGUiujbwQC\nCM0a+NchGQ0LWF+09anDHohIKEA+Nto54k3FxqAkRfTC0flBfe0AzYmPNxbOSb8Q\ncb/FIaqbLRdIfNjnbpse8bNFh27UVMQDr18jm0ab2t7BKSyoKN4eVok6soxCRqlZ\nNSUPxspsC+zO9S69KkGP0SqO4QKBgQDc6n1lU0U06jz2R52IshOq47+nC5TvK3r7\nqRfIRjjSMyabhwtTCSU8xALAqQLS+lFauFtGRRSFLK07aXvaWPWwW37xADq7ICc6\nLdsvaJv2rFvx0Uuwp+EJOSXl5NDEU27PTf/44tzC4Gp/SyfD1wQVu0TzVKFe1ZXT\n0P1X0fOOvQKBgGSGJeIZ035w/7vWP801joXeLFTQxi6eWvLaomCeYHhpPdIEqNsF\n/u+XNf7+5DKAx+ss3N4qwbaBlbhdauIIZ+et7fAtQyPPlD4Md20MCl3T4JiYbqdw\nkxU0MUErzcnmbF4493lInieraLinwSHsPDbIWczvSkWzyBMg7nQKyEnhAoGBAI82\nAAZQnfu4ob5yHKDB+Ff+/n4W1vzY7gf4zS8KvskdWbjXKbMxqY8j7jjhF7CXj2fF\nPX5nR+8xUDfEoQKiStuB5N/s6yXlqShhE8c/BGQ7xfsUWAH0QsEM6BGJbQDoqVwA\nT6ETyFMY0lEk8mlVmRNRbFhmE5p70X4X7DQjKcXtAoGBAMlyvK404Ne9hc/cvoLp\n25zliltf0s5599xdX4tHINbKupGqAIr8KlMxEKZWr49Fys9D1U4SkMck7OKvWSSX\n8kQWwJZVfkJWcdbdNZWcO3QPvF7UQ2w4D+an2t/DngjecMlNEPfS9AO6UhN5LCft\nJ0ldwDas8FggZEoFqmG+VGbC\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "google-sheet@fresh-strategy-369406.iam.gserviceaccount.com",
|
||||
"client_id": "110536180295168446962",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/google-sheet%40fresh-strategy-369406.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
|
294
index.html
294
index.html
@ -2,294 +2,44 @@
|
||||
<html lang="zh-TW">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta charset="UTF-8">
|
||||
<title>手動輸入組織,批次建立 Gitea 儲存庫與團隊</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#log {
|
||||
white-space: pre-wrap;
|
||||
background: #f0f0f0;
|
||||
padding: 1em;
|
||||
border-radius: 8px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
label,
|
||||
input {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 1em;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>
|
||||
輸入組織名稱並從 JSON 建立儲存庫+設定團隊
|
||||
<br />
|
||||
PS.請先建好團隊(或使用下方按鈕批次建立)
|
||||
</h2>
|
||||
<h2>輸入組織名稱並從 JSON 建立儲存庫+設定團隊<br>PS.請先建好團隊(或使用下方按鈕批次建立)</h2>
|
||||
|
||||
<label for="orgInput">組織名稱:</label>
|
||||
<input type="text" id="orgInput" placeholder="請輸入組織名稱" />
|
||||
<button onclick="loadAndCreateTeams()">創建團隊</button>
|
||||
<button onclick="loadAndCreateRepos()">開始建立儲存庫</button>
|
||||
<label>組織名稱:<input type="text" id="orgInput"></label>
|
||||
<button data-action="createTeams">創建團隊</button>
|
||||
<button data-action="createRepos">建立儲存庫</button>
|
||||
<button data-action="setActions">設定 Actions</button>
|
||||
|
||||
<div id="log"></div>
|
||||
|
||||
<script>
|
||||
const accessToken = "96ed6b6d33931b122c7f12f94153594be0d75b32";
|
||||
const proxy = "https://cors-anywhere.bir840124.workers.dev/?url=";
|
||||
const giteaAPIBase = "https://git.catan.com.tw/api/v1";
|
||||
const logDiv = document.getElementById('log');
|
||||
|
||||
const logBox = document.getElementById("log");
|
||||
function log(message) {
|
||||
logBox.textContent += message + "\n";
|
||||
logBox.scrollTop = logBox.scrollHeight;
|
||||
}
|
||||
|
||||
// 建立儲存庫功能
|
||||
async function loadAndCreateRepos() {
|
||||
logBox.textContent = "";
|
||||
const orgInput = document.getElementById("orgInput").value.trim();
|
||||
if (!orgInput) {
|
||||
alert("請輸入組織名稱");
|
||||
document.querySelectorAll('button').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
logDiv.innerHTML = ""; // ← 清空舊訊息
|
||||
const org = document.getElementById('orgInput').value.trim();
|
||||
if (!org) {
|
||||
logDiv.innerHTML = "❌ 請輸入組織名稱<br>";
|
||||
return;
|
||||
}
|
||||
log(`✅ 開始建立儲存庫:${orgInput}`);
|
||||
|
||||
try {
|
||||
const res = await fetch("repos.json");
|
||||
const repoList = await res.json();
|
||||
|
||||
for (const repo of repoList) {
|
||||
repo.org = orgInput;
|
||||
await createRepoWithSettings(repo);
|
||||
}
|
||||
|
||||
log("✅ 所有儲存庫處理完成!");
|
||||
} catch (err) {
|
||||
log("❌ 讀取 repos.json 失敗:" + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function repoExists(org, repoName) {
|
||||
const url = `${giteaAPIBase}/orgs/${org}/repos`;
|
||||
const res = await fetch(proxy + url, {
|
||||
headers: { "Authorization": `token ${accessToken}` }
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`查詢儲存庫失敗,狀態碼:${res.status}`);
|
||||
}
|
||||
const repos = await res.json();
|
||||
return repos.some(r => r.name === repoName);
|
||||
}
|
||||
|
||||
async function createRepoWithSettings(repo) {
|
||||
if (await repoExists(repo.org, repo.name)) {
|
||||
log(`⚠️ 儲存庫已存在,略過建立:${repo.org}/${repo.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const createUrl = `${giteaAPIBase}/orgs/${repo.org}/repos`;
|
||||
|
||||
const payload = {
|
||||
name: repo.name,
|
||||
description: repo.description || "",
|
||||
default_branch: repo.default_branch || "master",
|
||||
private: false,
|
||||
auto_init: true
|
||||
const evt = new EventSource(`run.php?org=${encodeURIComponent(org)}&action=${btn.dataset.action}`);
|
||||
evt.onmessage = e => {
|
||||
logDiv.innerHTML += e.data + "<br>";
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(proxy + createUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `token ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
log(`✅ 建立儲存庫:${repo.org}/${repo.name}`);
|
||||
|
||||
if (repo.teams?.length > 0) {
|
||||
for (const team of repo.teams) {
|
||||
await addTeamToRepo(repo.org, repo.name, team);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:建立 Issue
|
||||
if (repo.issue) {
|
||||
await createIssue(repo.org, repo.name, repo.issue);
|
||||
}
|
||||
|
||||
} else {
|
||||
log(`⚠️ 建立失敗:${repo.org}/${repo.name} - ${data.message}`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
log(`❌ 建立錯誤:${repo.org}/${repo.name} - ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function createIssue(org, repoName, issue) {
|
||||
const url = `${giteaAPIBase}/repos/${org}/${repoName}/issues`;
|
||||
const payload = {
|
||||
title: issue.title || "",
|
||||
body: issue.content || ""
|
||||
evt.onerror = () => {
|
||||
// logDiv.innerHTML += "執行完成<br>";
|
||||
evt.close();
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(proxy + url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `token ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
log(`📌 已建立 Issue:${issue.title}`);
|
||||
} else {
|
||||
const data = await res.json();
|
||||
log(`⚠️ 建立 Issue 失敗:${data.message || res.statusText}`);
|
||||
}
|
||||
} catch (err) {
|
||||
log(`❌ 建立 Issue 錯誤:${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function addTeamToRepo(org, repo, teamName) {
|
||||
const teamsURL = `${giteaAPIBase}/orgs/${org}/teams`;
|
||||
const teamRes = await fetch(proxy + teamsURL, {
|
||||
headers: { "Authorization": `token ${accessToken}` }
|
||||
});
|
||||
const teams = await teamRes.json();
|
||||
const targetTeam = teams.find(t => t.name === teamName);
|
||||
|
||||
if (!targetTeam) {
|
||||
log(`⚠️ 找不到團隊:${teamName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = `${giteaAPIBase}/teams/${targetTeam.id}/repos/${org}/${repo}`;
|
||||
const res = await fetch(proxy + url, {
|
||||
method: "PUT",
|
||||
headers: { "Authorization": `token ${accessToken}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
log(`👥 加入團隊:${teamName}`);
|
||||
} else {
|
||||
log(`⚠️ 加團隊失敗:${teamName}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 創建團隊功能
|
||||
async function loadAndCreateTeams() {
|
||||
logBox.textContent = "";
|
||||
const orgInput = document.getElementById("orgInput").value.trim();
|
||||
if (!orgInput) {
|
||||
alert("請輸入組織名稱");
|
||||
return;
|
||||
}
|
||||
log(`✅ 開始建立團隊:${orgInput}`);
|
||||
|
||||
try {
|
||||
const res = await fetch("teams.json");
|
||||
const teamList = await res.json();
|
||||
|
||||
for (const team of teamList) {
|
||||
await createTeam(orgInput, team);
|
||||
}
|
||||
|
||||
log("✅ 所有團隊建立完成!");
|
||||
} catch (err) {
|
||||
log("❌ 讀取 teams.json 失敗:" + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function createTeam(org, team) {
|
||||
const url = `${giteaAPIBase}/orgs/${org}/teams`;
|
||||
|
||||
const payload = {
|
||||
name: team.name,
|
||||
description: team.description || "",
|
||||
permission: "none",
|
||||
units: team.units || [],
|
||||
units_map: team.units_map || {},
|
||||
can_create_org_repo: false,
|
||||
includes_all_repositories: false
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(proxy + url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `token ${accessToken}`
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
log(`✅ 團隊建立成功:${team.name}`);
|
||||
if (team.members?.length > 0) {
|
||||
for (const username of team.members) {
|
||||
await addMemberToTeam(data.id, username);
|
||||
}
|
||||
}
|
||||
} else if (data.message?.includes("team already exists")) {
|
||||
log(`⚠️ 團隊已存在,略過:${team.name}`);
|
||||
} else {
|
||||
log(`⚠️ 建立團隊失敗:${team.name} - ${data.message}`);
|
||||
}
|
||||
} catch (err) {
|
||||
log(`❌ 團隊建立錯誤:${team.name} - ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function addMemberToTeam(teamId, username) {
|
||||
const url = `${giteaAPIBase}/teams/${teamId}/members/${username}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(proxy + url, {
|
||||
method: "PUT",
|
||||
headers: { "Authorization": `token ${accessToken}` }
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
log(`👤 新增成員:${username}`);
|
||||
} else {
|
||||
const data = await res.json();
|
||||
log(`⚠️ 新增成員失敗:${username} - ${data.message || res.statusText}`);
|
||||
}
|
||||
} catch (err) {
|
||||
log(`❌ 新增成員錯誤:${username} - ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
301
run.php
Normal file
301
run.php
Normal file
@ -0,0 +1,301 @@
|
||||
<?php
|
||||
// run.php
|
||||
require 'vendor/autoload.php';
|
||||
use Google\Client;
|
||||
use Google\Service\Sheets;
|
||||
|
||||
$GITEA_URL = 'https://git.catan.com.tw/api/v1';
|
||||
$GITEA_TOKEN = '96ed6b6d33931b122c7f12f94153594be0d75b32';
|
||||
$SERVICE_KEY = __DIR__ . '/google-service-key.json';
|
||||
$SHEET_ID = '1e-8Cj3Szkb-P0lTKQTeeaS_wI4S7KLS4wyzf5PjNyHQ';
|
||||
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('Connection: keep-alive');
|
||||
|
||||
function logMsg($msg) {
|
||||
echo "data: $msg\n\n";
|
||||
@ob_flush();
|
||||
flush();
|
||||
}
|
||||
|
||||
function getExcelValue($excelRef, $orgInput) {
|
||||
global $SERVICE_KEY, $SHEET_ID;
|
||||
if (!preg_match('/^excel:(\d+):\$(\w)$/', $excelRef, $m)) return null;
|
||||
|
||||
$gid = $m[1];
|
||||
$targetCol = strtoupper($m[2]);
|
||||
$colIndex = ord($targetCol) - 65;
|
||||
|
||||
try {
|
||||
$client = new Client();
|
||||
$client->setAuthConfig($SERVICE_KEY);
|
||||
$client->addScope(\Google\Service\Sheets::SPREADSHEETS_READONLY);
|
||||
$service = new \Google\Service\Sheets($client);
|
||||
|
||||
$spreadsheet = $service->spreadsheets->get($SHEET_ID);
|
||||
$sheetName = null;
|
||||
foreach ($spreadsheet->getSheets() as $sheet) {
|
||||
if ($sheet->getProperties()->getSheetId() === intval($gid)) {
|
||||
$sheetName = $sheet->getProperties()->getTitle();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$sheetName) { logMsg("⚠️ 找不到 GID: $gid"); return null; }
|
||||
|
||||
$response = $service->spreadsheets_values->get($SHEET_ID, $sheetName);
|
||||
$values = $response->getValues() ?? [];
|
||||
|
||||
$lastNonEmptyCol = []; // 保存每個欄位最後非空值
|
||||
|
||||
foreach ($values as $r => &$row) {
|
||||
// 保證列長度至少到目標欄位
|
||||
for ($c = 0; $c <= $colIndex; $c++) {
|
||||
$cell = $row[$c] ?? '';
|
||||
$cell = trim($cell);
|
||||
if ($cell !== '') {
|
||||
$lastNonEmptyCol[$c] = $cell;
|
||||
} else if (isset($lastNonEmptyCol[$c])) {
|
||||
$row[$c] = $lastNonEmptyCol[$c]; // 更新列
|
||||
} else {
|
||||
$row[$c] = ''; // 避免未設定
|
||||
}
|
||||
// logMsg("DEBUG row={$r} col={$c} value='{$row[$c]}'");
|
||||
}
|
||||
|
||||
$cellOrg = $row[0] ?? '';
|
||||
if ($cellOrg === $orgInput) {
|
||||
$value = $row[$colIndex] ?? '';
|
||||
// logMsg("DEBUG 找到 org='{$orgInput}',回傳 col={$colIndex} 值='{$value}'");
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
unset($row);
|
||||
|
||||
logMsg("⚠️ Excel 找不到 org={$orgInput} 的資料 (sheet='{$sheetName}')");
|
||||
return '';
|
||||
} catch (\Exception $e) {
|
||||
logMsg("⚠️ Excel 讀取錯誤: " . $e->getMessage());
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function fetchJSON($url, $method='GET', $data=null) {
|
||||
global $GITEA_TOKEN;
|
||||
$ch = curl_init($url);
|
||||
$headers = ["Content-Type: application/json"];
|
||||
if ($GITEA_TOKEN) $headers[] = "Authorization: token $GITEA_TOKEN";
|
||||
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
if ($method === 'POST') curl_setopt($ch, CURLOPT_POST, true);
|
||||
if ($method === 'PUT') curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||
if ($method === 'PATCH') curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
|
||||
if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
|
||||
$res = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
return [$code, json_decode($res, true)];
|
||||
}
|
||||
|
||||
function createTeams($org) {
|
||||
global $GITEA_URL;
|
||||
$teams = json_decode(file_get_contents('teams.json'), true) ?? [];
|
||||
|
||||
logMsg("🟢 開始執行 createTeams...");
|
||||
|
||||
// 先抓全部團隊
|
||||
[$codeAll, $allTeams] = fetchJSON("$GITEA_URL/orgs/$org/teams", 'GET');
|
||||
$allTeams = $allTeams ?? [];
|
||||
|
||||
foreach ($teams as $team) {
|
||||
$foundTeam = null;
|
||||
foreach ($allTeams as $t) {
|
||||
if (strcasecmp($t['name'], $team['name']) === 0) {
|
||||
$foundTeam = $t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($foundTeam) {
|
||||
logMsg("🔄 團隊已存在,更新權限: {$team['name']}");
|
||||
// 更新權限
|
||||
fetchJSON("$GITEA_URL/teams/{$foundTeam['id']}", 'PATCH', [
|
||||
'units_map' => $team['units_map'] ?? [],
|
||||
'permission' => $team['permission'] ?? 'write'
|
||||
]);
|
||||
|
||||
// 補齊成員
|
||||
[$codeMembers, $resMembers] = fetchJSON("$GITEA_URL/teams/{$foundTeam['id']}/members", 'GET');
|
||||
$existingMembers = array_column($resMembers ?? [], 'username');
|
||||
foreach ($team['members'] ?? [] as $member) {
|
||||
if (!in_array($member, $existingMembers)) {
|
||||
[$codeAdd, $resAdd] = fetchJSON("$GITEA_URL/teams/{$foundTeam['id']}/members/$member", 'PUT');
|
||||
if ($codeAdd === 204) {
|
||||
logMsg(" ✅ 新增成員: $member");
|
||||
} else {
|
||||
logMsg(" ⚠️ 新增成員失敗: $member → " . json_encode($resAdd, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
logMsg("➕ 建立團隊: {$team['name']}");
|
||||
[$codeCreate, $resCreate] = fetchJSON("$GITEA_URL/orgs/$org/teams", 'POST', [
|
||||
'name' => $team['name'],
|
||||
'units_map' => $team['units_map'] ?? [],
|
||||
'permission' => $team['permission'] ?? 'write'
|
||||
]);
|
||||
|
||||
if ($codeCreate === 201) {
|
||||
$teamId = $resCreate['id'];
|
||||
foreach ($team['members'] ?? [] as $member) {
|
||||
[$codeAdd, $resAdd] = fetchJSON("$GITEA_URL/teams/$teamId/members/$member", 'PUT');
|
||||
if ($codeAdd === 204) {
|
||||
logMsg(" ✅ 新增成員: $member");
|
||||
} else {
|
||||
logMsg(" ⚠️ 新增成員失敗: $member → " . json_encode($resAdd, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logMsg("⚠️ 建立團隊失敗: {$team['name']} → " . json_encode($resCreate, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logMsg("✅ 所有團隊設定完成!");
|
||||
}
|
||||
|
||||
function createRepos($org) {
|
||||
global $GITEA_URL;
|
||||
|
||||
$repos = json_decode(file_get_contents('repos.json'), true) ?? [];
|
||||
|
||||
logMsg("🟢 開始執行 createRepos...");
|
||||
|
||||
// 先抓全部 repo,避免重複建立
|
||||
[$codeAll, $allRepos] = fetchJSON("$GITEA_URL/orgs/$org/repos");
|
||||
$allRepos = $allRepos ?? [];
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
$repoExists = false;
|
||||
foreach ($allRepos as $r) {
|
||||
if ($r['name'] === $repo['name']) {
|
||||
$repoExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($repoExists) {
|
||||
logMsg("⚠️ 儲存庫已存在,略過建立:{$repo['name']}");
|
||||
continue;
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'name' => $repo['name'],
|
||||
'description' => $repo['description'] ?? '',
|
||||
'default_branch' => $repo['default_branch'] ?? 'master',
|
||||
'private' => false,
|
||||
'auto_init' => true
|
||||
];
|
||||
|
||||
[$codeCreate, $resCreate] = fetchJSON("$GITEA_URL/orgs/$org/repos", 'POST', $payload);
|
||||
|
||||
if ($codeCreate === 201) {
|
||||
$repoName = $resCreate['name'];
|
||||
logMsg("✅ 建立儲存庫:{$repoName}");
|
||||
|
||||
// 加入團隊
|
||||
foreach ($repo['teams'] ?? [] as $teamName) {
|
||||
[$codeTeams, $teamsList] = fetchJSON("$GITEA_URL/orgs/$org/teams");
|
||||
$targetTeam = null;
|
||||
foreach ($teamsList ?? [] as $t) {
|
||||
if ($t['name'] === $teamName) {
|
||||
$targetTeam = $t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($targetTeam) {
|
||||
[$codeAdd, $resAdd] = fetchJSON("$GITEA_URL/teams/{$targetTeam['id']}/repos/$org/$repoName", 'PUT');
|
||||
if (in_array($codeAdd, [200,204])) {
|
||||
logMsg(" 👥 已加入團隊:{$teamName}");
|
||||
} else {
|
||||
logMsg(" ⚠️ 加入團隊失敗:{$teamName} → " . json_encode($resAdd, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
} else {
|
||||
logMsg(" ⚠️ 找不到團隊:{$teamName}");
|
||||
}
|
||||
}
|
||||
|
||||
// 建立 Issue
|
||||
if (!empty($repo['issue'])) {
|
||||
$issuePayload = [
|
||||
'title' => $repo['issue']['title'] ?? '',
|
||||
'body' => $repo['issue']['content'] ?? ''
|
||||
];
|
||||
[$codeIssue, $resIssue] = fetchJSON("$GITEA_URL/repos/$org/$repoName/issues", 'POST', $issuePayload);
|
||||
if ($codeIssue === 201) logMsg(" 📌 已建立 Issue:{$issuePayload['title']}");
|
||||
else logMsg(" ⚠️ 建立 Issue 失敗:{$issuePayload['title']} → " . json_encode($resIssue, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
} else {
|
||||
logMsg("⚠️ 建立儲存庫失敗:{$repo['name']} → " . json_encode($resCreate, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
|
||||
logMsg("✅ 所有儲存庫處理完成!");
|
||||
}
|
||||
|
||||
function setActions($org) {
|
||||
global $GITEA_URL;
|
||||
$actions = json_decode(file_get_contents('actions_settings.json'), true) ?? [];
|
||||
|
||||
logMsg("🟢 開始執行 setActions...");
|
||||
|
||||
$projectSecrets = $actions['project_settings']['secrets'] ?? [];
|
||||
logMsg("🔧 設定 project");
|
||||
foreach($projectSecrets as $key=>$value) {
|
||||
$finalValue = (is_string($value) && str_starts_with($value,'excel:'))
|
||||
? getExcelValue($value,$org)
|
||||
: $value;
|
||||
if(trim($finalValue)==='') { logMsg(" ⚠️ 略過空值 Project Secret: $key"); continue; }
|
||||
|
||||
[$code,$res] = fetchJSON("$GITEA_URL/orgs/$org/actions/secrets/$key",'PUT',['data'=>$finalValue]);
|
||||
if (in_array($code, [200,204,201])) logMsg(" ✅ Project Secret 設定成功: $key");
|
||||
else logMsg(" ⚠️ Project Secret 設定失敗: $key → ".json_encode($res,JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
$reposArr = $actions['repos'] ?? [];
|
||||
foreach($reposArr as $repo) {
|
||||
$repoName = $repo['name'];
|
||||
logMsg("🔧 設定 repo: $repoName");
|
||||
$secrets = $repo['secrets'] ?? [];
|
||||
foreach($secrets as $key=>$value) {
|
||||
$finalValue = (is_string($value) && str_starts_with($value,'excel:'))
|
||||
? getExcelValue($value,$org)
|
||||
: $value;
|
||||
if(trim($finalValue)==='') { logMsg(" ⚠️ 略過空值 Repo Secret: $key"); continue; }
|
||||
|
||||
[$code,$res] = fetchJSON("$GITEA_URL/repos/$org/$repoName/actions/secrets/$key",'PUT',['data'=>$finalValue]);
|
||||
if (in_array($code, [200,204,201])) logMsg(" ✅ Repo Secret 設定成功: $key");
|
||||
else logMsg(" ⚠️ Repo Secret 設定失敗: $key → ".json_encode($res,JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
}
|
||||
logMsg("✅ 所有 Actions 設定完成!");
|
||||
}
|
||||
|
||||
$org = $_GET['org'] ?? '';
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
if (!$org) { logMsg("❌ 請輸入組織名稱"); exit; }
|
||||
|
||||
switch($action) {
|
||||
case 'createTeams': createTeams($org); break;
|
||||
case 'createRepos': createRepos($org); break;
|
||||
case 'setActions': setActions($org); break;
|
||||
default: logMsg("⚠️ 未知操作: $action"); break;
|
||||
}
|
||||
|
||||
logMsg("✅ 任務完成");
|
26
style.css
Normal file
26
style.css
Normal file
@ -0,0 +1,26 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#log {
|
||||
white-space: pre-wrap;
|
||||
background: #f0f0f0;
|
||||
padding: 1em;
|
||||
border-radius: 8px;
|
||||
height: 600px;
|
||||
overflow-y: auto;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
label,
|
||||
input {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 1em;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user