AdminStorage.vue 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262
  1. <template lang='pug'>
  2. q-page.admin-storage
  3. .row.q-pa-md.items-center
  4. .col-auto
  5. img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-ssd-animated.svg')
  6. .col.q-pl-md
  7. .text-h5.text-primary.animated.fadeInLeft {{ t('admin.storage.title') }}
  8. .text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.storage.subtitle') }}
  9. .col-auto.flex
  10. q-spinner-tail.q-mr-md(
  11. v-show='state.loading > 0'
  12. color='accent'
  13. size='sm'
  14. )
  15. q-btn-toggle.q-mr-md(
  16. v-model='state.displayMode'
  17. push
  18. no-caps
  19. :toggle-color='$q.dark.isActive ? `white` : `black`'
  20. :toggle-text-color='$q.dark.isActive ? `black` : `white`'
  21. :text-color='$q.dark.isActive ? `white` : `black`'
  22. :color='$q.dark.isActive ? `dark-1` : `white`'
  23. :options=`[
  24. { label: t('admin.storage.targets'), value: 'targets' },
  25. { label: t('admin.storage.deliveryPaths'), value: 'delivery' }
  26. ]`
  27. )
  28. q-separator.q-mr-md(vertical)
  29. q-btn.q-mr-sm.acrylic-btn(
  30. icon='las la-question-circle'
  31. flat
  32. color='grey'
  33. :href='siteStore.docsBase + `/admin/storage`'
  34. target='_blank'
  35. type='a'
  36. )
  37. q-btn(
  38. unelevated
  39. icon='fa-solid fa-check'
  40. :label='t(`common.actions.apply`)'
  41. color='secondary'
  42. @click='save'
  43. :loading='state.loading > 0'
  44. )
  45. q-separator(inset)
  46. //- ==========================================
  47. //- TARGETS
  48. //- ==========================================
  49. .row.q-pa-md.q-col-gutter-md(v-if='state.displayMode === `targets`')
  50. .col-auto
  51. q-card.rounded-borders.bg-dark
  52. q-list(
  53. style='min-width: 300px;'
  54. padding
  55. dark
  56. )
  57. q-item(
  58. v-for='tgt of state.targets'
  59. :key='tgt.key'
  60. active-class='bg-primary text-white'
  61. :active='state.selectedTarget === tgt.id'
  62. :to='`/_admin/` + adminStore.currentSiteId + `/storage/` + tgt.id'
  63. clickable
  64. )
  65. q-item-section(side)
  66. q-icon(:name='`img:` + tgt.icon')
  67. q-item-section
  68. q-item-label {{tgt.title}}
  69. q-item-label(caption, :class='getTargetSubtitleColor(tgt)') {{getTargetSubtitle(tgt)}}
  70. q-item-section(side)
  71. status-light(:color='tgt.isEnabled ? `positive` : `negative`', :pulse='tgt.isEnabled')
  72. .col(v-if='state.target')
  73. .row.q-col-gutter-md
  74. .col-12.col-lg
  75. //- -----------------------
  76. //- Setup
  77. //- -----------------------
  78. q-card.shadow-1.q-pb-sm.q-mb-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state !== `configured`')
  79. q-card-section
  80. .text-subtitle1 {{t('admin.storage.setup')}}
  81. .text-body2.text-grey {{ t('admin.storage.setupHint') }}
  82. template(v-if='state.target.setup.handler === `github` && state.target.setup.state === `notconfigured`')
  83. q-item
  84. blueprint-icon(icon='test-account')
  85. q-item-section
  86. q-item-label GitHub Account Type
  87. q-item-label(caption) Whether to use an organization or personal GitHub account during setup.
  88. q-item-section.col-auto
  89. q-btn-toggle(
  90. v-model='state.target.setup.values.accountType'
  91. push
  92. glossy
  93. no-caps
  94. toggle-color='primary'
  95. :options=`[
  96. { label: t('admin.storage.githubAccTypeOrg'), value: 'org' },
  97. { label: t('admin.storage.githubAccTypePersonal'), value: 'personal' }
  98. ]`
  99. )
  100. q-separator.q-my-sm(inset)
  101. template(v-if='state.target.setup.values.accountType === `org`')
  102. q-item
  103. blueprint-icon(icon='github')
  104. q-item-section
  105. q-item-label {{ t('admin.storage.githubOrg') }}
  106. q-item-label(caption) {{ t('admin.storage.githubOrgHint') }}
  107. q-item-section
  108. q-input(
  109. outlined
  110. v-model='state.target.setup.values.org'
  111. dense
  112. :aria-label='t(`admin.storage.githubOrg`)'
  113. )
  114. q-separator.q-my-sm(inset)
  115. q-item
  116. blueprint-icon(icon='dns')
  117. q-item-section
  118. q-item-label {{ t('admin.storage.githubPublicUrl') }}
  119. q-item-label(caption) {{ t('admin.storage.githubPublicUrlHint') }}
  120. q-item-section
  121. q-input(
  122. outlined
  123. v-model='state.target.setup.values.publicUrl'
  124. dense
  125. :aria-label='t(`admin.storage.githubPublicUrl`)'
  126. )
  127. q-card-section.q-pt-sm.text-right
  128. form(
  129. ref='githubSetupForm'
  130. method='POST'
  131. :action='state.setupCfg.action'
  132. )
  133. input(
  134. type='hidden'
  135. name='manifest'
  136. :value='state.setupCfg.manifest'
  137. )
  138. q-btn(
  139. unelevated
  140. icon='las la-angle-double-right'
  141. :label='t(`admin.storage.startSetup`)'
  142. color='secondary'
  143. @click='setupGitHub'
  144. :loading='state.setupCfg.loading'
  145. )
  146. template(v-else-if='state.target.setup.handler === `github` && state.target.setup.state === `pendinginstall`')
  147. q-card-section.q-py-none
  148. q-banner(
  149. rounded
  150. :class='$q.dark.isActive ? `bg-teal-9 text-white` : `bg-teal-1 text-teal-9`'
  151. ) {{t('admin.storage.githubFinish')}}
  152. q-card-section.q-pt-sm.text-right
  153. q-btn.q-mr-sm(
  154. unelevated
  155. icon='las la-times-circle'
  156. :label='t(`admin.storage.cancelSetup`)'
  157. color='negative'
  158. @click='setupDestroy'
  159. )
  160. q-btn(
  161. unelevated
  162. icon='las la-angle-double-right'
  163. :label='t(`admin.storage.finishSetup`)'
  164. color='secondary'
  165. @click='setupGitHubStep(`verify`)'
  166. :loading='state.setupCfg.loading'
  167. )
  168. q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.setup && state.target.setup.handler && state.target.setup.state === `configured`')
  169. q-card-section
  170. .text-subtitle1 {{t('admin.storage.setup')}}
  171. .text-body2.text-grey {{ t('admin.storage.setupConfiguredHint') }}
  172. q-item
  173. blueprint-icon.self-start(icon='matches', :hue-rotate='140')
  174. q-item-section
  175. q-item-label Uninstall
  176. q-item-label(caption) Delete the active configuration and start over the setup process.
  177. q-item-label.text-red(caption): strong This action cannot be undone!
  178. q-item-section(side)
  179. q-btn.acrylic-btn(
  180. flat
  181. icon='las la-arrow-circle-right'
  182. color='negative'
  183. @click='setupDestroy'
  184. :label='t(`admin.storage.uninstall`)'
  185. )
  186. //- -----------------------
  187. //- Content Types
  188. //- -----------------------
  189. q-card.shadow-1.q-pb-sm
  190. q-card-section
  191. .text-subtitle1 {{t('admin.storage.contentTypes')}}
  192. .text-body2.text-grey {{ t('admin.storage.contentTypesHint') }}
  193. q-item(tag='label')
  194. q-item-section(avatar)
  195. q-checkbox(
  196. v-model='state.target.contentTypes.activeTypes'
  197. :color='state.target.module === `db` ? `grey` : `primary`'
  198. val='pages'
  199. :aria-label='t(`admin.storage.contentTypePages`)'
  200. :disable='state.target.module === `db`'
  201. )
  202. q-item-section
  203. q-item-label {{t(`admin.storage.contentTypePages`)}}
  204. q-item-label(caption) {{t(`admin.storage.contentTypePagesHint`)}}
  205. q-item(tag='label')
  206. q-item-section(avatar)
  207. q-checkbox(
  208. v-model='state.target.contentTypes.activeTypes'
  209. color='primary'
  210. val='images'
  211. :aria-label='t(`admin.storage.contentTypeImages`)'
  212. )
  213. q-item-section
  214. q-item-label {{t(`admin.storage.contentTypeImages`)}}
  215. q-item-label(caption) {{t(`admin.storage.contentTypeImagesHint`)}}
  216. q-item(tag='label')
  217. q-item-section(avatar)
  218. q-checkbox(
  219. v-model='state.target.contentTypes.activeTypes'
  220. color='primary'
  221. val='documents'
  222. :aria-label='t(`admin.storage.contentTypeDocuments`)'
  223. )
  224. q-item-section
  225. q-item-label {{t(`admin.storage.contentTypeDocuments`)}}
  226. q-item-label(caption) {{t(`admin.storage.contentTypeDocumentsHint`)}}
  227. q-item(tag='label')
  228. q-item-section(avatar)
  229. q-checkbox(
  230. v-model='state.target.contentTypes.activeTypes'
  231. color='primary'
  232. val='others'
  233. :aria-label='t(`admin.storage.contentTypeOthers`)'
  234. )
  235. q-item-section
  236. q-item-label {{t(`admin.storage.contentTypeOthers`)}}
  237. q-item-label(caption) {{t(`admin.storage.contentTypeOthersHint`)}}
  238. q-item(tag='label')
  239. q-item-section(avatar)
  240. q-checkbox(
  241. v-model='state.target.contentTypes.activeTypes'
  242. color='primary'
  243. val='large'
  244. :aria-label='t(`admin.storage.contentTypeLargeFiles`)'
  245. )
  246. q-item-section
  247. q-item-label {{t(`admin.storage.contentTypeLargeFiles`)}}
  248. q-item-label(caption) {{t(`admin.storage.contentTypeLargeFilesHint`)}}
  249. q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.contentTypeLargeFilesDBWarn`)}}
  250. q-item-section(side)
  251. q-input(
  252. outlined
  253. :label='t(`admin.storage.contentTypeLargeFilesThreshold`)'
  254. v-model='state.target.contentTypes.largeThreshold'
  255. style='min-width: 150px;'
  256. dense
  257. )
  258. //- -----------------------
  259. //- Content Delivery
  260. //- -----------------------
  261. q-card.shadow-1.q-pb-sm.q-mt-md
  262. q-card-section
  263. .text-subtitle1 {{t('admin.storage.assetDelivery')}}
  264. .text-body2.text-grey {{ t('admin.storage.assetDeliveryHint') }}
  265. q-item(:tag='state.target.assetDelivery.isStreamingSupported ? `label` : null')
  266. q-item-section(avatar)
  267. q-checkbox(
  268. v-model='state.target.assetDelivery.streaming'
  269. :color='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported ? `grey` : `primary`'
  270. :aria-label='t(`admin.storage.contentTypePages`)'
  271. :disable='state.target.module === `db` || !state.target.assetDelivery.isStreamingSupported'
  272. )
  273. q-item-section
  274. q-item-label {{t(`admin.storage.assetStreaming`)}}
  275. q-item-label(caption) {{t(`admin.storage.assetStreamingHint`)}}
  276. q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isStreamingSupported', caption) {{t(`admin.storage.assetStreamingNotSupported`)}}
  277. q-item(:tag='state.target.assetDelivery.isDirectAccessSupported ? `label` : null')
  278. q-item-section(avatar)
  279. q-checkbox(
  280. v-model='state.target.assetDelivery.directAccess'
  281. :color='!state.target.assetDelivery.isDirectAccessSupported ? `grey` : `primary`'
  282. :aria-label='t(`admin.storage.contentTypePages`)'
  283. :disable='!state.target.assetDelivery.isDirectAccessSupported'
  284. )
  285. q-item-section
  286. q-item-label {{t(`admin.storage.assetDirectAccess`)}}
  287. q-item-label(caption) {{t(`admin.storage.assetDirectAccessHint`)}}
  288. q-item-label.text-deep-orange(v-if='!state.target.assetDelivery.isDirectAccessSupported', caption) {{t(`admin.storage.assetDirectAccessNotSupported`)}}
  289. //- -----------------------
  290. //- Configuration
  291. //- -----------------------
  292. q-card.shadow-1.q-pb-sm.q-mt-md
  293. q-card-section
  294. .text-subtitle1 {{t('admin.storage.config')}}
  295. q-banner.q-mt-md(
  296. v-if='!state.target.config || Object.keys(state.target.config).length < 1'
  297. rounded
  298. :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
  299. ) {{t('admin.storage.noConfigOption')}}
  300. template(
  301. v-for='(cfg, cfgKey, idx) in state.target.config'
  302. )
  303. template(
  304. v-if='configIfCheck(cfg.if)'
  305. )
  306. q-separator.q-my-sm(inset, v-if='idx > 0')
  307. q-item(v-if='cfg.type === `Boolean`', tag='label')
  308. blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
  309. q-item-section
  310. q-item-label {{cfg.title}}
  311. q-item-label(caption) {{cfg.hint}}
  312. q-item-section(avatar)
  313. q-toggle(
  314. v-model='cfg.value'
  315. color='primary'
  316. checked-icon='las la-check'
  317. unchecked-icon='las la-times'
  318. :aria-label='t(`admin.general.allowComments`)'
  319. :disable='cfg.readOnly'
  320. )
  321. q-item(v-else)
  322. blueprint-icon(:icon='cfg.icon', :hue-rotate='cfg.readOnly ? -45 : 0')
  323. q-item-section
  324. q-item-label {{cfg.title}}
  325. q-item-label(caption) {{cfg.hint}}
  326. q-item-section(
  327. :style='cfg.type === `Number` ? `flex: 0 0 150px;` : ``'
  328. :class='{ "col-auto": cfg.enum && cfg.enumDisplay === `buttons` }'
  329. )
  330. q-btn-toggle(
  331. v-if='cfg.enum && cfg.enumDisplay === `buttons`'
  332. v-model='cfg.value'
  333. push
  334. glossy
  335. no-caps
  336. toggle-color='primary'
  337. :options=`cfg.enum`
  338. :disable='cfg.readOnly'
  339. )
  340. q-select(
  341. v-else-if='cfg.enum'
  342. outlined
  343. v-model='cfg.value'
  344. :options='cfg.enum'
  345. emit-value
  346. map-options
  347. dense
  348. options-dense
  349. :aria-label='cfg.title'
  350. :disable='cfg.readOnly'
  351. )
  352. q-input(
  353. v-else
  354. outlined
  355. v-model='cfg.value'
  356. dense
  357. :type='cfg.multiline ? `textarea` : `input`'
  358. :aria-label='cfg.title'
  359. :disable='cfg.readOnly'
  360. )
  361. //- -----------------------
  362. //- Sync
  363. //- -----------------------
  364. q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.target.sync && Object.keys(state.target.sync).length > 0')
  365. q-card-section
  366. .text-subtitle1 {{t('admin.storage.sync')}}
  367. q-banner.q-mt-md(
  368. rounded
  369. :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
  370. ) {{t('admin.storage.noSyncModes')}}
  371. //- -----------------------
  372. //- Actions
  373. //- -----------------------
  374. q-card.shadow-1.q-pb-sm.q-mt-md
  375. q-card-section
  376. .text-subtitle1 {{t('admin.storage.actions')}}
  377. q-banner.q-mt-md(
  378. v-if='!state.target.actions || state.target.actions.length < 1'
  379. rounded
  380. :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
  381. ) {{t('admin.storage.noActions')}}
  382. q-banner.q-mt-md(
  383. v-else-if='!state.target.isEnabled'
  384. rounded
  385. :class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
  386. ) {{t('admin.storage.actionsInactiveWarn')}}
  387. template(
  388. v-if='state.target.isEnabled'
  389. v-for='(act, idx) in state.target.actions'
  390. )
  391. q-separator.q-my-sm(inset, v-if='idx > 0')
  392. q-item
  393. blueprint-icon.self-start(:icon='act.icon', :hue-rotate='45')
  394. q-item-section
  395. q-item-label {{act.label}}
  396. q-item-label(caption) {{act.hint}}
  397. q-item-label.text-red(v-if='act.warn', caption): strong {{act.warn}}
  398. q-item-section(side)
  399. q-btn.acrylic-btn(
  400. flat
  401. icon='las la-arrow-circle-right'
  402. color='primary'
  403. @click=''
  404. :label='t(`common.actions.proceed`)'
  405. )
  406. .col-12.col-lg-auto
  407. //- -----------------------
  408. //- Infobox
  409. //- -----------------------
  410. q-card.rounded-borders.q-pb-md(style='width: 300px;')
  411. q-card-section
  412. .text-subtitle1 {{state.target.title}}
  413. q-img.q-mt-sm.rounded-borders(
  414. :src='state.target.banner'
  415. fit='cover'
  416. no-spinner
  417. )
  418. .text-body2.q-mt-md {{state.target.description}}
  419. q-separator.q-mb-sm(inset)
  420. q-item
  421. q-item-section
  422. q-item-label.text-grey {{t(`admin.storage.vendor`)}}
  423. q-item-label {{state.target.vendor}}
  424. q-separator.q-my-sm(inset)
  425. q-item
  426. q-item-section
  427. q-item-label.text-grey {{t(`admin.storage.vendorWebsite`)}}
  428. q-item-label: a(:href='state.target.website', target='_blank', rel='noreferrer') {{state.target.website}}
  429. //- -----------------------
  430. //- Status
  431. //- -----------------------
  432. q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 300px;')
  433. q-card-section
  434. .text-subtitle1 {{ t('admin.storage.status') }}
  435. template(v-if='state.target.module !== `db`')
  436. q-item(tag='label')
  437. q-item-section
  438. q-item-label {{t(`admin.storage.enabled`)}}
  439. q-item-label(caption) {{t(`admin.storage.enabledHint`)}}
  440. q-item-label.text-deep-orange(v-if='state.target.module === `db`', caption) {{t(`admin.storage.enabledForced`)}}
  441. q-item-section(avatar)
  442. q-toggle(
  443. v-model='state.target.isEnabled'
  444. :disable='state.target.module === `db` || isSetupNeeded'
  445. color='primary'
  446. checked-icon='las la-check'
  447. unchecked-icon='las la-times'
  448. :aria-label='t(`admin.storage.enabled`)'
  449. )
  450. q-inner-loading(:showing='isSetupNeeded')
  451. q-icon(name='las la-exclamation-triangle', size='sm', color='negative')
  452. .text-body2.text-negative {{ t('admin.storage.setupRequired') }}
  453. q-separator.q-my-sm(inset)
  454. q-item
  455. q-item-section
  456. q-item-label.text-grey {{t(`admin.storage.currentState`)}}
  457. q-item-label.text-positive No issues detected.
  458. //- -----------------------
  459. //- Versioning
  460. //- -----------------------
  461. q-card.rounded-borders.q-pb-md.q-mt-md(style='width: 300px;')
  462. q-card-section
  463. .text-subtitle1 {{t(`admin.storage.versioning`)}}
  464. .text-body2.text-grey {{t(`admin.storage.versioningHint`)}}
  465. q-item(:tag='state.target.versioning.isSupported ? `label` : null')
  466. q-item-section
  467. q-item-label {{t(`admin.storage.useVersioning`)}}
  468. q-item-label(caption) {{t(`admin.storage.useVersioningHint`)}}
  469. q-item-label.text-deep-orange(v-if='!state.target.versioning.isSupported', caption) {{t(`admin.storage.versioningNotSupported`)}}
  470. q-item-label.text-deep-orange(v-if='state.target.versioning.isForceEnabled', caption) {{t(`admin.storage.versioningForceEnabled`)}}
  471. q-item-section(avatar)
  472. q-toggle(
  473. v-model='state.target.versioning.enabled'
  474. :disable='!state.target.versioning.isSupported || state.target.versioning.isForceEnabled'
  475. color='primary'
  476. checked-icon='las la-check'
  477. unchecked-icon='las la-times'
  478. :aria-label='t(`admin.storage.useVersioning`)'
  479. )
  480. //- ==========================================
  481. //- DELIVERY PATHS
  482. //- ==========================================
  483. .row.q-pa-md.q-col-gutter-md(v-if='state.displayMode === `delivery`')
  484. .col
  485. q-card.rounded-borders
  486. q-card-section.flex.items-center
  487. .text-caption.q-mr-sm {{ t('admin.storage.deliveryPathsLegend') }}
  488. q-chip(square, dense, color='blue-1', text-color='blue-8')
  489. q-avatar(icon='las la-ellipsis-h', color='blue', text-color='white')
  490. span.text-caption.q-px-sm {{ t('admin.storage.deliveryPathsUserRequest') }}
  491. q-chip(square, dense, color='teal-1', text-color='teal-8')
  492. q-avatar(icon='las la-ellipsis-h', color='positive', text-color='white')
  493. span.text-caption.q-px-sm {{ t('admin.storage.deliveryPathsPushToOrigin') }}
  494. q-chip(square, dense, color='red-1', text-color='red-8')
  495. q-avatar(icon='las la-minus', color='negative', text-color='white')
  496. span.text-caption.q-px-sm {{ t('admin.storage.missingOrigin') }}
  497. q-separator
  498. v-network-graph(
  499. :zoom-level='2'
  500. :configs='state.deliveryConfig'
  501. :nodes='state.deliveryNodes'
  502. :edges='state.deliveryEdges'
  503. :paths='state.deliveryPaths'
  504. :layouts='state.deliveryLayouts'
  505. style='height: 600px; background-color: #FFF;'
  506. )
  507. template(#override-node='{ nodeId, scale, config, ...slotProps }')
  508. rect(
  509. :rx='config.borderRadius * scale'
  510. :x='-config.radius * scale'
  511. :y='-config.radius * scale'
  512. :width='config.radius * scale * 2'
  513. :height='config.radius * scale * 2'
  514. :fill='config.color'
  515. v-bind='slotProps'
  516. )
  517. image(
  518. v-if='state.deliveryNodes[nodeId].icon && state.deliveryNodes[nodeId].icon.endsWith(`.svg`)'
  519. :x='(-config.radius + 5) * scale'
  520. :y='(-config.radius + 5) * scale'
  521. :width='(config.radius - 5) * scale * 2'
  522. :height='(config.radius - 5) * scale * 2'
  523. :xlink:href='state.deliveryNodes[nodeId].icon'
  524. )
  525. text(
  526. v-if='state.deliveryNodes[nodeId].icon && state.deliveryNodes[nodeId].iconText'
  527. :class='state.deliveryNodes[nodeId].icon'
  528. :font-size='22 * scale'
  529. fill='#ffffff'
  530. text-anchor='middle'
  531. dominant-baseline='central'
  532. v-html='state.deliveryNodes[nodeId].iconText'
  533. )
  534. //- .overline.my-5 {{t('admin.storage.syncDirection')}}
  535. //- .body-2.ml-3 {{t('admin.storage.syncDirectionSubtitle')}}
  536. //- .pr-3.pt-3
  537. //- v-radio-group.ml-3.py-0(v-model='target.mode')
  538. //- v-radio(
  539. //- :label='t(`admin.storage.syncDirBi`)'
  540. //- color='primary'
  541. //- value='sync'
  542. //- :disabled='target.supportedModes.indexOf(`sync`) < 0'
  543. //- )
  544. //- v-radio(
  545. //- :label='t(`admin.storage.syncDirPush`)'
  546. //- color='primary'
  547. //- value='push'
  548. //- :disabled='target.supportedModes.indexOf(`push`) < 0'
  549. //- )
  550. //- v-radio(
  551. //- :label='t(`admin.storage.syncDirPull`)'
  552. //- color='primary'
  553. //- value='pull'
  554. //- :disabled='target.supportedModes.indexOf(`pull`) < 0'
  555. //- )
  556. //- .body-2.ml-3
  557. //- strong {{t('admin.storage.syncDirBi')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`sync`) < 0') {{t('admin.storage.unsupported')}}]
  558. //- .pb-3 {{t('admin.storage.syncDirBiHint')}}
  559. //- strong {{t('admin.storage.syncDirPush')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`push`) < 0') {{t('admin.storage.unsupported')}}]
  560. //- .pb-3 {{t('admin.storage.syncDirPushHint')}}
  561. //- strong {{t('admin.storage.syncDirPull')}} #[em.red--text.text--lighten-2(v-if='target.supportedModes.indexOf(`pull`) < 0') {{t('admin.storage.unsupported')}}]
  562. //- .pb-3 {{t('admin.storage.syncDirPullHint')}}
  563. //- template(v-if='target.hasSchedule')
  564. //- v-divider.mt-3
  565. //- .overline.my-5 {{t('admin.storage.syncSchedule')}}
  566. //- .body-2.ml-3 {{t('admin.storage.syncScheduleHint')}}
  567. //- .pa-3
  568. //- duration-picker(v-model='target.syncInterval')
  569. //- i18next.caption.mt-3(path='admin.storage.syncScheduleCurrent', tag='div')
  570. //- strong(place='schedule') {{getDefaultSchedule(target.syncInterval)}}
  571. //- i18next.caption(path='admin.storage.syncScheduleDefault', tag='div')
  572. //- strong(place='schedule') {{getDefaultSchedule(target.syncIntervalDefault)}}
  573. </template>
  574. <script setup>
  575. import { cloneDeep, find, transform } from 'lodash-es'
  576. import gql from 'graphql-tag'
  577. import * as VNG from 'v-network-graph'
  578. import { useI18n } from 'vue-i18n'
  579. import { useMeta, useQuasar } from 'quasar'
  580. import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
  581. import { useRouter, useRoute } from 'vue-router'
  582. import { useAdminStore } from 'src/stores/admin'
  583. import { useSiteStore } from 'src/stores/site'
  584. import GithubSetupInstallDialog from '../components/GithubSetupInstallDialog.vue'
  585. // QUASAR
  586. const $q = useQuasar()
  587. // STORES
  588. const adminStore = useAdminStore()
  589. const siteStore = useSiteStore()
  590. // ROUTER
  591. const router = useRouter()
  592. const route = useRoute()
  593. // I18N
  594. const { t } = useI18n()
  595. // META
  596. useMeta({
  597. title: t('admin.storage.title')
  598. })
  599. // DATA
  600. const state = reactive({
  601. loading: 0,
  602. displayMode: 'targets',
  603. runningAction: false,
  604. runningActionHandler: '',
  605. selectedTarget: '',
  606. desiredTarget: '',
  607. target: null,
  608. targets: [],
  609. setupCfg: {
  610. action: '',
  611. manifest: '',
  612. loading: false
  613. },
  614. deliveryNodes: {},
  615. deliveryEdges: {},
  616. deliveryLayouts: {
  617. nodes: {}
  618. },
  619. deliveryPaths: [],
  620. deliveryConfig: VNG.defineConfigs({
  621. view: {
  622. layoutHandler: new VNG.GridLayout({ grid: 15 }),
  623. fit: true,
  624. mouseWheelZoomEnabled: false,
  625. grid: {
  626. visible: true,
  627. interval: 2.5,
  628. thickIncrements: 0
  629. }
  630. },
  631. node: {
  632. draggable: false,
  633. selectable: true,
  634. normal: {
  635. type: 'rect',
  636. color: node => node.color || '#1976D2',
  637. borderRadius: node => node.borderRadius || 5
  638. },
  639. label: {
  640. margin: 8
  641. }
  642. },
  643. edge: {
  644. normal: {
  645. width: 3,
  646. dasharray: edge => edge.animate === false ? 20 : 3,
  647. animate: edge => !(edge.animate === false),
  648. animationSpeed: edge => edge.animationSpeed || 50,
  649. color: edge => edge.color || '#1976D2'
  650. },
  651. type: 'straight',
  652. gap: 7,
  653. margin: 4,
  654. marker: {
  655. source: {
  656. type: 'none'
  657. },
  658. target: {
  659. type: 'none'
  660. }
  661. }
  662. },
  663. path: {
  664. visible: true,
  665. end: 'edgeOfNode',
  666. margin: 4,
  667. path: {
  668. width: 7,
  669. color: p => p.color,
  670. linecap: 'square'
  671. }
  672. }
  673. })
  674. })
  675. // REFS
  676. const githubSetupForm = ref(null)
  677. // COMPUTED
  678. const isSetupNeeded = computed(() => {
  679. return state.target?.setup?.handler && state.target.setup.state !== 'configured'
  680. })
  681. const isSetupCompleted = computed(() => {
  682. return state.target?.setup?.handler && state.target.setup.state !== 'configured'
  683. })
  684. // WATCHERS
  685. watch(() => adminStore.currentSiteId, async (newValue) => {
  686. await load()
  687. nextTick(() => {
  688. router.replace(`/_admin/${newValue}/storage/${state.selectedTarget}`)
  689. })
  690. })
  691. watch(() => state.displayMode, (newValue) => {
  692. if (newValue === 'delivery') {
  693. generateGraph()
  694. }
  695. })
  696. watch(() => state.selectedTarget, (newValue) => {
  697. state.target = find(state.targets, ['id', newValue]) || null
  698. })
  699. watch(() => state.targets, (newValue) => {
  700. if (newValue && newValue.length > 0) {
  701. if (state.desiredTarget) {
  702. state.selectedTarget = state.desiredTarget
  703. state.desiredTarget = ''
  704. } else {
  705. state.selectedTarget = find(state.targets, ['module', 'db'])?.id || null
  706. if (!route.params.id) {
  707. router.replace(`/_admin/${adminStore.currentSiteId}/storage/${state.selectedTarget}`)
  708. }
  709. }
  710. handleSetupCallback()
  711. }
  712. })
  713. watch(() => route.params.id, (to, from) => {
  714. if (!to) {
  715. return
  716. }
  717. if (state.targets.length < 1) {
  718. state.desiredTarget = to
  719. } else {
  720. state.selectedTarget = to
  721. }
  722. })
  723. // METHODS
  724. async function load () {
  725. state.loading++
  726. $q.loading.show()
  727. try {
  728. const resp = await APOLLO_CLIENT.query({
  729. query: gql`
  730. query getStorageTargets (
  731. $siteId: UUID!
  732. ) {
  733. storageTargets (
  734. siteId: $siteId
  735. ) {
  736. id
  737. isEnabled
  738. module
  739. title
  740. description
  741. icon
  742. banner
  743. vendor
  744. website
  745. contentTypes
  746. assetDelivery
  747. versioning
  748. sync
  749. status
  750. setup
  751. config
  752. actions
  753. }
  754. }`,
  755. variables: {
  756. siteId: adminStore.currentSiteId
  757. },
  758. fetchPolicy: 'network-only'
  759. })
  760. state.targets = cloneDeep(resp?.data?.storageTargets)
  761. } catch (err) {
  762. $q.notify({
  763. type: 'negative',
  764. message: 'Failed to load storage configuration.',
  765. caption: err.message,
  766. timeout: 20000
  767. })
  768. }
  769. $q.loading.hide()
  770. state.loading--
  771. }
  772. function configIfCheck (ifs) {
  773. if (!ifs || ifs.length < 1) { return true }
  774. return ifs.every(s => state.target.config[s.key]?.value === s.eq)
  775. }
  776. async function refresh () {
  777. await load()
  778. $q.notify({
  779. type: 'positive',
  780. message: 'List of storage targets has been refreshed.'
  781. })
  782. }
  783. async function save ({ silent }) {
  784. let saveSuccess = false
  785. if (!silent) { $q.loading.show() }
  786. try {
  787. const resp = await APOLLO_CLIENT.mutate({
  788. mutation: gql`
  789. mutation (
  790. $siteId: UUID!
  791. $targets: [StorageTargetInput]!
  792. ) {
  793. updateStorageTargets(
  794. siteId: $siteId
  795. targets: $targets
  796. ) {
  797. status {
  798. succeeded
  799. message
  800. }
  801. }
  802. }
  803. `,
  804. variables: {
  805. siteId: adminStore.currentSiteId,
  806. targets: state.targets.map(tgt => ({
  807. id: tgt.id,
  808. module: tgt.module,
  809. isEnabled: tgt.isEnabled,
  810. contentTypes: tgt.contentTypes.activeTypes,
  811. largeThreshold: tgt.contentTypes.largeThreshold,
  812. assetDeliveryFileStreaming: tgt.assetDelivery.streaming,
  813. assetDeliveryDirectAccess: tgt.assetDelivery.directAccess,
  814. useVersioning: tgt.versioning.enabled,
  815. config: transform(tgt.config, (r, v, k) => { r[k] = v.value }, {})
  816. }))
  817. }
  818. })
  819. if (resp?.data?.updateStorageTargets?.status?.succeeded) {
  820. saveSuccess = true
  821. if (!silent) {
  822. $q.notify({
  823. type: 'positive',
  824. message: t('admin.storage.saveSuccess')
  825. })
  826. }
  827. } else {
  828. throw new Error(resp?.data?.updateStorageTargets?.status?.message || 'Unexpected error')
  829. }
  830. } catch (err) {
  831. $q.notify({
  832. type: 'negative',
  833. message: t('admin.storage.saveFailed'),
  834. caption: err.message
  835. })
  836. }
  837. if (!silent) { $q.loading.hide() }
  838. return saveSuccess
  839. }
  840. function getTargetSubtitle (target) {
  841. if (!target.isEnabled) {
  842. return t('admin.storage.inactiveTarget')
  843. }
  844. const hasPages = target.contentTypes?.activeTypes?.includes('pages')
  845. const hasAssets = target.contentTypes?.activeTypes?.filter(c => c !== 'pages')?.length > 0
  846. if (hasPages && hasAssets) {
  847. return t('admin.storage.pagesAndAssets')
  848. } else if (hasPages) {
  849. return t('admin.storage.pagesOnly')
  850. } else if (hasAssets) {
  851. return t('admin.storage.assetsOnly')
  852. } else {
  853. return t('admin.storage.notConfigured')
  854. }
  855. }
  856. function getTargetSubtitleColor (target) {
  857. if (state.selectedTarget === target.id) {
  858. return 'text-blue-2'
  859. } else if (target.isEnabled) {
  860. return 'text-positive'
  861. } else {
  862. return 'text-grey-7'
  863. }
  864. }
  865. function getDefaultSchedule (val) {
  866. if (!val) { return 'N/A' }
  867. return '' // moment.duration(val).format('y [years], M [months], d [days], h [hours], m [minutes]')
  868. }
  869. async function executeAction (targetKey, handler) {
  870. // this.$store.commit('loadingStart', 'admin-storage-executeaction')
  871. // this.runningAction = true
  872. // this.runningActionHandler = handler
  873. // try {
  874. // await this.$apollo.mutate({
  875. // mutation: gql`{}`,
  876. // variables: {
  877. // targetKey,
  878. // handler
  879. // }
  880. // })
  881. // this.$store.commit('showNotification', {
  882. // message: 'Action completed.',
  883. // style: 'success',
  884. // icon: 'check'
  885. // })
  886. // } catch (err) {
  887. // console.warn(err)
  888. // }
  889. // this.runningAction = false
  890. // this.runningActionHandler = ''
  891. // this.$store.commit('loadingStop', 'admin-storage-executeaction')
  892. }
  893. async function handleSetupCallback () {
  894. if (state.targets.length < 1 || !state.selectedTarget) { return }
  895. nextTick(() => {
  896. if (state.target?.setup?.handler === 'github' && route.query.code) {
  897. setupGitHubStep('connect', route.query.code)
  898. }
  899. })
  900. }
  901. async function setupDestroy () {
  902. $q.dialog({
  903. title: t('admin.storage.destroyConfirm'),
  904. message: t('admin.storage.destroyConfirmInfo'),
  905. cancel: true,
  906. persistent: true
  907. }).onOk(async () => {
  908. $q.loading.show({
  909. message: t('admin.storage.destroyingSetup')
  910. })
  911. try {
  912. const resp = await APOLLO_CLIENT.mutate({
  913. mutation: gql`
  914. mutation (
  915. $targetId: UUID!
  916. ) {
  917. destroyStorageTargetSetup(
  918. targetId: $targetId
  919. ) {
  920. status {
  921. succeeded
  922. message
  923. }
  924. }
  925. }
  926. `,
  927. variables: {
  928. targetId: state.selectedTarget
  929. }
  930. })
  931. if (resp?.data?.destroyStorageTargetSetup?.status?.succeeded) {
  932. state.target.setup.state = 'notconfigured'
  933. setTimeout(() => {
  934. $q.loading.hide()
  935. $q.notify({
  936. type: 'positive',
  937. message: t('admin.storage.githubSetupDestroySuccess')
  938. })
  939. }, 2000)
  940. } else {
  941. throw new Error(resp?.data?.destroyStorageTargetSetup?.status?.message || 'Unexpected error')
  942. }
  943. } catch (err) {
  944. $q.notify({
  945. type: 'negative',
  946. message: t('admin.storage.githubSetupDestroyFailed'),
  947. caption: err.message
  948. })
  949. $q.loading.hide()
  950. }
  951. })
  952. }
  953. async function setupGitHub () {
  954. // -> Format values
  955. state.target.setup.values.publicUrl = state.target.setup.values.publicUrl.toLowerCase()
  956. // -> Basic input check
  957. if (state.target.setup.values.accountType === 'org' && state.target.setup.values.org.length < 1) {
  958. return $q.notify({
  959. type: 'negative',
  960. message: 'Invalid GitHub Organization',
  961. caption: 'Enter a valid github organization.'
  962. })
  963. }
  964. if (state.target.setup.values.publicUrl.length < 11 || !/^https?:\/\/.{4,}$/.test(state.target.setup.values.publicUrl)) {
  965. return $q.notify({
  966. type: 'negative',
  967. message: 'Invalid Wiki Public URL',
  968. caption: 'Enter a valid public URL for your wiki.'
  969. })
  970. }
  971. if (state.target.setup.values.publicUrl.endsWith('/')) {
  972. state.target.setup.values.publicUrl = state.target.setup.values.publicUrl.slice(0, -1)
  973. }
  974. // -> Generate manifest
  975. state.setupCfg.loading = true
  976. if (state.target.setup.values.accountType === 'org') {
  977. state.setupCfg.action = `https://github.com/organizations/${state.target.setup.values.org}/settings/apps/new`
  978. } else {
  979. state.setupCfg.action = 'https://github.com/settings/apps/new'
  980. }
  981. state.setupCfg.manifest = JSON.stringify({
  982. name: `Wiki.js - ${adminStore.currentSiteId.slice(-12)}`,
  983. description: 'Connects your Wiki.js to GitHub repositories and synchronize their contents.',
  984. url: state.target.setup.values.publicUrl,
  985. hook_attributes: {
  986. url: `${state.target.setup.values.publicUrl}/_github/${adminStore.currentSiteId}/events`
  987. },
  988. redirect_url: `${state.target.setup.values.publicUrl}/_admin/${adminStore.currentSiteId}/storage/${state.target.id}`,
  989. callback_urls: [
  990. `${state.target.setup.values.publicUrl}/_admin/${adminStore.currentSiteId}/storage/${state.target.id}`
  991. ],
  992. public: false,
  993. default_permissions: {
  994. contents: 'write',
  995. metadata: 'read',
  996. members: 'read'
  997. },
  998. default_events: [
  999. 'create',
  1000. 'delete',
  1001. 'push'
  1002. ]
  1003. })
  1004. $q.loading.show({
  1005. message: t('admin.storage.githubPreparingManifest')
  1006. })
  1007. if (await save({ silent: true })) {
  1008. githubSetupForm.value.submit()
  1009. } else {
  1010. state.setupCfg.loading = false
  1011. $q.loading.hide()
  1012. }
  1013. }
  1014. async function setupGitHubStep (step, code) {
  1015. $q.loading.show({
  1016. message: t('admin.storage.githubVerifying')
  1017. })
  1018. try {
  1019. const resp = await APOLLO_CLIENT.mutate({
  1020. mutation: gql`
  1021. mutation (
  1022. $targetId: UUID!
  1023. $state: JSON!
  1024. ) {
  1025. setupStorageTarget(
  1026. targetId: $targetId
  1027. state: $state
  1028. ) {
  1029. status {
  1030. succeeded
  1031. message
  1032. }
  1033. state
  1034. }
  1035. }
  1036. `,
  1037. variables: {
  1038. targetId: this.selectedTarget,
  1039. state: {
  1040. step,
  1041. ...code && { code }
  1042. }
  1043. }
  1044. })
  1045. if (resp?.data?.setupStorageTarget?.status?.succeeded) {
  1046. switch (resp.data.setupStorageTarget.state?.nextStep) {
  1047. case 'installApp': {
  1048. router.replace({ query: null })
  1049. $q.loading.hide()
  1050. $q.dialog({
  1051. component: GithubSetupInstallDialog,
  1052. persistent: true
  1053. }).onOk(() => {
  1054. $q.loading.show({
  1055. message: t('admin.storage.githubRedirecting')
  1056. })
  1057. window.location.assign(resp.data.setupStorageTarget.state?.url)
  1058. }).onCancel(() => {
  1059. throw new Error('Setup was aborted prematurely.')
  1060. })
  1061. break
  1062. }
  1063. case 'completed': {
  1064. this.target.isEnabled = true
  1065. this.target.setup.state = 'configured'
  1066. setTimeout(() => {
  1067. $q.loading.hide()
  1068. $q.notify({
  1069. type: 'positive',
  1070. message: t('admin.storage.githubSetupSuccess')
  1071. })
  1072. }, 2000)
  1073. break
  1074. }
  1075. default: {
  1076. throw new Error('Unknown Setup Step')
  1077. }
  1078. }
  1079. } else {
  1080. throw new Error(resp?.data?.setupStorageTarget?.status?.message || 'Unexpected error')
  1081. }
  1082. } catch (err) {
  1083. $q.loading.hide()
  1084. $q.notify({
  1085. type: 'negative',
  1086. message: t('admin.storage.githubSetupFailed'),
  1087. caption: err.message
  1088. })
  1089. }
  1090. }
  1091. function generateGraph () {
  1092. const types = [
  1093. { key: 'images', label: t('admin.storage.contentTypeImages'), icon: 'las', iconText: '&#xf1c5;' },
  1094. { key: 'documents', label: t('admin.storage.contentTypeDocuments'), icon: 'las', iconText: '&#xf1c1;' },
  1095. { key: 'others', label: t('admin.storage.contentTypeOthers'), icon: 'las', iconText: '&#xf15b;' },
  1096. { key: 'large', label: t('admin.storage.contentTypeLargeFiles'), icon: 'las', iconText: '&#xf1c6;' }
  1097. ]
  1098. // -> Create PagesNodes
  1099. state.deliveryNodes = {
  1100. user: { name: t('admin.storage.deliveryPathsUser'), borderRadius: 16, icon: '/_assets/icons/fluent-account.svg' },
  1101. pages: { name: t('admin.storage.contentTypePages'), color: '#3f51b5', icon: 'las', iconText: '&#xf15c;' },
  1102. pages_wiki: { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
  1103. }
  1104. state.deliveryEdges = {
  1105. user_pages: { source: 'user', target: 'pages' },
  1106. pages_in: { source: 'pages', target: 'pages_wiki' },
  1107. pages_out: { source: 'pages_wiki', target: 'pages' }
  1108. }
  1109. state.deliveryLayouts.nodes = {
  1110. user: { x: -30, y: 30 },
  1111. pages: { x: 0, y: 0 },
  1112. pages_wiki: { x: 60, y: 0 }
  1113. }
  1114. state.deliveryPaths = []
  1115. // -> Create Asset Nodes
  1116. for (const [i, tp] of types.entries()) {
  1117. state.deliveryNodes[tp.key] = { name: tp.label, color: '#3f51b5', icon: tp.icon, iconText: tp.iconText }
  1118. state.deliveryEdges[`user_${tp.key}`] = { source: 'user', target: tp.key }
  1119. state.deliveryLayouts.nodes[tp.key] = { x: 0, y: (i + 1) * 15 }
  1120. // -> Find target with direct access
  1121. const dt = find(state.targets, tgt => {
  1122. return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(tp.key) && tgt.isEnabled && tgt.assetDelivery.isDirectAccessSupported && tgt.assetDelivery.directAccess
  1123. })
  1124. if (dt) {
  1125. state.deliveryNodes[`${tp.key}_${dt.module}`] = { name: dt.title, icon: dt.icon }
  1126. state.deliveryNodes[`${tp.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
  1127. state.deliveryLayouts.nodes[`${tp.key}_${dt.module}`] = { x: 60, y: (i + 1) * 15 }
  1128. state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 120, y: (i + 1) * 15 }
  1129. state.deliveryEdges[`${tp.key}_${dt.module}_in`] = { source: tp.key, target: `${tp.key}_${dt.module}` }
  1130. state.deliveryEdges[`${tp.key}_${dt.module}_out`] = { source: `${tp.key}_${dt.module}`, target: tp.key }
  1131. state.deliveryEdges[`${tp.key}_${dt.module}_wiki`] = { source: `${tp.key}_wiki`, target: `${tp.key}_${dt.module}`, color: '#02c39a', animationSpeed: 25 }
  1132. continue
  1133. }
  1134. // -> Find target with streaming
  1135. const st = find(state.targets, tgt => {
  1136. return tgt.module !== 'db' && tgt.contentTypes.activeTypes.includes(tp.key) && tgt.isEnabled && tgt.assetDelivery.isStreamingSupported && tgt.assetDelivery.streaming
  1137. })
  1138. if (st) {
  1139. state.deliveryNodes[`${tp.key}_${st.module}`] = { name: st.title, icon: st.icon }
  1140. state.deliveryNodes[`${tp.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
  1141. state.deliveryLayouts.nodes[`${tp.key}_${st.module}`] = { x: 120, y: (i + 1) * 15 }
  1142. state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
  1143. state.deliveryEdges[`${tp.key}_wiki_in`] = { source: tp.key, target: `${tp.key}_wiki` }
  1144. state.deliveryEdges[`${tp.key}_wiki_out`] = { source: `${tp.key}_wiki`, target: tp.key }
  1145. state.deliveryEdges[`${tp.key}_${st.module}_out`] = { source: `${tp.key}_${st.module}`, target: `${tp.key}_wiki` }
  1146. state.deliveryEdges[`${tp.key}_${st.module}_in`] = { source: `${tp.key}_wiki`, target: `${tp.key}_${st.module}` }
  1147. state.deliveryEdges[`${tp.key}_${st.module}_wiki`] = { source: `${tp.key}_wiki`, target: `${tp.key}_${st.module}`, color: '#02c39a', animationSpeed: 25 }
  1148. continue
  1149. }
  1150. // -> Check DB fallback
  1151. const dbt = find(state.targets, ['module', 'db'])
  1152. if (dbt.contentTypes.activeTypes.includes(tp.key)) {
  1153. state.deliveryNodes[`${tp.key}_wiki`] = { name: 'Wiki.js', icon: '/_assets/logo-wikijs.svg', color: '#161b22' }
  1154. state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
  1155. state.deliveryEdges[`${tp.key}_db_in`] = { source: tp.key, target: `${tp.key}_wiki` }
  1156. state.deliveryEdges[`${tp.key}_db_out`] = { source: `${tp.key}_wiki`, target: tp.key }
  1157. } else {
  1158. state.deliveryNodes[`${tp.key}_wiki`] = { name: t('admin.storage.missingOrigin'), color: '#f03a47', icon: 'las', iconText: '&#xf071;' }
  1159. state.deliveryLayouts.nodes[`${tp.key}_wiki`] = { x: 60, y: (i + 1) * 15 }
  1160. state.deliveryEdges[`${tp.key}_db_in`] = { source: tp.key, target: `${tp.key}_wiki`, color: '#f03a47', animate: false }
  1161. state.deliveryPaths.push({ edges: [`${tp.key}_db_in`], color: '#f03a4755' })
  1162. }
  1163. }
  1164. console.info(state.deliveryEdges)
  1165. }
  1166. // MOUNTED
  1167. onMounted(() => {
  1168. if (!state.selectedTarget && route.params.id) {
  1169. if (state.targets.length < 1) {
  1170. state.desiredTarget = route.params.id
  1171. } else {
  1172. state.selectedTarget = route.params.id
  1173. }
  1174. }
  1175. if (adminStore.currentSiteId) {
  1176. load()
  1177. }
  1178. handleSetupCallback()
  1179. })
  1180. </script>
  1181. <style lang='scss' scoped>
  1182. .admin-storage-logo {
  1183. border-radius: 5px;
  1184. }
  1185. </style>