Compare commits
7 Commits
v1.3.0
...
7af88328c8
| Author | SHA1 | Date | |
|---|---|---|---|
| 7af88328c8 | |||
| 668e7c34bb | |||
| e796b4f400 | |||
| a4b5c20993 | |||
| d17f364fc5 | |||
| 5f79eec3dd | |||
| ed98bb57c0 |
@@ -79,7 +79,29 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Gitea release
|
- name: Create Gitea release
|
||||||
run: |
|
run: |
|
||||||
python3 -c "import json,os; v=os.environ['VERSION']; img=os.environ['IMAGE']; notes=open('/tmp/release_notes.txt').read(); open('/tmp/release_body.json','w').write(json.dumps({'tag_name':'v'+v,'name':'Catalyst v'+v,'body':'### Changes\n\n'+notes+'\n\n### Image\n\n'+img+':'+v,'draft':False,'prerelease':False}))"
|
cat > /tmp/make_release.py << 'PYEOF'
|
||||||
|
import json, os
|
||||||
|
v = os.environ['VERSION']
|
||||||
|
img = os.environ['IMAGE']
|
||||||
|
raw = open('/tmp/release_notes.txt').read().strip()
|
||||||
|
feats, fixes = [], []
|
||||||
|
for line in raw.splitlines():
|
||||||
|
msg = line.lstrip('- ').strip()
|
||||||
|
if msg.startswith('feat:'):
|
||||||
|
feats.append('- ' + msg[5:].strip())
|
||||||
|
elif msg.startswith('fix:'):
|
||||||
|
fixes.append('- ' + msg[4:].strip())
|
||||||
|
sections = []
|
||||||
|
if feats:
|
||||||
|
sections.append('### New Features\n\n' + '\n'.join(feats))
|
||||||
|
if fixes:
|
||||||
|
sections.append('### Bug Fixes\n\n' + '\n'.join(fixes))
|
||||||
|
notes = '\n\n'.join(sections) or '_No changes_'
|
||||||
|
body = notes + '\n\n### Image\n\n' + img + ':' + v
|
||||||
|
payload = {'tag_name': 'v'+v, 'name': 'Catalyst v'+v, 'body': body, 'draft': False, 'prerelease': False}
|
||||||
|
open('/tmp/release_body.json', 'w').write(json.dumps(payload))
|
||||||
|
PYEOF
|
||||||
|
python3 /tmp/make_release.py
|
||||||
curl -sf -X POST \
|
curl -sf -X POST \
|
||||||
-H "Authorization: token ${{ secrets.TOKEN }}" \
|
-H "Authorization: token ${{ secrets.TOKEN }}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
|||||||
+52
@@ -712,3 +712,55 @@ select:focus { border-color: var(--accent); }
|
|||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0; }
|
50% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── MOBILE ── */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
/* Reset desktop zoom — mobile browsers handle scaling themselves */
|
||||||
|
html { zoom: 1; }
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
nav { padding: 0 16px; }
|
||||||
|
|
||||||
|
/* Dashboard header */
|
||||||
|
.dash-header { padding: 18px 16px 14px; }
|
||||||
|
|
||||||
|
/* Stats bar */
|
||||||
|
.stat-cell { padding: 10px 16px; }
|
||||||
|
|
||||||
|
/* Toolbar — search full-width on first row, filters + button below */
|
||||||
|
.toolbar { flex-wrap: wrap; padding: 10px 16px; gap: 8px; }
|
||||||
|
.search-wrap { max-width: 100%; }
|
||||||
|
.toolbar-right { margin-left: 0; width: 100%; justify-content: flex-end; }
|
||||||
|
|
||||||
|
/* Instance grid — single column */
|
||||||
|
.instance-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 12px 16px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detail page */
|
||||||
|
.detail-page { padding: 16px; }
|
||||||
|
|
||||||
|
/* Detail header — stack title block above actions */
|
||||||
|
.detail-header { flex-direction: column; align-items: flex-start; gap: 14px; }
|
||||||
|
|
||||||
|
/* Detail sub — wrap items when they don't fit */
|
||||||
|
.detail-sub { flex-wrap: wrap; row-gap: 4px; }
|
||||||
|
|
||||||
|
/* Detail grid — single column */
|
||||||
|
.detail-grid { grid-template-columns: 1fr; }
|
||||||
|
|
||||||
|
/* Toggle grid — 2 columns instead of 3 */
|
||||||
|
.toggle-grid { grid-template-columns: 1fr 1fr; }
|
||||||
|
|
||||||
|
/* Confirm box — no fixed width on mobile */
|
||||||
|
.confirm-box { width: auto; max-width: calc(100vw - 32px); padding: 18px; }
|
||||||
|
|
||||||
|
/* History timeline — stack timestamp above event */
|
||||||
|
.tl-item { flex-direction: column; align-items: flex-start; gap: 3px; }
|
||||||
|
.tl-time { order: -1; }
|
||||||
|
|
||||||
|
/* Toast — stretch across bottom */
|
||||||
|
.toast { right: 16px; left: 16px; bottom: 16px; }
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
const VERSION = "1.3.0";
|
const VERSION = "1.3.1";
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "catalyst",
|
"name": "catalyst",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server/server.js",
|
"start": "node server/server.js",
|
||||||
|
|||||||
+3
-1
@@ -151,11 +151,13 @@ export function updateInstance(vmid, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function deleteInstance(vmid) {
|
export function deleteInstance(vmid) {
|
||||||
return db.prepare('DELETE FROM instances WHERE vmid = ?').run(vmid);
|
db.prepare('DELETE FROM instance_history WHERE vmid = ?').run(vmid);
|
||||||
|
db.prepare('DELETE FROM instances WHERE vmid = ?').run(vmid);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importInstances(rows) {
|
export function importInstances(rows) {
|
||||||
db.exec('BEGIN');
|
db.exec('BEGIN');
|
||||||
|
db.exec('DELETE FROM instance_history');
|
||||||
db.exec('DELETE FROM instances');
|
db.exec('DELETE FROM instances');
|
||||||
const insert = db.prepare(`
|
const insert = db.prepare(`
|
||||||
INSERT INTO instances
|
INSERT INTO instances
|
||||||
|
|||||||
@@ -164,6 +164,19 @@ describe('deleteInstance', () => {
|
|||||||
expect(getInstance(1)).toBeNull();
|
expect(getInstance(1)).toBeNull();
|
||||||
expect(getInstance(2)).not.toBeNull();
|
expect(getInstance(2)).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clears history for the deleted instance', () => {
|
||||||
|
createInstance({ ...base, name: 'a', vmid: 1 });
|
||||||
|
deleteInstance(1);
|
||||||
|
expect(getInstanceHistory(1)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not clear history for other instances', () => {
|
||||||
|
createInstance({ ...base, name: 'a', vmid: 1 });
|
||||||
|
createInstance({ ...base, name: 'b', vmid: 2 });
|
||||||
|
deleteInstance(1);
|
||||||
|
expect(getInstanceHistory(2).length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── importInstances ───────────────────────────────────────────────────────────
|
// ── importInstances ───────────────────────────────────────────────────────────
|
||||||
@@ -183,6 +196,12 @@ describe('importInstances', () => {
|
|||||||
importInstances([]);
|
importInstances([]);
|
||||||
expect(getInstances()).toEqual([]);
|
expect(getInstances()).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clears history for all replaced instances', () => {
|
||||||
|
createInstance({ ...base, name: 'old', vmid: 1 });
|
||||||
|
importInstances([{ ...base, name: 'new', vmid: 2 }]);
|
||||||
|
expect(getInstanceHistory(1)).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── instance history ─────────────────────────────────────────────────────────
|
// ── instance history ─────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user