Commit def29174a337a5192a9a7898d16866396e6622ca

Authored by Andrew Shvayka
2 parents 4f13461e b90f0ea0

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 80 changed files with 3765 additions and 78 deletions

Too many changes to show.

To preserve performance only 80 of 102 files are displayed.

... ... @@ -66,7 +66,7 @@
66 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 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 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 40 import org.thingsboard.server.dao.alarm.AlarmService;
41 41 import org.thingsboard.server.dao.asset.AssetService;
42 42 import org.thingsboard.server.dao.attributes.AttributesService;
  43 +import org.thingsboard.server.dao.audit.AuditLogService;
43 44 import org.thingsboard.server.dao.customer.CustomerService;
44 45 import org.thingsboard.server.dao.device.DeviceService;
45 46 import org.thingsboard.server.dao.event.EventService;
... ... @@ -114,6 +115,9 @@ public class ActorSystemContext {
114 115 @Getter private RelationService relationService;
115 116
116 117 @Autowired
  118 + @Getter private AuditLogService auditLogService;
  119 +
  120 + @Autowired
117 121 @Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;
118 122
119 123 @Value("${actors.session.sync.timeout}")
... ...
... ... @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Device;
26 26 import org.thingsboard.server.common.data.EntityType;
27 27 import org.thingsboard.server.common.data.Tenant;
28 28 import org.thingsboard.server.common.data.asset.Asset;
  29 +import org.thingsboard.server.common.data.audit.ActionType;
29 30 import org.thingsboard.server.common.data.id.*;
30 31 import org.thingsboard.server.common.data.kv.AttributeKey;
31 32 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
... ... @@ -41,9 +42,7 @@ import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotific
41 42 import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
42 43 import org.thingsboard.server.extensions.api.plugins.PluginCallback;
43 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 46 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
48 47 import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
49 48 import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
... ... @@ -197,6 +196,52 @@ public final class PluginProcessingContext implements PluginContext {
197 196 }
198 197
199 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 245 public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) {
201 246 validate(entityId, new ValidationCallback(callback, ctx -> {
202 247 ListenableFuture<List<TsKvEntry>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys);
... ... @@ -461,6 +506,29 @@ public final class PluginProcessingContext implements PluginContext {
461 506 }
462 507
463 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 532 public void scheduleTimeoutMsg(TimeoutMsg msg) {
465 533 pluginCtx.scheduleTimeoutMsg(msg);
466 534 }
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
27 27 import org.thingsboard.server.common.data.id.PluginId;
28 28 import org.thingsboard.server.dao.asset.AssetService;
29 29 import org.thingsboard.server.dao.attributes.AttributesService;
  30 +import org.thingsboard.server.dao.audit.AuditLogService;
30 31 import org.thingsboard.server.dao.customer.CustomerService;
31 32 import org.thingsboard.server.dao.device.DeviceService;
32 33 import org.thingsboard.server.dao.plugin.PluginService;
... ... @@ -63,6 +64,7 @@ public final class SharedPluginProcessingContext {
63 64 final ClusterRpcService rpcService;
64 65 final ClusterRoutingService routingService;
65 66 final RelationService relationService;
  67 + final AuditLogService auditLogService;
66 68 final PluginId pluginId;
67 69 final TenantId tenantId;
68 70
... ... @@ -86,6 +88,7 @@ public final class SharedPluginProcessingContext {
86 88 this.customerService = sysContext.getCustomerService();
87 89 this.tenantService = sysContext.getTenantService();
88 90 this.relationService = sysContext.getRelationService();
  91 + this.auditLogService = sysContext.getAuditLogService();
89 92 }
90 93
91 94 public PluginId getPluginId() {
... ...
... ... @@ -148,7 +148,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
148 148 DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
149 149
150 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 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 40 import org.springframework.web.cors.CorsUtils;
41 41 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
42 42 import org.springframework.web.filter.CorsFilter;
  43 +import org.thingsboard.server.dao.audit.AuditLogLevelFilter;
43 44 import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
44 45 import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
45 46 import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
... ... @@ -198,4 +199,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
198 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 21 import org.springframework.web.bind.annotation.*;
22 22 import org.thingsboard.server.common.data.Customer;
23 23 import org.thingsboard.server.common.data.EntitySubtype;
  24 +import org.thingsboard.server.common.data.EntityType;
24 25 import org.thingsboard.server.common.data.asset.Asset;
  26 +import org.thingsboard.server.common.data.audit.ActionType;
25 27 import org.thingsboard.server.common.data.id.AssetId;
26 28 import org.thingsboard.server.common.data.id.CustomerId;
27 29 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -73,8 +75,16 @@ public class AssetController extends BaseController {
73 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 85 } catch (Exception e) {
  86 + logEntityAction(emptyId(EntityType.ASSET), asset,
  87 + null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
78 88 throw handleException(e);
79 89 }
80 90 }
... ... @@ -86,9 +96,18 @@ public class AssetController extends BaseController {
86 96 checkParameter(ASSET_ID, strAssetId);
87 97 try {
88 98 AssetId assetId = new AssetId(toUUID(strAssetId));
89   - checkAssetId(assetId);
  99 + Asset asset = checkAssetId(assetId);
90 100 assetService.deleteAsset(assetId);
  101 +
  102 + logEntityAction(assetId, asset,
  103 + asset.getCustomerId(),
  104 + ActionType.DELETED, null, strAssetId);
  105 +
91 106 } catch (Exception e) {
  107 + logEntityAction(emptyId(EntityType.ASSET),
  108 + null,
  109 + null,
  110 + ActionType.DELETED, e, strAssetId);
92 111 throw handleException(e);
93 112 }
94 113 }
... ... @@ -102,13 +121,24 @@ public class AssetController extends BaseController {
102 121 checkParameter(ASSET_ID, strAssetId);
103 122 try {
104 123 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
105   - checkCustomerId(customerId);
  124 + Customer customer = checkCustomerId(customerId);
106 125
107 126 AssetId assetId = new AssetId(toUUID(strAssetId));
108 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 136 } catch (Exception e) {
  137 +
  138 + logEntityAction(emptyId(EntityType.ASSET), null,
  139 + null,
  140 + ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId, strCustomerId);
  141 +
112 142 throw handleException(e);
113 143 }
114 144 }
... ... @@ -124,8 +154,22 @@ public class AssetController extends BaseController {
124 154 if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
125 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 167 } catch (Exception e) {
  168 +
  169 + logEntityAction(emptyId(EntityType.ASSET), null,
  170 + null,
  171 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strAssetId);
  172 +
129 173 throw handleException(e);
130 174 }
131 175 }
... ... @@ -139,8 +183,19 @@ public class AssetController extends BaseController {
139 183 AssetId assetId = new AssetId(toUUID(strAssetId));
140 184 Asset asset = checkAssetId(assetId);
141 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 193 } catch (Exception e) {
  194 +
  195 + logEntityAction(emptyId(EntityType.ASSET), null,
  196 + null,
  197 + ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId);
  198 +
144 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 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
18 20 import lombok.extern.slf4j.Slf4j;
19 21 import org.apache.commons.lang3.StringUtils;
20 22 import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.beans.factory.annotation.Value;
21 24 import org.springframework.security.core.Authentication;
22 25 import org.springframework.security.core.context.SecurityContextHolder;
23 26 import org.springframework.web.bind.annotation.ExceptionHandler;
... ... @@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.alarm.Alarm;
27 30 import org.thingsboard.server.common.data.alarm.AlarmId;
28 31 import org.thingsboard.server.common.data.alarm.AlarmInfo;
29 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 35 import org.thingsboard.server.common.data.id.*;
31 36 import org.thingsboard.server.common.data.page.TextPageLink;
32 37 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -39,6 +44,7 @@ import org.thingsboard.server.common.data.widget.WidgetType;
39 44 import org.thingsboard.server.common.data.widget.WidgetsBundle;
40 45 import org.thingsboard.server.dao.alarm.AlarmService;
41 46 import org.thingsboard.server.dao.asset.AssetService;
  47 +import org.thingsboard.server.dao.audit.AuditLogService;
42 48 import org.thingsboard.server.dao.customer.CustomerService;
43 49 import org.thingsboard.server.dao.dashboard.DashboardService;
44 50 import org.thingsboard.server.dao.device.DeviceCredentialsService;
... ... @@ -72,6 +78,7 @@ public abstract class BaseController {
72 78
73 79 public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
74 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 82 @Autowired
76 83 private ThingsboardErrorResponseHandler errorResponseHandler;
77 84
... ... @@ -117,6 +124,9 @@ public abstract class BaseController {
117 124 @Autowired
118 125 protected RelationService relationService;
119 126
  127 + @Autowired
  128 + protected AuditLogService auditLogService;
  129 +
120 130 @ExceptionHandler(ThingsboardException.class)
121 131 public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
122 132 errorResponseHandler.handle(ex, response);
... ... @@ -540,4 +550,20 @@ public abstract class BaseController {
540 550 serverPort);
541 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 22 import org.springframework.security.access.prepost.PreAuthorize;
23 23 import org.springframework.web.bind.annotation.*;
24 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 27 import org.thingsboard.server.common.data.id.CustomerId;
26 28 import org.thingsboard.server.common.data.id.TenantId;
27 29 import org.thingsboard.server.common.data.page.TextPageData;
... ... @@ -86,8 +88,18 @@ public class CustomerController extends BaseController {
86 88 public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException {
87 89 try {
88 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 98 } catch (Exception e) {
  99 +
  100 + logEntityAction(emptyId(EntityType.CUSTOMER), customer,
  101 + null, customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  102 +
91 103 throw handleException(e);
92 104 }
93 105 }
... ... @@ -99,9 +111,20 @@ public class CustomerController extends BaseController {
99 111 checkParameter(CUSTOMER_ID, strCustomerId);
100 112 try {
101 113 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
102   - checkCustomerId(customerId);
  114 + Customer customer = checkCustomerId(customerId);
103 115 customerService.deleteCustomer(customerId);
  116 +
  117 + logEntityAction(customerId, customer,
  118 + customer.getId(),
  119 + ActionType.DELETED, null, strCustomerId);
  120 +
104 121 } catch (Exception e) {
  122 +
  123 + logEntityAction(emptyId(EntityType.CUSTOMER),
  124 + null,
  125 + null,
  126 + ActionType.DELETED, e, strCustomerId);
  127 +
105 128 throw handleException(e);
106 129 }
107 130 }
... ...
... ... @@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.*;
21 21 import org.thingsboard.server.common.data.Customer;
22 22 import org.thingsboard.server.common.data.Dashboard;
23 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 26 import org.thingsboard.server.common.data.id.CustomerId;
25 27 import org.thingsboard.server.common.data.id.DashboardId;
26 28 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -75,8 +77,17 @@ public class DashboardController extends BaseController {
75 77 public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException {
76 78 try {
77 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 87 } catch (Exception e) {
  88 + logEntityAction(emptyId(EntityType.DASHBOARD), dashboard,
  89 + null, dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  90 +
80 91 throw handleException(e);
81 92 }
82 93 }
... ... @@ -88,9 +99,20 @@ public class DashboardController extends BaseController {
88 99 checkParameter(DASHBOARD_ID, strDashboardId);
89 100 try {
90 101 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
91   - checkDashboardId(dashboardId);
  102 + Dashboard dashboard = checkDashboardId(dashboardId);
92 103 dashboardService.deleteDashboard(dashboardId);
  104 +
  105 + logEntityAction(dashboardId, dashboard,
  106 + dashboard.getCustomerId(),
  107 + ActionType.DELETED, null, strDashboardId);
  108 +
93 109 } catch (Exception e) {
  110 +
  111 + logEntityAction(emptyId(EntityType.DASHBOARD),
  112 + null,
  113 + null,
  114 + ActionType.DELETED, e, strDashboardId);
  115 +
94 116 throw handleException(e);
95 117 }
96 118 }
... ... @@ -104,13 +126,25 @@ public class DashboardController extends BaseController {
104 126 checkParameter(DASHBOARD_ID, strDashboardId);
105 127 try {
106 128 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
107   - checkCustomerId(customerId);
  129 + Customer customer = checkCustomerId(customerId);
108 130
109 131 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
110 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 142 } catch (Exception e) {
  143 +
  144 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  145 + null,
  146 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId, strCustomerId);
  147 +
114 148 throw handleException(e);
115 149 }
116 150 }
... ... @@ -126,8 +160,22 @@ public class DashboardController extends BaseController {
126 160 if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
127 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 173 } catch (Exception e) {
  174 +
  175 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  176 + null,
  177 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
  178 +
131 179 throw handleException(e);
132 180 }
133 181 }
... ... @@ -141,8 +189,19 @@ public class DashboardController extends BaseController {
141 189 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
142 190 Dashboard dashboard = checkDashboardId(dashboardId);
143 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 199 } catch (Exception e) {
  200 +
  201 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  202 + null,
  203 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
  204 +
146 205 throw handleException(e);
147 206 }
148 207 }
... ...
... ... @@ -22,6 +22,10 @@ import org.springframework.web.bind.annotation.*;
22 22 import org.thingsboard.server.common.data.Customer;
23 23 import org.thingsboard.server.common.data.Device;
24 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 29 import org.thingsboard.server.common.data.id.CustomerId;
26 30 import org.thingsboard.server.common.data.id.DeviceId;
27 31 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -29,7 +33,6 @@ import org.thingsboard.server.common.data.page.TextPageData;
29 33 import org.thingsboard.server.common.data.page.TextPageLink;
30 34 import org.thingsboard.server.common.data.security.Authority;
31 35 import org.thingsboard.server.common.data.security.DeviceCredentials;
32   -import org.thingsboard.server.common.data.device.DeviceSearchQuery;
33 36 import org.thingsboard.server.dao.exception.IncorrectParameterException;
34 37 import org.thingsboard.server.dao.model.ModelConstants;
35 38 import org.thingsboard.server.exception.ThingsboardErrorCode;
... ... @@ -75,14 +78,22 @@ public class DeviceController extends BaseController {
75 78 }
76 79 }
77 80 Device savedDevice = checkNotNull(deviceService.saveDevice(device));
  81 +
78 82 actorService
79 83 .onDeviceNameOrTypeUpdate(
80 84 savedDevice.getTenantId(),
81 85 savedDevice.getId(),
82 86 savedDevice.getName(),
83 87 savedDevice.getType());
  88 +
  89 + logEntityAction(savedDevice.getId(), savedDevice,
  90 + savedDevice.getCustomerId(),
  91 + device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
  92 +
84 93 return savedDevice;
85 94 } catch (Exception e) {
  95 + logEntityAction(emptyId(EntityType.DEVICE), device,
  96 + null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
86 97 throw handleException(e);
87 98 }
88 99 }
... ... @@ -94,9 +105,18 @@ public class DeviceController extends BaseController {
94 105 checkParameter(DEVICE_ID, strDeviceId);
95 106 try {
96 107 DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
97   - checkDeviceId(deviceId);
  108 + Device device = checkDeviceId(deviceId);
98 109 deviceService.deleteDevice(deviceId);
  110 +
  111 + logEntityAction(deviceId, device,
  112 + device.getCustomerId(),
  113 + ActionType.DELETED, null, strDeviceId);
  114 +
99 115 } catch (Exception e) {
  116 + logEntityAction(emptyId(EntityType.DEVICE),
  117 + null,
  118 + null,
  119 + ActionType.DELETED, e, strDeviceId);
100 120 throw handleException(e);
101 121 }
102 122 }
... ... @@ -110,13 +130,22 @@ public class DeviceController extends BaseController {
110 130 checkParameter(DEVICE_ID, strDeviceId);
111 131 try {
112 132 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
113   - checkCustomerId(customerId);
  133 + Customer customer = checkCustomerId(customerId);
114 134
115 135 DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
116 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 145 } catch (Exception e) {
  146 + logEntityAction(emptyId(EntityType.DEVICE), null,
  147 + null,
  148 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId, strCustomerId);
120 149 throw handleException(e);
121 150 }
122 151 }
... ... @@ -132,8 +161,19 @@ public class DeviceController extends BaseController {
132 161 if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
133 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 173 } catch (Exception e) {
  174 + logEntityAction(emptyId(EntityType.DEVICE), null,
  175 + null,
  176 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDeviceId);
137 177 throw handleException(e);
138 178 }
139 179 }
... ... @@ -147,8 +187,17 @@ public class DeviceController extends BaseController {
147 187 DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
148 188 Device device = checkDeviceId(deviceId);
149 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 197 } catch (Exception e) {
  198 + logEntityAction(emptyId(EntityType.DEVICE), null,
  199 + null,
  200 + ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId);
152 201 throw handleException(e);
153 202 }
154 203 }
... ... @@ -160,9 +209,16 @@ public class DeviceController extends BaseController {
160 209 checkParameter(DEVICE_ID, strDeviceId);
161 210 try {
162 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 218 } catch (Exception e) {
  219 + logEntityAction(emptyId(EntityType.DEVICE), null,
  220 + null,
  221 + ActionType.CREDENTIALS_READ, e, strDeviceId);
166 222 throw handleException(e);
167 223 }
168 224 }
... ... @@ -173,11 +229,17 @@ public class DeviceController extends BaseController {
173 229 public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException {
174 230 checkNotNull(deviceCredentials);
175 231 try {
176   - checkDeviceId(deviceCredentials.getDeviceId());
  232 + Device device = checkDeviceId(deviceCredentials.getDeviceId());
177 233 DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials));
178 234 actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId());
  235 + logEntityAction(device.getId(), device,
  236 + device.getCustomerId(),
  237 + ActionType.CREDENTIALS_UPDATED, null, deviceCredentials);
179 238 return result;
180 239 } catch (Exception e) {
  240 + logEntityAction(emptyId(EntityType.DEVICE), null,
  241 + null,
  242 + ActionType.CREDENTIALS_UPDATED, e, deviceCredentials);
181 243 throw handleException(e);
182 244 }
183 245 }
... ... @@ -306,5 +368,4 @@ public class DeviceController extends BaseController {
306 368 throw handleException(e);
307 369 }
308 370 }
309   -
310 371 }
... ...
... ... @@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
18 18 import org.springframework.http.HttpStatus;
19 19 import org.springframework.security.access.prepost.PreAuthorize;
20 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 23 import org.thingsboard.server.common.data.id.PluginId;
22 24 import org.thingsboard.server.common.data.id.TenantId;
23 25 import org.thingsboard.server.common.data.page.TextPageData;
... ... @@ -71,8 +73,17 @@ public class PluginController extends BaseController {
71 73 PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source));
72 74 actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(),
73 75 created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  76 +
  77 + logEntityAction(plugin.getId(), plugin,
  78 + null,
  79 + created ? ActionType.ADDED : ActionType.UPDATED, null);
  80 +
