014 Bearer
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<hr/>
|
||||
t test
|
||||
<hr/>
|
||||
<cftry>
|
||||
<cfdump var=#session#/>
|
||||
<cfcatch type="any"><cfdump var=#cfcatch#/></cfcatch>
|
||||
</cftry/>
|
||||
</cftry>
|
||||
|
||||
<cfset jwt = new lib.jwt(this.client_secret)/>
|
||||
|
||||
+191
-156
@@ -22,20 +22,18 @@
|
||||
<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.iam_url = "https://auth-api.ngcloud.ru/api/v1/auth/user"/>
|
||||
|
||||
<cfset this.datasource = "rpt"/>
|
||||
<cfset this.defaultdatasource = this.datasource/>
|
||||
<cfset request.DS = "#this.datasource#">
|
||||
<cfset request.language="">
|
||||
<!--- request scope is available in pseudoconstructor
|
||||
see also https://www.bennadel.com/blog/1437-coldfusion-scope-existence-during-various-request-types-and-events.htm --->
|
||||
<cfset request.DS = "#this.datasource#"/>
|
||||
<cfset request.language=""/>
|
||||
<cfset getDS(this.datasource)/>
|
||||
<!--- кажется, нужно инициализировать датасорцы в псевдоконструкторе - onRequest не получается --->
|
||||
<!--- *** проверить, что есть - если есть, не создавать --->
|
||||
|
||||
|
||||
<!--- это работает с административно заданным датасорцем, хотя, казалось бы, не должно
|
||||
(без окружения функция вернет пустую структуру). Видимо, административный датасорц обнаруживается раньше.
|
||||
Проверять на пустоту не хочется, чтобы не плодить в псевдоконструкторе переменные --->
|
||||
<!--- <cfdump var=#variables#/> --->
|
||||
|
||||
|
||||
<cffunction
|
||||
@@ -48,7 +46,6 @@
|
||||
<!--- *** мы не проверяем срок жизни сертификата и его отзыв--->
|
||||
<!--- Obtain idp certificate OnApplicationStart --->
|
||||
<cfset application.idpCertificate = getIdpCertificate(this.idpCertUrl)/>
|
||||
|
||||
<cfreturn true />
|
||||
</cffunction>
|
||||
|
||||
@@ -77,7 +74,7 @@
|
||||
|
||||
<!--- global settings --->
|
||||
<cfset request.RECORDS_PER_PAGE=500/>
|
||||
<cfset request.APP_VERSION="0.00.013"/>
|
||||
<cfset request.APP_VERSION="0.00.014"/>
|
||||
<cfset request.STAND=getStand()/>
|
||||
|
||||
<!--- application constants --->
|
||||
@@ -168,128 +165,156 @@
|
||||
(для начала делаем без рефреша, только проверяем протухание)
|
||||
А рефреш можно сделать на серверной стороне?
|
||||
Кладем данные из токена в реквест и наслаждаемся --->
|
||||
<!--- Вариант: если есть Authorization:bearer, идем с ним к IAM --->
|
||||
|
||||
<!--- <cfscript>
|
||||
var requestData = GetHttpRequestData();
|
||||
var headers = requestData.headers;
|
||||
|
||||
writedump(headers);
|
||||
<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"));
|
||||
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"));
|
||||
//}
|
||||
var result="";
|
||||
//try {
|
||||
var iamService = new http(method = "GET", charset = "utf-8", url = #this.iam_url#, timeout="5");
|
||||
iamService.addParam(type = "HEADER", name = "Accept", value = "application/json");
|
||||
iamService.addParam(type = "HEADER", name = "Authorization", value = "#authorizationHeader#"); //passthrough
|
||||
//writedump(this.iamServiceUrl);abort;
|
||||
var resp = iamService.send();
|
||||
//if (resp.status_code NEQ 200) throw("IDP response not OK");
|
||||
//writedump(resp);abort;
|
||||
var prefix = resp.getPrefix();
|
||||
if (prefix.status_code NEQ 200) {
|
||||
//writedump(this.iamServiceUrl);
|
||||
//writedump(resp);
|
||||
var iamStatusCode = (isValid("integer", prefix.status_code)) ? val(prefix.status_code) : 500;
|
||||
throw(message="IAM error", detail="URL: #this.iam_url# Status Code: #prefix.status_code#");
|
||||
//abort;
|
||||
//throw("IDP response not OK");
|
||||
}
|
||||
result = prefix.filecontent;
|
||||
var idpUserData=deserializeJson(result);
|
||||
request.auth.wz=idpUserData.userInfo.ClientId;//GUID!
|
||||
request.auth.login = idpUserData.userInfo.login;
|
||||
//request.usrUid=idpUserData.userInfo.contactId;//userId; //GUID!
|
||||
//writedump(idpUserData);abort;
|
||||
//} catch (e) {
|
||||
/*if (fileExists("#GetDirectoryFromPath(GetCurrentTemplatePath())#/etc/local-debug")) { //true for local debug without IDP, etc/* does not go to repository
|
||||
var result='{"accounts":[{"login":"","type":"telegram"}],"avatar":["d2d2b7ac-50af-432b-b7c4-f7d6561e288a"],"company":"ООО «НУБЕС»","companyId":"8ec70ac0-546d-42a7-8cff-339c8fb51a23","contactId":"983967a3-58c2-4cdd-84a5-8b427ccfac82","email":"smishchuk@nubes.ru","externalUser":false,"fio":{"fullName":"Мищук Сергей ","name":"Сергей","secondName":"","surname":"Мищук"},"groupIds":["94bf5be7-52f4-5c13-87c3-513786934685","ab12b6b8-0265-4683-a5c2-5e1a74a55216","aff008c3-7443-50f6-86e1-ecff3cd3b04d","d6000da0-c9aa-55eb-9882-f118b432730b","d89a33dc-3177-5854-9580-f7f860a5ab7c","ddfe2555-9ef4-42cb-9a2b-7f24e61e7747","df12926f-ecb8-5479-857f-6d291464baad","fda5c295-230a-5025-9797-b8b4e99e08aa","2be9b4b6-94d1-59f9-a649-cab228d82169","64fe6f5c-a91b-5fea-a7ca-d9823701ebd4"],"integration":{"serviceId":""},"login":"smishchuk@nubes.ru","mobilePhone":[],"position":"","userId":"d72530e1-66a4-412f-a046-38153c4e5405"}';}
|
||||
*/
|
||||
//writedump(resp);
|
||||
//return representationOf( {"exception"=e} );
|
||||
//rethrow(e);
|
||||
//}
|
||||
}
|
||||
writeDump(token_data);
|
||||
abort;
|
||||
}
|
||||
} else {
|
||||
writeoutput("No Authorization header present");//abort;
|
||||
}
|
||||
</cfscript> --->
|
||||
<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);
|
||||
} else {
|
||||
//точно мы так не словим дедлок ВПР
|
||||
|
||||
/*
|
||||
часто появляется ошибка: после неудачного запроса к 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 --->
|
||||
|
||||
lock scope="session" type="exclusive" timeout="3" {
|
||||
|
||||
//writedump(session);
|
||||
//writeoutput(getTickCount()-request.startTickCount);
|
||||
|
||||
//Это первая проба, с сессией
|
||||
//Когда протухнет сессия, мы можем пойти к IDP и повторно аутентифицироваться. Поскольку у нас сохраняются куки IDP, его сессия, возможно, будет жива. Если мы обновляли сессию IDP в другом приложении SSO, по всей видимости, она будет продолжаться. Если мы видим, что сессия IDP состарилась и хочет протухнуть, мы можем ее освежить и получить новый access token (который мы пока не используем - мы пользуемся ответом IDP) Наверное, правильно обходиться для этих целей вообще без сессии *** --->
|
||||
|
||||
//проверить sso
|
||||
//? сделать рефреш
|
||||
//? сделать логаут
|
||||
//? сделать отображение ошибки IDP
|
||||
//+ сделать получение сертификата
|
||||
|
||||
if (structKeyExists(session, "auth")) {
|
||||
request.auth = structCopy(session.auth);
|
||||
} else {
|
||||
//<!--- сессии нет --->
|
||||
session.auth_state=createGUID().toString()
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // lock session exclusive
|
||||
}
|
||||
</cfscript>
|
||||
|
||||
|
||||
<cfif structKeyExists(request, "auth")>
|
||||
@@ -418,39 +443,49 @@
|
||||
<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
|
||||
<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-----";
|
||||
//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#");
|
||||
}
|
||||
throw(message="RSA cetificate not found");
|
||||
} else {
|
||||
throw (message="Cannot obtain IDP certificate, request failed", detail="Status_Code #status_code#");
|
||||
}
|
||||
}
|
||||
|
||||
</cfscript>
|
||||
</cfscript>
|
||||
|
||||
<cffunction name="rethrow" returntype="void">
|
||||
<!--- https://www.raymondcamden.com/2004/03/09/3089633C-9FA0-606B-3F540AE9642A795F --->
|
||||
<cftry>
|
||||
<cfcatch>
|
||||
<cfrethrow/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfthrow type="Context validation error" message="RETHROW() called outside TRY-CATCH"/>
|
||||
</cffunction>
|
||||
</cfcomponent>
|
||||
@@ -70,6 +70,7 @@ VCD Computing: <b>#dateFormat(report.qComputingAge.dt_load,'YYYY-MM-DD')# #timeF
|
||||
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>
|
||||
Цены GPL<br/>
|
||||
|
||||
<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>
|
||||
@@ -83,7 +84,7 @@ S3 операции и трафик: <b>#dateFormat(report.qS3OpsTrfAge.dt_load,
|
||||
<th width="7%">Артикул</th>
|
||||
<th width="7%">Кол-во (метрика)</th>
|
||||
<th width="3%">Ед.изм.</th>
|
||||
<th width="3%">Цена ₽ со скид., с НДС</th>
|
||||
<th width="3%">Цена ₽ GPL, с НДС</th>
|
||||
<th width="3%">Стоимость ₽ с НДС</th>
|
||||
|
||||
<td width="3%"> </td>
|
||||
@@ -92,8 +93,8 @@ S3 операции и трафик: <b>#dateFormat(report.qS3OpsTrfAge.dt_load,
|
||||
<td width="10%">Компонент</td>
|
||||
<!--- <td width="10%">Клиентс. назв.</td> --->
|
||||
|
||||
<td width="3%">GPL с НДС</td>
|
||||
<td width="3%">Скидка%</td>
|
||||
<!--- <td width="3%">GPL с НДС</td>
|
||||
<td width="3%">Скидка%</td> --->
|
||||
|
||||
<!--- <td width="6%">Дата НОУ</td>
|
||||
<td width="6%">Дата оконч.</td>
|
||||
@@ -133,8 +134,8 @@ S3 операции и трафик: <b>#dateFormat(report.qS3OpsTrfAge.dt_load,
|
||||
<td>#component#</td>
|
||||
<!--- <td>#user_description#</td> --->
|
||||
|
||||
<td class="r">#price#</td>
|
||||
<td class="r">#discount#</td>
|
||||
<!--- <td class="r">#price#</td>
|
||||
<td class="r">#discount#</td> --->
|
||||
|
||||
<td class="r">#metric#</td>
|
||||
|
||||
|
||||
@@ -61,13 +61,14 @@
|
||||
</cffunction>
|
||||
|
||||
|
||||
<!--- <cfmodule template="mod/payg.cfm"
|
||||
<cfmodule template="mod/payg.cfm"
|
||||
dt_finish=#dt_finish#
|
||||
dt_start=#dt_start#
|
||||
tenant_wz_index=#tenant_wz_index#
|
||||
output="report"
|
||||
debug=#isDefined("DEBUG")#
|
||||
/> --->
|
||||
<!--- <cfset out={
|
||||
/>
|
||||
<cfset out={
|
||||
"hours"=#hours#,
|
||||
"dtStart="=#iso8601dtFormat(dt_start)#,
|
||||
"dtFinish"=#iso8601dtFormat(dt_finish)#,
|
||||
@@ -77,11 +78,13 @@
|
||||
"s3OpsTrfLoadedAt"="#iso8601dtFormat(report.qS3OpsTrfAge.dt_load)#",
|
||||
"records"=#report.qCharge.recordCount#,
|
||||
"queryDurationMs"=#(getTickCount() - msStartAt)#,
|
||||
"wz"=#request.auth.wz#,
|
||||
"login"=#request.auth.login#,
|
||||
"report"=#report.qCharge.reduce((result, row) => {
|
||||
result.append(row);
|
||||
return result;
|
||||
}, [])#
|
||||
}/> ---><cfset out={"test"=true}/>
|
||||
}/>
|
||||
|
||||
<cfcontent
|
||||
type="application/json"
|
||||
|
||||
Reference in New Issue
Block a user