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 must be installed and configured in PeopleSoft first.
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.
Start psLens and open the dashboard in your browser
Quick Start
Once the SWS framework is installed in PeopleSoft:
# Create a directory for psLensmkdir pslens &&cd pslens
# Create config.yaml with your PeopleSoft connection details# (see Configuration for full details)# Start psLens with Dockerdocker 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.
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:
The SWS framework, a small Integration Broker service installed inside your PeopleSoft environment.
The psLens application, a single Docker container hosted externally (default: fly.io) or on your own infrastructure.
Everything psLens displays (search results, alerts, reports) flows over a single HTTPS connection from the psLens container into your SWS endpoint. There is no other channel.
The Short Version
Two components, nothing else. SWS inside PeopleSoft; psLens as a Docker container outside it.
Traffic only flows one way. psLens calls SWS over HTTPS. SWS never reaches out 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. Anything outside the list is rejected before it touches the database.
Dedicated deployment per customer. No shared psLens app, no shared storage, no multi-tenant SaaS backend.
How the Pieces Fit
%%{init: {"flowchart": {"htmlLabels": true, "padding": 16, "nodeSpacing": 60, "rankSpacing": 80, "subGraphTitleMargin": {"top": 10, "bottom": 14}}}}%%
flowchart LR
USER(["Your team's<br/>web browser"])
subgraph EXT["Cedar Hills Group hosted <br/>or your own infrastructure"]
APP["psLens<br/>Docker container"]
end
subgraph PS["Your PeopleSoft Environment"]
SWS["SWS Framework<br/>REST endpoint"]
DB[("PeopleSoft DB<br/>read-only<br/>whitelisted tables")]
SWS --> DB
end
USER -- "HTTPS" --> APP
APP -- "HTTPS · Basic auth<br/>psoftQL JSON" --> SWS
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 subgraphStyle fill:#fafafa,stroke:#666,stroke-width:1px,color:#000
class SWS,DB ps
class APP ext
class USER user
class PS,EXT subgraphStyle
Your team reaches psLens with any current web browser over HTTPS. There is no desktop client to install. Everything the user sees comes from the psLens container; the browser never talks to PeopleSoft directly.
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 is allowed to 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 it is a config 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.
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:
Nothing from PeopleSoft is stored in the browser. 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. Even in a worst case where the psLens container were compromised, the SWS whitelist is the ceiling on what an attacker could read. They cannot drop into PeopleSoft, 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. Most of the work is performed by the CHG_PSLENS_SWSPQL service operation. There is a secondary service operation, CHG_PSLENS_METADATA_GET, that returns miscellaneous metadata about the PeopleSoft environment that cannot be read from the database tables and 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 that includes a query language, a web service framework, and a set of tools for building and managing REST APIs on top of PeopleSoft. psLens uses a small subset of the SWS framework to read data from PeopleSoft tables.
This installation guide assumes that Cedar Hills Group, Inc. is hosting the psLens web application. If you are hosting psLens yourself, please see Deployment Options for instructions on how to install and configure psLens in your own environment. The option where we host psLens is the simplest and fastest way to get started, and is the recommended option for most customers.
You will be given a zip file containing the CHG_PSLENS Application Designer project.
Import the project into your PeopleSoft DEV environment. Detailed instructions are included below.
Run the “whitelist” inserts to allow psLens to read the tables it needs. Detailed instructions are included below.
Configure the required service account for psLens API access and grant it the required permissions to read the tables psLens uses. Detailed instructions are included below.
Ensure that your instance of psLens can reach the PeopleSoft Integration Gateway. This often requires a firewall rule to allow the psLens server to reach the PeopleSoft Integration Gateway on port 443 (or your configured port).
Work with Cedar Hills Group to configure the psLens web application to connect to your PeopleSoft environments.
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.
You will be given a zip file containing the CHG_PSLENS Application Designer project.
Using Application Designer, import the project into your PeopleSoft environment.
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
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
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.
The psLens server will be identified by a custom egress IP address that Cedar Hills Group, Inc. will configure for your instance of psLens. You will need to 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 any questions about the connectivity, please contact Cedar Hills Group.
PeopleSoft administrators often want to smoke test the connectivity between psLens and the PeopleSoft Integration Gateway before we configure the psLens web application. The following curl command can be used to test the connectivity. You will need to 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. This is the hostname that psLens will use to connect to the PeopleSoft Integration Gateway. This must be resolvable from the psLens server.
PS_PORT - The port your Integration Gateway listens on. 8000 is the PeopleSoft default for HTTP; HTTPS is frequently on 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.
Keep the single quotes on the variable assignments. PeopleSoft passwords often contain !, $, or a space, and an unquoted value gets mangled by the shell before curl ever sees it. In bash and zsh, an unquoted ! triggers history expansion, the assignment fails with event not found, and curl then authenticates with an empty password.
A successful call returns HTTP 200 with a data.CHG_PSLENS_WL.fields array listing the whitelisted RECNAME values. Two common failures:
401 - the operator ID or password is wrong, or the account is locked.
404 - the node name or service operation path is wrong. Check PS_NODE against the node you created and confirm 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 controls which PeopleSoft tables can be queried through a global whitelist table (CHG_PSLENS_WL). You need to whitelist every table that psLens reads. The full list of tables (organized by feature area) is documented in the Reference section.
This page contains the SQL inserts you run once during installation. After running them, restart psLens (or wait for the next whitelist cache refresh) and confirm the Settings > Database Connections page shows the database as fully connected with 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)
INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('CHG_PSLENS_WL');-- Security tables
INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAUTHAS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAUTHBUSCOMP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAUTHITEM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAUTHPRCS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAUTHSIGNON');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAUTHWS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSCLASSDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMENUITEM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOBJGROUP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPRDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPROBJ');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSROLECLASS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSROLEDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSROLEUSER');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SCRTY_ACC_GRP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SCRTY_QUERY');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PTACM_ACCESSTBL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPTSCRTY_ADS_A');-- Metadata tables
INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSBCDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSBCITEM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSDBFIELD');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSDBFLDLABL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSKEYDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMENUDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSXFERITEM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPNLDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPNLFIELD');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPNLGROUP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPNLGRPDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPROJECTDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPROJECTITEM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPRSMATTRVAL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPRSMDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPRSMPERM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPRSMSYSATTR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPRSMSYSATTRVL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSRECDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSRECFIELD');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPTSF_SD');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPTSF_SD_ATTR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPTSF_SD_DCATR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPTSF_SRCCAT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPTSF_SRCCATAT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSRECDDLPARM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIDXDDLPARM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSSPCDDLPARM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSDDLMODEL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSDDLDEFPARMS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPTIONSADDL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSTBLSPCCAT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSRECTBLSPC');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSFLDDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSFLDSEGDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSFLDFIELDDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSXLATITEM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SQLSTMT_TBL');-- Integration Broker tables
INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAPMSGPUBCON');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAPMSGPUBHDR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAPMSGSUBCON');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAPMSGDOMSTAT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAPMSGDSPSTAT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBLOGHDR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBRTNGDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBAPPLDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBAPPLOPR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBAPPURI');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBAPPMETHOD');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBPARAM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBBASEPARAM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBTEMPLPARAM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBBASETMPLPRM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBAPPLSTATES');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSIBAPPLHDRPROP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGNODEDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSNODECONPROP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSNODEURITEXT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSNODESDOWN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPERATION');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPERATIONAC');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPERATIONURI');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPRHDLR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPRVERDFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPRVERDFNPARM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQUEUEDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQUEUEPART');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSRTNGDFNPARM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSRTNGDFNPROP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSSERVICE');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSSERVICEOPR');-- Process Scheduler tables
INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSDEFNGRP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSDEFNPNL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSJOBDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSJOBGRP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSJOBITEM');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSJOBPNL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSJOBMESSAGE');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSMUTUALEXCL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSRECUR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSRECURDATE');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSRECUREXEMPT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPRCSRQST');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSDEFNNOTIFY');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSDEFNCNTDIST');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSJOBNOTIFY');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PRCSJOBCNTDIST');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SERVERDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSSERVERSTAT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SERVERCATEGORY');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SERVERCLASS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SERVERNOTIFY');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('SERVEROPRTN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('DAEMONGROUP');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('DAEMONGROUP_VW');-- Developer tables
INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAEAPPLDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAEAPPLSTATE');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAEAPPLTEMPTBL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAESECTDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAESTEPDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSAESTMTDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSCONTDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSCONTENT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGATTR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGCATDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGFLDOVR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGPARTS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGREC');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGSETDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSMSGVER');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPACKAGEDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPCMNAME');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPCMPROG');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPCMTXT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYFIELD');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYRECORD');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYSTATS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYEXECLOG');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYSELECT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYCRITERIA');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYEXPR');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSQRYBIND');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSSQLDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSSQLTEXTDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSTREEDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSTREENODE');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSURLDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PT_URL_PROPS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSXPRPTDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSXPDATASRC');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSXPTMPLDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSXPTMPLFILEDEF');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSXPRPTVIEWER');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSCHGCTLDEF');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSCHGCTLLOCK');-- Audit & user profile tables
INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPTLOGINAUDIT');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSPRUFDEFN');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSUSEREMAIL');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPRALIAS');INSERTINTOPS_CHG_PSLENS_WL(RECNAME)VALUES('PSOPTIONS');
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.
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; persistent state (configuration history, report results, alert state) is stored in an embedded NATS data store.
Configuration Synchronization
Active configuration is synchronized automatically between config.yaml and the embedded NATS KV store:
Bare-Minimum Boot: The server can boot with zero configured database connections. Database connections can be added later directly through the Settings UI at /settings.
Disk Edit Recognition: On boot, if config.yaml has been modified on disk more recently than the active NATS KV store snapshot, psLens automatically syncs the file changes into NATS KV.
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:
1Password Secret References (op://...):
Pass 1Password Secret URIs directly in config.yaml or JSON configurations:
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.
Master Key Encryption (PSLENS_MASTER_KEY):
Set PSLENS_MASTER_KEY (a 64-character hex AES-256 key). Passwords entered in the Settings UI or stored in NATS KV/YAML are stored as encrypted strings (ENC[...]).
Environment Variable Overrides:
Override passwords dynamically via environment variables without editing files:
exportPSLENS_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.jsonserver:port:8080host:"0.0.0.0"natsStoreDir:"./data/nats"projectStoreDir:"./data/projects"dmsStoreDir:"./data/dms"traceStoreDir:"./data/traces"appBaseURL:"http://localhost:8080"recentlyViewed:maxItems:20databases:- 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:truealerts:enabled:trueintervalMinutes: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:falsedowntimes:- name:"Nightly Downtime"enabled:truestartTime:"22:00"endTime:"06:00"daysOfWeek:[1,2,3,4,5]- name:"Weekend Maintenance"enabled:truestartAt:"2026-07-04T00:00:00Z"endAt:"2026-07-05T23:59:59Z"alerts:enabled:trueintervalMinutes:5checks:long_running_processes:enabled:truethresholdMinutes:20anomalyMultiplier:4.0anomalyMinBaselineMinutes:10process_errors:enabled:truelookbackHours:24ib_operation_errors:enabled:truelookbackHours:24ib_pub_contract_errors:enabled:truelookbackHours:24ib_sub_contract_errors:enabled:truelookbackHours:24ib_operation_stalled:enabled:truethresholdMinutes:30ib_pub_contract_stalled:enabled:truethresholdMinutes:30ib_sub_contract_stalled:enabled:truethresholdMinutes:30genericSWSAlerts:- id:"stale_users"name:"Stale User Accounts"enabled:trueseverity:"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:5auth:enabled:trueauthorizedUsers:- "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:truealertTypes:["*"]databases:["PROD"]severityMin:"warning"type:"email"target:"psoft-alerts@example.com"- id:"slack-webhook"enabled:truealertTypes:["process_errors","ib_operation_errors"]databases:["*"]type:"webhook"target:"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"- id:"teams-webhook"enabled:truealertTypes:["*"]databases:["*"]type:"webhook"target:"https://example.webhook.office.com/webhookb2/..."reports:- id:"security-full-access-permlists"enabled:truedbNames:["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/..."}]},"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.
Setting
Default
Description
port
8080
TCP port psLens listens on
host
0.0.0.0
Network interface to bind (use 127.0.0.1 to restrict to localhost)
natsStoreDir
./data/nats
Directory for persistent NATS data (report results, alert history)
projectStoreDir
./data/projects
Directory for uploaded PeopleSoft project XML definitions
dmsStoreDir
./data/dms
Directory for imported Data Mover (DMS) script libraries
traceStoreDir
./data/traces
Directory for uploaded PeopleSoft trace files (.tracesql, .trc, .aet)
outageRetentionDays
90
Retention 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.
Setting
Required
Description
name
Yes
Short identifier shown in the UI (e.g., PROD, DEV)
description
Yes
Human-readable label for the database
baseURL
Yes
Full URL to the SWS psoftQL endpoint, including the service name
username
Yes
PeopleSoft operator ID for API authentication
password
Yes
Password for the operator ID
piaURL
No
Base URL for PeopleSoft Internet Architecture (used for deep links to PeopleSoft pages, if supported)
timezone
No
IANA timezone name for the database server (e.g., America/Chicago). Defaults to UTC if not set. Used to interpret timestamps correctly.
downtimes
No
List of scheduled downtime windows to suppress alerts and monitoring.
notifications
No
Database-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:
Setting
Required
Description
name
Yes
Identifier for the downtime window.
enabled
Yes
Whether the downtime rule is active.
startTime
No
Start time of day in HH:MM format (e.g., 22:00). Set together with endTime.
endTime
No
End 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.
daysOfWeek
No
Array 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.
startAt
No
Specific start timestamp in RFC3339 format (e.g., 2026-07-04T00:00:00Z) for one-off maintenance.
endAt
No
Specific end timestamp in RFC3339 format (e.g., 2026-07-05T23:59:59Z) for one-off maintenance.
startTime/endTime are wall-clock times in the connection’s timezone (UTC if unset). Rules are validated when the configuration is saved; a rule must define a recurring window (startTime + endTime) and/or a one-off range (startAt/endAt).
When a database is in a scheduled downtime, the connection manager sets its status to Downtime on the dashboard, background alert checks are skipped (including the Integration Broker down check), email/webhook notifications are suppressed, and the database does not count against overall health. When the window ends, psLens rechecks the connection immediately and treats the first failed check as a real outage — size windows to cover the system’s boot time.
A weekend window that spans from Saturday evening to Monday morning is composed of two rules, since an overnight window belongs to its start day:
downtimes:- name:"Weekend nights"enabled:truestartTime:"22:00"endTime:"06:00"daysOfWeek:[6,0]# Sat night -> Sun morning, Sun night -> Mon morning- name:"Sunday all day"enabled:truestartTime:"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:
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:
If OP_SERVICE_ACCOUNT_TOKEN (or PSLENS_OP_SERVICE_ACCOUNT_TOKEN) is set, psLens resolves all op:// references on boot using the 1Password SDK. If the environment variable is not set, 1Password resolution is skipped.
Database-Specific Notifications
By default, alert notifications for all databases are routed using global subscription rules (see Notifications & Webhooks Settings).
However, you can configure database-specific notifications under the notifications property of a database entry. Doing so acts as a complete override: all alerts (regardless of severity level) and resolutions for that specific database are routed directly to the database-level targets, bypassing global subscriptions entirely.
Each entry in the notifications block supports the following settings:
Setting
Type
Description
emailEnabled
Boolean
Activates email notifications for this database.
emailTarget
String
Comma-separated list of recipient email addresses.
webhookEnabled
Boolean
Activates webhook notifications for this database.
webhookTarget
String
Webhook destination URL (e.g., Slack, MS Teams, or a generic endpoint).
Example database connection configuration with inline notifications:
Each alert type under checks supports some or all of the following settings:
Setting
Description
enabled
Whether this check is active
thresholdMinutes
For stalled/long-running checks: how many minutes before flagging (default varies by check). For long_running_processes, this is the fallback static threshold.
lookbackHours
For error checks: how many hours back to look for failures (default varies by check)
excludeProcesses
List of process names to skip (for process-related checks)
excludeOperations
List of IB operation names to skip (for Integration Broker checks)
anomalyMultiplier
Multiplier applied to the rolling median duration to calculate the dynamic threshold (default: 4.0)
anomalyMinBaselineMinutes
Minimum baseline duration in minutes. Dynamic thresholds are capped to be at least this value to prevent false alerts on very fast processes (default: 10)
Available Alert Checks
Check Key
Name
Description
long_running_processes
Long-Running Processes
Flags processes running longer than their rolling median-based expected runtime (or thresholdMinutes fallback)
process_errors
Process Errors
Finds processes that failed within lookbackHours (default: 24 hours)
ib_operation_errors
IB Operation Errors
Finds async IB operations in Error or Timeout status within lookbackHours (default: 24 hours)
ib_pub_contract_errors
IB Publication Contract Errors
Finds pub contracts in Error or Timeout status within lookbackHours (default: 24 hours)
ib_sub_contract_errors
IB Subscription Contract Errors
Finds sub contracts in Error or Timeout status within lookbackHours (default: 24 hours)
ib_operation_stalled
IB Operations Stalled
Finds async IB operations stuck in New or Working status longer than thresholdMinutes (default: 30 min)
ib_pub_contract_stalled
IB Publication Contracts Stalled
Finds pub contracts stuck in New or Working status longer than thresholdMinutes (default: 30 min)
ib_sub_contract_stalled
IB Subscription Contracts Stalled
Finds sub contracts stuck in New or Working status longer than thresholdMinutes (default: 30 min)
locked_oprid_processes
Locked OPRID Scheduled Processes
Finds queued or scheduled processes whose submitting OPRID has a locked account
backlogged_processes
Backlogged Processes
Detects processes currently stuck in a Queued or Blocked state longer than thresholdMinutes (default: 30 min)
queue_latency
Queue Latency
Detects processes that experienced a start delay (BEGINDTTM - RUNDTTM) greater than thresholdMinutes (default: 15 min)
failed_logins
Failed Logins
Detects users with excessive failed login attempts in PSPTLOGINAUDIT (defaults to > thresholdCount of 5)
process_run_check
Process Run Check
Monitors configured critical processes and alerts when they haven’t run successfully within their configured time window
ib_operation_volume
Abnormal IB Operation Volume
Detects when IB operation instance volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_pub_contract_volume
Abnormal IB Pub Contract Volume
Detects when IB publication contract volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_sub_contract_volume
Abnormal IB Sub Contract Volume
Detects when IB subscription contract volume exceeds the historical average by a percentage specified in thresholdCount (default: 50)
ib_sync_exceptions
IB Sync Operation Exceptions
Detects synchronous service operations with errors in PSIBLOGHDR within lookbackHours (default: 24 hours). Disabled by default.
no_process_completed
No Process Completed
Fires when no process has successfully completed within the lookbackHours (default: 1 hour)
ib_down
Integration Broker Down
Alerts when SWS REST endpoint connection failures indicate the Integration Broker is down
weblib_down
Web Server / WebLib Down
Alerts when PeopleSoft Web Server is down or configured WebLib URLs fail to respond
ib_no_active_domain
IB No Active Domain
Alerts when there is no active domain found in PSAPMSGDOMSTAT
ib_dispatcher_down
IB Dispatcher Down
Alerts when an Integration Broker dispatcher process is inactive or has not updated status within thresholdMinutes (default: 10 min)
ib_nodes_down
IB Nodes Down
Alerts 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). Authentication is enabled by default. To disable authentication, set enabled: false explicitly in config.yaml. Authentication settings are file-authoritative and cannot be modified or turned off from the web interface.
Setting
Default
Description
enabled
true
Defaults to true. To disable, set to false explicitly in config.yaml.
mode
magic_link
Authentication mode: magic_link or oidc.
authorizedUsers
[]
Whitelist of email addresses allowed to log in (case-insensitive).
OIDC authorization scopes requested from the Identity Provider.
oidc.allowedGroups
[]
Optional list of required group claims to restrict access.
oidc.groupClaim
groups
Claim key in the ID token used for group membership verification.
ipAllowlist.enabled
false
Enable 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
The email server is edited in the app under Settings → Notifications (Email Server card). Changes apply to alert and report emails immediately; login (magic link) emails pick them up 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 that, the settings saved in the UI are authoritative. PSLENS_SMTP_* environment variables override both and make the card read-only in the UI.
Setting
Default
Description
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)
fromName
psLens
Sender name shown in emails (supports op:// 1Password references)
fromEmail
-
Sender email address for SMTP (supports op:// 1Password references)
Outbound email can be tested on the same card: enter a recipient address under Send Test Email and click Send Test. The result is recorded in Alert Delivery History.
Notifications & Webhooks Settings
Notification rules decide where alert messages are dispatched. They are managed in the app under Settings → Notifications, where email distribution lists, webhook destinations (Slack, Microsoft Teams, and custom endpoints), and subscription routing rules can be configured and live-tested.
Email Distribution Lists (notifications.email_lists)
Predefined recipient lists referenced by notifications and reports:
For production deployments, you should restrict access to the psLens interface.
Recommended Security Controls
Enable Built-In Magic Link Auth:
Turn on auth.enabled and configure SMTP credentials and authorizedUsers to require code validation on login.
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.
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 either turning on the built-in magic-link auth or placing an authenticated reverse proxy in front of it. Doing so exposes read access to PeopleSoft system metadata.
Active Configuration and Hot-Reloading
psLens keeps its settings in two places:
Active configuration (embedded key-value store): when you edit connections, credentials, email settings, alert schedules, or notification rules in the web interface, changes are saved here and take effect immediately without a server restart. Every change is kept as a numbered revision under Settings → Change History, where any revision can be viewed or restored.
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 the environment variable PSLENS_FORCE_SEED_CONFIG=true is set, the application seeds the 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 to overwrite the active configuration with the file contents (this discards changes made in the UI).
If the file path is writable, click Overwrite config.yaml on disk to write the active configuration to the file. On containerized or ephemeral deployment models (like fly.io without persistent storage volumes), these local file changes will be lost when the container restarts or is re-deployed.
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 the PSLENS_MASTER_KEY environment variable is set with a 32-byte hex-encoded key, all database and SMTP passwords entered in the UI are encrypted at rest. If the master key is not configured, passwords are saved in plaintext.
Rotating the Master Key
To rotate the cryptographic key used for credential encryption:
Enter a new 32-byte hex key in the Rotate Encryption Key field.
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.
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:
How do I get the image? Distribution and authentication.
How do I upgrade without losing my config? Volumes, env vars, and the master key.
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
psLens is published to the GitHub Container Registry (GHCR) as a private package. Cedar Hills Group issues a read-only token to each client.
Authenticating
Cedar Hills Group sends you a GitHub fine-grained personal access token (PAT) scoped to read:packages on the pslens package only.
The credentials are stored in ~/.docker/config.json. They persist across host reboots.
Verify the pull works:
docker pull ghcr.io/cedarhillsgroup/pslens:latest
Image Tags
The release pipeline publishes four tag flavors for every release:
Tag
Example
Use when
latest
ghcr.io/cedarhillsgroup/pslens:latest
Dev/test only — never pin production here
vMAJOR.MINOR.PATCH
:v1.4.2
Production — exact reproducibility
vMAJOR.MINOR
:v1.4
Production — auto-pickup of patch releases
Git SHA
:a3f8c12
Pinning to a pre-release build
Recommended: Pin production to vMAJOR.MINOR. You’ll automatically pick up patch fixes when you re-run docker compose pull, but never get an unexpected breaking change from a minor or major version bump.
When You Can’t Reach ghcr.io
If the Docker host can’t make outbound HTTPS to ghcr.io (common in segmented enterprise networks), use the air-gapped flow documented in 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 own private registry (Harbor, AWS ECR, Azure ACR, GitLab Registry). Pull it once, retag, push, and reference the mirrored image in your docker-compose.yml. Cedar Hills Group is happy to provide a one-time pull script if you need to automate this.
Troubleshooting Pull Failures
Error
Cause
Fix
denied: denied
Token expired or revoked
Renew the PAT with Cedar Hills Group
unauthorized
Token has the wrong scope
PAT needs read:packages on the pslens package
no basic auth credentials
docker login wasn’t run, or ~/.docker/config.json was lost
Re-run docker login ghcr.io
manifest unknown
The tag you asked for doesn’t exist yet
Check 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 is explicit about what survives docker compose pull && docker compose up -d and what doesn’t.
What Persists, What Doesn’t
Persistent (must be on a volume):
/data/nats — NATS JetStream store. Contains the recently-viewed objects KV, the report store (generated markdown reports), alert state, and, if you use the in-app config UI, 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 (re-fetched 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. As long as you don’t docker volume rm pslens_data, your data survives any number of image upgrades.
Three Configuration Modes
There are three ways to source configuration. Pick one based on how many people will administer the system and how you manage secrets.
Mode
Where config lives
Where secrets live
Best for
A. File-only
config.yaml bind-mounted from host
Plaintext in config.yaml
Internal-only dev/test
B. File + env override
config.yaml for non-secrets
PSLENS_DB_{NAME}_PASSWORD env vars, sourced from .env or a secrets manager
Recommended default for client-hosted
C. KV-encrypted
Minimal config.yaml; full config in NATS KV bucket, AES-256 encrypted at rest
Encrypted blob in /data/nats, unlocked by PSLENS_MASTER_KEY
Multi-admin setups where you use the in-app config UI
Mode B example (recommended)
config.yaml:
server:port:8080host:"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_PASSWORDtimezone:"America/Chicago"
.env (sibling of docker-compose.yml, chmod 600, gitignored):
docker-compose.yml references env_file: .env; Docker injects every 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 requiresPSLENS_MASTER_KEY to be set. It’s used to encrypt database passwords stored in the NATS KV bucket. Generate one once:
openssl rand -base64 32
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
Daily backup of the data volume is one line:
docker run --rm \
-v pslens_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/pslens-data-$(date +%F).tar.gz -C / data
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 — check into your infrastructure-as-code repo (gitignore the password fields, or use the placeholder pattern from Mode B).
.env — store in your secrets manager. Never check this into git.
To restore: stop psLens, docker volume create pslens_data, untar into the volume, restart.
3. TLS / HTTPS Options
psLens does not terminate 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. They’re compared below on the same axes: certificate source, automation, operational complexity, and the scenario each fits best.
Quick recommendation
Your scenario
Recommended option
Default — most clients
Option 3: Caddy sidecar
Corporate PKI with certs-as-code
Option 4: nginx sidecar (or Option 1 if minimalist)
Internal-only, small team, already using Tailscale
Option 6: Tailscale Serve / Funnel
Already running Traefik
Option 5: Traefik
Public internet, single host, no proxy wanted
Option 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.
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.
Axis
Detail
Certificate source
Let’s Encrypt (free, 90-day, auto-renewed at ~60 days)
Renewal
Fully automatic, in-process
Hot reload
N/A — the library reloads on its own renewal cycle
Pros
Cheapest TLS, zero ops effort after initial config
Cons
Requires port 80 reachable from the public internet for HTTP-01 challenge; rules out fully internal deployments
Best for
Public-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 terminates TLS and reverse-proxies to psLens on the internal Docker network.
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.4expose:- "8080"# no longer "ports:" — only Caddy needs an external portvolumes:- ./config.yaml:/app/config.yaml:ro- pslens_data:/dataenv_file:.envrestart:unless-stoppedcaddy:image:caddy:2-alpineports:- "80:80"- "443:443"volumes:- ./Caddyfile:/etc/caddy/Caddyfile:ro- caddy_data:/data- caddy_config:/configrestart:unless-stoppedvolumes:pslens_data:caddy_data:caddy_config:
Axis
Detail
Certificate source
Let’s Encrypt (public), or Caddy’s built-in CA (tls internal)
Renewal
Fully automatic; cert state in the caddy_data volume
Hot reload
Caddy reloads certs on its own renewal cycle
Pros
Trivial config, handles both public-internet and internal-only, decouples TLS from the app (restarting psLens doesn’t drop TLS sessions)
Cons
Second container to operate; internal CA requires distributing the root cert to clients
Best for
The 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{listen443sslhttp2;server_namepslens.example.com;ssl_certificate/certs/pslens.crt;ssl_certificate_key/certs/pslens.key;ssl_protocolsTLSv1.2TLSv1.3;location/{proxy_passhttp://pslens:8080;proxy_set_headerHost$host;proxy_set_headerX-Real-IP$remote_addr;proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;proxy_set_headerX-Forwarded-Proto$scheme;# Server-Sent Events: disable buffering for the SSE endpoints
proxy_bufferingoff;proxy_cacheoff;}}server{listen80;server_namepslens.example.com;return301https://$host$request_uri;}}
Important for psLens: the proxy_buffering off directive is required. psLens relies on Server-Sent Events for most of the UI; with buffering enabled, the UI will appear frozen until pages finish loading entirely.
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.
Axis
Detail
Certificate source
Customer-managed PEM (corporate CA, commercial CA, certbot)
The most-deployed reverse proxy on earth; every enterprise ops team has nginx runbooks; easy to add request-level customization (auth, rate-limits, rewrites)
Cons
No built-in cert automation; more boilerplate than Caddy for the same outcome on the happy path
Best for
Clients 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.
Bypass TLS-at-psLens entirely by exposing the service over a managed tunnel. TLS terminates at the tunnel provider’s edge; psLens stays on plain HTTP inside the tunnel.
Tailscale Serve (private to your tailnet — internal use):
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 shape; 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.
Axis
Detail
Certificate source
Tunnel provider (Tailscale or Cloudflare)
Renewal
Fully automatic
Pros
Zero TLS config on the psLens side; no inbound ports opened on the firewall; mesh networking (Tailscale) is great for multi-DB connectivity
Cons
Adds a third-party dependency in the data path; some clients have policies against cloud tunnels for compliance-relevant tools; rate limits
Best for
Internal-only deployments where you want easy access for a small team without standing up a reverse proxy or opening firewall ports
4. Upgrades
The upgrade flow depends on whether you can reach ghcr.io and whether you’re pinning to a specific version. The data volume and config.yaml are untouched in all three cases.
Standard (online) upgrade
If you pinned to :latest or to a vMAJOR.MINOR tag that’s auto-receiving patch fixes:
cd /opt/pslens
docker compose pull pslens
docker compose up -d pslens
pull fetches the new image; up -d recreates the psLens container with the new image and reattaches the existing volume and config. Data is preserved.
Pinned-version upgrade (recommended for production)
To upgrade to v1.5: edit the file, then pull and recreate:
# Edit docker-compose.yml: v1.4 to v1.5docker compose pull pslens
docker compose up -d pslens
Rollback is a one-line edit back to the previous tag, then docker compose up -d pslens again. The old image is still in the local Docker cache (unless you ran docker image prune in between).
Air-gapped upgrade
# On a machine with internet:docker pull ghcr.io/cedarhillsgroup/pslens:v1.5
docker save ghcr.io/cedarhillsgroup/pslens:v1.5 | gzip > pslens-v1.5.tar.gz
# Transfer the .tar.gz to the target host, then:docker load < pslens-v1.5.tar.gz
docker compose up -d pslens
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.
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 enabled, incoming HTTP requests from IP addresses outside the allowlist are immediately blocked with an HTTP 403 Forbidden response before any session authentication or application routing takes place.
Configuration
Add the security.ipAllowlist block to your config.yaml:
When psLens runs behind a reverse proxy, load balancer, or cloud provider (e.g. Fly.io, Cloudflare, AWS Application Load Balancer), client requests terminate at the proxy. Setting trustForwardHeaders: true enables client IP inspection in the following precedence:
Fly-Client-IP (Fly.io edge proxy)
CF-Connecting-IP (Cloudflare CDN)
X-Real-IP (Nginx / standard reverse proxies)
X-Forwarded-For (First IP in comma-separated proxy list)
RemoteAddr (Direct TCP connection socket)
Health Check Probes Exemption
The /healthz endpoint is always exempt from IP filtering. This ensures infrastructure health checks (Kubernetes liveness/readiness probes, Fly.io health monitors, AWS target groups) continue to report accurate instance health without needing proxy IP whitelisting.
Dynamic Updates
When configuration is managed via NATS KV / settings, IP allowlist changes take effect immediately on subsequent requests without requiring 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
Navigate to Settings → Authentication & SSO (/settings/auth).
Set the login method to Native OIDC / Single Sign-On.
Choose your Identity Provider preset or enter the Issuer URL:
Microsoft Entra ID (Azure AD):https://login.microsoftonline.com/{TENANT_ID}/v2.0
Click Discover Endpoints: psLens validates the connection and auto-detects endpoints, supported scopes, and token claim attributes.
In your Identity Provider’s App Registration console, register the exact Redirect URI displayed on the page:
https://your-pslens-domain.com/auth/callback
Paste the Client ID and Client Secret.
(Optional) Specify Allowed Groups (e.g. pslens-admins) to enforce role-based access control.
Click Save Authentication Settings. Changes take effect immediately without restarting psLens.
Supported Group Claims & RBAC
When verifying OIDC tokens, psLens extracts group memberships from the ID token claim defined in groupClaim (defaulting to groups or roles). If allowedGroups is configured, only users belonging to at least one listed group will be granted access upon successful authentication.