74 81 return plugin;
75 82 } catch (Exception e) {
  83 +
  84 + logEntityAction(emptyId(EntityType.PLUGIN), source,
  85 + null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  86 +
76 87 throw handleException(e);
77 88 }
78 89 }
... ... @@ -87,7 +98,18 @@ public class PluginController extends BaseController {
87 98 PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
88 99 pluginService.activatePluginById(pluginId);
89 100 actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED);
  101 +
  102 + logEntityAction(plugin.getId(), plugin,
  103 + null,
  104 + ActionType.ACTIVATED, null, strPluginId);
  105 +
90 106 } catch (Exception e) {
  107 +
  108 + logEntityAction(emptyId(EntityType.PLUGIN),
  109 + null,
  110 + null,
  111 + ActionType.ACTIVATED, e, strPluginId);
  112 +
91 113 throw handleException(e);
92 114 }
93 115 }
... ... @@ -102,7 +124,18 @@ public class PluginController extends BaseController {
102 124 PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
103 125 pluginService.suspendPluginById(pluginId);
104 126 actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED);
  127 +
  128 + logEntityAction(plugin.getId(), plugin,
  129 + null,
  130 + ActionType.SUSPENDED, null, strPluginId);
  131 +
105 132 } catch (Exception e) {
  133 +
  134 + logEntityAction(emptyId(EntityType.PLUGIN),
  135 + null,
  136 + null,
  137 + ActionType.SUSPENDED, e, strPluginId);
  138 +
106 139 throw handleException(e);
107 140 }
108 141 }
... ... @@ -189,7 +222,16 @@ public class PluginController extends BaseController {
189 222 PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
190 223 pluginService.deletePluginById(pluginId);
191 224 actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED);
  225 +
  226 + logEntityAction(pluginId, plugin,
  227 + null,
  228 + ActionType.DELETED, null, strPluginId);
  229 +
