apps.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. const asyncWrapper = require('../middleware/asyncWrapper');
  2. const ErrorResponse = require('../utils/ErrorResponse');
  3. const App = require('../models/App');
  4. const Config = require('../models/Config');
  5. const { Sequelize } = require('sequelize');
  6. const axios = require('axios');
  7. const Logger = require('../utils/Logger');
  8. const logger = new Logger();
  9. const k8s = require('@kubernetes/client-node');
  10. // @desc Create new app
  11. // @route POST /api/apps
  12. // @access Public
  13. exports.createApp = asyncWrapper(async (req, res, next) => {
  14. // Get config from database
  15. const pinApps = await Config.findOne({
  16. where: { key: 'pinAppsByDefault' },
  17. });
  18. let app;
  19. let _body = { ...req.body };
  20. if (req.file) {
  21. _body.icon = req.file.filename;
  22. }
  23. if (pinApps) {
  24. if (parseInt(pinApps.value)) {
  25. app = await App.create({
  26. ..._body,
  27. isPinned: true,
  28. });
  29. } else {
  30. app = await App.create(req.body);
  31. }
  32. }
  33. res.status(201).json({
  34. success: true,
  35. data: app,
  36. });
  37. });
  38. // @desc Get all apps
  39. // @route GET /api/apps
  40. // @access Public
  41. exports.getApps = asyncWrapper(async (req, res, next) => {
  42. // Get config from database
  43. const useOrdering = await Config.findOne({
  44. where: { key: 'useOrdering' },
  45. });
  46. const useDockerApi = await Config.findOne({
  47. where: { key: 'dockerApps' },
  48. });
  49. const useKubernetesApi = await Config.findOne({
  50. where: { key: 'kubernetesApps' },
  51. });
  52. const unpinStoppedApps = await Config.findOne({
  53. where: { key: 'unpinStoppedApps' },
  54. });
  55. const orderType = useOrdering ? useOrdering.value : 'createdAt';
  56. let apps;
  57. if (useDockerApi && useDockerApi.value == 1) {
  58. let containers = null;
  59. const host = await Config.findOne({
  60. where: { key: 'dockerHost' },
  61. });
  62. try {
  63. if (host.value.includes('localhost')) {
  64. let { data } = await axios.get(
  65. `http://${host.value}/containers/json?{"status":["running"]}`,
  66. {
  67. socketPath: '/var/run/docker.sock',
  68. }
  69. );
  70. containers = data;
  71. } else {
  72. let { data } = await axios.get(
  73. `http://${host.value}/containers/json?{"status":["running"]}`
  74. );
  75. containers = data;
  76. }
  77. } catch {
  78. logger.log(`Can't connect to the docker api on ${host.value}`, 'ERROR');
  79. }
  80. if (containers) {
  81. apps = await App.findAll({
  82. order: [[orderType, 'ASC']],
  83. });
  84. containers = containers.filter((e) => Object.keys(e.Labels).length !== 0);
  85. const dockerApps = [];
  86. for (const container of containers) {
  87. const labels = container.Labels;
  88. if (
  89. 'flame.name' in labels &&
  90. 'flame.url' in labels &&
  91. /^app/.test(labels['flame.type'])
  92. ) {
  93. for (let i = 0; i < labels['flame.name'].split(';').length; i++) {
  94. const names = labels['flame.name'].split(';');
  95. const urls = labels['flame.url'].split(';');
  96. let icons = '';
  97. if ('flame.icon' in labels) {
  98. icons = labels['flame.icon'].split(';');
  99. }
  100. dockerApps.push({
  101. name: names[i] || names[0],
  102. url: urls[i] || urls[0],
  103. icon: icons[i] || 'docker',
  104. });
  105. }
  106. }
  107. }
  108. if (unpinStoppedApps && unpinStoppedApps.value == 1) {
  109. for (const app of apps) {
  110. await app.update({ isPinned: false });
  111. }
  112. }
  113. for (const item of dockerApps) {
  114. if (apps.some((app) => app.name === item.name)) {
  115. const app = apps.filter((e) => e.name === item.name)[0];
  116. if (item.icon === 'custom') {
  117. await app.update({
  118. name: item.name,
  119. url: item.url,
  120. isPinned: true,
  121. });
  122. } else {
  123. await app.update({
  124. name: item.name,
  125. url: item.url,
  126. icon: item.icon,
  127. isPinned: true,
  128. });
  129. }
  130. } else {
  131. await App.create({
  132. name: item.name,
  133. url: item.url,
  134. icon: item.icon === 'custom' ? 'docker' : item.icon,
  135. isPinned: true,
  136. });
  137. }
  138. }
  139. }
  140. }
  141. if (useKubernetesApi && useKubernetesApi.value == 1) {
  142. let ingresses = null;
  143. try {
  144. const kc = new k8s.KubeConfig();
  145. kc.loadFromCluster();
  146. const k8sNetworkingV1Api = kc.makeApiClient(k8s.NetworkingV1Api);
  147. await k8sNetworkingV1Api.listIngressForAllNamespaces().then((res) => {
  148. ingresses = res.body.items;
  149. });
  150. } catch {
  151. logger.log("Can't connect to the kubernetes api", 'ERROR');
  152. }
  153. if (ingresses) {
  154. apps = await App.findAll({
  155. order: [[orderType, 'ASC']],
  156. });
  157. ingresses = ingresses.filter(
  158. (e) => Object.keys(e.metadata.annotations).length !== 0
  159. );
  160. const kubernetesApps = [];
  161. for (const ingress of ingresses) {
  162. const annotations = ingress.metadata.annotations;
  163. if (
  164. 'flame.pawelmalak/name' in annotations &&
  165. 'flame.pawelmalak/url' in annotations &&
  166. /^app/.test(annotations['flame.pawelmalak/type'])
  167. ) {
  168. kubernetesApps.push({
  169. name: annotations['flame.pawelmalak/name'],
  170. url: annotations['flame.pawelmalak/url'],
  171. icon: annotations['flame.pawelmalak/icon'] || 'kubernetes',
  172. });
  173. }
  174. }
  175. if (unpinStoppedApps && unpinStoppedApps.value == 1) {
  176. for (const app of apps) {
  177. await app.update({ isPinned: false });
  178. }
  179. }
  180. for (const item of kubernetesApps) {
  181. if (apps.some((app) => app.name === item.name)) {
  182. const app = apps.filter((e) => e.name === item.name)[0];
  183. await app.update({ ...item, isPinned: true });
  184. } else {
  185. await App.create({
  186. ...item,
  187. isPinned: true,
  188. });
  189. }
  190. }
  191. }
  192. }
  193. if (orderType == 'name') {
  194. apps = await App.findAll({
  195. order: [[Sequelize.fn('lower', Sequelize.col('name')), 'ASC']],
  196. });
  197. } else {
  198. apps = await App.findAll({
  199. order: [[orderType, 'ASC']],
  200. });
  201. }
  202. if (process.env.NODE_ENV === 'production') {
  203. // Set header to fetch containers info every time
  204. res.status(200).setHeader('Cache-Control', 'no-store').json({
  205. success: true,
  206. data: apps,
  207. });
  208. return;
  209. }
  210. res.status(200).json({
  211. success: true,
  212. data: apps,
  213. });
  214. });
  215. // @desc Get single app
  216. // @route GET /api/apps/:id
  217. // @access Public
  218. exports.getApp = asyncWrapper(async (req, res, next) => {
  219. const app = await App.findOne({
  220. where: { id: req.params.id },
  221. });
  222. if (!app) {
  223. return next(
  224. new ErrorResponse(`App with id of ${req.params.id} was not found`, 404)
  225. );
  226. }
  227. res.status(200).json({
  228. success: true,
  229. data: app,
  230. });
  231. });
  232. // @desc Update app
  233. // @route PUT /api/apps/:id
  234. // @access Public
  235. exports.updateApp = asyncWrapper(async (req, res, next) => {
  236. let app = await App.findOne({
  237. where: { id: req.params.id },
  238. });
  239. if (!app) {
  240. return next(
  241. new ErrorResponse(`App with id of ${req.params.id} was not found`, 404)
  242. );
  243. }
  244. let _body = { ...req.body };
  245. if (req.file) {
  246. _body.icon = req.file.filename;
  247. }
  248. app = await app.update(_body);
  249. res.status(200).json({
  250. success: true,
  251. data: app,
  252. });
  253. });
  254. // @desc Delete app
  255. // @route DELETE /api/apps/:id
  256. // @access Public
  257. exports.deleteApp = asyncWrapper(async (req, res, next) => {
  258. await App.destroy({
  259. where: { id: req.params.id },
  260. });
  261. res.status(200).json({
  262. success: true,
  263. data: {},
  264. });
  265. });
  266. // @desc Reorder apps
  267. // @route PUT /api/apps/0/reorder
  268. // @access Public
  269. exports.reorderApps = asyncWrapper(async (req, res, next) => {
  270. req.body.apps.forEach(async ({ id, orderId }) => {
  271. await App.update(
  272. { orderId },
  273. {
  274. where: { id },
  275. }
  276. );
  277. });
  278. res.status(200).json({
  279. success: true,
  280. data: {},
  281. });
  282. });