This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Getting Started

Get started with psLens: install the SWS framework, deploy psLens, and connect it to your PeopleSoft environment in minutes.

Getting Started with psLens

Install SWS in PeopleSoft, run the psLens container, point it at SWS, log in. The pages in order:

Overview

psLens is a single self-contained application: a Go binary that serves the web interface and stores report data locally. There is no separate database to manage. It connects to your PeopleSoft environment through the SWS (Secure Web Services) framework, which you must install and configure in PeopleSoft first.

psLens Dashboard showing active alerts and database connections

The psLens dashboard — your starting point for monitoring PeopleSoft environments

Hosting psLens in production? Read Deployment Options for HTTPS, version pinning, backups, and upgrade paths.

Setup Steps

  1. Read the Architecture Overview to see how the pieces fit together
  2. Install the SWS Framework in your PeopleSoft environment
  3. Install psLens on your server
  4. Configure psLens with your database connection details
  5. Start psLens and open the dashboard in your browser

Quick Start

After you install the SWS framework in PeopleSoft:

# Create a directory for psLens
mkdir pslens && cd pslens

# Create config.yaml with your PeopleSoft connection details
# (see Configuration for full details)

# Start psLens with Docker
docker compose up -d

# Open http://localhost:8080 in your browser

Not Ready to Install Yet?

If you are still evaluating architecture, security, or operator workflow, see it running first. A live demo is the quickest way to answer fit questions before you commit to an install path.

1 - Architecture Overview

How psLens connects to PeopleSoft: a small SWS framework inside your PeopleSoft environment, and a psLens Docker container hosted externally.

Architecture Overview

psLens has only two moving parts:

  1. The SWS framework, a small Integration Broker service installed inside your PeopleSoft environment.
  2. The psLens application, a single Docker container hosted externally (default: fly.io) or on your own infrastructure.

psLens queries PeopleSoft through SWS over HTTPS. When alert delivery is configured, the container also sends notifications to an SMTP server or chat webhook. Your team accesses the application in a browser over HTTPS.

The Short Version

  • Two components, nothing else. SWS runs inside PeopleSoft. psLens runs as a Docker container outside it.
  • Traffic only flows one way. psLens calls SWS over HTTPS. SWS never sends requests to psLens.
  • One protocol. REST + HTTP basic auth + psoftQL JSON queries. No database drivers, no ODBC, no jump hosts.
  • One scope. SWS only answers queries against PeopleTools metadata tables that you whitelist. SWS rejects anything outside the list before the query reaches the database.
  • Dedicated deployment per customer. No shared psLens app, no shared storage, no multi-tenant SaaS backend.

How the Pieces Fit

%%{init: {"theme": "redux-color", "flowchart": {"htmlLabels": true, "padding": 30, "nodeSpacing": 70, "rankSpacing": 70, "subGraphTitleMargin": {"top": 20, "bottom": 32}}}}%%
flowchart LR
    USER(["Your team's<br/>web browser"])
    APP["psLens<br/>Docker container"]

    subgraph PS["Your PeopleSoft Environment"]
        SWS["SWS Framework<br/>REST endpoint<br/>Read-only"]
        DB[("PeopleSoft DB<br/>read-only<br/>whitelisted tables")]
        SWS --> DB
    end

    EMAIL["Alerts via Email<br/>SMTP"]
    CHAT["Alerts via Chat - Teams/Slack/Google"]

    USER -- "HTTPS" --> APP
    APP -- "HTTPS · Basic auth<br/>psoftQL JSON" --> SWS
    APP -- "SMTP - Optional" --> EMAIL
    APP -- "HTTPS webhook" --> CHAT

    classDef ps fill:#e8f4fd,stroke:#0d6efd,stroke-width:2px,color:#000
    classDef ext fill:#fff5e6,stroke:#fd7e14,stroke-width:2px,color:#000
    classDef user fill:#e9f7ef,stroke:#198754,stroke-width:2px,color:#000
    classDef notify fill:#f3f0ff,stroke:#6f42c1,stroke-width:2px,color:#000
    classDef subgraphStyle fill:#fafafa,stroke:#666,stroke-width:1px,color:#000

    class SWS,DB ps
    class APP ext
    class USER user
    class EMAIL,CHAT notify
    class PS,EXT,ALERTS subgraphStyle

Your team reaches psLens with any current web browser over HTTPS. There is no desktop client to install. The browser never talks to PeopleSoft directly. If alert delivery is configured, psLens sends email through SMTP and posts to Slack, Microsoft Teams, or Google Chat through incoming webhooks.

For the reasoning behind this design, see Why psLens Runs on Go, Hypermedia, and SWS.

Inside Your PeopleSoft Environment: SWS

The SWS framework is a small Integration Broker service Cedar Hills Group provides. Your PeopleSoft team installs it once, alongside everything else PeopleSoft already runs. It exposes a single REST endpoint that accepts a structured query language called psoftQL and returns JSON.

What SWS gives you control over:

  • The whitelist. Your PeopleSoft admins decide which PeopleTools metadata tables SWS can read. psLens cannot ask for anything off the list.
  • The credentials. SWS authenticates incoming requests with HTTP basic auth. Your team owns the token. Rotating the token is a configuration change on both ends.
  • The audit trail. Calls land on your Integration Broker like any other inbound service, visible in the tooling your team already monitors.

No PeopleSoft database username or password is ever shared with psLens.

Outside Your Environment: psLens Container

psLens itself is a single Docker container: one Go binary, with embedded NATS for storing alert history and report output. That’s the entire runtime.

  • Default deployment is on fly.io as a managed instance dedicated to your organization.
  • Self-hosting is fully supported. Docker, docker-compose, bare-metal, and air-gapped environments are all covered in the installation guide.
  • Stateless toward PeopleSoft. psLens does not copy your business data. The only things it persists are alert history and report output, both inside its own dedicated storage. See the Security & Trust page for details on what is and isn’t stored.

When you upgrade psLens, you pull a new container image. Nothing inside PeopleSoft changes.

Server-Driven Hypermedia Architecture (Zero Client-Side Storage)

psLens renders pages on the server with Go templates and streams updates over Server-Sent Events using Datastar. There is no React, no Angular, no JSON API. The browser receives HTML fragments over SSE (TLS at the transport layer) and renders them.

Two consequences fall out of this:

  • The browser stores nothing from PeopleSoft. psLens does not write to LocalStorage, SessionStorage, or IndexedDB. Closing the tab takes the active session data with it.
  • No JSON wire format. The server sends pre-rendered HTML. There is no client-side data structure for an attacker to scrape or tamper with. Page transitions are server round-trips of a few KB of HTML, and the browser tab holds no result set.

Why This Shape

  • Bounded blast radius. If an attacker compromises the psLens container, the SWS whitelist limits what they can read. They cannot access PeopleSoft directly, run PeopleCode, or pivot to other tables.
  • Easy to upgrade and operate. New psLens features ship as a new container image. No PeopleSoft change request, no App Designer migration, no downtime on the PeopleSoft side.
  • Multi-environment from day one. A single psLens deployment can connect to DEV, TEST, and PROD at the same time. Point at the SWS endpoint in each environment via separate database entries in config.yaml.

Where Next

  • Installation installs SWS in PeopleSoft and runs the psLens container.
  • Configuration wires psLens to your PeopleSoft environments.
  • Security & Trust covers the read-only design, dedicated deployment, and what psLens does and doesn’t store.

2 - Installation

psLens uses a Cedar Hills Group, Inc. web service inside your PeopleSoft environment to connect to the psLens web application. This page describes how to install and configure the SWS framework for psLens.

The psLens web application connects to your PeopleSoft environments with a limited version of our SWS framework.

You will install a PeopleSoft Application Designer project (CHG_PSLENS) with two web services that expose a REST API for reading data from PeopleSoft tables. psLens calls the web services instead of talking to the database directly. The CHG_PSLENS_SWSPQL service operation performs most of the work. A secondary operation, CHG_PSLENS_METADATA_GET, returns metadata that database tables do not store. It relies on proprietary PeopleCode functions.

We do NOT deliver the full SWS framework to customers for psLens. The full SWS framework is a commercial product. It provides a query language, a web service framework, and tools for managing REST APIs on PeopleSoft. psLens uses a small subset of the framework to read PeopleSoft tables.

This installation guide assumes that Cedar Hills Group, Inc. hosts the psLens web application. If you host psLens yourself, see Deployment Options for local setup instructions. Hosting psLens with Cedar Hills Group provides the fastest setup. It is our recommended option for most customers.

%%{init: {"theme": "redux-color", "flowchart": {"htmlLabels": true, "padding": 16, "nodeSpacing": 60, "rankSpacing": 80, "subGraphTitleMargin": {"top": 10, "bottom": 14}}}}%%

flowchart LR

  subgraph EXT["psLens deployment"]
    psLens[psLens Web Application]
  end
  subgraph PS["Your PeopleSoft Infrastructure"]
    IB.DEV["DEV Integration Broker"]
    DB.DEV[("DEV PeopleSoft DB<br/>read-only<br/>whitelisted tables")]
    IB.DEV --> DB.DEV
    IB.TST["TST Integration Broker"]
    DB.TST[("TST PeopleSoft DB<br/>read-only<br/>whitelisted tables")]
    IB.TST --> DB.TST
    IB.PROD["PROD Integration Broker"]
    DB.PROD[("PROD PeopleSoft DB<br/>read-only<br/>whitelisted tables")]
    IB.PROD --> DB.PROD
end

  psLens -->|SWS https| IB.DEV
  psLens -->|SWS https| IB.TST
  psLens -->|SWS https| IB.PROD

The high-level installation steps are:

  • Download the CHG_PSLENS Application Designer project archive from your psLens instance (see details below).
  • Import the project into your PeopleSoft DEV environment (see details below).
  • Run the whitelist insert statements to grant table access (see details below).
  • Configure the service account for API access and assign the required permissions (see details below).
  • Ensure your psLens instance can reach the PeopleSoft Integration Gateway on port 443 (or your configured port).
  • Work with Cedar Hills Group to connect the psLens application to your environments.

Download the PeopleSoft Project