192 230 } catch (Exception e) {
  231 + logEntityAction(emptyId(EntityType.PLUGIN),
  232 + null,
  233 + null,
  234 + ActionType.DELETED, e, strPluginId);
193 235 throw handleException(e);
194 236 }
195 237 }
... ...
... ... @@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
18 18 import org.springframework.http.HttpStatus;
19 19 import org.springframework.security.access.prepost.PreAuthorize;
20 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 23 import org.thingsboard.server.common.data.id.RuleId;
22 24 import org.thingsboard.server.common.data.id.TenantId;
23 25 import org.thingsboard.server.common.data.page.TextPageData;
... ... @@ -73,8 +75,17 @@ public class RuleController extends BaseController {
73 75 RuleMetaData rule = checkNotNull(ruleService.saveRule(source));
74 76 actorService.onRuleStateChange(rule.getTenantId(), rule.getId(),
75 77 created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  78 +
  79 + logEntityAction(rule.getId(), rule,
  80 + null,
  81 + created ? ActionType.ADDED : ActionType.UPDATED, null);
  82 +
76 83 return rule;
77 84 } catch (Exception e) {
  85 +
  86 + logEntityAction(emptyId(EntityType.RULE), source,
  87 + null, source.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  88 +
78 89 throw handleException(e);
79 90 }
80 91 }
... ... @@ -89,7 +100,18 @@ public class RuleController extends BaseController {
89 100 RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
90 101 ruleService.activateRuleById(ruleId);
91 102 actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED);
  103 +
  104 + logEntityAction(rule.getId(), rule,
  105 + null,
  106 + ActionType.ACTIVATED, null, strRuleId);
  107 +
92 108 } catch (Exception e) {
  109 +
  110 + logEntityAction(emptyId(EntityType.RULE),
  111 + null,
  112 + null,
  113 + ActionType.ACTIVATED, e, strRuleId);
  114 +
93 115 throw handleException(e);
94 116 }
95 117 }
... ... @@ -104,7 +126,18 @@ public class RuleController extends BaseController {
104 126 RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
105 127 ruleService.suspendRuleById(ruleId);
106 128 actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED);
  129 +
  130 + logEntityAction(rule.getId(), rule,
  131 + null,
  132 + ActionType.SUSPENDED, null, strRuleId);
  133 +
