Skip to content

Commit

Permalink
Updated you and layout changes and doco
Browse files Browse the repository at this point in the history
  • Loading branch information
salty2011 committed Jan 17, 2025
1 parent 83496fd commit fc1caeb
Show file tree
Hide file tree
Showing 27 changed files with 1,849 additions and 461 deletions.
11 changes: 11 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
dist
dist-server
.git
.gitignore
.env
.env.*
*.log
.DS_Store
.vscode
.idea
51 changes: 32 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# wolfmanager
A web interface for managing Wolf, currently in development and does very little other than the following

- fetch list of your steam games
- fetch game artwork from steamgriddb

A web interface for managing Wolf, providing a centralized dashboard for game library management and Wolf configuration.

Current Features:
- Steam game library integration
- Game artwork fetching from SteamGridDB
- User management interface
- Admin settings configuration
- Task management and monitoring
- System log viewing
- Wolf API integration
- Configuration management

Here are some screenshots of the current state of the project
![screenshot](./images/dashboard.png)
Expand All @@ -13,33 +19,40 @@ Here are some screenshots of the current state of the project
## Game Management
- [ ] Add method for installing games using SteamCMD or scanning existing install library
- [ ] Add installed game into Wolf Configured apps along with game artwork and direct launch for steam
- [ ] Implement manual artwork search for games with missing artwork

## Wolf Management
- [ ] Expose wolf api to frontend
- [ ] display active wolf sessions
- [ ] add / remove client pairing
- [x] Expose wolf API to frontend
- [ ] Implement full session management
- [ ] Enhance client pairing workflow

## Security
- [ ] Create admin user with auth and admin only functions
- [ ] Add user management, this would just create a user state folder and point all pair clients to that user dir.
- [ ] Implement authentication system
- [ ] Add role-based access control
- [ ] Enhance user state isolation

## Other
= [ ] Publish the Bruno API collection I created to diagnose, research
- [ ] Publish Bruno API collection for diagnostics and development
- [ ] Add comprehensive error handling
- [ ] Implement automated testing

# Wishful Thinking
- [ ] Have wolf manager detect and create the wolf stack it manages
- [ ] Have Wolf Manager detect and create the wolf stack it manages
- [ ] Implement additional game libraries
- [ ] Add support for non-Steam game libraries
- [ ] Implement backup and restore functionality

# Known Issues
- Some artwork does not display for games, need a method for manually searching.
- It literally does nothing yet other than chew watts and look sorta pretty and as such have not implemented real features.
- Some artwork does not display for games, need a method for manually searching
- Authentication system needs to be implemented
- Some API endpoints need additional error handling

# FAQ
- Q: How does tis help with shared libraries?
- A: Long term what want to do is have it create an app entry for each game using the steam container we have and mount the specific game files into the container as a read only layer with the writable layer point back to the user profile path. The user experience would be launch moonlight and then select the game, it launches and and game state data is saved in the profile path. Game updates are then handled by WolfManager using SteamCMD.
- Q: How does this help with shared libraries?
- A: Long term what we want to do is have it create an app entry for each game using the steam container we have and mount the specific game files into the container as a read only layer with the writable layer pointing back to the user profile path. The user experience would be launch moonlight and then select the game, it launches and game state data is saved in the profile path. Game updates are then handled by WolfManager using SteamCMD.

- Q: How does multi-user work?
- A: Wolf doesn have the concept of users, just devices and where the state for each device is stored. Wolf does allow one state folder to work across multiple devices, so what we can do is have WolfManager create a user state folder for each user and then point all devices for that user to the same state folder. We can manage users and their pairing through Wolf Manager. But this is all future work and could change
- A: Wolf doesn't have the concept of users, just devices and where the state for each device is stored. Wolf does allow one state folder to work across multiple devices, so what we can do is have WolfManager create a user state folder for each user and then point all devices for that user to the same state folder. We can manage users and their pairing through Wolf Manager. But this is all future work and could change.

