Demo URL: https://gtn6w-niaaa-aaaam-afxaa-cai.icp0.io/
A production-ready audit logging application for the Internet Computer (IC) with a Motoko backend and Angular 21 frontend. Demonstrates immutable data structures, modern Angular patterns, and Material Design UI.
icLogging/
├── .gitignore # Git ignore rules
├── canister_ids.json # Canister IDs for deployed canisters
├── dfx.json # DFX project configuration
├── mops.toml # Motoko package manager configuration
├── package.json # Root package.json with npm scripts
├── package-lock.json # Root package-lock.json
├── src/
│ ├── backend/
│ │ └── main.mo # Motoko backend canister (immutable map-based storage)
│ ├── declarations/ # Generated TypeScript bindings from Candid
│ │ └── backend/
│ │ ├── backend.did.d.ts
│ │ ├── backend.did.js
│ │ ├── index.d.ts
│ │ └── index.js
│ └── frontend/ # Angular 21 frontend application
│ ├── src/
│ │ ├── app/
│ │ │ ├── components/
│ │ │ │ ├── info-dialog/ # About/Info modal
│ │ │ │ ├── logging/ # Main logging view component
│ │ │ │ ├── logging-add-dialog/ # Dialog for adding logs
│ │ │ │ ├── logging-detail-dialog/# Dialog for viewing log details
│ │ │ │ └── logging-table/ # Table component with sorting
│ │ │ ├── services/
│ │ │ │ ├── date-format.service.ts # Date formatting utilities
│ │ │ │ ├── ic-agent.service.ts # IC agent initialization
│ │ │ │ ├── log-level.service.ts # Shared log level utilities
│ │ │ │ ├── logging.service.ts # Logging API service
│ │ │ │ └── qr-code.service.ts # QR code generation service
│ │ │ ├── styles/
│ │ │ │ └── dialog-shared.scss # Shared dialog styles
│ │ │ ├── app.component.html # Root component template
│ │ │ ├── app.component.scss # Root component styles
│ │ │ ├── app.component.ts # Root component
│ │ │ └── environments/ # Environment configuration
│ │ ├── assets/ # Static assets
│ │ ├── favicon.ico # Favicon
│ │ ├── index.html # Application entry HTML
│ │ ├── main.ts # Application entry point
│ │ └── styles.scss # Global styles
│ ├── scripts/
│ │ ├── build.js # Build script
│ │ ├── deploy.js # Deploy script
│ │ └── generate-env.js # Environment generation script
│ ├── angular.json # Angular CLI configuration
│ ├── package.json # Frontend dependencies
│ ├── package-lock.json # Frontend lock file
│ ├── tsconfig.app.json # TypeScript config for app
│ └── tsconfig.json # TypeScript configuration
- DFX SDK installed
- Node.js 20+ and npm installed, nvm use v22.12.0
- Angular CLI 21 installed globally:
npm install -g @angular/cli@21
-
Install DFX (if not already installed):
sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)" -
Install frontend dependencies:
npm run install:frontend
-
Generate backend DID file:
dfx generate backend
This will create the TypeScript bindings for the backend canister.
-
Configure frontend environment files: Set the
backendCanisterIdandbackendAuthKeyin both environment files:src/frontend/src/environments/environment.ts(development)src/frontend/src/environments/environment.prod.ts(production)
export const environment = { production: false, // or true for prod backendCanisterId: 'your-canister-id-here', backendAuthKey: 'your-secret-key-here' };
Note: The
backendAuthKeymust match the key you'll set in the backend after deployment (see step 3 in Development section).
-
Start the local Internet Computer network:
dfx start
Keep this terminal running.
-
Deploy the canisters (in a new terminal):
dfx deploy
This will:
- Compile and deploy the Motoko backend canister
- Build the Angular frontend
- Deploy the frontend as an assets canister
-
Set the authentication key (required before adding logs): After deployment, you must set the authentication key using the
setAuthKeyfunction. This can only be called by the canister controller (installer).dfx canister call backend setAuthKey ""Important:
- Only the canister controller can call
setAuthKey - The key must match the
backendAuthKeyin your frontend environment files - Set the same key in both
environment.ts(development) andenvironment.prod.ts(production)
- Only the canister controller can call
-
Access the application:
- Frontend: Open the URL shown in the terminal (typically
http://localhost:8080) - Backend canister ID will be displayed after deployment
- Frontend: Open the URL shown in the terminal (typically
Edit src/backend/main.mo and redeploy:
dfx deploy backendFor frontend development with hot-reload:
-
Start DFX in one terminal:
dfx start
-
Deploy backend only:
dfx deploy backend
-
Run Angular dev server in another terminal:
cd src/frontend npm start -
Access the app at
http://localhost:4200
Note: When running the Angular dev server, the backend canister ID is automatically configured from the environment. The IcAgentService reads the canister ID from environment.backendCanisterId and detects the network (local vs. mainnet) automatically.
Create canister in subnet
## first canister creation commands
dfx canister create backend --ic --with-cycles 1T --subnet 4ecnw-byqwz-dtgss-ua2mh-pfvs7-c3lct-gtf4e-hnu75-j7eek-iifqm-sqe
## 2nn3mi-waaaa-aaaam-afw7q-cai
dfx canister create frontend --ic --with-cycles 1T --subnet 4ecnw-byqwz-dtgss-ua2mh-pfvs7-c3lct-gtf4e-hnu75-j7eek-iifqm-sqe
## gtn6w-niaaa-aaaam-afxaa-caiBuild the frontend for deployment:
npm run buildThen deploy:
dfx deploy- Motoko: Internet Computer's native language
- Motoko Core Library: Using
mo:core/pure/Mapfor immutable, ordered map storage - Persistent Actor: State persists across upgrades
- 1-based indexing: Audit entries start at ID 1
- Angular 21: Latest Angular with standalone components
- Angular Material 21: Material Design components
- TypeScript 5.9: Type-safe development
- ICP SDK 5.0:
@icp-sdk/corefor IC agent communication - RxJS 7.8: Reactive programming
- Signals: Angular's new reactive primitives
The Motoko backend uses immutable data structures and provides the following methods:
setAuthKey(key: Text) : async ()- Set the authentication key (only callable by canister controller/installer)- Security: Only the canister controller can call this function
- Required: Must be called after deployment before adding any log entries
- Usage:
dfx canister call backend setAuthKey "your-secret-key-here"
log(key: Text, level: Text, message: Text) : async Nat- Add an audit entry (returns the new entry ID)- Requires the
keyparameter to match the configuredauthKey
- Requires the
getLogs() : async [LogEntry]- Get all audit entries (ordered by ID)getLog(id: Nat) : async ?LogEntry- Get a specific audit entry by IDgetLogCount() : async Nat- Get the total number of audit entries
Note:
- Audit entries are immutable and cannot be deleted. This ensures a complete audit trail.
- The authentication key must be set via
setAuthKeybefore any audit entries can be added.
type LogEntry = {
id: Nat;
timestamp: Int;
level: Text;
message: Text;
};- Audit Table: Sortable table with columns for ID, Date/Time, and Level
- Add Audit Entries: Dialog-based form to add new audit entries with level selection
- View Details: Click any row to view full audit entry details in a modal
- QR Code Deep Links: Each log entry detail dialog displays a QR code that links directly to that entry. Scanning the QR code (or opening the URL) auto-opens the entry's detail dialog. Uses the
?entry=<id>query parameter for deep linking without a full Angular Router. - Refresh: Manual refresh button to reload audit entries from the backend
- About Dialog: Information about the application and technology stack
- Material Design 21: Modern, accessible UI components
- Responsive Design: Works on desktop and mobile devices
- Sortable Columns: Sort by ID, Date, or Level (default: ID descending)
- Visual Indicators: Color-coded icons for each audit level
- Error Handling: User-friendly error messages via snackbar notifications
- Loading States: Spinner indicators during async operations
- Accessibility: ARIA labels and keyboard navigation support
LoggingComponent: Main container componentLoggingTableComponent: Sortable table with row click handlersLoggingAddDialogComponent: Modal dialog for adding new audit entriesLoggingDetailDialogComponent: Modal dialog for viewing audit entry detailsInfoDialogComponent: About/Information modal
IcAgentService: Manages IC agent initialization and actor creationLoggingService: Handles all backend API calls for audit operationsLogLevelService: Shared utility for audit level colors and iconsDateFormatService: Utility service for formatting timestampsQrCodeService: Generates QR code data URLs and builds deep-link entry URLs
- Memory Management: Proper subscription cleanup with
takeUntilpattern - Error Handling: Comprehensive error handling with user feedback
- Type Safety: Full TypeScript type coverage
- Null Safety: Defensive programming with null checks
- Accessibility: ARIA labels and semantic HTML
-
Ensure you have cycles in your wallet
-
Deploy to mainnet:
dfx deploy --network ic
-
Set the authentication key (required after mainnet deployment):
dfx canister call backend setAuthKey "your-secret-key-here" --network icNote: Make sure to set the same key in your production environment file (
environment.prod.ts).
- User interacts with Angular frontend (adds audit entry, views details, etc.)
- Frontend service (
LoggingService) calls IC agent service - IC agent service (
IcAgentService) manages actor connection to backend canister - Backend canister processes request using immutable map storage
- Response flows back through the chain to update the UI
- Backend: Immutable ordered map (
Map.Map<Nat, LogEntry>) ensures data integrity - Frontend: Angular Signals for reactive state management
- Component Communication: Event emitters and service injection
- Immutable Audit Entries: Audit entries cannot be deleted, ensuring complete audit trail
- Ordered Storage: Map automatically maintains order by ID
- Standalone Components: Modern Angular architecture without NgModules
- Service Layer: Separation of concerns with dedicated services
- Error Boundaries: Comprehensive error handling at all levels
The backend API is accessible from any IC-compatible agent. In addition to the web UI, you can add audit entries programmatically using:
- Node.js: Using
@icp-sdk/coreor@dfinity/agent - Go: Using
github.com/dfinity/go-icp-sdk - Rust: Using
ic-agentcrate - Python: Using
ic-pylibrary
Example usage from external agents is documented in the About dialog within the application.
- Canister ID not found: Run
dfx deployfirst to create the canisters, then checkenvironment.ts - DID file errors: Run
dfx generate backendto regenerate TypeScript bindings - Frontend build errors: Ensure all dependencies are installed with
npm run install:frontend - Port conflicts: Change the port in
dfx.jsonor stop other services using port 8080 - Network errors: Ensure DFX is running (
dfx start) and canisters are deployed - Type errors: Regenerate DID files with
dfx generate backendafter backend changes - Authentication errors when adding audit entries:
- Ensure
setAuthKeyhas been called after deployment:dfx canister call backend setAuthKey "your-key" - Verify the
backendAuthKeyin your environment files matches the key set in the backend - Only the canister controller can call
setAuthKey- check your DFX identity withdfx identity whoami
- Ensure