107 134 } catch (Exception e) {
  135 +
  136 + logEntityAction(emptyId(EntityType.RULE),
  137 + null,
  138 + null,
  139 + ActionType.SUSPENDED, e, strRuleId);
  140 +
108 141 throw handleException(e);
109 142 }
110 143 }
... ... @@ -187,7 +220,18 @@ public class RuleController extends BaseController {
187 220 RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
188 221 ruleService.deleteRuleById(ruleId);
189 222 actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED);
  223 +
  224 + logEntityAction(ruleId, rule,
  225 + null,
  226 + ActionType.DELETED, null, strRuleId);
  227 +
190 228 } catch (Exception e) {
  229 +
  230 + logEntityAction(emptyId(EntityType.RULE),
  231 + null,
  232 + null,
  233 + ActionType.DELETED, e, strRuleId);
  234 +
191 235 throw handleException(e);
192 236 }
193 237 }
... ...
... ... @@ -19,7 +19,9 @@ import org.springframework.beans.factory.annotation.Autowired;
19 19 import org.springframework.http.HttpStatus;
20 20 import org.springframework.security.access.prepost.PreAuthorize;
21 21 import org.springframework.web.bind.annotation.*;
  22 +import org.thingsboard.server.common.data.EntityType;
22 23 import org.thingsboard.server.common.data.User;
  24 +import org.thingsboard.server.common.data.audit.ActionType;
