Site.coffee 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. class Site extends Class
  2. constructor: (row, @item_list) ->
  3. @deleted = false
  4. @show_errors = false
  5. @message_visible = false
  6. @message = null
  7. @message_class = ""
  8. @message_collapsed = false
  9. @message_timer = null
  10. @favorite = Page.settings.favorite_sites[row.address]
  11. @key = row.address
  12. @optional_helps = []
  13. @optional_helps_disabled = {}
  14. @setRow(row)
  15. @files = new SiteFiles(@)
  16. @menu = new Menu()
  17. @menu_helps = null
  18. setRow: (row) ->
  19. # Default values
  20. row.settings.modified ?= 0
  21. row.settings.size ?= 0
  22. row.settings.added ?= 0
  23. row.settings.peers ?= 0
  24. # Message
  25. if row.event?[0] == "updated" and row.content_updated != false
  26. @setMessage "Updated!", "done"
  27. else if row.event?[0] == "updating"
  28. @setMessage "Updating..."
  29. else if row.tasks > 0
  30. @setMessage "Updating: #{Math.max(row.tasks, row.bad_files)} left"
  31. else if row.bad_files > 0
  32. if row.peers <= 1
  33. @setMessage "No peers", "error"
  34. else
  35. @setMessage row.bad_files+" file update failed", "error"
  36. else if row.content_updated == false
  37. if row.peers <= 1
  38. @setMessage "No peers", "error"
  39. else
  40. @setMessage "Update failed", "error"
  41. else if row.tasks == 0 and @row?.tasks > 0
  42. @setMessage "Updated!", "done"
  43. if not row.body?
  44. row.body = ""
  45. @optional_helps = ([key, val] for key, val of row.settings.optional_help)
  46. @row = row
  47. setMessage: (message, @message_class="") ->
  48. # Set message
  49. if message
  50. @message = message
  51. @message_visible = true
  52. if @message_class == "error" and not @show_errors
  53. @message_collapsed = true
  54. else
  55. @message_collapsed = false
  56. else
  57. @message_visible = false
  58. # Hide done message after 3 seconds
  59. clearInterval(@message_timer)
  60. if @message_class == "done"
  61. @message_timer = setTimeout (=>
  62. @setMessage("")
  63. ), 5000
  64. Page.projector.scheduleRender()
  65. isWorking: ->
  66. @row.tasks > 0 or @row.event?[0] == "updating"
  67. handleFavoriteClick: =>
  68. @favorite = true
  69. @menu = new Menu()
  70. Page.settings.favorite_sites[@row.address] = true
  71. Page.saveSettings()
  72. Page.site_list.reorder()
  73. return false
  74. handleUnfavoriteClick: =>
  75. @favorite = false
  76. @menu = new Menu()
  77. delete Page.settings.favorite_sites[@row.address]
  78. Page.saveSettings()
  79. Page.site_list.reorder()
  80. return false
  81. handleUpdateClick: =>
  82. Page.cmd "siteUpdate", {"address": @row.address}
  83. @show_errors = true
  84. return false
  85. handleCheckfilesClick: =>
  86. Page.cmd "siteUpdate", {"address": @row.address, "check_files": true, since: 0}
  87. @show_errors = true
  88. return false
  89. handleResumeClick: =>
  90. Page.cmd "siteResume", {"address": @row.address}
  91. return false
  92. handlePauseClick: =>
  93. Page.cmd "sitePause", {"address": @row.address}
  94. return false
  95. handleCloneClick: =>
  96. Page.cmd "siteClone", {"address": @row.address}
  97. return false
  98. handleCloneUpgradeClick: =>
  99. Page.cmd "wrapperConfirm", ["Are you sure?" + " Any modifications you made on<br><b>#{@row.content.title}</b> site's js/css files will be lost.", "Upgrade"], (confirmed) =>
  100. Page.cmd "siteClone", {"address": @row.content.cloned_from, "root_inner_path": @row.content.clone_root, "target_address": @row.address}
  101. return false
  102. handleDeleteClick: =>
  103. if @row.settings.own or @row.privatekey
  104. Page.cmd "wrapperConfirm", ["You can delete your site using the site's sidebar.", ["Open site"]], (confirmed) =>
  105. if (confirmed)
  106. window.top.location = @getHref() + "#ZeroNet:OpenSidebar"
  107. else
  108. if not @row.content.title
  109. Page.cmd "siteDelete", {"address": @row.address}
  110. @item_list.deleteItem(@)
  111. Page.projector.scheduleRender()
  112. else if Page.server_info.rev > 2060
  113. Page.cmd "wrapperConfirm", ["Are you sure?" + " <b>#{@row.content.title}</b>", ["Delete", "Blacklist"]], (confirmed) =>
  114. if confirmed == 1
  115. Page.cmd "siteDelete", {"address": @row.address}
  116. @item_list.deleteItem(@)
  117. Page.projector.scheduleRender()
  118. else if confirmed == 2
  119. Page.cmd "wrapperPrompt", ["Blacklist <b>#{@row.content.title}</b>", "text", "Delete and Blacklist", "Reason"], (reason) =>
  120. Page.cmd "siteDelete", {"address": @row.address}
  121. Page.cmd "siteblockAdd", [@row.address, reason]
  122. @item_list.deleteItem(@)
  123. Page.projector.scheduleRender()
  124. else
  125. Page.cmd "wrapperConfirm", ["Are you sure?" + " <b>#{@row.content.title}</b>", "Delete"], (confirmed) =>
  126. if confirmed
  127. Page.cmd "siteDelete", {"address": @row.address}
  128. @item_list.deleteItem(@)
  129. Page.projector.scheduleRender()
  130. return false
  131. handleSettingsClick: (e) =>
  132. @menu.items = []
  133. if @favorite
  134. @menu.items.push ["Unfavorite", @handleUnfavoriteClick]
  135. else
  136. @menu.items.push ["Favorite", @handleFavoriteClick]
  137. @menu.items.push ["Update", @handleUpdateClick]
  138. @menu.items.push ["Check files", @handleCheckfilesClick]
  139. if @row.settings.serving
  140. @menu.items.push ["Pause", @handlePauseClick]
  141. else
  142. @menu.items.push ["Resume", @handleResumeClick]
  143. @menu.items.push ["Save as .zip", "/ZeroNet-Internal/Zip?address=#{@row.address}"]
  144. if @row.content.cloneable == true
  145. @menu.items.push ["Clone", @handleCloneClick]
  146. if @row.settings.own and @row.content.cloned_from and Page.server_info.rev >= 2080
  147. @menu.items.push ["---"]
  148. @menu.items.push ["Upgrade code", @handleCloneUpgradeClick]
  149. @menu.items.push ["---"]
  150. @menu.items.push ["Delete", @handleDeleteClick]
  151. if @menu.visible
  152. @menu.hide()
  153. else
  154. @menu.show()
  155. return false
  156. handleHelpClick: (directory, title) =>
  157. if @optional_helps_disabled[directory]
  158. Page.cmd "OptionalHelp", [directory, title, @row.address]
  159. delete @optional_helps_disabled[directory]
  160. else
  161. Page.cmd "OptionalHelpRemove", [directory, @row.address]
  162. @optional_helps_disabled[directory] = true
  163. return true
  164. handleHelpAllClick: =>
  165. if @row.settings.autodownloadoptional == true
  166. Page.cmd "OptionalHelpAll", [false, @row.address], =>
  167. @row.settings.autodownloadoptional = false
  168. Page.projector.scheduleRender()
  169. else
  170. Page.cmd "OptionalHelpAll", [true, @row.address], =>
  171. @row.settings.autodownloadoptional = true
  172. Page.projector.scheduleRender()
  173. handleHelpsClick: (e) =>
  174. if e.target.classList.contains("menu-item")
  175. return
  176. if not @menu_helps
  177. @menu_helps = new Menu()
  178. @menu_helps.items = []
  179. @menu_helps.items.push ["Help distribute all new files", @handleHelpAllClick, ( => return @row.settings.autodownloadoptional)]
  180. if @optional_helps.length > 0
  181. @menu_helps.items.push ["---"]
  182. for [directory, title] in @optional_helps
  183. @menu_helps.items.push [title, ( => @handleHelpClick(directory, title) ), ( => return not @optional_helps_disabled[directory] )]
  184. @menu_helps.toggle()
  185. return true
  186. getHref: (row) ->
  187. if @row.content?.domain
  188. ext = @row.content?.domain.split(".").pop()
  189. supported_plugins = {
  190. bit: ["Zeroname", "Dnschain", "Zeroname-local"],
  191. yo: ["NameYo"],
  192. yu: ["NameYo"],
  193. of: ["NameYo"],
  194. inf: ["NameYo"],
  195. zn: ["NameYo"],
  196. list: ["NameYo"]
  197. }[ext] or []
  198. has_plugin = Page.server_info?.plugins? and supported_plugins.some((plugin) -> plugin in Page.server_info?.plugins)
  199. else
  200. has_plugin = false
  201. if has_plugin and @row.content?.domain # Domain
  202. href = Text.getSiteUrl(@row.content.domain)
  203. else # Address
  204. href = Text.getSiteUrl(@row.address)
  205. if row?.inner_path
  206. return href + row.inner_path
  207. else
  208. return href
  209. handleLimitIncreaseClick: =>
  210. if Page.server_info.rev < 3170
  211. return Page.cmd "wrapperNotification", ["info", "You need ZeroNet Rev3170 to use this command"]
  212. Page.cmd "as", [@row.address, "siteSetLimit", @row.need_limit], (res) =>
  213. if res == "ok"
  214. Page.cmd "wrapperNotification", ["done", "Site <b>#{@row.content.title}</b> storage limit modified to <b>#{@row.need_limit}MB</b>", 5000]
  215. else
  216. Page.cmd "wrapperNotification", ["error", res.error]
  217. Page.projector.scheduleRender()
  218. return false
  219. render: =>
  220. now = Date.now()/1000
  221. h("div.site", {
  222. key: @key, "data-key": @key,
  223. classes: {
  224. "modified-lastday": now - @row.settings.modified < 60*60*24,
  225. "disabled": not @row.settings.serving and not @row.demo,
  226. "working": @isWorking()
  227. }
  228. },
  229. h("div.circle", {style: "color: #{Text.toColor(@row.address, 40, 50)}"}, ["\u2022"]),
  230. h("a.inner", {href: @getHref(), title: @row.content.title if @row.content.title?.length > 20}, [
  231. h("span.title", [@row.content.title or @row.address]),
  232. h("div.details", [
  233. h("span.modified", [
  234. h("div.icon-clock")
  235. if Page.settings.sites_orderby == "size"
  236. h("span.value", [(@row.settings.size/1024/1024 + @row.settings.size_optional?/1024/1024).toFixed(1), "MB"])
  237. else
  238. h("span.value", [Time.since(@row.settings.modified)])
  239. ]),
  240. h("span.peers", [
  241. h("div.icon-profile")
  242. h("span.value", [Math.max((if @row.settings.peers then @row.settings.peers else 0), @row.peers)])
  243. ])
  244. ])
  245. if @row.demo
  246. h("div.details.demo", "Activate \u00BB")
  247. if @row.need_limit
  248. h("a.details.needaction", {href: "#Set+limit", onclick: @handleLimitIncreaseClick}, "Set limit to #{@row.need_limit}MB")
  249. h("div.message",
  250. {classes: {visible: @message_visible, done: @message_class == 'done', error: @message_class == 'error', collapsed: @message_collapsed}},
  251. [@message]
  252. )
  253. ]),
  254. h("a.settings", {href: "#Settings", tabIndex: -1, onmousedown: @handleSettingsClick, onclick: Page.returnFalse}, ["\u22EE"]),
  255. @menu.render()
  256. )
  257. renderCircle: (value, max) ->
  258. if value < 1
  259. dashoffset = 75+(1-value)*75
  260. else
  261. dashoffset = Math.max(0, 75-((value-1)/9)*75)
  262. stroke = "hsl(#{Math.min(555, value*50)}, 100%, 61%)"
  263. return h("div.circle", {title: "Upload/Download ratio", innerHTML: """
  264. <svg class="circle-svg" width="30" height="30" viewPort="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg">
  265. <circle r="12" cx="15" cy="15" fill="transparent" class='circle-bg'></circle>
  266. <circle r="12" cx="15" cy="15" fill="transparent" class='circle-fg' style='stroke-dashoffset: #{dashoffset}; stroke: #{stroke}'></circle>
  267. </svg>
  268. """})
  269. renderOptionalStats: =>
  270. row = @row
  271. row.settings.bytes_sent ?= 0
  272. ratio = (row.settings.bytes_sent/row.settings.bytes_recv).toFixed(1)
  273. if ratio >= 100
  274. ratio = "\u221E"
  275. else if ratio >= 10
  276. ratio = (row.settings.bytes_sent/row.settings.bytes_recv).toFixed(0)
  277. ratio_hue = Math.min(555, (row.settings.bytes_sent/row.settings.bytes_recv)*50)
  278. h("div.site", {key: @key}, [
  279. h("div.title", [
  280. h("h3.name", h("a", {href: @getHref()}, row.content.title)),
  281. h("div.size", {title: "Site size limit: #{Text.formatSize(row.size_limit*1024*1024)}"}, [
  282. "#{Text.formatSize(row.settings.size)}",
  283. h("div.bar", h("div.bar-active", {style: "width: #{100*(row.settings.size/(row.size_limit*1024*1024))}%"}))
  284. ]),
  285. h("div.plus", "+"),
  286. h("div.size.size-optional", {title: "Optional files on site: #{Text.formatSize(row.settings.size_optional)}"}, [
  287. "#{Text.formatSize(row.settings.optional_downloaded)}",
  288. h("span.size-title", "Optional"),
  289. h("div.bar", h("div.bar-active", {style: "width: #{100*(row.settings.optional_downloaded/row.settings.size_optional)}%"}))
  290. ]),
  291. h("a.helps", {href: "#", onmousedown: @handleHelpsClick, onclick: Page.returnFalse},
  292. h("div.icon-share"),
  293. if @row.settings.autodownloadoptional then "\u2661" else @optional_helps.length,
  294. h("div.icon-arrow-down")
  295. if @menu_helps then @menu_helps.render()
  296. ),
  297. @renderCircle(parseFloat((row.settings.bytes_sent/row.settings.bytes_recv).toFixed(1)), 10),
  298. h("div.circle-value", {classes: {negative: ratio < 1}, style: "color: hsl(#{ratio_hue}, 70%, 60%)"}, ratio),
  299. h("div.transfers", [
  300. h("div.up", {"title": "Uploaded"}, "\u22F0 \u00A0#{Text.formatSize(row.settings.bytes_sent)}"),
  301. h("div.down", {"title": "Downloaded"}, "\u22F1 \u00A0#{Text.formatSize(row.settings.bytes_recv)}")
  302. ])
  303. ])
  304. @files.render()
  305. ])
  306. window.Site = Site