Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78488f004c | ||
|
|
54e1867bfc | ||
|
|
e3341f9de3 |
@@ -0,0 +1,34 @@
|
|||||||
|
name: Build Windows installer
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'windows/**'
|
||||||
|
- '.github/workflows/build-installer.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
env:
|
||||||
|
SIGNTOOL_CMD: ${{ secrets.SIGNTOOL_CMD }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Inno
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
choco install innosetup --no-progress -y
|
||||||
|
|
||||||
|
- name: Build installer
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
./windows/installer/build-installer.ps1
|
||||||
|
|
||||||
|
- name: Upload Setup.exe artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: awatch-rus-setup
|
||||||
|
path: windows/installer/output/*.exe
|
||||||
|
if-no-files-found: error
|
||||||
+147
-6
@@ -1,6 +1,6 @@
|
|||||||
(function () {
|
(function () {
|
||||||
window.__awRuPatchVersion = "template-v12-activity-heading-ru";
|
window.__awRuPatchVersion = "template-v13-pve-audit-day-filter";
|
||||||
document.documentElement.setAttribute("data-aw-ru-patch", "template-v12-activity-heading-ru");
|
document.documentElement.setAttribute("data-aw-ru-patch", "template-v13-pve-audit-day-filter");
|
||||||
|
|
||||||
const exact = new Map([
|
const exact = new Map([
|
||||||
["ActivityWatch", "АктивВотч"],
|
["ActivityWatch", "АктивВотч"],
|
||||||
@@ -333,6 +333,11 @@
|
|||||||
'.aw-ru-host-item-title { font-weight: 600; margin-bottom: 6px; }',
|
'.aw-ru-host-item-title { font-weight: 600; margin-bottom: 6px; }',
|
||||||
'.aw-ru-host-links { display: flex; flex-wrap: wrap; gap: 6px; }',
|
'.aw-ru-host-links { display: flex; flex-wrap: wrap; gap: 6px; }',
|
||||||
'.aw-ru-host-links a { display: inline-block; padding: 4px 8px; border-radius: 999px; background: rgba(90,140,255,.15); text-decoration: none; }',
|
'.aw-ru-host-links a { display: inline-block; padding: 4px 8px; border-radius: 999px; background: rgba(90,140,255,.15); text-decoration: none; }',
|
||||||
|
'.aw-ru-worktime-center { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(20,20,20,.03); }',
|
||||||
|
'.aw-ru-worktime-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: end; margin-bottom: 10px; }',
|
||||||
|
'.aw-ru-worktime-controls label { font-size: 12px; display:flex; flex-direction:column; gap:4px; }',
|
||||||
|
'.aw-ru-worktime-table { width: 100%; border-collapse: collapse; font-size: 13px; }',
|
||||||
|
'.aw-ru-worktime-table th, .aw-ru-worktime-table td { border: 1px solid rgba(120,120,120,.25); padding: 6px 8px; text-align: left; }',
|
||||||
'.aw-ru-pve-audit { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(10,20,40,.04); }',
|
'.aw-ru-pve-audit { margin: 16px 0; padding: 16px; border: 1px solid rgba(120,120,120,.35); border-radius: 8px; background: rgba(10,20,40,.04); }',
|
||||||
'.aw-ru-pve-audit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin: 12px 0 16px; }',
|
'.aw-ru-pve-audit-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin: 12px 0 16px; }',
|
||||||
'.aw-ru-pve-audit-card { border: 1px solid rgba(120,120,120,.22); border-radius: 8px; padding: 12px; background: rgba(255,255,255,.02); }',
|
'.aw-ru-pve-audit-card { border: 1px solid rgba(120,120,120,.22); border-radius: 8px; padding: 12px; background: rgba(255,255,255,.02); }',
|
||||||
@@ -735,6 +740,23 @@
|
|||||||
return !!(match && isPveLikeHost(decodeURIComponent(match[1] || "")));
|
return !!(match && isPveLikeHost(decodeURIComponent(match[1] || "")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSelectedDayFromHash() {
|
||||||
|
const hash = window.location.hash || "";
|
||||||
|
const match = hash.match(/^#\/activity\/[^/]+\/day\/([^/?#]+)/i);
|
||||||
|
return match && match[1] ? decodeURIComponent(match[1]) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterEventsBySelectedDay(events) {
|
||||||
|
const day = getSelectedDayFromHash();
|
||||||
|
if (!day) return Array.isArray(events) ? events : [];
|
||||||
|
const start = new Date(day + "T00:00:00");
|
||||||
|
const end = new Date(day + "T23:59:59.999");
|
||||||
|
return (Array.isArray(events) ? events : []).filter(function (event) {
|
||||||
|
const timestamp = event && event.timestamp ? new Date(event.timestamp) : null;
|
||||||
|
return timestamp && !isNaN(timestamp.getTime()) && timestamp >= start && timestamp <= end;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function extractHostFromBucket(bucketId, bucketMeta) {
|
function extractHostFromBucket(bucketId, bucketMeta) {
|
||||||
if (bucketMeta && bucketMeta.hostname) return String(bucketMeta.hostname);
|
if (bucketMeta && bucketMeta.hostname) return String(bucketMeta.hostname);
|
||||||
const prefixes = [
|
const prefixes = [
|
||||||
@@ -1306,10 +1328,10 @@
|
|||||||
loadBucketEvents("aw-console-commands_" + host, 50).catch(function () { return []; })
|
loadBucketEvents("aw-console-commands_" + host, 50).catch(function () { return []; })
|
||||||
]);
|
]);
|
||||||
const data = {
|
const data = {
|
||||||
web: webEvents || [],
|
web: filterEventsBySelectedDay(webEvents || []),
|
||||||
tasks: taskEvents || [],
|
tasks: filterEventsBySelectedDay(taskEvents || []),
|
||||||
ssh: sshEvents || [],
|
ssh: filterEventsBySelectedDay(sshEvents || []),
|
||||||
cmd: cmdEvents || []
|
cmd: filterEventsBySelectedDay(cmdEvents || [])
|
||||||
};
|
};
|
||||||
center.querySelector("[data-aw-ru-pve-web-count]").textContent = String(data.web.length);
|
center.querySelector("[data-aw-ru-pve-web-count]").textContent = String(data.web.length);
|
||||||
center.querySelector("[data-aw-ru-pve-task-count]").textContent = String(data.tasks.length);
|
center.querySelector("[data-aw-ru-pve-task-count]").textContent = String(data.tasks.length);
|
||||||
@@ -1584,6 +1606,124 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ms(v) {
|
||||||
|
return new Date(v).getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fmtHours(sec) {
|
||||||
|
return (Math.round((sec / 3600) * 100) / 100).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rangesIntersect(a0, a1, b0, b1) {
|
||||||
|
return Math.max(a0, b0) < Math.min(a1, b1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function intersectDurationSeconds(a0, a1, b0, b1) {
|
||||||
|
return Math.max(0, (Math.min(a1, b1) - Math.max(a0, b0)) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderPersonalWorktime(host, fromIso, toIso, target) {
|
||||||
|
const sessionBucket = "aw-worktime-sessions_" + host;
|
||||||
|
const windowBucket = "aw-watcher-window_" + host;
|
||||||
|
const afkBucket = "aw-watcher-afk_" + host;
|
||||||
|
const [sessionEvents, windowEvents, afkEvents] = await Promise.all([
|
||||||
|
loadBucketEvents(sessionBucket, 5000).catch(function () { return []; }),
|
||||||
|
loadBucketEvents(windowBucket, 10000).catch(function () { return []; }),
|
||||||
|
loadBucketEvents(afkBucket, 10000).catch(function () { return []; })
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fromMs = ms(fromIso);
|
||||||
|
const toMs = ms(toIso);
|
||||||
|
const userRanges = {};
|
||||||
|
(sessionEvents || []).forEach(function (e) {
|
||||||
|
const d = e && e.data ? e.data : {};
|
||||||
|
const u = String(d.username || "").trim();
|
||||||
|
if (!u) return;
|
||||||
|
const t0 = ms(e.timestamp);
|
||||||
|
const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
|
||||||
|
if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
|
||||||
|
userRanges[u] = userRanges[u] || [];
|
||||||
|
userRanges[u].push([t0, t1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeRanges = [];
|
||||||
|
const afkByTs = new Map();
|
||||||
|
(afkEvents || []).forEach(function (e) { afkByTs.set(String(e.timestamp || ""), e); });
|
||||||
|
(windowEvents || []).forEach(function (e) {
|
||||||
|
const t0 = ms(e.timestamp);
|
||||||
|
const t1 = t0 + Math.max(1, Number(e.duration || 0)) * 1000;
|
||||||
|
if (!rangesIntersect(t0, t1, fromMs, toMs)) return;
|
||||||
|
const afk = afkByTs.get(String(e.timestamp || ""));
|
||||||
|
const status = String(afk && afk.data && afk.data.status || "");
|
||||||
|
if (status && status.toLowerCase() === "afk") return;
|
||||||
|
activeRanges.push([t0, t1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const rows = Object.keys(userRanges).sort().map(function (u) {
|
||||||
|
let presentSec = 0;
|
||||||
|
let activeSec = 0;
|
||||||
|
userRanges[u].forEach(function (r) {
|
||||||
|
presentSec += intersectDurationSeconds(r[0], r[1], fromMs, toMs);
|
||||||
|
activeRanges.forEach(function (a) {
|
||||||
|
activeSec += intersectDurationSeconds(r[0], r[1], a[0], a[1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const idleSec = Math.max(0, presentSec - activeSec);
|
||||||
|
return { user: u, presentSec: presentSec, activeSec: activeSec, idleSec: idleSec };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!rows.length) {
|
||||||
|
target.innerHTML = '<div class="aw-ru-pve-audit-muted">Нет данных в aw-worktime-sessions_' + host + ' за выбранный интервал.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.innerHTML = '<table class="aw-ru-worktime-table"><thead><tr><th>Пользователь</th><th>Активно, ч</th><th>В сессии, ч</th><th>Простой, ч</th></tr></thead><tbody>' +
|
||||||
|
rows.map(function (r) {
|
||||||
|
return '<tr><td>' + r.user + '</td><td>' + fmtHours(r.activeSec) + '</td><td>' + fmtHours(r.presentSec) + '</td><td>' + fmtHours(r.idleSec) + '</td></tr>';
|
||||||
|
}).join("") +
|
||||||
|
'</tbody></table>';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function injectPersonalWorktimeCenter(root) {
|
||||||
|
const isActivity = /^#\/activity\//i.test(window.location.hash || "");
|
||||||
|
if (!isActivity) return;
|
||||||
|
if (root.querySelector(".aw-ru-worktime-center")) return;
|
||||||
|
const host = getCurrentHostFromHash();
|
||||||
|
if (!host) return;
|
||||||
|
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.className = "aw-ru-worktime-center";
|
||||||
|
const now = new Date();
|
||||||
|
const day = now.toISOString().slice(0, 10);
|
||||||
|
container.innerHTML =
|
||||||
|
'<h3 style="margin-top:0">Персональный учёт рабочего времени</h3>' +
|
||||||
|
'<div class="aw-ru-worktime-controls">' +
|
||||||
|
'<label>Дата <input type="date" data-aw-wt-date value="' + day + '"></label>' +
|
||||||
|
'<label>С <input type="time" data-aw-wt-from value="09:00"></label>' +
|
||||||
|
'<label>По <input type="time" data-aw-wt-to value="18:00"></label>' +
|
||||||
|
'<button type="button" data-aw-wt-run>Рассчитать</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div data-aw-wt-result class="aw-ru-pve-audit-muted">Нажмите "Рассчитать".</div>';
|
||||||
|
|
||||||
|
root.insertBefore(container, root.firstChild);
|
||||||
|
|
||||||
|
const run = async function () {
|
||||||
|
const date = container.querySelector("[data-aw-wt-date]").value;
|
||||||
|
const tFrom = container.querySelector("[data-aw-wt-from]").value || "09:00";
|
||||||
|
const tTo = container.querySelector("[data-aw-wt-to]").value || "18:00";
|
||||||
|
const result = container.querySelector("[data-aw-wt-result]");
|
||||||
|
result.textContent = "Расчёт...";
|
||||||
|
const fromIso = new Date(date + "T" + tFrom + ":00").toISOString();
|
||||||
|
const toIso = new Date(date + "T" + tTo + ":00").toISOString();
|
||||||
|
try {
|
||||||
|
await renderPersonalWorktime(host, fromIso, toIso, result);
|
||||||
|
} catch (e) {
|
||||||
|
result.textContent = "Ошибка расчёта: " + String(e.message || e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
container.querySelector("[data-aw-wt-run]").addEventListener("click", function () { run(); });
|
||||||
|
}
|
||||||
|
|
||||||
function applyPatch() {
|
function applyPatch() {
|
||||||
enforceSafeActivityViewForPveHost();
|
enforceSafeActivityViewForPveHost();
|
||||||
ensureSettingsHost();
|
ensureSettingsHost();
|
||||||
@@ -1599,6 +1739,7 @@
|
|||||||
injectDlpNavigation(document.body);
|
injectDlpNavigation(document.body);
|
||||||
injectDlpReviewCenter(document.body);
|
injectDlpReviewCenter(document.body);
|
||||||
injectDlpAlertsCenter(document.body);
|
injectDlpAlertsCenter(document.body);
|
||||||
|
injectPersonalWorktimeCenter(document.body).catch(function () {});
|
||||||
injectHostGroupsCenter(document.body).catch(function () {});
|
injectHostGroupsCenter(document.body).catch(function () {});
|
||||||
redirectBareTrendsRoute();
|
redirectBareTrendsRoute();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,6 +243,8 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$EndpointCollectorScriptSource,
|
[string]$EndpointCollectorScriptSource,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$SessionCollectorScriptSource,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$ExampleRulesSource,
|
[string]$ExampleRulesSource,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$ExamplePolicySource,
|
[string]$ExamplePolicySource,
|
||||||
@@ -256,6 +258,7 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
|
|
||||||
$collectorTarget = Join-Path $StateRoot 'browser-domains-native-collector.ps1'
|
$collectorTarget = Join-Path $StateRoot 'browser-domains-native-collector.ps1'
|
||||||
$endpointCollectorTarget = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1'
|
$endpointCollectorTarget = Join-Path $StateRoot 'dlp-endpoint-signals-collector.ps1'
|
||||||
|
$sessionCollectorTarget = Join-Path $StateRoot 'worktime-session-collector.ps1'
|
||||||
$exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json'
|
$exampleRulesTarget = Join-Path $StateRoot 'web-category-rules.example.json'
|
||||||
$rulesTarget = Join-Path $StateRoot 'web-category-rules.json'
|
$rulesTarget = Join-Path $StateRoot 'web-category-rules.json'
|
||||||
$examplePolicyTarget = Join-Path $StateRoot 'dlp-policy.example.json'
|
$examplePolicyTarget = Join-Path $StateRoot 'dlp-policy.example.json'
|
||||||
@@ -263,6 +266,7 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
|
|
||||||
Copy-Item -LiteralPath $CollectorScriptSource -Destination $collectorTarget -Force
|
Copy-Item -LiteralPath $CollectorScriptSource -Destination $collectorTarget -Force
|
||||||
Copy-Item -LiteralPath $EndpointCollectorScriptSource -Destination $endpointCollectorTarget -Force
|
Copy-Item -LiteralPath $EndpointCollectorScriptSource -Destination $endpointCollectorTarget -Force
|
||||||
|
Copy-Item -LiteralPath $SessionCollectorScriptSource -Destination $sessionCollectorTarget -Force
|
||||||
Copy-Item -LiteralPath $ExampleRulesSource -Destination $exampleRulesTarget -Force
|
Copy-Item -LiteralPath $ExampleRulesSource -Destination $exampleRulesTarget -Force
|
||||||
Copy-Item -LiteralPath $ExamplePolicySource -Destination $examplePolicyTarget -Force
|
Copy-Item -LiteralPath $ExamplePolicySource -Destination $examplePolicyTarget -Force
|
||||||
|
|
||||||
@@ -282,6 +286,7 @@ function Copy-ActivityWatchCollectorAssets {
|
|||||||
return [pscustomobject]@{
|
return [pscustomobject]@{
|
||||||
CollectorScript = $collectorTarget
|
CollectorScript = $collectorTarget
|
||||||
EndpointCollectorScript = $endpointCollectorTarget
|
EndpointCollectorScript = $endpointCollectorTarget
|
||||||
|
SessionCollectorScript = $sessionCollectorTarget
|
||||||
ExampleRules = $exampleRulesTarget
|
ExampleRules = $exampleRulesTarget
|
||||||
ActiveRules = $rulesTarget
|
ActiveRules = $rulesTarget
|
||||||
ExamplePolicy = $examplePolicyTarget
|
ExamplePolicy = $examplePolicyTarget
|
||||||
@@ -308,6 +313,8 @@ function New-ActivityWatchDeploymentConfig {
|
|||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$EndpointCollectorScript,
|
[string]$EndpointCollectorScript,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$SessionCollectorScript,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$RulesPath,
|
[string]$RulesPath,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$PolicyPath,
|
[string]$PolicyPath,
|
||||||
@@ -349,6 +356,7 @@ function New-ActivityWatchDeploymentConfig {
|
|||||||
logsRoot = $LogsRoot
|
logsRoot = $LogsRoot
|
||||||
collectorScript = $CollectorScript
|
collectorScript = $CollectorScript
|
||||||
endpointCollectorScript = $EndpointCollectorScript
|
endpointCollectorScript = $EndpointCollectorScript
|
||||||
|
sessionCollectorScript = $SessionCollectorScript
|
||||||
rulesPath = $RulesPath
|
rulesPath = $RulesPath
|
||||||
policyPath = $PolicyPath
|
policyPath = $PolicyPath
|
||||||
launchScript = $LaunchScriptPath
|
launchScript = $LaunchScriptPath
|
||||||
@@ -631,6 +639,7 @@ function Start-CollectorScriptIfNeeded {
|
|||||||
`$script:KnownBuckets = @{}
|
`$script:KnownBuckets = @{}
|
||||||
`$collectorScript = [string]`$config.paths.collectorScript
|
`$collectorScript = [string]`$config.paths.collectorScript
|
||||||
`$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { '' }
|
`$endpointCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]`$config.paths.endpointCollectorScript } else { '' }
|
||||||
|
`$sessionCollectorScript = if (`$config.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]`$config.paths.sessionCollectorScript } else { '' }
|
||||||
`$afkExe = Join-Path `$installRoot 'aw-watcher-afk\aw-watcher-afk.exe'
|
`$afkExe = Join-Path `$installRoot 'aw-watcher-afk\aw-watcher-afk.exe'
|
||||||
`$windowExe = Join-Path `$installRoot 'aw-watcher-window\aw-watcher-window.exe'
|
`$windowExe = Join-Path `$installRoot 'aw-watcher-window\aw-watcher-window.exe'
|
||||||
`$serverArgs = @('--host', [string]`$config.server.host, '--port', [string]`$config.server.port)
|
`$serverArgs = @('--host', [string]`$config.server.host, '--port', [string]`$config.server.port)
|
||||||
@@ -661,6 +670,7 @@ catch {
|
|||||||
}
|
}
|
||||||
Start-CollectorScriptIfNeeded -ScriptPath `$collectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
Start-CollectorScriptIfNeeded -ScriptPath `$collectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
||||||
Start-CollectorScriptIfNeeded -ScriptPath `$endpointCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
Start-CollectorScriptIfNeeded -ScriptPath `$endpointCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
||||||
|
Start-CollectorScriptIfNeeded -ScriptPath `$sessionCollectorScript -ConfigPath `$ConfigPath -PowerShellExe `$powershellExe -SessionId `$sessionId
|
||||||
"@
|
"@
|
||||||
|
|
||||||
Set-Content -LiteralPath $Path -Value $content -Encoding UTF8
|
Set-Content -LiteralPath $Path -Value $content -Encoding UTF8
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1'
|
|||||||
$recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1'
|
$recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1'
|
||||||
$collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1'
|
$collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1'
|
||||||
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
|
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
|
||||||
|
$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
|
||||||
$exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json'
|
$exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json'
|
||||||
$examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json'
|
$examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json'
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null
|
|||||||
$assetResult = Copy-ActivityWatchCollectorAssets `
|
$assetResult = Copy-ActivityWatchCollectorAssets `
|
||||||
-CollectorScriptSource $collectorSource `
|
-CollectorScriptSource $collectorSource `
|
||||||
-EndpointCollectorScriptSource $endpointCollectorSource `
|
-EndpointCollectorScriptSource $endpointCollectorSource `
|
||||||
|
-SessionCollectorScriptSource $sessionCollectorSource `
|
||||||
-ExampleRulesSource $exampleRulesSource `
|
-ExampleRulesSource $exampleRulesSource `
|
||||||
-ExamplePolicySource $examplePolicySource `
|
-ExamplePolicySource $examplePolicySource `
|
||||||
-StateRoot $StateRoot `
|
-StateRoot $StateRoot `
|
||||||
@@ -76,6 +78,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-LogsRoot $logsRoot `
|
-LogsRoot $logsRoot `
|
||||||
-CollectorScript $assetResult.CollectorScript `
|
-CollectorScript $assetResult.CollectorScript `
|
||||||
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
||||||
|
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
||||||
-RulesPath $assetResult.ActiveRules `
|
-RulesPath $assetResult.ActiveRules `
|
||||||
-PolicyPath $assetResult.ActivePolicy `
|
-PolicyPath $assetResult.ActivePolicy `
|
||||||
-PollSeconds $PollSeconds `
|
-PollSeconds $PollSeconds `
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ $launchScriptPath = Join-Path $StateRoot 'launch-watchers.ps1'
|
|||||||
$recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1'
|
$recoveryScriptPath = Join-Path $StateRoot 'recovery-loop.ps1'
|
||||||
$collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1'
|
$collectorSource = Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1'
|
||||||
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
|
$endpointCollectorSource = Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1'
|
||||||
|
$sessionCollectorSource = Join-Path $PSScriptRoot 'worktime-session-collector.ps1'
|
||||||
$exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json'
|
$exampleRulesSource = Join-Path $PSScriptRoot 'web-category-rules.example.json'
|
||||||
$examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json'
|
$examplePolicySource = Join-Path $PSScriptRoot 'dlp-policy.example.json'
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $InstallRoot | Out-Null
|
|||||||
$assetResult = Copy-ActivityWatchCollectorAssets `
|
$assetResult = Copy-ActivityWatchCollectorAssets `
|
||||||
-CollectorScriptSource $collectorSource `
|
-CollectorScriptSource $collectorSource `
|
||||||
-EndpointCollectorScriptSource $endpointCollectorSource `
|
-EndpointCollectorScriptSource $endpointCollectorSource `
|
||||||
|
-SessionCollectorScriptSource $sessionCollectorSource `
|
||||||
-ExampleRulesSource $exampleRulesSource `
|
-ExampleRulesSource $exampleRulesSource `
|
||||||
-ExamplePolicySource $examplePolicySource `
|
-ExamplePolicySource $examplePolicySource `
|
||||||
-StateRoot $StateRoot `
|
-StateRoot $StateRoot `
|
||||||
@@ -74,6 +76,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-LogsRoot $logsRoot `
|
-LogsRoot $logsRoot `
|
||||||
-CollectorScript $assetResult.CollectorScript `
|
-CollectorScript $assetResult.CollectorScript `
|
||||||
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
-EndpointCollectorScript $assetResult.EndpointCollectorScript `
|
||||||
|
-SessionCollectorScript $assetResult.SessionCollectorScript `
|
||||||
-RulesPath $assetResult.ActiveRules `
|
-RulesPath $assetResult.ActiveRules `
|
||||||
-PolicyPath $assetResult.ActivePolicy `
|
-PolicyPath $assetResult.ActivePolicy `
|
||||||
-PollSeconds $PollSeconds `
|
-PollSeconds $PollSeconds `
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ $effectiveLaunchScript = Join-Path $effectiveStateRoot 'launch-watchers.ps1'
|
|||||||
$effectiveRecoveryScript = Join-Path $effectiveStateRoot 'recovery-loop.ps1'
|
$effectiveRecoveryScript = Join-Path $effectiveStateRoot 'recovery-loop.ps1'
|
||||||
$effectiveCollector = Join-Path $effectiveStateRoot 'browser-domains-native-collector.ps1'
|
$effectiveCollector = Join-Path $effectiveStateRoot 'browser-domains-native-collector.ps1'
|
||||||
$effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$existingConfig.paths.endpointCollectorScript } else { Join-Path $effectiveStateRoot 'dlp-endpoint-signals-collector.ps1' }
|
$effectiveEndpointCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'endpointCollectorScript') { [string]$existingConfig.paths.endpointCollectorScript } else { Join-Path $effectiveStateRoot 'dlp-endpoint-signals-collector.ps1' }
|
||||||
|
$effectiveSessionCollector = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'sessionCollectorScript') { [string]$existingConfig.paths.sessionCollectorScript } else { Join-Path $effectiveStateRoot 'worktime-session-collector.ps1' }
|
||||||
$effectiveRules = Join-Path $effectiveStateRoot 'web-category-rules.json'
|
$effectiveRules = Join-Path $effectiveStateRoot 'web-category-rules.json'
|
||||||
$effectivePolicy = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$existingConfig.paths.policyPath } else { Join-Path $effectiveStateRoot 'dlp-policy.json' }
|
$effectivePolicy = if ($existingConfig -and $existingConfig.paths.PSObject.Properties.Name -contains 'policyPath') { [string]$existingConfig.paths.policyPath } else { Join-Path $effectiveStateRoot 'dlp-policy.json' }
|
||||||
|
|
||||||
@@ -96,6 +97,7 @@ Get-ActivityWatchExecutableMap -InstallRoot $effectiveInstallRoot | Out-Null
|
|||||||
$assetResult = Copy-ActivityWatchCollectorAssets `
|
$assetResult = Copy-ActivityWatchCollectorAssets `
|
||||||
-CollectorScriptSource (Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1') `
|
-CollectorScriptSource (Join-Path $PSScriptRoot 'browser-domains-native-collector.ps1') `
|
||||||
-EndpointCollectorScriptSource (Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1') `
|
-EndpointCollectorScriptSource (Join-Path $PSScriptRoot 'dlp-endpoint-signals-collector.ps1') `
|
||||||
|
-SessionCollectorScriptSource (Join-Path $PSScriptRoot 'worktime-session-collector.ps1') `
|
||||||
-ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') `
|
-ExampleRulesSource (Join-Path $PSScriptRoot 'web-category-rules.example.json') `
|
||||||
-ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') `
|
-ExamplePolicySource (Join-Path $PSScriptRoot 'dlp-policy.example.json') `
|
||||||
-StateRoot $effectiveStateRoot `
|
-StateRoot $effectiveStateRoot `
|
||||||
@@ -115,6 +117,7 @@ $config = New-ActivityWatchDeploymentConfig `
|
|||||||
-LogsRoot $effectiveLogsRoot `
|
-LogsRoot $effectiveLogsRoot `
|
||||||
-CollectorScript $effectiveCollector `
|
-CollectorScript $effectiveCollector `
|
||||||
-EndpointCollectorScript $effectiveEndpointCollector `
|
-EndpointCollectorScript $effectiveEndpointCollector `
|
||||||
|
-SessionCollectorScript $effectiveSessionCollector `
|
||||||
-RulesPath $effectiveRules `
|
-RulesPath $effectiveRules `
|
||||||
-PolicyPath $effectivePolicy `
|
-PolicyPath $effectivePolicy `
|
||||||
-PollSeconds $effectivePollSeconds `
|
-PollSeconds $effectivePollSeconds `
|
||||||
|
|||||||
@@ -0,0 +1,241 @@
|
|||||||
|
#define MyAppName "AWatch-rus Windows Agent"
|
||||||
|
#define MyAppVersion "1.0.0"
|
||||||
|
#define MyAppPublisher "AWatch-rus"
|
||||||
|
#define MyAppURL "https://example.local/awatch-rus"
|
||||||
|
#define MyAppExeName "AWatch-rus-Setup.exe"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppId={{7F2C4B63-2B8A-4F1C-BA12-66A7E2C0A0A1}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={autopf}\AWatch-rus
|
||||||
|
DefaultGroupName=AWatch-rus
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
PrivilegesRequired=admin
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
OutputDir=output
|
||||||
|
OutputBaseFilename={#MyAppExeName}
|
||||||
|
Compression=lzma2
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
SetupLogging=yes
|
||||||
|
; Optional production code signing. Configure SIGNTOOL_CMD in CI/local env
|
||||||
|
SignTool=byparam $q$zSIGNTOOL_CMD $f$q
|
||||||
|
DisableWelcomePage=no
|
||||||
|
AllowNoIcons=yes
|
||||||
|
UninstallDisplayIcon={app}\tools\validate-deployment.ps1
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\ActivityWatch.Windows.Common.psd1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\ActivityWatch.Windows.Common.psm1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\browser-domains-native-collector.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\dlp-endpoint-signals-collector.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\deploy-single-user.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\deploy-domain-users.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\deploy-ensemble.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\hardening-recovery.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\validate-deployment.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
Source: "..\web-category-rules.example.json"; DestDir: "{app}\config"; Flags: ignoreversion
|
||||||
|
Source: "..\dlp-policy.example.json"; DestDir: "{app}\config"; Flags: ignoreversion
|
||||||
|
Source: "README-INSTALLER.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "Invoke-AWatchRusInstall.ps1"; DestDir: "{app}\tools"; Flags: ignoreversion
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{autoprograms}\AWatch-rus\Run validation"; Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\validate-deployment.ps1"" -ConfigPath ""C:\ProgramData\ActivityWatch\deployment-config.json"""; WorkingDir: "{app}\tools"
|
||||||
|
Name: "{autodesktop}\AWatch-rus Validation"; Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\validate-deployment.ps1"" -ConfigPath ""C:\ProgramData\ActivityWatch\deployment-config.json"""; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\Invoke-AWatchRusInstall.ps1"" -ServerHost ""{code:GetServerHost}"" -ServerPort {code:GetServerPort} -Domain ""{code:GetDomain}"" -Users ""{code:GetUsers}"" -InstallRoot ""{code:GetInstallRoot}"" -StateRoot ""{code:GetStateRoot}"" -CustomRulesPath ""{app}\config\web-category-rules.example.json"" -CustomPolicyPath ""{app}\config\dlp-policy.example.json"" -AfkEnabled:{code:GetAfkEnabled} -WindowEnabled:{code:GetWindowEnabled}"; Flags: runhidden waituntilterminated
|
||||||
|
Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\tools\validate-deployment.ps1"" -ConfigPath ""{code:GetStateRoot}\deployment-config.json"""; Flags: postinstall shellexec skipifsilent
|
||||||
|
|
||||||
|
[UninstallRun]
|
||||||
|
Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Get-ScheduledTask -TaskName 'ActivityWatch Launch *' -ErrorAction SilentlyContinue | ForEach-Object { Unregister-ScheduledTask -TaskName $_.TaskName -Confirm:$false }; Unregister-ScheduledTask -TaskName 'ActivityWatch Recovery' -Confirm:$false -ErrorAction SilentlyContinue"""; Flags: runhidden waituntilterminated
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
var
|
||||||
|
ConfigPage: TWizardPage;
|
||||||
|
ServerHostEdit: TEdit;
|
||||||
|
ServerPortEdit: TEdit;
|
||||||
|
DomainEdit: TEdit;
|
||||||
|
UsersEdit: TEdit;
|
||||||
|
InstallRootEdit: TEdit;
|
||||||
|
StateRootEdit: TEdit;
|
||||||
|
AfkCheck: TNewCheckBox;
|
||||||
|
WindowCheck: TNewCheckBox;
|
||||||
|
|
||||||
|
procedure ApplyCmdParamIfPresent(const ParamName: string; Edit: TEdit);
|
||||||
|
forward;
|
||||||
|
procedure ApplyCmdBoolIfPresent(const ParamName: string; Check: TNewCheckBox);
|
||||||
|
forward;
|
||||||
|
|
||||||
|
procedure InitializeWizard;
|
||||||
|
begin
|
||||||
|
ConfigPage := CreateCustomPage(wpSelectTasks,
|
||||||
|
'AWatch-rus configuration',
|
||||||
|
'Specify deployment settings passed to deploy-ensemble.ps1');
|
||||||
|
|
||||||
|
ServerHostEdit := TEdit.Create(ConfigPage);
|
||||||
|
ServerHostEdit.Parent := ConfigPage.Surface;
|
||||||
|
ServerHostEdit.Left := ScaleX(0);
|
||||||
|
ServerHostEdit.Top := ScaleY(8);
|
||||||
|
ServerHostEdit.Width := ScaleX(420);
|
||||||
|
ServerHostEdit.Text := 'aw.example.local';
|
||||||
|
|
||||||
|
ServerPortEdit := TEdit.Create(ConfigPage);
|
||||||
|
ServerPortEdit.Parent := ConfigPage.Surface;
|
||||||
|
ServerPortEdit.Left := ScaleX(0);
|
||||||
|
ServerPortEdit.Top := ScaleY(40);
|
||||||
|
ServerPortEdit.Width := ScaleX(120);
|
||||||
|
ServerPortEdit.Text := '5600';
|
||||||
|
|
||||||
|
DomainEdit := TEdit.Create(ConfigPage);
|
||||||
|
DomainEdit.Parent := ConfigPage.Surface;
|
||||||
|
DomainEdit.Left := ScaleX(0);
|
||||||
|
DomainEdit.Top := ScaleY(72);
|
||||||
|
DomainEdit.Width := ScaleX(260);
|
||||||
|
DomainEdit.Text := 'CONTOSO';
|
||||||
|
|
||||||
|
UsersEdit := TEdit.Create(ConfigPage);
|
||||||
|
UsersEdit.Parent := ConfigPage.Surface;
|
||||||
|
UsersEdit.Left := ScaleX(0);
|
||||||
|
UsersEdit.Top := ScaleY(104);
|
||||||
|
UsersEdit.Width := ScaleX(420);
|
||||||
|
UsersEdit.Text := 'user1,user2';
|
||||||
|
|
||||||
|
InstallRootEdit := TEdit.Create(ConfigPage);
|
||||||
|
InstallRootEdit.Parent := ConfigPage.Surface;
|
||||||
|
InstallRootEdit.Left := ScaleX(0);
|
||||||
|
InstallRootEdit.Top := ScaleY(136);
|
||||||
|
InstallRootEdit.Width := ScaleX(420);
|
||||||
|
InstallRootEdit.Text := 'C:\Program Files\ActivityWatch';
|
||||||
|
|
||||||
|
StateRootEdit := TEdit.Create(ConfigPage);
|
||||||
|
StateRootEdit.Parent := ConfigPage.Surface;
|
||||||
|
StateRootEdit.Left := ScaleX(0);
|
||||||
|
StateRootEdit.Top := ScaleY(168);
|
||||||
|
StateRootEdit.Width := ScaleX(420);
|
||||||
|
StateRootEdit.Text := 'C:\ProgramData\ActivityWatch';
|
||||||
|
|
||||||
|
AfkCheck := TNewCheckBox.Create(ConfigPage);
|
||||||
|
AfkCheck.Parent := ConfigPage.Surface;
|
||||||
|
AfkCheck.Top := ScaleY(200);
|
||||||
|
AfkCheck.Caption := 'Enable AFK watcher';
|
||||||
|
AfkCheck.Checked := True;
|
||||||
|
|
||||||
|
WindowCheck := TNewCheckBox.Create(ConfigPage);
|
||||||
|
WindowCheck.Parent := ConfigPage.Surface;
|
||||||
|
WindowCheck.Top := ScaleY(224);
|
||||||
|
WindowCheck.Caption := 'Enable Window watcher';
|
||||||
|
WindowCheck.Checked := True;
|
||||||
|
ApplyCmdParamIfPresent('SERVERHOST', ServerHostEdit);
|
||||||
|
ApplyCmdParamIfPresent('SERVERPORT', ServerPortEdit);
|
||||||
|
ApplyCmdParamIfPresent('DOMAIN', DomainEdit);
|
||||||
|
ApplyCmdParamIfPresent('USERS', UsersEdit);
|
||||||
|
ApplyCmdParamIfPresent('INSTALLROOT', InstallRootEdit);
|
||||||
|
ApplyCmdParamIfPresent('STATEROOT', StateRootEdit);
|
||||||
|
ApplyCmdBoolIfPresent('AFKENABLED', AfkCheck);
|
||||||
|
ApplyCmdBoolIfPresent('WINDOWENABLED', WindowCheck);
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure ApplyCmdParamIfPresent(const ParamName: string; Edit: TEdit);
|
||||||
|
var
|
||||||
|
V: string;
|
||||||
|
begin
|
||||||
|
V := ExpandConstant('{param:' + ParamName + '|}');
|
||||||
|
if Trim(V) <> '' then
|
||||||
|
Edit.Text := V;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure ApplyCmdBoolIfPresent(const ParamName: string; Check: TNewCheckBox);
|
||||||
|
var
|
||||||
|
V: string;
|
||||||
|
begin
|
||||||
|
V := Lowercase(Trim(ExpandConstant('{param:' + ParamName + '|}')));
|
||||||
|
if (V = '1') or (V = 'true') or (V = 'yes') then
|
||||||
|
Check.Checked := True
|
||||||
|
else if (V = '0') or (V = 'false') or (V = 'no') then
|
||||||
|
Check.Checked := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function NextButtonClick(CurPageID: Integer): Boolean;
|
||||||
|
var
|
||||||
|
PortNum: Integer;
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
if CurPageID = ConfigPage.ID then
|
||||||
|
begin
|
||||||
|
if Trim(ServerHostEdit.Text) = '' then
|
||||||
|
begin
|
||||||
|
MsgBox('ServerHost is required.', mbError, MB_OK);
|
||||||
|
Result := False;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
PortNum := StrToIntDef(Trim(ServerPortEdit.Text), 0);
|
||||||
|
if (PortNum < 1) or (PortNum > 65535) then
|
||||||
|
begin
|
||||||
|
MsgBox('ServerPort must be in range 1..65535.', mbError, MB_OK);
|
||||||
|
Result := False;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if Trim(UsersEdit.Text) = '' then
|
||||||
|
begin
|
||||||
|
MsgBox('Users list is required (comma-separated).', mbError, MB_OK);
|
||||||
|
Result := False;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetServerHost(Param: string): string;
|
||||||
|
begin
|
||||||
|
Result := Trim(ServerHostEdit.Text);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetServerPort(Param: string): string;
|
||||||
|
begin
|
||||||
|
Result := Trim(ServerPortEdit.Text);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetDomain(Param: string): string;
|
||||||
|
begin
|
||||||
|
Result := Trim(DomainEdit.Text);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetUsers(Param: string): string;
|
||||||
|
begin
|
||||||
|
Result := Trim(UsersEdit.Text);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetInstallRoot(Param: string): string;
|
||||||
|
begin
|
||||||
|
Result := Trim(InstallRootEdit.Text);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetStateRoot(Param: string): string;
|
||||||
|
begin
|
||||||
|
Result := Trim(StateRootEdit.Text);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetAfkEnabled(Param: string): string;
|
||||||
|
begin
|
||||||
|
if AfkCheck.Checked then Result := '$true' else Result := '$false';
|
||||||
|
end;
|
||||||
|
|
||||||
|
function GetWindowEnabled(Param: string): string;
|
||||||
|
begin
|
||||||
|
if WindowCheck.Checked then Result := '$true' else Result := '$false';
|
||||||
|
end;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$ServerHost,
|
||||||
|
[int]$ServerPort = 5600,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Domain,
|
||||||
|
[Parameter(Mandatory=$true)][string]$Users,
|
||||||
|
[string]$InstallRoot = 'C:\Program Files\ActivityWatch',
|
||||||
|
[string]$StateRoot = 'C:\ProgramData\ActivityWatch',
|
||||||
|
[string]$CustomRulesPath,
|
||||||
|
[string]$CustomPolicyPath,
|
||||||
|
[bool]$AfkEnabled = $true,
|
||||||
|
[bool]$WindowEnabled = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$ensembleScript = Join-Path $scriptRoot 'deploy-ensemble.ps1'
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $ensembleScript)) {
|
||||||
|
throw "deploy-ensemble.ps1 not found at $ensembleScript"
|
||||||
|
}
|
||||||
|
|
||||||
|
$usersList = $Users.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
||||||
|
if (-not $usersList -or $usersList.Count -eq 0) {
|
||||||
|
throw 'Users must contain at least one username.'
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = @{
|
||||||
|
ServerHost = $ServerHost
|
||||||
|
ServerPort = $ServerPort
|
||||||
|
Domain = $Domain
|
||||||
|
Users = $usersList
|
||||||
|
InstallRoot = $InstallRoot
|
||||||
|
StateRoot = $StateRoot
|
||||||
|
AfkEnabled = $AfkEnabled
|
||||||
|
WindowEnabled = $WindowEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($CustomRulesPath -and (Test-Path -LiteralPath $CustomRulesPath)) {
|
||||||
|
$params['CustomRulesPath'] = $CustomRulesPath
|
||||||
|
}
|
||||||
|
if ($CustomPolicyPath -and (Test-Path -LiteralPath $CustomPolicyPath)) {
|
||||||
|
$params['CustomPolicyPath'] = $CustomPolicyPath
|
||||||
|
}
|
||||||
|
|
||||||
|
& $ensembleScript @params
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# AWatch-rus Inno Setup installer skeleton
|
||||||
|
|
||||||
|
This directory contains a ready-to-build scaffold for a classic Setup.exe installer.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
- `AWatch-rus-Setup.iss` — Inno Setup script.
|
||||||
|
- `Invoke-AWatchRusInstall.ps1` — wrapper that invokes `deploy-ensemble.ps1` with installer parameters.
|
||||||
|
- `build-installer.ps1` — one-click local build script for Inno Setup.
|
||||||
|
|
||||||
|
## Local build (one-click)
|
||||||
|
```powershell
|
||||||
|
cd .\windows\installer
|
||||||
|
.\build-installer.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional custom path to compiler:
|
||||||
|
```powershell
|
||||||
|
.\build-installer.ps1 -IsccPath 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Silent deployment parameters (SCCM/Intune/GPO)
|
||||||
|
Installer supports command-line parameters:
|
||||||
|
- `/SERVERHOST=aw.example.local`
|
||||||
|
- `/SERVERPORT=5600`
|
||||||
|
- `/DOMAIN=CONTOSO`
|
||||||
|
- `/USERS=user1,user2`
|
||||||
|
- `/INSTALLROOT="C:\Program Files\ActivityWatch"`
|
||||||
|
- `/STATEROOT="C:\ProgramData\ActivityWatch"`
|
||||||
|
- `/AFKENABLED=true|false`
|
||||||
|
- `/WINDOWENABLED=true|false`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```powershell
|
||||||
|
AWatch-rus-Setup.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART `
|
||||||
|
/SERVERHOST=aw.example.local /SERVERPORT=5600 /DOMAIN=CONTOSO `
|
||||||
|
/USERS=user1,user2 /AFKENABLED=true /WINDOWENABLED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI build and optional code signing
|
||||||
|
A GitHub Actions workflow builds the installer on `windows-latest` and uploads `Setup.exe` as an artifact.
|
||||||
|
|
||||||
|
Optional production signing is enabled via `SignTool=byparam ...` in `.iss` and expects `SIGNTOOL_CMD`.
|
||||||
|
Set repository secret `SIGNTOOL_CMD` with your full sign command template.
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
- Requires Administrator privileges.
|
||||||
|
- Copies toolkit scripts into `{app}\tools`.
|
||||||
|
- Collects server/domain/users parameters via wizard page (or command-line silent params).
|
||||||
|
- Runs `Invoke-AWatchRusInstall.ps1` -> `deploy-ensemble.ps1`.
|
||||||
|
- Runs validation at the end.
|
||||||
|
- Uninstall step removes `ActivityWatch` scheduled tasks.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$IsccPath = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
|
||||||
|
[string]$IssPath = "$PSScriptRoot\AWatch-rus-Setup.iss",
|
||||||
|
[switch]$SkipClean
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $IssPath)) {
|
||||||
|
throw "ISS file not found: $IssPath"
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $IsccPath)) {
|
||||||
|
throw "ISCC.exe not found: $IsccPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputDir = Join-Path $PSScriptRoot 'output'
|
||||||
|
if ((-not $SkipClean) -and (Test-Path -LiteralPath $outputDir)) {
|
||||||
|
Remove-Item -LiteralPath $outputDir -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Building installer from: $IssPath"
|
||||||
|
& $IsccPath $IssPath
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Inno Setup build failed with code: $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
$artifact = Get-ChildItem -Path $outputDir -Filter '*.exe' -File | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||||
|
if (-not $artifact) {
|
||||||
|
throw "Build finished but no .exe artifact found in: $outputDir"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Installer artifact: $($artifact.FullName)"
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
param(
|
||||||
|
[string]$ConfigPath = 'C:\ProgramData\ActivityWatch\deployment-config.json',
|
||||||
|
[string]$Hostname,
|
||||||
|
[int]$PollSeconds = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
function Get-Config {
|
||||||
|
param([string]$Path)
|
||||||
|
if (-not (Test-Path -LiteralPath $Path)) { throw "Config not found: $Path" }
|
||||||
|
return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-AwJsonPost {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][string]$Uri,
|
||||||
|
[Parameter(Mandatory = $true)][string]$Json
|
||||||
|
)
|
||||||
|
$bytes = [Text.Encoding]::UTF8.GetBytes($Json)
|
||||||
|
Invoke-RestMethod -Method Post -Uri $Uri -ContentType 'application/json; charset=utf-8' -Body $bytes | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-Bucket {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][string]$ApiBase,
|
||||||
|
[Parameter(Mandatory = $true)][string]$BucketId,
|
||||||
|
[Parameter(Mandatory = $true)][string]$HostnameValue
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
Invoke-RestMethod -Method Get -Uri "$ApiBase/buckets/$BucketId" | Out-Null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = @{
|
||||||
|
client = 'aw-worktime-session-collector'
|
||||||
|
type = 'aw.worktime.session'
|
||||||
|
hostname = $HostnameValue
|
||||||
|
} | ConvertTo-Json -Compress
|
||||||
|
Invoke-AwJsonPost -Uri "$ApiBase/buckets/$BucketId" -Json $body
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SessionRecords {
|
||||||
|
$records = @()
|
||||||
|
try {
|
||||||
|
$lines = (quser 2>$null)
|
||||||
|
if (-not $lines) { return @() }
|
||||||
|
foreach ($line in $lines | Select-Object -Skip 1) {
|
||||||
|
$clean = ($line -replace '^\s*>?', '').Trim()
|
||||||
|
if (-not $clean) { continue }
|
||||||
|
$parts = $clean -split '\s+'
|
||||||
|
if ($parts.Count -lt 4) { continue }
|
||||||
|
$user = $parts[0]
|
||||||
|
$sessionName = $parts[1]
|
||||||
|
$sessionId = 0
|
||||||
|
if ($parts[2] -match '^\d+$') {
|
||||||
|
$sessionId = [int]$parts[2]
|
||||||
|
}
|
||||||
|
$state = $parts[3]
|
||||||
|
$records += [pscustomobject]@{
|
||||||
|
username = $user
|
||||||
|
sessionName = $sessionName
|
||||||
|
sessionId = $sessionId
|
||||||
|
state = $state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
return $records
|
||||||
|
}
|
||||||
|
|
||||||
|
$cfg = Get-Config -Path $ConfigPath
|
||||||
|
$hostValue = if ($Hostname) { $Hostname } else { [string]$env:COMPUTERNAME }
|
||||||
|
$apiBase = '{0}://{1}:{2}/api/0' -f [string]$cfg.server.scheme, [string]$cfg.server.host, [string]$cfg.server.port
|
||||||
|
$bucketId = 'aw-worktime-sessions_' + $hostValue
|
||||||
|
$pulse = 120
|
||||||
|
$sleepSec = if ($PollSeconds -gt 0) { $PollSeconds } elseif ($cfg.collector -and $cfg.collector.pollSeconds) { [int]$cfg.collector.pollSeconds } else { 30 }
|
||||||
|
|
||||||
|
Ensure-Bucket -ApiBase $apiBase -BucketId $bucketId -HostnameValue $hostValue
|
||||||
|
|
||||||
|
while ($true) {
|
||||||
|
$now = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')
|
||||||
|
$records = Get-SessionRecords
|
||||||
|
if (-not $records -or $records.Count -eq 0) {
|
||||||
|
$records = @([pscustomobject]@{
|
||||||
|
username = $env:USERNAME
|
||||||
|
sessionName = ''
|
||||||
|
sessionId = (Get-Process -Id $PID).SessionId
|
||||||
|
state = 'Unknown'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rec in $records) {
|
||||||
|
$payload = @{
|
||||||
|
timestamp = $now
|
||||||
|
duration = 0
|
||||||
|
data = @{
|
||||||
|
username = [string]$rec.username
|
||||||
|
userId = "$($env:USERDOMAIN)\$($rec.username)"
|
||||||
|
sessionId = [int]$rec.sessionId
|
||||||
|
sessionName = [string]$rec.sessionName
|
||||||
|
state = [string]$rec.state
|
||||||
|
active = ($rec.state -match 'Active')
|
||||||
|
hostname = $hostValue
|
||||||
|
source = 'worktime-session-collector'
|
||||||
|
}
|
||||||
|
} | ConvertTo-Json -Depth 6 -Compress
|
||||||
|
try {
|
||||||
|
Invoke-AwJsonPost -Uri "$apiBase/buckets/$bucketId/heartbeat?pulsetime=$pulse" -Json $payload
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Seconds $sleepSec
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user