23 25 import org.thingsboard.server.common.data.id.CustomerId;
24 26 import org.thingsboard.server.common.data.id.TenantId;
25 27 import org.thingsboard.server.common.data.id.UserId;
... ... @@ -92,8 +94,17 @@ public class UserController extends BaseController {
92 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 102 return savedUser;
96 103 } catch (Exception e) {
  104 +
  105 + logEntityAction(emptyId(EntityType.USER), user,
  106 + null, user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  107 +
97 108 throw handleException(e);
98 109 }
99 110 }
... ... @@ -156,9 +167,18 @@ public class UserController extends BaseController {
156 167 checkParameter(USER_ID, strUserId);
157 168 try {
158 169 UserId userId = new UserId(toUUID(strUserId));
159   - checkUserId(userId);
  170 + User user = checkUserId(userId);
160 171 userService.deleteUser(userId);
  172 +
  173 + logEntityAction(userId, user,
  174 + user.getCustomerId(),
  175 + ActionType.DELETED, null, strUserId);
  176 +
161 177 } catch (Exception e) {
  178 + logEntityAction(emptyId(EntityType.USER),
  179 + null,
  180 + null,
  181 + ActionType.DELETED, e, strUserId);
162 182 throw handleException(e);
163 183 }
164 184 }
... ...
... ... @@ -30,6 +30,7 @@ import org.springframework.web.context.request.async.DeferredResult;
30 30 import org.thingsboard.server.actors.service.ActorService;
31 31 import org.thingsboard.server.common.data.id.CustomerId;
32 32 import org.thingsboard.server.common.data.id.TenantId;
  33 +import org.thingsboard.server.common.data.id.UserId;
33 34 import org.thingsboard.server.common.data.plugin.PluginMetaData;
34 35 import org.thingsboard.server.controller.BaseController;
35 36 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -68,7 +69,10 @@ public class PluginApiController extends BaseController {
68 69 if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){
69 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 76 actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result));
73 77 } else {
74 78 result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN));
... ...
... ... @@ -28,6 +28,7 @@ import org.springframework.context.annotation.Lazy;
28 28 import org.springframework.web.bind.annotation.RequestMapping;
29 29 import org.springframework.web.bind.annotation.RestController;
30 30 import org.thingsboard.server.actors.service.ActorService;
  31 +import org.thingsboard.server.common.data.id.UserId;
31 32 import org.thingsboard.server.config.WebSocketConfiguration;
32 33 import org.thingsboard.server.extensions.api.plugins.PluginConstants;
33 34 import org.thingsboard.server.service.security.model.SecurityUser;
... ... @@ -151,8 +152,10 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
151 152 TenantId tenantId = currentUser.getTenantId();
152 153 CustomerId customerId = currentUser.getCustomerId();
153 154 if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) {
  155 + UserId userId = currentUser.getId();
  156 + String userName = currentUser.getName();
154 157 PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId,
155   - currentUser.getCustomerId());
  158 + currentUser.getCustomerId(), userId, userName);
156 159 return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(),
157 160 session.getLocalAddress(), session.getRemoteAddress());
158 161 } else {
... ...
  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 81 case "1.3.1":
82 82 log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ...");
83 83
  84 + databaseUpgradeService.upgradeDatabase("1.3.1");
  85 +
84 86 log.info("Updating system data...");
85 87
86 88 systemDataLoaderService.deleteSystemWidgetBundle("charts");
... ...
... ... @@ -159,6 +159,12 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
159 159 break;
160 160 case "1.3.0":
161 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 168 default:
163 169 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
164 170 }
... ...
... ... @@ -61,6 +61,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
61 61 }
62 62 log.info("Schema updated.");
63 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 73 default:
65 74 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
66 75 }
... ...
... ... @@ -282,7 +282,6 @@ spring:
282 282 username: "${SPRING_DATASOURCE_USERNAME:sa}"
283 283 password: "${SPRING_DATASOURCE_PASSWORD:}"
284 284
285   -
286 285 # PostgreSQL DAO Configuration
287 286 #spring:
288 287 # data:
... ... @@ -298,3 +297,23 @@ spring:
298 297 # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"
299 298 # username: "${SPRING_DATASOURCE_USERNAME:postgres}"
300 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 66 import org.thingsboard.server.common.data.id.TenantId;
67 67 import org.thingsboard.server.common.data.id.UUIDBased;
68 68 import org.thingsboard.server.common.data.page.TextPageLink;
  69 +import org.thingsboard.server.common.data.page.TimePageLink;
69 70 import org.thingsboard.server.common.data.security.Authority;
70 71 import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
71 72 import org.thingsboard.server.service.mail.TestMailService;
... ... @@ -336,6 +337,35 @@ public abstract class AbstractControllerTest {
336 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 369 protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
340 370 return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass);
341 371 }
... ...
  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 +}
... ...
  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 +}
... ...
  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 20 import com.datastax.driver.core.ProtocolOptions.Compression;
