fbf40f75b2
- petrol watch - copilot usage Signed-off-by: Peter Siegmund <developer@mars3142.org>
176 lines
4.7 KiB
JavaScript
176 lines
4.7 KiB
JavaScript
class CopilotAction {
|
||
constructor($UD, context) {
|
||
this.$UD = $UD;
|
||
this.context = context;
|
||
this.config = {
|
||
url: '',
|
||
refreshRate: '4',
|
||
};
|
||
this.refreshTimer = null;
|
||
this.debounceTimer = 0;
|
||
}
|
||
|
||
setActive() {}
|
||
|
||
onClear() {
|
||
if (this.refreshTimer) {
|
||
clearInterval(this.refreshTimer);
|
||
this.refreshTimer = null;
|
||
}
|
||
if (this.debounceTimer) {
|
||
clearTimeout(this.debounceTimer);
|
||
this.debounceTimer = 0;
|
||
}
|
||
}
|
||
|
||
setParams(jsn) {
|
||
this.config = Object.assign(this.config, (jsn && jsn.param) || {});
|
||
this.start();
|
||
}
|
||
|
||
onRun() {
|
||
this.fetchData();
|
||
}
|
||
|
||
start() {
|
||
this.onClear();
|
||
this.fetchData();
|
||
const duration = this.getRefreshDuration();
|
||
if (duration > 0) {
|
||
this.refreshTimer = setInterval(() => this.fetchData(), duration * 60 * 1000);
|
||
}
|
||
}
|
||
|
||
async fetchData() {
|
||
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
||
this.debounceTimer = setTimeout(async () => {
|
||
if (!this.config.url) {
|
||
this.renderGauge(null);
|
||
return;
|
||
}
|
||
try {
|
||
const response = await fetch(this.config.url);
|
||
const text = await response.text();
|
||
const value = this.parsePrometheus(text);
|
||
this.renderGauge(value !== null ? parseFloat(value) : null);
|
||
} catch (e) {
|
||
this.renderGauge(null);
|
||
}
|
||
}, 300);
|
||
}
|
||
|
||
parsePrometheus(text) {
|
||
for (const line of text.split('\n')) {
|
||
const trimmed = line.trim();
|
||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||
if (trimmed.startsWith('github_copilot_usage_percentage')) {
|
||
const closingBrace = trimmed.indexOf('}');
|
||
if (closingBrace !== -1) return trimmed.slice(closingBrace + 1).trim();
|
||
// no labels
|
||
const parts = trimmed.split(/\s+/);
|
||
if (parts.length >= 2) return parts[1];
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
renderGauge(value) {
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = 196;
|
||
canvas.height = 196;
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
ctx.fillStyle = '#000000';
|
||
ctx.fillRect(0, 0, 196, 196);
|
||
|
||
const cx = 98, cy = 100;
|
||
const r = 68;
|
||
const lineWidth = 14;
|
||
|
||
// Gauge runs from 225° to 315° clockwise (270° sweep)
|
||
const startAngle = 135 * Math.PI / 180;
|
||
const totalSweep = 270 * Math.PI / 180;
|
||
const greenEnd = startAngle + 0.50 * totalSweep; // 50%
|
||
const yellowEnd = startAngle + 0.80 * totalSweep; // 80%
|
||
const endAngle = startAngle + totalSweep; // 100%
|
||
|
||
// Background track
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, r, startAngle, endAngle, false);
|
||
ctx.strokeStyle = '#333333';
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.lineCap = 'round';
|
||
ctx.stroke();
|
||
|
||
if (value !== null) {
|
||
const pct = Math.max(0, Math.min(100, value)) / 100;
|
||
const valueAngle = startAngle + pct * totalSweep;
|
||
|
||
// Green segment (0–50%)
|
||
if (pct > 0) {
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, r, startAngle, Math.min(valueAngle, greenEnd), false);
|
||
ctx.strokeStyle = '#4caf50';
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.lineCap = 'round';
|
||
ctx.stroke();
|
||
}
|
||
|
||
// Yellow segment (50–80%)
|
||
if (pct > 0.5) {
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, r, greenEnd, Math.min(valueAngle, yellowEnd), false);
|
||
ctx.strokeStyle = '#ffeb3b';
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.lineCap = 'butt';
|
||
ctx.stroke();
|
||
}
|
||
|
||
// Red segment (80–100%)
|
||
if (pct > 0.8) {
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, r, yellowEnd, valueAngle, false);
|
||
ctx.strokeStyle = '#f44336';
|
||
ctx.lineWidth = lineWidth;
|
||
ctx.lineCap = 'butt';
|
||
ctx.stroke();
|
||
}
|
||
|
||
// Value text — centered, colored
|
||
const color = pct <= 0.5 ? '#4caf50' : pct <= 0.8 ? '#ffeb3b' : '#f44336';
|
||
const displayText = value.toFixed(1) + '%';
|
||
const fSize = displayText.length > 6 ? 28 : 34;
|
||
ctx.fillStyle = color;
|
||
ctx.font = `bold ${fSize}px "Source Han Sans SC"`;
|
||
ctx.textBaseline = 'middle';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(displayText, cx, cy + 8);
|
||
} else {
|
||
ctx.fillStyle = '#666666';
|
||
ctx.font = 'bold 28px "Source Han Sans SC"';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('N/A', cx, cy + 8);
|
||
}
|
||
|
||
// Label — bottom
|
||
ctx.fillStyle = '#7ec8e3';
|
||
ctx.font = 'bold 20px "Source Han Sans SC"';
|
||
ctx.fillText('Copilot', cx, 165);
|
||
|
||
this.$UD.setBaseDataIcon(this.context, canvas.toDataURL('image/png'));
|
||
}
|
||
|
||
getRefreshDuration() {
|
||
switch (this.config.refreshRate) {
|
||
case '1': return 1;
|
||
case '2': return 2;
|
||
case '3': return 5;
|
||
case '4': return 10;
|
||
case '5': return 30;
|
||
case '6': return 60;
|
||
default: return 0;
|
||
}
|
||
}
|
||
}
|