gitea pr approval request counter
Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
+5
-1
@@ -14,6 +14,10 @@
|
||||
"Gitea URL": "Gitea URL",
|
||||
"Owner": "Eigentümer",
|
||||
"Repository": "Repository",
|
||||
"API Token": "API Token"
|
||||
"API Token": "API Token",
|
||||
"PR Filter": "PR-Filter",
|
||||
"Assigned to me": "Mir zugewiesen",
|
||||
"Review requested": "Review angefordert",
|
||||
"Both": "Beides"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
"Gitea URL": "Gitea URL",
|
||||
"Owner": "Owner",
|
||||
"Repository": "Repository",
|
||||
"API Token": "API Token"
|
||||
"API Token": "API Token",
|
||||
"PR Filter": "PR Filter",
|
||||
"Assigned to me": "Assigned to me",
|
||||
"Review requested": "Review requested",
|
||||
"Both": "Both"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,19 @@
|
||||
],
|
||||
"Tooltip": "Shows the latest Gitea Actions run status for a repository",
|
||||
"UUID": "dev.mars3142.ulanzideck.collection.gitea"
|
||||
},
|
||||
{
|
||||
"Name": "Gitea Pull Requests",
|
||||
"Icon": "assets/icons/gitea.png",
|
||||
"PropertyInspectorPath": "property-inspector/giteapr/inspector.html",
|
||||
"States": [
|
||||
{
|
||||
"Name": "Default",
|
||||
"Image": "assets/icons/gitea.png"
|
||||
}
|
||||
],
|
||||
"Tooltip": "Shows open pull requests assigned to you across all repositories",
|
||||
"UUID": "dev.mars3142.ulanzideck.collection.giteapr"
|
||||
}
|
||||
],
|
||||
"OS": [
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
class GiteaPRAction extends ActionBase {
|
||||
constructor($UD, context) {
|
||||
super($UD, context);
|
||||
this.config = {
|
||||
url: '',
|
||||
token: '',
|
||||
filter: 'review_requested',
|
||||
refreshRate: '4',
|
||||
};
|
||||
}
|
||||
|
||||
setActive() {}
|
||||
|
||||
setParams(jsn) {
|
||||
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
|
||||
this.startTimer(() => this.fetchPRs(), this.config.refreshRate);
|
||||
}
|
||||
|
||||
onRun() {
|
||||
if (this.config.url) {
|
||||
this.$UD.openUrl(`${this.config.url}/pulls?type=your_repositories&sort=&state=open&q=`);
|
||||
}
|
||||
}
|
||||
|
||||
fetchPRs() {
|
||||
this.debounce(async () => {
|
||||
if (!this.config.url || !this.config.token) {
|
||||
this.renderButton(null, false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const headers = { Authorization: `token ${this.config.token}` };
|
||||
const filter = this.config.filter || 'review_requested';
|
||||
const base = `${this.config.url}/api/v1/repos/issues/search?type=pullrequest&state=open&limit=50`;
|
||||
|
||||
const fetchIds = async (url) => {
|
||||
const r = await fetch(url, { headers, signal: AbortSignal.timeout(8000) });
|
||||
console.log('GiteaPR status:', r.status, url);
|
||||
if (!r.ok) {
|
||||
const body = await r.text();
|
||||
console.log('GiteaPR error body:', body);
|
||||
return null;
|
||||
}
|
||||
const data = await r.json();
|
||||
return Array.isArray(data) ? data.map(i => i.id) : [];
|
||||
};
|
||||
|
||||
let ids;
|
||||
if (filter === 'both') {
|
||||
const [a, b] = await Promise.all([
|
||||
fetchIds(`${base}&assigned=true`),
|
||||
fetchIds(`${base}&review_requested=true`),
|
||||
]);
|
||||
if (a === null || b === null) { this.renderButton(null, true); return; }
|
||||
ids = [...new Set([...a, ...b])];
|
||||
} else {
|
||||
const param = filter === 'assigned' ? '&assigned=true' : '&review_requested=true';
|
||||
ids = await fetchIds(`${base}${param}`);
|
||||
if (ids === null) { this.renderButton(null, true); return; }
|
||||
}
|
||||
|
||||
this.renderButton(ids.length, false);
|
||||
} catch (e) {
|
||||
// timeout or network error = VPN likely down
|
||||
this.renderButton(null, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderButton(count, isError) {
|
||||
const { canvas, ctx } = this.createCanvas();
|
||||
|
||||
// Label — top
|
||||
ctx.fillStyle = '#7ec8e3';
|
||||
ctx.font = 'bold 22px "Source Han Sans SC"';
|
||||
ctx.fillText('Pull Requests', 98, 30);
|
||||
|
||||
if (count === null) {
|
||||
// No connection — show plug/disconnected icon
|
||||
ctx.fillStyle = isError ? '#ff6b6b' : '#888888';
|
||||
ctx.font = '70px "Source Han Sans SC"';
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
const bm = ctx.measureText('⚡');
|
||||
ctx.fillText('⚡', 98, 98 + (bm.actualBoundingBoxAscent - bm.actualBoundingBoxDescent) / 2);
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
ctx.fillStyle = isError ? '#ff6b6b' : '#888888';
|
||||
ctx.font = 'bold 22px "Source Han Sans SC"';
|
||||
ctx.fillText(isError ? 'API Error' : 'Offline', 98, 166);
|
||||
} else {
|
||||
// Count — large center number (visually centered using actual bounding box)
|
||||
const color = count === 0 ? '#6bff6b' : count < 5 ? '#f0c040' : '#ff6b6b';
|
||||
ctx.fillStyle = color;
|
||||
const fSize = count > 99 ? 60 : count > 9 ? 80 : 96;
|
||||
ctx.font = `bold ${fSize}px "Source Han Sans SC"`;
|
||||
const text = count > 999 ? '999+' : String(count);
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
const m = ctx.measureText(text);
|
||||
ctx.fillText(text, 98, 98 + (m.actualBoundingBoxAscent - m.actualBoundingBoxDescent) / 2);
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// Subtext
|
||||
const filter = this.config.filter || 'review_requested';
|
||||
const label = filter === 'assigned' ? 'assigned' : filter === 'review_requested' ? 'review req.' : 'open';
|
||||
ctx.fillStyle = '#888888';
|
||||
ctx.font = '18px "Source Han Sans SC"';
|
||||
ctx.fillText(label, 98, 166);
|
||||
}
|
||||
|
||||
this.setIcon(canvas);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
<script src="./actions/PetrolAction.js"></script>
|
||||
<script src="./actions/CopilotAction.js"></script>
|
||||
<script src="./actions/GiteaAction.js"></script>
|
||||
<script src="./actions/GiteaPRAction.js"></script>
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -17,6 +17,8 @@ $UD.onAdd(jsn => {
|
||||
ACTION_CACHES[context] = new CopilotAction($UD, context);
|
||||
} else if (name === 'gitea') {
|
||||
ACTION_CACHES[context] = new GiteaAction($UD, context);
|
||||
} else if (name === 'giteapr') {
|
||||
ACTION_CACHES[context] = new GiteaPRAction($UD, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
<title>Gitea PRs</title>
|
||||
<link rel="stylesheet" href="../../libs/css/uspi.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="uspi-wrapper">
|
||||
<form id="property-inspector">
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Gitea URL</div>
|
||||
<input class="uspi-item-value" type="text" name="url" placeholder="https://git.example.com" />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>API Token</div>
|
||||
<input class="uspi-item-value" type="password" name="token" placeholder="gitea_token..." />
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>PR Filter</div>
|
||||
<select class="uspi-item-value" name="filter">
|
||||
<option value="assigned" data-localize>Assigned to me</option>
|
||||
<option value="review_requested" selected data-localize>Review requested</option>
|
||||
<option value="both" data-localize>Both</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="uspi-item">
|
||||
<div class="uspi-item-label" data-localize>Refresh Rate</div>
|
||||
<select class="uspi-item-value" name="refreshRate">
|
||||
<option value="0" data-localize>On Press</option>
|
||||
<option value="1" data-localize>Every 1 min</option>
|
||||
<option value="2" data-localize>Every 2 min</option>
|
||||
<option value="3" data-localize>Every 5 min</option>
|
||||
<option value="4" selected data-localize>Every 10 min</option>
|
||||
<option value="5" data-localize>Every 30 min</option>
|
||||
<option value="6" data-localize>Every Hour</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="../../libs/js/constants.js"></script>
|
||||
<script src="../../libs/js/eventEmitter.js"></script>
|
||||
<script src="../../libs/js/utils.js"></script>
|
||||
<script src="../../libs/js/ulanzideckApi.js"></script>
|
||||
<script src="./inspector.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
let ACTION_SETTING = {};
|
||||
let form = '';
|
||||
|
||||
$UD.connect('dev.mars3142.ulanzideck.collection.giteapr');
|
||||
|
||||
$UD.onConnected(() => {
|
||||
form = document.querySelector('#property-inspector');
|
||||
|
||||
form.addEventListener('input', Utils.debounce(() => {
|
||||
const value = Utils.getFormValue(form);
|
||||
ACTION_SETTING = { ...ACTION_SETTING, ...value };
|
||||
$UD.sendParamFromPlugin(ACTION_SETTING);
|
||||
}));
|
||||
});
|
||||
|
||||
$UD.onAdd(jsn => {
|
||||
if (jsn && jsn.param) settingSaveParam(jsn.param);
|
||||
});
|
||||
|
||||
$UD.onParamFromApp(jsn => {
|
||||
settingSaveParam((jsn && jsn.param) || {});
|
||||
});
|
||||
|
||||
$UD.onParamFromPlugin(jsn => {
|
||||
settingSaveParam((jsn && jsn.param) || {});
|
||||
});
|
||||
|
||||
function settingSaveParam(params) {
|
||||
ACTION_SETTING = { ...ACTION_SETTING, ...params };
|
||||
if (form) Utils.setFormValue(ACTION_SETTING, form);
|
||||
}
|
||||
Reference in New Issue
Block a user