The Application Designer project files are delivered directly inside your running psLens instance:

  • Sign in to your psLens web application.
  • In the left navigation sidebar under Settings & Configuration, select PeopleSoft Project (or navigate to Settings and click the PeopleSoft Project card).
  • On the PeopleSoft Project Download page, the latest release archive is featured at the top. Releases are named by ISO date (YYYY-MM-DD-pslens-psoft-project.zip). Always install the most recent release.
  • Click Download Latest Project (.zip) to save the archive to your workstation.
  • Extract the archive. The extracted directory contains the CHG_PSLENS Application Designer project.

PeopleSoft Project Installation

You will repeat these steps for each PeopleSoft environment you want to connect to psLens (DEV, TST, PROD). We recommend starting with DEV first, then TST, then PROD. The steps are the same for each environment. You can follow your standard change management process for importing the project into TST and PROD.

  • Using Application Designer, import the project into your PeopleSoft environment from the extracted folder (Tools → Copy Project → From File).
  • After importing the project, you need to build the CHG_PSLENS_WL table.
    • This holds the list of whitelisted tables that psLens can read. You will run the whitelist inserts to populate this table.
Build the imported Application Designer project

Build

  • Compile all the project PeopleCode to ensure that the project was fully imported. There should be no errors. If there are errors, please contact Cedar Hills Group for assistance or try to re-import the project.
Compile the imported Application Designer project PeopleCode

Compile

Whitelist Inserts

You will need to run the whitelist inserts to allow psLens to read the tables it needs. Please see the Whitelist Tables page for the full list of inserts. You will need to run these inserts in each PeopleSoft environment you want to connect to psLens (DEV, TST, PROD).

PeopleSoft Service Account

psLens uses a dedicated PeopleSoft operator ID (OPRID) for API access. This is the account that psLens uses to authenticate to the SWS framework.

The naming convention for the account is CHG_PSLENS_API_USER. You can use a different name if you prefer, but you will need to update the psLens configuration to match.

  • Create OPRID: CHG_PSLENS_API_USER
    • Add a complex password that meets your security requirements. This password will be used in the psLens configuration. Use a password manager to generate a strong password.
  • Grant the following permissions to the account:
    • ID Type: NONE
    • Role: CHG_PSLENS_API_USER
      • Permission List: CHG_PSLENS_API_USER

The account should NOT have any other permissions or roles. It should only have the permissions required to read the whitelisted tables. If you have any sort of dynamic security roles, make sure that they do not grant any additional permissions to this account. The account should be as limited as possible. The permission list that we deliver in the project is the entire set of permissions that psLens needs to read the whitelisted tables. If you have any questions about the permissions, please contact Cedar Hills Group.

psLens to PeopleSoft Connectivity

We will cover the most common configuration where Cedar Hills Group, Inc. hosts the psLens web application and you have a network team that can allow the psLens server to reach the PeopleSoft Integration Gateway.

The default configuration for psLens is to connect to the PeopleSoft Integration Gateway over HTTPS from the internet. The psLens server must be able to reach the PeopleSoft Integration Gateway on port 443 (or your configured port). This often requires a firewall rule to allow the psLens server to reach the PeopleSoft Integration Gateway. The hostname of your PeopleSoft Integration Gateway must be resolvable from the psLens server. This is often an entry in your public DNS. Most organizations do NOT expose the PeopleSoft Integration Gateway to the public internet.

If your organization exposes the PeopleSoft Integration Gateway to the public internet, you likely will not need to make any changes to your firewall.

Cedar Hills Group configures a dedicated egress IP address for your psLens instance. Work with your network team to allow this IP address to reach the PeopleSoft Integration Gateway on port 443 (or your configured port). If you have questions about connectivity, contact Cedar Hills Group.

%%{init: {"theme": "redux-color", "flowchart": {"htmlLabels": true, "padding": 16, "nodeSpacing": 60, "rankSpacing": 80, "subGraphTitleMargin": {"top": 10, "bottom": 14}}}}%%

flowchart LR
  psLens[psLens Web Application]
  subgraph PS["Your Infrastructure"]
    DNS["External DNS"]
    FW["Firewall / Network Team"]

    IB.DEV["DEV Integration Broker"]
    DB.DEV[("DEV PeopleSoft DB<br/>read-only<br/>whitelisted tables")]
    IB.DEV --> DB.DEV
    FW --> IB.DEV
  end

  psLens -->|SWS https| FW
  psLens -->|DNS lookup| DNS

Curl Testing

PeopleSoft administrators often want to test connectivity between psLens and the PeopleSoft Integration Gateway before configuring the web application. The following curl command tests connectivity. Replace the placeholders with your actual values.

  • PS_LENS_API_USER - The PeopleSoft operator ID that psLens uses to authenticate to the SWS framework. This is the account that you created in the previous step. The default value is CHG_PSLENS_API_USER.
  • PS_LENS_API_PASSWORD - The password for the PeopleSoft operator ID that psLens uses to authenticate to the SWS framework. This is the password that you set when you created the account.
  • PS_HOST - The hostname of your PeopleSoft Integration Gateway. psLens uses this hostname to connect to the PeopleSoft Integration Gateway. The psLens server must resolve this hostname.
  • PS_PORT - The port your Integration Gateway listens on. The PeopleSoft default for HTTP is port 8000. HTTPS frequently uses port 8443.
  • PS_NODE - The name of the PeopleSoft node that fronts the REST listening connector. The default value is PSFT_CS. Use the node that matches your environment.
PS_LENS_API_USER='CHG_PSLENS_API_USER'
PS_LENS_API_PASSWORD='your-password-here'
PS_HOST='psft.example.com'
PS_PORT='8000'
PS_NODE='PSFT_CS'

curl --request POST \
  --url "https://${PS_HOST}:${PS_PORT}/PSIGW/RESTListeningConnector/${PS_NODE}/CHG_PSLENS_SWSPQL/" \
  -u "${PS_LENS_API_USER}:${PS_LENS_API_PASSWORD}" \
  --header 'Content-Type: application/json' \
  --data '{
  "isDebugMode": false,
  "includeFieldTypes": true,
  "includeAllDescriptions": true,
  "includeKeyFieldIndicators": true,
  "includeAllFieldLabels": true,
  "records": [
    {
      "recordName": "CHG_PSLENS_WL",
      "includeDescriptionsFor": [],
      "excludeFields": []
    }
  ]
}'

Keep single quotes around variable assignments. PeopleSoft passwords often contain !, $, or spaces. An unquoted value breaks in the shell before curl executes. In bash and zsh, an unquoted ! triggers history expansion, causing assignment failure and leaving curl to authenticate with an empty password.

A successful call returns HTTP 200 with a data.CHG_PSLENS_WL.fields array listing whitelisted RECNAME values. Two common failures:

  • 401 - The operator ID or password is incorrect, or an administrator locked the account.
  • 404 - The node name or service operation path is incorrect. Check PS_NODE against your created node. Make sure the CHG_PSLENS_SWSPQL service operation is active.

3 - Whitelist Tables

psLens controls which PeopleSoft tables can be queried through a whitelist table (CHG_PSLENS_WL). You need to whitelist every table that psLens reads.

Whitelist Tables

psLens uses a global whitelist table (CHG_PSLENS_WL) to control which PeopleSoft tables clients can query. You must whitelist every table that psLens reads. The Reference section documents the complete table list by feature area.

This page provides the SQL insert statements to run during installation. After running the statements, restart psLens or wait for cache refresh. Check the Settings > Database Connections page. Make sure the database status shows no missing-table warnings.

PeopleTools Tables Common to All Features

Run all of the inserts below. Each block matches a category in the Reference page. If you add new functionality to psLens that queries a new record, add it here too.

-- Whitelist record itself (optional: CHG_PSLENS_WL is always readable by the framework)
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('CHG_PSLENS_WL');

-- Security tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAUTHAS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAUTHBUSCOMP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAUTHITEM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAUTHPRCS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAUTHSIGNON');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAUTHWS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSCLASSDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMENUITEM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOBJGROUP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPRDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPROBJ');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSROLECLASS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSROLEDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSROLEUSER');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_ACC_GRP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_QUERY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PTACM_ACCESSTBL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPTSCRTY_ADS_A');

-- Metadata tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSBCDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSBCITEM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSDBFIELD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSDBFLDLABL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSKEYDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMENUDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSXFERITEM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPNLDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPNLFIELD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPNLGROUP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPNLGRPDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPROJECTDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPROJECTITEM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPRSMATTRVAL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPRSMDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPRSMPERM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPRSMSYSATTR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPRSMSYSATTRVL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSRECDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSRECFIELD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPTSF_SD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPTSF_SD_ATTR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPTSF_SD_DCATR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPTSF_SRCCAT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPTSF_SRCCATAT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSRECDDLPARM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIDXDDLPARM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSSPCDDLPARM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSDDLMODEL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSDDLDEFPARMS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPTIONSADDL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSTBLSPCCAT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSRECTBLSPC');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSFLDDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSFLDSEGDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSFLDFIELDDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSXLATITEM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SQLSTMT_TBL');

-- Integration Broker tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAPMSGPUBCON');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAPMSGPUBHDR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAPMSGSUBCON');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAPMSGDOMSTAT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAPMSGDSPSTAT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBLOGHDR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBRTNGDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBAPPLDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBAPPLOPR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBAPPURI');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBAPPMETHOD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBPARAM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBBASEPARAM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBTEMPLPARAM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBBASETMPLPRM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBAPPLSTATES');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSIBAPPLHDRPROP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGNODEDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSNODECONPROP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSNODEURITEXT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSNODESDOWN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPERATION');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPERATIONAC');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPERATIONURI');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPRHDLR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPRVERDFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPRVERDFNPARM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQUEUEDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQUEUEPART');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSRTNGDFNPARM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSRTNGDFNPROP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSSERVICE');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSSERVICEOPR');

-- Process Scheduler tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSDEFNGRP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSDEFNPNL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSJOBDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSJOBGRP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSJOBITEM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSJOBPNL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSJOBMESSAGE');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSMUTUALEXCL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSRECUR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSRECURDATE');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSRECUREXEMPT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPRCSRQST');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSDEFNNOTIFY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSDEFNCNTDIST');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSJOBNOTIFY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PRCSJOBCNTDIST');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SERVERDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSSERVERSTAT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SERVERCATEGORY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SERVERCLASS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SERVERNOTIFY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SERVEROPRTN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('DAEMONGROUP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('DAEMONGROUP_VW');

