Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This repository now includes a responsive Cockpit UI built with React. The inte
## ✅ Features

- **Monthly billing summaries** displayed right in Cockpit’s navigation menu.
- **Selectable historical months** lets you view past billing periods for the current year while defaulting to the current month.
- **Invoice dashboards** to view, download, and archive invoice PDFs.
- **Detailed cost drill-downs** (core‑hours, instance‑hours, GB‑month) for per‑account transparency.
- **Historical billing data** accessible from account inception for auditing and trend analysis.
Expand Down
54 changes: 47 additions & 7 deletions src/slurmcostmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,37 @@ const PLUGIN_BASE =
window.cockpit.manifest.path) ||
'/usr/share/cockpit/slurmcostmanager';

function getBillingPeriod(now = new Date()) {
const end = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
const start = new Date(Date.UTC(now.getFullYear(), now.getMonth(), 1));
function getBillingPeriod(ref = new Date()) {
const today = new Date();
let year, month, end;
if (typeof ref === 'string') {
[year, month] = ref.split('-').map(Number);
month -= 1;
const isCurrent = year === today.getFullYear() && month === today.getMonth();
end = isCurrent
? new Date(Date.UTC(today.getFullYear(), today.getMonth(), today.getDate()))
: new Date(Date.UTC(year, month + 1, 0));
} else {
year = ref.getFullYear();
month = ref.getMonth();
end = new Date(Date.UTC(year, month, ref.getDate()));
}
const start = new Date(Date.UTC(year, month, 1));
return {
start: start.toISOString().slice(0, 10),
end: end.toISOString().slice(0, 10)
};
}

function useBillingData() {
function useBillingData(month) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);

const load = useCallback(async () => {
try {
let json;
if (window.cockpit && window.cockpit.spawn) {
const { start, end } = getBillingPeriod();
const { start, end } = getBillingPeriod(month);
const args = [
'python3',
`${PLUGIN_BASE}/slurmdb.py`,
Expand All @@ -53,7 +66,7 @@ function useBillingData() {
console.error(e);
setError(e.message || String(e));
}
}, []);
}, [month]);

useEffect(() => {
load();
Expand Down Expand Up @@ -1049,8 +1062,18 @@ function Rates({ onRatesUpdated }) {

function App() {
const [view, setView] = useState('summary');
const { data, error, reload } = useBillingData();
const now = new Date();
const defaultMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(
2,
'0'
)}`;
const [month, setMonth] = useState(defaultMonth);
const { data, error, reload } = useBillingData(month);
const [showErrorDetails, setShowErrorDetails] = useState(false);
const monthOptions = Array.from(
{ length: now.getMonth() + 1 },
(_, i) => `${now.getFullYear()}-${String(i + 1).padStart(2, '0')}`
);

return React.createElement(
'div',
Expand All @@ -1074,6 +1097,23 @@ function App() {
'Settings'
)
),
view !== 'settings' &&
React.createElement(
'div',
{ className: 'month-select' },
React.createElement(
'label',
null,
'Month: ',
React.createElement(
'select',
{ value: month, onChange: e => setMonth(e.target.value) },
monthOptions.map(m =>
React.createElement('option', { key: m, value: m }, m)
)
)
)
),
view !== 'settings' && !data && !error && React.createElement('p', null, 'Loading...'),
view !== 'settings' &&
error &&
Expand Down
Loading