# Tools
Wolf Manager was programmed in react, and I used Cursor AI to do the heavy lifting while I focused on the the design, reasearch and problem solving.
# Tools and Technology
Wolf Manager is built with React and TypeScript, featuring a modern component-based architecture. The project uses Vite for building and development. Development is assisted by Cursor AI for implementation while focusing on design, research, and problem-solving.
2 changes: 2 additions & 0 deletions config/logs/wolf-manager.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"timestamp":"2025-01-17T01:19:56.040Z","level":"info","message":"Sending config to client (sensitive data redacted)","component":"Server","data":{"libraryPath":"","usersPath":"/config/users","cachePath":"/config/cache/artwork","steamGridDbApiKey":"","debugEnabled":false,"users":{}}}
{"timestamp":"2025-01-17T01:19:56.056Z","level":"info","message":"Sending config to client (sensitive data redacted)","component":"Server","data":{"libraryPath":"","usersPath":"/config/users","cachePath":"/config/cache/artwork","steamGridDbApiKey":"","debugEnabled":false,"users":{}}}
1 change: 1 addition & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions docs/known-issues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions docs/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file modified images/dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"express": "^4.18.2",
"node-fetch": "^3.3.2",
"cors": "^2.8.5",
"archiver": "^6.0.1"
"archiver": "^6.0.1",
"lodash": "^4.17.21"
},
"devDependencies": {
"@types/react": "^18.2.0",
Expand All @@ -34,6 +35,7 @@
"@emotion/types": "^0.7.0",
"@types/react-router-dom": "^5.3.3",
"@mui/types": "^7.2.13",
"@types/node-fetch": "^2.6.11"
"@types/node-fetch": "^2.6.11",
"@types/lodash": "^4.14.202"
}
}
105 changes: 105 additions & 0 deletions server/games/GameManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import fs from 'fs/promises';
import path from 'path';
import { SteamGame } from '../../src/types/config';
import { serverLog } from '../utils/logger.js';

interface GameData {
games: Record<number, SteamGame>; // All known games indexed by appId
userGames: Record<string, number[]>; // User -> array of appIds they own
lastUpdated: Record<string, string>; // User -> ISO timestamp of last update
}

export class GameManager {
private static instance: GameManager;
private data: GameData;
private readonly gamesPath = '/config/games.json';

private constructor() {
this.data = {
games: {},
userGames: {},
lastUpdated: {}
};
}

static async getInstance(): Promise<GameManager> {
if (!GameManager.instance) {
GameManager.instance = new GameManager();
await GameManager.instance.loadGames();
}
return GameManager.instance;
}

private async loadGames(): Promise<void> {
try {
serverLog('debug', 'Loading games data...', 'GameManager');
const gamesData = await fs.readFile(this.gamesPath, 'utf-8');
this.data = JSON.parse(gamesData);
serverLog('info', 'Games data loaded successfully', 'GameManager');
} catch (error) {
// If file doesn't exist, we'll use default empty data
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
serverLog('error', 'Error loading games data', 'GameManager', error);
}
}
}

private async saveGames(): Promise<void> {
try {
serverLog('debug', 'Saving games data...', 'GameManager');
await fs.mkdir(path.dirname(this.gamesPath), { recursive: true });
await fs.writeFile(this.gamesPath, JSON.stringify(this.data, null, 2));
serverLog('info', 'Games data saved successfully', 'GameManager');
} catch (error) {
serverLog('error', 'Failed to save games data', 'GameManager', error);
throw new Error('Failed to save games data');
}
}

async updateUserGames(username: string, games: SteamGame[]): Promise<void> {
try {
serverLog('debug', 'Updating user games', 'GameManager', { username, gameCount: games.length });

// Update the global games list
games.forEach(game => {
this.data.games[game.appid] = game;
});

// Update user's game list
this.data.userGames[username] = games.map(game => game.appid);
this.data.lastUpdated[username] = new Date().toISOString();

await this.saveGames();
serverLog('info', 'User games updated successfully', 'GameManager', { username });
} catch (error) {
serverLog('error', 'Failed to update user games', 'GameManager', error);
throw error;
}
}

async getUserGames(username: string): Promise<SteamGame[]> {
const appIds = this.data.userGames[username] || [];
return appIds.map(appId => this.data.games[appId]);
}

async getAllGames(): Promise<SteamGame[]> {
return Object.values(this.data.games);
}

getLastUpdated(username: string): string | null {
return this.data.lastUpdated[username] || null;
}

async deleteUserGames(username: string): Promise<void> {
try {
serverLog('debug', 'Deleting user games', 'GameManager', { username });
delete this.data.userGames[username];
delete this.data.lastUpdated[username];
await this.saveGames();
serverLog('info', 'User games deleted successfully', 'GameManager', { username });
} catch (error) {
serverLog('error', 'Failed to delete user games', 'GameManager', error);
throw error;
}
}
}
Loading

0 comments on commit fc1caeb

Please sign in to comment.