Compare commits

...
2 Commits
Author SHA1 Message Date
smishchuk a8bfd3de9e 013 s3 cold prices added 2025-10-20 19:52:59 +03:00
smishchuk 46169b449e 012 2025-10-14 15:19:23 +03:00
8 changed files with 230 additions and 527 deletions
+7
View File
@@ -0,0 +1,7 @@
t test
<cftry>
<cfdump var=#session#/>
<cfcatch type="any"><cfdump var=#cfcatch#/></cfcatch>
</cftry/>
<cfset jwt = new lib.jwt(this.client_secret)/>
+36 -2
View File
@@ -77,7 +77,7 @@
<!--- global settings --->
<cfset request.RECORDS_PER_PAGE=500/>
<cfset request.APP_VERSION="0.00.012"/>
<cfset request.APP_VERSION="0.00.013"/>
<cfset request.STAND=getStand()/>
<!--- application constants --->
@@ -157,8 +157,42 @@
<cfcookie name="CFID" value="#session.CFID#"/>
<cfcookie name="CFTOKEN" value="#session.CFTOKEN#"/>
<!--- SSO --->
<!--- Если без сессии. Проверяем наличие токена в заголовке (и в куках?)
Если его нет, идем к IDP за токеном. Получив токен, складываем его в куки
Если токен есть, распаковываем его. Проверяем протухание. Если прошла установленная часть времени жизни, идем за рефрешем (внимание: это сработает только в браузере,
если клиент не умеет редиректы - то токен должен быть достаточно свежий)
(для начала делаем без рефреша, только проверяем протухание)
А рефреш можно сделать на серверной стороне?
Кладем данные из токена в реквест и наслаждаемся --->
<!--- <cfscript>
var requestData = GetHttpRequestData();
var headers = requestData.headers;
writedump(headers);
if (structKeyExists(headers,"authorization")) {
var authorizationHeader = headers.authorization;
if (authorizationHeader.startsWith("Bearer ")) {
var bearerToken = Mid(authorizationHeader, 8);
writeDump(bearerToken);
var token=toString(binaryDecode(bearerToken, "base64"));
writeDump(token);
jwt = new lib.jwt(this.client_secret);
lock scope="application" type="readonly" timeout="1" {
var token_data = (jwt.decode(token, application.idpCertificate,"RS256"));
}
writeDump(token_data);
abort;
}
} else {
writeoutput("No Authorization header present");//abort;
}
</cfscript> --->
<cflock scope="session" type="exclusive" timeout="3">
<!---
<cfdump var=#session#/>
@@ -334,7 +368,7 @@
</cffunction>
<cffunction
<cffunction
name="getDS"
access="private"
returntype="void"
-422
View File
@@ -1,422 +0,0 @@
<cfcomponent
displayname="Application"
output="true"
hint="Handle the application.">
<!--- Pseudoconstructor --->
<!--- Set up the application. --->
<cfset this.Name = "PAYG-REPORT" />
<cfset this.applicationTimeout = createTimeSpan( 0, 3, 0, 0 ) />
<cfset this.sessionmanagement="Yes"/>
<cfset this.clientmanagement="No"/>
<cfset this.sessiontimeout=CreateTimeSpan(0, 1, 0, 0)/>
<cfset this.setclientcookies="No"/>
<cfset this.mappings = structNew() />
<cfset this.mappings["/mod"] = getDirectoryFromPath(getCurrentTemplatePath()) & "mod/" />
<cfset this.customTagPaths = expandPath(getDirectoryFromPath(getCurrentTemplatePath()) & "mod")/>
<!--- SSO --->
<cfset this.client_id = "payg-report.deck.nubes.ru"/>
<cfset this.auth_endpoint = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/auth" />
<cfset this.access_token_endpoint = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token" />
<cfset this.idpCertUrl = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs" />
<cfset this.client_secret = createObject("java", "java.lang.System").getEnv("IDP_CLIENT_SECRET")/>
<cfset this.datasource = "rpt"/>
<cfset this.defaultdatasource = this.datasource/>
<cfset request.DS = "#this.datasource#">
<cfset request.language="">
<cfset getDS(this.datasource)/>
<!--- кажется, нужно инициализировать датасорцы в псевдоконструкторе - onRequest не получается --->
<!--- *** проверить, что есть - если есть, не создавать --->
<!--- это работает с административно заданным датасорцем, хотя, казалось бы, не должно
(без окружения функция вернет пустую структуру). Видимо, административный датасорц обнаруживается раньше.
Проверять на пустоту не хочется, чтобы не плодить в псевдоконструкторе переменные --->
<!--- <cfdump var=#variables#/> --->
<cffunction
name="OnApplicationStart"
access="public"
returntype="boolean"
output="false"
hint="Fires when the application is first created.">
<!--- *** мы не проверяем срок жизни сертификата и его отзыв--->
<!--- Obtain idp certificate OnApplicationStart --->
<cfset application.idpCertificate = getIdpCertificate(this.idpCertUrl)/>
<cfreturn true />
</cffunction>
<cffunction
name="OnRequest"
access="public"
returntype="void"
output="true"
hint="Fires after pre page processing is complete.">
<cfargument name="template" type="string" required="true"/>
<cfset request.startTickCount=getTickCount()/>
<cfset setEncoding("FORM", "UTF-8")>
<cfset setEncoding("URL", "UTF-8")>
<cfset request.PERMISSION_NONE=0/>
<cfset request.PERMISSION_READ=1/>
<cfset request.PERMISSION_WRITE=2/>
<!--- <cfset request.UNDEFINED_USR_ID=-1/>
<cfset request.ANONYMOUS_USR_ID=2/>
<cfset request.GUEST_USR_ID=3/> --->
<!--- global settings --->
<cfset request.RECORDS_PER_PAGE=500/>
<cfset request.APP_VERSION="0.00.011"/>
<cfset request.STAND=getStand()/>
<!--- application constants --->
<!---<cfset request.SMTP_SERVER="172.16.16.16"/>
<cfset request.MAIL_FROM_FOR_NOTIFICATION="Pipeline tracker <pipeline@mail.ru>"/>
<cfset request.SUBJECT_PREFIX="[PIPELINE TRACKER] "/>--->
<!---<cfset request.BASE_URL="https://smtest.dtln.local/pipeline/"/>*** сделать нормально. Внимание! При наличии прокси CGI.SERVER_NAME может быть заменено --->
<cfset local = {} />
<cfset local.basePath = getDirectoryFromPath(
getCurrentTemplatePath()
) />
<cfset local.targetPath = getDirectoryFromPath(
expandPath( arguments.template )
) />
<cfset local.requestDepth = (
listLen( local.targetPath, "\/" ) -
listLen( local.basePath, "\/" )
) />
<cfset request.webRoot = repeatString(
"../",
local.requestDepth
) />
<!---
While we wouldn't normally do this for every page
request (it would normally be cached in the
application initialization), I'm going to calculate
the site URL based on the web root.
--->
<cfset request.siteUrl = (
IIF(
(CGI.server_port_secure <!--- CGI.https EQ "On" does not work with apache+tomcat and nginx+tomcat --->),
DE( "https://" ),
DE( "http://" )
) &
cgi.http_host &
reReplace(
getDirectoryFromPath( arguments.template ), "([^\\/]+[\\/]){#local.requestDepth#}$",
"",
"one"
)
) />
<cfset request.thisPage=Replace(ReplaceNoCase(expandPath(ARGUMENTS.template), local.basePath, ""), "\", "/")/>
<!--- <cfset request.thisUrl="#request.thisPage##len(CGI.queryString)#"/> чревато циклом --->
<!--- получение сертификата может занимать длительное время.
Блокировать на это время приложение или сессию не следует.
Вообще не следует ничего блокировать на время сетевого взаимодействия
Нижеследующая, на первый взгляд, странная конструкция ровно для этого придумана
--->
<!--- <cfoutput>onRequest:check for cert started #(getTickCount()-request.startTickCount)#</cfoutput> --->
<cfset var idpCertificateExists = false/>
<cflock scope="application" type="readonly" timeout=1>
<cfset request.APP_NAME=this.Name/>
<cfset idpCertificateExists = structKeyExists(application,"idpCertificate")/>
</cflock>
<cfif NOT idpCertificateExists>
<!--- Obtain IDP Certificate (onRequest) --->
<cfset var idpCertificate = getIdpCertificate(this.idpCertUrl) />
<cflock scope="application" type="exclusive" timeout=1>
<cfset application.idpCertificate = idpCertificate/>
</cflock>
</cfif>
<!--- <cfoutput>onRequest:check for cert finished #(getTickCount()-request.startTickCount)#</cfoutput> --->
<cfset this.redirect_uri = "https://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#" />
<cfinclude template="inc/functions.cfm"/>
<cfcookie name="CFID" value="#session.CFID#"/>
<cfcookie name="CFTOKEN" value="#session.CFTOKEN#"/>
<!--- SSO --->
<cflock scope="session" type="exclusive" timeout="3">
<!---
<cfdump var=#session#/>
<cfoutput>#(getTickCount()-request.startTickCount)#</cfoutput>--->
<!---
Это первая проба, с сессией
Когда протухнет сессия, мы можем пойти к IDP и повторно аутентифицироваться. Поскольку у нас сохраняются куки IDP, его сессия, возможно, будет жива. Если мы обновляли сессию IDP в другом приложении SSO, по всей видимости, она будет продолжаться. Если мы видим, что сессия IDP состарилась и хочет протухнуть, мы можем ее освежить и получить новый access token (который мы пока не используем - мы пользуемся ответом IDP) Наверное, правильно обходиться для этих целей вообще без сессии *** --->
<!---
проверить sso
? сделать рефреш
? сделать логаут
? сделать отображение ошибки IDP
+ сделать получение сертификата
--->
<cfif structKeyExists(session, "auth")>
<cfset request.auth = structCopy(session.auth)/>
<!--- <cfset request.auth.wz = "WZ01553"/> --->
<!--- тут нужно сделать проверку и рефреш токена --->
<!--- сессия есть --->
<cfelse>
<!--- сессии нет --->
<cfset session.auth_state=createGUID().toString()/>
<cfscript>
jwt = new lib.jwt(this.client_secret);
idp = new lib.oauth2(this.client_id, this.client_secret, this.auth_endpoint, this.access_token_endpoint, this.redirect_uri);
/*
часто появляется ошибка: после неудачного запроса к IDP
в адресной строке браузера остается все, с чем его редиректил IDP,
а повторный запрос с этим контентом уже не работает. Получаем 400 Bad Request -
может быть, KeyCloak так защищается от циклических редиректов
Не очень понятно, как предотвратить повторный запрос (можно, конечно, использовать сессию, но странно)
*/
//writeOutput(getTickCount()-request.startTickCount);
if(structKeyExists(url, "code")) { //обрабатываем редирект от IDP *** наличие поля code в URL введет нас в заблуждение, но эvar = data, label =то только без валидной сессии
//dump(var = url.code, label = "URL.code");
// Request access token from idp with the
// authorization code that we got via the URL
//aFormFields = [];
//data = idp.makeAccessTokenRequest((url.code).content, aFormFields);
try {
writeOutput("idp.makeAccessTokenRequest");
writeOutput(getTickCount()-request.startTickCount);
resp = idp.makeAccessTokenRequest(url.code/*, aFormFields*/);
writeOutput("idp.makeAccessTokenRequest Done");
writeOutput(getTickCount()-request.startTickCount);
//dump(resp);
data = deserializeJson(resp.content);
//writedump(data);
// Print full response from idp
//dump(var = data, label = "makeAccessTokenRequest - Response from idp");
//echo('<a href="./index.cfm?refresh=#data.refresh_token#">Refresh token #data.refresh_token#</a>');
session.auth.expires_in = data.expires_in;
session.auth.refresh_expires_in = data.refresh_expires_in;
session.auth.access_token = data.access_token;
session.auth.refresh_token = data.refresh_token;
session.auth.id_token = data.id_token;
session.auth.token_type = data.token_type;
session.idp_response_content = structCopy(data);
//flush()
lock scope="application" type="readonly" timeout="1" {
token_data = (jwt.decode(data.access_token, application.idpCertificate,"RS256"));
}
writeOutput("jwt.decode Done");
writeOutput(getTickCount()-request.startTickCount);
writedump(token_data);
session.auth.login = token_data.preferred_username;
session.auth.wz = token_data.ClientID;
session.auth.fullname = token_data.name;
session.auth.groups = token_data.groups;
session.auth.session_state = token_data.session_state;
session.auth.sid = token_data.sid;
session.auth.auth_time = token_data.auth_time;
session.auth.exp = token_data.exp;
session.auth.iat = token_data.iat;
//session.sid = token_data.sid;
session.token_data = structCopy(token_data);
location(this.redirect_uri,false);
} catch (e) {echo('<br>****************** #e.message# : #e.detail# **********');}
} else {
strURL = idp.buildRedirectToAuthURL({"scope":'openid profile email',"state":session.auth_state,"allow_signup":false});
echo('<a href="#strURL#">self-made Auth link (idp) #strURL#</a> <br> <br>');
location(strUrl,false);
}
</cfscript>
</cfif>
</cflock><!--- session exclusive --->
<cfif structKeyExists(request, "auth")>
<cfinclude template="#ARGUMENTS.template#"/>
<cfelse>
<cfset currentDir = (
lCase(
replace(
getDirectoryFromPath(
replaceNoCase(
expandPath(
ARGUMENTS.template
),
getDirectoryFromPath(
getCurrentTemplatePath()
),
""
)
), "\", "/", "ALL"
)
)
)/>
<!--- <cfif currentDir EQ "saml/"><!--- note traling slash --->
<cfinclude template="#ARGUMENTS.template#"/>
<cfelse>
<cfinclude template="login.cfm" />
</cfif> --->
</cfif>
<cfreturn />
</cffunction>
<cffunction
name="OnRequestEnd"
access="public"
returntype="void"
output="true"
hint="Fires after the page processing is complete.">
<!--- Attention! Before CF9, OnrequestEnd is not executed in case of redirect. That is why we use session.save_login --->
<cfif structKeyExists(session, "save_login")>
<cfif session.save_login EQ "">
<!--- unset (expire) cookie --->
<cfcookie expires="-1" name="portalUser" value="">
<cfelse>
<!--- set persistent cookie with no expiration --->
<cfcookie expires="NEVER" name="portalUser" value="#encrypt(session.save_login,COOKIEENCKEY)#">
</cfif>
<cfset structDelete(session, "save_login")>
</cfif>
<cfreturn />
</cffunction>
<cffunction
name="getStand"
access="private"
returntype="string"
output="true"><!--- *** duplicated in svc-api --->
<cftry>
<cfquery name=qConfig>
select value as stand from config
where name='STAND'
</cfquery>
<cfreturn qConfig.stand/>
<cfcatch type="ANY">
<!--- do nothing, default will be returned --->
</cfcatch>
</cftry>
<cfreturn ""/>
</cffunction>
<cffunction
name="getDS"
access="private"
returntype="void"
output="true"
hint="Configure data source from environment variables (if datasource with the name provided already exists, does nothing). Convention: data source name is an environment varialble prefix">
<cfargument name="dsname" type="string" required="true"/>
<cfargument name="prefix" type="string" default=#dsname#/>
<cftry>
<cfquery name="qTestDs" datasource=#arguments.dsname#>
select 1;
</cfquery>
<cfcatch type="any">
<cfset var ds={}/>
<cfset var system = createObject("java", "java.lang.System")/>
<cfloop list="class,connectionString,database,driver,dbdriver,host,port,type,url,username,password,bundleName,bundleVersion,connectionLimit,liveTimeout,validate" item="field"><!--- driver vs dbdriver --->
<cfset var value=system.getEnv("#arguments.prefix#_#field#")/>
<cfif isDefined("value") AND len(value)>
<cfset structInsert(ds,field,value)/>
</cfif>
</cfloop>
<cfif structIsEmpty(ds)>
<cfthrow type="application" message="Datasource not configured" detail="Datasource not defined in the environment. Expected prefix is #arguments.prefix#"/>
</cfif>
<!--- test datasource (just to get exception if invalid) --->
<cftry>
<cfquery name="qTestDsNextTry" datasource=#ds#>
select 2;
</cfquery>
<cfcatch type="any">
<!--- <cfdump var=#ds#/><cfabort/> --->
<cfdump var=#arguments#/>
<cfdump var=#ds#/>
<cfrethrow/>
</cfcatch>
</cftry>
<cfset this.datasources["#arguments.dsname#"]=#ds#/> <!--- Интересно, доступен ли здесь this --->
</cfcatch>
</cftry>
<!--- <cfdump var=#ds#/> --->
<cfreturn/>
</cffunction>
<cfscript>
//https://keycloak.nubes.ru/admin
//https://keycloak.nubes.ru/realms/SSH_CA/account //smishchuk@mgmt.nubes.ru
//https://stackoverflow.com/questions/28658735/what-are-keycloaks-oauth2-openid-connect-endpoints
//http://https://keycloak.nubes.ru/realms/SSH_CA/.well-known/openid-configuration
//https://keycloak.nubes.ru/realms/cloud/.well-known/openid-configuration тут ссылка
//https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs тут серт
//только надо его обрамить -----BEGIN CERTIFICATE-----
function getIdpCertificate(certUrl) {
var httpService = new http();
httpService.setMethod( "get" );
httpService.setCharset( "utf-8" );
httpService.setUrl(arguments.certUrl);
var result = httpService.send().getPrefix();
var status_code = result.ResponseHeader['Status_Code'];
if ('200' == status_code) {
var content = deserializeJson(result.FileContent);
//мы подразумеваем структуру конкретно KeyCloak
var key = "";
for (key in content.keys) {
if ("RSA" == key.kty AND "sig" == key.use) {
return "-----BEGIN CERTIFICATE-----" & key.x5c[1] & "-----END CERTIFICATE-----";
}
}
throw(message="RSA cetificate not found");
} else {
throw (message="Cannot obtain IDP certificate, request failed", detail="Status_Code #status_code#");
}
}
</cfscript>
</cfcomponent>
+3
View File
@@ -16,6 +16,7 @@
<cfif isDefined("output_xls")><cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
/><layout:xml qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.xml"/>
@@ -23,6 +24,7 @@
</cfif><cfif isDefined("output_json")><cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
/><!--- <cfdump var=#report#/><cfabort/> ---><layout:json qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.json"/>
@@ -57,6 +59,7 @@
<cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
/>
+4 -4
View File
@@ -61,13 +61,13 @@
</cffunction>
<cfmodule template="mod/payg.cfm"
<!--- <cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
output="report"
debug=#isDefined("DEBUG")#
/>
<cfset out={
/> --->
<!--- <cfset out={
"hours"=#hours#,
"dtStart="=#iso8601dtFormat(dt_start)#,
"dtFinish"=#iso8601dtFormat(dt_finish)#,
@@ -81,7 +81,7 @@
result.append(row);
return result;
}, [])#
}/>
}/> ---><cfset out={"test"=true}/>
<cfcontent
type="application/json"
-90
View File
@@ -1,90 +0,0 @@
<cfset msStartAt=getTickCount()/>
<cfparam name="dt_finish" type="date" default=#createDateTime(year(Now()),month(Now()),1,0,0,0)#/>
<cfparam name="dt_start" type="date" default=#dateAdd('m',-1,dt_finish)#/>
<cfset hours=dateDiff('h',dt_start,dt_finish)/>
<cfset tenant_wz_index=val(mid(request.auth.wz,3,5))/><!--- cut off WZ --->
<cffunction name="snake2camel"
returntype="string"
output="false"
hint="convert snake style name to camel style name">
<cfargument name="snake" type="string" required="true" />
<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>
</cffunction>
<cffunction name="camel2snake"
returntype="string"
output="false"
hint="convert camel style name to snake style name">
<cfargument name="camel" type="string" required="true" />
<cfreturn #reReplace(ARGUMENTS.camel,"([A-Z])","_\l\1","ALL")#/>
</cffunction>
<cffunction name="iso8601dtFormat"
returntype="string"
output="false"
hint="formates date and time like this: 2023-12-18T15:41:42.516+0300">
<cfargument name="input" required="true" />
<cfreturn #dateFormat( input, "yyyy-mm-dd" )#T#timeFormat( input, "HH:mm:ss.lXX" )#/>
</cffunction>
<cffunction name="query2json"
output="false"
hint="print query in json format as array of structures">
<cfargument name="qry" type="query" required="true"/><!--- column names should not contain commas --->
<cfargument name="convertSnakeToCamel" type="boolean" default=true/>
<cfset var columnsIn=#arguments.qry.columnList()#/>
<cfset var columnsOut=""/>
<cfif arguments.convertSnakeToCamel>
<cfloop list=#columnsIn# item="col">
<cfset columnsOut=listAppend(columnsOut,snake2camel(col))/>
</cfloop>
<cfset var outArray=arrayNew(1)/>
//copy fields to structure, renaming fields, this preserves data type
<cfloop query=#arguments.qry#>
<cfset var structRow=structNew()/>
<cfloop index="i" from="1" to=#listLen(columnsOut)#>
<cfset structInsert(structRow,"#listGetAt(#columnsOut#,i)#",arguments.qry["#listGetAt(#columnsIn#,i)#"])/>
</cfloop>
<cfset arrayAppend(outArray,structRow)/>
</cfloop>
<cfreturn serializeJSON(outArray)/>
<cfelse>
<cfreturn serializeJSON(arguments.qry,"struct")/>
</cfif>
</cffunction>
<cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
output="report"
debug=#isDefined("DEBUG")#
/>
<cfset out={
"hours"=#hours#,
"dtStart="=#dt_start#,
"dtFinish"=#dt_finish#,
"vcdComputingLoadedAt"="#iso8601dtFormat(report.qComputingAge.dt_load)#",
"vcdStorageLoadedAt"="#iso8601dtFormat(report.qStorageAge.dt_load)#",
"s3StorageLoadedAt"="#iso8601dtFormat(report.qComputingAge.dt_load)#",
"s3OpsTrfLoadedAt"="#iso8601dtFormat(report.qS3OpsTrfAge.dt_load)#",
"records"=#report.qCharge.recordCount#,
"queryDurationMs"=#(getTickCount() - msStartAt)#,
"report"=#report.qCharge.reduce((result, row) => {
result.append(row);
return result;
}, [])#
}/>
<cfcontent
type="application/json"
/><cfoutput>#serializeJSON(out)#</cfoutput>
+21 -9
View File
@@ -5,9 +5,10 @@
<cfparam name="ATTRIBUTES.dt_start" type="date" default=#dateAdd('m',-1,ATTRIBUTES.dt_finish)#/>
<cfparam name="ATTRIBUTES.output" type="string" default="report"/>
<cfparam name="ATTRIBUTES.debug" type="boolean" default=false/>
<cfparam name="ATTRIBUTES.tenant_wz_index" type="string"/>
<cfset hours=dateDiff('h', ATTRIBUTES.dt_start, ATTRIBUTES.dt_finish)/>
<cfset tenant_wz_index=val(mid(request.auth.wz,3,5))/><!--- cut off WZ --->
<!--- QoQ does not support join syntax, at least of Lucee 5.4--->
@@ -17,10 +18,15 @@
<cfquery name="qSpec">
select * from (
values
('Объектное хранилище S3 CEPH','Дисковое пространство: Объем хранения','paas.s3.ceph.vol-m','ГБайт',1.80,0)
,('Объектное хранилище S3 CEPH','Исходящий трафик','paas.s3.ceph.trf-m','ГБайт',1.50,0)
,('Объектное хранилище S3 CEPH','Запросы GET, HEAD, OPTIONS','paas.s3.ceph.get-m','10тыс.шт.',0.35,0)
,('Объектное хранилище S3 CEPH','Запросы PUT, POST, PATCH','paas.s3.ceph.put-m','10тыс.шт.',4.50,0)
('Объектное хранилище S3 CEPH','Дисковое пространство: Объем хранения (HOT)','paas.s3.ceph.vol-m','ГБайт',1.80,0)
,('Объектное хранилище S3 CEPH','Исходящий трафик (HOT)','paas.s3.ceph.trf-m','ГБайт',1.50,0)
,('Объектное хранилище S3 CEPH','Запросы GET, HEAD, OPTIONS (HOT)','paas.s3.ceph.get-m','10тыс.шт.',0.35,0)
,('Объектное хранилище S3 CEPH','Запросы PUT, POST, PATCH (HOT)','paas.s3.ceph.put-m','10тыс.шт.',4.50,0)
,('Объектное хранилище S3 CEPH','Дисковое пространство: Объем хранения (COLD)','paas.s3.cphc.vol-m','ГБайт',0.84*1.2,0)
,('Объектное хранилище S3 CEPH','Исходящий трафик (COLD)','paas.s3.cphc.trf-m','ГБайт',1.25*1.2,0)
,('Объектное хранилище S3 CEPH','Запросы GET, HEAD, OPTIONS (COLD)','paas.s3.cphc.get-m','10тыс.шт.',0.8*1.2,0)
,('Объектное хранилище S3 CEPH','Запросы PUT, POST, PATCH (COLD)','paas.s3.cphc.put-m','10тыс.шт.',9.7*1.2,0)
) as
s (svc, component, code, unit, price, discount)
order by code
@@ -41,7 +47,7 @@
from ngcloud_ru.capacity_resource
join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
where tenant.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#tenant_wz_index#/>
where tenant.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#ATTRIBUTES.tenant_wz_index#/>
AND capacity_resource.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_start#/>
AND capacity_resource.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_finish#/>
group by tenant.wzcode, vdc.name
@@ -80,7 +86,7 @@
join ngcloud_ru.vdc on capacity_storage.vdc_id=vdc.id
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
join ngcloud_ru.storage_profile_types on capacity_storage.storage_profile_types_id=storage_profile_types.id
where tenant.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#tenant_wz_index#/>
where tenant.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#ATTRIBUTES.tenant_wz_index#/>
AND capacity_storage.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_start#/>
AND capacity_storage.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_finish#/>
group by tenant.wzcode, vdc.name, storage_profile_types.name
@@ -118,7 +124,7 @@ HOT_FREE_LIMIT 1 10 1 100
join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id
join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id
where bucket_stat.placement_id in (1,2)
AND bucket_stat.owner = <cfqueryparam cfsqltype="cf_sql_varchar" value=#tenant_wz_index#/>
AND bucket_stat.owner = <cfqueryparam cfsqltype="cf_sql_varchar" value=#ATTRIBUTES.tenant_wz_index#/>
AND bucket_stat.timestamp_addition >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_start#/>
AND bucket_stat.timestamp_addition < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_finish#/>
group by bucket_stat.owner, bucket_stat.placement_id
@@ -203,7 +209,7 @@ select
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
join s3billing.placement on bucket_info.placement_id=placement.id
where 1=1
AND user_info.name = <cfqueryparam cfsqltype="cf_sql_varchar" value=#tenant_wz_index#/>
AND user_info.name = <cfqueryparam cfsqltype="cf_sql_varchar" value=#ATTRIBUTES.tenant_wz_index#/>
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_start#/>
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#ATTRIBUTES.dt_finish#/>
group by user_info.name, bucket_info.placement_id
@@ -245,6 +251,7 @@ select wz, code || '.trf-m', bytes_sent as raw_metric, bytes_sent_gb as metric,
SELECT
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount">
<d:field title="Артикул">s.code</d:field>
<d:field title="Артикул">m.code as m_code</d:field>
<d:field title="Услуга">s.svc</d:field>
<d:field title="Компонент">s.component</d:field>
<d:field title="Кол-во (метрика)" cfSqlType="CF_SQL_NUMERIC">m.chargeable_metric</d:field>
@@ -266,6 +273,11 @@ WHERE m.wz=<cfqueryparam cfsqltype="cf_sql_varchar" value=#request.auth.wz#/> AN
order by s.code
</cfquery>
<cfif (ATTRIBUTES.debug)>
<cfdump var=#qCharge#/>
<cfflush/>
</cfif>
<cfset var report={}/>
<cfset report.qComputingAge = qComputingAge/>
+159
View File
@@ -0,0 +1,159 @@
<cfsilent>
<cfimport prefix="m" taglib="lib"/>
<cfimport prefix="c" taglib="lib/controls"/>
<cfimport prefix="d" taglib="lib/data"/>
<cfimport prefix="layout" taglib="layout"/>
</cfsilent>
<m:prepare_ls entity="payg" accessObject="" pageInfoOut="pageInfo" trackOut="tr"/>
<cfparam name="dt_finish" type="date" default=#createDateTime(year(Now()),month(Now()),1,0,0,0)#/>
<cfparam name="dt_start" type="date" default=#dateAdd('m',-1,dt_finish)#/>
<cfset hours=dateDiff('h',dt_start,dt_finish)/>
<cfset tenant_wz_index=val(mid(request.auth.wz,3,5))/><!--- cut off WZ --->
<cfset tenant_wz_index="00169"/><!--- cut off WZ --->
<cfif isDefined("output_xls")><cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
/><layout:xml qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.xml"/>
<cfabort/>
</cfif><cfif isDefined("output_json")><cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
/><!--- <cfdump var=#report#/><cfabort/> ---><layout:json qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.json"/>
<cfabort/>
</cfif><!---
---><layout:page section="header" pageInfo=#pageInfo#>
<layout:attribute name="title">
<cfoutput><b>Отчет по PAYG</b> #request.auth.wz#</cfoutput>
</layout:attribute>
<layout:attribute name="controls">
<!---skip filter link, filter is not implemented--->
<!---<layout:language_switch/>--->
</layout:attribute>
</layout:page>
<!--- Внимание! округление вверх при расчете метрик get, put S3. Расчетные суммы могут содержать погрешности округления, поэтому окончательный расчет стоимости (количество * цену) должен выполняться в бухгалтерской программе.
<b>Если у клиента больше 1 действующего допника с услугами PAYG, то данные в отчете могут быть замножены, в этом случае считать вручную</b> --->
<cfoutput>
<form method="post" action="">
Период с <input type="text" name="dt_start" value="#dateFormat(dt_start,'YYYY-MM-DD')#"/>
по <input type="text" name="dt_finish" value="#dateFormat(dt_finish,'YYYY-MM-DD')#"/>
<input type="submit" style="cursor:pointer;"/>
<!--- <input type="submit" name="DEBUG" value="DEBUG" style="cursor:pointer;"/> --->
</form>
<cfflush/>
<cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
/>
Часов в периоде: <b>#hours#</b> Строк в отчете: <b>#report.qCharge.recordCount#</b><br/>
Актуальность данных:
VCD Computing: <b>#dateFormat(report.qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qComputingAge.dt_load,'HH:MM:SS')#</b>
VCD Storage: <b>#dateFormat(report.qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qStorageAge.dt_load,'HH:MM:SS')#</b>
S3 хранение: <b>#dateFormat(report.qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3VolAge.dt_load,'HH:MM:SS')#</b>
S3 операции и трафик: <b>#dateFormat(report.qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3OpsTrfAge.dt_load,'HH:MM:SS')#</b>
<br/>
<a href="#request.thisPage#?output_xls" title="экспорт в Excel" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/xls.gif" style="vertical-align:text-bottom;"/></a>
<a href="#request.thisPage#?output_json" title="экспорт в json" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/json.svg" style="vertical-align:text-bottom;" width="13" height="13"/></a>
</cfoutput>
<table class="worktable">
<thead>
<td width="1%"></td>
<!--- <th width="3%">WZ</th> --->
<th width="7%">Артикул</th>
<th width="7%">Кол-во (метрика)</th>
<th width="3%">Ед.изм.</th>
<th width="3%">Цена &#8381; со скид., с НДС</th>
<th width="3%">Стоимость &#8381; с НДС</th>
<td width="3%">&nbsp;&nbsp;&nbsp;</td>
<td width="10%">Услуга</td>
<td width="10%">Компонент</td>
<!--- <td width="10%">Клиентс. назв.</td> --->
<td width="3%">GPL с НДС</td>
<td width="3%">Скидка%</td>
<!--- <td width="6%">Дата НОУ</td>
<td width="6%">Дата оконч.</td>
<td width="3%">Ключ строки</td> --->
<!--- <td width="3%">Сырая метрика</td> --->
<td width="3%">Оплачиваемый объем</td>
<!--- <td width="5%">Договор</td>
<td width="10%">Допник</td>
<td width="10%">Сделка</td> --->
</thead>
<cfoutput query="report.qCharge" group="svc">
<cfset var acc=0/>
<tr>
<td bgcolor="##ccc"></td>
<td bgcolor="##ccc" colspan="5" class="b"> #svc#</td>
<td></td>
<td bgcolor="##ccc" colspan="99"></td>
</tr>
<cfoutput>
<cfset acc=acc+charge/>
<tr>
<td>#currentRow#</a></td>
<td>#code#</td>
<td class="r" style="font-size:120%; padding:0 1em;">#chargeable_metric#</td>
<td class="c" style="font-size:90%;">#unit#</td>
<td class="r">#discounted_price#</td>
<td class="r">#charge#</td>
<td></td>
<td>#svc#</td>
<td>#component#</td>
<!--- <td>#user_description#</td> --->
<td class="r">#price#</td>
<td class="r">#discount#</td>
<td class="r">#metric#</td>
</tr>
</cfoutput>
<tr>
<td bgcolor="##eee" style="border-bottom:2px solid gray;"></td>
<td bgcolor="##eee" colspan="4" class="r" style="border-bottom:2px solid gray;"><b>Итого</b> #svc#:</td>
<td bgcolor="##eee" colspan="1" class="r b" style="border-bottom:2px solid gray;">#NumFmt(acc,2)#</td>
<td colspan="14" style="border-bottom:2px solid gray;"></td>
</tr >
</cfoutput>
</table>
<!--- <cfdump var=#qCharge#/> --->
<layout:page section="footer"/>
<!--- select * from s3billing.billing_per_user_fix_pl('2025-05-01 00:00:00','2025-06-01 00:00:00','1395'); --->