-- Developer tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAEAPPLDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAEAPPLSTATE');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAEAPPLTEMPTBL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAESECTDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAESTEPDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSAESTMTDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSCONTDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSCONTENT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGATTR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGCATDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGFLDOVR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGPARTS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGREC');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGSETDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSMSGVER');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPACKAGEDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPCMNAME');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPCMPROG');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPCMTXT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYFIELD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYRECORD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYSTATS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYEXECLOG');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYSELECT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYCRITERIA');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYEXPR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSQRYBIND');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSSQLDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSSQLTEXTDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSTREEDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSTREENODE');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSURLDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PT_URL_PROPS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSXPRPTDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSXPDATASRC');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSXPTMPLDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSXPTMPLFILEDEF');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSXPRPTVIEWER');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSCHGCTLDEF');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSCHGCTLLOCK');

-- Audit & user profile tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPTLOGINAUDIT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSACCESSLOG');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSPRUFDEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSUSEREMAIL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPRALIAS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSOPTIONS');

-- Applied Maintenance tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('MAINTENANCE_LOG');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSRELEASE');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('PSSTATUS');

Campus Solutions Specific Tables

Only run these in a Campus Solutions database. If you run them in a non-CS database, you will get errors about missing tables.

-- Campus Solutions row-level security tables
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('ES_SECURITY_DTL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('ES_SECURITY_TBL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('OPER_ROLE_DEFN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('OPR_DEFAULT_TBL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('OPR_DEF_TBL_CS');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('OPR_GRP_3C_TBL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('RUNCNT_USERPROF');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('RUN_CNTL_HR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('RUN_CNTL_SECVWU');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SAA_SCRTY_AARPT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SAD_TEST_SCTY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCCPU_SRTY_TBL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCC_GE_SCRTY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCC_NTF_OPR_CON');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCC_SCRTY_RLCAT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCC_SCRT_RUNCTL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCC_SL_TRN_SCTY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCC_STY_TBL_CMP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCC_VLTCNV_RCTL');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_ADM_ACTN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_APPL_CTR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_PROG_ACTN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_RECR_CTR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_ACAD');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_CAR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_INST');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_MLSTN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_PLAN');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_PROG');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_SRVC');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TBL_STGP');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SCRTY_TSCRPT');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SEV_PRG_SP_SCTY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SEV_SCHLCD_SCTY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SSR_AIR_OPRSCTY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SSR_ANID_SCRTY');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SSR_APT_ACT_SCR');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SSR_SCRTY_EXAM');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SSR_SCRTY_GRSTA');
INSERT INTO PS_CHG_PSLENS_WL (RECNAME) VALUES ('SSR_SCRTY_TSRPT');

4 - Configuration

psLens reads a config.yaml file from the same directory as the binary.

Configuration

psLens reads a config.yaml file from the same directory as the binary. There is no external database server to set up. An embedded NATS data store saves persistent state (configuration history, report results, and alert state).

Configuration Synchronization

psLens synchronizes active configuration automatically between config.yaml and the embedded NATS KV store:

  • Bare-Minimum Boot: The server can boot with zero configured database connections. Administrators can add database connections later directly through the Settings UI at /settings.
  • Disk Edit Recognition: On boot, psLens checks modification timestamps. If config.yaml is newer than the NATS KV snapshot, psLens syncs file changes into NATS KV automatically.
  • UI Auto Write-Back: Changes made via the Settings UI update NATS KV and automatically write back to config.yaml on disk.

Password & Secret Security Options

