(string) config('projectsend.version'), 'edition' => $capabilities->edition()->value, 'capabilities' => $capabilities->enabledKeys(), 'seats' => [ 'staff' => [ 'used' => $seats->staffUsed(), // null is unlimited, and is emitted as null rather than // as 0 or as an absent key: a reader that mistook one // for the other would report an installation selling // unlimited accounts as one that may hold none. 'limit' => $seats->staffLimit(), ], 'clients' => [ 'used' => $seats->clientUsed(), 'limit' => $seats->clientLimit(), ], ], 'activity' => [ // Null means "no staff account has ever signed in here", // and is emitted rather than left out for the same reason // an unlimited seat count is: a watcher has to be able to // tell that apart from "we got no answer". Collapsing the // two is how a broken probe reads as a dormant fleet. 'last_staff_login_at' => $this->lastStaffLoginAt(), ], ]; if ($this->option('json')) { $this->line((string) json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); return self::SUCCESS; } $this->line("ProjectSend {$status['version']} ({$status['edition']})"); $this->line('Capabilities: '.(implode(', ', $status['capabilities']) ?: 'none')); $this->line('Staff seats: '.$this->seatLine($status['seats']['staff'])); $this->line('Clients: '.$this->seatLine($status['seats']['clients'])); $this->line('Last staff login: '.($status['activity']['last_staff_login_at'] ?? 'never')); return self::SUCCESS; } /** * The most recent interactive staff sign-in, or null if there has * never been one. */ private function lastStaffLoginAt(): ?string { $latest = ActivityLog::query() ->where('action', Action::Login->value) ->where('actor_type', UserType::Staff->value) ->max('created_at'); // `action` and `actor_type` carry an index each, so this narrows // on one of them rather than reading the log. return $latest === null ? null : Carbon::parse($latest)->toIso8601String(); } /** * @param array{used: int, limit: int|null} $seat */ private function seatLine(array $seat): string { return $seat['limit'] === null ? "{$seat['used']} of unlimited" : "{$seat['used']} of {$seat['limit']}"; } }