mirror of
https://github.com/HeyPuter/puter.git
synced 2026-01-07 13:40:51 -06:00
fix: service usage screen
This fixes service monthly usage counts as shown in Settings.
This commit is contained in:
@@ -331,6 +331,9 @@ const install = async ({ services, app, useapi, modapi }) => {
|
||||
|
||||
const { KernelInfoService } = require('./services/KernelInfoService');
|
||||
services.registerService('kernel-info', KernelInfoService);
|
||||
|
||||
const { DriverUsagePolicyService } = require('./services/drivers/DriverUsagePolicyService');
|
||||
services.registerService('driver-usage-policy', DriverUsagePolicyService);
|
||||
}
|
||||
|
||||
const install_legacy = async ({ services }) => {
|
||||
|
||||
@@ -25,6 +25,9 @@ const { CodeUtil } = require("../codex/CodeUtil");
|
||||
|
||||
/**
|
||||
* Base class for all driver implementations.
|
||||
*
|
||||
* @deprecated - we use traits on services now. This class is kept for compatibility
|
||||
* with EntityStoreImplementation and DBKVStore which still use this.
|
||||
*/
|
||||
class Driver extends AdvancedBase {
|
||||
constructor (...a) {
|
||||
@@ -169,6 +172,7 @@ class Driver extends AdvancedBase {
|
||||
'driver.interface': this.constructor.INTERFACE,
|
||||
'driver.implementation': this.constructor.ID,
|
||||
'driver.method': method,
|
||||
...(this.get_usage_extra ? this.get_usage_extra() : {}),
|
||||
};
|
||||
await svc_monthlyUsage.increment(actor, method_key, extra);
|
||||
}
|
||||
|
||||
@@ -90,6 +90,12 @@ class EntityStoreImplementation extends Driver {
|
||||
super();
|
||||
this.service = service;
|
||||
}
|
||||
get_usage_extra () {
|
||||
return {
|
||||
['driver.interface']: 'puter-es',
|
||||
['driver.implementation']: 'puter-es:' + this.service,
|
||||
};
|
||||
}
|
||||
static METHODS = {
|
||||
create: async function ({ object, options }) {
|
||||
const svc_es = this.services.get(this.service);
|
||||
|
||||
@@ -62,29 +62,35 @@ module.exports = eggspress('/drivers/usage', {
|
||||
const app = await get_app({ id: row.app_id });
|
||||
|
||||
const identifying_fields = {
|
||||
service: row.extra,
|
||||
service: JSON.parse(row.extra),
|
||||
year: row.year,
|
||||
month: row.month,
|
||||
};
|
||||
|
||||
// EntityStorage identifiers weren't tracked properly. We don't realy need
|
||||
// to track or show them, so this isn't a huge deal, but we need to make
|
||||
// sure they don't populate garbage data into the usage report.
|
||||
if ( ! identifying_fields.service['driver.implementation'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const svc_driverUsage = req.services.get('driver-usage-policy');
|
||||
const policy = await svc_driverUsage.get_effective_policy({
|
||||
actor,
|
||||
service_name: identifying_fields.service['driver.implementation'],
|
||||
trait_name: identifying_fields.service['driver.interface'],
|
||||
});
|
||||
|
||||
// console.log(`POLICY FOR ${identifying_fields.service['driver.implementation']} ${identifying_fields.service['driver.interface']}`, policy);
|
||||
|
||||
const user_usage_key = hash_serializable_object(identifying_fields);
|
||||
|
||||
if ( ! usages.user[user_usage_key] ) {
|
||||
usages.user[user_usage_key] = {
|
||||
...identifying_fields,
|
||||
policy,
|
||||
};
|
||||
|
||||
const method_key = row.extra['driver.implementation'] +
|
||||
':' + row.extra['driver.method'];
|
||||
const sla_key = `driver:impl:${method_key}`;
|
||||
|
||||
const svc_sla = x.get('services').get('sla');
|
||||
const sla = await svc_sla.get(
|
||||
user_is_verified ? 'user_verified' : 'user_unverified',
|
||||
sla_key
|
||||
);
|
||||
|
||||
usages.user[user_usage_key].monthly_limit =
|
||||
sla?.monthly_limit || null;
|
||||
usages.user[user_usage_key].monthly_limit = policy?.['monthy-limit'] ?? null;
|
||||
}
|
||||
|
||||
usages.user[user_usage_key].monthly_usage =
|
||||
@@ -109,7 +115,8 @@ module.exports = eggspress('/drivers/usage', {
|
||||
|
||||
const app_usage_key = hash_serializable_object(id_plus_app);
|
||||
|
||||
if ( ! app_usages[app_usage_key] ) {
|
||||
// DISABLED FOR NOW: need to rework this for the new policy system
|
||||
if ( false ) if ( ! app_usages[app_usage_key] ) {
|
||||
app_usages[app_usage_key] = {
|
||||
...identifying_fields,
|
||||
};
|
||||
|
||||
@@ -132,6 +132,20 @@ class ConfigurableCountingService extends BaseService {
|
||||
pricing_category: JSON.stringify(pricing_category),
|
||||
};
|
||||
|
||||
const duplicate_update_part =
|
||||
`count = count + 1${
|
||||
custom_col_names.length > 0 ? ', ' : ''
|
||||
} ${
|
||||
custom_col_names.map((name) => `${name} = ${name} + ?`).join(', ')
|
||||
}`;
|
||||
|
||||
const identifying_keys = [
|
||||
`year`, `month`,
|
||||
`service_type`, `service_name`,
|
||||
`actor_key`,
|
||||
`pricing_category_hash`
|
||||
]
|
||||
|
||||
const sql =
|
||||
`INSERT INTO monthly_usage_counts (${
|
||||
Object.keys(required_data).join(', ')
|
||||
@@ -141,11 +155,13 @@ class ConfigurableCountingService extends BaseService {
|
||||
`VALUES (${
|
||||
Object.keys(required_data).map(() => '?').join(', ')
|
||||
}, 1, ${custom_col_values.map(() => '?').join(', ')}) ` +
|
||||
`ON DUPLICATE KEY UPDATE count = count + 1${
|
||||
custom_col_names.length > 0 ? ', ' : ''
|
||||
} ${
|
||||
custom_col_names.map((name) => `${name} = ${name} + ?`).join(', ')
|
||||
}`;
|
||||
this.db.case({
|
||||
mysql: 'ON DUIPLICATE KEY UPDATE ' + duplicate_update_part,
|
||||
sqlite: `ON CONFLICT(${
|
||||
identifying_keys.map(v => `\`${v}\``).join(', ')
|
||||
}) DO UPDATE SET ${duplicate_update_part}`,
|
||||
})
|
||||
;
|
||||
|
||||
const value_array = [
|
||||
...Object.values(required_data),
|
||||
|
||||
88
src/backend/src/services/drivers/DriverUsagePolicyService.js
Normal file
88
src/backend/src/services/drivers/DriverUsagePolicyService.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const APIError = require("../../api/APIError");
|
||||
const { PermissionUtil } = require("../auth/PermissionService");
|
||||
const BaseService = require("../BaseService");
|
||||
|
||||
// DO WE HAVE enough information to get the policy for the newer drivers?
|
||||
// - looks like it: service:<name of service>:<name of trait>
|
||||
|
||||
class DriverUsagePolicyService extends BaseService {
|
||||
async get_policies_for_option_ (option) {
|
||||
// NOT FINAL: before implementing cascading monthly usage,
|
||||
// this return will be removed and the code below it will
|
||||
// be uncommented
|
||||
return option.path;
|
||||
/*
|
||||
const svc_systemData = this.services.get('system-data');
|
||||
const svc_su = this.services.get('su');
|
||||
|
||||
const policies = await Promise.all(option.path.map(async path_node => {
|
||||
const policy = await svc_su.sudo(async () => {
|
||||
return await svc_systemData.interpret(option.data);
|
||||
});
|
||||
return {
|
||||
...path_node,
|
||||
policy,
|
||||
};
|
||||
}));
|
||||
return policies;
|
||||
*/
|
||||
}
|
||||
|
||||
async select_best_option_ (options) {
|
||||
return options[0];
|
||||
}
|
||||
|
||||
// TODO: DRY: This is identical to the method of the same name in
|
||||
// DriverService, except after the line with a comment containing
|
||||
// the string "[DEVIATION]".
|
||||
async get_effective_policy ({ actor, service_name, trait_name }) {
|
||||
const svc_permission = this.services.get('permission');
|
||||
const reading = await svc_permission.scan(
|
||||
actor,
|
||||
PermissionUtil.join('service', service_name, 'ii', trait_name),
|
||||
);
|
||||
console.log({
|
||||
perm: PermissionUtil.join('service', service_name, 'ii', trait_name),
|
||||
reading: require('util').inspect(reading, { depth: null }),
|
||||
});
|
||||
const options = PermissionUtil.reading_to_options(reading);
|
||||
console.log('OPTIONS', JSON.stringify(options, undefined, ' '));
|
||||
if ( options.length <= 0 ) {
|
||||
return undefined;
|
||||
}
|
||||
const option = await this.select_best_option_(options);
|
||||
const policies = await this.get_policies_for_option_(option);
|
||||
console.log('SLA', JSON.stringify(policies, undefined, ' '));
|
||||
|
||||
// NOT FINAL: For now we apply monthly usage logic
|
||||
// to the first holder of the permission. Later this
|
||||
// will be changed so monthly usage can cascade across
|
||||
// multiple actors. I decided not to implement this
|
||||
// immediately because it's a hefty time sink and it's
|
||||
// going to be some time before we can offer this feature
|
||||
// to the end-user either way.
|
||||
|
||||
let effective_policy = null;
|
||||
for ( const policy of policies ) {
|
||||
if ( policy.holder ) {
|
||||
effective_policy = policy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// === [DEVIATION] In DriverService, this is part of call_new_ ===
|
||||
const svc_systemData = this.services.get('system-data');
|
||||
const svc_su = this.services.get('su');
|
||||
effective_policy = await svc_su.sudo(async () => {
|
||||
return await svc_systemData.interpret(effective_policy.data);
|
||||
});
|
||||
|
||||
effective_policy = effective_policy.policy;
|
||||
|
||||
return effective_policy;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DriverUsagePolicyService,
|
||||
};
|
||||
@@ -62,12 +62,17 @@ export default {
|
||||
const { monthly_limit, monthly_usage } = service;
|
||||
let usageDisplay = ``;
|
||||
|
||||
const first_identifier = false ||
|
||||
service.service['driver.implementation'] ||
|
||||
service.service['driver.interface'] ||
|
||||
'';
|
||||
|
||||
if (monthly_limit !== null) {
|
||||
let usage_percentage = (monthly_usage / monthly_limit * 100).toFixed(0);
|
||||
usage_percentage = usage_percentage > 100 ? 100 : usage_percentage; // Cap at 100%
|
||||
usageDisplay = `
|
||||
<div class="driver-usage" style="margin-bottom: 10px;">
|
||||
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(service.service['driver.interface'])} (${html_encode(service.service['driver.method'])}):</h3>
|
||||
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(first_identifier)} (${html_encode(service.service['driver.method'])}):</h3>
|
||||
<span style="font-size: 13px; margin-bottom: 3px;">${monthly_usage} used of ${monthly_limit}</span>
|
||||
<div class="usage-progbar-wrapper" style="width: 100%;">
|
||||
<div class="usage-progbar" style="width: ${usage_percentage}%;"><span class="usage-progbar-percent">${usage_percentage}%</span></div>
|
||||
@@ -78,7 +83,7 @@ export default {
|
||||
else {
|
||||
usageDisplay = `
|
||||
<div class="driver-usage" style="margin-bottom: 10px;">
|
||||
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(service.service['driver.interface'])} (${html_encode(service.service['driver.method'])}):</h3>
|
||||
<h3 style="margin-bottom: 5px; font-size: 14px;">${html_encode(first_identifier)} (${html_encode(service.service['driver.method'])}):</h3>
|
||||
<span style="font-size: 13px; margin-bottom: 3px;">${i18n('usage')}: ${monthly_usage} (${i18n('unlimited')})</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user