21 21 import com.datastax.driver.mapping.Mapper;
22 22 import com.datastax.driver.mapping.MappingManager;
23   -import lombok.AccessLevel;
24   -import lombok.Data;
25   -import lombok.Getter;
26 23 import lombok.extern.slf4j.Slf4j;
27 24 import org.apache.commons.lang3.StringUtils;
28 25 import org.springframework.beans.factory.annotation.Autowired;
... ... @@ -36,7 +33,6 @@ import java.util.Collections;
36 33 import java.util.List;
37 34
38 35 @Slf4j
39   -@Data
40 36 public abstract class AbstractCassandraCluster {
41 37
42 38 private static final String COMMA = ",";
... ... @@ -77,7 +73,7 @@ public abstract class AbstractCassandraCluster {
77 73 private Cluster cluster;
78 74 private Cluster.Builder clusterBuilder;
79 75
80   - @Getter(AccessLevel.NONE) private Session session;
  76 + private Session session;
81 77
82 78 private MappingManager mappingManager;
83 79
... ... @@ -115,6 +111,10 @@ public abstract class AbstractCassandraCluster {
115 111 }
116 112 }
117 113
  114 + public Cluster getCluster() {
  115 + return cluster;
  116 + }
  117 +
118 118 public Session getSession() {
119 119 if (!isInstall()) {
120 120 return session;
... ...
... ... @@ -44,6 +44,13 @@ public class ModelConstants {
44 44 public static final String ADDITIONAL_INFO_PROPERTY = "additional_info";
45 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 55 * Cassandra user constants.
49 56 */
... ... @@ -135,6 +142,31 @@ public class ModelConstants {
135 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 170 * Cassandra asset constants.
139 171 */
140 172 public static final String ASSET_COLUMN_FAMILY_NAME = "asset";
... ... @@ -310,13 +342,6 @@ public class ModelConstants {
310 342 public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf";
311 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 345 public static final String PARTITION_COLUMN = "partition";
321 346 public static final String KEY_COLUMN = "key";
322 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 +}
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
  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 548 AND event_type IS NOT NULL AND event_uid IS NOT NULL
549 549 PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid)
550 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' };
\ No newline at end of file
... ...
... ... @@ -47,6 +47,21 @@ CREATE TABLE IF NOT EXISTS asset (
47 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 65 CREATE TABLE IF NOT EXISTS attribute_kv (
51 66 entity_type varchar(255),
52 67 entity_id varchar(31),
... ...
... ... @@ -24,7 +24,7 @@ import java.util.Arrays;
24 24
25 25 @RunWith(ClasspathSuite.class)
26 26 @ClassnameFilters({
27   - "org.thingsboard.server.dao.sql.*AAATest"
  27 + "org.thingsboard.server.dao.sql.*THIS_MUST_BE_FIXED_Test"
28 28 })
29 29 public class JpaDaoTestSuite {
30 30
... ...
... ... @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
22 22 import com.fasterxml.jackson.databind.node.TextNode;
23 23 import org.junit.runner.RunWith;
24 24 import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.context.annotation.Bean;
25 26 import org.springframework.context.annotation.ComponentScan;
26 27 import org.springframework.context.annotation.Configuration;
27 28 import org.springframework.test.annotation.DirtiesContext;
... ... @@ -29,6 +30,7 @@ import org.springframework.test.context.ContextConfiguration;
29 30 import org.springframework.test.context.junit4.SpringRunner;
30 31 import org.springframework.test.context.support.AnnotationConfigContextLoader;
31 32 import org.thingsboard.server.common.data.BaseData;
  33 +import org.thingsboard.server.common.data.EntityType;
32 34 import org.thingsboard.server.common.data.Event;
33 35 import org.thingsboard.server.common.data.id.EntityId;
34 36 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -40,6 +42,8 @@ import org.thingsboard.server.common.data.plugin.PluginMetaData;
40 42 import org.thingsboard.server.common.data.rule.RuleMetaData;
41 43 import org.thingsboard.server.dao.alarm.AlarmService;
42 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 47 import org.thingsboard.server.dao.component.ComponentDescriptorService;
44 48 import org.thingsboard.server.dao.customer.CustomerService;
45 49 import org.thingsboard.server.dao.dashboard.DashboardService;
... ... @@ -58,6 +62,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
58 62
59 63 import java.io.IOException;
60 64 import java.util.Comparator;
  65 +import java.util.HashMap;
  66 +import java.util.Map;
61 67 import java.util.UUID;
62 68 import java.util.concurrent.ThreadLocalRandom;
63 69
... ... @@ -227,4 +233,14 @@ public abstract class AbstractServiceTest {
227 233 oNode.set("configuration", readFromResource(configuration));
228 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 4
5 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 11 cache.type=caffeine
8 12 #cache.type=redis
9 13
... ... @@ -16,7 +20,10 @@ caffeine.specs.deviceCredentials.maxSize=100000
16 20 caffeine.specs.devices.timeToLiveInMinutes=1440
17 21 caffeine.specs.devices.maxSize=100000
18 22
  23 +caching.specs.devices.timeToLiveInMinutes=1440
  24 +caching.specs.devices.maxSize=100000
  25 +
19 26 redis.connection.host=localhost
20 27 redis.connection.port=6379
21 28 redis.connection.db=0
22   -redis.connection.password=
\ No newline at end of file
  29 +redis.connection.password=
... ...
1 1 DROP TABLE IF EXISTS admin_settings;
2 2 DROP TABLE IF EXISTS alarm;
3 3 DROP TABLE IF EXISTS asset;
  4 +DROP TABLE IF EXISTS audit_log;
4 5 DROP TABLE IF EXISTS attribute_kv;
5 6 DROP TABLE IF EXISTS component_descriptor;
6 7 DROP TABLE IF EXISTS customer;
... ...
... ... @@ -15,10 +15,7 @@
15 15 */
16 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 20 import java.io.Serializable;
24 21
... ... @@ -30,13 +27,18 @@ public final class PluginApiCallSecurityContext implements Serializable {
30 27 private final PluginId pluginId;
31 28 private final TenantId tenantId;
32 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 35 super();
36 36 this.pluginTenantId = pluginTenantId;
37 37 this.pluginId = pluginId;
38 38 this.tenantId = tenantId;
39 39 this.customerId = customerId;
  40 + this.userId = userId;
  41 + this.userName = userName;
40 42 }
41 43
42 44 public TenantId getPluginTenantId(){
... ... @@ -67,4 +69,12 @@ public final class PluginApiCallSecurityContext implements Serializable {
67 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 24 import org.thingsboard.server.common.data.relation.EntityRelation;
25 25 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
26 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 28 import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
31 29 import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
32 30 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
... ... @@ -60,6 +58,7 @@ public interface PluginContext {
60 58
61 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 64 Websocket API
... ... @@ -96,6 +95,12 @@ public interface PluginContext {
96 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 104 void saveAttributes(TenantId tenantId, EntityId entityId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback);
100 105
101 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 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.id.DeviceId;
20 20 import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
21 22
22 23 import java.io.Serializable;
23 24 import java.util.UUID;
... ... @@ -28,6 +29,7 @@ import java.util.UUID;
28 29 @Data
29 30 public class ToDeviceRpcRequest implements Serializable {
30 31 private final UUID id;
  32 + private final PluginApiCallSecurityContext securityCtx;
31 33 private final TenantId tenantId;
32 34 private final DeviceId deviceId;
33 35 private final boolean oneway;
... ...
... ... @@ -152,7 +152,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler {
152 152 pendingMsgs.put(uid, requestMd);
153 153 log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId);
154 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 156 } else {
157 157 replyWithError(ctx, requestMd, RpcError.FORBIDDEN);
158 158 }
... ...
... ... @@ -49,7 +49,7 @@ public class RpcManager {
49 49 LocalRequestMetaData md = localRpcRequests.remove(requestId);
50 50 if (md != null) {
51 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 53 } else {
54 54 log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
55 55 }
... ... @@ -62,7 +62,7 @@ public class RpcManager {
62 62 LocalRequestMetaData md = localRpcRequests.remove(requestId);
63 63 if (md != null) {
64 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 94
95 95 private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException {
96 96 long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout);
  97 + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
97 98 ctx.checkAccess(deviceId, new PluginCallback<Void>() {
98 99 @Override
99 100 public void onSuccess(PluginContext ctx, Void value) {
100   - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
101 101 ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
  102 + msg.getSecurityCtx(),
102 103 tenantId,
103 104 deviceId,
104 105 oneWay,
... ... @@ -116,15 +117,17 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler {
116 117 } else {
117 118 response = new ResponseEntity(HttpStatus.UNAUTHORIZED);
118 119 }
  120 + ctx.logRpcRequest(msg.getSecurityCtx(), deviceId, body, oneWay, Optional.empty(), e);
119 121 msg.getResponseHolder().setResult(response);
120 122 }
121 123 });
122 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 128 Optional<RpcError> rpcError = response.getError();
127 129 if (rpcError.isPresent()) {
  130 + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
128 131 RpcError error = rpcError.get();
129 132 switch (error) {
130 133 case TIMEOUT:
... ... @@ -142,12 +145,15 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler {
142 145 if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) {
143 146 String data = responseData.get();
144 147 try {
  148 + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
145 149 responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK));
146 150 } catch (IOException e) {
147 151 log.debug("Failed to decode device response: {}", data, e);
  152 + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, e);
148 153 responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));
149 154 }
150 155 } else {
  156 + ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null);
151 157 responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
152 158 }
153 159 }
... ...
... ... @@ -77,7 +77,7 @@ public class RpcRuleMsgHandler implements RuleMsgHandler {
77 77 @Override
78 78 public void onSuccess(PluginContext ctx, Void value) {
79 79 ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(),
80   - tenantId, tmpId, true, expirationTime, body)
  80 + null, tenantId, tmpId, true, expirationTime, body)
81 81 );
82 82 log.trace("[{}] Sent RPC Call Action msg", tmpId);
83 83 }
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.EntityType;
28 28 import org.thingsboard.server.common.data.id.DeviceId;
29 29 import org.thingsboard.server.common.data.id.EntityId;
30 30 import org.thingsboard.server.common.data.id.EntityIdFactory;
  31 +import org.thingsboard.server.common.data.id.UUIDBased;
31 32 import org.thingsboard.server.common.data.kv.*;
32 33 import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
33 34 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
... ... @@ -150,18 +151,19 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
150 151 private void handleHttpGetAttributesValues(PluginContext ctx, PluginRestMsg msg,
151 152 RestRequest request, String scope, EntityId entityId) throws ServletException {
152 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 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 161 ctx.loadAttributes(entityId, scope, keyList, callback);
159 162 } else {
160 163 ctx.loadAttributes(entityId, scope, callback);
161 164 }
162 165 } else {
163   - if (!StringUtils.isEmpty(keys)) {
164   - List<String> keyList = Arrays.asList(keys.split(","));
  166 + if (keyList != null && !keyList.isEmpty()) {
165 167 ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), keyList, callback);
166 168 } else {
167 169 ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), callback);
... ... @@ -230,9 +232,11 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
230 232 if (attributes.isEmpty()) {
231 233 throw new IllegalArgumentException("No attributes data found in request body!");
232 234 }
  235 +
233 236 ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(IllegalArgumentException::new).getTenantId(), entityId, scope, attributes, new PluginCallback<Void>() {
234 237 @Override
235 238 public void onSuccess(PluginContext ctx, Void value) {
  239 + ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, null);
236 240 msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
237 241 subscriptionManager.onAttributesUpdateFromServer(ctx, entityId, scope, attributes);
238 242 }
... ... @@ -240,6 +244,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
240 244 @Override
241 245 public void onFailure(PluginContext ctx, Exception e) {
242 246 log.error("Failed to save attributes", e);
  247 + ctx.logAttributesUpdated(msg.getSecurityCtx(), entityId, scope, attributes, e);
243 248 handleError(e, msg, HttpStatus.BAD_REQUEST);
244 249 }
245 250 });
... ... @@ -334,15 +339,18 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
334 339 String keysParam = request.getParameter("keys");
335 340 if (!StringUtils.isEmpty(keysParam)) {
336 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 344 @Override
339 345 public void onSuccess(PluginContext ctx, Void value) {
  346 + ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, null);
340 347 msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
341 348 }
342 349
343 350 @Override
344 351 public void onFailure(PluginContext ctx, Exception e) {
345 352 log.error("Failed to remove attributes", e);
  353 + ctx.logAttributesDeleted(msg.getSecurityCtx(), entityId, scope, keyList, e);
346 354 handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR);
347 355 }
348 356 });
... ... @@ -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 386 return new PluginCallback<List<AttributeKvEntry>>() {
378 387 @Override
379 388 public void onSuccess(PluginContext ctx, List<AttributeKvEntry> attributes) {
380 389 List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(),
381 390 attribute.getKey(), attribute.getValue())).collect(Collectors.toList());
  391 + ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, null);
