Commit def29174a337a5192a9a7898d16866396e6622ca
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
102 changed files
with
4281 additions
and
114 deletions
@@ -66,7 +66,7 @@ | @@ -66,7 +66,7 @@ | ||
66 | "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n self.ctx.html = self.ctx.settings.cardHtml;\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n}\n\n", | 66 | "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n self.ctx.html = self.ctx.settings.cardHtml;\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n}\n\n", |
67 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", | 67 | "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"<div class='card'>HTML code here</div>\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", |
68 | "dataKeySettingsSchema": "{}\n", | 68 | "dataKeySettingsSchema": "{}\n", |
69 | - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"https://thingsboard.io/images/logo_small.png\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | 69 | + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"<div class='card'>\\n <div class='content'>\\n <div class='column'>\\n <h1>Value title</h1>\\n <div class='value'>\\n ${My value:2} units.\\n </div> \\n <div class='description'>\\n Value description text\\n </div>\\n </div>\\n <img height=\\\"80px\\\" src=\\\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMzIwIiB3aWR0aD0iMzIwIj48ZyBzdHJva2Utd2lkdGg9IjI4Ij48ZyBmaWxsPSIjMzA1NjgwIiBjb2xvcj0iIzAwMCIgd2hpdGUtc3BhY2U9Im5vcm1hbCI+PHBhdGggc3R5bGU9InRleHQtZGVjb3JhdGlvbi1jb2xvcjojMDAwO2lzb2xhdGlvbjphdXRvO21peC1ibGVuZC1tb2RlOm5vcm1hbDtibG9jay1wcm9ncmVzc2lvbjp0Yjt0ZXh0LWRlY29yYXRpb24tbGluZTpub25lO3RleHQtZGVjb3JhdGlvbi1zdHlsZTpzb2xpZDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmUiIGQ9Ik0xNTEuMTMgMGMtMjguMzYzIDAtNTQuOTE1IDcuOTE1LTc3LjYxMyAyMS41MzdhMzYuNTc4IDM2LjU3OCAwIDAgMC0yMy4wNjctOC4xOTQgOC43NjYgOC43NjYgMCAwIDAtLjAwNCAwYy0yMC4xNTQuMDAxLTM2LjY3OSAxNi41MjgtMzYuNjc4IDM2LjY4MmE4Ljc2NiA4Ljc2NiAwIDAgMCAwIC4wMSAzNi42OSAzNi42OSAwIDAgMCA4LjEwNCAyMi45MjhjLTEzLjgzIDIyLjgzLTIxLjg3IDQ5LjU4LTIxLjg3IDc4LjE3YTguNzY2IDguNzY2IDAgMSAwIDE3LjUzIDBjMC0yNC43MDIgNi43Mi00Ny43NDggMTguMzc5LTY3LjU3NCA0LjU2NiAxLjk4NSA5LjQ3MiAzLjE1IDE0LjUxOSAzLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEyIDBjMjAuMTU1IDAgMzYuNjgzLTE2LjUyNyAzNi42ODItMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAwNGMtLjAwMS01LTEuMTM4LTkuODYzLTMuMDgzLTE0LjM5NyAxOS43MTctMTEuNDg0IDQyLjU4NS0xOC4wOTUgNjcuMDg1LTE4LjA5NWE4Ljc2NiA4Ljc2NiAwIDEgMCAwLTE3LjUzek01MC40NCAzMC44OGM1LjkxMy4wMDIgMTEuMTkxIDIuNTEyIDE0LjgzNiA3LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMCAuMTgzLjIxNCAxOS4xMzcgMTkuMTM3IDAgMCAxIDQuMTM0IDExLjg2M2MtLjAwMiAxMC42NzctOC40NjggMTkuMTQ0LTE5LjE0NCAxOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMS0xMi00LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEzLS4wMSAxOS4xMzYgMTkuMTM2IDAgMCAxLTcuMTQ0LTE0Ljg5MmMuMDAzLTEwLjY3NyA4LjQ3LTE5LjE0NCAxOS4xNDgtMTkuMTQ2eiIvPjxwYXRoIHN0eWxlPSJ0ZXh0LWRlY29yYXRpb24tY29sb3I6IzAwMDtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWw7YmxvY2stcHJvZ3Jlc3Npb246dGI7dGV4dC1kZWNvcmF0aW9uLWxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb24tc3R5bGU6c29saWQ7dGV4dC1pbmRlbnQ6MDt0ZXh0LXRyYW5zZm9ybTpub25lIiBkPSJNNjYuOTkyIDEwMi44M2E4LjE4NyA4LjE4NyAwIDAgMC0yLjI1OCA2LjA3MSA4LjYwNCA4LjYwNCAwIDAgMCAyLjMzOCA1LjUxOGM2LjgwNSA2Ljg1NiAyMC4yMjMgMjAuMjIzIDIwLjIyMyAyMC4yMjNsMTEuODQ0LTExLjgzcy0xMi45NzMtMTIuOTYxLTIwLjE3Ni0yMC4xNzFjLTEuNjA0LTEuNjMyLTMuNzUtMi4zMTQtNi4wMTItMi4zMjRhOC4xNSA4LjE1IDAgMCAwLTUuOTYgMi41MTJ6bTMyLjE0NyAxOS45ODNMNjIuNSAxNTkuNDUyYy0zLjk3NSAzLjk3Ni0zLjk3NSAxMC40MjEgMCAxNC4zOTdsMTguMTU2IDE4LjE1NiAzMS43NTMgMzEuNzUzIDMwLjQ3OCAzMC40NzhjMy45NzYgMy45NzYgMTAuNDIyIDMuOTc2IDE0LjM5OCAwbDI0Ljc5MS0yNC43OTEgMzcuOTE0LTM3LjkxNCAzNi42MzktMzYuNjM5YzMuOTc1LTMuOTc2IDMuOTc1LTEwLjQyMiAwLTE0LjM5OGwtMTguNjMtMTguNjMtMzEuNzUtMzEuNzYtMzAuMDEtMzBjLTMuOTc3LTMuOTc1LTEwLjQyMi0zLjk3NS0xNC4zOTggMGwtMjQuNzkgMjQuNzktMzcuOTEgMzcuOTF6bTM3LjkxMS0zNy45MXMtMTIuOTczLTEyLjk2MS0yMC4xNzYtMjAuMTcxYy0xLjYwNC0xLjYzMi0zLjc1LTIuMzE0LTYuMDEyLTIuMzI0LTQuNzE3LS4wMjMtOC40MzQgMy44NjEtOC4yMTcgOC41ODNhOC42MDQgOC42MDQgMCAwIDAgMi4zMzcgNS41MThjNi44MDUgNi44NTYgMjAuMjIzIDIwLjIyMyAyMC4yMjMgMjAuMjIzbDExLjg0NC0xMS44M3ptNjkuMTkzIDUuMjEzczEyLjk2MS0xMi45NzMgMjAuMTcxLTIwLjE3NmMxLjYzMy0xLjYwNCAyLjMxNC0zLjc1IDIuMzI0LTYuMDEyLjAyMy00LjcxNi0zLjg2MS04LjQzNC04LjU4My04LjIxN2E4LjYwNCA4LjYwNCAwIDAgMC01LjUxOCAyLjMzOGMtNi44NTYgNi44MDUtMjAuMjIzIDIwLjIyMy0yMC4yMjMgMjAuMjIzbDExLjgzIDExLjg0NHptMzEuNzUzIDMxLjc1M3MxMi45NjEtMTIuOTczIDIwLjE3MS0yMC4xNzZjMS42MzMtMS42MDQgMi4zMTQtMy43NSAyLjMyNC02LjAxMi4wMjMtNC43MTYtMy44NjEtOC40MzQtOC41ODMtOC4yMTdhOC42MDQgOC42MDQgMCAwIDAtNS41MTggMi4zMzhjLTYuODU2IDYuODA1LTIwLjIyMyAyMC4yMjMtMjAuMjIzIDIwLjIyM2wxMS44MyAxMS44NDR6bS0xOC4wMDkgNjkuNjY3czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjQgNC43MTcuMDIyIDguNDM0LTMuODYyIDguMjE3LTguNTg0bC0uMDAyLjAwMmE4LjYwNiA4LjYwNiAwIDAgMC0yLjMzOC01LjUxOGMtNi44MDUtNi44NTYtMjAuMjIyLTIwLjIyMi0yMC4yMjItMjAuMjIybC0xMS44NDQgMTEuODN6bS0zNy45MTQgMzcuOTE0czEyLjk3MyAxMi45NjEgMjAuMTc4IDIwLjE3YzEuNjA0IDEuNjMyIDMuNzUgMi4zMTMgNi4wMTIgMi4zMjMgNC43MTcuMDIzIDguNDM0LTMuODYxIDguMjE3LTguNTgzaC0uMDAyYTguNjAzIDguNjAzIDAgMCAwLTIuMzM3LTUuNTE4Yy02LjgwNS02Ljg1Ni0yMC4yMjMtMjAuMjIzLTIwLjIyMy0yMC4yMjNsLTExLjg0NCAxMS44M3ptLTY5LjY2Ny01LjY4N3MtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDIgOC42MDIgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44Mi0xMS44NHptLTMxLjc0My0zMS43NHMtMTIuOTYxIDEyLjk3My0yMC4xNjkgMjAuMTc4Yy0xLjYzMiAxLjYwNC0yLjMxNCAzLjc1LTIuMzI0IDYuMDEyLS4wMjMgNC43MTcgMy44NjEgOC40MzQgOC41ODMgOC4yMTdoLS4wMDJhOC42MDQgOC42MDQgMCAwIDAgNS41MTgtMi4zMzdjNi44NTYtNi44MDUgMjAuMjIzLTIwLjIyMyAyMC4yMjMtMjAuMjIzbC0xMS44My0xMS44NXpNMTY3LjkgMTAxLjQ3YzEuNjgtMS43MDYgMy45NjctMi42NiA2LjI5Ny0yLjYyNmE3Ljg5IDcuODkgMCAwIDEgNC41NjMgMS41MWwxNi40OTkgMTIuMWMzLjIgMi4yOTcgNC4xNDQgNi42NTkgMi4yMyAxMC4zMTItMS45MTMgMy42NTMtNi4xMjMgNS41MjQtOS45NSA0LjQyM2w2LjEyNCAyMy45NDhjMS4xMTMgNC4zNTEtMS41NjQgOC45NjctNS45ODQgMTAuMzE3bC00NC42NDIgMTMuNjMgOC4yNDYgMzEuODg0YzEuMTczIDQuMzctMS41MDIgOS4wNDQtNS45NTUgMTAuNDA3cy04Ljk3NS0xLjExMS0xMC4wNjgtNS41MDVsLTEwLjI4Mi0zOS43N2MtMS4xMjYtNC4zNTUgMS41NS04Ljk4NCA1Ljk3Ni0xMC4zMzdsNDQuNjYxLTEzLjYzNy00LjEyMi0xNi4xMThjLTIuNzYzIDMuMDY0LTcuMjMzIDMuODA4LTEwLjU4NiAxLjc2MS0zLjM1My0yLjA0Ny00LjYxNC02LjI5LTIuOTg2LTEwLjA0N2w4LjExNy0xOS40NTRhOC44NzIgOC44NzIgMCAwIDEgMS44NjMtMi43OTd6IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48cGF0aCBzdHlsZT0idGV4dC1kZWNvcmF0aW9uLWNvbG9yOiMwMDA7aXNvbGF0aW9uOmF1dG87bWl4LWJsZW5kLW1vZGU6bm9ybWFsO2Jsb2NrLXByb2dyZXNzaW9uOnRiO3RleHQtZGVjb3JhdGlvbi1saW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uLXN0eWxlOnNvbGlkO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZSIgZD0iTTE2OC44NyAzMjAuMDRjMjguMzYzIDAgNTQuOTE1LTcuOTE1IDc3LjYxNC0yMS41MzhhMzYuNTc4IDM2LjU3OCAwIDAgMCAyMy4wNjcgOC4xOTQgOC43NjYgOC43NjYgMCAwIDAgLjAwNCAwYzIwLjE1NSAwIDM2LjY4LTE2LjUyOCAzNi42NzktMzYuNjgyYTguNzY2IDguNzY2IDAgMCAwIDAtLjAxMSAzNi42ODggMzYuNjg4IDAgMCAwLTguMTAzLTIyLjkyN2MxMy44MjUtMjIuODIgMjEuODY2LTQ5LjU3MiAyMS44NjYtNzguMTYyYTguNzY2IDguNzY2IDAgMSAwLTE3LjUzMSAwYzAgMjQuNzAzLTYuNzIgNDcuNzQ5LTE4LjM3OCA2Ny41NzUtNC41NjctMS45ODUtOS40NzMtMy4xNS0xNC41Mi0zLjE1N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMDEyIDBjLTIwLjE1NS0uMDAxLTM2LjY4MyAxNi41MjctMzYuNjgyIDM2LjY4Mi4wMDIgNC45OTkgMS4xMzkgOS44NjIgMy4wODMgMTQuMzk3LTE5LjcxNyAxMS40ODQtNDIuNTg2IDE4LjA5NS02Ny4wODYgMTguMDk1YTguNzY2IDguNzY2IDAgMSAwIDAgMTcuNTN6bTEwMC42OS0zMC44NzVjLTUuOTEzIDAtMTEuMTkxLTIuNTEyLTE0LjgzNi03LjA3N2E4Ljc2NiA4Ljc2NiAwIDAgMC0uMTgzLS4yMTQgMTkuMTM2IDE5LjEzNiAwIDAgMS00LjEzNC0xMS44NjNjLjAwMi0xMC42NzcgOC40NjgtMTkuMTQ0IDE5LjE0NC0xOS4xNDhhMTkuMTQ1IDE5LjE0NSAwIDAgMSAxMiA0LjI1NCA4Ljc2NiA4Ljc2NiAwIDAgMCAuMDEzLjAxIDE5LjEzNiAxOS4xMzYgMCAwIDEgNy4xNDQgMTQuODkyYy0uMDAzIDEwLjY3Ny04LjQ3IDE5LjE0NS0xOS4xNDggMTkuMTQ2eiIvPjwvZz48L2c+PC9zdmc+\\\" />\\n </div>\\n</div>\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" |
70 | } | 70 | } |
71 | }, | 71 | }, |
72 | { | 72 | { |
1 | +-- | ||
2 | +-- Copyright © 2016-2017 The Thingsboard Authors | ||
3 | +-- | ||
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +-- you may not use this file except in compliance with the License. | ||
6 | +-- You may obtain a copy of the License at | ||
7 | +-- | ||
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +-- | ||
10 | +-- Unless required by applicable law or agreed to in writing, software | ||
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +-- See the License for the specific language governing permissions and | ||
14 | +-- limitations under the License. | ||
15 | +-- | ||
16 | + | ||
17 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( | ||
18 | + tenant_id timeuuid, | ||
19 | + id timeuuid, | ||
20 | + customer_id timeuuid, | ||
21 | + entity_id timeuuid, | ||
22 | + entity_type text, | ||
23 | + entity_name text, | ||
24 | + user_id timeuuid, | ||
25 | + user_name text, | ||
26 | + action_type text, | ||
27 | + action_data text, | ||
28 | + action_status text, | ||
29 | + action_failure_details text, | ||
30 | + PRIMARY KEY ((tenant_id, entity_id, entity_type), id) | ||
31 | +); | ||
32 | + | ||
33 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id ( | ||
34 | + tenant_id timeuuid, | ||
35 | + id timeuuid, | ||
36 | + customer_id timeuuid, | ||
37 | + entity_id timeuuid, | ||
38 | + entity_type text, | ||
39 | + entity_name text, | ||
40 | + user_id timeuuid, | ||
41 | + user_name text, | ||
42 | + action_type text, | ||
43 | + action_data text, | ||
44 | + action_status text, | ||
45 | + action_failure_details text, | ||
46 | + PRIMARY KEY ((tenant_id, customer_id), id) | ||
47 | +); | ||
48 | + | ||
49 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( | ||
50 | + tenant_id timeuuid, | ||
51 | + id timeuuid, | ||
52 | + customer_id timeuuid, | ||
53 | + entity_id timeuuid, | ||
54 | + entity_type text, | ||
55 | + entity_name text, | ||
56 | + user_id timeuuid, | ||
57 | + user_name text, | ||
58 | + action_type text, | ||
59 | + action_data text, | ||
60 | + action_status text, | ||
61 | + action_failure_details text, | ||
62 | + PRIMARY KEY ((tenant_id, user_id), id) | ||
63 | +); | ||
64 | + | ||
65 | + | ||
66 | + | ||
67 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( | ||
68 | + tenant_id timeuuid, | ||
69 | + id timeuuid, | ||
70 | + partition bigint, | ||
71 | + customer_id timeuuid, | ||
72 | + entity_id timeuuid, | ||
73 | + entity_type text, | ||
74 | + entity_name text, | ||
75 | + user_id timeuuid, | ||
76 | + user_name text, | ||
77 | + action_type text, | ||
78 | + action_data text, | ||
79 | + action_status text, | ||
80 | + action_failure_details text, | ||
81 | + PRIMARY KEY ((tenant_id, partition), id) | ||
82 | +); | ||
83 | + | ||
84 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( | ||
85 | + tenant_id timeuuid, | ||
86 | + partition bigint, | ||
87 | + PRIMARY KEY (( tenant_id ), partition) | ||
88 | +) WITH CLUSTERING ORDER BY ( partition ASC ) | ||
89 | +AND compaction = { 'class' : 'LeveledCompactionStrategy' }; |
1 | +-- | ||
2 | +-- Copyright © 2016-2017 The Thingsboard Authors | ||
3 | +-- | ||
4 | +-- Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +-- you may not use this file except in compliance with the License. | ||
6 | +-- You may obtain a copy of the License at | ||
7 | +-- | ||
8 | +-- http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +-- | ||
10 | +-- Unless required by applicable law or agreed to in writing, software | ||
11 | +-- distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +-- See the License for the specific language governing permissions and | ||
14 | +-- limitations under the License. | ||
15 | +-- | ||
16 | + | ||
17 | +CREATE TABLE IF NOT EXISTS audit_log ( | ||
18 | + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, | ||
19 | + tenant_id varchar(31), | ||
20 | + customer_id varchar(31), | ||
21 | + entity_id varchar(31), | ||
22 | + entity_type varchar(255), | ||
23 | + entity_name varchar(255), | ||
24 | + user_id varchar(31), | ||
25 | + user_name varchar(255), | ||
26 | + action_type varchar(255), | ||
27 | + action_data varchar(1000000), | ||
28 | + action_status varchar(255), | ||
29 | + action_failure_details varchar(1000000) | ||
30 | +); | ||
31 | + |
@@ -40,6 +40,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; | @@ -40,6 +40,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; | ||
40 | import org.thingsboard.server.dao.alarm.AlarmService; | 40 | import org.thingsboard.server.dao.alarm.AlarmService; |
41 | import org.thingsboard.server.dao.asset.AssetService; | 41 | import org.thingsboard.server.dao.asset.AssetService; |
42 | import org.thingsboard.server.dao.attributes.AttributesService; | 42 | import org.thingsboard.server.dao.attributes.AttributesService; |
43 | +import org.thingsboard.server.dao.audit.AuditLogService; | ||
43 | import org.thingsboard.server.dao.customer.CustomerService; | 44 | import org.thingsboard.server.dao.customer.CustomerService; |
44 | import org.thingsboard.server.dao.device.DeviceService; | 45 | import org.thingsboard.server.dao.device.DeviceService; |
45 | import org.thingsboard.server.dao.event.EventService; | 46 | import org.thingsboard.server.dao.event.EventService; |
@@ -114,6 +115,9 @@ public class ActorSystemContext { | @@ -114,6 +115,9 @@ public class ActorSystemContext { | ||
114 | @Getter private RelationService relationService; | 115 | @Getter private RelationService relationService; |
115 | 116 | ||
116 | @Autowired | 117 | @Autowired |
118 | + @Getter private AuditLogService auditLogService; | ||
119 | + | ||
120 | + @Autowired | ||
117 | @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; | 121 | @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint; |
118 | 122 | ||
119 | @Value("${actors.session.sync.timeout}") | 123 | @Value("${actors.session.sync.timeout}") |
@@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Device; | @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Device; | ||
26 | import org.thingsboard.server.common.data.EntityType; | 26 | import org.thingsboard.server.common.data.EntityType; |
27 | import org.thingsboard.server.common.data.Tenant; | 27 | import org.thingsboard.server.common.data.Tenant; |
28 | import org.thingsboard.server.common.data.asset.Asset; | 28 | import org.thingsboard.server.common.data.asset.Asset; |
29 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
29 | import org.thingsboard.server.common.data.id.*; | 30 | import org.thingsboard.server.common.data.id.*; |
30 | import org.thingsboard.server.common.data.kv.AttributeKey; | 31 | import org.thingsboard.server.common.data.kv.AttributeKey; |
31 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; | 32 | import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
@@ -41,9 +42,7 @@ import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotific | @@ -41,9 +42,7 @@ import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotific | ||
41 | import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; | 42 | import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; |
42 | import org.thingsboard.server.extensions.api.plugins.PluginCallback; | 43 | import org.thingsboard.server.extensions.api.plugins.PluginCallback; |
43 | import org.thingsboard.server.extensions.api.plugins.PluginContext; | 44 | import org.thingsboard.server.extensions.api.plugins.PluginContext; |
44 | -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; | ||
45 | -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; | ||
46 | -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; | 45 | +import org.thingsboard.server.extensions.api.plugins.msg.*; |
47 | import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; | 46 | import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
48 | import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; | 47 | import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; |
49 | import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; | 48 | import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; |
@@ -197,6 +196,52 @@ public final class PluginProcessingContext implements PluginContext { | @@ -197,6 +196,52 @@ public final class PluginProcessingContext implements PluginContext { | ||
197 | } | 196 | } |
198 | 197 | ||
199 | @Override | 198 | @Override |
199 | + public void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, | ||
200 | + List<AttributeKvEntry> attributes, Exception e) { | ||
201 | + pluginCtx.auditLogService.logEntityAction( | ||
202 | + ctx.getTenantId(), | ||
203 | + ctx.getCustomerId(), | ||
204 | + ctx.getUserId(), | ||
205 | + ctx.getUserName(), | ||
206 | + (UUIDBased & EntityId)entityId, | ||
207 | + null, | ||
208 | + ActionType.ATTRIBUTES_UPDATED, | ||
209 | + e, | ||
210 | + attributeType, | ||
211 | + attributes); | ||
212 | + } | ||
213 | + | ||
214 | + @Override | ||
215 | + public void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) { | ||
216 | + pluginCtx.auditLogService.logEntityAction( | ||
217 | + ctx.getTenantId(), | ||
218 | + ctx.getCustomerId(), | ||
219 | + ctx.getUserId(), | ||
220 | + ctx.getUserName(), | ||
221 | + (UUIDBased & EntityId)entityId, | ||
222 | + null, | ||
223 | + ActionType.ATTRIBUTES_DELETED, | ||
224 | + e, | ||
225 | + attributeType, | ||
226 | + keys); | ||
227 | + } | ||
228 | + | ||
229 | + @Override | ||
230 | + public void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) { | ||
231 | + pluginCtx.auditLogService.logEntityAction( | ||
232 | + ctx.getTenantId(), | ||
233 | + ctx.getCustomerId(), | ||
234 | + ctx.getUserId(), | ||
235 | + ctx.getUserName(), | ||
236 | + (UUIDBased & EntityId)entityId, | ||
237 | + null, | ||
238 | + ActionType.ATTRIBUTES_READ, | ||
239 | + e, | ||
240 | + attributeType, | ||
241 | + keys); | ||
242 | + } | ||
243 | + | ||
244 | + @Override | ||
200 | public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) { | 245 | public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) { |
201 | validate(entityId, new ValidationCallback(callback, ctx -> { | 246 | validate(entityId, new ValidationCallback(callback, ctx -> { |
202 | ListenableFuture<List<TsKvEntry>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys); | 247 | ListenableFuture<List<TsKvEntry>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys); |
@@ -461,6 +506,29 @@ public final class PluginProcessingContext implements PluginContext { | @@ -461,6 +506,29 @@ public final class PluginProcessingContext implements PluginContext { | ||
461 | } | 506 | } |
462 | 507 | ||
463 | @Override | 508 | @Override |
509 | + public void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e) { | ||
510 | + String rpcErrorStr = ""; | ||
511 | + if (rpcError.isPresent()) { | ||
512 | + rpcErrorStr = "RPC Error: " + rpcError.get().name(); | ||
513 | + } | ||
514 | + String method = body.getMethod(); | ||
515 | + String params = body.getParams(); | ||
516 | + pluginCtx.auditLogService.logEntityAction( | ||
517 | + ctx.getTenantId(), | ||
518 | + ctx.getCustomerId(), | ||
519 | + ctx.getUserId(), | ||
520 | + ctx.getUserName(), | ||
521 | + deviceId, | ||
522 | + null, | ||
523 | + ActionType.RPC_CALL, | ||
524 | + e, | ||
525 | + rpcErrorStr, | ||
526 | + new Boolean(oneWay), | ||
527 | + method, | ||
528 | + params); | ||
529 | + } | ||
530 | + | ||
531 | + @Override | ||
464 | public void scheduleTimeoutMsg(TimeoutMsg msg) { | 532 | public void scheduleTimeoutMsg(TimeoutMsg msg) { |
465 | pluginCtx.scheduleTimeoutMsg(msg); | 533 | pluginCtx.scheduleTimeoutMsg(msg); |
466 | } | 534 | } |
@@ -27,6 +27,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; | @@ -27,6 +27,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; | ||
27 | import org.thingsboard.server.common.data.id.PluginId; | 27 | import org.thingsboard.server.common.data.id.PluginId; |
28 | import org.thingsboard.server.dao.asset.AssetService; | 28 | import org.thingsboard.server.dao.asset.AssetService; |
29 | import org.thingsboard.server.dao.attributes.AttributesService; | 29 | import org.thingsboard.server.dao.attributes.AttributesService; |
30 | +import org.thingsboard.server.dao.audit.AuditLogService; | ||
30 | import org.thingsboard.server.dao.customer.CustomerService; | 31 | import org.thingsboard.server.dao.customer.CustomerService; |
31 | import org.thingsboard.server.dao.device.DeviceService; | 32 | import org.thingsboard.server.dao.device.DeviceService; |
32 | import org.thingsboard.server.dao.plugin.PluginService; | 33 | import org.thingsboard.server.dao.plugin.PluginService; |
@@ -63,6 +64,7 @@ public final class SharedPluginProcessingContext { | @@ -63,6 +64,7 @@ public final class SharedPluginProcessingContext { | ||
63 | final ClusterRpcService rpcService; | 64 | final ClusterRpcService rpcService; |
64 | final ClusterRoutingService routingService; | 65 | final ClusterRoutingService routingService; |
65 | final RelationService relationService; | 66 | final RelationService relationService; |
67 | + final AuditLogService auditLogService; | ||
66 | final PluginId pluginId; | 68 | final PluginId pluginId; |
67 | final TenantId tenantId; | 69 | final TenantId tenantId; |
68 | 70 | ||
@@ -86,6 +88,7 @@ public final class SharedPluginProcessingContext { | @@ -86,6 +88,7 @@ public final class SharedPluginProcessingContext { | ||
86 | this.customerService = sysContext.getCustomerService(); | 88 | this.customerService = sysContext.getCustomerService(); |
87 | this.tenantService = sysContext.getTenantService(); | 89 | this.tenantService = sysContext.getTenantService(); |
88 | this.relationService = sysContext.getRelationService(); | 90 | this.relationService = sysContext.getRelationService(); |
91 | + this.auditLogService = sysContext.getAuditLogService(); | ||
89 | } | 92 | } |
90 | 93 | ||
91 | public PluginId getPluginId() { | 94 | public PluginId getPluginId() { |
@@ -148,7 +148,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener { | @@ -148,7 +148,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener { | ||
148 | DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId())); | 148 | DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId())); |
149 | 149 | ||
150 | ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams()); | 150 | ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams()); |
151 | - ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); | 151 | + ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); |
152 | 152 | ||
153 | return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request); | 153 | return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request); |
154 | } | 154 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.config; | ||
17 | + | ||
18 | +import org.springframework.boot.context.properties.ConfigurationProperties; | ||
19 | +import org.springframework.context.annotation.Configuration; | ||
20 | +import org.thingsboard.server.common.data.EntityType; | ||
21 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
22 | + | ||
23 | +import java.util.HashMap; | ||
24 | +import java.util.Map; | ||
25 | + | ||
26 | +@Configuration | ||
27 | +@ConfigurationProperties(prefix = "audit_log.logging_level") | ||
28 | +public class AuditLogLevelProperties { | ||
29 | + | ||
30 | + private Map<String, String> mask = new HashMap<>(); | ||
31 | + | ||
32 | + public AuditLogLevelProperties() { | ||
33 | + super(); | ||
34 | + } | ||
35 | + | ||
36 | + public void setMask(Map<String, String> mask) { | ||
37 | + this.mask = mask; | ||
38 | + } | ||
39 | + | ||
40 | + public Map<String, String> getMask() { | ||
41 | + return this.mask; | ||
42 | + } | ||
43 | +} |
@@ -40,6 +40,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | @@ -40,6 +40,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | ||
40 | import org.springframework.web.cors.CorsUtils; | 40 | import org.springframework.web.cors.CorsUtils; |
41 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; | 41 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
42 | import org.springframework.web.filter.CorsFilter; | 42 | import org.springframework.web.filter.CorsFilter; |
43 | +import org.thingsboard.server.dao.audit.AuditLogLevelFilter; | ||
43 | import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; | 44 | import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; |
44 | import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider; | 45 | import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider; |
45 | import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter; | 46 | import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter; |
@@ -198,4 +199,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | @@ -198,4 +199,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | ||
198 | return new CorsFilter(source); | 199 | return new CorsFilter(source); |
199 | } | 200 | } |
200 | } | 201 | } |
202 | + | ||
203 | + @Bean | ||
204 | + public AuditLogLevelFilter auditLogLevelFilter(@Autowired AuditLogLevelProperties auditLogLevelProperties) { | ||
205 | + return new AuditLogLevelFilter(auditLogLevelProperties.getMask()); | ||
206 | + } | ||
201 | } | 207 | } |
@@ -21,7 +21,9 @@ import org.springframework.security.access.prepost.PreAuthorize; | @@ -21,7 +21,9 @@ import org.springframework.security.access.prepost.PreAuthorize; | ||
21 | import org.springframework.web.bind.annotation.*; | 21 | import org.springframework.web.bind.annotation.*; |
22 | import org.thingsboard.server.common.data.Customer; | 22 | import org.thingsboard.server.common.data.Customer; |
23 | import org.thingsboard.server.common.data.EntitySubtype; | 23 | import org.thingsboard.server.common.data.EntitySubtype; |
24 | +import org.thingsboard.server.common.data.EntityType; | ||
24 | import org.thingsboard.server.common.data.asset.Asset; | 25 | import org.thingsboard.server.common.data.asset.Asset; |
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
25 | import org.thingsboard.server.common.data.id.AssetId; | 27 | import org.thingsboard.server.common.data.id.AssetId; |
26 | import org.thingsboard.server.common.data.id.CustomerId; | 28 | import org.thingsboard.server.common.data.id.CustomerId; |
27 | import org.thingsboard.server.common.data.id.TenantId; | 29 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -73,8 +75,16 @@ public class AssetController extends BaseController { | @@ -73,8 +75,16 @@ public class AssetController extends BaseController { | ||
73 | checkCustomerId(asset.getCustomerId()); | 75 | checkCustomerId(asset.getCustomerId()); |
74 | } | 76 | } |
75 | } | 77 | } |
76 | - return checkNotNull(assetService.saveAsset(asset)); | 78 | + Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); |
79 | + | ||
80 | + logEntityAction(savedAsset.getId(), savedAsset, | ||
81 | + savedAsset.getCustomerId(), | ||
82 | + asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | ||
83 | + | ||
84 | + return savedAsset; | ||
77 | } catch (Exception e) { | 85 | } catch (Exception e) { |
86 | + logEntityAction(emptyId(EntityType.ASSET), asset, | ||
87 | + null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | ||
78 | throw handleException(e); | 88 | throw handleException(e); |
79 | } | 89 | } |
80 | } | 90 | } |
@@ -86,9 +96,18 @@ public class AssetController extends BaseController { | @@ -86,9 +96,18 @@ public class AssetController extends BaseController { | ||
86 | checkParameter(ASSET_ID, strAssetId); | 96 | checkParameter(ASSET_ID, strAssetId); |
87 | try { | 97 | try { |
88 | AssetId assetId = new AssetId(toUUID(strAssetId)); | 98 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
89 | - checkAssetId(assetId); | 99 | + Asset asset = checkAssetId(assetId); |
90 | assetService.deleteAsset(assetId); | 100 | assetService.deleteAsset(assetId); |
101 | + | ||
102 | + logEntityAction(assetId, asset, | ||
103 | + asset.getCustomerId(), | ||
104 | + ActionType.DELETED, null, strAssetId); | ||
105 | + | ||
91 | } catch (Exception e) { | 106 | } catch (Exception e) { |
107 | + logEntityAction(emptyId(EntityType.ASSET), | ||
108 | + null, | ||
109 | + null, | ||
110 | + ActionType.DELETED, e, strAssetId); | ||
92 | throw handleException(e); | 111 | throw handleException(e); |
93 | } | 112 | } |
94 | } | 113 | } |
@@ -102,13 +121,24 @@ public class AssetController extends BaseController { | @@ -102,13 +121,24 @@ public class AssetController extends BaseController { | ||
102 | checkParameter(ASSET_ID, strAssetId); | 121 | checkParameter(ASSET_ID, strAssetId); |
103 | try { | 122 | try { |
104 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | 123 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
105 | - checkCustomerId(customerId); | 124 | + Customer customer = checkCustomerId(customerId); |
106 | 125 | ||
107 | AssetId assetId = new AssetId(toUUID(strAssetId)); | 126 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
108 | checkAssetId(assetId); | 127 | checkAssetId(assetId); |
109 | 128 | ||
110 | - return checkNotNull(assetService.assignAssetToCustomer(assetId, customerId)); | 129 | + Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(assetId, customerId)); |
130 | + | ||
131 | + logEntityAction(assetId, savedAsset, | ||
132 | + savedAsset.getCustomerId(), | ||
133 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName()); | ||
134 | + | ||
135 | + return savedAsset; | ||
111 | } catch (Exception e) { | 136 | } catch (Exception e) { |
137 | + | ||
138 | + logEntityAction(emptyId(EntityType.ASSET), null, | ||
139 | + null, | ||
140 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId, strCustomerId); | ||
141 | + | ||
112 | throw handleException(e); | 142 | throw handleException(e); |
113 | } | 143 | } |
114 | } | 144 | } |
@@ -124,8 +154,22 @@ public class AssetController extends BaseController { | @@ -124,8 +154,22 @@ public class AssetController extends BaseController { | ||
124 | if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { | 154 | if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { |
125 | throw new IncorrectParameterException("Asset isn't assigned to any customer!"); | 155 | throw new IncorrectParameterException("Asset isn't assigned to any customer!"); |
126 | } | 156 | } |
127 | - return checkNotNull(assetService.unassignAssetFromCustomer(assetId)); | 157 | + |
158 | + Customer customer = checkCustomerId(asset.getCustomerId()); | ||
159 | + | ||
160 | + Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(assetId)); | ||
161 | + | ||
162 | + logEntityAction(assetId, asset, | ||
163 | + asset.getCustomerId(), | ||
164 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strAssetId, customer.getId().toString(), customer.getName()); | ||
165 | + | ||
166 | + return savedAsset; | ||
128 | } catch (Exception e) { | 167 | } catch (Exception e) { |
168 | + | ||
169 | + logEntityAction(emptyId(EntityType.ASSET), null, | ||
170 | + null, | ||
171 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strAssetId); | ||
172 | + | ||
129 | throw handleException(e); | 173 | throw handleException(e); |
130 | } | 174 | } |
131 | } | 175 | } |
@@ -139,8 +183,19 @@ public class AssetController extends BaseController { | @@ -139,8 +183,19 @@ public class AssetController extends BaseController { | ||
139 | AssetId assetId = new AssetId(toUUID(strAssetId)); | 183 | AssetId assetId = new AssetId(toUUID(strAssetId)); |
140 | Asset asset = checkAssetId(assetId); | 184 | Asset asset = checkAssetId(assetId); |
141 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId()); | 185 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId()); |
142 | - return checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId())); | 186 | + Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(assetId, publicCustomer.getId())); |
187 | + | ||
188 | + logEntityAction(assetId, savedAsset, | ||
189 | + savedAsset.getCustomerId(), | ||
190 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, publicCustomer.getId().toString(), publicCustomer.getName()); | ||
191 | + | ||
192 | + return savedAsset; | ||
143 | } catch (Exception e) { | 193 | } catch (Exception e) { |
194 | + | ||
195 | + logEntityAction(emptyId(EntityType.ASSET), null, | ||
196 | + null, | ||
197 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId); | ||
198 | + | ||
144 | throw handleException(e); | 199 | throw handleException(e); |
145 | } | 200 | } |
146 | } | 201 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller; | ||
17 | + | ||
18 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
19 | +import org.springframework.web.bind.annotation.*; | ||
20 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
21 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
22 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | ||
23 | +import org.thingsboard.server.common.data.id.TenantId; | ||
24 | +import org.thingsboard.server.common.data.id.UserId; | ||
25 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
26 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
27 | +import org.thingsboard.server.exception.ThingsboardException; | ||
28 | + | ||
29 | +import java.util.UUID; | ||
30 | + | ||
31 | +@RestController | ||
32 | +@RequestMapping("/api") | ||
33 | +public class AuditLogController extends BaseController { | ||
34 | + | ||
35 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
36 | + @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"limit"}, method = RequestMethod.GET) | ||
37 | + @ResponseBody | ||
38 | + public TimePageData<AuditLog> getAuditLogsByCustomerId( | ||
39 | + @PathVariable("customerId") String strCustomerId, | ||
40 | + @RequestParam int limit, | ||
41 | + @RequestParam(required = false) Long startTime, | ||
42 | + @RequestParam(required = false) Long endTime, | ||
43 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | ||
44 | + @RequestParam(required = false) String offset) throws ThingsboardException { | ||
45 | + try { | ||
46 | + checkParameter("CustomerId", strCustomerId); | ||
47 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
48 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | ||
49 | + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), pageLink)); | ||
50 | + } catch (Exception e) { | ||
51 | + throw handleException(e); | ||
52 | + } | ||
53 | + } | ||
54 | + | ||
55 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
56 | + @RequestMapping(value = "/audit/logs/user/{userId}", params = {"limit"}, method = RequestMethod.GET) | ||
57 | + @ResponseBody | ||
58 | + public TimePageData<AuditLog> getAuditLogsByUserId( | ||
59 | + @PathVariable("userId") String strUserId, | ||
60 | + @RequestParam int limit, | ||
61 | + @RequestParam(required = false) Long startTime, | ||
62 | + @RequestParam(required = false) Long endTime, | ||
63 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | ||
64 | + @RequestParam(required = false) String offset) throws ThingsboardException { | ||
65 | + try { | ||
66 | + checkParameter("UserId", strUserId); | ||
67 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
68 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | ||
69 | + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), pageLink)); | ||
70 | + } catch (Exception e) { | ||
71 | + throw handleException(e); | ||
72 | + } | ||
73 | + } | ||
74 | + | ||
75 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
76 | + @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) | ||
77 | + @ResponseBody | ||
78 | + public TimePageData<AuditLog> getAuditLogsByEntityId( | ||
79 | + @PathVariable("entityType") String strEntityType, | ||
80 | + @PathVariable("entityId") String strEntityId, | ||
81 | + @RequestParam int limit, | ||
82 | + @RequestParam(required = false) Long startTime, | ||
83 | + @RequestParam(required = false) Long endTime, | ||
84 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | ||
85 | + @RequestParam(required = false) String offset) throws ThingsboardException { | ||
86 | + try { | ||
87 | + checkParameter("EntityId", strEntityId); | ||
88 | + checkParameter("EntityType", strEntityType); | ||
89 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
90 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | ||
91 | + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); | ||
92 | + } catch (Exception e) { | ||
93 | + throw handleException(e); | ||
94 | + } | ||
95 | + } | ||
96 | + | ||
97 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
98 | + @RequestMapping(value = "/audit/logs", params = {"limit"}, method = RequestMethod.GET) | ||
99 | + @ResponseBody | ||
100 | + public TimePageData<AuditLog> getAuditLogs( | ||
101 | + @RequestParam int limit, | ||
102 | + @RequestParam(required = false) Long startTime, | ||
103 | + @RequestParam(required = false) Long endTime, | ||
104 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | ||
105 | + @RequestParam(required = false) String offset) throws ThingsboardException { | ||
106 | + try { | ||
107 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
108 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | ||
109 | + return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, pageLink)); | ||
110 | + } catch (Exception e) { | ||
111 | + throw handleException(e); | ||
112 | + } | ||
113 | + } | ||
114 | +} |
@@ -15,9 +15,12 @@ | @@ -15,9 +15,12 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.controller; | 16 | package org.thingsboard.server.controller; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
18 | import lombok.extern.slf4j.Slf4j; | 20 | import lombok.extern.slf4j.Slf4j; |
19 | import org.apache.commons.lang3.StringUtils; | 21 | import org.apache.commons.lang3.StringUtils; |
20 | import org.springframework.beans.factory.annotation.Autowired; | 22 | import org.springframework.beans.factory.annotation.Autowired; |
23 | +import org.springframework.beans.factory.annotation.Value; | ||
21 | import org.springframework.security.core.Authentication; | 24 | import org.springframework.security.core.Authentication; |
22 | import org.springframework.security.core.context.SecurityContextHolder; | 25 | import org.springframework.security.core.context.SecurityContextHolder; |
23 | import org.springframework.web.bind.annotation.ExceptionHandler; | 26 | import org.springframework.web.bind.annotation.ExceptionHandler; |
@@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm; | @@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm; | ||
27 | import org.thingsboard.server.common.data.alarm.AlarmId; | 30 | import org.thingsboard.server.common.data.alarm.AlarmId; |
28 | import org.thingsboard.server.common.data.alarm.AlarmInfo; | 31 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
29 | import org.thingsboard.server.common.data.asset.Asset; | 32 | import org.thingsboard.server.common.data.asset.Asset; |
33 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
34 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
30 | import org.thingsboard.server.common.data.id.*; | 35 | import org.thingsboard.server.common.data.id.*; |
31 | import org.thingsboard.server.common.data.page.TextPageLink; | 36 | import org.thingsboard.server.common.data.page.TextPageLink; |
32 | import org.thingsboard.server.common.data.page.TimePageLink; | 37 | import org.thingsboard.server.common.data.page.TimePageLink; |
@@ -39,6 +44,7 @@ import org.thingsboard.server.common.data.widget.WidgetType; | @@ -39,6 +44,7 @@ import org.thingsboard.server.common.data.widget.WidgetType; | ||
39 | import org.thingsboard.server.common.data.widget.WidgetsBundle; | 44 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
40 | import org.thingsboard.server.dao.alarm.AlarmService; | 45 | import org.thingsboard.server.dao.alarm.AlarmService; |
41 | import org.thingsboard.server.dao.asset.AssetService; | 46 | import org.thingsboard.server.dao.asset.AssetService; |
47 | +import org.thingsboard.server.dao.audit.AuditLogService; | ||
42 | import org.thingsboard.server.dao.customer.CustomerService; | 48 | import org.thingsboard.server.dao.customer.CustomerService; |
43 | import org.thingsboard.server.dao.dashboard.DashboardService; | 49 | import org.thingsboard.server.dao.dashboard.DashboardService; |
44 | import org.thingsboard.server.dao.device.DeviceCredentialsService; | 50 | import org.thingsboard.server.dao.device.DeviceCredentialsService; |
@@ -72,6 +78,7 @@ public abstract class BaseController { | @@ -72,6 +78,7 @@ public abstract class BaseController { | ||
72 | 78 | ||
73 | public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | 79 | public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; |
74 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; | 80 | public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; |
81 | + | ||
75 | @Autowired | 82 | @Autowired |
76 | private ThingsboardErrorResponseHandler errorResponseHandler; | 83 | private ThingsboardErrorResponseHandler errorResponseHandler; |
77 | 84 | ||
@@ -117,6 +124,9 @@ public abstract class BaseController { | @@ -117,6 +124,9 @@ public abstract class BaseController { | ||
117 | @Autowired | 124 | @Autowired |
118 | protected RelationService relationService; | 125 | protected RelationService relationService; |
119 | 126 | ||
127 | + @Autowired | ||
128 | + protected AuditLogService auditLogService; | ||
129 | + | ||
120 | @ExceptionHandler(ThingsboardException.class) | 130 | @ExceptionHandler(ThingsboardException.class) |
121 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { | 131 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { |
122 | errorResponseHandler.handle(ex, response); | 132 | errorResponseHandler.handle(ex, response); |
@@ -540,4 +550,20 @@ public abstract class BaseController { | @@ -540,4 +550,20 @@ public abstract class BaseController { | ||
540 | serverPort); | 550 | serverPort); |
541 | return baseUrl; | 551 | return baseUrl; |
542 | } | 552 | } |
553 | + | ||
554 | + protected <I extends UUIDBased & EntityId> I emptyId(EntityType entityType) { | ||
555 | + return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); | ||
556 | + } | ||
557 | + | ||
558 | + protected <E extends BaseData<I> & HasName, | ||
559 | + I extends UUIDBased & EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId, | ||
560 | + ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { | ||
561 | + User user = getCurrentUser(); | ||
562 | + if (customerId == null || customerId.isNullUid()) { | ||
563 | + customerId = user.getCustomerId(); | ||
564 | + } | ||
565 | + auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); | ||
566 | + } | ||
567 | + | ||
568 | + | ||
543 | } | 569 | } |
@@ -22,6 +22,8 @@ import org.springframework.http.HttpStatus; | @@ -22,6 +22,8 @@ import org.springframework.http.HttpStatus; | ||
22 | import org.springframework.security.access.prepost.PreAuthorize; | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
23 | import org.springframework.web.bind.annotation.*; | 23 | import org.springframework.web.bind.annotation.*; |
24 | import org.thingsboard.server.common.data.Customer; | 24 | import org.thingsboard.server.common.data.Customer; |
25 | +import org.thingsboard.server.common.data.EntityType; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
25 | import org.thingsboard.server.common.data.id.CustomerId; | 27 | import org.thingsboard.server.common.data.id.CustomerId; |
26 | import org.thingsboard.server.common.data.id.TenantId; | 28 | import org.thingsboard.server.common.data.id.TenantId; |
27 | import org.thingsboard.server.common.data.page.TextPageData; | 29 | import org.thingsboard.server.common.data.page.TextPageData; |
@@ -86,8 +88,18 @@ public class CustomerController extends BaseController { | @@ -86,8 +88,18 @@ public class CustomerController extends BaseController { | ||
86 | public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException { | 88 | public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException { |
87 | try { | 89 | try { |
88 | customer.setTenantId(getCurrentUser().getTenantId()); | 90 | customer.setTenantId(getCurrentUser().getTenantId()); |
89 | - return checkNotNull(customerService.saveCustomer(customer)); | 91 | + Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); |
92 | + | ||
93 | + logEntityAction(savedCustomer.getId(), savedCustomer, | ||
94 | + savedCustomer.getId(), | ||
95 | + customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | ||
96 | + | ||
97 | + return savedCustomer; | ||
90 | } catch (Exception e) { | 98 | } catch (Exception e) { |
99 | + | ||
100 | + logEntityAction(emptyId(EntityType.CUSTOMER), customer, | ||
101 | + null, customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | ||
102 | + | ||
91 | throw handleException(e); | 103 | throw handleException(e); |
92 | } | 104 | } |
93 | } | 105 | } |
@@ -99,9 +111,20 @@ public class CustomerController extends BaseController { | @@ -99,9 +111,20 @@ public class CustomerController extends BaseController { | ||
99 | checkParameter(CUSTOMER_ID, strCustomerId); | 111 | checkParameter(CUSTOMER_ID, strCustomerId); |
100 | try { | 112 | try { |
101 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | 113 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
102 | - checkCustomerId(customerId); | 114 | + Customer customer = checkCustomerId(customerId); |
103 | customerService.deleteCustomer(customerId); | 115 | customerService.deleteCustomer(customerId); |
116 | + | ||
117 | + logEntityAction(customerId, customer, | ||
118 | + customer.getId(), | ||
119 | + ActionType.DELETED, null, strCustomerId); | ||
120 | + | ||
104 | } catch (Exception e) { | 121 | } catch (Exception e) { |
122 | + | ||
123 | + logEntityAction(emptyId(EntityType.CUSTOMER), | ||
124 | + null, | ||
125 | + null, | ||
126 | + ActionType.DELETED, e, strCustomerId); | ||
127 | + | ||
105 | throw handleException(e); | 128 | throw handleException(e); |
106 | } | 129 | } |
107 | } | 130 | } |
@@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.*; | @@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.*; | ||
21 | import org.thingsboard.server.common.data.Customer; | 21 | import org.thingsboard.server.common.data.Customer; |
22 | import org.thingsboard.server.common.data.Dashboard; | 22 | import org.thingsboard.server.common.data.Dashboard; |
23 | import org.thingsboard.server.common.data.DashboardInfo; | 23 | import org.thingsboard.server.common.data.DashboardInfo; |
24 | +import org.thingsboard.server.common.data.EntityType; | ||
25 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
24 | import org.thingsboard.server.common.data.id.CustomerId; | 26 | import org.thingsboard.server.common.data.id.CustomerId; |
25 | import org.thingsboard.server.common.data.id.DashboardId; | 27 | import org.thingsboard.server.common.data.id.DashboardId; |
26 | import org.thingsboard.server.common.data.id.TenantId; | 28 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -75,8 +77,17 @@ public class DashboardController extends BaseController { | @@ -75,8 +77,17 @@ public class DashboardController extends BaseController { | ||
75 | public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { | 77 | public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { |
76 | try { | 78 | try { |
77 | dashboard.setTenantId(getCurrentUser().getTenantId()); | 79 | dashboard.setTenantId(getCurrentUser().getTenantId()); |
78 | - return checkNotNull(dashboardService.saveDashboard(dashboard)); | 80 | + Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); |
81 | + | ||
82 | + logEntityAction(savedDashboard.getId(), savedDashboard, | ||
83 | + savedDashboard.getCustomerId(), | ||
84 | + dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | ||
85 | + | ||
86 | + return savedDashboard; | ||
79 | } catch (Exception e) { | 87 | } catch (Exception e) { |
88 | + logEntityAction(emptyId(EntityType.DASHBOARD), dashboard, | ||
89 | + null, dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | ||
90 | + | ||
80 | throw handleException(e); | 91 | throw handleException(e); |
81 | } | 92 | } |
82 | } | 93 | } |
@@ -88,9 +99,20 @@ public class DashboardController extends BaseController { | @@ -88,9 +99,20 @@ public class DashboardController extends BaseController { | ||
88 | checkParameter(DASHBOARD_ID, strDashboardId); | 99 | checkParameter(DASHBOARD_ID, strDashboardId); |
89 | try { | 100 | try { |
90 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); | 101 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
91 | - checkDashboardId(dashboardId); | 102 | + Dashboard dashboard = checkDashboardId(dashboardId); |
92 | dashboardService.deleteDashboard(dashboardId); | 103 | dashboardService.deleteDashboard(dashboardId); |
104 | + | ||
105 | + logEntityAction(dashboardId, dashboard, | ||
106 | + dashboard.getCustomerId(), | ||
107 | + ActionType.DELETED, null, strDashboardId); | ||
108 | + | ||
93 | } catch (Exception e) { | 109 | } catch (Exception e) { |
110 | + | ||
111 | + logEntityAction(emptyId(EntityType.DASHBOARD), | ||
112 | + null, | ||
113 | + null, | ||
114 | + ActionType.DELETED, e, strDashboardId); | ||
115 | + | ||
94 | throw handleException(e); | 116 | throw handleException(e); |
95 | } | 117 | } |
96 | } | 118 | } |
@@ -104,13 +126,25 @@ public class DashboardController extends BaseController { | @@ -104,13 +126,25 @@ public class DashboardController extends BaseController { | ||
104 | checkParameter(DASHBOARD_ID, strDashboardId); | 126 | checkParameter(DASHBOARD_ID, strDashboardId); |
105 | try { | 127 | try { |
106 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | 128 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
107 | - checkCustomerId(customerId); | 129 | + Customer customer = checkCustomerId(customerId); |
108 | 130 | ||
109 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); | 131 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
110 | checkDashboardId(dashboardId); | 132 | checkDashboardId(dashboardId); |
111 | 133 | ||
112 | - return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId)); | 134 | + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId)); |
135 | + | ||
136 | + logEntityAction(dashboardId, savedDashboard, | ||
137 | + savedDashboard.getCustomerId(), | ||
138 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName()); | ||
139 | + | ||
140 | + | ||
141 | + return savedDashboard; | ||
113 | } catch (Exception e) { | 142 | } catch (Exception e) { |
143 | + | ||
144 | + logEntityAction(emptyId(EntityType.DASHBOARD), null, | ||
145 | + null, | ||
146 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId, strCustomerId); | ||
147 | + | ||
114 | throw handleException(e); | 148 | throw handleException(e); |
115 | } | 149 | } |
116 | } | 150 | } |
@@ -126,8 +160,22 @@ public class DashboardController extends BaseController { | @@ -126,8 +160,22 @@ public class DashboardController extends BaseController { | ||
126 | if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { | 160 | if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { |
127 | throw new IncorrectParameterException("Dashboard isn't assigned to any customer!"); | 161 | throw new IncorrectParameterException("Dashboard isn't assigned to any customer!"); |
128 | } | 162 | } |
129 | - return checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId)); | 163 | + |
164 | + Customer customer = checkCustomerId(dashboard.getCustomerId()); | ||
165 | + | ||
166 | + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId)); | ||
167 | + | ||
168 | + logEntityAction(dashboardId, dashboard, | ||
169 | + dashboard.getCustomerId(), | ||
170 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName()); | ||
171 | + | ||
172 | + return savedDashboard; | ||
130 | } catch (Exception e) { | 173 | } catch (Exception e) { |
174 | + | ||
175 | + logEntityAction(emptyId(EntityType.DASHBOARD), null, | ||
176 | + null, | ||
177 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId); | ||
178 | + | ||
131 | throw handleException(e); | 179 | throw handleException(e); |
132 | } | 180 | } |
133 | } | 181 | } |
@@ -141,8 +189,19 @@ public class DashboardController extends BaseController { | @@ -141,8 +189,19 @@ public class DashboardController extends BaseController { | ||
141 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); | 189 | DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); |
142 | Dashboard dashboard = checkDashboardId(dashboardId); | 190 | Dashboard dashboard = checkDashboardId(dashboardId); |
143 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId()); | 191 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId()); |
144 | - return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId())); | 192 | + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId())); |
193 | + | ||
194 | + logEntityAction(dashboardId, savedDashboard, | ||
195 | + savedDashboard.getCustomerId(), | ||
196 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName()); | ||
197 | + | ||
198 | + return savedDashboard; | ||
145 | } catch (Exception e) { | 199 | } catch (Exception e) { |
200 | + | ||
201 | + logEntityAction(emptyId(EntityType.DASHBOARD), null, | ||
202 | + null, | ||
203 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId); | ||
204 | + | ||
146 | throw handleException(e); | 205 | throw handleException(e); |
147 | } | 206 | } |
148 | } | 207 | } |
@@ -22,6 +22,10 @@ import org.springframework.web.bind.annotation.*; | @@ -22,6 +22,10 @@ import org.springframework.web.bind.annotation.*; | ||
22 | import org.thingsboard.server.common.data.Customer; | 22 | import org.thingsboard.server.common.data.Customer; |
23 | import org.thingsboard.server.common.data.Device; | 23 | import org.thingsboard.server.common.data.Device; |
24 | import org.thingsboard.server.common.data.EntitySubtype; | 24 | import org.thingsboard.server.common.data.EntitySubtype; |
25 | +import org.thingsboard.server.common.data.EntityType; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
27 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
28 | +import org.thingsboard.server.common.data.device.DeviceSearchQuery; | ||
25 | import org.thingsboard.server.common.data.id.CustomerId; | 29 | import org.thingsboard.server.common.data.id.CustomerId; |
26 | import org.thingsboard.server.common.data.id.DeviceId; | 30 | import org.thingsboard.server.common.data.id.DeviceId; |
27 | import org.thingsboard.server.common.data.id.TenantId; | 31 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -29,7 +33,6 @@ import org.thingsboard.server.common.data.page.TextPageData; | @@ -29,7 +33,6 @@ import org.thingsboard.server.common.data.page.TextPageData; | ||
29 | import org.thingsboard.server.common.data.page.TextPageLink; | 33 | import org.thingsboard.server.common.data.page.TextPageLink; |
30 | import org.thingsboard.server.common.data.security.Authority; | 34 | import org.thingsboard.server.common.data.security.Authority; |
31 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 35 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
32 | -import org.thingsboard.server.common.data.device.DeviceSearchQuery; | ||
33 | import org.thingsboard.server.dao.exception.IncorrectParameterException; | 36 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
34 | import org.thingsboard.server.dao.model.ModelConstants; | 37 | import org.thingsboard.server.dao.model.ModelConstants; |
35 | import org.thingsboard.server.exception.ThingsboardErrorCode; | 38 | import org.thingsboard.server.exception.ThingsboardErrorCode; |
@@ -75,14 +78,22 @@ public class DeviceController extends BaseController { | @@ -75,14 +78,22 @@ public class DeviceController extends BaseController { | ||
75 | } | 78 | } |
76 | } | 79 | } |
77 | Device savedDevice = checkNotNull(deviceService.saveDevice(device)); | 80 | Device savedDevice = checkNotNull(deviceService.saveDevice(device)); |
81 | + | ||
78 | actorService | 82 | actorService |
79 | .onDeviceNameOrTypeUpdate( | 83 | .onDeviceNameOrTypeUpdate( |
80 | savedDevice.getTenantId(), | 84 | savedDevice.getTenantId(), |
81 | savedDevice.getId(), | 85 | savedDevice.getId(), |
82 | savedDevice.getName(), | 86 | savedDevice.getName(), |
83 | savedDevice.getType()); | 87 | savedDevice.getType()); |
88 | + | ||
89 | + logEntityAction(savedDevice.getId(), savedDevice, | ||
90 | + savedDevice.getCustomerId(), | ||
91 | + device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | ||
92 | + | ||
84 | return savedDevice; | 93 | return savedDevice; |
85 | } catch (Exception e) { | 94 | } catch (Exception e) { |
95 | + logEntityAction(emptyId(EntityType.DEVICE), device, | ||
96 | + null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | ||
86 | throw handleException(e); | 97 | throw handleException(e); |
87 | } | 98 | } |
88 | } | 99 | } |
@@ -94,9 +105,18 @@ public class DeviceController extends BaseController { | @@ -94,9 +105,18 @@ public class DeviceController extends BaseController { | ||
94 | checkParameter(DEVICE_ID, strDeviceId); | 105 | checkParameter(DEVICE_ID, strDeviceId); |
95 | try { | 106 | try { |
96 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | 107 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
97 | - checkDeviceId(deviceId); | 108 | + Device device = checkDeviceId(deviceId); |
98 | deviceService.deleteDevice(deviceId); | 109 | deviceService.deleteDevice(deviceId); |
110 | + | ||
111 | + logEntityAction(deviceId, device, | ||
112 | + device.getCustomerId(), | ||
113 | + ActionType.DELETED, null, strDeviceId); | ||
114 | + | ||
99 | } catch (Exception e) { | 115 | } catch (Exception e) { |
116 | + logEntityAction(emptyId(EntityType.DEVICE), | ||
117 | + null, | ||
118 | + null, | ||
119 | + ActionType.DELETED, e, strDeviceId); | ||
100 | throw handleException(e); | 120 | throw handleException(e); |
101 | } | 121 | } |
102 | } | 122 | } |
@@ -110,13 +130,22 @@ public class DeviceController extends BaseController { | @@ -110,13 +130,22 @@ public class DeviceController extends BaseController { | ||
110 | checkParameter(DEVICE_ID, strDeviceId); | 130 | checkParameter(DEVICE_ID, strDeviceId); |
111 | try { | 131 | try { |
112 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | 132 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
113 | - checkCustomerId(customerId); | 133 | + Customer customer = checkCustomerId(customerId); |
114 | 134 | ||
115 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | 135 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
116 | checkDeviceId(deviceId); | 136 | checkDeviceId(deviceId); |
117 | 137 | ||
118 | - return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, customerId)); | 138 | + Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(deviceId, customerId)); |
139 | + | ||
140 | + logEntityAction(deviceId, savedDevice, | ||
141 | + savedDevice.getCustomerId(), | ||
142 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, strCustomerId, customer.getName()); | ||
143 | + | ||
144 | + return savedDevice; | ||
119 | } catch (Exception e) { | 145 | } catch (Exception e) { |
146 | + logEntityAction(emptyId(EntityType.DEVICE), null, | ||
147 | + null, | ||
148 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId, strCustomerId); | ||
120 | throw handleException(e); | 149 | throw handleException(e); |
121 | } | 150 | } |
122 | } | 151 | } |
@@ -132,8 +161,19 @@ public class DeviceController extends BaseController { | @@ -132,8 +161,19 @@ public class DeviceController extends BaseController { | ||
132 | if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { | 161 | if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { |
133 | throw new IncorrectParameterException("Device isn't assigned to any customer!"); | 162 | throw new IncorrectParameterException("Device isn't assigned to any customer!"); |
134 | } | 163 | } |
135 | - return checkNotNull(deviceService.unassignDeviceFromCustomer(deviceId)); | 164 | + Customer customer = checkCustomerId(device.getCustomerId()); |
165 | + | ||
166 | + Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(deviceId)); | ||
167 | + | ||
168 | + logEntityAction(deviceId, device, | ||
169 | + device.getCustomerId(), | ||
170 | + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDeviceId, customer.getId().toString(), customer.getName()); | ||
171 | + | ||
172 | + return savedDevice; | ||
136 | } catch (Exception e) { | 173 | } catch (Exception e) { |
174 | + logEntityAction(emptyId(EntityType.DEVICE), null, | ||
175 | + null, | ||
176 | + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDeviceId); | ||
137 | throw handleException(e); | 177 | throw handleException(e); |
138 | } | 178 | } |
139 | } | 179 | } |
@@ -147,8 +187,17 @@ public class DeviceController extends BaseController { | @@ -147,8 +187,17 @@ public class DeviceController extends BaseController { | ||
147 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | 187 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
148 | Device device = checkDeviceId(deviceId); | 188 | Device device = checkDeviceId(deviceId); |
149 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId()); | 189 | Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId()); |
150 | - return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId())); | 190 | + Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId())); |
191 | + | ||
192 | + logEntityAction(deviceId, savedDevice, | ||
193 | + savedDevice.getCustomerId(), | ||
194 | + ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, publicCustomer.getId().toString(), publicCustomer.getName()); | ||
195 | + | ||
196 | + return savedDevice; | ||
151 | } catch (Exception e) { | 197 | } catch (Exception e) { |
198 | + logEntityAction(emptyId(EntityType.DEVICE), null, | ||
199 | + null, | ||
200 | + ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId); | ||
152 | throw handleException(e); | 201 | throw handleException(e); |
153 | } | 202 | } |
154 | } | 203 | } |
@@ -160,9 +209,16 @@ public class DeviceController extends BaseController { | @@ -160,9 +209,16 @@ public class DeviceController extends BaseController { | ||
160 | checkParameter(DEVICE_ID, strDeviceId); | 209 | checkParameter(DEVICE_ID, strDeviceId); |
161 | try { | 210 | try { |
162 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | 211 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
163 | - checkDeviceId(deviceId); | ||
164 | - return checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId)); | 212 | + Device device = checkDeviceId(deviceId); |
213 | + DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(deviceId)); | ||
214 | + logEntityAction(deviceId, device, | ||
215 | + device.getCustomerId(), | ||
216 | + ActionType.CREDENTIALS_READ, null, strDeviceId); | ||
217 | + return deviceCredentials; | ||
165 | } catch (Exception e) { | 218 | } catch (Exception e) { |
219 | + logEntityAction(emptyId(EntityType.DEVICE), null, | ||
220 | + null, | ||
221 | + ActionType.CREDENTIALS_READ, e, strDeviceId); | ||
166 | throw handleException(e); | 222 | throw handleException(e); |
167 | } | 223 | } |
168 | } | 224 | } |
@@ -173,11 +229,17 @@ public class DeviceController extends BaseController { | @@ -173,11 +229,17 @@ public class DeviceController extends BaseController { | ||
173 | public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { | 229 | public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { |
174 | checkNotNull(deviceCredentials); | 230 | checkNotNull(deviceCredentials); |
175 | try { | 231 | try { |
176 | - checkDeviceId(deviceCredentials.getDeviceId()); | 232 | + Device device = checkDeviceId(deviceCredentials.getDeviceId()); |
177 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); | 233 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); |
178 | actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); | 234 | actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); |
235 | + logEntityAction(device.getId(), device, | ||
236 | + device.getCustomerId(), | ||
237 | + ActionType.CREDENTIALS_UPDATED, null, deviceCredentials); | ||
179 | return result; | 238 | return result; |
180 | } catch (Exception e) { | 239 | } catch (Exception e) { |
240 | + logEntityAction(emptyId(EntityType.DEVICE), null, | ||
241 | + null, | ||
242 | + ActionType.CREDENTIALS_UPDATED, e, deviceCredentials); | ||
181 | throw handleException(e); | 243 | throw handleException(e); |
182 | } | 244 | } |
183 | } | 245 | } |
@@ -306,5 +368,4 @@ public class DeviceController extends BaseController { | @@ -306,5 +368,4 @@ public class DeviceController extends BaseController { | ||
306 | throw handleException(e); | 368 | throw handleException(e); |
307 | } | 369 | } |
308 | } | 370 | } |
309 | - | ||
310 | } | 371 | } |
@@ -18,6 +18,8 @@ package org.thingsboard.server.controller; | @@ -18,6 +18,8 @@ package org.thingsboard.server.controller; | ||
18 | import org.springframework.http.HttpStatus; | 18 | import org.springframework.http.HttpStatus; |
19 | import org.springframework.security.access.prepost.PreAuthorize; | 19 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | import org.springframework.web.bind.annotation.*; | 20 | import org.springframework.web.bind.annotation.*; |
21 | +import org.thingsboard.server.common.data.EntityType; | ||
22 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
21 | import org.thingsboard.server.common.data.id.PluginId; | 23 | import org.thingsboard.server.common.data.id.PluginId; |
22 | import org.thingsboard.server.common.data.id.TenantId; | 24 | import org.thingsboard.server.common.data.id.TenantId; |
23 | import org.thingsboard.server.common.data.page.TextPageData; | 25 | import org.thingsboard.server.common.data.page.TextPageData; |
@@ -71,8 +73,17 @@ public class PluginController extends BaseController { | @@ -71,8 +73,17 @@ public class PluginController extends BaseController { | ||
71 | PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source)); | 73 | PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source)); |
72 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), | 74 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), |
73 | created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); | 75 | created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
76 | + | ||
77 | + logEntityAction(plugin.getId(), plugin, | ||
78 | + null, | ||
79 | + created ? ActionType.ADDED : ActionType.UPDATED, null); | ||
80 | + | ||
74 | return plugin; | 81 | return plugin; |
75 | } catch (Exception e) { | 82 | } catch (Exception e) { |
83 | + | ||
84 | + logEntityAction(emptyId(EntityType.PLUGIN), source, | ||
85 | + null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | ||
86 | + | ||
76 | throw handleException(e); | 87 | throw handleException(e); |
77 | } | 88 | } |
78 | } | 89 | } |
@@ -87,7 +98,18 @@ public class PluginController extends BaseController { | @@ -87,7 +98,18 @@ public class PluginController extends BaseController { | ||
87 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); | 98 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); |
88 | pluginService.activatePluginById(pluginId); | 99 | pluginService.activatePluginById(pluginId); |
89 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED); | 100 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED); |
101 | + | ||
102 | + logEntityAction(plugin.getId(), plugin, | ||
103 | + null, | ||
104 | + ActionType.ACTIVATED, null, strPluginId); | ||
105 | + | ||
90 | } catch (Exception e) { | 106 | } catch (Exception e) { |
107 | + | ||
108 | + logEntityAction(emptyId(EntityType.PLUGIN), | ||
109 | + null, | ||
110 | + null, | ||
111 | + ActionType.ACTIVATED, e, strPluginId); | ||
112 | + | ||
91 | throw handleException(e); | 113 | throw handleException(e); |
92 | } | 114 | } |
93 | } | 115 | } |
@@ -102,7 +124,18 @@ public class PluginController extends BaseController { | @@ -102,7 +124,18 @@ public class PluginController extends BaseController { | ||
102 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); | 124 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); |
103 | pluginService.suspendPluginById(pluginId); | 125 | pluginService.suspendPluginById(pluginId); |
104 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED); | 126 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED); |
127 | + | ||
128 | + logEntityAction(plugin.getId(), plugin, | ||
129 | + null, | ||
130 | + ActionType.SUSPENDED, null, strPluginId); | ||
131 | + | ||
105 | } catch (Exception e) { | 132 | } catch (Exception e) { |
133 | + | ||
134 | + logEntityAction(emptyId(EntityType.PLUGIN), | ||
135 | + null, | ||
136 | + null, | ||
137 | + ActionType.SUSPENDED, e, strPluginId); | ||
138 | + | ||
106 | throw handleException(e); | 139 | throw handleException(e); |
107 | } | 140 | } |
108 | } | 141 | } |
@@ -189,7 +222,16 @@ public class PluginController extends BaseController { | @@ -189,7 +222,16 @@ public class PluginController extends BaseController { | ||
189 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); | 222 | PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId)); |
190 | pluginService.deletePluginById(pluginId); | 223 | pluginService.deletePluginById(pluginId); |
191 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED); | 224 | actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED); |
225 | + | ||
226 | + logEntityAction(pluginId, plugin, | ||
227 | + null, | ||
228 | + ActionType.DELETED, null, strPluginId); | ||
229 | + | ||
192 | } catch (Exception e) { | 230 | } catch (Exception e) { |
231 | + logEntityAction(emptyId(EntityType.PLUGIN), | ||
232 | + null, | ||
233 | + null, | ||
234 | + ActionType.DELETED, e, strPluginId); | ||
193 | throw handleException(e); | 235 | throw handleException(e); |
194 | } | 236 | } |
195 | } | 237 | } |
@@ -18,6 +18,8 @@ package org.thingsboard.server.controller; | @@ -18,6 +18,8 @@ package org.thingsboard.server.controller; | ||
18 | import org.springframework.http.HttpStatus; | 18 | import org.springframework.http.HttpStatus; |
19 | import org.springframework.security.access.prepost.PreAuthorize; | 19 | import org.springframework.security.access.prepost.PreAuthorize; |
20 | import org.springframework.web.bind.annotation.*; | 20 | import org.springframework.web.bind.annotation.*; |
21 | +import org.thingsboard.server.common.data.EntityType; | ||
22 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
21 | import org.thingsboard.server.common.data.id.RuleId; | 23 | import org.thingsboard.server.common.data.id.RuleId; |
22 | import org.thingsboard.server.common.data.id.TenantId; | 24 | import org.thingsboard.server.common.data.id.TenantId; |
23 | import org.thingsboard.server.common.data.page.TextPageData; | 25 | import org.thingsboard.server.common.data.page.TextPageData; |
@@ -73,8 +75,17 @@ public class RuleController extends BaseController { | @@ -73,8 +75,17 @@ public class RuleController extends BaseController { | ||
73 | RuleMetaData rule = checkNotNull(ruleService.saveRule(source)); | 75 | RuleMetaData rule = checkNotNull(ruleService.saveRule(source)); |
74 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), | 76 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), |
75 | created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); | 77 | created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); |
78 | + | ||
79 | + logEntityAction(rule.getId(), rule, | ||
80 | + null, | ||
81 | + created ? ActionType.ADDED : ActionType.UPDATED, null); | ||
82 | + | ||
76 | return rule; | 83 | return rule; |
77 | } catch (Exception e) { | 84 | } catch (Exception e) { |
85 | + | ||
86 | + logEntityAction(emptyId(EntityType.RULE), source, | ||
87 | + null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | ||
88 | + | ||
78 | throw handleException(e); | 89 | throw handleException(e); |
79 | } | 90 | } |
80 | } | 91 | } |
@@ -89,7 +100,18 @@ public class RuleController extends BaseController { | @@ -89,7 +100,18 @@ public class RuleController extends BaseController { | ||
89 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); | 100 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); |
90 | ruleService.activateRuleById(ruleId); | 101 | ruleService.activateRuleById(ruleId); |
91 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED); | 102 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED); |
103 | + | ||
104 | + logEntityAction(rule.getId(), rule, | ||
105 | + null, | ||
106 | + ActionType.ACTIVATED, null, strRuleId); | ||
107 | + | ||
92 | } catch (Exception e) { | 108 | } catch (Exception e) { |
109 | + | ||
110 | + logEntityAction(emptyId(EntityType.RULE), | ||
111 | + null, | ||
112 | + null, | ||
113 | + ActionType.ACTIVATED, e, strRuleId); | ||
114 | + | ||
93 | throw handleException(e); | 115 | throw handleException(e); |
94 | } | 116 | } |
95 | } | 117 | } |
@@ -104,7 +126,18 @@ public class RuleController extends BaseController { | @@ -104,7 +126,18 @@ public class RuleController extends BaseController { | ||
104 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); | 126 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); |
105 | ruleService.suspendRuleById(ruleId); | 127 | ruleService.suspendRuleById(ruleId); |
106 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED); | 128 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED); |
129 | + | ||
130 | + logEntityAction(rule.getId(), rule, | ||
131 | + null, | ||
132 | + ActionType.SUSPENDED, null, strRuleId); | ||
133 | + | ||
107 | } catch (Exception e) { | 134 | } catch (Exception e) { |
135 | + | ||
136 | + logEntityAction(emptyId(EntityType.RULE), | ||
137 | + null, | ||
138 | + null, | ||
139 | + ActionType.SUSPENDED, e, strRuleId); | ||
140 | + | ||
108 | throw handleException(e); | 141 | throw handleException(e); |
109 | } | 142 | } |
110 | } | 143 | } |
@@ -187,7 +220,18 @@ public class RuleController extends BaseController { | @@ -187,7 +220,18 @@ public class RuleController extends BaseController { | ||
187 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); | 220 | RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId)); |
188 | ruleService.deleteRuleById(ruleId); | 221 | ruleService.deleteRuleById(ruleId); |
189 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED); | 222 | actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED); |
223 | + | ||
224 | + logEntityAction(ruleId, rule, | ||
225 | + null, | ||
226 | + ActionType.DELETED, null, strRuleId); | ||
227 | + | ||
190 | } catch (Exception e) { | 228 | } catch (Exception e) { |
229 | + | ||
230 | + logEntityAction(emptyId(EntityType.RULE), | ||
231 | + null, | ||
232 | + null, | ||
233 | + ActionType.DELETED, e, strRuleId); | ||
234 | + | ||
191 | throw handleException(e); | 235 | throw handleException(e); |
192 | } | 236 | } |
193 | } | 237 | } |
@@ -19,7 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired; | @@ -19,7 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired; | ||
19 | import org.springframework.http.HttpStatus; | 19 | import org.springframework.http.HttpStatus; |
20 | import org.springframework.security.access.prepost.PreAuthorize; | 20 | import org.springframework.security.access.prepost.PreAuthorize; |
21 | import org.springframework.web.bind.annotation.*; | 21 | import org.springframework.web.bind.annotation.*; |
22 | +import org.thingsboard.server.common.data.EntityType; | ||
22 | import org.thingsboard.server.common.data.User; | 23 | import org.thingsboard.server.common.data.User; |
24 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
23 | import org.thingsboard.server.common.data.id.CustomerId; | 25 | import org.thingsboard.server.common.data.id.CustomerId; |
24 | import org.thingsboard.server.common.data.id.TenantId; | 26 | import org.thingsboard.server.common.data.id.TenantId; |
25 | import org.thingsboard.server.common.data.id.UserId; | 27 | import org.thingsboard.server.common.data.id.UserId; |
@@ -92,8 +94,17 @@ public class UserController extends BaseController { | @@ -92,8 +94,17 @@ public class UserController extends BaseController { | ||
92 | throw e; | 94 | throw e; |
93 | } | 95 | } |
94 | } | 96 | } |
97 | + | ||
98 | + logEntityAction(savedUser.getId(), savedUser, | ||
99 | + savedUser.getCustomerId(), | ||
100 | + user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); | ||
101 | + | ||
95 | return savedUser; | 102 | return savedUser; |
96 | } catch (Exception e) { | 103 | } catch (Exception e) { |
104 | + | ||
105 | + logEntityAction(emptyId(EntityType.USER), user, | ||
106 | + null, user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); | ||
107 | + | ||
97 | throw handleException(e); | 108 | throw handleException(e); |
98 | } | 109 | } |
99 | } | 110 | } |
@@ -156,9 +167,18 @@ public class UserController extends BaseController { | @@ -156,9 +167,18 @@ public class UserController extends BaseController { | ||
156 | checkParameter(USER_ID, strUserId); | 167 | checkParameter(USER_ID, strUserId); |
157 | try { | 168 | try { |
158 | UserId userId = new UserId(toUUID(strUserId)); | 169 | UserId userId = new UserId(toUUID(strUserId)); |
159 | - checkUserId(userId); | 170 | + User user = checkUserId(userId); |
160 | userService.deleteUser(userId); | 171 | userService.deleteUser(userId); |
172 | + | ||
173 | + logEntityAction(userId, user, | ||
174 | + user.getCustomerId(), | ||
175 | + ActionType.DELETED, null, strUserId); | ||
176 | + | ||
161 | } catch (Exception e) { | 177 | } catch (Exception e) { |
178 | + logEntityAction(emptyId(EntityType.USER), | ||
179 | + null, | ||
180 | + null, | ||
181 | + ActionType.DELETED, e, strUserId); | ||
162 | throw handleException(e); | 182 | throw handleException(e); |
163 | } | 183 | } |
164 | } | 184 | } |
@@ -30,6 +30,7 @@ import org.springframework.web.context.request.async.DeferredResult; | @@ -30,6 +30,7 @@ import org.springframework.web.context.request.async.DeferredResult; | ||
30 | import org.thingsboard.server.actors.service.ActorService; | 30 | import org.thingsboard.server.actors.service.ActorService; |
31 | import org.thingsboard.server.common.data.id.CustomerId; | 31 | import org.thingsboard.server.common.data.id.CustomerId; |
32 | import org.thingsboard.server.common.data.id.TenantId; | 32 | import org.thingsboard.server.common.data.id.TenantId; |
33 | +import org.thingsboard.server.common.data.id.UserId; | ||
33 | import org.thingsboard.server.common.data.plugin.PluginMetaData; | 34 | import org.thingsboard.server.common.data.plugin.PluginMetaData; |
34 | import org.thingsboard.server.controller.BaseController; | 35 | import org.thingsboard.server.controller.BaseController; |
35 | import org.thingsboard.server.dao.model.ModelConstants; | 36 | import org.thingsboard.server.dao.model.ModelConstants; |
@@ -68,7 +69,10 @@ public class PluginApiController extends BaseController { | @@ -68,7 +69,10 @@ public class PluginApiController extends BaseController { | ||
68 | if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){ | 69 | if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){ |
69 | tenantId = null; | 70 | tenantId = null; |
70 | } | 71 | } |
71 | - PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId, customerId); | 72 | + UserId userId = getCurrentUser().getId(); |
73 | + String userName = getCurrentUser().getName(); | ||
74 | + PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), | ||
75 | + tenantId, customerId, userId, userName); | ||
72 | actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result)); | 76 | actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result)); |
73 | } else { | 77 | } else { |
74 | result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN)); | 78 | result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN)); |
@@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy; | @@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy; | ||
28 | import org.springframework.web.bind.annotation.RequestMapping; | 28 | import org.springframework.web.bind.annotation.RequestMapping; |
29 | import org.springframework.web.bind.annotation.RestController; | 29 | import org.springframework.web.bind.annotation.RestController; |
30 | import org.thingsboard.server.actors.service.ActorService; | 30 | import org.thingsboard.server.actors.service.ActorService; |
31 | +import org.thingsboard.server.common.data.id.UserId; | ||
31 | import org.thingsboard.server.config.WebSocketConfiguration; | 32 | import org.thingsboard.server.config.WebSocketConfiguration; |
32 | import org.thingsboard.server.extensions.api.plugins.PluginConstants; | 33 | import org.thingsboard.server.extensions.api.plugins.PluginConstants; |
33 | import org.thingsboard.server.service.security.model.SecurityUser; | 34 | import org.thingsboard.server.service.security.model.SecurityUser; |
@@ -151,8 +152,10 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug | @@ -151,8 +152,10 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug | ||
151 | TenantId tenantId = currentUser.getTenantId(); | 152 | TenantId tenantId = currentUser.getTenantId(); |
152 | CustomerId customerId = currentUser.getCustomerId(); | 153 | CustomerId customerId = currentUser.getCustomerId(); |
153 | if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) { | 154 | if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) { |
155 | + UserId userId = currentUser.getId(); | ||
156 | + String userName = currentUser.getName(); | ||
154 | PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId, | 157 | PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId, |
155 | - currentUser.getCustomerId()); | 158 | + currentUser.getCustomerId(), userId, userName); |
156 | return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(), | 159 | return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(), |
157 | session.getLocalAddress(), session.getRemoteAddress()); | 160 | session.getLocalAddress(), session.getRemoteAddress()); |
158 | } else { | 161 | } else { |
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallConfiguration.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +package org.thingsboard.server.install; | ||
18 | + | ||
19 | +import org.springframework.context.annotation.Bean; | ||
20 | +import org.springframework.context.annotation.Configuration; | ||
21 | +import org.springframework.context.annotation.Profile; | ||
22 | +import org.thingsboard.server.dao.audit.AuditLogLevelFilter; | ||
23 | + | ||
24 | +import java.util.HashMap; | ||
25 | + | ||
26 | +@Configuration | ||
27 | +@Profile("install") | ||
28 | +public class ThingsboardInstallConfiguration { | ||
29 | + | ||
30 | + @Bean | ||
31 | + public AuditLogLevelFilter emptyAuditLogLevelFilter() { | ||
32 | + return new AuditLogLevelFilter(new HashMap<>()); | ||
33 | + } | ||
34 | +} |
@@ -81,6 +81,8 @@ public class ThingsboardInstallService { | @@ -81,6 +81,8 @@ public class ThingsboardInstallService { | ||
81 | case "1.3.1": | 81 | case "1.3.1": |
82 | log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."); | 82 | log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."); |
83 | 83 | ||
84 | + databaseUpgradeService.upgradeDatabase("1.3.1"); | ||
85 | + | ||
84 | log.info("Updating system data..."); | 86 | log.info("Updating system data..."); |
85 | 87 | ||
86 | systemDataLoaderService.deleteSystemWidgetBundle("charts"); | 88 | systemDataLoaderService.deleteSystemWidgetBundle("charts"); |
@@ -159,6 +159,12 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { | @@ -159,6 +159,12 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { | ||
159 | break; | 159 | break; |
160 | case "1.3.0": | 160 | case "1.3.0": |
161 | break; | 161 | break; |
162 | + case "1.3.1": | ||
163 | + log.info("Updating schema ..."); | ||
164 | + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL); | ||
165 | + loadCql(schemaUpdateFile); | ||
166 | + log.info("Schema updated."); | ||
167 | + break; | ||
162 | default: | 168 | default: |
163 | throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); | 169 | throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); |
164 | } | 170 | } |
@@ -61,6 +61,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { | @@ -61,6 +61,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { | ||
61 | } | 61 | } |
62 | log.info("Schema updated."); | 62 | log.info("Schema updated."); |
63 | break; | 63 | break; |
64 | + case "1.3.1": | ||
65 | + log.info("Updating schema ..."); | ||
66 | + schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL); | ||
67 | + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | ||
68 | + String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); | ||
69 | + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script | ||
70 | + } | ||
71 | + log.info("Schema updated."); | ||
72 | + break; | ||
64 | default: | 73 | default: |
65 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); | 74 | throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); |
66 | } | 75 | } |
@@ -282,7 +282,6 @@ spring: | @@ -282,7 +282,6 @@ spring: | ||
282 | username: "${SPRING_DATASOURCE_USERNAME:sa}" | 282 | username: "${SPRING_DATASOURCE_USERNAME:sa}" |
283 | password: "${SPRING_DATASOURCE_PASSWORD:}" | 283 | password: "${SPRING_DATASOURCE_PASSWORD:}" |
284 | 284 | ||
285 | - | ||
286 | # PostgreSQL DAO Configuration | 285 | # PostgreSQL DAO Configuration |
287 | #spring: | 286 | #spring: |
288 | # data: | 287 | # data: |
@@ -298,3 +297,23 @@ spring: | @@ -298,3 +297,23 @@ spring: | ||
298 | # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" | 297 | # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" |
299 | # username: "${SPRING_DATASOURCE_USERNAME:postgres}" | 298 | # username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
300 | # password: "${SPRING_DATASOURCE_PASSWORD:postgres}" | 299 | # password: "${SPRING_DATASOURCE_PASSWORD:postgres}" |
300 | + | ||
301 | +# Audit log parameters | ||
302 | +audit_log: | ||
303 | + # Enable/disable audit log functionality. | ||
304 | + enabled: "${AUDIT_LOG_ENABLED:true}" | ||
305 | + # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS | ||
306 | + by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}" | ||
307 | + # Number of days as history period if startTime and endTime are not specified | ||
308 | + default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" | ||
309 | + # Logging levels per each entity type. | ||
310 | + # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations) | ||
311 | + logging_level: | ||
312 | + mask: | ||
313 | + "device": "${AUDIT_LOG_MASK_DEVICE:W}" | ||
314 | + "asset": "${AUDIT_LOG_MASK_ASSET:W}" | ||
315 | + "dashboard": "${AUDIT_LOG_MASK_DASHBOARD:W}" | ||
316 | + "customer": "${AUDIT_LOG_MASK_CUSTOMER:W}" | ||
317 | + "user": "${AUDIT_LOG_MASK_USER:W}" | ||
318 | + "rule": "${AUDIT_LOG_MASK_RULE:W}" | ||
319 | + "plugin": "${AUDIT_LOG_MASK_PLUGIN:W}" |
@@ -66,6 +66,7 @@ import org.thingsboard.server.common.data.User; | @@ -66,6 +66,7 @@ import org.thingsboard.server.common.data.User; | ||
66 | import org.thingsboard.server.common.data.id.TenantId; | 66 | import org.thingsboard.server.common.data.id.TenantId; |
67 | import org.thingsboard.server.common.data.id.UUIDBased; | 67 | import org.thingsboard.server.common.data.id.UUIDBased; |
68 | import org.thingsboard.server.common.data.page.TextPageLink; | 68 | import org.thingsboard.server.common.data.page.TextPageLink; |
69 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
69 | import org.thingsboard.server.common.data.security.Authority; | 70 | import org.thingsboard.server.common.data.security.Authority; |
70 | import org.thingsboard.server.config.ThingsboardSecurityConfiguration; | 71 | import org.thingsboard.server.config.ThingsboardSecurityConfiguration; |
71 | import org.thingsboard.server.service.mail.TestMailService; | 72 | import org.thingsboard.server.service.mail.TestMailService; |
@@ -336,6 +337,35 @@ public abstract class AbstractControllerTest { | @@ -336,6 +337,35 @@ public abstract class AbstractControllerTest { | ||
336 | return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); | 337 | return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); |
337 | } | 338 | } |
338 | 339 | ||
340 | + protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType, | ||
341 | + TimePageLink pageLink, | ||
342 | + Object... urlVariables) throws Exception { | ||
343 | + List<Object> pageLinkVariables = new ArrayList<>(); | ||
344 | + urlTemplate += "limit={limit}"; | ||
345 | + pageLinkVariables.add(pageLink.getLimit()); | ||
346 | + if (pageLink.getStartTime() != null) { | ||
347 | + urlTemplate += "&startTime={startTime}"; | ||
348 | + pageLinkVariables.add(pageLink.getStartTime()); | ||
349 | + } | ||
350 | + if (pageLink.getEndTime() != null) { | ||
351 | + urlTemplate += "&endTime={endTime}"; | ||
352 | + pageLinkVariables.add(pageLink.getEndTime()); | ||
353 | + } | ||
354 | + if (pageLink.getIdOffset() != null) { | ||
355 | + urlTemplate += "&offset={offset}"; | ||
356 | + pageLinkVariables.add(pageLink.getIdOffset().toString()); | ||
357 | + } | ||
358 | + if (pageLink.isAscOrder()) { | ||
359 | + urlTemplate += "&ascOrder={ascOrder}"; | ||
360 | + pageLinkVariables.add(pageLink.isAscOrder()); | ||
361 | + } | ||
362 | + Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; | ||
363 | + System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); | ||
364 | + System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size()); | ||
365 | + | ||
366 | + return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); | ||
367 | + } | ||
368 | + | ||
339 | protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception { | 369 | protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception { |
340 | return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); | 370 | return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); |
341 | } | 371 | } |
application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.core.type.TypeReference; | ||
19 | +import org.junit.After; | ||
20 | +import org.junit.Assert; | ||
21 | +import org.junit.Before; | ||
22 | +import org.junit.Test; | ||
23 | +import org.thingsboard.server.common.data.Device; | ||
24 | +import org.thingsboard.server.common.data.Tenant; | ||
25 | +import org.thingsboard.server.common.data.User; | ||
26 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
27 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
28 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
29 | +import org.thingsboard.server.common.data.security.Authority; | ||
30 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
31 | + | ||
32 | +import java.util.ArrayList; | ||
33 | +import java.util.List; | ||
34 | + | ||
35 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
36 | + | ||
37 | +public abstract class BaseAuditLogControllerTest extends AbstractControllerTest { | ||
38 | + | ||
39 | + private Tenant savedTenant; | ||
40 | + private User tenantAdmin; | ||
41 | + | ||
42 | + @Before | ||
43 | + public void beforeTest() throws Exception { | ||
44 | + loginSysAdmin(); | ||
45 | + | ||
46 | + Tenant tenant = new Tenant(); | ||
47 | + tenant.setTitle("My tenant"); | ||
48 | + savedTenant = doPost("/api/tenant", tenant, Tenant.class); | ||
49 | + Assert.assertNotNull(savedTenant); | ||
50 | + | ||
51 | + tenantAdmin = new User(); | ||
52 | + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); | ||
53 | + tenantAdmin.setTenantId(savedTenant.getId()); | ||
54 | + tenantAdmin.setEmail("tenant2@thingsboard.org"); | ||
55 | + tenantAdmin.setFirstName("Joe"); | ||
56 | + tenantAdmin.setLastName("Downs"); | ||
57 | + | ||
58 | + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); | ||
59 | + } | ||
60 | + | ||
61 | + @After | ||
62 | + public void afterTest() throws Exception { | ||
63 | + loginSysAdmin(); | ||
64 | + | ||
65 | + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) | ||
66 | + .andExpect(status().isOk()); | ||
67 | + } | ||
68 | + | ||
69 | + @Test | ||
70 | + public void testAuditLogs() throws Exception { | ||
71 | + for (int i = 0; i < 178; i++) { | ||
72 | + Device device = new Device(); | ||
73 | + device.setName("Device" + i); | ||
74 | + device.setType("default"); | ||
75 | + doPost("/api/device", device, Device.class); | ||
76 | + } | ||
77 | + | ||
78 | + List<AuditLog> loadedAuditLogs = new ArrayList<>(); | ||
79 | + TimePageLink pageLink = new TimePageLink(23); | ||
80 | + TimePageData<AuditLog> pageData; | ||
81 | + do { | ||
82 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs?", | ||
83 | + new TypeReference<TimePageData<AuditLog>>() { | ||
84 | + }, pageLink); | ||
85 | + loadedAuditLogs.addAll(pageData.getData()); | ||
86 | + if (pageData.hasNext()) { | ||
87 | + pageLink = pageData.getNextPageLink(); | ||
88 | + } | ||
89 | + } while (pageData.hasNext()); | ||
90 | + | ||
91 | + Assert.assertEquals(178, loadedAuditLogs.size()); | ||
92 | + | ||
93 | + loadedAuditLogs = new ArrayList<>(); | ||
94 | + pageLink = new TimePageLink(23); | ||
95 | + do { | ||
96 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs/customer/" + ModelConstants.NULL_UUID + "?", | ||
97 | + new TypeReference<TimePageData<AuditLog>>() { | ||
98 | + }, pageLink); | ||
99 | + loadedAuditLogs.addAll(pageData.getData()); | ||
100 | + if (pageData.hasNext()) { | ||
101 | + pageLink = pageData.getNextPageLink(); | ||
102 | + } | ||
103 | + } while (pageData.hasNext()); | ||
104 | + | ||
105 | + Assert.assertEquals(178, loadedAuditLogs.size()); | ||
106 | + | ||
107 | + loadedAuditLogs = new ArrayList<>(); | ||
108 | + pageLink = new TimePageLink(23); | ||
109 | + do { | ||
110 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs/user/" + tenantAdmin.getId().getId().toString() + "?", | ||
111 | + new TypeReference<TimePageData<AuditLog>>() { | ||
112 | + }, pageLink); | ||
113 | + loadedAuditLogs.addAll(pageData.getData()); | ||
114 | + if (pageData.hasNext()) { | ||
115 | + pageLink = pageData.getNextPageLink(); | ||
116 | + } | ||
117 | + } while (pageData.hasNext()); | ||
118 | + | ||
119 | + Assert.assertEquals(178, loadedAuditLogs.size()); | ||
120 | + } | ||
121 | + | ||
122 | + @Test | ||
123 | + public void testAuditLogs_byTenantIdAndEntityId() throws Exception { | ||
124 | + Device device = new Device(); | ||
125 | + device.setName("Device name"); | ||
126 | + device.setType("default"); | ||
127 | + Device savedDevice = doPost("/api/device", device, Device.class); | ||
128 | + for (int i = 0; i < 178; i++) { | ||
129 | + savedDevice.setName("Device name" + i); | ||
130 | + doPost("/api/device", savedDevice, Device.class); | ||
131 | + } | ||
132 | + | ||
133 | + List<AuditLog> loadedAuditLogs = new ArrayList<>(); | ||
134 | + TimePageLink pageLink = new TimePageLink(23); | ||
135 | + TimePageData<AuditLog> pageData; | ||
136 | + do { | ||
137 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs/entity/DEVICE/" + savedDevice.getId().getId() + "?", | ||
138 | + new TypeReference<TimePageData<AuditLog>>() { | ||
139 | + }, pageLink); | ||
140 | + loadedAuditLogs.addAll(pageData.getData()); | ||
141 | + if (pageData.hasNext()) { | ||
142 | + pageLink = pageData.getNextPageLink(); | ||
143 | + } | ||
144 | + } while (pageData.hasNext()); | ||
145 | + | ||
146 | + Assert.assertEquals(179, loadedAuditLogs.size()); | ||
147 | + } | ||
148 | +} |
application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller.nosql; | ||
17 | + | ||
18 | +import org.thingsboard.server.controller.BaseAuditLogControllerTest; | ||
19 | +import org.thingsboard.server.dao.service.DaoNoSqlTest; | ||
20 | + | ||
21 | +@DaoNoSqlTest | ||
22 | +public class AuditLogControllerNoSqlTest extends BaseAuditLogControllerTest { | ||
23 | +} |
application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller.sql; | ||
17 | + | ||
18 | +import org.thingsboard.server.controller.BaseAuditLogControllerTest; | ||
19 | +import org.thingsboard.server.dao.service.DaoSqlTest; | ||
20 | + | ||
21 | +@DaoSqlTest | ||
22 | +public class AuditLogControllerSqlTest extends BaseAuditLogControllerTest { | ||
23 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.audit; | ||
17 | + | ||
18 | +public enum ActionStatus { | ||
19 | + SUCCESS, FAILURE | ||
20 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.audit; | ||
17 | + | ||
18 | +import lombok.Getter; | ||
19 | + | ||
20 | +@Getter | ||
21 | +public enum ActionType { | ||
22 | + ADDED(false), // log entity | ||
23 | + DELETED(false), // log string id | ||
24 | + UPDATED(false), // log entity | ||
25 | + ATTRIBUTES_UPDATED(false), // log attributes/values | ||
26 | + ATTRIBUTES_DELETED(false), // log attributes | ||
27 | + RPC_CALL(false), // log method and params | ||
28 | + CREDENTIALS_UPDATED(false), // log new credentials | ||
29 | + ASSIGNED_TO_CUSTOMER(false), // log customer name | ||
30 | + UNASSIGNED_FROM_CUSTOMER(false), // log customer name | ||
31 | + ACTIVATED(false), // log string id | ||
32 | + SUSPENDED(false), // log string id | ||
33 | + CREDENTIALS_READ(true), // log device id | ||
34 | + ATTRIBUTES_READ(true); // log attributes | ||
35 | + | ||
36 | + private final boolean isRead; | ||
37 | + | ||
38 | + ActionType(boolean isRead) { | ||
39 | + this.isRead = isRead; | ||
40 | + } | ||
41 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.audit; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import lombok.Data; | ||
20 | +import lombok.EqualsAndHashCode; | ||
21 | +import org.thingsboard.server.common.data.BaseData; | ||
22 | +import org.thingsboard.server.common.data.id.*; | ||
23 | + | ||
24 | +@EqualsAndHashCode(callSuper = true) | ||
25 | +@Data | ||
26 | +public class AuditLog extends BaseData<AuditLogId> { | ||
27 | + | ||
28 | + private TenantId tenantId; | ||
29 | + private CustomerId customerId; | ||
30 | + private EntityId entityId; | ||
31 | + private String entityName; | ||
32 | + private UserId userId; | ||
33 | + private String userName; | ||
34 | + private ActionType actionType; | ||
35 | + private JsonNode actionData; | ||
36 | + private ActionStatus actionStatus; | ||
37 | + private String actionFailureDetails; | ||
38 | + | ||
39 | + public AuditLog() { | ||
40 | + super(); | ||
41 | + } | ||
42 | + | ||
43 | + public AuditLog(AuditLogId id) { | ||
44 | + super(id); | ||
45 | + } | ||
46 | + | ||
47 | + public AuditLog(AuditLog auditLog) { | ||
48 | + super(auditLog); | ||
49 | + this.tenantId = auditLog.getTenantId(); | ||
50 | + this.customerId = auditLog.getCustomerId(); | ||
51 | + this.entityId = auditLog.getEntityId(); | ||
52 | + this.entityName = auditLog.getEntityName(); | ||
53 | + this.userId = auditLog.getUserId(); | ||
54 | + this.userName = auditLog.getUserName(); | ||
55 | + this.actionType = auditLog.getActionType(); | ||
56 | + this.actionData = auditLog.getActionData(); | ||
57 | + this.actionStatus = auditLog.getActionStatus(); | ||
58 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | ||
59 | + } | ||
60 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.id; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.annotation.JsonCreator; | ||
19 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
20 | + | ||
21 | +import java.util.UUID; | ||
22 | + | ||
23 | +public class AuditLogId extends UUIDBased { | ||
24 | + | ||
25 | + private static final long serialVersionUID = 1L; | ||
26 | + | ||
27 | + @JsonCreator | ||
28 | + public AuditLogId(@JsonProperty("id") UUID id) { | ||
29 | + super(id); | ||
30 | + } | ||
31 | + | ||
32 | + public static AuditLogId fromString(String auditLogId) { | ||
33 | + return new AuditLogId(UUID.fromString(auditLogId)); | ||
34 | + } | ||
35 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.google.common.util.concurrent.ListenableFuture; | ||
19 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
20 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
21 | +import org.thingsboard.server.common.data.id.EntityId; | ||
22 | +import org.thingsboard.server.common.data.id.UserId; | ||
23 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
24 | + | ||
25 | +import java.util.List; | ||
26 | +import java.util.UUID; | ||
27 | + | ||
28 | +public interface AuditLogDao { | ||
29 | + | ||
30 | + ListenableFuture<Void> saveByTenantId(AuditLog auditLog); | ||
31 | + | ||
32 | + ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog); | ||
33 | + | ||
34 | + ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog); | ||
35 | + | ||
36 | + ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog); | ||
37 | + | ||
38 | + ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog); | ||
39 | + | ||
40 | + List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); | ||
41 | + | ||
42 | + List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink); | ||
43 | + | ||
44 | + List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink); | ||
45 | + | ||
46 | + List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); | ||
47 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import org.thingsboard.server.common.data.EntityType; | ||
19 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
20 | + | ||
21 | +import java.util.HashMap; | ||
22 | +import java.util.Map; | ||
23 | + | ||
24 | +public class AuditLogLevelFilter { | ||
25 | + | ||
26 | + private Map<EntityType, AuditLogLevelMask> entityTypeMask = new HashMap<>(); | ||
27 | + | ||
28 | + public AuditLogLevelFilter(Map<String, String> mask) { | ||
29 | + entityTypeMask.clear(); | ||
30 | + mask.forEach((entityTypeStr, logLevelMaskStr) -> { | ||
31 | + EntityType entityType = EntityType.valueOf(entityTypeStr.toUpperCase()); | ||
32 | + AuditLogLevelMask logLevelMask = AuditLogLevelMask.valueOf(logLevelMaskStr.toUpperCase()); | ||
33 | + entityTypeMask.put(entityType, logLevelMask); | ||
34 | + }); | ||
35 | + } | ||
36 | + | ||
37 | + public boolean logEnabled(EntityType entityType, ActionType actionType) { | ||
38 | + AuditLogLevelMask logLevelMask = entityTypeMask.get(entityType); | ||
39 | + if (logLevelMask != null) { | ||
40 | + return actionType.isRead() ? logLevelMask.isRead() : logLevelMask.isWrite(); | ||
41 | + } else { | ||
42 | + return false; | ||
43 | + } | ||
44 | + } | ||
45 | + | ||
46 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import lombok.Getter; | ||
19 | + | ||
20 | +@Getter | ||
21 | +public enum AuditLogLevelMask { | ||
22 | + | ||
23 | + OFF(false, false), | ||
24 | + W(true, false), | ||
25 | + RW(true, true); | ||
26 | + | ||
27 | + private final boolean write; | ||
28 | + private final boolean read; | ||
29 | + | ||
30 | + AuditLogLevelMask(boolean write, boolean read) { | ||
31 | + this.write = write; | ||
32 | + this.read = read; | ||
33 | + } | ||
34 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import lombok.Getter; | ||
19 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
20 | +import org.thingsboard.server.dao.model.nosql.AuditLogEntity; | ||
21 | + | ||
22 | +import java.util.ArrayList; | ||
23 | +import java.util.List; | ||
24 | +import java.util.UUID; | ||
25 | + | ||
26 | +public class AuditLogQueryCursor { | ||
27 | + @Getter | ||
28 | + private final UUID tenantId; | ||
29 | + @Getter | ||
30 | + private final List<AuditLogEntity> data; | ||
31 | + @Getter | ||
32 | + private final TimePageLink pageLink; | ||
33 | + | ||
34 | + private final List<Long> partitions; | ||
35 | + | ||
36 | + private int partitionIndex; | ||
37 | + private int currentLimit; | ||
38 | + | ||
39 | + public AuditLogQueryCursor(UUID tenantId, TimePageLink pageLink, List<Long> partitions) { | ||
40 | + this.tenantId = tenantId; | ||
41 | + this.partitions = partitions; | ||
42 | + this.partitionIndex = partitions.size() - 1; | ||
43 | + this.data = new ArrayList<>(); | ||
44 | + this.currentLimit = pageLink.getLimit(); | ||
45 | + this.pageLink = pageLink; | ||
46 | + } | ||
47 | + | ||
48 | + public boolean hasNextPartition() { | ||
49 | + return partitionIndex >= 0; | ||
50 | + } | ||
51 | + | ||
52 | + public boolean isFull() { | ||
53 | + return currentLimit <= 0; | ||
54 | + } | ||
55 | + | ||
56 | + public long getNextPartition() { | ||
57 | + long partition = partitions.get(partitionIndex); | ||
58 | + partitionIndex--; | ||
59 | + return partition; | ||
60 | + } | ||
61 | + | ||
62 | + public int getCurrentLimit() { | ||
63 | + return currentLimit; | ||
64 | + } | ||
65 | + | ||
66 | + public void addData(List<AuditLogEntity> newData) { | ||
67 | + currentLimit -= newData.size(); | ||
68 | + data.addAll(newData); | ||
69 | + } | ||
70 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.google.common.util.concurrent.ListenableFuture; | ||
20 | +import org.thingsboard.server.common.data.BaseData; | ||
21 | +import org.thingsboard.server.common.data.HasName; | ||
22 | +import org.thingsboard.server.common.data.User; | ||
23 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
24 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
25 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
26 | +import org.thingsboard.server.common.data.id.*; | ||
27 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
28 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
29 | + | ||
30 | +import java.util.List; | ||
31 | + | ||
32 | +public interface AuditLogService { | ||
33 | + | ||
34 | + TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); | ||
35 | + | ||
36 | + TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink); | ||
37 | + | ||
38 | + TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); | ||
39 | + | ||
40 | + TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); | ||
41 | + | ||
42 | + <E extends BaseData<I> & HasName, | ||
43 | + I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction( | ||
44 | + TenantId tenantId, | ||
45 | + CustomerId customerId, | ||
46 | + UserId userId, | ||
47 | + String userName, | ||
48 | + I entityId, | ||
49 | + E entity, | ||
50 | + ActionType actionType, | ||
51 | + Exception e, Object... additionalInfo); | ||
52 | + | ||
53 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.datastax.driver.core.utils.UUIDs; | ||
19 | +import com.fasterxml.jackson.databind.JsonNode; | ||
20 | +import com.fasterxml.jackson.databind.ObjectMapper; | ||
21 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
22 | +import com.fasterxml.jackson.databind.node.ObjectNode; | ||
23 | +import com.google.common.collect.Lists; | ||
24 | +import com.google.common.util.concurrent.Futures; | ||
25 | +import com.google.common.util.concurrent.ListenableFuture; | ||
26 | +import lombok.extern.slf4j.Slf4j; | ||
27 | +import org.springframework.beans.factory.annotation.Autowired; | ||
28 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
29 | +import org.springframework.stereotype.Service; | ||
30 | +import org.springframework.util.StringUtils; | ||
31 | +import org.thingsboard.server.common.data.BaseData; | ||
32 | +import org.thingsboard.server.common.data.EntityType; | ||
33 | +import org.thingsboard.server.common.data.HasName; | ||
34 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
35 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
36 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
37 | +import org.thingsboard.server.common.data.id.*; | ||
38 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
39 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
40 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
41 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
42 | +import org.thingsboard.server.dao.entity.EntityService; | ||
43 | +import org.thingsboard.server.dao.exception.DataValidationException; | ||
44 | +import org.thingsboard.server.dao.service.DataValidator; | ||
45 | + | ||
46 | +import java.io.PrintWriter; | ||
47 | +import java.io.StringWriter; | ||
48 | +import java.util.List; | ||
49 | + | ||
50 | +import static org.thingsboard.server.dao.service.Validator.validateEntityId; | ||
51 | +import static org.thingsboard.server.dao.service.Validator.validateId; | ||
52 | + | ||
53 | +@Slf4j | ||
54 | +@Service | ||
55 | +@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true") | ||
56 | +public class AuditLogServiceImpl implements AuditLogService { | ||
57 | + | ||
58 | + private static final ObjectMapper objectMapper = new ObjectMapper(); | ||
59 | + | ||
60 | + private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | ||
61 | + private static final int INSERTS_PER_ENTRY = 3; | ||
62 | + | ||
63 | + @Autowired | ||
64 | + private AuditLogLevelFilter auditLogLevelFilter; | ||
65 | + | ||
66 | + @Autowired | ||
67 | + private AuditLogDao auditLogDao; | ||
68 | + | ||
69 | + @Autowired | ||
70 | + private EntityService entityService; | ||
71 | + | ||
72 | + @Override | ||
73 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { | ||
74 | + log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink); | ||
75 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
76 | + validateId(customerId, "Incorrect customerId " + customerId); | ||
77 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId.getId(), customerId, pageLink); | ||
78 | + return new TimePageData<>(auditLogs, pageLink); | ||
79 | + } | ||
80 | + | ||
81 | + @Override | ||
82 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { | ||
83 | + log.trace("Executing findAuditLogsByTenantIdAndUserId [{}], [{}], [{}]", tenantId, userId, pageLink); | ||
84 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
85 | + validateId(userId, "Incorrect userId" + userId); | ||
86 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId.getId(), userId, pageLink); | ||
87 | + return new TimePageData<>(auditLogs, pageLink); | ||
88 | + } | ||
89 | + | ||
90 | + @Override | ||
91 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { | ||
92 | + log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); | ||
93 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
94 | + validateEntityId(entityId, INCORRECT_TENANT_ID + entityId); | ||
95 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink); | ||
96 | + return new TimePageData<>(auditLogs, pageLink); | ||
97 | + } | ||
98 | + | ||
99 | + @Override | ||
100 | + public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { | ||
101 | + log.trace("Executing findAuditLogs [{}]", pageLink); | ||
102 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
103 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantId(tenantId.getId(), pageLink); | ||
104 | + return new TimePageData<>(auditLogs, pageLink); | ||
105 | + } | ||
106 | + | ||
107 | + @Override | ||
108 | + public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> | ||
109 | + logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, | ||
110 | + ActionType actionType, Exception e, Object... additionalInfo) { | ||
111 | + if (canLog(entityId.getEntityType(), actionType)) { | ||
112 | + JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo); | ||
113 | + ActionStatus actionStatus = ActionStatus.SUCCESS; | ||
114 | + String failureDetails = ""; | ||
115 | + String entityName = ""; | ||
116 | + if (entity != null) { | ||
117 | + entityName = entity.getName(); | ||
118 | + } else { | ||
119 | + try { | ||
120 | + entityName = entityService.fetchEntityNameAsync(entityId).get(); | ||
121 | + } catch (Exception ex) {} | ||
122 | + } | ||
123 | + if (e != null) { | ||
124 | + actionStatus = ActionStatus.FAILURE; | ||
125 | + failureDetails = getFailureStack(e); | ||
126 | + } | ||
127 | + if (actionType == ActionType.RPC_CALL) { | ||
128 | + String rpcErrorString = extractParameter(String.class, additionalInfo); | ||
129 | + if (!StringUtils.isEmpty(rpcErrorString)) { | ||
130 | + actionStatus = ActionStatus.FAILURE; | ||
131 | + failureDetails = rpcErrorString; | ||
132 | + } | ||
133 | + } | ||
134 | + return logAction(tenantId, | ||
135 | + entityId, | ||
136 | + entityName, | ||
137 | + customerId, | ||
138 | + userId, | ||
139 | + userName, | ||
140 | + actionType, | ||
141 | + actionData, | ||
142 | + actionStatus, | ||
143 | + failureDetails); | ||
144 | + } else { | ||
145 | + return null; | ||
146 | + } | ||
147 | + } | ||
148 | + | ||
149 | + private <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> JsonNode constructActionData(I entityId, | ||
150 | + E entity, | ||
151 | + ActionType actionType, | ||
152 | + Object... additionalInfo) { | ||
153 | + ObjectNode actionData = objectMapper.createObjectNode(); | ||
154 | + switch(actionType) { | ||
155 | + case ADDED: | ||
156 | + case UPDATED: | ||
157 | + ObjectNode entityNode = objectMapper.valueToTree(entity); | ||
158 | + if (entityId.getEntityType() == EntityType.DASHBOARD) { | ||
159 | + entityNode.put("configuration", ""); | ||
160 | + } | ||
161 | + actionData.set("entity", entityNode); | ||
162 | + break; | ||
163 | + case DELETED: | ||
164 | + case ACTIVATED: | ||
165 | + case SUSPENDED: | ||
166 | + case CREDENTIALS_READ: | ||
167 | + String strEntityId = extractParameter(String.class, additionalInfo); | ||
168 | + actionData.put("entityId", strEntityId); | ||
169 | + break; | ||
170 | + case ATTRIBUTES_UPDATED: | ||
171 | + actionData.put("entityId", entityId.toString()); | ||
172 | + String scope = extractParameter(String.class, 0, additionalInfo); | ||
173 | + List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); | ||
174 | + actionData.put("scope", scope); | ||
175 | + ObjectNode attrsNode = objectMapper.createObjectNode(); | ||
176 | + if (attributes != null) { | ||
177 | + for (AttributeKvEntry attr : attributes) { | ||
178 | + attrsNode.put(attr.getKey(), attr.getValueAsString()); | ||
179 | + } | ||
180 | + } | ||
181 | + actionData.set("attributes", attrsNode); | ||
182 | + break; | ||
183 | + case ATTRIBUTES_DELETED: | ||
184 | + case ATTRIBUTES_READ: | ||
185 | + actionData.put("entityId", entityId.toString()); | ||
186 | + scope = extractParameter(String.class, 0, additionalInfo); | ||
187 | + actionData.put("scope", scope); | ||
188 | + List<String> keys = extractParameter(List.class, 1, additionalInfo); | ||
189 | + ArrayNode attrsArrayNode = actionData.putArray("attributes"); | ||
190 | + if (keys != null) { | ||
191 | + keys.forEach(attrsArrayNode::add); | ||
192 | + } | ||
193 | + break; | ||
194 | + case RPC_CALL: | ||
195 | + actionData.put("entityId", entityId.toString()); | ||
196 | + Boolean oneWay = extractParameter(Boolean.class, 1, additionalInfo); | ||
197 | + String method = extractParameter(String.class, 2, additionalInfo); | ||
198 | + String params = extractParameter(String.class, 3, additionalInfo); | ||
199 | + actionData.put("oneWay", oneWay); | ||
200 | + actionData.put("method", method); | ||
201 | + actionData.put("params", params); | ||
202 | + break; | ||
203 | + case CREDENTIALS_UPDATED: | ||
204 | + actionData.put("entityId", entityId.toString()); | ||
205 | + DeviceCredentials deviceCredentials = extractParameter(DeviceCredentials.class, additionalInfo); | ||
206 | + actionData.set("credentials", objectMapper.valueToTree(deviceCredentials)); | ||
207 | + break; | ||
208 | + case ASSIGNED_TO_CUSTOMER: | ||
209 | + strEntityId = extractParameter(String.class, 0, additionalInfo); | ||
210 | + String strCustomerId = extractParameter(String.class, 1, additionalInfo); | ||
211 | + String strCustomerName = extractParameter(String.class, 2, additionalInfo); | ||
212 | + actionData.put("entityId", strEntityId); | ||
213 | + actionData.put("assignedCustomerId", strCustomerId); | ||
214 | + actionData.put("assignedCustomerName", strCustomerName); | ||
215 | + break; | ||
216 | + case UNASSIGNED_FROM_CUSTOMER: | ||
217 | + strEntityId = extractParameter(String.class, 0, additionalInfo); | ||
218 | + strCustomerId = extractParameter(String.class, 1, additionalInfo); | ||
219 | + strCustomerName = extractParameter(String.class, 2, additionalInfo); | ||
220 | + actionData.put("entityId", strEntityId); | ||
221 | + actionData.put("unassignedCustomerId", strCustomerId); | ||
222 | + actionData.put("unassignedCustomerName", strCustomerName); | ||
223 | + break; | ||
224 | + } | ||
225 | + return actionData; | ||
226 | + } | ||
227 | + | ||
228 | + private <T> T extractParameter(Class<T> clazz, Object... additionalInfo) { | ||
229 | + return extractParameter(clazz, 0, additionalInfo); | ||
230 | + } | ||
231 | + | ||
232 | + private <T> T extractParameter(Class<T> clazz, int index, Object... additionalInfo) { | ||
233 | + T result = null; | ||
234 | + if (additionalInfo != null && additionalInfo.length > index) { | ||
235 | + Object paramObject = additionalInfo[index]; | ||
236 | + if (clazz.isInstance(paramObject)) { | ||
237 | + result = clazz.cast(paramObject); | ||
238 | + } | ||
239 | + } | ||
240 | + return result; | ||
241 | + } | ||
242 | + | ||
243 | + private String getFailureStack(Exception e) { | ||
244 | + StringWriter sw = new StringWriter(); | ||
245 | + e.printStackTrace(new PrintWriter(sw)); | ||
246 | + return sw.toString(); | ||
247 | + } | ||
248 | + | ||
249 | + private boolean canLog(EntityType entityType, ActionType actionType) { | ||
250 | + return auditLogLevelFilter.logEnabled(entityType, actionType); | ||
251 | + } | ||
252 | + | ||
253 | + private AuditLog createAuditLogEntry(TenantId tenantId, | ||
254 | + EntityId entityId, | ||
255 | + String entityName, | ||
256 | + CustomerId customerId, | ||
257 | + UserId userId, | ||
258 | + String userName, | ||
259 | + ActionType actionType, | ||
260 | + JsonNode actionData, | ||
261 | + ActionStatus actionStatus, | ||
262 | + String actionFailureDetails) { | ||
263 | + AuditLog result = new AuditLog(); | ||
264 | + result.setId(new AuditLogId(UUIDs.timeBased())); | ||
265 | + result.setTenantId(tenantId); | ||
266 | + result.setEntityId(entityId); | ||
267 | + result.setEntityName(entityName); | ||
268 | + result.setCustomerId(customerId); | ||
269 | + result.setUserId(userId); | ||
270 | + result.setUserName(userName); | ||
271 | + result.setActionType(actionType); | ||
272 | + result.setActionData(actionData); | ||
273 | + result.setActionStatus(actionStatus); | ||
274 | + result.setActionFailureDetails(actionFailureDetails); | ||
275 | + return result; | ||
276 | + } | ||
277 | + | ||
278 | + private ListenableFuture<List<Void>> logAction(TenantId tenantId, | ||
279 | + EntityId entityId, | ||
280 | + String entityName, | ||
281 | + CustomerId customerId, | ||
282 | + UserId userId, | ||
283 | + String userName, | ||
284 | + ActionType actionType, | ||
285 | + JsonNode actionData, | ||
286 | + ActionStatus actionStatus, | ||
287 | + String actionFailureDetails) { | ||
288 | + AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName, | ||
289 | + actionType, actionData, actionStatus, actionFailureDetails); | ||
290 | + log.trace("Executing logAction [{}]", auditLogEntry); | ||
291 | + auditLogValidator.validate(auditLogEntry); | ||
292 | + List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); | ||
293 | + futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); | ||
294 | + futures.add(auditLogDao.saveByTenantId(auditLogEntry)); | ||
295 | + futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); | ||
296 | + futures.add(auditLogDao.saveByTenantIdAndCustomerId(auditLogEntry)); | ||
297 | + futures.add(auditLogDao.saveByTenantIdAndUserId(auditLogEntry)); | ||
298 | + return Futures.allAsList(futures); | ||
299 | + } | ||
300 | + | ||
301 | + private DataValidator<AuditLog> auditLogValidator = | ||
302 | + new DataValidator<AuditLog>() { | ||
303 | + @Override | ||
304 | + protected void validateDataImpl(AuditLog auditLog) { | ||
305 | + if (auditLog.getEntityId() == null) { | ||
306 | + throw new DataValidationException("Entity Id should be specified!"); | ||
307 | + } | ||
308 | + if (auditLog.getTenantId() == null) { | ||
309 | + throw new DataValidationException("Tenant Id should be specified!"); | ||
310 | + } | ||
311 | + if (auditLog.getUserId() == null) { | ||
312 | + throw new DataValidationException("User Id should be specified!"); | ||
313 | + } | ||
314 | + } | ||
315 | + }; | ||
316 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.datastax.driver.core.BoundStatement; | ||
19 | +import com.datastax.driver.core.PreparedStatement; | ||
20 | +import com.datastax.driver.core.ResultSet; | ||
21 | +import com.datastax.driver.core.ResultSetFuture; | ||
22 | +import com.datastax.driver.core.querybuilder.QueryBuilder; | ||
23 | +import com.datastax.driver.core.querybuilder.Select; | ||
24 | +import com.google.common.base.Function; | ||
25 | +import com.google.common.util.concurrent.Futures; | ||
26 | +import com.google.common.util.concurrent.ListenableFuture; | ||
27 | +import lombok.extern.slf4j.Slf4j; | ||
28 | +import org.springframework.beans.factory.annotation.Autowired; | ||
29 | +import org.springframework.beans.factory.annotation.Value; | ||
30 | +import org.springframework.core.env.Environment; | ||
31 | +import org.springframework.stereotype.Component; | ||
32 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
33 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
34 | +import org.thingsboard.server.common.data.id.EntityId; | ||
35 | +import org.thingsboard.server.common.data.id.UserId; | ||
36 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
37 | +import org.thingsboard.server.dao.DaoUtil; | ||
38 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
39 | +import org.thingsboard.server.dao.model.nosql.AuditLogEntity; | ||
40 | +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; | ||
41 | +import org.thingsboard.server.dao.timeseries.TsPartitionDate; | ||
42 | +import org.thingsboard.server.dao.util.NoSqlDao; | ||
43 | + | ||
44 | +import javax.annotation.Nullable; | ||
45 | +import javax.annotation.PostConstruct; | ||
46 | +import javax.annotation.PreDestroy; | ||
47 | +import java.time.Instant; | ||
48 | +import java.time.LocalDate; | ||
49 | +import java.time.LocalDateTime; | ||
50 | +import java.time.ZoneOffset; | ||
51 | +import java.util.*; | ||
52 | +import java.util.concurrent.ExecutorService; | ||
53 | +import java.util.concurrent.Executors; | ||
54 | +import java.util.stream.Collectors; | ||
55 | + | ||
56 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | ||
57 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
58 | + | ||
59 | +@Component | ||
60 | +@Slf4j | ||
61 | +@NoSqlDao | ||
62 | +public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLogEntity, AuditLog> implements AuditLogDao { | ||
63 | + | ||
64 | + private static final String INSERT_INTO = "INSERT INTO "; | ||
65 | + | ||
66 | + @Autowired | ||
67 | + private Environment environment; | ||
68 | + | ||
69 | + @Override | ||
70 | + protected Class<AuditLogEntity> getColumnFamilyClass() { | ||
71 | + return AuditLogEntity.class; | ||
72 | + } | ||
73 | + | ||
74 | + @Override | ||
75 | + protected String getColumnFamilyName() { | ||
76 | + return AUDIT_LOG_COLUMN_FAMILY_NAME; | ||
77 | + } | ||
78 | + | ||
79 | + protected ExecutorService readResultsProcessingExecutor; | ||
80 | + | ||
81 | + @Value("${audit_log.by_tenant_partitioning}") | ||
82 | + private String partitioning; | ||
83 | + private TsPartitionDate tsFormat; | ||
84 | + | ||
85 | + @Value("${audit_log.default_query_period}") | ||
86 | + private Integer defaultQueryPeriodInDays; | ||
87 | + | ||
88 | + private PreparedStatement partitionInsertStmt; | ||
89 | + private PreparedStatement saveByTenantStmt; | ||
90 | + private PreparedStatement saveByTenantIdAndUserIdStmt; | ||
91 | + private PreparedStatement saveByTenantIdAndEntityIdStmt; | ||
92 | + private PreparedStatement saveByTenantIdAndCustomerIdStmt; | ||
93 | + | ||
94 | + private boolean isInstall() { | ||
95 | + return environment.acceptsProfiles("install"); | ||
96 | + } | ||
97 | + | ||
98 | + @PostConstruct | ||
99 | + public void init() { | ||
100 | + if (!isInstall()) { | ||
101 | + Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning); | ||
102 | + if (partition.isPresent()) { | ||
103 | + tsFormat = partition.get(); | ||
104 | + } else { | ||
105 | + log.warn("Incorrect configuration of partitioning {}", partitioning); | ||
106 | + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); | ||
107 | + } | ||
108 | + } | ||
109 | + readResultsProcessingExecutor = Executors.newCachedThreadPool(); | ||
110 | + } | ||
111 | + | ||
112 | + @PreDestroy | ||
113 | + public void stopExecutor() { | ||
114 | + if (readResultsProcessingExecutor != null) { | ||
115 | + readResultsProcessingExecutor.shutdownNow(); | ||
116 | + } | ||
117 | + } | ||
118 | + | ||
119 | + private <T> ListenableFuture<T> getFuture(ResultSetFuture future, java.util.function.Function<ResultSet, T> transformer) { | ||
120 | + return Futures.transform(future, new Function<ResultSet, T>() { | ||
121 | + @Nullable | ||
122 | + @Override | ||
123 | + public T apply(@Nullable ResultSet input) { | ||
124 | + return transformer.apply(input); | ||
125 | + } | ||
126 | + }, readResultsProcessingExecutor); | ||
127 | + } | ||
128 | + | ||
129 | + @Override | ||
130 | + public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { | ||
131 | + log.debug("Save saveByTenantId [{}] ", auditLog); | ||
132 | + | ||
133 | + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
134 | + BoundStatement stmt = getSaveByTenantStmt().bind(); | ||
135 | + stmt = setSaveStmtVariables(stmt, auditLog, partition); | ||
136 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
137 | + } | ||
138 | + | ||
139 | + @Override | ||
140 | + public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { | ||
141 | + log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); | ||
142 | + | ||
143 | + BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); | ||
144 | + stmt = setSaveStmtVariables(stmt, auditLog, -1); | ||
145 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
146 | + } | ||
147 | + | ||
148 | + @Override | ||
149 | + public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) { | ||
150 | + log.debug("Save saveByTenantIdAndCustomerId [{}] ", auditLog); | ||
151 | + | ||
152 | + BoundStatement stmt = getSaveByTenantIdAndCustomerIdStmt().bind(); | ||
153 | + stmt = setSaveStmtVariables(stmt, auditLog, -1); | ||
154 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
155 | + } | ||
156 | + | ||
157 | + @Override | ||
158 | + public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) { | ||
159 | + log.debug("Save saveByTenantIdAndUserId [{}] ", auditLog); | ||
160 | + | ||
161 | + BoundStatement stmt = getSaveByTenantIdAndUserIdStmt().bind(); | ||
162 | + stmt = setSaveStmtVariables(stmt, auditLog, -1); | ||
163 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
164 | + } | ||
165 | + | ||
166 | + private BoundStatement setSaveStmtVariables(BoundStatement stmt, AuditLog auditLog, long partition) { | ||
167 | + stmt.setUUID(0, auditLog.getId().getId()) | ||
168 | + .setUUID(1, auditLog.getTenantId().getId()) | ||
169 | + .setUUID(2, auditLog.getCustomerId().getId()) | ||
170 | + .setUUID(3, auditLog.getEntityId().getId()) | ||
171 | + .setString(4, auditLog.getEntityId().getEntityType().name()) | ||
172 | + .setString(5, auditLog.getEntityName()) | ||
173 | + .setUUID(6, auditLog.getUserId().getId()) | ||
174 | + .setString(7, auditLog.getUserName()) | ||
175 | + .setString(8, auditLog.getActionType().name()) | ||
176 | + .setString(9, auditLog.getActionData() != null ? auditLog.getActionData().toString() : null) | ||
177 | + .setString(10, auditLog.getActionStatus().name()) | ||
178 | + .setString(11, auditLog.getActionFailureDetails()); | ||
179 | + if (partition > -1) { | ||
180 | + stmt.setLong(12, partition); | ||
181 | + } | ||
182 | + return stmt; | ||
183 | + } | ||
184 | + | ||
185 | + @Override | ||
186 | + public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { | ||
187 | + log.debug("Save savePartitionsByTenantId [{}] ", auditLog); | ||
188 | + | ||
189 | + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
190 | + | ||
191 | + BoundStatement stmt = getPartitionInsertStmt().bind(); | ||
192 | + stmt = stmt.setUUID(0, auditLog.getTenantId().getId()) | ||
193 | + .setLong(1, partition); | ||
194 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
195 | + } | ||
196 | + | ||
197 | + private PreparedStatement getSaveByTenantStmt() { | ||
198 | + if (saveByTenantStmt == null) { | ||
199 | + saveByTenantStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF, true); | ||
200 | + } | ||
201 | + return saveByTenantStmt; | ||
202 | + } | ||
203 | + | ||
204 | + private PreparedStatement getSaveByTenantIdAndEntityIdStmt() { | ||
205 | + if (saveByTenantIdAndEntityIdStmt == null) { | ||
206 | + saveByTenantIdAndEntityIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF, false); | ||
207 | + } | ||
208 | + return saveByTenantIdAndEntityIdStmt; | ||
209 | + } | ||
210 | + | ||
211 | + private PreparedStatement getSaveByTenantIdAndCustomerIdStmt() { | ||
212 | + if (saveByTenantIdAndCustomerIdStmt == null) { | ||
213 | + saveByTenantIdAndCustomerIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_CUSTOMER_ID_CF, false); | ||
214 | + } | ||
215 | + return saveByTenantIdAndCustomerIdStmt; | ||
216 | + } | ||
217 | + | ||
218 | + private PreparedStatement getSaveByTenantIdAndUserIdStmt() { | ||
219 | + if (saveByTenantIdAndUserIdStmt == null) { | ||
220 | + saveByTenantIdAndUserIdStmt = getSaveByTenantIdAndCFName(ModelConstants.AUDIT_LOG_BY_USER_ID_CF, false); | ||
221 | + } | ||
222 | + return saveByTenantIdAndUserIdStmt; | ||
223 | + } | ||
224 | + | ||
225 | + private PreparedStatement getSaveByTenantIdAndCFName(String cfName, boolean hasPartition) { | ||
226 | + List columnsList = new ArrayList(); | ||
227 | + columnsList.add(ModelConstants.AUDIT_LOG_ID_PROPERTY); | ||
228 | + columnsList.add(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY); | ||
229 | + columnsList.add(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY); | ||
230 | + columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY); | ||
231 | + columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY); | ||
232 | + columnsList.add(ModelConstants.AUDIT_LOG_ENTITY_NAME_PROPERTY); | ||
233 | + columnsList.add(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY); | ||
234 | + columnsList.add(ModelConstants.AUDIT_LOG_USER_NAME_PROPERTY); | ||
235 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY); | ||
236 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_DATA_PROPERTY); | ||
237 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_STATUS_PROPERTY); | ||
238 | + columnsList.add(ModelConstants.AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY); | ||
239 | + if (hasPartition) { | ||
240 | + columnsList.add(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY); | ||
241 | + } | ||
242 | + StringJoiner values = new StringJoiner(","); | ||
243 | + for (int i=0;i<columnsList.size();i++) { | ||
244 | + values.add("?"); | ||
245 | + } | ||
246 | + String statementString = INSERT_INTO + cfName + " (" + String.join(",", columnsList) + ") VALUES (" + values.toString() + ")"; | ||
247 | + return getSession().prepare(statementString); | ||
248 | + } | ||
249 | + | ||
250 | + private PreparedStatement getPartitionInsertStmt() { | ||
251 | + if (partitionInsertStmt == null) { | ||
252 | + partitionInsertStmt = getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF + | ||
253 | + "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + | ||
254 | + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + | ||
255 | + " VALUES(?, ?)"); | ||
256 | + } | ||
257 | + return partitionInsertStmt; | ||
258 | + } | ||
259 | + | ||
260 | + private long toPartitionTs(long ts) { | ||
261 | + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | ||
262 | + return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); | ||
263 | + } | ||
264 | + | ||
265 | + @Override | ||
266 | + public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { | ||
267 | + log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); | ||
268 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_ENTITY_ID_CF, | ||
269 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | ||
270 | + eq(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), | ||
271 | + eq(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY, entityId.getId())), | ||
272 | + pageLink); | ||
273 | + log.trace("Found audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); | ||
274 | + return DaoUtil.convertDataList(entities); | ||
275 | + } | ||
276 | + | ||
277 | + @Override | ||
278 | + public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { | ||
279 | + log.trace("Try to find audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); | ||
280 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_CUSTOMER_ID_CF, | ||
281 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | ||
282 | + eq(ModelConstants.AUDIT_LOG_CUSTOMER_ID_PROPERTY, customerId.getId())), | ||
283 | + pageLink); | ||
284 | + log.trace("Found audit logs by tenant [{}], customer [{}] and pageLink [{}]", tenantId, customerId, pageLink); | ||
285 | + return DaoUtil.convertDataList(entities); | ||
286 | + } | ||
287 | + | ||
288 | + @Override | ||
289 | + public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { | ||
290 | + log.trace("Try to find audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); | ||
291 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_USER_ID_CF, | ||
292 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | ||
293 | + eq(ModelConstants.AUDIT_LOG_USER_ID_PROPERTY, userId.getId())), | ||
294 | + pageLink); | ||
295 | + log.trace("Found audit logs by tenant [{}], user [{}] and pageLink [{}]", tenantId, userId, pageLink); | ||
296 | + return DaoUtil.convertDataList(entities); | ||
297 | + } | ||
298 | + | ||
299 | + @Override | ||
300 | + public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { | ||
301 | + log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); | ||
302 | + | ||
303 | + long minPartition; | ||
304 | + if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { | ||
305 | + minPartition = toPartitionTs(pageLink.getStartTime()); | ||
306 | + } else { | ||
307 | + minPartition = toPartitionTs(LocalDate.now().minusDays(defaultQueryPeriodInDays).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
308 | + } | ||
309 | + | ||
310 | + long maxPartition; | ||
311 | + if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { | ||
312 | + maxPartition = toPartitionTs(pageLink.getEndTime()); | ||
313 | + } else { | ||
314 | + maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
315 | + } | ||
316 | + | ||
317 | + List<Long> partitions = fetchPartitions(tenantId, minPartition, maxPartition) | ||
318 | + .all() | ||
319 | + .stream() | ||
320 | + .map(row -> row.getLong(ModelConstants.PARTITION_COLUMN)) | ||
321 | + .collect(Collectors.toList()); | ||
322 | + | ||
323 | + AuditLogQueryCursor cursor = new AuditLogQueryCursor(tenantId, pageLink, partitions); | ||
324 | + List<AuditLogEntity> entities = fetchSequentiallyWithLimit(cursor); | ||
325 | + log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); | ||
326 | + return DaoUtil.convertDataList(entities); | ||
327 | + } | ||
328 | + | ||
329 | + private List<AuditLogEntity> fetchSequentiallyWithLimit(AuditLogQueryCursor cursor) { | ||
330 | + if (cursor.isFull() || !cursor.hasNextPartition()) { | ||
331 | + return cursor.getData(); | ||
332 | + } else { | ||
333 | + cursor.addData(findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF, | ||
334 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, cursor.getTenantId()), | ||
335 | + eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, cursor.getNextPartition())), | ||
336 | + cursor.getPageLink())); | ||
337 | + return fetchSequentiallyWithLimit(cursor); | ||
338 | + } | ||
339 | + } | ||
340 | + | ||
341 | + private ResultSet fetchPartitions(UUID tenantId, long minPartition, long maxPartition) { | ||
342 | + Select.Where select = QueryBuilder.select(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY).from(ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF) | ||
343 | + .where(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId)); | ||
344 | + select.and(QueryBuilder.gte(ModelConstants.PARTITION_COLUMN, minPartition)); | ||
345 | + select.and(QueryBuilder.lte(ModelConstants.PARTITION_COLUMN, maxPartition)); | ||
346 | + return getSession().execute(select); | ||
347 | + } | ||
348 | + | ||
349 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.google.common.util.concurrent.Futures; | ||
20 | +import com.google.common.util.concurrent.ListenableFuture; | ||
21 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
22 | +import org.thingsboard.server.common.data.BaseData; | ||
23 | +import org.thingsboard.server.common.data.HasName; | ||
24 | +import org.thingsboard.server.common.data.User; | ||
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
28 | +import org.thingsboard.server.common.data.id.*; | ||
29 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
30 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
31 | + | ||
32 | +import java.util.Collections; | ||
33 | +import java.util.List; | ||
34 | + | ||
35 | +@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false") | ||
36 | +public class DummyAuditLogServiceImpl implements AuditLogService { | ||
37 | + | ||
38 | + @Override | ||
39 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { | ||
40 | + return new TimePageData<>(null, pageLink); | ||
41 | + } | ||
42 | + | ||
43 | + @Override | ||
44 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, TimePageLink pageLink) { | ||
45 | + return new TimePageData<>(null, pageLink); | ||
46 | + } | ||
47 | + | ||
48 | + @Override | ||
49 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { | ||
50 | + return new TimePageData<>(null, pageLink); | ||
51 | + } | ||
52 | + | ||
53 | + @Override | ||
54 | + public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { | ||
55 | + return new TimePageData<>(null, pageLink); | ||
56 | + } | ||
57 | + | ||
58 | + @Override | ||
59 | + public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) { | ||
60 | + return null; | ||
61 | + } | ||
62 | + | ||
63 | +} |
@@ -20,9 +20,6 @@ import com.datastax.driver.core.*; | @@ -20,9 +20,6 @@ import com.datastax.driver.core.*; | ||
20 | import com.datastax.driver.core.ProtocolOptions.Compression; | 20 | import com.datastax.driver.core.ProtocolOptions.Compression; |
21 | import com.datastax.driver.mapping.Mapper; | 21 | import com.datastax.driver.mapping.Mapper; |
22 | import com.datastax.driver.mapping.MappingManager; | 22 | import com.datastax.driver.mapping.MappingManager; |
23 | -import lombok.AccessLevel; | ||
24 | -import lombok.Data; | ||
25 | -import lombok.Getter; | ||
26 | import lombok.extern.slf4j.Slf4j; | 23 | import lombok.extern.slf4j.Slf4j; |
27 | import org.apache.commons.lang3.StringUtils; | 24 | import org.apache.commons.lang3.StringUtils; |
28 | import org.springframework.beans.factory.annotation.Autowired; | 25 | import org.springframework.beans.factory.annotation.Autowired; |
@@ -36,7 +33,6 @@ import java.util.Collections; | @@ -36,7 +33,6 @@ import java.util.Collections; | ||
36 | import java.util.List; | 33 | import java.util.List; |
37 | 34 | ||
38 | @Slf4j | 35 | @Slf4j |
39 | -@Data | ||
40 | public abstract class AbstractCassandraCluster { | 36 | public abstract class AbstractCassandraCluster { |
41 | 37 | ||
42 | private static final String COMMA = ","; | 38 | private static final String COMMA = ","; |
@@ -77,7 +73,7 @@ public abstract class AbstractCassandraCluster { | @@ -77,7 +73,7 @@ public abstract class AbstractCassandraCluster { | ||
77 | private Cluster cluster; | 73 | private Cluster cluster; |
78 | private Cluster.Builder clusterBuilder; | 74 | private Cluster.Builder clusterBuilder; |
79 | 75 | ||
80 | - @Getter(AccessLevel.NONE) private Session session; | 76 | + private Session session; |
81 | 77 | ||
82 | private MappingManager mappingManager; | 78 | private MappingManager mappingManager; |
83 | 79 | ||
@@ -115,6 +111,10 @@ public abstract class AbstractCassandraCluster { | @@ -115,6 +111,10 @@ public abstract class AbstractCassandraCluster { | ||
115 | } | 111 | } |
116 | } | 112 | } |
117 | 113 | ||
114 | + public Cluster getCluster() { | ||
115 | + return cluster; | ||
116 | + } | ||
117 | + | ||
118 | public Session getSession() { | 118 | public Session getSession() { |
119 | if (!isInstall()) { | 119 | if (!isInstall()) { |
120 | return session; | 120 | return session; |
@@ -44,6 +44,13 @@ public class ModelConstants { | @@ -44,6 +44,13 @@ public class ModelConstants { | ||
44 | public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; | 44 | public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; |
45 | public static final String ENTITY_TYPE_PROPERTY = "entity_type"; | 45 | public static final String ENTITY_TYPE_PROPERTY = "entity_type"; |
46 | 46 | ||
47 | + public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; | ||
48 | + public static final String ENTITY_ID_COLUMN = "entity_id"; | ||
49 | + public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; | ||
50 | + public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; | ||
51 | + public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; | ||
52 | + | ||
53 | + | ||
47 | /** | 54 | /** |
48 | * Cassandra user constants. | 55 | * Cassandra user constants. |
49 | */ | 56 | */ |
@@ -135,6 +142,31 @@ public class ModelConstants { | @@ -135,6 +142,31 @@ public class ModelConstants { | ||
135 | public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; | 142 | public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; |
136 | 143 | ||
137 | /** | 144 | /** |
145 | + * Cassandra audit log constants. | ||
146 | + */ | ||
147 | + public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log"; | ||
148 | + | ||
149 | + public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id"; | ||
150 | + public static final String AUDIT_LOG_BY_CUSTOMER_ID_CF = "audit_log_by_customer_id"; | ||
151 | + public static final String AUDIT_LOG_BY_USER_ID_CF = "audit_log_by_user_id"; | ||
152 | + public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id"; | ||
153 | + public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions"; | ||
154 | + | ||
155 | + public static final String AUDIT_LOG_ID_PROPERTY = ID_PROPERTY; | ||
156 | + public static final String AUDIT_LOG_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; | ||
157 | + public static final String AUDIT_LOG_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; | ||
158 | + public static final String AUDIT_LOG_ENTITY_TYPE_PROPERTY = ENTITY_TYPE_PROPERTY; | ||
159 | + public static final String AUDIT_LOG_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN; | ||
160 | + public static final String AUDIT_LOG_ENTITY_NAME_PROPERTY = "entity_name"; | ||
161 | + public static final String AUDIT_LOG_USER_ID_PROPERTY = USER_ID_PROPERTY; | ||
162 | + public static final String AUDIT_LOG_PARTITION_PROPERTY = "partition"; | ||
163 | + public static final String AUDIT_LOG_USER_NAME_PROPERTY = "user_name"; | ||
164 | + public static final String AUDIT_LOG_ACTION_TYPE_PROPERTY = "action_type"; | ||
165 | + public static final String AUDIT_LOG_ACTION_DATA_PROPERTY = "action_data"; | ||
166 | + public static final String AUDIT_LOG_ACTION_STATUS_PROPERTY = "action_status"; | ||
167 | + public static final String AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY = "action_failure_details"; | ||
168 | + | ||
169 | + /** | ||
138 | * Cassandra asset constants. | 170 | * Cassandra asset constants. |
139 | */ | 171 | */ |
140 | public static final String ASSET_COLUMN_FAMILY_NAME = "asset"; | 172 | public static final String ASSET_COLUMN_FAMILY_NAME = "asset"; |
@@ -310,13 +342,6 @@ public class ModelConstants { | @@ -310,13 +342,6 @@ public class ModelConstants { | ||
310 | public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf"; | 342 | public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf"; |
311 | public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf"; | 343 | public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf"; |
312 | 344 | ||
313 | - | ||
314 | - public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; | ||
315 | - public static final String ENTITY_ID_COLUMN = "entity_id"; | ||
316 | - public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; | ||
317 | - public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; | ||
318 | - public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; | ||
319 | - | ||
320 | public static final String PARTITION_COLUMN = "partition"; | 345 | public static final String PARTITION_COLUMN = "partition"; |
321 | public static final String KEY_COLUMN = "key"; | 346 | public static final String KEY_COLUMN = "key"; |
322 | public static final String TS_COLUMN = "ts"; | 347 | public static final String TS_COLUMN = "ts"; |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.nosql; | ||
17 | + | ||
18 | +import com.datastax.driver.core.utils.UUIDs; | ||
19 | +import com.datastax.driver.mapping.annotations.Column; | ||
20 | +import com.datastax.driver.mapping.annotations.Table; | ||
21 | +import com.fasterxml.jackson.databind.JsonNode; | ||
22 | +import lombok.Data; | ||
23 | +import lombok.NoArgsConstructor; | ||
24 | +import org.thingsboard.server.common.data.EntityType; | ||
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
28 | +import org.thingsboard.server.common.data.id.*; | ||
29 | +import org.thingsboard.server.dao.model.BaseEntity; | ||
30 | +import org.thingsboard.server.dao.model.type.ActionStatusCodec; | ||
31 | +import org.thingsboard.server.dao.model.type.ActionTypeCodec; | ||
32 | +import org.thingsboard.server.dao.model.type.EntityTypeCodec; | ||
33 | +import org.thingsboard.server.dao.model.type.JsonCodec; | ||
34 | + | ||
35 | +import java.util.UUID; | ||
36 | + | ||
37 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
38 | + | ||
39 | +@Table(name = AUDIT_LOG_COLUMN_FAMILY_NAME) | ||
40 | +@Data | ||
41 | +@NoArgsConstructor | ||
42 | +public class AuditLogEntity implements BaseEntity<AuditLog> { | ||
43 | + | ||
44 | + @Column(name = ID_PROPERTY) | ||
45 | + private UUID id; | ||
46 | + | ||
47 | + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) | ||
48 | + private UUID tenantId; | ||
49 | + | ||
50 | + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) | ||
51 | + private UUID customerId; | ||
52 | + | ||
53 | + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class) | ||
54 | + private EntityType entityType; | ||
55 | + | ||
56 | + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) | ||
57 | + private UUID entityId; | ||
58 | + | ||
59 | + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) | ||
60 | + private String entityName; | ||
61 | + | ||
62 | + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) | ||
63 | + private UUID userId; | ||
64 | + | ||
65 | + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) | ||
66 | + private String userName; | ||
67 | + | ||
68 | + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY, codec = ActionTypeCodec.class) | ||
69 | + private ActionType actionType; | ||
70 | + | ||
71 | + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY, codec = JsonCodec.class) | ||
72 | + private JsonNode actionData; | ||
73 | + | ||
74 | + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY, codec = ActionStatusCodec.class) | ||
75 | + private ActionStatus actionStatus; | ||
76 | + | ||
77 | + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) | ||
78 | + private String actionFailureDetails; | ||
79 | + | ||
80 | + @Override | ||
81 | + public UUID getId() { | ||
82 | + return id; | ||
83 | + } | ||
84 | + | ||
85 | + @Override | ||
86 | + public void setId(UUID id) { | ||
87 | + this.id = id; | ||
88 | + } | ||
89 | + | ||
90 | + public AuditLogEntity(AuditLog auditLog) { | ||
91 | + if (auditLog.getId() != null) { | ||
92 | + this.id = auditLog.getId().getId(); | ||
93 | + } | ||
94 | + if (auditLog.getTenantId() != null) { | ||
95 | + this.tenantId = auditLog.getTenantId().getId(); | ||
96 | + } | ||
97 | + if (auditLog.getEntityId() != null) { | ||
98 | + this.entityType = auditLog.getEntityId().getEntityType(); | ||
99 | + this.entityId = auditLog.getEntityId().getId(); | ||
100 | + } | ||
101 | + if (auditLog.getCustomerId() != null) { | ||
102 | + this.customerId = auditLog.getCustomerId().getId(); | ||
103 | + } | ||
104 | + if (auditLog.getUserId() != null) { | ||
105 | + this.userId = auditLog.getUserId().getId(); | ||
106 | + } | ||
107 | + this.entityName = auditLog.getEntityName(); | ||
108 | + this.userName = auditLog.getUserName(); | ||
109 | + this.actionType = auditLog.getActionType(); | ||
110 | + this.actionData = auditLog.getActionData(); | ||
111 | + this.actionStatus = auditLog.getActionStatus(); | ||
112 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | ||
113 | + } | ||
114 | + | ||
115 | + @Override | ||
116 | + public AuditLog toData() { | ||
117 | + AuditLog auditLog = new AuditLog(new AuditLogId(id)); | ||
118 | + auditLog.setCreatedTime(UUIDs.unixTimestamp(id)); | ||
119 | + if (tenantId != null) { | ||
120 | + auditLog.setTenantId(new TenantId(tenantId)); | ||
121 | + } | ||
122 | + if (entityId != null && entityType != null) { | ||
123 | + auditLog.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); | ||
124 | + } | ||
125 | + if (customerId != null) { | ||
126 | + auditLog.setCustomerId(new CustomerId(customerId)); | ||
127 | + } | ||
128 | + if (userId != null) { | ||
129 | + auditLog.setUserId(new UserId(userId)); | ||
130 | + } | ||
131 | + auditLog.setEntityName(this.entityName); | ||
132 | + auditLog.setUserName(this.userName); | ||
133 | + auditLog.setActionType(this.actionType); | ||
134 | + auditLog.setActionData(this.actionData); | ||
135 | + auditLog.setActionStatus(this.actionStatus); | ||
136 | + auditLog.setActionFailureDetails(this.actionFailureDetails); | ||
137 | + return auditLog; | ||
138 | + } | ||
139 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.sql; | ||
17 | + | ||
18 | +import com.datastax.driver.core.utils.UUIDs; | ||
19 | +import com.fasterxml.jackson.databind.JsonNode; | ||
20 | +import lombok.Data; | ||
21 | +import lombok.EqualsAndHashCode; | ||
22 | +import org.hibernate.annotations.Type; | ||
23 | +import org.hibernate.annotations.TypeDef; | ||
24 | +import org.thingsboard.server.common.data.EntityType; | ||
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
28 | +import org.thingsboard.server.common.data.id.*; | ||
29 | +import org.thingsboard.server.dao.model.BaseEntity; | ||
30 | +import org.thingsboard.server.dao.model.BaseSqlEntity; | ||
31 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
32 | +import org.thingsboard.server.dao.util.mapping.JsonStringType; | ||
33 | + | ||
34 | +import javax.persistence.*; | ||
35 | + | ||
36 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
37 | + | ||
38 | +@Data | ||
39 | +@EqualsAndHashCode(callSuper = true) | ||
40 | +@Entity | ||
41 | +@TypeDef(name = "json", typeClass = JsonStringType.class) | ||
42 | +@Table(name = ModelConstants.AUDIT_LOG_COLUMN_FAMILY_NAME) | ||
43 | +public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntity<AuditLog> { | ||
44 | + | ||
45 | + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) | ||
46 | + private String tenantId; | ||
47 | + | ||
48 | + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) | ||
49 | + private String customerId; | ||
50 | + | ||
51 | + @Enumerated(EnumType.STRING) | ||
52 | + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY) | ||
53 | + private EntityType entityType; | ||
54 | + | ||
55 | + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) | ||
56 | + private String entityId; | ||
57 | + | ||
58 | + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) | ||
59 | + private String entityName; | ||
60 | + | ||
61 | + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) | ||
62 | + private String userId; | ||
63 | + | ||
64 | + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) | ||
65 | + private String userName; | ||
66 | + | ||
67 | + @Enumerated(EnumType.STRING) | ||
68 | + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY) | ||
69 | + private ActionType actionType; | ||
70 | + | ||
71 | + @Type(type = "json") | ||
72 | + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY) | ||
73 | + private JsonNode actionData; | ||
74 | + | ||
75 | + @Enumerated(EnumType.STRING) | ||
76 | + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY) | ||
77 | + private ActionStatus actionStatus; | ||
78 | + | ||
79 | + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) | ||
80 | + private String actionFailureDetails; | ||
81 | + | ||
82 | + public AuditLogEntity() { | ||
83 | + super(); | ||
84 | + } | ||
85 | + | ||
86 | + public AuditLogEntity(AuditLog auditLog) { | ||
87 | + if (auditLog.getId() != null) { | ||
88 | + this.setId(auditLog.getId().getId()); | ||
89 | + } | ||
90 | + if (auditLog.getTenantId() != null) { | ||
91 | + this.tenantId = toString(auditLog.getTenantId().getId()); | ||
92 | + } | ||
93 | + if (auditLog.getCustomerId() != null) { | ||
94 | + this.customerId = toString(auditLog.getCustomerId().getId()); | ||
95 | + } | ||
96 | + if (auditLog.getEntityId() != null) { | ||
97 | + this.entityId = toString(auditLog.getEntityId().getId()); | ||
98 | + this.entityType = auditLog.getEntityId().getEntityType(); | ||
99 | + } | ||
100 | + if (auditLog.getUserId() != null) { | ||
101 | + this.userId = toString(auditLog.getUserId().getId()); | ||
102 | + } | ||
103 | + this.entityName = auditLog.getEntityName(); | ||
104 | + this.userName = auditLog.getUserName(); | ||
105 | + this.actionType = auditLog.getActionType(); | ||
106 | + this.actionData = auditLog.getActionData(); | ||
107 | + this.actionStatus = auditLog.getActionStatus(); | ||
108 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | ||
109 | + } | ||
110 | + | ||
111 | + @Override | ||
112 | + public AuditLog toData() { | ||
113 | + AuditLog auditLog = new AuditLog(new AuditLogId(getId())); | ||
114 | + auditLog.setCreatedTime(UUIDs.unixTimestamp(getId())); | ||
115 | + if (tenantId != null) { | ||
116 | + auditLog.setTenantId(new TenantId(toUUID(tenantId))); | ||
117 | + } | ||
118 | + if (customerId != null) { | ||
119 | + auditLog.setCustomerId(new CustomerId(toUUID(customerId))); | ||
120 | + } | ||
121 | + if (entityId != null) { | ||
122 | + auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); | ||
123 | + } | ||
124 | + if (userId != null) { | ||
125 | + auditLog.setUserId(new UserId(toUUID(entityId))); | ||
126 | + } | ||
127 | + auditLog.setEntityName(this.entityName); | ||
128 | + auditLog.setUserName(this.userName); | ||
129 | + auditLog.setActionType(this.actionType); | ||
130 | + auditLog.setActionData(this.actionData); | ||
131 | + auditLog.setActionStatus(this.actionStatus); | ||
132 | + auditLog.setActionFailureDetails(this.actionFailureDetails); | ||
133 | + return auditLog; | ||
134 | + } | ||
135 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.type; | ||
17 | + | ||
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | ||
19 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
20 | + | ||
21 | +public class ActionStatusCodec extends EnumNameCodec<ActionStatus> { | ||
22 | + | ||
23 | + public ActionStatusCodec() { | ||
24 | + super(ActionStatus.class); | ||
25 | + } | ||
26 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.type; | ||
17 | + | ||
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | ||
19 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
20 | + | ||
21 | +public class ActionTypeCodec extends EnumNameCodec<ActionType> { | ||
22 | + | ||
23 | + public ActionTypeCodec() { | ||
24 | + super(ActionType.class); | ||
25 | + } | ||
26 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.sql.audit; | ||
17 | + | ||
18 | +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; | ||
19 | +import org.springframework.data.repository.CrudRepository; | ||
20 | +import org.thingsboard.server.dao.model.sql.AuditLogEntity; | ||
21 | + | ||
22 | +public interface AuditLogRepository extends CrudRepository<AuditLogEntity, String>, JpaSpecificationExecutor<AuditLogEntity> { | ||
23 | + | ||
24 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.sql.audit; | ||
17 | + | ||
18 | +import com.google.common.util.concurrent.ListenableFuture; | ||
19 | +import com.google.common.util.concurrent.ListeningExecutorService; | ||
20 | +import com.google.common.util.concurrent.MoreExecutors; | ||
21 | +import org.springframework.beans.factory.annotation.Autowired; | ||
22 | +import org.springframework.data.domain.PageRequest; | ||
23 | +import org.springframework.data.domain.Pageable; | ||
24 | +import org.springframework.data.domain.Sort; | ||
25 | +import org.springframework.data.jpa.domain.Specification; | ||
26 | +import org.springframework.data.repository.CrudRepository; | ||
27 | +import org.springframework.stereotype.Component; | ||
28 | +import org.thingsboard.server.common.data.UUIDConverter; | ||
29 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
30 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
31 | +import org.thingsboard.server.common.data.id.EntityId; | ||
32 | +import org.thingsboard.server.common.data.id.UserId; | ||
33 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
34 | +import org.thingsboard.server.dao.DaoUtil; | ||
35 | +import org.thingsboard.server.dao.audit.AuditLogDao; | ||
36 | +import org.thingsboard.server.dao.model.sql.AuditLogEntity; | ||
37 | +import org.thingsboard.server.dao.sql.JpaAbstractDao; | ||
38 | +import org.thingsboard.server.dao.sql.JpaAbstractSearchTimeDao; | ||
39 | +import org.thingsboard.server.dao.util.SqlDao; | ||
40 | + | ||
41 | +import javax.annotation.PreDestroy; | ||
42 | +import javax.persistence.criteria.Predicate; | ||
43 | +import java.util.ArrayList; | ||
44 | +import java.util.List; | ||
45 | +import java.util.UUID; | ||
46 | +import java.util.concurrent.Executors; | ||
47 | + | ||
48 | +import static org.springframework.data.jpa.domain.Specifications.where; | ||
49 | +import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY; | ||
50 | + | ||
51 | +@Component | ||
52 | +@SqlDao | ||
53 | +public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> implements AuditLogDao { | ||
54 | + | ||
55 | + private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); | ||
56 | + | ||
57 | + @Autowired | ||
58 | + private AuditLogRepository auditLogRepository; | ||
59 | + | ||
60 | + @Override | ||
61 | + protected Class<AuditLogEntity> getEntityClass() { | ||
62 | + return AuditLogEntity.class; | ||
63 | + } | ||
64 | + | ||
65 | + @Override | ||
66 | + protected CrudRepository<AuditLogEntity, String> getCrudRepository() { | ||
67 | + return auditLogRepository; | ||
68 | + } | ||
69 | + | ||
70 | + @PreDestroy | ||
71 | + void onDestroy() { | ||
72 | + insertService.shutdown(); | ||
73 | + } | ||
74 | + | ||
75 | + @Override | ||
76 | + public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { | ||
77 | + return insertService.submit(() -> { | ||
78 | + save(auditLog); | ||
79 | + return null; | ||
80 | + }); | ||
81 | + } | ||
82 | + | ||
83 | + @Override | ||
84 | + public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { | ||
85 | + return insertService.submit(() -> null); | ||
86 | + } | ||
87 | + | ||
88 | + @Override | ||
89 | + public ListenableFuture<Void> saveByTenantIdAndCustomerId(AuditLog auditLog) { | ||
90 | + return insertService.submit(() -> null); | ||
91 | + } | ||
92 | + | ||
93 | + @Override | ||
94 | + public ListenableFuture<Void> saveByTenantIdAndUserId(AuditLog auditLog) { | ||
95 | + return insertService.submit(() -> null); | ||
96 | + } | ||
97 | + | ||
98 | + @Override | ||
99 | + public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { | ||
100 | + return insertService.submit(() -> null); | ||
101 | + } | ||
102 | + | ||
103 | + @Override | ||
104 | + public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { | ||
105 | + return findAuditLogs(tenantId, entityId, null, null, pageLink); | ||
106 | + } | ||
107 | + | ||
108 | + @Override | ||
109 | + public List<AuditLog> findAuditLogsByTenantIdAndCustomerId(UUID tenantId, CustomerId customerId, TimePageLink pageLink) { | ||
110 | + return findAuditLogs(tenantId, null, customerId, null, pageLink); | ||
111 | + } | ||
112 | + | ||
113 | + @Override | ||
114 | + public List<AuditLog> findAuditLogsByTenantIdAndUserId(UUID tenantId, UserId userId, TimePageLink pageLink) { | ||
115 | + return findAuditLogs(tenantId, null, null, userId, pageLink); | ||
116 | + } | ||
117 | + | ||
118 | + @Override | ||
119 | + public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { | ||
120 | + return findAuditLogs(tenantId, null, null, null, pageLink); | ||
121 | + } | ||
122 | + | ||
123 | + private List<AuditLog> findAuditLogs(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId, TimePageLink pageLink) { | ||
124 | + Specification<AuditLogEntity> timeSearchSpec = JpaAbstractSearchTimeDao.getTimeSearchPageSpec(pageLink, "id"); | ||
125 | + Specification<AuditLogEntity> fieldsSpec = getEntityFieldsSpec(tenantId, entityId, customerId, userId); | ||
126 | + Sort.Direction sortDirection = pageLink.isAscOrder() ? Sort.Direction.ASC : Sort.Direction.DESC; | ||
127 | + Pageable pageable = new PageRequest(0, pageLink.getLimit(), sortDirection, ID_PROPERTY); | ||
128 | + return DaoUtil.convertDataList(auditLogRepository.findAll(where(timeSearchSpec).and(fieldsSpec), pageable).getContent()); | ||
129 | + } | ||
130 | + | ||
131 | + private Specification<AuditLogEntity> getEntityFieldsSpec(UUID tenantId, EntityId entityId, CustomerId customerId, UserId userId) { | ||
132 | + return (root, criteriaQuery, criteriaBuilder) -> { | ||
133 | + List<Predicate> predicates = new ArrayList<>(); | ||
134 | + if (tenantId != null) { | ||
135 | + Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("tenantId"), UUIDConverter.fromTimeUUID(tenantId)); | ||
136 | + predicates.add(tenantIdPredicate); | ||
137 | + } | ||
138 | + if (entityId != null) { | ||
139 | + Predicate entityTypePredicate = criteriaBuilder.equal(root.get("entityType"), entityId.getEntityType()); | ||
140 | + predicates.add(entityTypePredicate); | ||
141 | + Predicate entityIdPredicate = criteriaBuilder.equal(root.get("entityId"), UUIDConverter.fromTimeUUID(entityId.getId())); | ||
142 | + predicates.add(entityIdPredicate); | ||
143 | + } | ||
144 | + if (customerId != null) { | ||
145 | + Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("customerId"), UUIDConverter.fromTimeUUID(customerId.getId())); | ||
146 | + predicates.add(tenantIdPredicate); | ||
147 | + } | ||
148 | + if (userId != null) { | ||
149 | + Predicate tenantIdPredicate = criteriaBuilder.equal(root.get("userId"), UUIDConverter.fromTimeUUID(userId.getId())); | ||
150 | + predicates.add(tenantIdPredicate); | ||
151 | + } | ||
152 | + return criteriaBuilder.and(predicates.toArray(new Predicate[]{})); | ||
153 | + }; | ||
154 | + } | ||
155 | +} |
@@ -548,3 +548,78 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS | @@ -548,3 +548,78 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS | ||
548 | AND event_type IS NOT NULL AND event_uid IS NOT NULL | 548 | AND event_type IS NOT NULL AND event_uid IS NOT NULL |
549 | PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) | 549 | PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) |
550 | WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); | 550 | WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); |
551 | + | ||
552 | + | ||
553 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( | ||
554 | + tenant_id timeuuid, | ||
555 | + id timeuuid, | ||
556 | + customer_id timeuuid, | ||
557 | + entity_id timeuuid, | ||
558 | + entity_type text, | ||
559 | + entity_name text, | ||
560 | + user_id timeuuid, | ||
561 | + user_name text, | ||
562 | + action_type text, | ||
563 | + action_data text, | ||
564 | + action_status text, | ||
565 | + action_failure_details text, | ||
566 | + PRIMARY KEY ((tenant_id, entity_id, entity_type), id) | ||
567 | +); | ||
568 | + | ||
569 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_customer_id ( | ||
570 | + tenant_id timeuuid, | ||
571 | + id timeuuid, | ||
572 | + customer_id timeuuid, | ||
573 | + entity_id timeuuid, | ||
574 | + entity_type text, | ||
575 | + entity_name text, | ||
576 | + user_id timeuuid, | ||
577 | + user_name text, | ||
578 | + action_type text, | ||
579 | + action_data text, | ||
580 | + action_status text, | ||
581 | + action_failure_details text, | ||
582 | + PRIMARY KEY ((tenant_id, customer_id), id) | ||
583 | +); | ||
584 | + | ||
585 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_user_id ( | ||
586 | + tenant_id timeuuid, | ||
587 | + id timeuuid, | ||
588 | + customer_id timeuuid, | ||
589 | + entity_id timeuuid, | ||
590 | + entity_type text, | ||
591 | + entity_name text, | ||
592 | + user_id timeuuid, | ||
593 | + user_name text, | ||
594 | + action_type text, | ||
595 | + action_data text, | ||
596 | + action_status text, | ||
597 | + action_failure_details text, | ||
598 | + PRIMARY KEY ((tenant_id, user_id), id) | ||
599 | +); | ||
600 | + | ||
601 | + | ||
602 | + | ||
603 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( | ||
604 | + tenant_id timeuuid, | ||
605 | + id timeuuid, | ||
606 | + partition bigint, | ||
607 | + customer_id timeuuid, | ||
608 | + entity_id timeuuid, | ||
609 | + entity_type text, | ||
610 | + entity_name text, | ||
611 | + user_id timeuuid, | ||
612 | + user_name text, | ||
613 | + action_type text, | ||
614 | + action_data text, | ||
615 | + action_status text, | ||
616 | + action_failure_details text, | ||
617 | + PRIMARY KEY ((tenant_id, partition), id) | ||
618 | +); | ||
619 | + | ||
620 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( | ||
621 | + tenant_id timeuuid, | ||
622 | + partition bigint, | ||
623 | + PRIMARY KEY (( tenant_id ), partition) | ||
624 | +) WITH CLUSTERING ORDER BY ( partition ASC ) | ||
625 | +AND compaction = { 'class' : 'LeveledCompactionStrategy' }; |
@@ -47,6 +47,21 @@ CREATE TABLE IF NOT EXISTS asset ( | @@ -47,6 +47,21 @@ CREATE TABLE IF NOT EXISTS asset ( | ||
47 | type varchar(255) | 47 | type varchar(255) |
48 | ); | 48 | ); |
49 | 49 | ||
50 | +CREATE TABLE IF NOT EXISTS audit_log ( | ||
51 | + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, | ||
52 | + tenant_id varchar(31), | ||
53 | + customer_id varchar(31), | ||
54 | + entity_id varchar(31), | ||
55 | + entity_type varchar(255), | ||
56 | + entity_name varchar(255), | ||
57 | + user_id varchar(31), | ||
58 | + user_name varchar(255), | ||
59 | + action_type varchar(255), | ||
60 | + action_data varchar(1000000), | ||
61 | + action_status varchar(255), | ||
62 | + action_failure_details varchar(1000000) | ||
63 | +); | ||
64 | + | ||
50 | CREATE TABLE IF NOT EXISTS attribute_kv ( | 65 | CREATE TABLE IF NOT EXISTS attribute_kv ( |
51 | entity_type varchar(255), | 66 | entity_type varchar(255), |
52 | entity_id varchar(31), | 67 | entity_id varchar(31), |
@@ -24,7 +24,7 @@ import java.util.Arrays; | @@ -24,7 +24,7 @@ import java.util.Arrays; | ||
24 | 24 | ||
25 | @RunWith(ClasspathSuite.class) | 25 | @RunWith(ClasspathSuite.class) |
26 | @ClassnameFilters({ | 26 | @ClassnameFilters({ |
27 | - "org.thingsboard.server.dao.sql.*AAATest" | 27 | + "org.thingsboard.server.dao.sql.*THIS_MUST_BE_FIXED_Test" |
28 | }) | 28 | }) |
29 | public class JpaDaoTestSuite { | 29 | public class JpaDaoTestSuite { |
30 | 30 |
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; | @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; | ||
22 | import com.fasterxml.jackson.databind.node.TextNode; | 22 | import com.fasterxml.jackson.databind.node.TextNode; |
23 | import org.junit.runner.RunWith; | 23 | import org.junit.runner.RunWith; |
24 | import org.springframework.beans.factory.annotation.Autowired; | 24 | import org.springframework.beans.factory.annotation.Autowired; |
25 | +import org.springframework.context.annotation.Bean; | ||
25 | import org.springframework.context.annotation.ComponentScan; | 26 | import org.springframework.context.annotation.ComponentScan; |
26 | import org.springframework.context.annotation.Configuration; | 27 | import org.springframework.context.annotation.Configuration; |
27 | import org.springframework.test.annotation.DirtiesContext; | 28 | import org.springframework.test.annotation.DirtiesContext; |
@@ -29,6 +30,7 @@ import org.springframework.test.context.ContextConfiguration; | @@ -29,6 +30,7 @@ import org.springframework.test.context.ContextConfiguration; | ||
29 | import org.springframework.test.context.junit4.SpringRunner; | 30 | import org.springframework.test.context.junit4.SpringRunner; |
30 | import org.springframework.test.context.support.AnnotationConfigContextLoader; | 31 | import org.springframework.test.context.support.AnnotationConfigContextLoader; |
31 | import org.thingsboard.server.common.data.BaseData; | 32 | import org.thingsboard.server.common.data.BaseData; |
33 | +import org.thingsboard.server.common.data.EntityType; | ||
32 | import org.thingsboard.server.common.data.Event; | 34 | import org.thingsboard.server.common.data.Event; |
33 | import org.thingsboard.server.common.data.id.EntityId; | 35 | import org.thingsboard.server.common.data.id.EntityId; |
34 | import org.thingsboard.server.common.data.id.TenantId; | 36 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -40,6 +42,8 @@ import org.thingsboard.server.common.data.plugin.PluginMetaData; | @@ -40,6 +42,8 @@ import org.thingsboard.server.common.data.plugin.PluginMetaData; | ||
40 | import org.thingsboard.server.common.data.rule.RuleMetaData; | 42 | import org.thingsboard.server.common.data.rule.RuleMetaData; |
41 | import org.thingsboard.server.dao.alarm.AlarmService; | 43 | import org.thingsboard.server.dao.alarm.AlarmService; |
42 | import org.thingsboard.server.dao.asset.AssetService; | 44 | import org.thingsboard.server.dao.asset.AssetService; |
45 | +import org.thingsboard.server.dao.audit.AuditLogLevelFilter; | ||
46 | +import org.thingsboard.server.dao.audit.AuditLogLevelMask; | ||
43 | import org.thingsboard.server.dao.component.ComponentDescriptorService; | 47 | import org.thingsboard.server.dao.component.ComponentDescriptorService; |
44 | import org.thingsboard.server.dao.customer.CustomerService; | 48 | import org.thingsboard.server.dao.customer.CustomerService; |
45 | import org.thingsboard.server.dao.dashboard.DashboardService; | 49 | import org.thingsboard.server.dao.dashboard.DashboardService; |
@@ -58,6 +62,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; | @@ -58,6 +62,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService; | ||
58 | 62 | ||
59 | import java.io.IOException; | 63 | import java.io.IOException; |
60 | import java.util.Comparator; | 64 | import java.util.Comparator; |
65 | +import java.util.HashMap; | ||
66 | +import java.util.Map; | ||
61 | import java.util.UUID; | 67 | import java.util.UUID; |
62 | import java.util.concurrent.ThreadLocalRandom; | 68 | import java.util.concurrent.ThreadLocalRandom; |
63 | 69 | ||
@@ -227,4 +233,14 @@ public abstract class AbstractServiceTest { | @@ -227,4 +233,14 @@ public abstract class AbstractServiceTest { | ||
227 | oNode.set("configuration", readFromResource(configuration)); | 233 | oNode.set("configuration", readFromResource(configuration)); |
228 | return oNode; | 234 | return oNode; |
229 | } | 235 | } |
236 | + | ||
237 | + @Bean | ||
238 | + public AuditLogLevelFilter auditLogLevelFilter() { | ||
239 | + Map<String,String> mask = new HashMap<>(); | ||
240 | + for (EntityType entityType : EntityType.values()) { | ||
241 | + mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name()); | ||
242 | + } | ||
243 | + return new AuditLogLevelFilter(mask); | ||
244 | + } | ||
245 | + | ||
230 | } | 246 | } |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.sql.audit; | ||
17 | + | ||
18 | +import org.thingsboard.server.dao.AbstractJpaDaoTest; | ||
19 | + | ||
20 | +public class JpaAuditLogDaoTest extends AbstractJpaDaoTest { | ||
21 | +} |
@@ -4,6 +4,10 @@ zk.zk_dir=/thingsboard | @@ -4,6 +4,10 @@ zk.zk_dir=/thingsboard | ||
4 | 4 | ||
5 | updates.enabled=false | 5 | updates.enabled=false |
6 | 6 | ||
7 | +audit_log.enabled=true | ||
8 | +audit_log.by_tenant_partitioning=MONTHS | ||
9 | +audit_log.default_query_period=30 | ||
10 | + | ||
7 | cache.type=caffeine | 11 | cache.type=caffeine |
8 | #cache.type=redis | 12 | #cache.type=redis |
9 | 13 | ||
@@ -16,7 +20,10 @@ caffeine.specs.deviceCredentials.maxSize=100000 | @@ -16,7 +20,10 @@ caffeine.specs.deviceCredentials.maxSize=100000 | ||
16 | caffeine.specs.devices.timeToLiveInMinutes=1440 | 20 | caffeine.specs.devices.timeToLiveInMinutes=1440 |
17 | caffeine.specs.devices.maxSize=100000 | 21 | caffeine.specs.devices.maxSize=100000 |
18 | 22 | ||
23 | +caching.specs.devices.timeToLiveInMinutes=1440 | ||
24 | +caching.specs.devices.maxSize=100000 | ||
25 | + | ||
19 | redis.connection.host=localhost | 26 | redis.connection.host=localhost |
20 | redis.connection.port=6379 | 27 | redis.connection.port=6379 |
21 | redis.connection.db=0 | 28 | redis.connection.db=0 |
22 | -redis.connection.password= | ||
29 | +redis.connection.password= |
1 | DROP TABLE IF EXISTS admin_settings; | 1 | DROP TABLE IF EXISTS admin_settings; |
2 | DROP TABLE IF EXISTS alarm; | 2 | DROP TABLE IF EXISTS alarm; |
3 | DROP TABLE IF EXISTS asset; | 3 | DROP TABLE IF EXISTS asset; |
4 | +DROP TABLE IF EXISTS audit_log; | ||
4 | DROP TABLE IF EXISTS attribute_kv; | 5 | DROP TABLE IF EXISTS attribute_kv; |
5 | DROP TABLE IF EXISTS component_descriptor; | 6 | DROP TABLE IF EXISTS component_descriptor; |
6 | DROP TABLE IF EXISTS customer; | 7 | DROP TABLE IF EXISTS customer; |
@@ -15,10 +15,7 @@ | @@ -15,10 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.extensions.api.plugins; | 16 | package org.thingsboard.server.extensions.api.plugins; |
17 | 17 | ||
18 | -import org.thingsboard.server.common.data.id.CustomerId; | ||
19 | -import org.thingsboard.server.common.data.id.EntityId; | ||
20 | -import org.thingsboard.server.common.data.id.PluginId; | ||
21 | -import org.thingsboard.server.common.data.id.TenantId; | 18 | +import org.thingsboard.server.common.data.id.*; |
22 | 19 | ||
23 | import java.io.Serializable; | 20 | import java.io.Serializable; |
24 | 21 | ||
@@ -30,13 +27,18 @@ public final class PluginApiCallSecurityContext implements Serializable { | @@ -30,13 +27,18 @@ public final class PluginApiCallSecurityContext implements Serializable { | ||
30 | private final PluginId pluginId; | 27 | private final PluginId pluginId; |
31 | private final TenantId tenantId; | 28 | private final TenantId tenantId; |
32 | private final CustomerId customerId; | 29 | private final CustomerId customerId; |
30 | + private final UserId userId; | ||
31 | + private final String userName; | ||
33 | 32 | ||
34 | - public PluginApiCallSecurityContext(TenantId pluginTenantId, PluginId pluginId, TenantId tenantId, CustomerId customerId) { | 33 | + public PluginApiCallSecurityContext(TenantId pluginTenantId, PluginId pluginId, TenantId tenantId, CustomerId customerId, |
34 | + UserId userId, String userName) { | ||
35 | super(); | 35 | super(); |
36 | this.pluginTenantId = pluginTenantId; | 36 | this.pluginTenantId = pluginTenantId; |
37 | this.pluginId = pluginId; | 37 | this.pluginId = pluginId; |
38 | this.tenantId = tenantId; | 38 | this.tenantId = tenantId; |
39 | this.customerId = customerId; | 39 | this.customerId = customerId; |
40 | + this.userId = userId; | ||
41 | + this.userName = userName; | ||
40 | } | 42 | } |
41 | 43 | ||
42 | public TenantId getPluginTenantId(){ | 44 | public TenantId getPluginTenantId(){ |
@@ -67,4 +69,12 @@ public final class PluginApiCallSecurityContext implements Serializable { | @@ -67,4 +69,12 @@ public final class PluginApiCallSecurityContext implements Serializable { | ||
67 | return customerId; | 69 | return customerId; |
68 | } | 70 | } |
69 | 71 | ||
72 | + public UserId getUserId() { | ||
73 | + return userId; | ||
74 | + } | ||
75 | + | ||
76 | + public String getUserName() { | ||
77 | + return userName; | ||
78 | + } | ||
79 | + | ||
70 | } | 80 | } |
@@ -24,9 +24,7 @@ import org.thingsboard.server.common.data.kv.TsKvQuery; | @@ -24,9 +24,7 @@ import org.thingsboard.server.common.data.kv.TsKvQuery; | ||
24 | import org.thingsboard.server.common.data.relation.EntityRelation; | 24 | import org.thingsboard.server.common.data.relation.EntityRelation; |
25 | import org.thingsboard.server.common.data.relation.RelationTypeGroup; | 25 | import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
26 | import org.thingsboard.server.common.msg.cluster.ServerAddress; | 26 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
27 | -import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; | ||
28 | -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; | ||
29 | -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; | 27 | +import org.thingsboard.server.extensions.api.plugins.msg.*; |
30 | import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; | 28 | import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; |
31 | import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; | 29 | import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; |
32 | import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; | 30 | import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
@@ -60,6 +58,7 @@ public interface PluginContext { | @@ -60,6 +58,7 @@ public interface PluginContext { | ||
60 | 58 | ||
61 | void scheduleTimeoutMsg(TimeoutMsg<?> timeoutMsg); | 59 | void scheduleTimeoutMsg(TimeoutMsg<?> timeoutMsg); |
62 | 60 | ||
61 | + void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e); | ||
63 | 62 | ||
64 | /* | 63 | /* |
65 | Websocket API | 64 | Websocket API |
@@ -96,6 +95,12 @@ public interface PluginContext { | @@ -96,6 +95,12 @@ public interface PluginContext { | ||
96 | Attributes API | 95 | Attributes API |
97 | */ | 96 | */ |
98 | 97 | ||
98 | + void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, Exception e); | ||
99 | + | ||
100 | + void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e); | ||
101 | + | ||
102 | + void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e); | ||
103 | + | ||
99 | void saveAttributes(TenantId tenantId, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback); | 104 | void saveAttributes(TenantId tenantId, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback); |
100 | 105 | ||
101 | void removeAttributes(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys, PluginCallback<Void> callback); | 106 | void removeAttributes(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys, PluginCallback<Void> callback); |
@@ -18,6 +18,7 @@ package org.thingsboard.server.extensions.api.plugins.msg; | @@ -18,6 +18,7 @@ package org.thingsboard.server.extensions.api.plugins.msg; | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | import org.thingsboard.server.common.data.id.DeviceId; | 19 | import org.thingsboard.server.common.data.id.DeviceId; |
20 | import org.thingsboard.server.common.data.id.TenantId; | 20 | import org.thingsboard.server.common.data.id.TenantId; |
21 | +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; | ||
21 | 22 | ||
22 | import java.io.Serializable; | 23 | import java.io.Serializable; |
23 | import java.util.UUID; | 24 | import java.util.UUID; |
@@ -28,6 +29,7 @@ import java.util.UUID; | @@ -28,6 +29,7 @@ import java.util.UUID; | ||
28 | @Data | 29 | @Data |
29 | public class ToDeviceRpcRequest implements Serializable { | 30 | public class ToDeviceRpcRequest implements Serializable { |
30 | private final UUID id; | 31 | private final UUID id; |
32 | + private final PluginApiCallSecurityContext securityCtx; | ||
31 | private final TenantId tenantId; | 33 | private final TenantId tenantId; |
32 | private final DeviceId deviceId; | 34 | private final DeviceId deviceId; |
33 | private final boolean oneway; | 35 | private final boolean oneway; |
@@ -152,7 +152,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler { | @@ -152,7 +152,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler { | ||
152 | pendingMsgs.put(uid, requestMd); | 152 | pendingMsgs.put(uid, requestMd); |
153 | log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId); | 153 | log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId); |
154 | ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(ON_MSG_METHOD_NAME, GSON.toJson(params.get("body"))); | 154 | ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(ON_MSG_METHOD_NAME, GSON.toJson(params.get("body"))); |
155 | - ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody)); | 155 | + ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, null, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody)); |
156 | } else { | 156 | } else { |
157 | replyWithError(ctx, requestMd, RpcError.FORBIDDEN); | 157 | replyWithError(ctx, requestMd, RpcError.FORBIDDEN); |
158 | } | 158 | } |
@@ -49,7 +49,7 @@ public class RpcManager { | @@ -49,7 +49,7 @@ public class RpcManager { | ||
49 | LocalRequestMetaData md = localRpcRequests.remove(requestId); | 49 | LocalRequestMetaData md = localRpcRequests.remove(requestId); |
50 | if (md != null) { | 50 | if (md != null) { |
51 | log.trace("[{}] Processing local rpc response from device [{}]", requestId, md.getRequest().getDeviceId()); | 51 | log.trace("[{}] Processing local rpc response from device [{}]", requestId, md.getRequest().getDeviceId()); |
52 | - restHandler.reply(ctx, md.getResponseWriter(), response); | 52 | + restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), response); |
53 | } else { | 53 | } else { |
54 | log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); | 54 | log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); |
55 | } | 55 | } |
@@ -62,7 +62,7 @@ public class RpcManager { | @@ -62,7 +62,7 @@ public class RpcManager { | ||
62 | LocalRequestMetaData md = localRpcRequests.remove(requestId); | 62 | LocalRequestMetaData md = localRpcRequests.remove(requestId); |
63 | if (md != null) { | 63 | if (md != null) { |
64 | log.trace("[{}] Processing rpc timeout for local device [{}]", requestId, md.getRequest().getDeviceId()); | 64 | log.trace("[{}] Processing rpc timeout for local device [{}]", requestId, md.getRequest().getDeviceId()); |
65 | - restHandler.reply(ctx, md.getResponseWriter(), timeoutReponse); | 65 | + restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), timeoutReponse); |
66 | } | 66 | } |
67 | } | 67 | } |
68 | } | 68 | } |
@@ -94,11 +94,12 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { | @@ -94,11 +94,12 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { | ||
94 | 94 | ||
95 | private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException { | 95 | private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException { |
96 | long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout); | 96 | long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout); |
97 | + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); | ||
97 | ctx.checkAccess(deviceId, new PluginCallback<Void>() { | 98 | ctx.checkAccess(deviceId, new PluginCallback<Void>() { |
98 | @Override | 99 | @Override |
99 | public void onSuccess(PluginContext ctx, Void value) { | 100 | public void onSuccess(PluginContext ctx, Void value) { |
100 | - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); | ||
101 | ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), | 101 | ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), |
102 | + msg.getSecurityCtx(), | ||
102 | tenantId, | 103 | tenantId, |
103 | deviceId, | 104 | deviceId, |
104 | oneWay, | 105 | oneWay, |
@@ -116,15 +117,17 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { | @@ -116,15 +117,17 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { | ||
116 | } else { | 117 | } else { |
117 | response = new ResponseEntity(HttpStatus.UNAUTHORIZED); | 118 | response = new ResponseEntity(HttpStatus.UNAUTHORIZED); |
118 | } | 119 | } |
120 | + ctx.logRpcRequest(msg.getSecurityCtx(), deviceId, body, oneWay, Optional.empty(), e); | ||
119 | msg.getResponseHolder().setResult(response); | 121 | msg.getResponseHolder().setResult(response); |
120 | } | 122 | } |
121 | }); | 123 | }); |
122 | return true; | 124 | return true; |
123 | } | 125 | } |
124 | 126 | ||
125 | - public void reply(PluginContext ctx, DeferredResult<ResponseEntity> responseWriter, FromDeviceRpcResponse response) { | 127 | + public void reply(PluginContext ctx, ToDeviceRpcRequest rpcRequest, DeferredResult<ResponseEntity> responseWriter, FromDeviceRpcResponse response) { |
126 | Optional<RpcError> rpcError = response.getError(); | 128 | Optional<RpcError> rpcError = response.getError(); |
127 | if (rpcError.isPresent()) { | 129 | if (rpcError.isPresent()) { |
130 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); | ||
128 | RpcError error = rpcError.get(); | 131 | RpcError error = rpcError.get(); |
129 | switch (error) { | 132 | switch (error) { |
130 | case TIMEOUT: | 133 | case TIMEOUT: |
@@ -142,12 +145,15 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { | @@ -142,12 +145,15 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { | ||
142 | if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) { | 145 | if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) { |
143 | String data = responseData.get(); | 146 | String data = responseData.get(); |
144 | try { | 147 | try { |
148 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); | ||
145 | responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); | 149 | responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); |
146 | } catch (IOException e) { | 150 | } catch (IOException e) { |
147 | log.debug("Failed to decode device response: {}", data, e); | 151 | log.debug("Failed to decode device response: {}", data, e); |
152 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, e); | ||
148 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); | 153 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); |
149 | } | 154 | } |
150 | } else { | 155 | } else { |
156 | + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); | ||
151 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK)); | 157 | responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK)); |
152 | } | 158 | } |
153 | } | 159 | } |
@@ -77,7 +77,7 @@ public class RpcRuleMsgHandler implements RuleMsgHandler { | @@ -77,7 +77,7 @@ public class RpcRuleMsgHandler implements RuleMsgHandler { | ||
77 | @Override | 77 | @Override |
78 | public void onSuccess(PluginContext ctx, Void value) { | 78 | public void onSuccess(PluginContext ctx, Void value) { |
79 | ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(), | 79 | ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(), |
80 | - tenantId, tmpId, true, expirationTime, body) | 80 | + null, tenantId, tmpId, true, expirationTime, body) |
81 | ); | 81 | ); |
82 | log.trace("[{}] Sent RPC Call Action msg", tmpId); | 82 | log.trace("[{}] Sent RPC Call Action msg", tmpId); |
83 | } | 83 | } |
@@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.EntityType; | @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.EntityType; | ||
28 | import org.thingsboard.server.common.data.id.DeviceId; | 28 | import org.thingsboard.server.common.data.id.DeviceId; |
29 | import org.thingsboard.server.common.data.id.EntityId; | 29 | import org.thingsboard.server.common.data.id.EntityId; |
30 | import org.thingsboard.server.common.data.id.EntityIdFactory; | 30 | import org.thingsboard.server.common.data.id.EntityIdFactory; |
31 | +import org.thingsboard.server.common.data.id.UUIDBased; | ||
31 | import org.thingsboard.server.common.data.kv.*; | 32 | import org.thingsboard.server.common.data.kv.*; |
32 | import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; | 33 | import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; |
33 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; | 34 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
@@ -150,18 +151,19 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | @@ -150,18 +151,19 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | ||
150 | private void handleHttpGetAttributesValues(PluginContext ctx, PluginRestMsg msg, | 151 | private void handleHttpGetAttributesValues(PluginContext ctx, PluginRestMsg msg, |
151 | RestRequest request, String scope, EntityId entityId) throws ServletException { | 152 | RestRequest request, String scope, EntityId entityId) throws ServletException { |
152 | String keys = request.getParameter("keys", ""); | 153 | String keys = request.getParameter("keys", ""); |
153 | - | ||
154 | - PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg); | 154 | + List<String> keyList = null; |
155 | + if (!StringUtils.isEmpty(keys)) { | ||
156 | + keyList = Arrays.asList(keys.split(",")); | ||
157 | + } | ||
158 | + PluginCallback<List<AttributeKvEntry>> callback = getAttributeValuesPluginCallback(msg, scope, entityId, keyList); | ||
155 | if (!StringUtils.isEmpty(scope)) { | 159 | if (!StringUtils.isEmpty(scope)) { |
156 | - if (!StringUtils.isEmpty(keys)) { | ||
157 | - List<String> keyList = Arrays.asList(keys.split(",")); | 160 | + if (keyList != null && !keyList.isEmpty()) { |
158 | ctx.loadAttributes(entityId, scope, keyList, callback); | 161 | ctx.loadAttributes(entityId, scope, keyList, callback); |
159 | } else { | 162 | } else { |
160 | ctx.loadAttributes(entityId, scope, callback); | 163 | ctx.loadAttributes(entityId, scope, callback); |
161 | } | 164 | } |
162 | } else { | 165 | } else { |
163 | - if (!StringUtils.isEmpty(keys)) { | ||
164 | - List<String> keyList = Arrays.asList(keys.split(",")); | 166 | + if (keyList != null && !keyList.isEmpty()) { |
165 | ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), keyList, callback); | 167 | ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), keyList, callback); |
166 | } else { | 168 | } else { |
167 | ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), callback); | 169 | ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), callback); |
@@ -230,9 +232,11 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | @@ -230,9 +232,11 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | ||
230 | if (attributes.isEmpty()) { | 232 | if (attributes.isEmpty()) { |
231 | throw new IllegalArgumentException("No attributes data found in request body!"); | 233 | throw new IllegalArgumentException("No attributes data found in request body!"); |
232 | } | 234 | } |
235 | + | ||
233 | ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, attributes, new PluginCallback<Void>() { | 236 | ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, attributes, new PluginCallback<Void>() { |
234 | @Override | 237 | @Override |
235 | public void onSuccess(PluginContext ctx, Void value) { | 238 | public void onSuccess(PluginContext ctx, Void value) { |
239 | + ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, null); | ||
236 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); | 240 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); |
237 | subscriptionManager.onAttributesUpdateFromServer(ctx, entityId, scope, attributes); | 241 | subscriptionManager.onAttributesUpdateFromServer(ctx, entityId, scope, attributes); |
238 | } | 242 | } |
@@ -240,6 +244,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | @@ -240,6 +244,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | ||
240 | @Override | 244 | @Override |
241 | public void onFailure(PluginContext ctx, Exception e) { | 245 | public void onFailure(PluginContext ctx, Exception e) { |
242 | log.error("Failed to save attributes", e); | 246 | log.error("Failed to save attributes", e); |
247 | + ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, e); | ||
243 | handleError(e, msg, HttpStatus.BAD_REQUEST); | 248 | handleError(e, msg, HttpStatus.BAD_REQUEST); |
244 | } | 249 | } |
245 | }); | 250 | }); |
@@ -334,15 +339,18 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | @@ -334,15 +339,18 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | ||
334 | String keysParam = request.getParameter("keys"); | 339 | String keysParam = request.getParameter("keys"); |
335 | if (!StringUtils.isEmpty(keysParam)) { | 340 | if (!StringUtils.isEmpty(keysParam)) { |
336 | String[] keys = keysParam.split(","); | 341 | String[] keys = keysParam.split(","); |
337 | - ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, Arrays.asList(keys), new PluginCallback<Void>() { | 342 | + List<String> keyList = Arrays.asList(keys); |
343 | + ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, keyList, new PluginCallback<Void>() { | ||
338 | @Override | 344 | @Override |
339 | public void onSuccess(PluginContext ctx, Void value) { | 345 | public void onSuccess(PluginContext ctx, Void value) { |
346 | + ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, null); | ||
340 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); | 347 | msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); |
341 | } | 348 | } |
342 | 349 | ||
343 | @Override | 350 | @Override |
344 | public void onFailure(PluginContext ctx, Exception e) { | 351 | public void onFailure(PluginContext ctx, Exception e) { |
345 | log.error("Failed to remove attributes", e); | 352 | log.error("Failed to remove attributes", e); |
353 | + ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, e); | ||
346 | handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR); | 354 | handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR); |
347 | } | 355 | } |
348 | }); | 356 | }); |
@@ -373,18 +381,21 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | @@ -373,18 +381,21 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { | ||
373 | }; | 381 | }; |
374 | } | 382 | } |
375 | 383 | ||
376 | - private PluginCallback<List<AttributeKvEntry>> getAttributeValuesPluginCallback(final PluginRestMsg msg) { | 384 | + private PluginCallback<List<AttributeKvEntry>> getAttributeValuesPluginCallback(final PluginRestMsg msg, final String scope, |
385 | + final EntityId entityId, final List<String> keyList) { | ||
377 | return new PluginCallback<List<AttributeKvEntry>>() { | 386 | return new PluginCallback<List<AttributeKvEntry>>() { |
378 | @Override | 387 | @Override |
379 | public void onSuccess(PluginContext ctx, List<AttributeKvEntry> attributes) { | 388 | public void onSuccess(PluginContext ctx, List<AttributeKvEntry> attributes) { |
380 | List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(), | 389 | List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(), |
381 | attribute.getKey(), attribute.getValue())).collect(Collectors.toList()); | 390 | attribute.getKey(), attribute.getValue())).collect(Collectors.toList()); |
391 | + ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, null); | ||
382 | msg.getResponseHolder().setResult(new ResponseEntity<>(values, HttpStatus.OK)); | 392 | msg.getResponseHolder().setResult(new ResponseEntity<>(values, HttpStatus.OK)); |
383 | } | 393 | } |
384 | 394 | ||
385 | @Override | 395 | @Override |
386 | public void onFailure(PluginContext ctx, Exception e) { | 396 | public void onFailure(PluginContext ctx, Exception e) { |
387 | log.error("Failed to fetch attributes", e); | 397 | log.error("Failed to fetch attributes", e); |
398 | + ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, e); | ||
388 | handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR); | 399 | handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR); |
389 | } | 400 | } |
390 | }; | 401 | }; |
@@ -29,7 +29,7 @@ | @@ -29,7 +29,7 @@ | ||
29 | </section> | 29 | </section> |
30 | <div flex layout="column" class="tb-alarm-container md-whiteframe-z1"> | 30 | <div flex layout="column" class="tb-alarm-container md-whiteframe-z1"> |
31 | <md-list flex layout="column" class="tb-alarm-table"> | 31 | <md-list flex layout="column" class="tb-alarm-table"> |
32 | - <md-list class="tb-row tb-header" layout="row" tb-alarm-header> | 32 | + <md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-alarm-header> |
33 | </md-list> | 33 | </md-list> |
34 | <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" | 34 | <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" |
35 | ng-show="$root.loading"></md-progress-linear> | 35 | ng-show="$root.loading"></md-progress-linear> |
@@ -39,7 +39,7 @@ | @@ -39,7 +39,7 @@ | ||
39 | class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span> | 39 | class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span> |
40 | <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> | 40 | <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> |
41 | <md-list-item md-virtual-repeat="alarm in theAlarms" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> | 41 | <md-list-item md-virtual-repeat="alarm in theAlarms" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> |
42 | - <md-list class="tb-row" flex layout="row" tb-alarm-row alarm="{{alarm}}"> | 42 | + <md-list class="tb-row" flex layout="row" layout-align="start center" tb-alarm-row alarm="{{alarm}}"> |
43 | </md-list> | 43 | </md-list> |
44 | <md-divider flex></md-divider> | 44 | <md-divider flex></md-divider> |
45 | </md-list-item> | 45 | </md-list-item> |
ui/src/app/api/audit-log.service.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +export default angular.module('thingsboard.api.auditLog', []) | ||
17 | + .factory('auditLogService', AuditLogService) | ||
18 | + .name; | ||
19 | + | ||
20 | +/*@ngInject*/ | ||
21 | +function AuditLogService($http, $q) { | ||
22 | + | ||
23 | + var service = { | ||
24 | + getAuditLogsByEntityId: getAuditLogsByEntityId, | ||
25 | + getAuditLogsByUserId: getAuditLogsByUserId, | ||
26 | + getAuditLogsByCustomerId: getAuditLogsByCustomerId, | ||
27 | + getAuditLogs: getAuditLogs | ||
28 | + } | ||
29 | + | ||
30 | + return service; | ||
31 | + | ||
32 | + function getAuditLogsByEntityId (entityType, entityId, pageLink) { | ||
33 | + var deferred = $q.defer(); | ||
34 | + var url = `/api/audit/logs/entity/${entityType}/${entityId}?limit=${pageLink.limit}`; | ||
35 | + | ||
36 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | ||
37 | + url += '&startTime=' + pageLink.startTime; | ||
38 | + } | ||
39 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | ||
40 | + url += '&endTime=' + pageLink.endTime; | ||
41 | + } | ||
42 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | ||
43 | + url += '&offset=' + pageLink.idOffset; | ||
44 | + } | ||
45 | + $http.get(url, null).then(function success(response) { | ||
46 | + deferred.resolve(response.data); | ||
47 | + }, function fail() { | ||
48 | + deferred.reject(); | ||
49 | + }); | ||
50 | + return deferred.promise; | ||
51 | + } | ||
52 | + | ||
53 | + function getAuditLogsByUserId (userId, pageLink) { | ||
54 | + var deferred = $q.defer(); | ||
55 | + var url = `/api/audit/logs/user/${userId}?limit=${pageLink.limit}`; | ||
56 | + | ||
57 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | ||
58 | + url += '&startTime=' + pageLink.startTime; | ||
59 | + } | ||
60 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | ||
61 | + url += '&endTime=' + pageLink.endTime; | ||
62 | + } | ||
63 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | ||
64 | + url += '&offset=' + pageLink.idOffset; | ||
65 | + } | ||
66 | + $http.get(url, null).then(function success(response) { | ||
67 | + deferred.resolve(response.data); | ||
68 | + }, function fail() { | ||
69 | + deferred.reject(); | ||
70 | + }); | ||
71 | + return deferred.promise; | ||
72 | + } | ||
73 | + | ||
74 | + function getAuditLogsByCustomerId (customerId, pageLink) { | ||
75 | + var deferred = $q.defer(); | ||
76 | + var url = `/api/audit/logs/customer/${customerId}?limit=${pageLink.limit}`; | ||
77 | + | ||
78 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | ||
79 | + url += '&startTime=' + pageLink.startTime; | ||
80 | + } | ||
81 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | ||
82 | + url += '&endTime=' + pageLink.endTime; | ||
83 | + } | ||
84 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | ||
85 | + url += '&offset=' + pageLink.idOffset; | ||
86 | + } | ||
87 | + $http.get(url, null).then(function success(response) { | ||
88 | + deferred.resolve(response.data); | ||
89 | + }, function fail() { | ||
90 | + deferred.reject(); | ||
91 | + }); | ||
92 | + return deferred.promise; | ||
93 | + } | ||
94 | + | ||
95 | + function getAuditLogs (pageLink) { | ||
96 | + var deferred = $q.defer(); | ||
97 | + var url = `/api/audit/logs?limit=${pageLink.limit}`; | ||
98 | + | ||
99 | + if (angular.isDefined(pageLink.startTime) && pageLink.startTime != null) { | ||
100 | + url += '&startTime=' + pageLink.startTime; | ||
101 | + } | ||
102 | + if (angular.isDefined(pageLink.endTime) && pageLink.endTime != null) { | ||
103 | + url += '&endTime=' + pageLink.endTime; | ||
104 | + } | ||
105 | + if (angular.isDefined(pageLink.idOffset) && pageLink.idOffset != null) { | ||
106 | + url += '&offset=' + pageLink.idOffset; | ||
107 | + } | ||
108 | + $http.get(url, null).then(function success(response) { | ||
109 | + deferred.resolve(response.data); | ||
110 | + }, function fail() { | ||
111 | + deferred.reject(); | ||
112 | + }); | ||
113 | + return deferred.promise; | ||
114 | + } | ||
115 | + | ||
116 | +} |
@@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.device', [thingsboardTypes]) | @@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.device', [thingsboardTypes]) | ||
20 | .name; | 20 | .name; |
21 | 21 | ||
22 | /*@ngInject*/ | 22 | /*@ngInject*/ |
23 | -function DeviceService($http, $q, attributeService, customerService, types) { | 23 | +function DeviceService($http, $q, $window, userService, attributeService, customerService, types) { |
24 | 24 | ||
25 | var service = { | 25 | var service = { |
26 | assignDeviceToCustomer: assignDeviceToCustomer, | 26 | assignDeviceToCustomer: assignDeviceToCustomer, |
@@ -181,14 +181,27 @@ function DeviceService($http, $q, attributeService, customerService, types) { | @@ -181,14 +181,27 @@ function DeviceService($http, $q, attributeService, customerService, types) { | ||
181 | return deferred.promise; | 181 | return deferred.promise; |
182 | } | 182 | } |
183 | 183 | ||
184 | - function getDeviceCredentials(deviceId) { | 184 | + function getDeviceCredentials(deviceId, sync) { |
185 | var deferred = $q.defer(); | 185 | var deferred = $q.defer(); |
186 | var url = '/api/device/' + deviceId + '/credentials'; | 186 | var url = '/api/device/' + deviceId + '/credentials'; |
187 | - $http.get(url, null).then(function success(response) { | ||
188 | - deferred.resolve(response.data); | ||
189 | - }, function fail() { | ||
190 | - deferred.reject(); | ||
191 | - }); | 187 | + if (sync) { |
188 | + var request = new $window.XMLHttpRequest(); | ||
189 | + request.open('GET', url, false); | ||
190 | + request.setRequestHeader("Accept", "application/json, text/plain, */*"); | ||
191 | + userService.setAuthorizationRequestHeader(request); | ||
192 | + request.send(null); | ||
193 | + if (request.status === 200) { | ||
194 | + deferred.resolve(angular.fromJson(request.responseText)); | ||
195 | + } else { | ||
196 | + deferred.reject(); | ||
197 | + } | ||
198 | + } else { | ||
199 | + $http.get(url, null).then(function success(response) { | ||
200 | + deferred.resolve(response.data); | ||
201 | + }, function fail() { | ||
202 | + deferred.reject(); | ||
203 | + }); | ||
204 | + } | ||
192 | return deferred.promise; | 205 | return deferred.promise; |
193 | } | 206 | } |
194 | 207 |
@@ -54,6 +54,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -54,6 +54,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
54 | refreshJwtToken: refreshJwtToken, | 54 | refreshJwtToken: refreshJwtToken, |
55 | refreshTokenPending: refreshTokenPending, | 55 | refreshTokenPending: refreshTokenPending, |
56 | updateAuthorizationHeader: updateAuthorizationHeader, | 56 | updateAuthorizationHeader: updateAuthorizationHeader, |
57 | + setAuthorizationRequestHeader: setAuthorizationRequestHeader, | ||
57 | gotoDefaultPlace: gotoDefaultPlace, | 58 | gotoDefaultPlace: gotoDefaultPlace, |
58 | forceDefaultPlace: forceDefaultPlace, | 59 | forceDefaultPlace: forceDefaultPlace, |
59 | updateLastPublicDashboardId: updateLastPublicDashboardId, | 60 | updateLastPublicDashboardId: updateLastPublicDashboardId, |
@@ -367,6 +368,14 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | @@ -367,6 +368,14 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi | ||
367 | return jwtToken; | 368 | return jwtToken; |
368 | } | 369 | } |
369 | 370 | ||
371 | + function setAuthorizationRequestHeader(request) { | ||
372 | + var jwtToken = store.get('jwt_token'); | ||
373 | + if (jwtToken) { | ||
374 | + request.setRequestHeader('X-Authorization', 'Bearer ' + jwtToken); | ||
375 | + } | ||
376 | + return jwtToken; | ||
377 | + } | ||
378 | + | ||
370 | function getTenantAdmins(tenantId, pageLink) { | 379 | function getTenantAdmins(tenantId, pageLink) { |
371 | var deferred = $q.defer(); | 380 | var deferred = $q.defer(); |
372 | var url = '/api/tenant/' + tenantId + '/users?limit=' + pageLink.limit; | 381 | var url = '/api/tenant/' + tenantId + '/users?limit=' + pageLink.limit; |
@@ -63,6 +63,7 @@ import thingsboardApiTime from './api/time.service'; | @@ -63,6 +63,7 @@ import thingsboardApiTime from './api/time.service'; | ||
63 | import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter'; | 63 | import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter'; |
64 | import thingsboardHelp from './help/help.directive'; | 64 | import thingsboardHelp from './help/help.directive'; |
65 | import thingsboardToast from './services/toast'; | 65 | import thingsboardToast from './services/toast'; |
66 | +import thingsboardClipboard from './services/clipboard.service'; | ||
66 | import thingsboardHome from './layout'; | 67 | import thingsboardHome from './layout'; |
67 | import thingsboardApiLogin from './api/login.service'; | 68 | import thingsboardApiLogin from './api/login.service'; |
68 | import thingsboardApiDevice from './api/device.service'; | 69 | import thingsboardApiDevice from './api/device.service'; |
@@ -72,6 +73,7 @@ import thingsboardApiAsset from './api/asset.service'; | @@ -72,6 +73,7 @@ import thingsboardApiAsset from './api/asset.service'; | ||
72 | import thingsboardApiAttribute from './api/attribute.service'; | 73 | import thingsboardApiAttribute from './api/attribute.service'; |
73 | import thingsboardApiEntity from './api/entity.service'; | 74 | import thingsboardApiEntity from './api/entity.service'; |
74 | import thingsboardApiAlarm from './api/alarm.service'; | 75 | import thingsboardApiAlarm from './api/alarm.service'; |
76 | +import thingsboardApiAuditLog from './api/audit-log.service'; | ||
75 | 77 | ||
76 | import 'typeface-roboto'; | 78 | import 'typeface-roboto'; |
77 | import 'font-awesome/css/font-awesome.min.css'; | 79 | import 'font-awesome/css/font-awesome.min.css'; |
@@ -123,6 +125,7 @@ angular.module('thingsboard', [ | @@ -123,6 +125,7 @@ angular.module('thingsboard', [ | ||
123 | thingsboardKeyboardShortcut, | 125 | thingsboardKeyboardShortcut, |
124 | thingsboardHelp, | 126 | thingsboardHelp, |
125 | thingsboardToast, | 127 | thingsboardToast, |
128 | + thingsboardClipboard, | ||
126 | thingsboardHome, | 129 | thingsboardHome, |
127 | thingsboardApiLogin, | 130 | thingsboardApiLogin, |
128 | thingsboardApiDevice, | 131 | thingsboardApiDevice, |
@@ -132,6 +135,7 @@ angular.module('thingsboard', [ | @@ -132,6 +135,7 @@ angular.module('thingsboard', [ | ||
132 | thingsboardApiAttribute, | 135 | thingsboardApiAttribute, |
133 | thingsboardApiEntity, | 136 | thingsboardApiEntity, |
134 | thingsboardApiAlarm, | 137 | thingsboardApiAlarm, |
138 | + thingsboardApiAuditLog, | ||
135 | uiRouter]) | 139 | uiRouter]) |
136 | .config(AppConfig) | 140 | .config(AppConfig) |
137 | .factory('globalInterceptor', GlobalInterceptor) | 141 | .factory('globalInterceptor', GlobalInterceptor) |
@@ -66,4 +66,10 @@ | @@ -66,4 +66,10 @@ | ||
66 | entity-type="{{vm.types.entityType.asset}}"> | 66 | entity-type="{{vm.types.entityType.asset}}"> |
67 | </tb-relation-table> | 67 | </tb-relation-table> |
68 | </md-tab> | 68 | </md-tab> |
69 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | ||
70 | + <tb-audit-log-table flex entity-type="vm.types.entityType.asset" | ||
71 | + entity-id="vm.grid.operatingItem().id.id" | ||
72 | + audit-log-mode="{{vm.types.auditLogMode.entity}}"> | ||
73 | + </tb-audit-log-table> | ||
74 | + </md-tab> | ||
69 | </tb-grid> | 75 | </tb-grid> |
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +import $ from 'jquery'; | ||
17 | +import 'brace/ext/language_tools'; | ||
18 | +import 'brace/mode/java'; | ||
19 | +import 'brace/theme/github'; | ||
20 | + | ||
21 | +/* eslint-disable angular/angularelement */ | ||
22 | + | ||
23 | +import './audit-log-details-dialog.scss'; | ||
24 | + | ||
25 | +/*@ngInject*/ | ||
26 | +export default function AuditLogDetailsDialogController($mdDialog, types, auditLog, showingCallback) { | ||
27 | + | ||
28 | + var vm = this; | ||
29 | + | ||
30 | + showingCallback.onShowing = function(scope, element) { | ||
31 | + updateEditorSize(element, vm.actionData, 'tb-audit-log-action-data'); | ||
32 | + vm.actionDataEditor.resize(); | ||
33 | + if (vm.displayFailureDetails) { | ||
34 | + updateEditorSize(element, vm.actionFailureDetails, 'tb-audit-log-failure-details'); | ||
35 | + vm.failureDetailsEditor.resize(); | ||
36 | + } | ||
37 | + }; | ||
38 | + | ||
39 | + vm.types = types; | ||
40 | + vm.auditLog = auditLog; | ||
41 | + vm.displayFailureDetails = auditLog.actionStatus == types.auditLogActionStatus.FAILURE.value; | ||
42 | + vm.actionData = auditLog.actionDataText; | ||
43 | + vm.actionFailureDetails = auditLog.actionFailureDetails; | ||
44 | + | ||
45 | + vm.actionDataContentOptions = { | ||
46 | + useWrapMode: false, | ||
47 | + mode: 'java', | ||
48 | + showGutter: false, | ||
49 | + showPrintMargin: false, | ||
50 | + theme: 'github', | ||
51 | + advanced: { | ||
52 | + enableSnippets: false, | ||
53 | + enableBasicAutocompletion: false, | ||
54 | + enableLiveAutocompletion: false | ||
55 | + }, | ||
56 | + onLoad: function (_ace) { | ||
57 | + vm.actionDataEditor = _ace; | ||
58 | + } | ||
59 | + }; | ||
60 | + | ||
61 | + vm.failureDetailsContentOptions = { | ||
62 | + useWrapMode: false, | ||
63 | + mode: 'java', | ||
64 | + showGutter: false, | ||
65 | + showPrintMargin: false, | ||
66 | + theme: 'github', | ||
67 | + advanced: { | ||
68 | + enableSnippets: false, | ||
69 | + enableBasicAutocompletion: false, | ||
70 | + enableLiveAutocompletion: false | ||
71 | + }, | ||
72 | + onLoad: function (_ace) { | ||
73 | + vm.failureDetailsEditor = _ace; | ||
74 | + } | ||
75 | + }; | ||
76 | + | ||
77 | + function updateEditorSize(element, content, editorId) { | ||
78 | + var newHeight = 200; | ||
79 | + var newWidth = 600; | ||
80 | + if (content && content.length > 0) { | ||
81 | + var lines = content.split('\n'); | ||
82 | + newHeight = 16 * lines.length + 16; | ||
83 | + var maxLineLength = 0; | ||
84 | + for (var i in lines) { | ||
85 | + var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, ''); | ||
86 | + var lineLength = line.length; | ||
87 | + maxLineLength = Math.max(maxLineLength, lineLength); | ||
88 | + } | ||
89 | + newWidth = 8 * maxLineLength + 16; | ||
90 | + } | ||
91 | + $('#'+editorId, element).height(newHeight.toString() + "px").css('min-height', newHeight.toString() + "px") | ||
92 | + .width(newWidth.toString() + "px"); | ||
93 | + } | ||
94 | + | ||
95 | + vm.close = close; | ||
96 | + | ||
97 | + function close () { | ||
98 | + $mdDialog.hide(); | ||
99 | + } | ||
100 | + | ||
101 | +} | ||
102 | + | ||
103 | +/* eslint-enable angular/angularelement */ |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +#tb-audit-log-action-data, #tb-audit-log-failure-details { | ||
18 | + min-width: 400px; | ||
19 | + min-height: 50px; | ||
20 | + width: 100%; | ||
21 | + height: 100%; | ||
22 | + border: 1px solid #C0C0C0; | ||
23 | +} |
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-dialog aria-label="{{ 'audit-log.audit-log-details' | translate }}"> | ||
19 | + <md-toolbar> | ||
20 | + <div class="md-toolbar-tools"> | ||
21 | + <h2 translate>audit-log.audit-log-details</h2> | ||
22 | + <span flex></span> | ||
23 | + <md-button class="md-icon-button" ng-click="vm.close()"> | ||
24 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | ||
25 | + </md-button> | ||
26 | + </div> | ||
27 | + </md-toolbar> | ||
28 | + <md-dialog-content> | ||
29 | + <div class="md-dialog-content" layout="column"> | ||
30 | + <label translate class="tb-title no-padding">audit-log.action-data</label> | ||
31 | + <div flex id="tb-audit-log-action-data" readonly | ||
32 | + ui-ace="vm.actionDataContentOptions" | ||
33 | + ng-model="vm.actionData"> | ||
34 | + </div> | ||
35 | + <span style="height: 30px;"></span> | ||
36 | + <label ng-show="vm.displayFailureDetails" translate class="tb-title no-padding">audit-log.failure-details</label> | ||
37 | + <div ng-show="vm.displayFailureDetails" flex id="tb-audit-log-failure-details" readonly | ||
38 | + ui-ace="vm.failureDetailsContentOptions" | ||
39 | + ng-model="vm.actionFailureDetails"> | ||
40 | + </div> | ||
41 | + </div> | ||
42 | + </md-dialog-content> | ||
43 | + <md-dialog-actions layout="row"> | ||
44 | + <span flex></span> | ||
45 | + <md-button ng-disabled="$root.loading" ng-click="vm.close()" style="margin-right:20px;">{{ 'action.close' | | ||
46 | + translate }} | ||
47 | + </md-button> | ||
48 | + </md-dialog-actions> | ||
49 | +</md-dialog> |
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +/* eslint-disable import/no-unresolved, import/default */ | ||
17 | + | ||
18 | +import auditLogHeaderTemplate from './audit-log-header.tpl.html'; | ||
19 | + | ||
20 | +/* eslint-enable import/no-unresolved, import/default */ | ||
21 | + | ||
22 | +/*@ngInject*/ | ||
23 | +export default function AuditLogHeaderDirective($compile, $templateCache, types) { | ||
24 | + | ||
25 | + var linker = function (scope, element, attrs) { | ||
26 | + | ||
27 | + var template = $templateCache.get(auditLogHeaderTemplate); | ||
28 | + element.html(template); | ||
29 | + scope.auditLogMode = attrs.auditLogMode; | ||
30 | + scope.types = types; | ||
31 | + $compile(element.contents())(scope); | ||
32 | + | ||
33 | + }; | ||
34 | + | ||
35 | + return { | ||
36 | + restrict: "A", | ||
37 | + replace: false, | ||
38 | + link: linker, | ||
39 | + scope: false | ||
40 | + }; | ||
41 | +} |
ui/src/app/audit/audit-log-header.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div translate class="tb-cell" flex="30">audit-log.timestamp</div> | ||
19 | +<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="10">audit-log.entity-type</div> | ||
20 | +<div ng-if="auditLogMode != types.auditLogMode.entity" translate class="tb-cell" flex="30">audit-log.entity-name</div> | ||
21 | +<div ng-if="auditLogMode != types.auditLogMode.user" translate class="tb-cell" flex="30">audit-log.user</div> | ||
22 | +<div translate class="tb-cell" flex="15">audit-log.type</div> | ||
23 | +<div translate class="tb-cell" flex="15">audit-log.status</div> | ||
24 | +<div translate class="tb-cell" flex="10">audit-log.details</div> |
ui/src/app/audit/audit-log-row.directive.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +/* eslint-disable import/no-unresolved, import/default */ | ||
17 | + | ||
18 | +import auditLogDetailsDialogTemplate from './audit-log-details-dialog.tpl.html'; | ||
19 | + | ||
20 | +import auditLogRowTemplate from './audit-log-row.tpl.html'; | ||
21 | + | ||
22 | +/* eslint-enable import/no-unresolved, import/default */ | ||
23 | + | ||
24 | +/*@ngInject*/ | ||
25 | +export default function AuditLogRowDirective($compile, $templateCache, types, $mdDialog, $document) { | ||
26 | + | ||
27 | + var linker = function (scope, element, attrs) { | ||
28 | + | ||
29 | + var template = $templateCache.get(auditLogRowTemplate); | ||
30 | + element.html(template); | ||
31 | + | ||
32 | + scope.auditLog = attrs.auditLog; | ||
33 | + scope.auditLogMode = attrs.auditLogMode; | ||
34 | + scope.types = types; | ||
35 | + | ||
36 | + scope.showAuditLogDetails = function($event) { | ||
37 | + var onShowingCallback = { | ||
38 | + onShowing: function(){} | ||
39 | + } | ||
40 | + $mdDialog.show({ | ||
41 | + controller: 'AuditLogDetailsDialogController', | ||
42 | + controllerAs: 'vm', | ||
43 | + templateUrl: auditLogDetailsDialogTemplate, | ||
44 | + locals: { | ||
45 | + auditLog: scope.auditLog, | ||
46 | + showingCallback: onShowingCallback | ||
47 | + }, | ||
48 | + parent: angular.element($document[0].body), | ||
49 | + targetEvent: $event, | ||
50 | + fullscreen: true, | ||
51 | + skipHide: true, | ||
52 | + onShowing: function(scope, element) { | ||
53 | + onShowingCallback.onShowing(scope, element); | ||
54 | + } | ||
55 | + }); | ||
56 | + } | ||
57 | + | ||
58 | + $compile(element.contents())(scope); | ||
59 | + } | ||
60 | + | ||
61 | + return { | ||
62 | + restrict: "A", | ||
63 | + replace: false, | ||
64 | + link: linker, | ||
65 | + scope: false | ||
66 | + }; | ||
67 | +} |
ui/src/app/audit/audit-log-row.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<div class="tb-cell" flex="30">{{ auditLog.createdTime | date : 'yyyy-MM-dd HH:mm:ss' }}</div> | ||
19 | +<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="10">{{ auditLog.entityTypeText }}</div> | ||
20 | +<div ng-if="auditLogMode != types.auditLogMode.entity" class="tb-cell" flex="30">{{ auditLog.entityName }}</div> | ||
21 | +<div ng-if="auditLogMode != types.auditLogMode.user" class="tb-cell" flex="30">{{ auditLog.userName }}</div> | ||
22 | +<div class="tb-cell" flex="15">{{ auditLog.actionTypeText }}</div> | ||
23 | +<div class="tb-cell" flex="15">{{ auditLog.actionStatusText }}</div> | ||
24 | +<div class="tb-cell" flex="10"> | ||
25 | + <md-button class="md-icon-button md-primary" | ||
26 | + ng-click="showAuditLogDetails($event)" | ||
27 | + aria-label="{{ 'action.view' | translate }}"> | ||
28 | + <md-tooltip md-direction="top"> | ||
29 | + {{ 'audit-log.details' | translate }} | ||
30 | + </md-tooltip> | ||
31 | + <md-icon aria-label="{{ 'action.view' | translate }}" | ||
32 | + class="material-icons"> | ||
33 | + more_horiz | ||
34 | + </md-icon> | ||
35 | + </md-button> | ||
36 | +</div> |
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +import './audit-log.scss'; | ||
17 | + | ||
18 | +/* eslint-disable import/no-unresolved, import/default */ | ||
19 | + | ||
20 | +import auditLogTableTemplate from './audit-log-table.tpl.html'; | ||
21 | + | ||
22 | +/* eslint-enable import/no-unresolved, import/default */ | ||
23 | + | ||
24 | +/*@ngInject*/ | ||
25 | +export default function AuditLogTableDirective($compile, $templateCache, $rootScope, $filter, $translate, types, auditLogService) { | ||
26 | + | ||
27 | + var linker = function (scope, element) { | ||
28 | + | ||
29 | + var template = $templateCache.get(auditLogTableTemplate); | ||
30 | + | ||
31 | + element.html(template); | ||
32 | + | ||
33 | + scope.types = types; | ||
34 | + | ||
35 | + var pageSize = 20; | ||
36 | + var startTime = 0; | ||
37 | + var endTime = 0; | ||
38 | + | ||
39 | + scope.timewindow = { | ||
40 | + history: { | ||
41 | + timewindowMs: 24 * 60 * 60 * 1000 // 1 day | ||
42 | + } | ||
43 | + } | ||
44 | + | ||
45 | + scope.topIndex = 0; | ||
46 | + scope.searchText = ''; | ||
47 | + | ||
48 | + scope.theAuditLogs = { | ||
49 | + getItemAtIndex: function (index) { | ||
50 | + if (index > scope.auditLogs.filtered.length) { | ||
51 | + scope.theAuditLogs.fetchMoreItems_(index); | ||
52 | + return null; | ||
53 | + } | ||
54 | + return scope.auditLogs.filtered[index]; | ||
55 | + }, | ||
56 | + | ||
57 | + getLength: function () { | ||
58 | + if (scope.auditLogs.hasNext) { | ||
59 | + return scope.auditLogs.filtered.length + scope.auditLogs.nextPageLink.limit; | ||
60 | + } else { | ||
61 | + return scope.auditLogs.filtered.length; | ||
62 | + } | ||
63 | + }, | ||
64 | + | ||
65 | + fetchMoreItems_: function () { | ||
66 | + if (scope.auditLogs.hasNext && !scope.auditLogs.pending) { | ||
67 | + var promise = getAuditLogsPromise(scope.auditLogs.nextPageLink); | ||
68 | + if (promise) { | ||
69 | + scope.auditLogs.pending = true; | ||
70 | + promise.then( | ||
71 | + function success(auditLogs) { | ||
72 | + scope.auditLogs.data = scope.auditLogs.data.concat(prepareAuditLogsData(auditLogs.data)); | ||
73 | + scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText}); | ||
74 | + scope.auditLogs.nextPageLink = auditLogs.nextPageLink; | ||
75 | + scope.auditLogs.hasNext = auditLogs.hasNext; | ||
76 | + if (scope.auditLogs.hasNext) { | ||
77 | + scope.auditLogs.nextPageLink.limit = pageSize; | ||
78 | + } | ||
79 | + scope.auditLogs.pending = false; | ||
80 | + }, | ||
81 | + function fail() { | ||
82 | + scope.auditLogs.hasNext = false; | ||
83 | + scope.auditLogs.pending = false; | ||
84 | + }); | ||
85 | + } else { | ||
86 | + scope.auditLogs.hasNext = false; | ||
87 | + } | ||
88 | + } | ||
89 | + } | ||
90 | + }; | ||
91 | + | ||
92 | + function prepareAuditLogsData(data) { | ||
93 | + data.forEach( | ||
94 | + auditLog => { | ||
95 | + auditLog.entityTypeText = $translate.instant(types.entityTypeTranslations[auditLog.entityId.entityType].type); | ||
96 | + auditLog.actionTypeText = $translate.instant(types.auditLogActionType[auditLog.actionType].name); | ||
97 | + auditLog.actionStatusText = $translate.instant(types.auditLogActionStatus[auditLog.actionStatus].name); | ||
98 | + auditLog.actionDataText = auditLog.actionData ? angular.toJson(auditLog.actionData, true) : ''; | ||
99 | + } | ||
100 | + ); | ||
101 | + return data; | ||
102 | + } | ||
103 | + | ||
104 | + scope.$watch("entityId", function(newVal, prevVal) { | ||
105 | + if (newVal && !angular.equals(newVal, prevVal)) { | ||
106 | + resetFilter(); | ||
107 | + scope.reload(); | ||
108 | + } | ||
109 | + }); | ||
110 | + | ||
111 | + scope.$watch("userId", function(newVal, prevVal) { | ||
112 | + if (newVal && !angular.equals(newVal, prevVal)) { | ||
113 | + resetFilter(); | ||
114 | + scope.reload(); | ||
115 | + } | ||
116 | + }); | ||
117 | + | ||
118 | + scope.$watch("customerId", function(newVal, prevVal) { | ||
119 | + if (newVal && !angular.equals(newVal, prevVal)) { | ||
120 | + resetFilter(); | ||
121 | + scope.reload(); | ||
122 | + } | ||
123 | + }); | ||
124 | + | ||
125 | + function getAuditLogsPromise(pageLink) { | ||
126 | + switch(scope.auditLogMode) { | ||
127 | + case types.auditLogMode.tenant: | ||
128 | + return auditLogService.getAuditLogs(pageLink); | ||
129 | + case types.auditLogMode.entity: | ||
130 | + if (scope.entityType && scope.entityId) { | ||
131 | + return auditLogService.getAuditLogsByEntityId(scope.entityType, scope.entityId, | ||
132 | + pageLink); | ||
133 | + } else { | ||
134 | + return null; | ||
135 | + } | ||
136 | + case types.auditLogMode.user: | ||
137 | + if (scope.userId) { | ||
138 | + return auditLogService.getAuditLogsByUserId(scope.userId, pageLink); | ||
139 | + } else { | ||
140 | + return null; | ||
141 | + } | ||
142 | + case types.auditLogMode.customer: | ||
143 | + if (scope.customerId) { | ||
144 | + return auditLogService.getAuditLogsByCustomerId(scope.customerId, pageLink); | ||
145 | + } else { | ||
146 | + return null; | ||
147 | + } | ||
148 | + } | ||
149 | + } | ||
150 | + | ||
151 | + function destroyWatchers() { | ||
152 | + if (scope.timewindowWatchHandle) { | ||
153 | + scope.timewindowWatchHandle(); | ||
154 | + scope.timewindowWatchHandle = null; | ||
155 | + } | ||
156 | + if (scope.searchTextWatchHandle) { | ||
157 | + scope.searchTextWatchHandle(); | ||
158 | + scope.searchTextWatchHandle = null; | ||
159 | + } | ||
160 | + } | ||
161 | + | ||
162 | + function initWatchers() { | ||
163 | + scope.timewindowWatchHandle = scope.$watch("timewindow", function(newVal, prevVal) { | ||
164 | + if (newVal && !angular.equals(newVal, prevVal)) { | ||
165 | + scope.reload(); | ||
166 | + } | ||
167 | + }, true); | ||
168 | + | ||
169 | + scope.searchTextWatchHandle = scope.$watch("searchText", function(newVal, prevVal) { | ||
170 | + if (!angular.equals(newVal, prevVal)) { | ||
171 | + scope.searchTextUpdated(); | ||
172 | + } | ||
173 | + }, true); | ||
174 | + } | ||
175 | + | ||
176 | + function resetFilter() { | ||
177 | + destroyWatchers(); | ||
178 | + scope.timewindow = { | ||
179 | + history: { | ||
180 | + timewindowMs: 24 * 60 * 60 * 1000 // 1 day | ||
181 | + } | ||
182 | + }; | ||
183 | + scope.searchText = ''; | ||
184 | + initWatchers(); | ||
185 | + } | ||
186 | + | ||
187 | + function updateTimeWindowRange () { | ||
188 | + if (scope.timewindow.history.timewindowMs) { | ||
189 | + var currentTime = (new Date).getTime(); | ||
190 | + startTime = currentTime - scope.timewindow.history.timewindowMs; | ||
191 | + endTime = currentTime; | ||
192 | + } else { | ||
193 | + startTime = scope.timewindow.history.fixedTimewindow.startTimeMs; | ||
194 | + endTime = scope.timewindow.history.fixedTimewindow.endTimeMs; | ||
195 | + } | ||
196 | + } | ||
197 | + | ||
198 | + scope.reload = function() { | ||
199 | + scope.topIndex = 0; | ||
200 | + updateTimeWindowRange(); | ||
201 | + scope.auditLogs = { | ||
202 | + data: [], | ||
203 | + filtered: [], | ||
204 | + nextPageLink: { | ||
205 | + limit: pageSize, | ||
206 | + startTime: startTime, | ||
207 | + endTime: endTime | ||
208 | + }, | ||
209 | + hasNext: true, | ||
210 | + pending: false | ||
211 | + }; | ||
212 | + scope.theAuditLogs.getItemAtIndex(pageSize); | ||
213 | + } | ||
214 | + | ||
215 | + scope.searchTextUpdated = function() { | ||
216 | + scope.auditLogs.filtered = $filter('filter')(scope.auditLogs.data, {$: scope.searchText}); | ||
217 | + scope.theAuditLogs.getItemAtIndex(pageSize); | ||
218 | + } | ||
219 | + | ||
220 | + scope.noData = function() { | ||
221 | + return scope.auditLogs.data.length == 0 && !scope.auditLogs.hasNext; | ||
222 | + } | ||
223 | + | ||
224 | + scope.hasData = function() { | ||
225 | + return scope.auditLogs.data.length > 0; | ||
226 | + } | ||
227 | + | ||
228 | + scope.loading = function() { | ||
229 | + return $rootScope.loading; | ||
230 | + } | ||
231 | + | ||
232 | + scope.hasScroll = function() { | ||
233 | + var repeatContainer = scope.repeatContainer[0]; | ||
234 | + if (repeatContainer) { | ||
235 | + var scrollElement = repeatContainer.children[0]; | ||
236 | + if (scrollElement) { | ||
237 | + return scrollElement.scrollHeight > scrollElement.clientHeight; | ||
238 | + } | ||
239 | + } | ||
240 | + return false; | ||
241 | + } | ||
242 | + | ||
243 | + scope.reload(); | ||
244 | + | ||
245 | + initWatchers(); | ||
246 | + | ||
247 | + $compile(element.contents())(scope); | ||
248 | + } | ||
249 | + | ||
250 | + return { | ||
251 | + restrict: "E", | ||
252 | + link: linker, | ||
253 | + scope: { | ||
254 | + entityType: '=?', | ||
255 | + entityId: '=?', | ||
256 | + userId: '=?', | ||
257 | + customerId: '=?', | ||
258 | + auditLogMode: '@', | ||
259 | + pageMode: '@?' | ||
260 | + } | ||
261 | + }; | ||
262 | +} |
ui/src/app/audit/audit-log-table.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | +<md-content flex class="md-padding tb-absolute-fill" layout="column"> | ||
19 | + <div flex layout="column" class="tb-audit-logs" ng-class="{'md-whiteframe-z1': pageMode}"> | ||
20 | + <div layout="column" layout-gt-sm="row" layout-align-gt-sm="start center" class="tb-audit-log-toolbar" ng-class="{'md-padding': pageMode, 'tb-audit-log-margin-18px': !pageMode}"> | ||
21 | + <tb-timewindow ng-model="timewindow" history-only as-button="true"></tb-timewindow> | ||
22 | + <div flex layout="row" layout-align="start center"> | ||
23 | + <md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> | ||
24 | + <md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> | ||
25 | + <md-tooltip md-direction="top"> | ||
26 | + {{'audit-log.search' | translate}} | ||
27 | + </md-tooltip> | ||
28 | + </md-button> | ||
29 | + <md-input-container flex class="tb-audit-log-search-input"> | ||
30 | + <label> </label> | ||
31 | + <input ng-model="searchText" placeholder="{{'audit-log.search' | translate}}"/> | ||
32 | + </md-input-container> | ||
33 | + <md-button ng-disabled="$root.loading" class="md-icon-button" aria-label="Close" ng-click="searchText = ''"> | ||
34 | + <md-icon aria-label="Close" class="material-icons">close</md-icon> | ||
35 | + <md-tooltip md-direction="top"> | ||
36 | + {{ 'audit-log.clear-search' | translate }} | ||
37 | + </md-tooltip> | ||
38 | + </md-button> | ||
39 | + <md-button ng-disabled="$root.loading" | ||
40 | + class="md-icon-button" ng-click="reload()"> | ||
41 | + <md-icon>refresh</md-icon> | ||
42 | + <md-tooltip md-direction="top"> | ||
43 | + {{ 'action.refresh' | translate }} | ||
44 | + </md-tooltip> | ||
45 | + </md-button> | ||
46 | + </div> | ||
47 | + </div> | ||
48 | + <div flex layout="column" class="tb-audit-log-container" ng-class="{'md-whiteframe-z1': !pageMode}"> | ||
49 | + <md-list flex layout="column" class="tb-audit-log-table" ng-class="{'tb-audit-log-table-full': pageMode}"> | ||
50 | + <md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-audit-log-header audit-log-mode="{{auditLogMode}}"> | ||
51 | + </md-list> | ||
52 | + <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" | ||
53 | + ng-show="$root.loading"></md-progress-linear> | ||
54 | + <md-divider></md-divider> | ||
55 | + <span translate layout-align="center center" | ||
56 | + style="margin-top: 25px;" | ||
57 | + class="tb-prompt" ng-show="noData()">audit-log.no-audit-logs-prompt</span> | ||
58 | + <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> | ||
59 | + <md-list-item md-virtual-repeat="auditLog in theAuditLogs" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> | ||
60 | + <md-list class="tb-row" flex layout="row" layout-align="start center" tb-audit-log-row audit-log-mode="{{auditLogMode}}" audit-log="{{auditLog}}"> | ||
61 | + </md-list> | ||
62 | + <md-divider flex></md-divider> | ||
63 | + </md-list-item> | ||
64 | + </md-virtual-repeat-container> | ||
65 | + </md-list> | ||
66 | + </div> | ||
67 | + </div> | ||
68 | +</md-content> |
ui/src/app/audit/audit-log.routes.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +/* eslint-disable import/no-unresolved, import/default */ | ||
17 | + | ||
18 | +import auditLogsTemplate from './audit-logs.tpl.html'; | ||
19 | + | ||
20 | +/* eslint-enable import/no-unresolved, import/default */ | ||
21 | + | ||
22 | +/*@ngInject*/ | ||
23 | +export default function AuditLogRoutes($stateProvider) { | ||
24 | + $stateProvider | ||
25 | + .state('home.auditLogs', { | ||
26 | + url: '/auditLogs', | ||
27 | + module: 'private', | ||
28 | + auth: ['TENANT_ADMIN'], | ||
29 | + views: { | ||
30 | + "content@home": { | ||
31 | + templateUrl: auditLogsTemplate, | ||
32 | + controller: 'AuditLogsController', | ||
33 | + controllerAs: 'vm' | ||
34 | + } | ||
35 | + }, | ||
36 | + data: { | ||
37 | + searchEnabled: false, | ||
38 | + pageTitle: 'audit-log.audit-logs' | ||
39 | + }, | ||
40 | + ncyBreadcrumb: { | ||
41 | + label: '{"icon": "track_changes", "label": "audit-log.audit-logs"}' | ||
42 | + } | ||
43 | + }); | ||
44 | +} |
ui/src/app/audit/audit-log.scss
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +.tb-audit-logs { | ||
18 | + background-color: #fff; | ||
19 | + .tb-audit-log-margin-18px { | ||
20 | + margin-bottom: 18px; | ||
21 | + } | ||
22 | + .tb-audit-log-toolbar { | ||
23 | + font-size: 20px; | ||
24 | + } | ||
25 | + md-input-container.tb-audit-log-search-input { | ||
26 | + .md-errors-spacer { | ||
27 | + min-height: 0px; | ||
28 | + } | ||
29 | + } | ||
30 | +} | ||
31 | + | ||
32 | +.tb-audit-log-container { | ||
33 | + overflow-x: auto; | ||
34 | +} | ||
35 | + | ||
36 | + | ||
37 | + | ||
38 | +md-list.tb-audit-log-table { | ||
39 | + padding: 0px; | ||
40 | + min-width: 700px; | ||
41 | + &.tb-audit-log-table-full { | ||
42 | + min-width: 900px; | ||
43 | + } | ||
44 | + | ||
45 | + md-list-item { | ||
46 | + padding: 0px; | ||
47 | + } | ||
48 | + | ||
49 | + .tb-row { | ||
50 | + height: 48px; | ||
51 | + padding: 0px; | ||
52 | + overflow: hidden; | ||
53 | + } | ||
54 | + | ||
55 | + .tb-row:hover { | ||
56 | + background-color: #EEEEEE; | ||
57 | + } | ||
58 | + | ||
59 | + .tb-header:hover { | ||
60 | + background: none; | ||
61 | + } | ||
62 | + | ||
63 | + .tb-header { | ||
64 | + .tb-cell { | ||
65 | + color: rgba(0,0,0,.54); | ||
66 | + font-size: 12px; | ||
67 | + font-weight: 700; | ||
68 | + white-space: nowrap; | ||
69 | + background: none; | ||
70 | + } | ||
71 | + } | ||
72 | + | ||
73 | + .tb-cell { | ||
74 | + padding: 0 24px; | ||
75 | + margin: auto 0; | ||
76 | + color: rgba(0,0,0,.87); | ||
77 | + font-size: 13px; | ||
78 | + vertical-align: middle; | ||
79 | + text-align: left; | ||
80 | + overflow: hidden; | ||
81 | + .md-button { | ||
82 | + padding: 0; | ||
83 | + margin: 0; | ||
84 | + } | ||
85 | + } | ||
86 | + | ||
87 | + .tb-cell.tb-number { | ||
88 | + text-align: right; | ||
89 | + } | ||
90 | + | ||
91 | +} |
ui/src/app/audit/audit-logs.controller.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/*@ngInject*/ | ||
18 | +export default function AuditLogsController(types) { | ||
19 | + | ||
20 | + var vm = this; | ||
21 | + | ||
22 | + vm.types = types; | ||
23 | + | ||
24 | +} |
ui/src/app/audit/audit-logs.tpl.html
0 → 100644
1 | +<!-- | ||
2 | + | ||
3 | + Copyright © 2016-2017 The Thingsboard Authors | ||
4 | + | ||
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | + you may not use this file except in compliance with the License. | ||
7 | + You may obtain a copy of the License at | ||
8 | + | ||
9 | + http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | + | ||
11 | + Unless required by applicable law or agreed to in writing, software | ||
12 | + distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | + See the License for the specific language governing permissions and | ||
15 | + limitations under the License. | ||
16 | + | ||
17 | +--> | ||
18 | + | ||
19 | +<tb-audit-log-table class="md-whiteframe-z1" | ||
20 | + flex | ||
21 | + audit-log-mode="{{vm.types.auditLogMode.tenant}}" | ||
22 | + page-mode="true"> | ||
23 | +</tb-audit-log-table> |
ui/src/app/audit/index.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +import AuditLogRoutes from './audit-log.routes'; | ||
18 | +import AuditLogsController from './audit-logs.controller'; | ||
19 | +import AuditLogDetailsDialogController from './audit-log-details-dialog.controller'; | ||
20 | +import AuditLogHeaderDirective from './audit-log-header.directive'; | ||
21 | +import AuditLogRowDirective from './audit-log-row.directive'; | ||
22 | +import AuditLogTableDirective from './audit-log-table.directive'; | ||
23 | + | ||
24 | +export default angular.module('thingsboard.auditLog', []) | ||
25 | + .config(AuditLogRoutes) | ||
26 | + .controller('AuditLogsController', AuditLogsController) | ||
27 | + .controller('AuditLogDetailsDialogController', AuditLogDetailsDialogController) | ||
28 | + .directive('tbAuditLogHeader', AuditLogHeaderDirective) | ||
29 | + .directive('tbAuditLogRow', AuditLogRowDirective) | ||
30 | + .directive('tbAuditLogTable', AuditLogTableDirective) | ||
31 | + .name; |
@@ -156,6 +156,63 @@ export default angular.module('thingsboard.types', []) | @@ -156,6 +156,63 @@ export default angular.module('thingsboard.types', []) | ||
156 | color: "green" | 156 | color: "green" |
157 | } | 157 | } |
158 | }, | 158 | }, |
159 | + auditLogActionType: { | ||
160 | + "ADDED": { | ||
161 | + name: "audit-log.type-added" | ||
162 | + }, | ||
163 | + "DELETED": { | ||
164 | + name: "audit-log.type-deleted" | ||
165 | + }, | ||
166 | + "UPDATED": { | ||
167 | + name: "audit-log.type-updated" | ||
168 | + }, | ||
169 | + "ATTRIBUTES_UPDATED": { | ||
170 | + name: "audit-log.type-attributes-updated" | ||
171 | + }, | ||
172 | + "ATTRIBUTES_DELETED": { | ||
173 | + name: "audit-log.type-attributes-deleted" | ||
174 | + }, | ||
175 | + "RPC_CALL": { | ||
176 | + name: "audit-log.type-rpc-call" | ||
177 | + }, | ||
178 | + "CREDENTIALS_UPDATED": { | ||
179 | + name: "audit-log.type-credentials-updated" | ||
180 | + }, | ||
181 | + "ASSIGNED_TO_CUSTOMER": { | ||
182 | + name: "audit-log.type-assigned-to-customer" | ||
183 | + }, | ||
184 | + "UNASSIGNED_FROM_CUSTOMER": { | ||
185 | + name: "audit-log.type-unassigned-from-customer" | ||
186 | + }, | ||
187 | + "ACTIVATED": { | ||
188 | + name: "audit-log.type-activated" | ||
189 | + }, | ||
190 | + "SUSPENDED": { | ||
191 | + name: "audit-log.type-suspended" | ||
192 | + }, | ||
193 | + "CREDENTIALS_READ": { | ||
194 | + name: "audit-log.type-credentials-read" | ||
195 | + }, | ||
196 | + "ATTRIBUTES_READ": { | ||
197 | + name: "audit-log.type-attributes-read" | ||
198 | + } | ||
199 | + }, | ||
200 | + auditLogActionStatus: { | ||
201 | + "SUCCESS": { | ||
202 | + value: "SUCCESS", | ||
203 | + name: "audit-log.status-success" | ||
204 | + }, | ||
205 | + "FAILURE": { | ||
206 | + value: "FAILURE", | ||
207 | + name: "audit-log.status-failure" | ||
208 | + } | ||
209 | + }, | ||
210 | + auditLogMode: { | ||
211 | + tenant: "tenant", | ||
212 | + entity: "entity", | ||
213 | + user: "user", | ||
214 | + customer: "customer" | ||
215 | + }, | ||
159 | aliasFilterType: { | 216 | aliasFilterType: { |
160 | singleEntity: { | 217 | singleEntity: { |
161 | value: 'singleEntity', | 218 | value: 'singleEntity', |
@@ -125,7 +125,7 @@ function Grid() { | @@ -125,7 +125,7 @@ function Grid() { | ||
125 | } | 125 | } |
126 | 126 | ||
127 | /*@ngInject*/ | 127 | /*@ngInject*/ |
128 | -function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $timeout, $translate, $mdMedia, $templateCache, $window) { | 128 | +function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $timeout, $translate, $mdMedia, $templateCache, $window, userService) { |
129 | 129 | ||
130 | var vm = this; | 130 | var vm = this; |
131 | 131 | ||
@@ -157,6 +157,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $time | @@ -157,6 +157,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $time | ||
157 | vm.saveItem = saveItem; | 157 | vm.saveItem = saveItem; |
158 | vm.toggleItemSelection = toggleItemSelection; | 158 | vm.toggleItemSelection = toggleItemSelection; |
159 | vm.triggerResize = triggerResize; | 159 | vm.triggerResize = triggerResize; |
160 | + vm.isTenantAdmin = isTenantAdmin; | ||
160 | 161 | ||
161 | $scope.$watch(function () { | 162 | $scope.$watch(function () { |
162 | return $mdMedia('xs') || $mdMedia('sm'); | 163 | return $mdMedia('xs') || $mdMedia('sm'); |
@@ -634,6 +635,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $time | @@ -634,6 +635,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $mdUtil, $time | ||
634 | w.triggerHandler('resize'); | 635 | w.triggerHandler('resize'); |
635 | } | 636 | } |
636 | 637 | ||
638 | + function isTenantAdmin() { | ||
639 | + return userService.getAuthority() == 'TENANT_ADMIN'; | ||
640 | + } | ||
641 | + | ||
637 | function moveToTop() { | 642 | function moveToTop() { |
638 | moveToIndex(0, true); | 643 | moveToIndex(0, true); |
639 | } | 644 | } |
@@ -66,5 +66,10 @@ | @@ -66,5 +66,10 @@ | ||
66 | entity-type="{{vm.types.entityType.customer}}"> | 66 | entity-type="{{vm.types.entityType.customer}}"> |
67 | </tb-relation-table> | 67 | </tb-relation-table> |
68 | </md-tab> | 68 | </md-tab> |
69 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | ||
70 | + <tb-audit-log-table flex customer-id="vm.grid.operatingItem().id.id" | ||
71 | + audit-log-mode="{{vm.types.auditLogMode.customer}}"> | ||
72 | + </tb-audit-log-table> | ||
73 | + </md-tab> | ||
69 | </md-tabs> | 74 | </md-tabs> |
70 | </tb-grid> | 75 | </tb-grid> |
@@ -19,13 +19,24 @@ | @@ -19,13 +19,24 @@ | ||
19 | <details-buttons tb-help="'dashboards'" help-container-id="help-container"> | 19 | <details-buttons tb-help="'dashboards'" help-container-id="help-container"> |
20 | <div id="help-container"></div> | 20 | <div id="help-container"></div> |
21 | </details-buttons> | 21 | </details-buttons> |
22 | - <tb-dashboard-details dashboard="vm.grid.operatingItem()" | ||
23 | - is-edit="vm.grid.detailsConfig.isDetailsEditMode" | ||
24 | - dashboard-scope="vm.dashboardsScope" | ||
25 | - the-form="vm.grid.detailsForm" | ||
26 | - on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" | ||
27 | - on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)" | ||
28 | - on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" | ||
29 | - on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" | ||
30 | - on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> | 22 | + <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}" |
23 | + id="tabs" md-border-bottom flex class="tb-absolute-fill"> | ||
24 | + <md-tab label="{{ 'dashboard.details' | translate }}"> | ||
25 | + <tb-dashboard-details dashboard="vm.grid.operatingItem()" | ||
26 | + is-edit="vm.grid.detailsConfig.isDetailsEditMode" | ||
27 | + dashboard-scope="vm.dashboardsScope" | ||
28 | + the-form="vm.grid.detailsForm" | ||
29 | + on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" | ||
30 | + on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)" | ||
31 | + on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" | ||
32 | + on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" | ||
33 | + on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> | ||
34 | + </md-tab> | ||
35 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | ||
36 | + <tb-audit-log-table flex entity-type="vm.types.entityType.dashboard" | ||
37 | + entity-id="vm.grid.operatingItem().id.id" | ||
38 | + audit-log-mode="{{vm.types.auditLogMode.entity}}"> | ||
39 | + </tb-audit-log-table> | ||
40 | + </md-tab> | ||
41 | + </md-tabs> | ||
31 | </tb-grid> | 42 | </tb-grid> |
@@ -39,10 +39,8 @@ | @@ -39,10 +39,8 @@ | ||
39 | <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> | 39 | <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> |
40 | <span translate>device.copyId</span> | 40 | <span translate>device.copyId</span> |
41 | </md-button> | 41 | </md-button> |
42 | - <md-button ngclipboard data-clipboard-action="copy" | ||
43 | - ngclipboard-success="onAccessTokenCopied(e)" | ||
44 | - data-clipboard-text="{{deviceCredentials.credentialsId}}" ng-show="!isEdit" | ||
45 | - class="md-raised"> | 42 | + <md-button ng-show="!isEdit" |
43 | + class="md-raised" ng-click="copyAccessToken($event)"> | ||
46 | <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> | 44 | <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> |
47 | <span translate>device.copyAccessToken</span> | 45 | <span translate>device.copyAccessToken</span> |
48 | </md-button> | 46 | </md-button> |
@@ -20,7 +20,7 @@ import deviceFieldsetTemplate from './device-fieldset.tpl.html'; | @@ -20,7 +20,7 @@ import deviceFieldsetTemplate from './device-fieldset.tpl.html'; | ||
20 | /* eslint-enable import/no-unresolved, import/default */ | 20 | /* eslint-enable import/no-unresolved, import/default */ |
21 | 21 | ||
22 | /*@ngInject*/ | 22 | /*@ngInject*/ |
23 | -export default function DeviceDirective($compile, $templateCache, toast, $translate, types, deviceService, customerService) { | 23 | +export default function DeviceDirective($compile, $templateCache, toast, $translate, types, clipboardService, deviceService, customerService) { |
24 | var linker = function (scope, element) { | 24 | var linker = function (scope, element) { |
25 | var template = $templateCache.get(deviceFieldsetTemplate); | 25 | var template = $templateCache.get(deviceFieldsetTemplate); |
26 | element.html(template); | 26 | element.html(template); |
@@ -30,17 +30,8 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | @@ -30,17 +30,8 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | ||
30 | scope.isPublic = false; | 30 | scope.isPublic = false; |
31 | scope.assignedCustomer = null; | 31 | scope.assignedCustomer = null; |
32 | 32 | ||
33 | - scope.deviceCredentials = null; | ||
34 | - | ||
35 | scope.$watch('device', function(newVal) { | 33 | scope.$watch('device', function(newVal) { |
36 | if (newVal) { | 34 | if (newVal) { |
37 | - if (scope.device.id) { | ||
38 | - deviceService.getDeviceCredentials(scope.device.id.id).then( | ||
39 | - function success(credentials) { | ||
40 | - scope.deviceCredentials = credentials; | ||
41 | - } | ||
42 | - ); | ||
43 | - } | ||
44 | if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) { | 35 | if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) { |
45 | scope.isAssignedToCustomer = true; | 36 | scope.isAssignedToCustomer = true; |
46 | customerService.getShortCustomerInfo(scope.device.customerId.id).then( | 37 | customerService.getShortCustomerInfo(scope.device.customerId.id).then( |
@@ -61,8 +52,20 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | @@ -61,8 +52,20 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | ||
61 | toast.showSuccess($translate.instant('device.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); | 52 | toast.showSuccess($translate.instant('device.idCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); |
62 | }; | 53 | }; |
63 | 54 | ||
64 | - scope.onAccessTokenCopied = function() { | ||
65 | - toast.showSuccess($translate.instant('device.accessTokenCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); | 55 | + scope.copyAccessToken = function(e) { |
56 | + const trigger = e.delegateTarget || e.currentTarget; | ||
57 | + if (scope.device.id) { | ||
58 | + deviceService.getDeviceCredentials(scope.device.id.id, true).then( | ||
59 | + function success(credentials) { | ||
60 | + var credentialsId = credentials.credentialsId; | ||
61 | + clipboardService.copyToClipboard(trigger, credentialsId).then( | ||
62 | + () => { | ||
63 | + toast.showSuccess($translate.instant('device.accessTokenCopiedMessage'), 750, angular.element(element).parent().parent(), 'bottom left'); | ||
64 | + } | ||
65 | + ); | ||
66 | + } | ||
67 | + ); | ||
68 | + } | ||
66 | }; | 69 | }; |
67 | 70 | ||
68 | $compile(element.contents())(scope); | 71 | $compile(element.contents())(scope); |
@@ -74,4 +74,10 @@ | @@ -74,4 +74,10 @@ | ||
74 | entity-type="{{vm.types.entityType.device}}"> | 74 | entity-type="{{vm.types.entityType.device}}"> |
75 | </tb-extension-table> | 75 | </tb-extension-table> |
76 | </md-tab> | 76 | </md-tab> |
77 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | ||
78 | + <tb-audit-log-table flex entity-type="vm.types.entityType.device" | ||
79 | + entity-id="vm.grid.operatingItem().id.id" | ||
80 | + audit-log-mode="{{vm.types.auditLogMode.entity}}"> | ||
81 | + </tb-audit-log-table> | ||
82 | + </md-tab> | ||
77 | </tb-grid> | 83 | </tb-grid> |
@@ -26,9 +26,16 @@ | @@ -26,9 +26,16 @@ | ||
26 | </md-select> | 26 | </md-select> |
27 | </md-input-container> | 27 | </md-input-container> |
28 | <tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow> | 28 | <tb-timewindow flex ng-model="timewindow" history-only as-button="true"></tb-timewindow> |
29 | + <md-button ng-disabled="$root.loading" | ||
30 | + class="md-icon-button" ng-click="reload()"> | ||
31 | + <md-icon>refresh</md-icon> | ||
32 | + <md-tooltip md-direction="top"> | ||
33 | + {{ 'action.refresh' | translate }} | ||
34 | + </md-tooltip> | ||
35 | + </md-button> | ||
29 | </section> | 36 | </section> |
30 | <md-list flex layout="column" class="md-whiteframe-z1 tb-event-table"> | 37 | <md-list flex layout="column" class="md-whiteframe-z1 tb-event-table"> |
31 | - <md-list class="tb-row tb-header" layout="row" tb-event-header event-type="{{eventType}}"> | 38 | + <md-list class="tb-row tb-header" layout="row" layout-align="start center" tb-event-header event-type="{{eventType}}"> |
32 | </md-list> | 39 | </md-list> |
33 | <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" | 40 | <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading" |
34 | ng-show="$root.loading"></md-progress-linear> | 41 | ng-show="$root.loading"></md-progress-linear> |
@@ -38,7 +45,7 @@ | @@ -38,7 +45,7 @@ | ||
38 | class="tb-prompt" ng-show="noData()">event.no-events-prompt</span> | 45 | class="tb-prompt" ng-show="noData()">event.no-events-prompt</span> |
39 | <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> | 46 | <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer"> |
40 | <md-list-item md-virtual-repeat="event in theEvents" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> | 47 | <md-list-item md-virtual-repeat="event in theEvents" md-on-demand flex ng-style="hasScroll() ? {'margin-right':'-15px'} : {}"> |
41 | - <md-list class="tb-row" flex layout="row" tb-event-row event-type="{{eventType}}" event="{{event}}"> | 48 | + <md-list class="tb-row" flex layout="row" layout-align="start center" tb-event-row event-type="{{eventType}}" event="{{event}}"> |
42 | </md-list> | 49 | </md-list> |
43 | <md-divider flex></md-divider> | 50 | <md-divider flex></md-divider> |
44 | </md-list-item> | 51 | </md-list-item> |
@@ -35,6 +35,7 @@ import thingsboardUserMenu from './user-menu.directive'; | @@ -35,6 +35,7 @@ import thingsboardUserMenu from './user-menu.directive'; | ||
35 | import thingsboardEntity from '../entity'; | 35 | import thingsboardEntity from '../entity'; |
36 | import thingsboardEvent from '../event'; | 36 | import thingsboardEvent from '../event'; |
37 | import thingsboardAlarm from '../alarm'; | 37 | import thingsboardAlarm from '../alarm'; |
38 | +import thingsboardAuditLog from '../audit'; | ||
38 | import thingsboardExtension from '../extension'; | 39 | import thingsboardExtension from '../extension'; |
39 | import thingsboardTenant from '../tenant'; | 40 | import thingsboardTenant from '../tenant'; |
40 | import thingsboardCustomer from '../customer'; | 41 | import thingsboardCustomer from '../customer'; |
@@ -67,6 +68,7 @@ export default angular.module('thingsboard.home', [ | @@ -67,6 +68,7 @@ export default angular.module('thingsboard.home', [ | ||
67 | thingsboardEntity, | 68 | thingsboardEntity, |
68 | thingsboardEvent, | 69 | thingsboardEvent, |
69 | thingsboardAlarm, | 70 | thingsboardAlarm, |
71 | + thingsboardAuditLog, | ||
70 | thingsboardExtension, | 72 | thingsboardExtension, |
71 | thingsboardTenant, | 73 | thingsboardTenant, |
72 | thingsboardCustomer, | 74 | thingsboardCustomer, |
@@ -286,6 +286,38 @@ export default angular.module('thingsboard.locale', []) | @@ -286,6 +286,38 @@ export default angular.module('thingsboard.locale', []) | ||
286 | "selected-attributes": "{ count, select, 1 {1 attribute} other {# attributes} } selected", | 286 | "selected-attributes": "{ count, select, 1 {1 attribute} other {# attributes} } selected", |
287 | "selected-telemetry": "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } selected" | 287 | "selected-telemetry": "{ count, select, 1 {1 telemetry unit} other {# telemetry units} } selected" |
288 | }, | 288 | }, |
289 | + "audit-log": { | ||
290 | + "audit": "Audit", | ||
291 | + "audit-logs": "Audit Logs", | ||
292 | + "timestamp": "Timestamp", | ||
293 | + "entity-type": "Entity Type", | ||
294 | + "entity-name": "Entity Name", | ||
295 | + "user": "User", | ||
296 | + "type": "Type", | ||
297 | + "status": "Status", | ||
298 | + "details": "Details", | ||
299 | + "type-added": "Added", | ||
300 | + "type-deleted": "Deleted", | ||
301 | + "type-updated": "Updated", | ||
302 | + "type-attributes-updated": "Attributes updated", | ||
303 | + "type-attributes-deleted": "Attributes deleted", | ||
304 | + "type-rpc-call": "RPC call", | ||
305 | + "type-credentials-updated": "Credentials updated", | ||
306 | + "type-assigned-to-customer": "Assigned to Customer", | ||
307 | + "type-unassigned-from-customer": "Unassigned from Customer", | ||
308 | + "type-activated": "Activated", | ||
309 | + "type-suspended": "Suspended", | ||
310 | + "type-credentials-read": "Credentials read", | ||
311 | + "type-attributes-read": "Attributes read", | ||
312 | + "status-success": "Success", | ||
313 | + "status-failure": "Failure", | ||
314 | + "audit-log-details": "Audit log details", | ||
315 | + "no-audit-logs-prompt": "No logs found", | ||
316 | + "action-data": "Action data", | ||
317 | + "failure-details": "Failure details", | ||
318 | + "search": "Search audit logs", | ||
319 | + "clear-search": "Clear search" | ||
320 | + }, | ||
289 | "confirm-on-exit": { | 321 | "confirm-on-exit": { |
290 | "message": "You have unsaved changes. Are you sure you want to leave this page?", | 322 | "message": "You have unsaved changes. Are you sure you want to leave this page?", |
291 | "html-message": "You have unsaved changes.<br/>Are you sure you want to leave this page?", | 323 | "html-message": "You have unsaved changes.<br/>Are you sure you want to leave this page?", |
@@ -1183,7 +1215,8 @@ export default angular.module('thingsboard.locale', []) | @@ -1183,7 +1215,8 @@ export default angular.module('thingsboard.locale', []) | ||
1183 | "activation-link": "User activation link", | 1215 | "activation-link": "User activation link", |
1184 | "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :", | 1216 | "activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :", |
1185 | "copy-activation-link": "Copy activation link", | 1217 | "copy-activation-link": "Copy activation link", |
1186 | - "activation-link-copied-message": "User activation link has been copied to clipboard" | 1218 | + "activation-link-copied-message": "User activation link has been copied to clipboard", |
1219 | + "details": "Details" | ||
1187 | }, | 1220 | }, |
1188 | "value": { | 1221 | "value": { |
1189 | "type": "Value type", | 1222 | "type": "Value type", |
@@ -66,5 +66,12 @@ | @@ -66,5 +66,12 @@ | ||
66 | entity-type="{{vm.types.entityType.plugin}}"> | 66 | entity-type="{{vm.types.entityType.plugin}}"> |
67 | </tb-relation-table> | 67 | </tb-relation-table> |
68 | </md-tab> | 68 | </md-tab> |
69 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isPluginEditable(vm.grid.operatingItem()) && vm.grid.isTenantAdmin()" | ||
70 | + md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | ||
71 | + <tb-audit-log-table flex entity-type="vm.types.entityType.plugin" | ||
72 | + entity-id="vm.grid.operatingItem().id.id" | ||
73 | + audit-log-mode="{{vm.types.auditLogMode.entity}}"> | ||
74 | + </tb-audit-log-table> | ||
75 | + </md-tab> | ||
69 | </md-tabs> | 76 | </md-tabs> |
70 | </tb-grid> | 77 | </tb-grid> |
@@ -66,5 +66,12 @@ | @@ -66,5 +66,12 @@ | ||
66 | entity-type="{{vm.types.entityType.rule}}"> | 66 | entity-type="{{vm.types.entityType.rule}}"> |
67 | </tb-relation-table> | 67 | </tb-relation-table> |
68 | </md-tab> | 68 | </md-tab> |
69 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.isRuleEditable(vm.grid.operatingItem()) && vm.grid.isTenantAdmin()" | ||
70 | + md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | ||
71 | + <tb-audit-log-table flex entity-type="vm.types.entityType.rule" | ||
72 | + entity-id="vm.grid.operatingItem().id.id" | ||
73 | + audit-log-mode="{{vm.types.auditLogMode.entity}}"> | ||
74 | + </tb-audit-log-table> | ||
75 | + </md-tab> | ||
69 | </md-tabs> | 76 | </md-tabs> |
70 | </tb-grid> | 77 | </tb-grid> |
ui/src/app/services/clipboard.service.js
0 → 100644
1 | +/* | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +export default angular.module('thingsboard.clipboard', []) | ||
17 | + .factory('clipboardService', ClipboardService) | ||
18 | + .name; | ||
19 | + | ||
20 | +/*@ngInject*/ | ||
21 | +function ClipboardService($q) { | ||
22 | + | ||
23 | + var fakeHandler, fakeHandlerCallback, fakeElem; | ||
24 | + | ||
25 | + var service = { | ||
26 | + copyToClipboard: copyToClipboard | ||
27 | + }; | ||
28 | + | ||
29 | + return service; | ||
30 | + | ||
31 | + /* eslint-disable */ | ||
32 | + function copyToClipboard(trigger, text) { | ||
33 | + var deferred = $q.defer(); | ||
34 | + const isRTL = document.documentElement.getAttribute('dir') == 'rtl'; | ||
35 | + removeFake(); | ||
36 | + fakeHandlerCallback = () => removeFake(); | ||
37 | + fakeHandler = document.body.addEventListener('click', fakeHandlerCallback) || true; | ||
38 | + fakeElem = document.createElement('textarea'); | ||
39 | + fakeElem.style.fontSize = '12pt'; | ||
40 | + fakeElem.style.border = '0'; | ||
41 | + fakeElem.style.padding = '0'; | ||
42 | + fakeElem.style.margin = '0'; | ||
43 | + fakeElem.style.position = 'absolute'; | ||
44 | + fakeElem.style[ isRTL ? 'right' : 'left' ] = '-9999px'; | ||
45 | + let yPosition = window.pageYOffset || document.documentElement.scrollTop; | ||
46 | + fakeElem.style.top = `${yPosition}px`; | ||
47 | + fakeElem.setAttribute('readonly', ''); | ||
48 | + fakeElem.value = text; | ||
49 | + document.body.appendChild(fakeElem); | ||
50 | + var selectedText = select(fakeElem); | ||
51 | + | ||
52 | + let succeeded; | ||
53 | + try { | ||
54 | + succeeded = document.execCommand('copy'); | ||
55 | + } | ||
56 | + catch (err) { | ||
57 | + succeeded = false; | ||
58 | + } | ||
59 | + if (trigger) { | ||
60 | + trigger.focus(); | ||
61 | + } | ||
62 | + window.getSelection().removeAllRanges(); | ||
63 | + removeFake(); | ||
64 | + if (succeeded) { | ||
65 | + deferred.resolve(selectedText); | ||
66 | + } else { | ||
67 | + deferred.reject(); | ||
68 | + } | ||
69 | + return deferred.promise; | ||
70 | + } | ||
71 | + | ||
72 | + function removeFake() { | ||
73 | + if (fakeHandler) { | ||
74 | + document.body.removeEventListener('click', fakeHandlerCallback); | ||
75 | + fakeHandler = null; | ||
76 | + fakeHandlerCallback = null; | ||
77 | + } | ||
78 | + if (fakeElem) { | ||
79 | + document.body.removeChild(fakeElem); | ||
80 | + fakeElem = null; | ||
81 | + } | ||
82 | + } | ||
83 | + | ||
84 | + function select(element) { | ||
85 | + var selectedText; | ||
86 | + | ||
87 | + if (element.nodeName === 'SELECT') { | ||
88 | + element.focus(); | ||
89 | + | ||
90 | + selectedText = element.value; | ||
91 | + } | ||
92 | + else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { | ||
93 | + var isReadOnly = element.hasAttribute('readonly'); | ||
94 | + | ||
95 | + if (!isReadOnly) { | ||
96 | + element.setAttribute('readonly', ''); | ||
97 | + } | ||
98 | + | ||
99 | + element.select(); | ||
100 | + element.setSelectionRange(0, element.value.length); | ||
101 | + | ||
102 | + if (!isReadOnly) { | ||
103 | + element.removeAttribute('readonly'); | ||
104 | + } | ||
105 | + | ||
106 | + selectedText = element.value; | ||
107 | + } | ||
108 | + else { | ||
109 | + if (element.hasAttribute('contenteditable')) { | ||
110 | + element.focus(); | ||
111 | + } | ||
112 | + | ||
113 | + var selection = window.getSelection(); | ||
114 | + var range = document.createRange(); | ||
115 | + | ||
116 | + range.selectNodeContents(element); | ||
117 | + selection.removeAllRanges(); | ||
118 | + selection.addRange(range); | ||
119 | + | ||
120 | + selectedText = selection.toString(); | ||
121 | + } | ||
122 | + | ||
123 | + return selectedText; | ||
124 | + } | ||
125 | + | ||
126 | + /* eslint-enable */ | ||
127 | + | ||
128 | +} |
@@ -211,6 +211,12 @@ function Menu(userService, $state, $rootScope) { | @@ -211,6 +211,12 @@ function Menu(userService, $state, $rootScope) { | ||
211 | type: 'link', | 211 | type: 'link', |
212 | state: 'home.dashboards', | 212 | state: 'home.dashboards', |
213 | icon: 'dashboards' | 213 | icon: 'dashboards' |
214 | + }, | ||
215 | + { | ||
216 | + name: 'audit-log.audit-logs', | ||
217 | + type: 'link', | ||
218 | + state: 'home.auditLogs', | ||
219 | + icon: 'track_changes' | ||
214 | }]; | 220 | }]; |
215 | 221 | ||
216 | homeSections = | 222 | homeSections = |
@@ -273,6 +279,16 @@ function Menu(userService, $state, $rootScope) { | @@ -273,6 +279,16 @@ function Menu(userService, $state, $rootScope) { | ||
273 | state: 'home.dashboards' | 279 | state: 'home.dashboards' |
274 | } | 280 | } |
275 | ] | 281 | ] |
282 | + }, | ||
283 | + { | ||
284 | + name: 'audit-log.audit', | ||
285 | + places: [ | ||
286 | + { | ||
287 | + name: 'audit-log.audit-logs', | ||
288 | + icon: 'track_changes', | ||
289 | + state: 'home.auditLogs' | ||
290 | + } | ||
291 | + ] | ||
276 | }]; | 292 | }]; |
277 | 293 | ||
278 | } else if (authority === 'CUSTOMER_USER') { | 294 | } else if (authority === 'CUSTOMER_USER') { |
@@ -42,6 +42,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | @@ -42,6 +42,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d | ||
42 | 42 | ||
43 | var vm = this; | 43 | var vm = this; |
44 | 44 | ||
45 | + vm.types = types; | ||
46 | + | ||
45 | vm.userGridConfig = { | 47 | vm.userGridConfig = { |
46 | deleteItemTitleFunc: deleteUserTitle, | 48 | deleteItemTitleFunc: deleteUserTitle, |
47 | deleteItemContentFunc: deleteUserText, | 49 | deleteItemContentFunc: deleteUserText, |
@@ -19,10 +19,20 @@ | @@ -19,10 +19,20 @@ | ||
19 | <details-buttons tb-help="'users'" help-container-id="help-container"> | 19 | <details-buttons tb-help="'users'" help-container-id="help-container"> |
20 | <div id="help-container"></div> | 20 | <div id="help-container"></div> |
21 | </details-buttons> | 21 | </details-buttons> |
22 | - <tb-user user="vm.grid.operatingItem()" | ||
23 | - is-edit="vm.grid.detailsConfig.isDetailsEditMode" | ||
24 | - the-form="vm.grid.detailsForm" | ||
25 | - on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)" | ||
26 | - on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)" | ||
27 | - on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user> | 22 | + <md-tabs ng-class="{'tb-headless': vm.grid.detailsConfig.isDetailsEditMode}" |
23 | + id="tabs" md-border-bottom flex class="tb-absolute-fill"> | ||
24 | + <md-tab label="{{ 'user.details' | translate }}"> | ||
25 | + <tb-user user="vm.grid.operatingItem()" | ||
26 | + is-edit="vm.grid.detailsConfig.isDetailsEditMode" | ||
27 | + the-form="vm.grid.detailsForm" | ||
28 | + on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)" | ||
29 | + on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)" | ||
30 | + on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user> | ||
31 | + </md-tab> | ||
32 | + <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}"> | ||
33 | + <tb-audit-log-table flex user-id="vm.grid.operatingItem().id.id" | ||
34 | + audit-log-mode="{{vm.types.auditLogMode.user}}"> | ||
35 | + </tb-audit-log-table> | ||
36 | + </md-tab> | ||
37 | + </md-tabs> | ||
28 | </tb-grid> | 38 | </tb-grid> |
@@ -203,6 +203,19 @@ md-sidenav { | @@ -203,6 +203,19 @@ md-sidenav { | ||
203 | * THINGSBOARD SPECIFIC | 203 | * THINGSBOARD SPECIFIC |
204 | ***********************/ | 204 | ***********************/ |
205 | 205 | ||
206 | +label { | ||
207 | + &.tb-title { | ||
208 | + pointer-events: none; | ||
209 | + color: #666; | ||
210 | + font-size: 13px; | ||
211 | + font-weight: 400; | ||
212 | + padding-bottom: 15px; | ||
213 | + &.no-padding { | ||
214 | + padding-bottom: 0px; | ||
215 | + } | ||
216 | + } | ||
217 | +} | ||
218 | + | ||
206 | .tb-noselect { | 219 | .tb-noselect { |
207 | -webkit-touch-callout: none; /* iOS Safari */ | 220 | -webkit-touch-callout: none; /* iOS Safari */ |
208 | -webkit-user-select: none; /* Safari */ | 221 | -webkit-user-select: none; /* Safari */ |
@@ -147,7 +147,8 @@ fi.prototype.parseRules = function (rules) { | @@ -147,7 +147,8 @@ fi.prototype.parseRules = function (rules) { | ||
147 | rules = rules.split('\r\n').join('\n'); | 147 | rules = rules.split('\r\n').join('\n'); |
148 | var ret = []; | 148 | var ret = []; |
149 | 149 | ||
150 | - rules = rules.split(';'); | 150 | + // Split all rules but keep semicolon for base64 url data |
151 | + rules = rules.split(/;(?!base64)/); | ||
151 | 152 | ||
152 | //proccess rules line by line | 153 | //proccess rules line by line |
153 | for (var i = 0; i < rules.length; i++) { | 154 | for (var i = 0; i < rules.length; i++) { |