psLens provides three secure methods for passing database and server credentials:

  1. 1Password Secret References (op://...): Pass 1Password Secret URIs directly in config.yaml or JSON configurations:

    databases:
      - name: "PROD"
        username: "PSLENS_API"
        password: "op://Employee-Vault/PeopleSoft-Prod-API/password"
    
    smtp:
      host: "op://Employee-Vault/SMTP-Mailtrap/host"
      port: "op://Employee-Vault/SMTP-Mailtrap/port"
      username: "op://Employee-Vault/SMTP-Mailtrap/username"
      password: "op://Employee-Vault/SMTP-Mailtrap/password"
      fromName: "op://Employee-Vault/SMTP-Mailtrap/from_name"
      fromEmail: "op://Employee-Vault/SMTP-Mailtrap/from_email"
    
    auth:
      oidc:
        issuerUrl: "op://Employee-Vault/OIDC-Okta/issuer_url"
        clientId: "op://Employee-Vault/OIDC-Okta/client_id"
        clientSecret: "op://Employee-Vault/OIDC-Okta/client_secret"
        redirectUrl: "op://Employee-Vault/OIDC-Okta/redirect_url"
    

    Set OP_SERVICE_ACCOUNT_TOKEN in the environment. Secrets resolve in-memory at boot using the 1Password SDK and are never written back to disk or KV storage in plaintext.

  2. Master Key Encryption (PSLENS_MASTER_KEY): Set PSLENS_MASTER_KEY (a 64-character hex AES-256 key). psLens saves passwords in NATS KV and YAML as encrypted strings (ENC[...]).

  3. Environment Variable Overrides: Override passwords dynamically via environment variables without editing files:

    export PSLENS_DB_PROD_PASSWORD="YourPasswordHere"
    

Bare Minimum Configuration Example

To start psLens with zero pre-configured database connections, use the following minimal config.yaml:

server:
  port: 8080

Full Configuration Examples

psLens supports configuration in either YAML (config.yaml) or JSON (config.json) format. The application detects the format automatically on boot.

YAML Configuration Example (config.yaml)

# Schema validation pointer (optional, for IDE support)
# yaml-language-server: $schema=http://localhost:8080/static/config-schema.json

server:
  port: 8080
  host: "0.0.0.0"
  natsStoreDir: "./data/nats"
  projectStoreDir: "./data/projects"
  dmsStoreDir: "./data/dms"
  traceStoreDir: "./data/traces"
  appBaseURL: "http://localhost:8080"
  recentlyViewed:
    maxItems: 20

databases:
  - name: "PROD"
    description: "Production PeopleSoft HR"
    baseURL: "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_PSLENS_SWSPQL/"
    username: "PSLENS_API"
    password: "your-api-password"
    piaURL: "https://psft.example.com/psp/ps/"
    timezone: "America/Chicago"
    production: true
    alerts:
      enabled: true
      intervalMinutes: 10

  - name: "DEV"
    description: "Development Environment"
    baseURL: "https://psftdev.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_PSLENS_SWSPQL/"
    username: "PSLENS_API"
    password: "dev-api-password"
    timezone: "America/Chicago"
    production: false
    downtimes:
      - name: "Nightly Downtime"
        enabled: true
        startTime: "22:00"
        endTime: "06:00"
        daysOfWeek: [1, 2, 3, 4, 5]
      - name: "Weekend Maintenance"
        enabled: true
        startAt: "2026-07-04T00:00:00Z"
        endAt: "2026-07-05T23:59:59Z"

alerts:
  enabled: true
  intervalMinutes: 5
  checks:
    long_running_processes:
      enabled: true
      thresholdMinutes: 20
      anomalyMultiplier: 4.0
      anomalyMinBaselineMinutes: 10
    process_errors:
      enabled: true
      lookbackHours: 24
    ib_operation_errors:
      enabled: true
      lookbackHours: 24
    ib_pub_contract_errors:
      enabled: true
      lookbackHours: 24
    ib_sub_contract_errors:
      enabled: true
      lookbackHours: 24
    ib_operation_stalled:
      enabled: true
      thresholdMinutes: 30
    ib_pub_contract_stalled:
      enabled: true
      thresholdMinutes: 30
    ib_sub_contract_stalled:
      enabled: true
      thresholdMinutes: 30
  genericSWSAlerts:
    - id: "stale_users"
      name: "Stale User Accounts"
      enabled: true
      severity: "warning"
      alertOn: "row_found"
      message: "Warning: Stale user accounts detected"
      query:
        records:
          - recordName: "PSOPRDEFN"
            sqlWhereClause: "LASTUPDDTTM < CAST('2026-01-01' AS TIMESTAMP) AND ACCTLOCK = 0"
        rowLimit: 5

auth:
  enabled: true
  authorizedUsers:
    - "admin@example.com"
    - "auditor@example.com"

smtp:
  host: "smtp.mailtrap.io"
  port: "2525"
  username: "smtp-user"
  password: "smtp-password"
  fromName: "psLens Alerts"
  fromEmail: "alerts@pslens.example.com"

notifications:
  subscriptions:
    - id: "team-email"
      enabled: true
      alertTypes: ["*"]
      databases: ["PROD"]
      severityMin: "warning"
      type: "email"
      target: "psoft-alerts@example.com"
    - id: "slack-webhook"
      enabled: true
      alertTypes: ["process_errors", "ib_operation_errors"]
      databases: ["*"]
      type: "webhook"
      target: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
    - id: "teams-webhook"
      enabled: true
      alertTypes: ["*"]
      databases: ["*"]
      type: "webhook"
      target: "https://example.webhook.office.com/webhookb2/..."
    - id: "google-chat-webhook"
      enabled: true
      alertTypes: ["*"]
      databases: ["*"]
      type: "webhook"
      target: "https://chat.googleapis.com/v1/spaces/AAAA.../messages?key=...&token=..."

reports:
  - id: "security-full-access-permlists"
    enabled: true
    dbNames: ["PROD"]
    schedule:
      interval: "daily"
      timeOfDay: "02:00"
    emailTarget: "psoft-alerts@example.com"

JSON Configuration Example (config.json)

{
  "$schema": "http://localhost:8080/static/config-schema.json",
  "server": {
    "port": 8080,
    "host": "0.0.0.0",
    "natsStoreDir": "./data/nats",
    "projectStoreDir": "./data/projects",
    "dmsStoreDir": "./data/dms",
    "traceStoreDir": "./data/traces",
    "appBaseURL": "http://localhost:8080",
    "recentlyViewed": {
      "maxItems": 20
    }
  },
  "databases": [
    {
      "name": "PROD",
      "description": "Production PeopleSoft HR",
      "baseURL": "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_PSLENS_SWSPQL/",
      "username": "PSLENS_API",
      "password": "your-api-password",
      "piaURL": "https://psft.example.com/psp/ps/",
      "timezone": "America/Chicago",
      "production": true,
      "alerts": {
        "enabled": true,
        "intervalMinutes": 10
      }
    },
    {
      "name": "DEV",
      "description": "Development Environment",
      "baseURL": "https://psftdev.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_PSLENS_SWSPQL/",
      "username": "PSLENS_API",
      "password": "dev-api-password",
      "timezone": "America/Chicago",
      "production": false
    }
  ],
  "alerts": {
    "enabled": true,
    "intervalMinutes": 5,
    "checks": {
      "long_running_processes": {
        "enabled": true,
        "thresholdMinutes": 20,
        "anomalyMultiplier": 4.0,
        "anomalyMinBaselineMinutes": 10
      },
      "process_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_operation_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_pub_contract_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_sub_contract_errors": {
        "enabled": true,
        "lookbackHours": 24
      },
      "ib_operation_stalled": {
        "enabled": true,
        "thresholdMinutes": 30
      },
      "ib_pub_contract_stalled": {
        "enabled": true,
        "thresholdMinutes": 30
      },
      "ib_sub_contract_stalled": {
        "enabled": true,
        "thresholdMinutes": 30
      }
    },
    "genericSWSAlerts": [
      {
        "id": "stale_users",
        "name": "Stale User Accounts",
        "enabled": true,
        "severity": "warning",
        "alertOn": "row_found",
        "message": "Warning: Stale user accounts detected",
        "query": {
          "records": [
            {
              "recordName": "PSOPRDEFN",
              "sqlWhereClause": "LASTUPDDTTM < CAST('2026-01-01' AS TIMESTAMP) and ACCTLOCK = 0"
            }
          ],
          "rowLimit": 5
        }
      }
    ]
  },
  "auth": {
    "enabled": true,
    "authorizedUsers": [
      "admin@example.com",
      "auditor@example.com"
    ]
  },
  "smtp": {
    "host": "smtp.mailtrap.io",
    "port": "2525",
    "username": "smtp-user",
    "password": "smtp-password",
    "fromName": "psLens Alerts",
    "fromEmail": "alerts@pslens.example.com"
  },
  "notifications": {
    "subscriptions": [
      {
        "id": "team-email",
        "enabled": true,
        "alertTypes": ["*"],
        "databases": ["PROD"],
        "severityMin": "warning",
        "type": "email",
        "target": "psoft-alerts@example.com"
      },
      {
        "id": "slack-webhook",
        "enabled": true,
        "alertTypes": ["process_errors", "ib_operation_errors"],
        "databases": ["*"],
        "type": "webhook",
        "target": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
      },
      {
        "id": "teams-webhook",
        "enabled": true,
        "alertTypes": ["*"],
        "databases": ["*"],
        "type": "webhook",
        "target": "https://example.webhook.office.com/webhookb2/..."
      },
      {
        "id": "google-chat-webhook",
        "enabled": true,
        "alertTypes": ["*"],
        "databases": ["*"],
        "type": "webhook",
        "target": "https://chat.googleapis.com/v1/spaces/AAAA.../messages?key=...&token=..."
      }
    ]
  },
  "reports": [
    {
      "id": "security-full-access-permlists",
      "enabled": true,
      "dbNames": ["PROD"],
      "schedule": {
        "interval": "daily",
        "timeOfDay": "02:00"
      },
      "emailTarget": "psoft-alerts@example.com"
    }
  ]
}

Server Settings

The server section controls how psLens listens for incoming connections and where it stores data.

SettingDefaultDescription
port8080TCP port psLens listens on
host0.0.0.0Network interface to bind (use 127.0.0.1 to restrict to localhost)
natsStoreDir./data/natsDirectory for persistent NATS data (report results, alert history)
projectStoreDir./data/projectsDirectory for uploaded PeopleSoft project XML definitions
dmsStoreDir./data/dmsDirectory for imported Data Mover (DMS) script libraries
traceStoreDir./data/tracesDirectory for uploaded PeopleSoft trace files (.tracesql, .trc, .aet)
outageRetentionDays90Retention period in days for historical downtime and outage events stored in NATS JetStream.

Tip: Storage directories (natsStoreDir, traceStoreDir, etc.) can also be configured via environment variables (PSLENS_TRACE_STORE_DIR, PSLENS_PROJECT_STORE_DIR, etc.) and should be placed on persistent storage.


Database Connections

You can configure one or more PeopleSoft databases under the databases list. psLens monitors the health of each connection and shows status on the dashboard.

SettingRequiredDescription
nameYesShort identifier shown in the UI (e.g., PROD, DEV)
descriptionYesHuman-readable label for the database
baseURLYesFull URL to the SWS psoftQL endpoint, including the service name
usernameYesPeopleSoft operator ID for API authentication
passwordYesPassword for the operator ID
piaURLNoBase URL for PeopleSoft Internet Architecture (used for deep links to PeopleSoft pages, if supported)
timezoneNoIANA timezone name for the database server (e.g., America/Chicago). Defaults to UTC if not set. Used to interpret timestamps correctly.
downtimesNoList of scheduled downtime windows to suppress alerts and monitoring.
notificationsNoDatabase-specific notification delivery settings (bypasses global subscriptions).

Scheduled Downtimes

Downtimes allow silencing alerts and pausing connection health checks for non-production environments that go offline regularly (e.g., overnight or on weekends).

Each entry in the downtimes list supports the following settings:

SettingRequiredDescription
nameYesIdentifier for the downtime window.
enabledYesWhether the downtime rule is active.
startTimeNoStart time of day in HH:MM format (e.g., 22:00). Set together with endTime.
endTimeNoEnd time of day in HH:MM format (e.g., 06:00). An end time earlier than the start crosses midnight. 24:00 means end of day, so an all-day window is 00:00–24:00.
daysOfWeekNoArray of integers for days of week. 0 is Sunday, 1 is Monday, …, 6 is Saturday. Omit to apply every day. An overnight window belongs to the day it starts.
startAtNoSpecific start timestamp in RFC3339 format (e.g., 2026-07-04T00:00:00Z) for one-off maintenance.
endAtNoSpecific end timestamp in RFC3339 format (e.g., 2026-07-05T23:59:59Z) for one-off maintenance.

Values for startTime and endTime use wall-clock times in the connection timezone (UTC if unset). psLens checks rules when saving configuration. A rule must define a recurring window (startTime and endTime) or a one-off range (startAt and endAt).

During scheduled downtime, the connection manager sets database status to Downtime on the dashboard. psLens skips background alert checks and suppresses notification dispatches. The database does not count against overall health. When the window ends, psLens checks the connection immediately. It treats the first failed check as an outage. Size windows to cover system boot time.

A weekend window that spans from Saturday evening to Monday morning requires two rules. An overnight window belongs to its start day:

downtimes:
  - name: "Weekend nights"
    enabled: true
    startTime: "22:00"
    endTime: "06:00"
    daysOfWeek: [6, 0]   # Sat night -> Sun morning, Sun night -> Mon morning
  - name: "Sunday all day"
    enabled: true
    startTime: "00:00"
    endTime: "24:00"
    daysOfWeek: [0]

The baseURL Format

The baseURL is the Integration Broker REST endpoint for the SWS service. It follows this pattern:

https://{igw-host}:{port}/PSIGW/RESTListeningConnector/{database-name}/CHG_PSLENS_SWSPQL/

You must include the trailing slash.

Environment Variable Overrides

Sensitive settings like passwords can be overridden with environment variables to avoid storing them in config.yaml. The override format is:

PSLENS_DB_{NAME}_{FIELD}

Where {NAME} matches the name field of the database in config.yaml.

For Fly.io and Docker container compatibility, non-alphanumeric characters (such as dashes - or dots .) in database names are automatically converted to underscores (_) and uppercased.

For example, for a database named COOL-DB-FOR-DEV, you can use:

export PSLENS_DB_COOL_DB_FOR_DEV_PASSWORD="my-secure-password"

The raw name PSLENS_DB_COOL-DB-FOR-DEV_PASSWORD remains supported for backward compatibility where supported by your shell.

1Password Secret References

You can place 1Password secret references (op://...) directly in config.yaml or through the Settings UI. Supported fields include:

  • Database Connections: baseURL, username, password, piaURL
  • Database Notifications: notifications.emailTarget, notifications.webhookTarget
  • Database WebLib Health Checks: alerts.checks[].weblibTestTargets[].url, username, password
  • SMTP Mailer: smtp.host, smtp.port, smtp.username, smtp.password, smtp.fromName, smtp.fromEmail
  • Authentication (OIDC): auth.oidc.issuerUrl, auth.oidc.clientId, auth.oidc.clientSecret, auth.oidc.redirectUrl
  • Notifications & Subscriptions: notifications.email_lists[].emails, notifications.webhooks[].url, notifications.subscriptions[].target
databases:
  - name: "PROD"
    baseURL: "op://VaultName/ItemName/base_url"
    username: "PSLENS_API"
    password: "op://VaultName/ItemName/password"

To resolve 1Password secret references at boot, set a 1Password Service Account token in the environment:

export OP_SERVICE_ACCOUNT_TOKEN="op_service_account_token_here"

When you provide OP_SERVICE_ACCOUNT_TOKEN (or PSLENS_OP_SERVICE_ACCOUNT_TOKEN), psLens resolves all op:// references on boot using the 1Password SDK. Without this variable, psLens skips 1Password resolution.

Database-Specific Notifications

By default, global subscription rules route alert notifications for all databases (see Notifications & Webhooks Settings).

You can configure database-specific notifications under the notifications property of a database entry. This setting acts as an override. Database-level targets receive all alerts and resolutions for that database, bypassing global subscriptions.

Each entry in the notifications block supports the following settings:

SettingTypeDescription
emailEnabledBooleanActivates email notifications for this database.
emailTargetStringComma-separated list of recipient email addresses.
webhookEnabledBooleanActivates webhook notifications for this database.
webhookTargetStringWebhook ID or destination URL (Slack, MS Teams, Google Chat, or a generic endpoint).

Example database connection configuration with inline notifications:

databases:
  - name: "PROD"
    description: "Production Environment"
    baseURL: "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_PSLENS_SWSPQL/"
    username: "PSLENS_API"
    password: "securepassword"
    notifications:
      emailEnabled: true
      emailTarget: "prod-alerts@example.com"
      webhookEnabled: true
      webhookTarget: "https://hooks.slack.com/services/..."

Alerts Configuration

The alerts section controls the background alert checking system. See the Alerts section for details on what each alert detects.

Top-Level Alert Settings

SettingDefaultDescription
enabledfalseWhether to run background alert checks
intervalMinutes5How often (in minutes) to run all alert checks
genericSWSAlerts[]List of queryable SWS alerts. See Generic SWS Alerts for details.

Alert Check Settings

Each alert type under checks supports some or all of the following settings:

SettingDescription
enabledWhether this check is active
thresholdMinutesFor stalled/long-running checks: how many minutes before flagging (default varies by check). For long_running_processes, this is the fallback static threshold.
lookbackHoursFor error checks: how many hours back to look for failures (default varies by check)
excludeProcessesList of process names to skip (for process-related checks)
excludeOperationsList of IB operation names to skip (for Integration Broker checks)
anomalyMultiplierMultiplier applied to the rolling median duration to calculate the dynamic threshold (default: 4.0)
anomalyMinBaselineMinutesMinimum baseline duration in minutes. psLens caps dynamic thresholds to at least this value to prevent false alerts on very fast processes (default: 10)

Available Alert Checks

Check KeyNameDescription
long_running_processesLong-Running ProcessesFlags processes running longer than their rolling median-based expected runtime (or thresholdMinutes fallback)
process_errorsProcess ErrorsFinds processes that failed within lookbackHours (default: 24 hours)
ib_operation_errorsIB Operation ErrorsFinds async IB operations in Error or Timeout status within lookbackHours (default: 24 hours)
ib_pub_contract_errorsIB Publication Contract ErrorsFinds pub contracts in Error or Timeout status within lookbackHours (default: 24 hours)
ib_sub_contract_errorsIB Subscription Contract ErrorsFinds sub contracts in Error or Timeout status within lookbackHours (default: 24 hours)
ib_operation_stalledIB Operations StalledFinds async IB operations stuck in New or Working status longer than thresholdMinutes (default: 30 min)
ib_pub_contract_stalledIB Publication Contracts StalledFinds pub contracts stuck in New or Working status longer than thresholdMinutes (default: 30 min)
ib_sub_contract_stalledIB Subscription Contracts StalledFinds sub contracts stuck in New or Working status longer than thresholdMinutes (default: 30 min)
locked_oprid_processesLocked OPRID Scheduled ProcessesFinds queued or scheduled processes whose submitting OPRID has a locked account
backlogged_processesBacklogged ProcessesDetects processes currently stuck in a Queued or Blocked state longer than thresholdMinutes (default: 30 min)
queue_latencyQueue LatencyDetects processes that experienced a start delay (BEGINDTTM - RUNDTTM) greater than thresholdMinutes (default: 15 min)
failed_loginsFailed LoginsDetects users with excessive failed login attempts in PSPTLOGINAUDIT (defaults to > thresholdCount of 5)
process_run_checkProcess Run CheckMonitors configured critical processes and alerts when they haven’t run successfully within their configured time window
ib_operation_volumeAbnormal IB Operation VolumeDetects when IB operation instance volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_pub_contract_volumeAbnormal IB Pub Contract VolumeDetects when IB publication contract volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_sub_contract_volumeAbnormal IB Sub Contract VolumeDetects when IB subscription contract volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_sync_exceptionsIB Sync Operation ExceptionsDetects synchronous service operations with errors in PSIBLOGHDR within lookbackHours (default: 24 hours). Disabled by default.
no_process_completedNo Process CompletedFires when no process has successfully completed within the lookbackHours (default: 1 hour)
ib_downIntegration Broker DownAlerts when SWS REST endpoint connection failures indicate the Integration Broker is down
weblib_downWeb Server / WebLib DownAlerts when PeopleSoft Web Server is down or configured WebLib URLs fail to respond
ib_no_active_domainIB No Active DomainAlerts when there is no active domain found in PSAPMSGDOMSTAT
ib_dispatcher_downIB Dispatcher DownAlerts when an Integration Broker dispatcher process is inactive or has not updated status within thresholdMinutes (default: 10 min)
ib_nodes_downIB Nodes DownAlerts when there are entries in PSNODESDOWN indicating message nodes are down

Authentication Settings

The auth section configures native authentication (email-based magic link or native OpenID Connect SSO). psLens enables authentication by default. To disable authentication, set enabled: false explicitly in config.yaml. Authentication settings are file-authoritative. You cannot change or turn off authentication from the web interface.

SettingDefaultDescription
enabledtrueDefaults to true. To disable, set to false explicitly in config.yaml.
modemagic_linkAuthentication mode: magic_link or oidc.
authorizedUsers[]Whitelist of email addresses allowed to log in (case-insensitive).
oidc.issuerUrl-OpenID Connect Issuer URL (supports op:// 1Password references).
oidc.clientId-OIDC Application Client ID (supports op:// 1Password references).
oidc.clientSecret-OIDC Application Client Secret (supports op:// 1Password references).
oidc.redirectUrl-Optional custom OAuth2 callback URL (supports op:// 1Password references).
oidc.scopes["openid", "profile", "email"]OIDC authorization scopes requested from the Identity Provider.
oidc.allowedGroups[]Optional list of required group claims to restrict access.
oidc.groupClaimgroupsClaim key in the ID token used for group membership verification.
ipAllowlist.enabledfalseEnable IP allowlist enforcement. Fails closed (denies all) if list is empty.
ipAllowlist.allowedCIDRs[]List of allowed CIDR blocks or IP addresses (e.g. ["10.0.0.0/8", "192.168.1.50"]).

SMTP Settings

Configure email server settings under Settings → Notifications (Email Server card). Changes apply to alert and report emails immediately. Login (magic link) emails use new settings after the next server restart. A blank password on save keeps the stored password.

The smtp block in config.yaml seeds the email server on first boot only. After initial boot, settings saved in the UI remain authoritative. PSLENS_SMTP_* environment variables override both sources and make the UI card read-only.

SettingDefaultDescription
host-SMTP server hostname/IP (supports op:// 1Password references)
port-SMTP port (e.g. 25, 465, 587, 2525, supports op:// references)
username-Username for SMTP auth (supports op:// 1Password references)
password-Password for SMTP auth (supports op:// 1Password references)
fromNamepsLensSender name shown in emails (supports op:// 1Password references)
fromEmail-Sender email address for SMTP. Required: no email is sent without it (supports op:// 1Password references)

Test outbound email on the same card: enter a recipient address under Send Test Email and click Send Test. psLens records the test result in Alert Delivery History.


Notifications & Webhooks Settings

Notification rules determine where psLens dispatches alert messages. Manage rules in the app under Settings → Notifications. There, you configure and test email distribution lists, webhook destinations, and subscription routing rules. For step-by-step instructions on creating webhooks in Google Chat, Slack, and Microsoft Teams, see Webhook Integrations.

Email Distribution Lists (notifications.email_lists)

Predefined recipient lists referenced by notifications and reports:

notifications:
  email_lists:
    ops_team:
      name: "Operations Team"
      emails: "ops@company.com, pager@company.com"

Webhooks (notifications.webhooks)

Predefined incoming webhook URLs for Slack, Microsoft Teams Workflows (Adaptive Cards), Google Chat (cardsV2), or custom JSON receivers:

notifications:
  webhooks:
    slack_alerts:
      name: "Slack Alert Channel"
      url: "https://hooks.slack.com/services/..."
    teams_ops:
      name: "Teams Operations Channel"
      url: "https://default...powerautomate.../invoke"
    gchat_ops:
      name: "Google Chat Space"
      url: "https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=..."

To create a Google Chat incoming webhook URL, open the target Space in Google Chat, click the Space name header → Apps & integrations → + Add webhooks, save the webhook, and copy the https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=... link. See Webhook Integrations for full setup instructions.

Subscriptions (notifications.subscriptions)

Routing subscriptions matching fired alerts and dispatching them to email or webhook targets:

PropertyTypeDescription
idStringUnique identifier for the subscription
enabledBooleanActivates or silences the subscription
alertTypesList of StringsAlert check keys to match (e.g. ["*"] for all, or ["process_errors"])
databasesList of StringsDatabase names to match (e.g. ["*"] or ["PROD"])
severityMinStringMinimum alert severity ("info", "warning", "critical")
typeStringDispatch protocol: "email" or "webhook"
targetStringTarget destination: email address, distribution list key, or webhook destination URL / key

Note: psLens automatically detects Slack, MS Teams Adaptive Card, and Google Chat webhook URLs and formats native payloads accordingly.


Reports Scheduling Settings

The reports section allows you to define static scheduling configs and completion notifications directly in the configuration file.

Report Properties

Under reports, define a list of scheduled report objects:

PropertyTypeDescription
idStringThe unique report definition ID (e.g. security-full-access-permlists)
enabledBooleanEnables or disables the scheduled execution of this report (defaults to true)
dbNamesList of StringsDatabase names to target (e.g., ["PROD"]). If omitted, targets all active databases.
scheduleObjectTiming recurrence properties (see below)
emailTargetStringOptional email address to send report completion notifications to
webhookTargetStringOptional Slack, MS Teams, Google Chat, or JSON webhook endpoint to send notifications to

Schedule Recurrence Properties

Under reports.schedule, define when the job should run:

PropertyTypeDescription
intervalStringRecurrence frequency: "daily", "weekly", or "monthly"
timeOfDayString24-hour format execution time ("HH:MM", e.g., "02:00") in server local time
dayOfWeekIntegerOptional. Day of the week for weekly runs (0 for Sunday, 1 for Monday, …, 6 for Saturday)
dayOfMonthIntegerOptional. Day of the month for monthly runs (1 to 28)

JSON Schema Validation

To enable autocompletion, tooltips, and real-time schema validation within your IDE (such as VS Code), use the built-in JSON schema:

Option A: Live URL (Online)

If your psLens server is running locally (e.g., on port 8080), you can add the $schema parameter to the top of your JSON config file:

{
  "$schema": "http://localhost:8080/static/config-schema.json",
  "server": {
    "port": 8080
  }
}

Option B: Local File Reference (Offline)

To configure your workspace offline, reference the schema file directly. In VS Code, add this to your .vscode/settings.json:

{
  "json.schemas": [
    {
      "fileMatch": ["config.json"],
      "url": "./static/config-schema.json"
    }
  ],
  "yaml.schemas": {
    "./static/config-schema.json": ["config.yaml"]
  }
}

Securing psLens

For production deployments, you should restrict access to the psLens interface.

  1. Enable Built-In Magic Link Auth: Turn on auth.enabled and configure SMTP credentials and authorizedUsers to require code validation on login.
  2. Setup a Master Key (PSLENS_MASTER_KEY): Provide a 32-byte (64 hex characters) key in the PSLENS_MASTER_KEY environment variable. All database and SMTP passwords entered in the UI or configuration are then encrypted at rest using AES-256-GCM.
  3. Reverse Proxy / VPN: Place psLens behind a reverse proxy (e.g., Cloudflare Access, oauth2-proxy, nginx, Tailscale) to delegate authentication to your company’s Identity Provider (SAML/OIDC). When using an external SSO proxy, you can keep auth.enabled disabled and restrict the psLens binary to bind only on 127.0.0.1 or internal networks.

Warning: Never expose psLens to the public internet without authentication. Enable built-in magic-link auth or place an authenticated reverse proxy in front of psLens. Unprotected instances expose read access to PeopleSoft metadata.


Active Configuration and Hot-Reloading

psLens keeps its settings in two places:

  1. Active configuration (embedded key-value store): Web interface edits save here and take effect immediately without server restarts. psLens records each change as a numbered revision under Settings → Change History. You can view or restore any revision.
  2. config.yaml on disk: the bootstrap file that seeds the active configuration on first boot. Saved changes are also written back to it as a backup on the persistent storage volume.

Configuration Seeding

On initial boot, or when you set the environment variable PSLENS_FORCE_SEED_CONFIG=true, psLens seeds active configuration from config.yaml.

If the active configuration drifts from config.yaml, the Settings → Advanced page shows a “Settings differ from the config.yaml file on disk” notice. To resolve it:

  • Click Load settings from config.yaml on the Advanced page. This overwrites active configuration with file contents and discards changes made in the UI.
  • If the file path is writable, click Overwrite config.yaml on disk to write active configuration to disk. On ephemeral deployment models without persistent volumes, restarting or re-deploying the container discards local file changes.
  • Alternatively, copy the Active Configuration YAML from the Advanced page into your config.yaml file to bring the disk file in sync.

Secret Key Management (Encryption at Rest)

When you configure the PSLENS_MASTER_KEY environment variable with a 32-byte hex-encoded key, psLens encrypts database and SMTP passwords at rest. Without this master key, psLens saves passwords in plaintext.

Rotating the Master Key

To rotate the cryptographic key used for credential encryption:

  1. Enter a new 32-byte hex key in the Rotate Encryption Key field.
  2. Click Rotate Master Key. The server decrypts all stored credentials using the old key and re-encrypts them with the new key in NATS KV.
  3. Update the PSLENS_MASTER_KEY environment variable in your deployment configuration (e.g., Fly.io secrets or .env file) to match the new key before restarting the container. If the container restarts with a mismatched key, it cannot decrypt the configuration.

5 - Deployment Options

This page is for clients who want to host psLens themselves in a Docker container. It covers three questions in order:

Deployment Options

This page is for clients who want to host psLens themselves in a Docker container. It covers three questions in order:

  1. How do I get the image? Distribution and authentication.
  2. How do I upgrade without losing my config? Volumes, env vars, and the master key.
  3. How do I do HTTPS? Six TLS options compared on the same axes.

If you just want a 5-minute install on a private network, the Installation page is enough. Come back here when you’re ready to put psLens in front of real users.


1. Image Distribution

Cedar Hills Group publishes psLens to the GitHub Container Registry (GHCR) as a private package. Each client gets read access of its own, so access can be granted and removed per client.

Authenticating

  1. Cedar Hills Group grants read access to the pslens package to a GitHub account: your own (send us the username), or a pull-only account we create for you if you do not use GitHub.

  2. On that account, create a personal access token (classic) with only the read:packages scope. GHCR accepts classic tokens only; fine-grained tokens do not work for container pulls.

  3. On the Docker host:

    echo "YOUR_TOKEN" | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin
    

    Docker stores the credentials in ~/.docker/config.json. They persist across host reboots.

  4. Verify the pull works, using the version from your upgrade ticket or the changelog:

    docker pull ghcr.io/cedarhillsgroup/pslens:v1.0.0
    

Image Tags

The release pipeline publishes three tag flavors for every release:

TagExampleUse when
latestghcr.io/cedarhillsgroup/pslens:latestDev/test only — never pin production here
vMAJOR.MINOR.PATCH:v1.4.2Production — exact reproducibility
vMAJOR.MINOR:v1.4Production — auto-pickup of patch releases

The client package’s docker-compose.yml reads the tag from PSLENS_VERSION in .env and refuses to start without one.

Recommended: Pin production to vMAJOR.MINOR. You will automatically pick up patch fixes when running docker compose pull. You will avoid unexpected breaking changes from minor or major version bumps.

When You Can’t Reach ghcr.io

If the Docker host cannot make outbound HTTPS connections to ghcr.io (common in segmented enterprise networks), use the air-gapped flow from Installation:

# On a machine with internet access:
docker pull ghcr.io/cedarhillsgroup/pslens:v1.4.2
docker save ghcr.io/cedarhillsgroup/pslens:v1.4.2 | gzip > pslens-v1.4.2.tar.gz

# Transfer the .tar.gz to the target host (USB, internal artifact repo, etc.), then:
docker load < pslens-v1.4.2.tar.gz

You can also mirror the image into your private registry (Harbor, AWS ECR, Azure ACR, GitLab Registry). Pull the image once, retag, push, and reference the mirrored image in docker-compose.yml.

Troubleshooting Pull Failures

ErrorCauseFix
denied: deniedToken expired or revokedRenew the PAT with Cedar Hills Group
unauthorizedWrong token type or scope, or no package accessClassic PAT with read:packages, on an account granted access to pslens
no basic auth credentialsdocker login wasn’t run, or ~/.docker/config.json was lostRe-run docker login ghcr.io
manifest unknownThe tag you asked for doesn’t exist yetReview the release notes for available tags

2. Configuration and Secrets

The most failure-prone part of self-hosted deployment is preserving configuration and secrets through upgrades. This section explains what survives docker compose pull && docker compose up -d and what does not.

What Persists, What Doesn’t

Persistent (must reside on a volume):

  • /data/nats — NATS JetStream store. Contains recently-viewed objects, generated markdown reports, and alert state. When using the in-app config UI, it also stores the AES-256-encrypted database passwords KV.
  • /data/projects — project store for uploaded .zip project archives.
  • /app/config.yaml — bind-mounted from the host filesystem.

Ephemeral (re-created on every container start):

  • Whitelist cache (read from PeopleSoft on startup).
  • PIA URL discovery cache.
  • In-memory session state.

The default docker-compose.yml in Installation already wires the persistent items correctly: a named volume pslens_data mounted at /data, and config.yaml bind-mounted at /app/config.yaml:ro. Unless you run docker volume rm pslens_data, your data survives image upgrades.

Three Configuration Modes

There are three ways to source configuration. Pick one based on how many people administer the system and how you manage secrets.

ModeWhere config livesWhere secrets liveBest for
A. File-onlyconfig.yaml bind-mounted from hostPlaintext in config.yamlInternal-only dev/test
B. File + env overrideconfig.yaml for non-secretsPSLENS_DB_{NAME}_PASSWORD env vars, sourced from .env or a secrets managerRecommended default for client-hosted
C. KV-encryptedMinimal config.yaml. Full config in NATS KV bucket, AES-256 encrypted at rest.Encrypted blob in /data/nats, unlocked by PSLENS_MASTER_KEYMulti-admin setups where you use the in-app config UI

config.yaml:

server:
  port: 8080
  host: "0.0.0.0"
  appBaseURL: "https://pslens.example.com"
  natsStoreDir: "/data/nats"

databases:
  - name: "PROD"
    description: "Production HCM"
    baseURL: "https://psft.example.com:8000/PSIGW/RESTListeningConnector/PSFT_HR/CHG_PSLENS_SWSPQL/"
    username: "PSLENS_API"
    password: "placeholder"      # Overridden by PSLENS_DB_PROD_PASSWORD
    timezone: "America/Chicago"

.env (sibling of docker-compose.yml, chmod 600, gitignored; start from the package’s .env.example):

PSLENS_VERSION=v1.0.0
PSLENS_DB_PROD_PASSWORD=actual-password-here
PSLENS_MASTER_KEY=64-hex-character-key

The docker-compose.yml file references env_file: .env. Docker injects each variable into the container at startup.

Tip: The env-var override convention is PSLENS_DB_{NAME}_PASSWORD where {NAME} is the database name from config.yaml, uppercased. For a database named DEV_HR, the variable is PSLENS_DB_DEV_HR_PASSWORD.

About PSLENS_MASTER_KEY

In production, psLens requires PSLENS_MASTER_KEY to be set. It’s used to encrypt database passwords stored in the NATS KV bucket. Generate one once:

openssl rand -hex 32

The key must be 64 hexadecimal characters (32 bytes). psLens refuses to start with any other format.

Critical: back this key up out-of-band in your password manager, AWS Secrets Manager, HashiCorp Vault, or wherever you keep root-of-trust secrets. If you lose the master key, the encrypted password blob in /data/nats becomes unrecoverable and you’ll have to re-enter every database password.

Backups

Back up the data volume with the client package’s script, from the folder that holds docker-compose.yml:

./upgrade.sh --backup-only

It stops psLens for a few seconds so the data is consistent, writes backups/pslens-data-<version>-<timestamp>.tar.gz, and starts psLens again. It uses the psLens image itself, so it works on hosts without internet access. Docker Compose names the volume after your install folder (for example pslens_pslens_data), so commands that name pslens_data directly back up an empty volume instead. Schedule the script daily with cron.

What to back up where:

  • Data volume (pslens_data) — daily tarball, retain 14-30 days. Captures reports, alert state, and encrypted passwords KV.
  • config.yaml — commit to your infrastructure-as-code repo (gitignore the password fields, or use the placeholder pattern from Mode B).
  • .env — store in your secrets manager. Never commit this into git.

To restore, run these from the install folder, using the backup file name:

docker compose stop pslens
docker run --rm --user 0 --volumes-from "$(docker compose ps -aq pslens)" -v "$(pwd)/backups:/backup" \
  --entrypoint bash "ghcr.io/cedarhillsgroup/pslens:$(sed -n 's/^PSLENS_VERSION=//p' .env)" \
  -c 'rm -rf /data/* && tar xzf /backup/pslens-data-v1.0.0-20261021-020000.tar.gz -C /'
docker compose up -d pslens

3. TLS / HTTPS Options

psLens does not serve TLS in the binary by default. It listens on plain HTTP and expects either a reverse proxy, an in-binary TLS configuration, or a tunnel to provide HTTPS.

There are six viable options. The table below compares them on the same axes: certificate source, automation, operational complexity, and the scenario each fits best.

Quick recommendation

Your scenarioRecommended option
Default — most clientsOption 3: Caddy sidecar
Corporate PKI with certs-as-codeOption 4: nginx sidecar (or Option 1 if minimalist)
Internal-only, small team, already using TailscaleOption 6: Tailscale Serve / Funnel
Already running TraefikOption 5: Traefik
Public internet, single host, no proxy wantedOption 2: in-binary autocert

Details on each option follow.

Option 1: Go-native TLS via crypto/tls (cert files)

psLens loads a PEM cert + key from disk and serves TLS directly. No reverse proxy, no extra container, no external dependencies.

Status: This requires a small code change to psLens (currently the binary only listens on plain HTTP). Contact Cedar Hills Group if you need this option — it’s roughly 30 lines of Go and a config block. Tracked in the backlog.

How it works once implemented:

server:
  tls:
    enabled: true
    certFile: "/certs/pslens.crt"
    keyFile: "/certs/pslens.key"

Mount /certs as a read-only bind from the host where your PKI tooling drops renewed certs.

AxisDetail
Certificate sourceCustomer-provided — corporate CA, commercial CA, or self-signed
RenewalCustomer’s responsibility (cron + cert rotation tool)
Hot reloadNot supported. A container restart picks up new certs.
ProsZero external dependencies, single container, familiar to enterprise teams with existing PKI
ConsYou manage the certificate lifecycle. An expired certificate causes an outage.
Best forEnterprises with internal PKI tooling (Venafi, AWS ACM Private CA, Vault PKI)

Option 2: Go-native TLS via acme/autocert (Let’s Encrypt)

psLens requests and renews Let’s Encrypt certs in-process using the golang.org/x/crypto/acme/autocert library.

Status: Like Option 1, this requires a small code addition to psLens. Contact Cedar Hills Group.

How it works once implemented:

server:
  tls:
    autocert:
      enabled: true
      hostnames: ["pslens.example.com"]
      email: "admin@example.com"
      cacheDir: "/data/acme"

Cert cache lives in /data/acme so it survives container restarts as long as the pslens_data volume does. psLens listens on :80 for the ACME HTTP-01 challenge and :443 for TLS.

AxisDetail
Certificate sourceLet’s Encrypt (free, 90-day, auto-renewed at ~60 days)
RenewalFully automatic, in-process
Hot reloadN/A — the library reloads on its own renewal cycle
ProsCheapest TLS, zero ops effort after initial config
ConsRequires port 80 reachable from the public internet for the HTTP-01 challenge. This requirement rules out fully internal deployments.
Best forPublic-internet hosts on a real domain (pslens.client.com)

Option 3: Caddy sidecar

Run Caddy as a second service in the same docker-compose.yml. Caddy handles TLS and reverse-proxies to psLens on the internal Docker network.

Caddyfile (5 lines for the public-internet case):

pslens.example.com {
    reverse_proxy pslens:8080
    encode gzip
}

For an internal-only deployment (no public DNS, no Let’s Encrypt), use Caddy’s built-in CA:

pslens.internal.example.com {
    tls internal
    reverse_proxy pslens:8080
}

You’ll need to add Caddy’s root cert to client browsers (push it via MDM) so they trust the internal cert.

docker-compose.yml addition:

services:
  pslens:
    image: ghcr.io/cedarhillsgroup/pslens:v1.4
    expose:
      - "8080"      # no longer "ports:" — only Caddy needs an external port
    volumes:
      - ./config.yaml:/app/config.yaml:ro
      - pslens_data:/data
    env_file: .env
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

volumes:
  pslens_data:
  caddy_data:
  caddy_config:
AxisDetail
Certificate sourceLet’s Encrypt (public), or Caddy’s built-in CA (tls internal)
RenewalFully automatic. Certificate state resides in the caddy_data volume.
Hot reloadCaddy reloads certs on its own renewal cycle
ProsTrivial config, handles both public-internet and internal-only, decouples TLS from the app (restarting psLens doesn’t drop TLS sessions)
ConsSecond container to operate. An internal CA requires distributing the root cert to clients.
Best forThe default recommended option for most client deployments

Option 4: nginx sidecar

Same shape as Caddy but with nginx, using customer-provided cert files.

nginx.conf:

events {}

http {
    server {
        listen 443 ssl http2;
        server_name pslens.example.com;

        ssl_certificate     /certs/pslens.crt;
        ssl_certificate_key /certs/pslens.key;
        ssl_protocols       TLSv1.2 TLSv1.3;

        location / {
            proxy_pass         http://pslens:8080;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;

            # Server-Sent Events: disable buffering for the SSE endpoints
            proxy_buffering    off;
            proxy_cache        off;
        }
    }

    server {
        listen 80;
        server_name pslens.example.com;
        return 301 https://$host$request_uri;
    }
}

Important for psLens: You must configure the proxy_buffering off directive. psLens relies on Server-Sent Events for UI updates. With buffering enabled, the UI appears frozen until pages finish loading.

docker-compose.yml addition:

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/certs:ro
    restart: unless-stopped

Cert renewal is a separate concern, typically certbot run as a host cron job that replaces the files in ./certs/ and signals nginx with docker compose exec nginx nginx -s reload.

AxisDetail
Certificate sourceCustomer-managed PEM (corporate CA, commercial CA, certbot)
RenewalCustomer’s responsibility (commonly certbot + cron)
Hot reloadYes via nginx -s reload
ProsWidely deployed reverse proxy with extensive team runbooks. Easy to add request-level customization such as auth and rate limits.
ConsNo built-in certificate automation. Requires more boilerplate than Caddy for the same outcome.
Best forClients who already standardize on nginx, or who need request-level customization

Option 5: Traefik sidecar

Same shape as Caddy but Traefik discovers routes from Docker labels on the psLens service. Useful only if the client already runs Traefik.

docker-compose.yml addition:

  pslens:
    # ... rest of config ...
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pslens.rule=Host(`pslens.example.com`)"
      - "traefik.http.routers.pslens.tls.certresolver=letsencrypt"
      - "traefik.http.services.pslens.loadbalancer.server.port=8080"

  traefik:
    image: traefik:v3
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      - --certificatesresolvers.letsencrypt.acme.email=admin@example.com
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_data:/letsencrypt
    restart: unless-stopped
AxisDetail
Certificate sourceLet’s Encrypt, customer PEM, or Vault
RenewalAutomatic
Hot reloadLive config reload from Docker labels
ProsIf the client already runs Traefik, psLens plugs in with zero net-new ops
ConsAdopting Traefik just for psLens isn’t worth the learning curve
Best forClients who already use Traefik (common in Kubernetes shops)

Option 6: Tailscale Serve / Funnel (or Cloudflare Tunnel)

Bypass TLS-at-psLens entirely by exposing the service over a managed tunnel. The tunnel provider edge handles TLS. psLens remains on plain HTTP inside the tunnel.

Tailscale Serve (private to your tailnet — internal use):

tailscale serve --bg --https 443 http://localhost:8080

You’ll get a URL like https://pslens.tailnet-name.ts.net. Tailscale issues and renews the cert. Only members of your tailnet can reach it.

Tailscale Funnel (public internet via Tailscale’s edge):

tailscale funnel --bg 443

Same URL format. Reachable from the public internet but rate-limited and not designed for high-volume traffic. Fine for an admin dashboard.

Cloudflare Tunnel (public, no inbound ports):

Install cloudflared on the Docker host (or run it as a sidecar container). Authenticate, create a tunnel, point a Cloudflare-managed DNS name at it.

AxisDetail
Certificate sourceTunnel provider (Tailscale or Cloudflare)
RenewalFully automatic
ProsZero TLS configuration on psLens. No inbound ports opened on the firewall. Mesh networking works well for multi-database connectivity.
ConsAdds a third-party dependency in the data path. Client policies may restrict cloud tunnels. Providers enforce rate limits.
Best forInternal-only deployments where you want easy access for a small team without standing up a reverse proxy or opening firewall ports

4. Upgrades

Cedar Hills Group tells you about each release, with the version to install. Upgrades never change config.yaml, and the script backs up the data volume first.

Standard upgrade

From the folder that holds docker-compose.yml:

./upgrade.sh v1.5.0

The script, from the client package:

  1. Pulls ghcr.io/cedarhillsgroup/pslens:v1.5.0. If the pull fails, nothing else changes.
  2. Stops psLens and backs up the data volume to backups/.
  3. Sets PSLENS_VERSION=v1.5.0 in .env.
  4. Starts psLens and waits until /healthz reports v1.5.0.
  5. Prints the rollback command, and how to restore the backup.

Use an exact version (v1.5.0) or a minor version (v1.5). The script refuses latest. If PSLENS_VERSION is a minor version, docker compose pull pslens && docker compose up -d pslens also picks up patch releases, without the backup.

Rollback

Run the script with the version you were running before:

./upgrade.sh v1.4.2

The old image is usually still in the local Docker cache. If the newer release changed stored data, also restore the backup it took (see Backups).

Air-gapped upgrade

# On a machine with internet:
docker pull ghcr.io/cedarhillsgroup/pslens:v1.5.0
docker save ghcr.io/cedarhillsgroup/pslens:v1.5.0 | gzip > pslens-v1.5.0.tar.gz

# Transfer the .tar.gz to the target host, then:
docker load < pslens-v1.5.0.tar.gz
./upgrade.sh --offline v1.5.0

--offline skips the pull and stops with an error if the image is not loaded.

Checking the running version

GET /healthz returns the running version and commit:

{"status":"ok","version":"v1.5.0","commit":"3f1c2ab..."}

Releases before this change return plain ok.

Cedar Hills Group’s breaking-change contract

  • Stable across minor versions: env-var names (PSLENS_DB_{NAME}_PASSWORD, PSLENS_MASTER_KEY), volume mount paths (/data, /app/config.yaml), and the data on disk.
  • Documented in CHANGELOG.md: any config schema change. Schema changes happen on major version bumps.
  • Automatic: NATS KV bucket schema migrations run on first start of a new version.

Before a major-version upgrade: always take a backup of the pslens_data volume (see Backups above). If something goes wrong, you can restore the volume and roll the image back to the previous tag.


See Also

6 - IP & CIDR Allowlist

Restrict hosted psLens instance access strictly to authorized corporate IP addresses and CIDR subnets.

IP & CIDR Allowlist

psLens supports network-level IP address and CIDR subnet restrictions. When you enable this feature, psLens blocks requests from addresses outside the allowlist. It returns an HTTP 403 Forbidden response before session authentication or routing runs.

Configuration

Add the security.ipAllowlist block to your config.yaml:

security:
  ipAllowlist:
    enabled: true
    allowedCIDRs:
      - "203.0.113.50"          # Single corporate static IP
      - "198.51.100.0/24"       # Office subnet
      - "10.0.0.0/8"            # Internal VPC / VPN network
      - "2001:db8::/32"         # IPv6 block
    trustForwardHeaders: true   # Enable when running behind reverse proxies / CDN (Fly.io, Cloudflare, AWS ALB)
    trustedProxies:             # Optional: explicit reverse proxy CIDRs/IPs allowed to send proxy headers
      - "10.0.0.0/8"
      - "172.16.0.0/12"

Reverse Proxy & Cloud Ingress (trustForwardHeaders)

When psLens runs behind a reverse proxy, load balancer, or cloud provider (such as Fly.io, Cloudflare, or AWS ALB), client requests terminate at the proxy.

To prevent IP spoofing, psLens inspects forwarded headers only when the direct connection socket (RemoteAddr) originates from a trusted proxy:

  • If you set trustedProxies explicitly, the direct peer must match a configured IP address or CIDR.
  • If you omit trustedProxies and set trustForwardHeaders: true, psLens trusts local and private networks (loopback, RFC 1918, RFC 4193) and Fly.io internal 6PN (fdaa::/16).

When psLens trusts the peer, it inspects headers in the following order:

  1. Fly-Client-IP (Fly.io edge proxy)
  2. CF-Connecting-IP (Cloudflare CDN)
  3. X-Real-IP (Nginx / standard reverse proxies)
  4. X-Forwarded-For (First IP in comma-separated proxy list)
  5. RemoteAddr (Direct TCP connection socket)

Health Check Probes Exemption

The /healthz endpoint is always exempt from IP filtering. Infrastructure health checks (Kubernetes liveness and readiness probes, Fly.io monitors, AWS target groups) continue to report instance health without proxy IP whitelisting.

Dynamic Updates

When you manage configuration through NATS KV or the settings UI, IP allowlist changes take effect immediately on subsequent requests without server restarts.

7 - SSO & OIDC Discovery

Set up native Single Sign-On (SSO) using OpenID Connect discovery for Microsoft Entra ID, Okta, Google Workspace, Keycloak, or Auth0.

Single Sign-On (SSO) with OpenID Connect

psLens supports native Single Sign-On (SSO) via OpenID Connect (OIDC). Instead of manually entering authorization and token endpoints in YAML files, psLens provides an automated discovery tool at /settings/auth that queries /.well-known/openid-configuration directly from your Identity Provider.

Setting Up Single Sign-On in psLens

  1. Navigate to Settings → Authentication & SSO (/settings/auth).

  2. Set the login method to Native OIDC / Single Sign-On.

  3. Choose your Identity Provider preset or enter the Issuer URL:

    • Microsoft Entra ID (Azure AD): https://login.microsoftonline.com/{TENANT_ID}/v2.0
    • Okta: https://{YOUR_OKTA_DOMAIN}.okta.com
    • Google Workspace: https://accounts.google.com
    • Keycloak: https://{KEYCLOAK_HOST}/realms/{REALM_NAME}
    • Auth0: https://{TENANT}.auth0.com/
  4. Click Discover Endpoints: psLens tests the connection and detects endpoints, supported scopes, and token claim attributes.

  5. In your Identity Provider’s App Registration console, register the exact Redirect URI shown on the page:

    https://your-pslens-domain.com/auth/callback
    
  6. Paste the Client ID and Client Secret.

  7. (Optional) Specify Allowed Groups (such as pslens-admins) to enforce role-based access control.

  8. Click Save Authentication Settings. Changes take effect immediately without restarting psLens.

Supported Group Claims & RBAC

When checking OIDC tokens, psLens extracts group memberships from the ID token claim defined in groupClaim (defaulting to groups or roles). If you configure allowedGroups, only users belonging to at least one listed group gain access upon authentication.

8 - Webhook Integrations (Slack, Microsoft Teams & Google Chat)

Configure incoming webhooks for Google Chat, Slack, and Microsoft Teams to deliver real-time PeopleSoft alerts and scheduled report notifications.

Webhook Integrations

psLens dispatches real-time alert notifications (firing and resolution events) and scheduled report completion summaries to incoming webhooks. psLens automatically detects the target platform from the webhook URL and formats native rich card payloads without extra configuration:

PlatformURL Pattern DetectedNative Payload Format
Google Chatchat.googleapis.comGoogle Chat Cards v2 (cardsV2) with Material Symbols icons, 2-column metadata grid, color-coded status, action buttons, and conversation threading (threadKey)
Slackhooks.slack.com or slackSlack Block Kit (attachments + blocks) with color-coded sidebar, fields, and interactive link buttons
Microsoft Teamswebhook.office.com, logic.azure.com, or powerautomateAdaptive Cards v1.4 (message attachment) with FactSet metadata and Action.OpenUrl buttons
Custom WebhookAny other http:// or https:// URLStructured JSON payload containing event_type, status, alert, database, timestamp, and details

Setting Up Google Chat Webhooks

Google Workspace spaces support incoming webhooks that allow psLens to post formatted Cards v2 alert and report messages directly into a space.

Where to Get a Google Chat Webhook URL

Open Google Chat in your web browser (creating webhooks requires the web or desktop app, not the mobile app) and open the Space where you want psLens notifications delivered.

Click the Space name at the top of the conversation header to open the space menu, then select Apps & integrations.

In the Webhooks section, click + Add webhooks (or Manage webhooks).

Enter a name for the webhook (for example, psLens Alerts) and optionally provide an avatar image URL, then click Save.

Click the three-dot menu (⋮) next to your newly created webhook and select Copy link.

The copied URL will look like this:

https://chat.googleapis.com/v1/spaces/AAQAn4B-uPg/messages?key=AIzaSy...&token=bCtXq1...

Note: If Apps & integrations → Webhooks is not visible or is greyed out, ask your Google Workspace administrator to enable incoming webhooks in the Google Admin Console under Apps → Google Workspace → Google Chat → Externaligs / Webhooks.

How Google Chat Delivery Works in psLens

  • Rich Cards v2 Layout: Alert and report notifications render as structured Google Chat cardsV2 messages with a header, severity/environment metadata columns, affected item summaries, and an Open in psLens button (when server.appBaseURL is configured).
  • Automatic Threading: Alert notifications automatically include a deterministic threadKey per database and alert type along with messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD, so firing alerts, reminders, and RESOLVED notifications group into the same conversation thread in your space.
  • Secret Obfuscation: Both key and token query parameters in chat.googleapis.com URLs are automatically masked (***) in the psLens UI, delivery history, and application logs.

Setting Up Slack Webhooks

In Slack, open your workspace settings or visit the Slack Apps portal and create or select an app with Incoming Webhooks enabled.

Click Add New Webhook to Workspace, select the target channel, and copy the generated https://hooks.slack.com/services/... URL.


Setting Up Microsoft Teams Webhooks

In Microsoft Teams, open the target channel, click the three-dot menu (⋯) next to the channel name, and select Workflows (or Connectors).

Choose the Post to a channel when a webhook request is received workflow template, select your team and channel, and copy the generated Power Automate / Office 365 webhook URL.


Registering & Routing Webhooks in psLens

Once you have copied your webhook URL from Google Chat, Slack, or Microsoft Teams, register it in psLens either through the web UI or in config.yaml.

Open Settings → Notifications (/settings/notifications) and scroll to the Webhook Destinations card.

Click Add Webhook, enter an ID (e.g., gchat_ops), a Display Name (e.g., Google Chat Ops Space), and paste the Webhook URL, then click Save Webhook. psLens automatically displays a Google Chat, Slack, or MS Teams badge next to the webhook.

Click the Send Test (paper-plane) button next to the webhook to send a test card to your channel or space and confirm connectivity.

Once registered, you can route notifications to this webhook in three places:

  • Global Alert Subscriptions: On Settings → Notifications, add a subscription under Notification Subscriptions, set Delivery Method to Webhook, and select your webhook from the dropdown.
  • Database-Specific Alert Delivery: On Settings → Databases → [Database] → Alerts, under Notification Delivery Settings, toggle Webhook Delivery on, select your webhook, and click Save Delivery.
  • Scheduled Reports: On any report page under Schedule & Notifications, toggle Webhook delivery on and select your webhook.

Via config.yaml

You can also define reusable webhooks and subscriptions directly in config.yaml (including op:// 1Password secret references):

notifications:
  webhooks:
    gchat_ops:
      name: "Google Chat Operations Space"
      url: "https://chat.googleapis.com/v1/spaces/AAAA.../messages?key=...&token=..."
    slack_alerts:
      name: "Slack Alert Channel"
      url: "https://hooks.slack.com/services/T000/B000/XXXX"
    teams_ops:
      name: "Teams Operations Channel"
      url: "https://default...powerautomate.../invoke"

  subscriptions:
    - id: "google-chat-prod"
      enabled: true
      alertTypes: ["*"]
      databases: ["PROD"]
      severityMin: "warning"
      type: "webhook"
      target: "gchat_ops"