382 392 msg.getResponseHolder().setResult(new ResponseEntity<>(values, HttpStatus.OK));
383 393 }
384 394
385 395 @Override
386 396 public void onFailure(PluginContext ctx, Exception e) {
387 397 log.error("Failed to fetch attributes", e);
  398 + ctx.logAttributesRead(msg.getSecurityCtx(), entityId, scope, keyList, e);
388 399 handleError(e, msg, HttpStatus.INTERNAL_SERVER_ERROR);
389 400 }
390 401 };
... ...
... ... @@ -29,7 +29,7 @@
29 29 </section>
30 30 <div flex layout="column" class="tb-alarm-container md-whiteframe-z1">
31 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 33 </md-list>
34 34 <md-progress-linear style="max-height: 0px;" md-mode="indeterminate" ng-disabled="!$root.loading"
35 35 ng-show="$root.loading"></md-progress-linear>
... ... @@ -39,7 +39,7 @@
39 39 class="tb-prompt" ng-show="noData()">alarm.no-alarms-prompt</span>
40 40 <md-virtual-repeat-container ng-show="hasData()" flex md-top-index="topIndex" tb-scope-element="repeatContainer">
41 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 43 </md-list>
44 44 <md-divider flex></md-divider>
45 45 </md-list-item>
... ...
  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 20 .name;
