Initial commit
This commit is contained in:
commit
5ed52c279d
8
.claude/settings.local.json
Normal file
8
.claude/settings.local.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Skill(deep-research)",
|
||||
"Skill(deep-research:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
dist/
|
||||
*.vsix
|
||||
8
.vscodeignore
Normal file
8
.vscodeignore
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
src/**
|
||||
node_modules/**
|
||||
.vscode/**
|
||||
tsconfig.json
|
||||
esbuild.js
|
||||
**/*.map
|
||||
**/*.ts
|
||||
!dist/**
|
||||
47
esbuild.js
Normal file
47
esbuild.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const esbuild = require("esbuild");
|
||||
|
||||
const production = process.argv.includes("--production");
|
||||
const watch = process.argv.includes("--watch");
|
||||
|
||||
const ctx = esbuild.context({
|
||||
entryPoints: ["src/extension.ts"],
|
||||
bundle: true,
|
||||
format: "cjs",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
platform: "node",
|
||||
outfile: "dist/extension.js",
|
||||
external: ["vscode"],
|
||||
logLevel: "silent",
|
||||
plugins: [
|
||||
{
|
||||
name: "esbuild-problem-matcher",
|
||||
setup(build) {
|
||||
build.onStart(() => {
|
||||
console.log("[watch] build started");
|
||||
});
|
||||
build.onEnd((result) => {
|
||||
result.errors.forEach(({ text, location }) => {
|
||||
console.error(`✘ [ERROR] ${text}`);
|
||||
if (location) {
|
||||
console.error(
|
||||
` ${location.file}:${location.line}:${location.column}:`
|
||||
);
|
||||
}
|
||||
});
|
||||
console.log("[watch] build finished");
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
ctx.then(async (c) => {
|
||||
if (watch) {
|
||||
await c.watch();
|
||||
} else {
|
||||
await c.rebuild();
|
||||
await c.dispose();
|
||||
}
|
||||
});
|
||||
1
genieacs_ps_vscode_plugin
Submodule
1
genieacs_ps_vscode_plugin
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c1df3e5802601a5dbba0cc2d30889ebd41b4bceb
|
||||
7
info.env.example
Normal file
7
info.env.example
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# GenieACS connection settings
|
||||
GENIEACS_URL=http://localhost:7557
|
||||
GENIEACS_USER=admin
|
||||
GENIEACS_PASS=yourpassword
|
||||
|
||||
# Secret pushed into provisioning scripts by push-secret.js
|
||||
TPLG_SECRET=your-secret-here
|
||||
10
media/genieacs.svg
Normal file
10
media/genieacs.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
|
||||
<!-- Router/device icon representing ACS -->
|
||||
<rect x="1" y="5" width="14" height="7" rx="1.5" stroke="currentColor" stroke-width="1.2" fill="none"/>
|
||||
<circle cx="4" cy="8.5" r="1" fill="currentColor"/>
|
||||
<circle cx="7" cy="8.5" r="1" fill="currentColor"/>
|
||||
<circle cx="10" cy="8.5" r="1" fill="currentColor"/>
|
||||
<line x1="8" y1="5" x2="8" y2="3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<line x1="5.5" y1="4" x2="8" y2="3" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
|
||||
<line x1="10.5" y1="4" x2="8" y2="3" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 703 B |
489
package-lock.json
generated
Normal file
489
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
{
|
||||
"name": "genieacs-editor",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "genieacs-editor",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/vscode": "^1.85.0",
|
||||
"esbuild": "^0.20.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.85.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.125.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz",
|
||||
"integrity": "sha512-0icm/ZQAaism87P0ekHqi4/Ju9du+Tm0RUW+y7vqRsxY2cY0FNRX1nAnaW7nT6npPt2tfHiheZ55Zm9UhqonFA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.20.2",
|
||||
"@esbuild/android-arm": "0.20.2",
|
||||
"@esbuild/android-arm64": "0.20.2",
|
||||
"@esbuild/android-x64": "0.20.2",
|
||||
"@esbuild/darwin-arm64": "0.20.2",
|
||||
"@esbuild/darwin-x64": "0.20.2",
|
||||
"@esbuild/freebsd-arm64": "0.20.2",
|
||||
"@esbuild/freebsd-x64": "0.20.2",
|
||||
"@esbuild/linux-arm": "0.20.2",
|
||||
"@esbuild/linux-arm64": "0.20.2",
|
||||
"@esbuild/linux-ia32": "0.20.2",
|
||||
"@esbuild/linux-loong64": "0.20.2",
|
||||
"@esbuild/linux-mips64el": "0.20.2",
|
||||
"@esbuild/linux-ppc64": "0.20.2",
|
||||
"@esbuild/linux-riscv64": "0.20.2",
|
||||
"@esbuild/linux-s390x": "0.20.2",
|
||||
"@esbuild/linux-x64": "0.20.2",
|
||||
"@esbuild/netbsd-x64": "0.20.2",
|
||||
"@esbuild/openbsd-x64": "0.20.2",
|
||||
"@esbuild/sunos-x64": "0.20.2",
|
||||
"@esbuild/win32-arm64": "0.20.2",
|
||||
"@esbuild/win32-ia32": "0.20.2",
|
||||
"@esbuild/win32-x64": "0.20.2"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
153
package.json
Normal file
153
package.json
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
{
|
||||
"name": "genieacs-editor",
|
||||
"displayName": "GenieACS Script Editor",
|
||||
"description": "Connect to GenieACS, browse and edit provisioning scripts with full JavaScript support",
|
||||
"version": "0.1.0",
|
||||
"publisher": "targo",
|
||||
"engines": {
|
||||
"vscode": "^1.85.0"
|
||||
},
|
||||
"categories": ["Other"],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/targo/genieacs-editor"
|
||||
},
|
||||
"activationEvents": [],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"title": "GenieACS",
|
||||
"properties": {
|
||||
"genieacs.serverUrl": {
|
||||
"type": "string",
|
||||
"default": "http://localhost:7557",
|
||||
"description": "GenieACS NBI base URL (e.g. http://localhost:7557)"
|
||||
},
|
||||
"genieacs.username": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "GenieACS username (leave empty if no auth)"
|
||||
},
|
||||
"genieacs.password": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "GenieACS password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "genieacs-explorer",
|
||||
"title": "GenieACS",
|
||||
"icon": "media/genieacs.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"genieacs-explorer": [
|
||||
{
|
||||
"id": "genieacsScripts",
|
||||
"name": "Provisioning Scripts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "genieacs.refresh",
|
||||
"title": "Refresh",
|
||||
"icon": "$(refresh)",
|
||||
"category": "GenieACS"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.openScript",
|
||||
"title": "Open Script",
|
||||
"category": "GenieACS"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.newScript",
|
||||
"title": "New Script",
|
||||
"icon": "$(add)",
|
||||
"category": "GenieACS"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.deleteScript",
|
||||
"title": "Delete Script",
|
||||
"icon": "$(trash)",
|
||||
"category": "GenieACS"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.renameScript",
|
||||
"title": "Rename Script",
|
||||
"category": "GenieACS"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.configure",
|
||||
"title": "Configure Server",
|
||||
"icon": "$(settings-gear)",
|
||||
"category": "GenieACS"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.testConnection",
|
||||
"title": "Test Connection (Diagnostics)",
|
||||
"category": "GenieACS"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "genieacs.refresh",
|
||||
"when": "view == genieacsScripts",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.newScript",
|
||||
"when": "view == genieacsScripts",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.configure",
|
||||
"when": "view == genieacsScripts",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "genieacs.deleteScript",
|
||||
"when": "view == genieacsScripts && viewItem == script",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.renameScript",
|
||||
"when": "view == genieacsScripts && viewItem == script",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "genieacs.deleteScript",
|
||||
"when": "view == genieacsScripts && viewItem == script",
|
||||
"group": "1_modification"
|
||||
}
|
||||
]
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "genieacs-script",
|
||||
"aliases": ["GenieACS Script"],
|
||||
"extensions": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "npm run build",
|
||||
"build": "node esbuild.js --production",
|
||||
"watch": "node esbuild.js --watch",
|
||||
"compile": "tsc --noEmit",
|
||||
"package": "vsce package"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/vscode": "^1.85.0",
|
||||
"esbuild": "^0.20.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
256
src/extension.ts
Normal file
256
src/extension.ts
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
import * as vscode from "vscode";
|
||||
import { ScriptsProvider, ScriptItem } from "./scriptsProvider";
|
||||
import { ScriptFileSystemProvider } from "./scriptFileSystem";
|
||||
import { createScript, deleteScript, testConnection } from "./genieacsApi";
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// --- File system provider ---
|
||||
const fsProvider = new ScriptFileSystemProvider();
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.registerFileSystemProvider("genieacs", fsProvider, {
|
||||
isCaseSensitive: true,
|
||||
isReadonly: false,
|
||||
})
|
||||
);
|
||||
|
||||
// --- Tree view ---
|
||||
const scriptsProvider = new ScriptsProvider();
|
||||
const treeView = vscode.window.createTreeView("genieacsScripts", {
|
||||
treeDataProvider: scriptsProvider,
|
||||
showCollapseAll: false,
|
||||
});
|
||||
context.subscriptions.push(treeView);
|
||||
|
||||
// Trigger initial load
|
||||
scriptsProvider.reload();
|
||||
|
||||
// --- Commands ---
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("genieacs.refresh", () => {
|
||||
scriptsProvider.reload();
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"genieacs.openScript",
|
||||
async (item: ScriptItem) => {
|
||||
const uri = ScriptFileSystemProvider.uriForScript(item.script._id);
|
||||
try {
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
const editor = await vscode.window.showTextDocument(doc, {
|
||||
preview: false,
|
||||
});
|
||||
// Force JavaScript language mode for syntax highlighting + IntelliSense
|
||||
await vscode.languages.setTextDocumentLanguage(doc, "javascript");
|
||||
// Scroll to top
|
||||
editor.revealRange(new vscode.Range(0, 0, 0, 0));
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to open script: ${err instanceof Error ? err.message : String(err)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("genieacs.newScript", async () => {
|
||||
const id = await vscode.window.showInputBox({
|
||||
prompt: "Enter a name for the new script",
|
||||
placeHolder: "e.g. provision-model-xyz",
|
||||
validateInput: (v) =>
|
||||
v.trim() ? undefined : "Script name cannot be empty",
|
||||
});
|
||||
if (!id) return;
|
||||
|
||||
const trimmed = id.trim();
|
||||
const initial = [
|
||||
`"use strict";`,
|
||||
``,
|
||||
`// GenieACS provisioning script: ${trimmed}`,
|
||||
`// Available globals: declare, commit, log`,
|
||||
``,
|
||||
].join("\n");
|
||||
|
||||
try {
|
||||
await createScript(trimmed, initial);
|
||||
scriptsProvider.reload();
|
||||
|
||||
// Open the newly created script
|
||||
const uri = ScriptFileSystemProvider.uriForScript(trimmed);
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
await vscode.window.showTextDocument(doc, { preview: false });
|
||||
await vscode.languages.setTextDocumentLanguage(doc, "javascript");
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to create script: ${err instanceof Error ? err.message : String(err)}`
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"genieacs.deleteScript",
|
||||
async (item: ScriptItem) => {
|
||||
const answer = await vscode.window.showWarningMessage(
|
||||
`Delete script "${item.script._id}"? This cannot be undone.`,
|
||||
{ modal: true },
|
||||
"Delete"
|
||||
);
|
||||
if (answer !== "Delete") return;
|
||||
|
||||
try {
|
||||
await deleteScript(item.script._id);
|
||||
// Close the open editor for this script if it exists
|
||||
const uri = ScriptFileSystemProvider.uriForScript(item.script._id);
|
||||
fsProvider.invalidate(item.script._id);
|
||||
for (const editor of vscode.window.visibleTextEditors) {
|
||||
if (editor.document.uri.toString() === uri.toString()) {
|
||||
await vscode.window.showTextDocument(editor.document, {
|
||||
viewColumn: editor.viewColumn,
|
||||
});
|
||||
await vscode.commands.executeCommand(
|
||||
"workbench.action.closeActiveEditor"
|
||||
);
|
||||
}
|
||||
}
|
||||
scriptsProvider.reload();
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to delete script: ${err instanceof Error ? err.message : String(err)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
"genieacs.renameScript",
|
||||
async (item: ScriptItem) => {
|
||||
const newId = await vscode.window.showInputBox({
|
||||
prompt: "Enter new name for the script",
|
||||
value: item.script._id,
|
||||
validateInput: (v) =>
|
||||
v.trim() ? undefined : "Script name cannot be empty",
|
||||
});
|
||||
if (!newId || newId.trim() === item.script._id) return;
|
||||
|
||||
try {
|
||||
// GenieACS has no rename endpoint — copy then delete
|
||||
await createScript(newId.trim(), item.script.script);
|
||||
await deleteScript(item.script._id);
|
||||
|
||||
fsProvider.invalidate(item.script._id);
|
||||
const oldUri = ScriptFileSystemProvider.uriForScript(item.script._id);
|
||||
for (const editor of vscode.window.visibleTextEditors) {
|
||||
if (editor.document.uri.toString() === oldUri.toString()) {
|
||||
await vscode.window.showTextDocument(editor.document, {
|
||||
viewColumn: editor.viewColumn,
|
||||
});
|
||||
await vscode.commands.executeCommand(
|
||||
"workbench.action.closeActiveEditor"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
scriptsProvider.reload();
|
||||
|
||||
// Open renamed script
|
||||
const uri = ScriptFileSystemProvider.uriForScript(newId.trim());
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
await vscode.window.showTextDocument(doc, { preview: false });
|
||||
await vscode.languages.setTextDocumentLanguage(doc, "javascript");
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to rename script: ${err instanceof Error ? err.message : String(err)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("genieacs.testConnection", async () => {
|
||||
const result = await testConnection();
|
||||
const panel = vscode.window.createOutputChannel("GenieACS Diagnostics");
|
||||
panel.clear();
|
||||
panel.appendLine("=== GenieACS Connection Diagnostics ===");
|
||||
panel.appendLine(`URL: ${result.url}`);
|
||||
panel.appendLine(`Status: ${result.status ?? "N/A"}`);
|
||||
panel.appendLine(`Error: ${result.error ?? "none"}`);
|
||||
panel.appendLine(`Body: ${result.body ?? "(empty)"}`);
|
||||
panel.show();
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("genieacs.configure", async () => {
|
||||
const url = await vscode.window.showInputBox({
|
||||
prompt: "GenieACS NBI URL",
|
||||
value:
|
||||
vscode.workspace
|
||||
.getConfiguration("genieacs")
|
||||
.get<string>("serverUrl") || "http://localhost:7557",
|
||||
placeHolder: "http://localhost:7557",
|
||||
});
|
||||
if (url === undefined) return;
|
||||
|
||||
const username = await vscode.window.showInputBox({
|
||||
prompt: "Username (leave empty if no authentication)",
|
||||
value:
|
||||
vscode.workspace
|
||||
.getConfiguration("genieacs")
|
||||
.get<string>("username") || "",
|
||||
});
|
||||
if (username === undefined) return;
|
||||
|
||||
let password = "";
|
||||
if (username) {
|
||||
const pw = await vscode.window.showInputBox({
|
||||
prompt: "Password",
|
||||
password: true,
|
||||
value:
|
||||
vscode.workspace
|
||||
.getConfiguration("genieacs")
|
||||
.get<string>("password") || "",
|
||||
});
|
||||
if (pw === undefined) return;
|
||||
password = pw;
|
||||
}
|
||||
|
||||
const cfg = vscode.workspace.getConfiguration("genieacs");
|
||||
await cfg.update("serverUrl", url, vscode.ConfigurationTarget.Global);
|
||||
await cfg.update("username", username, vscode.ConfigurationTarget.Global);
|
||||
await cfg.update("password", password, vscode.ConfigurationTarget.Global);
|
||||
|
||||
vscode.window.showInformationMessage("GenieACS configuration saved.");
|
||||
scriptsProvider.reload();
|
||||
})
|
||||
);
|
||||
|
||||
// Auto-save: when a genieacs: document is saved, write back to server
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidSaveTextDocument(async (doc) => {
|
||||
if (doc.uri.scheme !== "genieacs") return;
|
||||
// writeFile is triggered by VS Code automatically through the filesystem provider
|
||||
// This handler is just for user feedback
|
||||
vscode.window.setStatusBarMessage("$(check) Saved to GenieACS", 3000);
|
||||
})
|
||||
);
|
||||
|
||||
// When a genieacs: document is opened, ensure JS language mode
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidOpenTextDocument(async (doc) => {
|
||||
if (doc.uri.scheme !== "genieacs") return;
|
||||
if (doc.languageId !== "javascript") {
|
||||
await vscode.languages.setTextDocumentLanguage(doc, "javascript");
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
122
src/genieacsApi.ts
Normal file
122
src/genieacsApi.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import * as https from "https";
|
||||
import * as http from "http";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
export interface GenieScript {
|
||||
_id: string;
|
||||
script: string;
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
const cfg = vscode.workspace.getConfiguration("genieacs");
|
||||
return {
|
||||
serverUrl: (cfg.get<string>("serverUrl") || "http://localhost:7557").replace(/\/$/, ""),
|
||||
username: cfg.get<string>("username") || "",
|
||||
password: cfg.get<string>("password") || "",
|
||||
};
|
||||
}
|
||||
|
||||
function request(
|
||||
method: string,
|
||||
path: string,
|
||||
body?: string,
|
||||
contentType = "application/json"
|
||||
): Promise<{ status: number; data: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { serverUrl, username, password } = getConfig();
|
||||
const url = new URL(serverUrl + path);
|
||||
const isHttps = url.protocol === "https:";
|
||||
const lib = isHttps ? https : http;
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": contentType,
|
||||
};
|
||||
|
||||
if (username) {
|
||||
const encoded = Buffer.from(`${username}:${password}`).toString("base64");
|
||||
headers["Authorization"] = `Basic ${encoded}`;
|
||||
}
|
||||
|
||||
const options: http.RequestOptions = {
|
||||
hostname: url.hostname,
|
||||
port: url.port || (isHttps ? 443 : 80),
|
||||
path: url.pathname + url.search,
|
||||
method,
|
||||
headers,
|
||||
};
|
||||
|
||||
const req = lib.request(options, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => resolve({ status: res.statusCode ?? 0, data }));
|
||||
});
|
||||
|
||||
req.on("error", reject);
|
||||
|
||||
if (body) req.write(body);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export async function listScripts(): Promise<GenieScript[]> {
|
||||
const res = await request("GET", "/provisions?limit=1000");
|
||||
if (res.status !== 200) {
|
||||
throw new Error(`Failed to list provisions (HTTP ${res.status}): ${res.data}`);
|
||||
}
|
||||
return JSON.parse(res.data) as GenieScript[];
|
||||
}
|
||||
|
||||
export async function getScript(id: string): Promise<GenieScript> {
|
||||
// GenieACS NBI does not have a single-item GET for provisions;
|
||||
// filter the list by _id instead.
|
||||
const res = await request(
|
||||
"GET",
|
||||
`/provisions?query=${encodeURIComponent(JSON.stringify({ _id: id }))}`
|
||||
);
|
||||
if (res.status !== 200) {
|
||||
throw new Error(`Failed to get provision "${id}" (HTTP ${res.status}): ${res.data}`);
|
||||
}
|
||||
const list = JSON.parse(res.data) as GenieScript[];
|
||||
if (list.length === 0) throw new Error(`Provision "${id}" not found`);
|
||||
return list[0];
|
||||
}
|
||||
|
||||
export async function saveScript(id: string, content: string): Promise<void> {
|
||||
// GenieACS expects the raw script text as the body, not JSON-wrapped
|
||||
const res = await request(
|
||||
"PUT",
|
||||
`/provisions/${encodeURIComponent(id)}`,
|
||||
content,
|
||||
"application/javascript"
|
||||
);
|
||||
if (res.status !== 200 && res.status !== 201 && res.status !== 204) {
|
||||
throw new Error(`Failed to save provision "${id}" (HTTP ${res.status}): ${res.data}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createScript(id: string, content: string): Promise<void> {
|
||||
await saveScript(id, content);
|
||||
}
|
||||
|
||||
export async function deleteScript(id: string): Promise<void> {
|
||||
const res = await request("DELETE", `/provisions/${encodeURIComponent(id)}`);
|
||||
if (res.status !== 200 && res.status !== 204) {
|
||||
throw new Error(`Failed to delete provision "${id}" (HTTP ${res.status}): ${res.data}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function testConnection(): Promise<{
|
||||
url: string;
|
||||
status?: number;
|
||||
body?: string;
|
||||
error?: string;
|
||||
}> {
|
||||
const { serverUrl } = getConfig();
|
||||
const url = `${serverUrl}/provisions?limit=5`;
|
||||
try {
|
||||
const res = await request("GET", "/provisions?limit=5");
|
||||
return { url, status: res.status, body: res.data.slice(0, 500) };
|
||||
} catch (err) {
|
||||
return { url, error: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
89
src/scriptFileSystem.ts
Normal file
89
src/scriptFileSystem.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import * as vscode from "vscode";
|
||||
import { getScript, saveScript } from "./genieacsApi";
|
||||
|
||||
/**
|
||||
* Virtual filesystem for GenieACS scripts.
|
||||
* URIs look like: genieacs:/scripts/<scriptId>.js
|
||||
*
|
||||
* Opening a URI fetches the script from the API and presents it as a JS file.
|
||||
* Saving writes back to the API.
|
||||
*/
|
||||
export class ScriptFileSystemProvider implements vscode.FileSystemProvider {
|
||||
private _onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
|
||||
readonly onDidChangeFile = this._onDidChangeFile.event;
|
||||
|
||||
// In-memory cache: scriptId → { content, mtime }
|
||||
private cache = new Map<string, { content: Uint8Array; mtime: number }>();
|
||||
|
||||
static scriptIdFromUri(uri: vscode.Uri): string {
|
||||
// path is /scripts/<id>.js → strip prefix and .js suffix
|
||||
const match = uri.path.match(/^\/scripts\/(.+)\.js$/);
|
||||
if (!match) throw new Error(`Unexpected URI path: ${uri.path}`);
|
||||
return decodeURIComponent(match[1]);
|
||||
}
|
||||
|
||||
static uriForScript(id: string): vscode.Uri {
|
||||
return vscode.Uri.parse(`genieacs:/scripts/${encodeURIComponent(id)}.js`);
|
||||
}
|
||||
|
||||
watch(): vscode.Disposable {
|
||||
return { dispose: () => {} };
|
||||
}
|
||||
|
||||
stat(uri: vscode.Uri): vscode.FileStat {
|
||||
const id = ScriptFileSystemProvider.scriptIdFromUri(uri);
|
||||
const cached = this.cache.get(id);
|
||||
return {
|
||||
type: vscode.FileType.File,
|
||||
ctime: 0,
|
||||
mtime: cached?.mtime ?? 0,
|
||||
size: cached?.content.byteLength ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
readDirectory(): never {
|
||||
throw vscode.FileSystemError.NoPermissions("readDirectory not supported");
|
||||
}
|
||||
|
||||
createDirectory(): never {
|
||||
throw vscode.FileSystemError.NoPermissions("createDirectory not supported");
|
||||
}
|
||||
|
||||
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
|
||||
const id = ScriptFileSystemProvider.scriptIdFromUri(uri);
|
||||
const script = await getScript(id);
|
||||
const content = Buffer.from(script.script ?? "", "utf8");
|
||||
this.cache.set(id, { content, mtime: Date.now() });
|
||||
return content;
|
||||
}
|
||||
|
||||
async writeFile(
|
||||
uri: vscode.Uri,
|
||||
content: Uint8Array,
|
||||
_options: { create: boolean; overwrite: boolean }
|
||||
): Promise<void> {
|
||||
const id = ScriptFileSystemProvider.scriptIdFromUri(uri);
|
||||
const text = Buffer.from(content).toString("utf8");
|
||||
await saveScript(id, text);
|
||||
|
||||
const mtime = Date.now();
|
||||
this.cache.set(id, { content, mtime });
|
||||
this._onDidChangeFile.fire([{ type: vscode.FileChangeType.Changed, uri }]);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
const id = ScriptFileSystemProvider.scriptIdFromUri(uri);
|
||||
this.cache.delete(id);
|
||||
}
|
||||
|
||||
rename(): never {
|
||||
throw vscode.FileSystemError.NoPermissions(
|
||||
"Rename via filesystem not supported — use the tree view context menu"
|
||||
);
|
||||
}
|
||||
|
||||
/** Evict a cached entry so the next open re-fetches from the server. */
|
||||
invalidate(id: string): void {
|
||||
this.cache.delete(id);
|
||||
}
|
||||
}
|
||||
79
src/scriptsProvider.ts
Normal file
79
src/scriptsProvider.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import * as vscode from "vscode";
|
||||
import { GenieScript, listScripts } from "./genieacsApi";
|
||||
|
||||
export class ScriptItem extends vscode.TreeItem {
|
||||
constructor(
|
||||
public readonly script: GenieScript,
|
||||
public readonly collapsibleState = vscode.TreeItemCollapsibleState.None
|
||||
) {
|
||||
super(script._id, collapsibleState);
|
||||
this.tooltip = `GenieACS provisioning script: ${script._id}`;
|
||||
this.description = "";
|
||||
this.contextValue = "script";
|
||||
this.command = {
|
||||
command: "genieacs.openScript",
|
||||
title: "Open Script",
|
||||
arguments: [this],
|
||||
};
|
||||
this.iconPath = new vscode.ThemeIcon("symbol-file");
|
||||
}
|
||||
}
|
||||
|
||||
export class ScriptsProvider implements vscode.TreeDataProvider<ScriptItem> {
|
||||
private _onDidChangeTreeData = new vscode.EventEmitter<ScriptItem | undefined | void>();
|
||||
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||
|
||||
private scripts: GenieScript[] = [];
|
||||
private loading = false;
|
||||
private lastError: string | null = null;
|
||||
|
||||
refresh(): void {
|
||||
this.scripts = [];
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
async reload(): Promise<void> {
|
||||
this.loading = true;
|
||||
this.lastError = null;
|
||||
this._onDidChangeTreeData.fire();
|
||||
|
||||
try {
|
||||
this.scripts = await listScripts();
|
||||
this.scripts.sort((a, b) => a._id.localeCompare(b._id));
|
||||
} catch (err) {
|
||||
this.lastError = err instanceof Error ? err.message : String(err);
|
||||
this.scripts = [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
}
|
||||
|
||||
getTreeItem(element: ScriptItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
async getChildren(): Promise<ScriptItem[]> {
|
||||
if (this.loading) {
|
||||
const item = new vscode.TreeItem("Loading scripts...");
|
||||
item.iconPath = new vscode.ThemeIcon("loading~spin");
|
||||
return [item as unknown as ScriptItem];
|
||||
}
|
||||
|
||||
if (this.lastError) {
|
||||
const item = new vscode.TreeItem("Connection failed — click to configure");
|
||||
item.iconPath = new vscode.ThemeIcon("error");
|
||||
item.command = { command: "genieacs.configure", title: "Configure" };
|
||||
item.tooltip = this.lastError;
|
||||
return [item as unknown as ScriptItem];
|
||||
}
|
||||
|
||||
if (this.scripts.length === 0) {
|
||||
const item = new vscode.TreeItem("No scripts found");
|
||||
item.iconPath = new vscode.ThemeIcon("info");
|
||||
return [item as unknown as ScriptItem];
|
||||
}
|
||||
|
||||
return this.scripts.map((s) => new ScriptItem(s));
|
||||
}
|
||||
}
|
||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user