Skip to content

Commit 193da63

Browse files
committed
fix: service usage screen
This fixes service monthly usage counts as shown in Settings.
1 parent c0b109d commit 193da63

File tree

7 files changed

+151
-22
lines changed

7 files changed

+151
-22
lines changed

src/backend/src/CoreModule.js

+3
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ const install = async ({ services, app, useapi, modapi }) => {
331331

332332
const { KernelInfoService } = require('./services/KernelInfoService');
333333
services.registerService('kernel-info', KernelInfoService);
334+
335+
const { DriverUsagePolicyService } = require('./services/drivers/DriverUsagePolicyService');
336+
services.registerService('driver-usage-policy', DriverUsagePolicyService);
334337
}
335338

336339
const install_legacy = async ({ services }) => {

src/backend/src/definitions/Driver.js

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ const { CodeUtil } = require("../codex/CodeUtil");
2525

2626
/**
2727
* Base class for all driver implementations.
28+
*
29+
* @deprecated - we use traits on services now. This class is kept for compatibility
30+
* with EntityStoreImplementation and DBKVStore which still use this.
2831
*/
2932
class Driver extends AdvancedBase {
3033
constructor (...a) {
@@ -169,6 +172,7 @@ class Driver extends AdvancedBase {
169172
'driver.interface': this.constructor.INTERFACE,
170173
'driver.implementation': this.constructor.ID,
171174
'driver.method': method,
175+
...(this.get_usage_extra ? this.get_usage_extra() : {}),
172176
};
173177
await svc_monthlyUsage.increment(actor, method_key, extra);
174178
}

src/backend/src/drivers/EntityStoreImplementation.js

+6
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ class EntityStoreImplementation extends Driver {
9090
super();
9191
this.service = service;
9292
}
93+
get_usage_extra () {
94+
return {
95+
['driver.interface']: 'puter-es',
96+
['driver.implementation']: 'puter-es:' + this.service,
97+
};
98+
}
9399
static METHODS = {
94100
create: async function ({ object, options }) {
95101
const svc_es = this.services.get(this.service);

src/backend/src/routers/drivers/usage.js

+22-15
Original file line numberDiff line numberDiff line change
@@ -62,29 +62,35 @@ module.exports = eggspress('/drivers/usage', {
6262
const app = await get_app({ id: row.app_id });
6363

6464
const identifying_fields = {
65-
service: row.extra,
65+
service: JSON.parse(row.extra),
6666
year: row.year,
6767
month: row.month,
6868
};
69+
70+
// EntityStorage identifiers weren't tracked properly. We don't realy need
71+
// to track or show them, so this isn't a huge deal, but we need to make
72+
// sure they don't populate garbage data into the usage report.
73+
if ( ! identifying_fields.service['driver.implementation'] ) {
74+
continue;
75+
}
76+
77+
const svc_driverUsage = req.services.get('driver-usage-policy');
78+
const policy = await svc_driverUsage.get_effective_policy({
79+
actor,
80+
service_name: identifying_fields.service['driver.implementation'],
81+
trait_name: identifying_fields.service['driver.interface'],
82+
});
83+
84+
// console.log(`POLICY FOR ${identifying_fields.service['driver.implementation']} ${identifying_fields.service['driver.interface']}`, policy);
85+
6986
const user_usage_key = hash_serializable_object(identifying_fields);
7087

7188
if ( ! usages.user[user_usage_key] ) {
7289
usages.user[user_usage_key] = {
7390
...identifying_fields,
91+
policy,
7492
};
75-
76-
const method_key = row.extra['driver.implementation'] +
77-
':' + row.extra['driver.method'];
78-
const sla_key = `driver:impl:${method_key}`;
79-
80-
const svc_sla = x.get('services').get('sla');
81-
const sla = await svc_sla.get(
82-
user_is_verified ? 'user_verified' : 'user_unverified',
83-
sla_key
84-
);
85-
86-
usages.user[user_usage_key].monthly_limit =
87-
sla?.monthly_limit || null;
93+
usages.user[user_usage_key].monthly_limit = policy?.['monthy-limit'] ?? null;
8894
}
8995

9096
usages.user[user_usage_key].monthly_usage =
@@ -109,7 +115,8 @@ module.exports = eggspress('/drivers/usage', {
109115

110116
const app_usage_key = hash_serializable_object(id_plus_app);
111117

112-
if ( ! app_usages[app_usage_key] ) {
118+
// DISABLED FOR NOW: need to rework this for the new policy system
119+
if ( false ) if ( ! app_usages[app_usage_key] ) {
113120
app_usages[app_usage_key] = {
114121
...identifying_fields,
115122
};

src/backend/src/services/ConfigurableCountingService.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@ class ConfigurableCountingService extends BaseService {
132132
pricing_category: JSON.stringify(pricing_category),
133133
};
134134

135+
const duplicate_update_part =
136+
`count = count + 1${
137+
custom_col_names.length > 0 ? ', ' : ''
138+
} ${
139+
custom_col_names.map((name) => `${name} = ${name} + ?`).join(', ')
140+
}`;
141+
142+
const identifying_keys = [
143+
`year`, `month`,
144+
`service_type`, `service_name`,
145+
`actor_key`,
146+
`pricing_category_hash`
147+
]
148+
135149
const sql =
136150
`INSERT INTO monthly_usage_counts (${
137151
Object.keys(required_data).join(', ')
@@ -141,11 +155,13 @@ class ConfigurableCountingService extends BaseService {
141155
`VALUES (${
142156
Object.keys(required_data).map(() => '?').join(', ')
143157
}, 1, ${custom_col_values.map(() => '?').join(', ')}) ` +
144-
`ON DUPLICATE KEY UPDATE count = count + 1${
145-
custom_col_names.length > 0 ? ', ' : ''
146-
} ${
147-
custom_col_names.map((name) => `${name} = ${name} + ?`).join(', ')
148-
}`;
158+
this.db.case({
159+
mysql: 'ON DUIPLICATE KEY UPDATE ' + duplicate_update_part,
160+
sqlite: `ON CONFLICT(${
161+
identifying_keys.map(v => `\`${v}\``).join(', ')
162+
}) DO UPDATE SET ${duplicate_update_part}`,
163+
})
164+
;
149165

150166
const value_array = [
151167
...Object.values(required_data),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const APIError = require("../../api/APIError");
2+
const { PermissionUtil } = require("../auth/PermissionService");
3+
const BaseService = require("../BaseService");
4+
5+
// DO WE HAVE enough information to get the policy for the newer drivers?
6+
// - looks like it: service:<name of service>:<name of trait>
7+
8+
class DriverUsagePolicyService extends BaseService {
9+
async get_policies_for_option_ (option) {
10+
// NOT FINAL: before implementing cascading monthly usage,
11+
// this return will be removed and the code below it will
12+
// be uncommented
13+
return option.path;
14+
/*
15+
const svc_systemData = this.services.get('system-data');
16+
const svc_su = this.services.get('su');
17+
18+
const policies = await Promise.all(option.path.map(async path_node => {
19+
const policy = await svc_su.sudo(async () => {
20+
return await svc_systemData.interpret(option.data);
21+
});
22+
return {
23+
...path_node,
24+
policy,
25+
};
26+
}));
27+
return policies;
28+
*/
29+
}
30+
31+
async select_best_option_ (options) {
32+
return options[0];
33+
}
34+
35+
// TODO: DRY: This is identical to the method of the same name in
36+
// DriverService, except after the line with a comment containing
37+
// the string "[DEVIATION]".
38+
async get_effective_policy ({ actor, service_name, trait_name }) {
39+
const svc_permission = this.services.get('permission');
40+
const reading = await svc_permission.scan(
41+
actor,
42+
PermissionUtil.join('service', service_name, 'ii', trait_name),
43+
);
44+
console.log({
45+
perm: PermissionUtil.join('service', service_name, 'ii', trait_name),
46+
reading: require('util').inspect(reading, { depth: null }),
47+
});
48+
const options = PermissionUtil.reading_to_options(reading);
49+
console.log('OPTIONS', JSON.stringify(options, undefined, ' '));
50+
if ( options.length <= 0 ) {
51+
return undefined;
52+
}
53+
const option = await this.select_best_option_(options);
54+
const policies = await this.get_policies_for_option_(option);
55+
console.log('SLA', JSON.stringify(policies, undefined, ' '));
56+
57+
// NOT FINAL: For now we apply monthly usage logic
58+
// to the first holder of the permission. Later this
59+
// will be changed so monthly usage can cascade across
60+
// multiple actors. I decided not to implement this
61+
// immediately because it's a hefty time sink and it's
62+
// going to be some time before we can offer this feature
63+
// to the end-user either way.
64+
65+
let effective_policy = null;
66+
for ( const policy of policies ) {
67+
if ( policy.holder ) {
68+
effective_policy = policy;
69+
break;
70+
}
71+
}
72+
73+
// === [DEVIATION] In DriverService, this is part of call_new_ ===
74+
const svc_systemData = this.services.get('system-data');
75+
const svc_su = this.services.get('su');
76+
effective_policy = await svc_su.sudo(async () => {
77+
return await svc_systemData.interpret(effective_policy.data);
78+
});
79+
80+
effective_policy = effective_policy.policy;
81+
82+
return effective_policy;
83+
}
84+
}
85+
86+
module.exports = {
87+
DriverUsagePolicyService,
88+
};

src/gui/src/UI/Settings/UITabUsage.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,17 @@ export default {
6262
const { monthly_limit, monthly_usage } = service;
6363
let usageDisplay = ``;
6464

65+
const first_identifier = false ||
66+
service.service['driver.implementation'] ||
67+
service.service['driver.interface'] ||
68+
'';
69+
6570
if (monthly_limit !== null) {
6671
let usage_percentage = (monthly_usage / monthly_limit * 100).toFixed(0);
6772
usage_percentage = usage_percentage > 100 ? 100 : usage_percentage; // Cap at 100%
6873
usageDisplay = `
6974
<div class="driver-usage" style="margin-bottom: 10px;">
70-
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(service.service['driver.interface'])} (${html_encode(service.service['driver.method'])}):</h3>
75+
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(first_identifier)} (${html_encode(service.service['driver.method'])}):</h3>
7176
<span style="font-size: 13px; margin-bottom: 3px;">${monthly_usage} used of ${monthly_limit}</span>
7277
<div class="usage-progbar-wrapper" style="width: 100%;">
7378
<div class="usage-progbar" style="width: ${usage_percentage}%;"><span class="usage-progbar-percent">${usage_percentage}%</span></div>
@@ -78,7 +83,7 @@ export default {
7883
else {
7984
usageDisplay = `
8085
<div class="driver-usage" style="margin-bottom: 10px;">
81-
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(service.service['driver.interface'])} (${html_encode(service.service['driver.method'])}):</h3>
86+
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(first_identifier)} (${html_encode(service.service['driver.method'])}):</h3>
8287
<span style="font-size: 13px; margin-bottom: 3px;">${i18n('usage')}: ${monthly_usage} (${i18n('unlimited')})</span>
8388
</div>
8489
`;

0 commit comments

Comments
 (0)