21 21
22 22 /*@ngInject*/
23   -function DeviceService($http, $q, attributeService, customerService, types) {
  23 +function DeviceService($http, $q, $window, userService, attributeService, customerService, types) {
24 24
25 25 var service = {
26 26 assignDeviceToCustomer: assignDeviceToCustomer,
... ... @@ -181,14 +181,27 @@ function DeviceService($http, $q, attributeService, customerService, types) {
181 181 return deferred.promise;
182 182 }
183 183
184   - function getDeviceCredentials(deviceId) {
  184 + function getDeviceCredentials(deviceId, sync) {
185 185 var deferred = $q.defer();
186 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 205 return deferred.promise;
193 206 }
194 207
... ...
... ... @@ -54,6 +54,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
54 54 refreshJwtToken: refreshJwtToken,
55 55 refreshTokenPending: refreshTokenPending,
56 56 updateAuthorizationHeader: updateAuthorizationHeader,
  57 + setAuthorizationRequestHeader: setAuthorizationRequestHeader,
57 58 gotoDefaultPlace: gotoDefaultPlace,
58 59 forceDefaultPlace: forceDefaultPlace,
59 60 updateLastPublicDashboardId: updateLastPublicDashboardId,
... ... @@ -367,6 +368,14 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
367 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 379 function getTenantAdmins(tenantId, pageLink) {
371 380 var deferred = $q.defer();
372 381 var url = '/api/tenant/' + tenantId + '/users?limit=' + pageLink.limit;
... ...
... ... @@ -63,6 +63,7 @@ import thingsboardApiTime from './api/time.service';
63 63 import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
64 64 import thingsboardHelp from './help/help.directive';
65 65 import thingsboardToast from './services/toast';
  66 +import thingsboardClipboard from './services/clipboard.service';
66 67 import thingsboardHome from './layout';
67 68 import thingsboardApiLogin from './api/login.service';
68 69 import thingsboardApiDevice from './api/device.service';
... ... @@ -72,6 +73,7 @@ import thingsboardApiAsset from './api/asset.service';
72 73 import thingsboardApiAttribute from './api/attribute.service';
73 74 import thingsboardApiEntity from './api/entity.service';
74 75 import thingsboardApiAlarm from './api/alarm.service';
  76 +import thingsboardApiAuditLog from './api/audit-log.service';
75 77
76 78 import 'typeface-roboto';
77 79 import 'font-awesome/css/font-awesome.min.css';
... ... @@ -123,6 +125,7 @@ angular.module('thingsboard', [
123 125 thingsboardKeyboardShortcut,
124 126 thingsboardHelp,
125 127 thingsboardToast,
  128 + thingsboardClipboard,
126 129 thingsboardHome,
127 130 thingsboardApiLogin,
128 131 thingsboardApiDevice,
... ... @@ -132,6 +135,7 @@ angular.module('thingsboard', [
132 135 thingsboardApiAttribute,
133 136 thingsboardApiEntity,
134 137 thingsboardApiAlarm,
  138 + thingsboardApiAuditLog,
135 139 uiRouter])
136 140 .config(AppConfig)
137 141 .factory('globalInterceptor', GlobalInterceptor)
... ...
... ... @@ -66,4 +66,10 @@
66 66 entity-type="{{vm.types.entityType.asset}}">
67 67 </tb-relation-table>
68 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 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 +}
\ No newline at end of file
... ...
  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 +}
... ...
  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>
... ...
  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 +}
... ...
  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 +}
... ...
  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>&nbsp;</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>
... ...
  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 +}
... ...