@daemonitor/plugins 1.0.3 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
- export { PluginConfigProvider } from "./lib/PluginConfigProvider.js";
2
- export { BasePlugin } from "./lib/BasePlugin.js";
3
- export { MonitoringPlugin } from "./lib/MonitoringPlugin.js";
4
- export { PluginLoader } from "./lib/PluginLoader.js";
5
- export { PluginManager } from "./lib/PluginManager.js";
6
- export * as Plugins from "./plugins/index.js";
1
+ // Export core plugin utilities
2
+ export { PluginConfigProvider } from "./lib/PluginConfigProvider";
3
+ export { PluginManager } from "./lib/PluginManager";
4
+ export { PluginLoader } from "./lib/PluginLoader";
5
+ // Only export creation functions, not types
6
+ export { createBasePlugin } from "./lib/BasePlugin";
7
+ export { createMonitoringPlugin } from "./lib/MonitoringPlugin";
8
+ // Export the PM2 plugin factory function
9
+ export { createPM2Plugin } from "./plugins/monitoring/PM2Plugin";
@@ -1,17 +1,27 @@
1
- import { PluginConfigProvider } from "./PluginConfigProvider.js";
2
- class BasePlugin {
3
- static currentPluginIndex = 0;
4
- alias;
5
- name;
6
- description;
7
- config;
8
- constructor(alias, name, description) {
9
- this.config = PluginConfigProvider.get(alias);
10
- this.name = name;
11
- this.description = description;
12
- }
13
- getName() {
14
- return this.name;
15
- }
1
+ import { PluginConfigProvider } from "./PluginConfigProvider";
2
+ let currentPluginIndex = 0;
3
+ export function createBasePlugin(alias, name, description, setupFn, monitorFn, refreshFn, teardownFn) {
4
+ const config = PluginConfigProvider.get(alias);
5
+ const plugin = {
6
+ alias,
7
+ name,
8
+ description,
9
+ config,
10
+ setup: async function () {
11
+ return setupFn(this);
12
+ },
13
+ monitor: async function () {
14
+ return monitorFn(this);
15
+ },
16
+ refresh: async function () {
17
+ return refreshFn(this);
18
+ },
19
+ teardown: async function () {
20
+ return teardownFn(this);
21
+ },
22
+ getName: function () {
23
+ return this.name;
24
+ }
25
+ };
26
+ return plugin;
16
27
  }
17
- export { BasePlugin };
@@ -1,24 +1,40 @@
1
- import { BasePlugin } from "./BasePlugin.js";
2
- import { PluginManager } from "@daemonitor/plugins";
3
- class MonitoringPlugin extends BasePlugin {
4
- static currentPluginIndex = 0;
5
- instanceName;
6
- refreshTimer;
7
- uniqueId;
8
- pluginIndex;
9
- constructor(alias, name, description) {
10
- super(alias, name, description);
11
- this.pluginIndex = MonitoringPlugin.currentPluginIndex++;
12
- this.instanceName = this.config.name || name;
13
- this.uniqueId = this.config?.uniqueId || this.alias + "-" + this.pluginIndex;
14
- }
15
- async send(data, unique_id) {
16
- for (const apiConnection of PluginManager.API_CONNECTIONS) {
17
- await apiConnection.sendData({ name: this.instanceName, ...data }, this.name, unique_id || this.uniqueId);
1
+ import { PluginManager } from "./PluginManager";
2
+ let monitoringPluginIndex = 0;
3
+ export function createMonitoringPlugin(alias, name, description, setupFn, monitorFn, refreshFn, teardownFn) {
4
+ const config = PluginManager.getConfig ? PluginManager.getConfig(alias) : {};
5
+ const pluginIndex = monitoringPluginIndex++;
6
+ const instanceName = config?.name || name;
7
+ const uniqueId = config?.uniqueId || `${alias}-${pluginIndex}`;
8
+ const plugin = {
9
+ alias,
10
+ name,
11
+ description,
12
+ config,
13
+ instanceName,
14
+ uniqueId,
15
+ pluginIndex,
16
+ setup: async function () {
17
+ return setupFn(this);
18
+ },
19
+ monitor: async function () {
20
+ return monitorFn(this);
21
+ },
22
+ refresh: async function () {
23
+ return refreshFn(this);
24
+ },
25
+ teardown: async function () {
26
+ return teardownFn(this);
27
+ },
28
+ getName: function () {
29
+ return this.name;
30
+ },
31
+ send: async function (data, unique_id) {
32
+ // Access API_CONNECTIONS from PluginManager
33
+ const connections = PluginManager.API_CONNECTIONS || [];
34
+ for (const apiConnection of connections) {
35
+ await apiConnection.sendData({ name: this.instanceName, ...data }, this.name, unique_id || this.uniqueId);
36
+ }
18
37
  }
19
- }
20
- getName() {
21
- return this.name;
22
- }
38
+ };
39
+ return plugin;
23
40
  }
24
- export { MonitoringPlugin };
@@ -1,10 +1,17 @@
1
- class PluginConfigProvider {
1
+ export class PluginConfigProvider {
2
2
  static config = {};
3
- static async get(key) {
4
- if (!PluginConfigProvider.config[key]) {
5
- return null;
6
- }
7
- return PluginConfigProvider.config[key];
3
+ /**
4
+ * Load the full client config (the parsed client.config.json). Per-plugin
5
+ * sections are keyed by alias (e.g. config.docker, config.pm2). Without this
6
+ * the per-alias `get()` below always returns null and plugins silently fall
7
+ * back to their hardcoded defaults.
8
+ */
9
+ static loadAll(config) {
10
+ PluginConfigProvider.config = config || {};
11
+ }
12
+ /** Synchronous lookup of a plugin's config section by alias. */
13
+ static get(key) {
14
+ const section = PluginConfigProvider.config?.[key];
15
+ return section == null ? null : section;
8
16
  }
9
17
  }
10
- export { PluginConfigProvider };
@@ -1,13 +1,28 @@
1
- import PluginClasses from "../plugins/index.js";
2
- export class PluginLoader {
3
- static async loadPlugins() {
4
- const plugins = [];
5
- const pluginClasses = PluginClasses;
6
- for (const file of Object.keys(pluginClasses)) {
7
- const PluginClass = pluginClasses[file];
8
- const pluginInstance = new PluginClass();
9
- plugins.push(pluginInstance);
1
+ import pluginsFactory from "../plugins/index";
2
+ export const PluginLoader = {
3
+ // Load all plugins using the factories
4
+ loadPlugins: async function () {
5
+ try {
6
+ const plugins = [];
7
+ // Create plugin instances using the factory functions
8
+ const pluginFactories = pluginsFactory;
9
+ for (const pluginName of Object.keys(pluginFactories)) {
10
+ const createPlugin = pluginFactories[pluginName];
11
+ if (typeof createPlugin === 'function') {
12
+ try {
13
+ const plugin = await createPlugin();
14
+ plugins.push(plugin);
15
+ }
16
+ catch (err) {
17
+ console.error(`Error creating plugin ${pluginName}:`, err);
18
+ }
19
+ }
20
+ }
21
+ return plugins;
22
+ }
23
+ catch (error) {
24
+ console.error("Error loading plugins:", error);
25
+ return [];
10
26
  }
11
- return plugins;
12
27
  }
13
- }
28
+ };
@@ -1,19 +1,37 @@
1
- import { PluginLoader } from "./PluginLoader.js";
2
- class PluginManager {
3
- static API_CONNECTIONS = [];
4
- availablePlugins;
5
- activePlugins;
6
- configuredPlugins;
7
- constructor(configuredPlugins) {
8
- this.availablePlugins = [];
9
- this.activePlugins = [];
10
- this.configuredPlugins = configuredPlugins;
11
- }
12
- async initialize() {
13
- return this.loadPlugins().then(() => this.setupAll());
14
- }
15
- async loadPlugins() {
16
- return await PluginLoader.loadPlugins().then(plugins => {
1
+ import { PluginLoader } from "./PluginLoader";
2
+ import { PluginConfigProvider } from "./PluginConfigProvider";
3
+ // Singleton state
4
+ const state = {
5
+ availablePlugins: [],
6
+ activePlugins: [],
7
+ configuredPlugins: []
8
+ };
9
+ // Export as namespace instead of class
10
+ export const PluginManager = {
11
+ // Global connections array
12
+ API_CONNECTIONS: [],
13
+ // Initialize the manager. Accepts either the legacy string[] of plugin
14
+ // aliases, or the full client config object ({ plugins: [...], docker: {...}, ... }).
15
+ // Passing the full object also populates PluginConfigProvider so plugins
16
+ // receive their per-alias config section (dockerBin, refreshInterval, etc.).
17
+ initialize: async function (config) {
18
+ let configuredPlugins;
19
+ if (Array.isArray(config)) {
20
+ configuredPlugins = config;
21
+ }
22
+ else {
23
+ configuredPlugins = config?.plugins || [];
24
+ PluginConfigProvider.loadAll(config);
25
+ }
26
+ state.configuredPlugins = configuredPlugins.map((p) => String(p).toLowerCase());
27
+ await this.loadPlugins();
28
+ await this.setupAll();
29
+ return;
30
+ },
31
+ // Load all available plugins
32
+ loadPlugins: async function () {
33
+ try {
34
+ const plugins = await PluginLoader.loadPlugins();
17
35
  if (!plugins) {
18
36
  throw new Error("Failed to load plugins.");
19
37
  }
@@ -23,47 +41,63 @@ class PluginManager {
23
41
  else {
24
42
  console.log(`Loaded ${plugins.length} plugins.`);
25
43
  }
26
- this.availablePlugins = plugins;
27
- });
28
- }
29
- async addConnector(apiConnection) {
30
- PluginManager.API_CONNECTIONS.push(apiConnection);
31
- }
32
- async setupAll() {
44
+ state.availablePlugins = plugins;
45
+ }
46
+ catch (error) {
47
+ console.error("Error loading plugins:", error);
48
+ throw error;
49
+ }
50
+ },
51
+ // Add a connector for plugins to use
52
+ addConnector: function (apiConnection) {
53
+ this.API_CONNECTIONS.push(apiConnection);
54
+ },
55
+ // Get connections for plugins
56
+ getConnections: function () {
57
+ return this.API_CONNECTIONS;
58
+ },
59
+ // Get config for a plugin by alias
60
+ getConfig: function (alias) {
61
+ return PluginConfigProvider.get(alias);
62
+ },
63
+ // Set up all the configured plugins
64
+ setupAll: async function () {
33
65
  console.log("Setting up plugins...");
34
- // const configuredPlugins = await AppConfigProvider.get("plugins")
35
- console.log("Configured plugins:", this.configuredPlugins.join(", "));
36
- if (!this.configuredPlugins)
66
+ console.log("Configured plugins:", state.configuredPlugins.join(", "));
67
+ if (!state.configuredPlugins || state.configuredPlugins.length === 0) {
37
68
  throw new Error("No plugins configured. Set up config.json first.");
38
- this.activePlugins = this.availablePlugins.filter(plugin => {
39
- return this.configuredPlugins.includes(plugin.getName().toLowerCase());
69
+ }
70
+ state.activePlugins = state.availablePlugins.filter(plugin => {
71
+ return state.configuredPlugins.includes(plugin.getName().toLowerCase());
40
72
  });
41
- if (!this.activePlugins)
73
+ if (state.activePlugins.length === 0) {
42
74
  throw new Error("No plugins to set up.");
43
- for (const plugin of this.activePlugins) {
75
+ }
76
+ for (const plugin of state.activePlugins) {
44
77
  await plugin.setup();
45
78
  }
46
- }
47
- async monitorAll() {
79
+ },
80
+ // Start monitoring with all plugins
81
+ monitorAll: async function () {
48
82
  const results = [];
49
- if (!this.availablePlugins)
83
+ if (!state.availablePlugins || state.availablePlugins.length === 0) {
50
84
  throw new Error("Plugins not loaded yet! Call loadPlugins() first.");
51
- if (this.availablePlugins.length === 0)
52
- throw new Error("No plugins loaded.");
53
- if (!PluginManager.API_CONNECTIONS || PluginManager.API_CONNECTIONS.length === 0)
85
+ }
86
+ if (this.API_CONNECTIONS.length === 0) {
54
87
  throw new Error("No connections loaded yet! Call addConnector() first.");
55
- console.log(`Monitoring ${this.activePlugins.length} plugins...`);
56
- for (const plugin of this.activePlugins) {
88
+ }
89
+ console.log(`Monitoring ${state.activePlugins.length} plugins...`);
90
+ for (const plugin of state.activePlugins) {
57
91
  console.log(` + ${plugin.getName()}`);
58
- const data = plugin.monitor();
92
+ const data = await plugin.monitor();
59
93
  results.push(data);
60
94
  }
61
95
  return results;
62
- }
63
- async teardownAll() {
64
- for (const plugin of this.activePlugins) {
96
+ },
97
+ // Tear down all plugins
98
+ teardownAll: async function () {
99
+ for (const plugin of state.activePlugins) {
65
100
  await plugin.teardown();
66
101
  }
67
102
  }
68
- }
69
- export { PluginManager };
103
+ };
@@ -0,0 +1,36 @@
1
+ // Aggregate named service processes (nginx, php-fpm, db, …) from `ps` output.
2
+ // Used for host processes (OSPlugin) and per-container processes (LxcPlugin via
3
+ // `lxc exec … ps`). MEM (rss) is exact; CPU is `ps` %cpu, which is the average
4
+ // over each process's lifetime — a rough indicator for long-running daemons.
5
+ // The command whose output parseProcesses() expects (headerless: comm rss pcpu).
6
+ export const PS_CMD = "ps -eo comm=,rss=,pcpu=";
7
+ const WATCH = [
8
+ { key: "nginx", test: (c) => c.startsWith("nginx") },
9
+ { key: "php-fpm", test: (c) => /^php-?fpm/i.test(c) },
10
+ { key: "mysql", test: (c) => c.startsWith("mysqld") || c.startsWith("mariadbd") },
11
+ { key: "postgres", test: (c) => c.startsWith("postgres") },
12
+ { key: "redis", test: (c) => c.startsWith("redis-server") || c.startsWith("redis") },
13
+ { key: "node", test: (c) => c.startsWith("node") },
14
+ ];
15
+ export function parseProcesses(out) {
16
+ const agg = new Map();
17
+ for (const line of String(out || "").split("\n")) {
18
+ // Last two whitespace-separated tokens are rss + pcpu; the rest is comm
19
+ // (which may itself contain spaces/slashes, e.g. node process titles).
20
+ const m = line.trim().match(/^(.+?)\s+(\d+)\s+([\d.]+)$/);
21
+ if (!m)
22
+ continue;
23
+ const comm = m[1].trim();
24
+ const w = WATCH.find((x) => x.test(comm));
25
+ if (!w)
26
+ continue;
27
+ const cur = agg.get(w.key) || { name: w.key, count: 0, cpu: 0, mem: 0 };
28
+ cur.count++;
29
+ cur.cpu += Number(m[3]) || 0;
30
+ cur.mem += (Number(m[2]) || 0) * 1024; // rss KiB -> bytes
31
+ agg.set(w.key, cur);
32
+ }
33
+ return Array.from(agg.values())
34
+ .map((p) => ({ ...p, cpu: Math.round(p.cpu * 10) / 10 }))
35
+ .sort((a, b) => b.mem - a.mem);
36
+ }
@@ -1,12 +1,33 @@
1
- import { EwelinkPlugin } from "./monitoring/EwelinkPlugin.js";
2
- import { OSPlugin } from "./monitoring/OSPlugin.js";
3
- import { PM2Plugin } from "./monitoring/PM2Plugin.js";
4
- import { MongoDBPlugin } from "./monitoring/MongoDBPlugin.js";
5
- import { WebsitePlugin } from "./monitoring/WebsitePlugin.js";
1
+ // Import plugin factory functions
2
+ import { createPM2Plugin } from "./monitoring/PM2Plugin";
3
+ import { createDockerPlugin } from "./monitoring/DockerPlugin";
4
+ import { createLxcPlugin } from "./monitoring/LxcPlugin";
5
+ import { createOSPlugin } from "./monitoring/OSPlugin";
6
+ import { createWebsitePlugin } from "./monitoring/WebsitePlugin";
7
+ import { createWordpressPlugin } from "./monitoring/WordpressPlugin";
8
+ import { createLogWatchPlugin } from "./monitoring/LogWatchPlugin";
9
+ import { createNetScanPlugin } from "./monitoring/NetScanPlugin";
10
+ // In future, implement other plugin factory functions using the same pattern
11
+ // import { createOSPlugin } from "./monitoring/OSPlugin"
12
+ // import { createMongoDBPlugin } from "./monitoring/MongoDBPlugin"
13
+ // import { createWebsitePlugin } from "./monitoring/WebsitePlugin"
14
+ // import { createCloudflarePlugin } from "./monitoring/CloudflarePlugin"
15
+ // import { createEwelinkPlugin } from "./monitoring/EwelinkPlugin"
16
+ // Export factory functions to create plugin instances.
17
+ // (Keys are display-only; the manager activates a plugin when its getName()
18
+ // lowercased matches an alias in the config `plugins` array.)
6
19
  export default {
7
- EwelinkPlugin,
8
- MongoDBPlugin,
9
- OSPlugin,
10
- PM2Plugin,
11
- WebsitePlugin
20
+ PM2Plugin: createPM2Plugin,
21
+ Docker: createDockerPlugin,
22
+ Lxc: createLxcPlugin,
23
+ OS: createOSPlugin,
24
+ Website: createWebsitePlugin,
25
+ Wordpress: createWordpressPlugin,
26
+ Logwatch: createLogWatchPlugin,
27
+ Netscan: createNetScanPlugin,
28
+ // CloudflarePlugin: createCloudflarePlugin,
29
+ // EwelinkPlugin: createEwelinkPlugin,
30
+ // MongoDBPlugin: createMongoDBPlugin,
31
+ // OSPlugin: createOSPlugin,
32
+ // WebsitePlugin: createWebsitePlugin,
12
33
  };
@@ -0,0 +1,170 @@
1
+ import { createMonitoringPlugin } from "../../lib/MonitoringPlugin";
2
+ import { exec } from "child_process";
3
+ import { promisify } from "util";
4
+ import { hostname } from "os";
5
+ const execAsync = promisify(exec);
6
+ const EXEC_OPTS = { maxBuffer: 32 * 1024 * 1024 }; // containers.length can be large
7
+ const HEALTH_RE = /\((healthy|unhealthy|health: starting|starting)\)/i;
8
+ /** Convert docker's "83.86MiB" / "1.952GiB" memory strings to bytes. */
9
+ function memToBytes(value) {
10
+ if (!value)
11
+ return 0;
12
+ const unit = value.replace(/[0-9.\s]/g, "").toUpperCase();
13
+ const num = parseFloat(value.replace(/[^0-9.]/g, "")) || 0;
14
+ const mult = {
15
+ B: 1, KB: 1e3, KIB: 1024, MB: 1e6, MIB: 1024 ** 2,
16
+ GB: 1e9, GIB: 1024 ** 3, TB: 1e12, TIB: 1024 ** 4,
17
+ };
18
+ return Math.round(num * (mult[unit] ?? 1));
19
+ }
20
+ /**
21
+ * Collect all containers (including stopped) in a small, fixed number of
22
+ * exec calls regardless of how many containers exist:
23
+ * 1. `docker ps -a` — names, image, state, status, compose labels
24
+ * 2. `docker stats` — live CPU / memory for running containers (one batch)
25
+ * 3. `docker inspect` — restart count, health, exit code (one batch)
26
+ */
27
+ async function collectContainers(bin) {
28
+ const psFmt = '{{.ID}}|{{.Names}}|{{.Image}}|{{.State}}|{{.Status}}|{{.Label "com.docker.compose.project"}}|{{.Label "com.docker.compose.service"}}';
29
+ const { stdout: psOut } = await execAsync(`${bin} ps -a --format '${psFmt}'`, EXEC_OPTS);
30
+ const containers = [];
31
+ const byId = new Map();
32
+ for (const line of psOut.split("\n")) {
33
+ if (!line.trim())
34
+ continue;
35
+ const [id, name, image, state, status, project, service] = line.split("|");
36
+ const running = (state || "").toLowerCase() === "running";
37
+ const healthMatch = HEALTH_RE.exec(status || "");
38
+ const c = {
39
+ id,
40
+ name,
41
+ service: service || "",
42
+ image,
43
+ state: state || "",
44
+ status: status || "",
45
+ running,
46
+ health: healthMatch ? healthMatch[1].replace(/^health:\s*/i, "").toLowerCase() : "",
47
+ cpu: 0, mem: 0, memLimit: 0, memPercent: 0, restarts: 0, exitCode: 0,
48
+ };
49
+ c.__project = project || "";
50
+ containers.push(c);
51
+ byId.set(id, c);
52
+ }
53
+ if (!containers.length)
54
+ return containers;
55
+ // 2. live stats for running containers (one batched call, best-effort)
56
+ try {
57
+ const { stdout: statsOut } = await execAsync(`${bin} stats --no-stream --format '{{.ID}}|{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}'`, EXEC_OPTS);
58
+ for (const line of statsOut.split("\n")) {
59
+ if (!line.trim())
60
+ continue;
61
+ const [id, cpuPerc, memUsage, memPerc] = line.split("|");
62
+ const c = byId.get(id);
63
+ if (!c)
64
+ continue;
65
+ c.cpu = parseFloat((cpuPerc || "").replace("%", "")) || 0;
66
+ const [used, limit] = (memUsage || "").split("/").map((p) => p.trim());
67
+ c.mem = memToBytes(used);
68
+ c.memLimit = memToBytes(limit);
69
+ c.memPercent = parseFloat((memPerc || "").replace("%", "")) || 0;
70
+ }
71
+ }
72
+ catch (err) {
73
+ console.error("docker: stats collection failed:", err.message);
74
+ }
75
+ // 3. restart count + exit code (one batched inspect, best-effort).
76
+ // NOTE: we deliberately do NOT read .State.Health here — on some engines the
77
+ // template errors with `map has no entry for key "Health"` for containers
78
+ // without a healthcheck, which would fail the whole batch. Health is parsed
79
+ // from the ps `Status` string ("(healthy)" / "(unhealthy)") instead.
80
+ try {
81
+ const ids = containers.map((c) => c.id).join(" ");
82
+ const inspectFmt = '{{.Id}}|{{.RestartCount}}|{{.State.ExitCode}}';
83
+ const { stdout: inspOut } = await execAsync(`${bin} inspect --format '${inspectFmt}' ${ids}`, EXEC_OPTS);
84
+ for (const line of inspOut.split("\n")) {
85
+ if (!line.trim())
86
+ continue;
87
+ const [fullId, restarts, exitCode] = line.split("|");
88
+ const c = byId.get(fullId.slice(0, 12));
89
+ if (!c)
90
+ continue;
91
+ c.restarts = parseInt(restarts, 10) || 0;
92
+ c.exitCode = parseInt(exitCode, 10) || 0;
93
+ }
94
+ }
95
+ catch (err) {
96
+ console.error("docker: inspect collection failed:", err.message);
97
+ }
98
+ return containers;
99
+ }
100
+ export function createDockerPlugin() {
101
+ let refreshTimer = null;
102
+ let available = false;
103
+ let dockerBin = "docker";
104
+ const setupFn = async (plugin) => {
105
+ dockerBin = plugin.config?.dockerBin || plugin.config?.bin || "docker";
106
+ try {
107
+ await execAsync(`${dockerBin} version --format '{{.Server.Version}}'`, EXEC_OPTS);
108
+ available = true;
109
+ }
110
+ catch (err) {
111
+ // Don't throw: a missing docker engine shouldn't take down the whole client.
112
+ available = false;
113
+ console.error(`docker: engine not available via "${dockerBin}" — plugin idle:`, err.message);
114
+ }
115
+ };
116
+ const refreshFn = async (plugin) => {
117
+ if (!available)
118
+ return;
119
+ try {
120
+ const all = await collectContainers(dockerBin);
121
+ // Optional filtering by compose project.
122
+ const only = plugin.config?.projects || [];
123
+ const exclude = plugin.config?.exclude || [];
124
+ // Group by compose project (standalone containers group under their own name).
125
+ const groups = new Map();
126
+ for (const c of all) {
127
+ const project = c.__project || c.name;
128
+ delete c.__project;
129
+ if (only.length && !only.includes(project))
130
+ continue;
131
+ if (exclude.includes(project))
132
+ continue;
133
+ const arr = groups.get(project) || [];
134
+ arr.push(c);
135
+ groups.set(project, arr);
136
+ }
137
+ const host = hostname();
138
+ for (const [project, containers] of groups) {
139
+ const running = containers.filter((c) => c.running).length;
140
+ const payload = {
141
+ name: project,
142
+ engine: "docker",
143
+ host,
144
+ project,
145
+ running,
146
+ total: containers.length,
147
+ containers,
148
+ timestamp: Date.now(),
149
+ };
150
+ await plugin.send(payload, `docker-${project}`);
151
+ }
152
+ }
153
+ catch (err) {
154
+ console.error("docker: refresh error:", err.message);
155
+ }
156
+ };
157
+ const monitorFn = async (plugin) => {
158
+ if (!available)
159
+ return;
160
+ await refreshFn(plugin);
161
+ refreshTimer = setInterval(() => refreshFn(plugin), plugin.config?.refreshInterval || 60000);
162
+ };
163
+ const teardownFn = async () => {
164
+ if (refreshTimer)
165
+ clearInterval(refreshTimer);
166
+ refreshTimer = null;
167
+ };
168
+ return createMonitoringPlugin("docker", "docker", "Docker container monitoring plugin", setupFn, monitorFn, refreshFn, teardownFn);
169
+ }
170
+ export default createDockerPlugin;