diff --git a/.meteor/packages b/.meteor/packages
index 5dd240ea647..e64873f3d2a 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -123,3 +123,4 @@ yasaricli:slugify
yasinuslu:blaze-meta
# sanjo:jasmine
# velocity:html-reporter
+rocketchat:assets
diff --git a/.meteor/versions b/.meteor/versions
index 44db73cc5ff..8c88fab5cc1 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -122,6 +122,7 @@ reactive-dict@1.1.3
reactive-var@1.0.6
reload@1.1.4
retry@1.0.4
+rocketchat:assets@0.0.1
rocketchat:authorization@0.0.1
rocketchat:autolinker@0.0.1
rocketchat:channel-settings@0.0.1
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index fd9b9d2b84f..9dc586d6867 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -168,10 +168,14 @@
"Install_FxOs_done" : "Great! You can now use Rocket.Chat via the icon on your homescreen. Have fun with Rocket.Chat!",
"Install_FxOs_error" : "Sorry, that did not work as intended! The following error appeared:",
"Install_FxOs_follow_instructions" : "Please confirm the app installation on your device (press \"Install\" when prompted).",
+ "Invalid_asset" : "Invalid asset",
"Invalid_confirm_pass" : "The password confirmation does not match password",
"Invalid_Secret_URL" : "Invalid Secret URL",
"Invalid_secret_URL_message" : "The URL provided is invalid.",
"Invalid_email" : "The e-mail entered is invalid",
+ "Invalid_file_height" : "Invalid file height",
+ "Invalid_file_type" : "Invalid file type",
+ "Invalid_file_width" : "Invalid file width",
"Invalid_name" : "The name must not be empty",
"Invalid_pass" : "The password must not be empty",
"Invalid_room_name" : "%s is not a valid room name,
use only letters, numbers and dashes",
@@ -419,6 +423,7 @@
"Unread_Rooms" : "Unread Rooms",
"Unread_Rooms_Mode" : "Unread Rooms Mode",
"Upload_file_question" : "Upload file?",
+ "Uploading_file" : "Uploading file...",
"Use_Emojis" : "Use Emojis",
"Use_initials_avatar" : "Use your username initials",
"use_menu" : "Use the side menu to access your rooms and chats",
diff --git a/packages/rocketchat-assets/.npm/package/.gitignore b/packages/rocketchat-assets/.npm/package/.gitignore
new file mode 100644
index 00000000000..3c3629e647f
--- /dev/null
+++ b/packages/rocketchat-assets/.npm/package/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/packages/rocketchat-assets/.npm/package/README b/packages/rocketchat-assets/.npm/package/README
new file mode 100644
index 00000000000..3d492553a43
--- /dev/null
+++ b/packages/rocketchat-assets/.npm/package/README
@@ -0,0 +1,7 @@
+This directory and the files immediately inside it are automatically generated
+when you change this package's NPM dependencies. Commit the files in this
+directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
+so that others run the same versions of sub-dependencies.
+
+You should NOT check in the node_modules directory that Meteor automatically
+creates; if you are using git, the .gitignore file tells git to ignore it.
diff --git a/packages/rocketchat-assets/.npm/package/npm-shrinkwrap.json b/packages/rocketchat-assets/.npm/package/npm-shrinkwrap.json
new file mode 100644
index 00000000000..f69e18d8fe1
--- /dev/null
+++ b/packages/rocketchat-assets/.npm/package/npm-shrinkwrap.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "image-size": {
+ "version": "0.4.0"
+ }
+ }
+}
diff --git a/packages/rocketchat-assets/package.js b/packages/rocketchat-assets/package.js
new file mode 100644
index 00000000000..e5f75eaf3d2
--- /dev/null
+++ b/packages/rocketchat-assets/package.js
@@ -0,0 +1,28 @@
+Package.describe({
+ name: 'rocketchat:assets',
+ version: '0.0.1',
+ summary: '',
+ git: ''
+});
+
+Package.onUse(function(api) {
+ api.versionsFrom('1.0');
+
+ api.use([
+ 'coffeescript',
+ 'underscore',
+ 'webapp',
+ 'rocketchat:file',
+ 'rocketchat:lib@0.0.1'
+ ]);
+
+ api.addFiles('server/assets.coffee', 'server');
+});
+
+Npm.depends({
+ "image-size": "0.4.0"
+});
+
+Package.onTest(function(api) {
+
+});
diff --git a/packages/rocketchat-assets/server/assets.coffee b/packages/rocketchat-assets/server/assets.coffee
new file mode 100644
index 00000000000..acf9d258456
--- /dev/null
+++ b/packages/rocketchat-assets/server/assets.coffee
@@ -0,0 +1,165 @@
+sizeOf = Npm.require 'image-size'
+
+
+@RocketChatAssetsInstance = new RocketChatFile.GridFS
+ name: 'assets'
+
+
+assets =
+ 'favicon.ico':
+ label: 'favicon.ico'
+ defaultUrl: 'favicon.ico?v=3'
+ constraints:
+ type: 'image'
+ contentType: 'image/vnd.microsoft.icon'
+ extention: 'ico'
+ width: undefined
+ height: undefined
+ 'favicon.svg':
+ label: 'favicon.svg'
+ defaultUrl: '/images/logo/icon.svg?v=3'
+ constraints:
+ type: 'image'
+ contentType: 'image/svg+xml'
+ extention: 'svg'
+ width: undefined
+ height: undefined
+ 'favicon_64.png':
+ label: 'favicon.png (64x64)'
+ defaultUrl: 'images/logo/favicon-64x64.png?v=3'
+ constraints:
+ type: 'image'
+ contentType: 'image/png'
+ extention: 'png'
+ width: 64
+ height: 64
+ 'favicon_96.png':
+ label: 'favicon.png (96x96)'
+ defaultUrl: 'images/logo/favicon-96x96.png?v=3'
+ constraints:
+ type: 'image'
+ contentType: 'image/png'
+ extention: 'png'
+ width: 96
+ height: 96
+ 'favicon_128.png':
+ label: 'favicon.png (128x128)'
+ defaultUrl: 'images/logo/favicon-128x128.png?v=3'
+ constraints:
+ type: 'image'
+ contentType: 'image/png'
+ extention: 'png'
+ width: 128
+ height: 128
+ 'favicon_192.png':
+ label: 'favicon.png (192x192)'
+ defaultUrl: 'images/logo/android-chrome-192x192.png?v=3'
+ constraints:
+ type: 'image'
+ contentType: 'image/png'
+ extention: 'png'
+ width: 192
+ height: 192
+ 'favicon_256.png':
+ label: 'favicon.png (256x256)'
+ defaultUrl: 'images/logo/favicon-256x256.png?v=3'
+ constraints:
+ type: 'image'
+ contentType: 'image/png'
+ extention: 'png'
+ width: 256
+ height: 256
+
+
+RocketChat.settings.addGroup 'Assets'
+for key, value of assets
+ RocketChat.settings.add "Assets_#{key}", '', { type: 'asset', group: 'Assets', fileConstraints: value.constraints, i18nLabel: value.label, asset: key }
+
+
+Meteor.methods
+ unsetAsset: (asset) ->
+ unless Meteor.userId()
+ throw new Meteor.Error 'invalid-user', "[methods] unsetAsset -> Invalid user"
+
+ hasPermission = RocketChat.authz.hasPermission Meteor.userId(), 'manage-assets'
+ unless hasPermission
+ throw new Meteor.Error 'manage-assets-not-allowed', "[methods] unsetAsset -> Manage assets not allowed"
+
+ if not assets[asset]?
+ throw new Meteor.Error "Invalid_asset"
+
+ RocketChatAssetsInstance.deleteFile asset
+ RocketChat.settings.clearById "Assets_#{asset}"
+
+
+Meteor.methods
+ setAsset: (binaryContent, contentType, asset) ->
+ unless Meteor.userId()
+ throw new Meteor.Error 'invalid-user', "[methods] setAsset -> Invalid user"
+
+ hasPermission = RocketChat.authz.hasPermission Meteor.userId(), 'manage-assets'
+ unless hasPermission
+ throw new Meteor.Error 'manage-assets-not-allowed', "[methods] unsetAsset -> Manage assets not allowed"
+
+ if not assets[asset]?
+ throw new Meteor.Error "Invalid_asset"
+
+ if contentType isnt assets[asset].constraints.contentType
+ throw new Meteor.Error "Invalid_file_type"
+
+ file = new Buffer(binaryContent, 'binary')
+
+ if assets[asset].constraints.width? or assets[asset].constraints.height?
+ dimensions = sizeOf file
+
+ if assets[asset].constraints.width? and assets[asset].constraints.width isnt dimensions.width
+ throw new Meteor.Error "Invalid_file_width"
+
+ if assets[asset].constraints.height? and assets[asset].constraints.height isnt dimensions.height
+ throw new Meteor.Error "Invalid_file_height"
+
+ rs = RocketChatFile.bufferToStream file
+ RocketChatAssetsInstance.deleteFile asset
+ ws = RocketChatAssetsInstance.createWriteStream asset, contentType
+ ws.on 'end', Meteor.bindEnvironment ->
+ Meteor.setTimeout ->
+ RocketChat.settings.updateById "Assets_#{asset}", "/assets/#{asset}"
+ , 200
+
+ rs.pipe ws
+ return
+
+
+WebApp.connectHandlers.use '/assets/', (req, res, next) ->
+ params =
+ asset: decodeURIComponent(req.url.replace(/^\//, '').replace(/\?.*$/, ''))
+
+ file = RocketChatAssetsInstance.getFileWithReadStream params.asset
+
+ # res.setHeader 'Content-Disposition', 'inline'
+
+ if not file?
+ if assets[params.asset]?.defaultUrl?
+ res.writeHead 301,
+ Location: Meteor.absoluteUrl(assets[params.asset].defaultUrl)
+ else
+ res.writeHead 404
+ res.end()
+ return
+
+ reqModifiedHeader = req.headers["if-modified-since"];
+ if reqModifiedHeader?
+ if reqModifiedHeader == file.uploadDate?.toUTCString()
+ res.setHeader 'Last-Modified', reqModifiedHeader
+ res.writeHead 304
+ res.end()
+ return
+
+ res.setHeader 'Cache-Control', 'public, max-age=0'
+ res.setHeader 'Expires', '-1'
+ res.setHeader 'Last-Modified', file.uploadDate?.toUTCString() or new Date().toUTCString()
+ res.setHeader 'Content-Type', file.contentType
+ res.setHeader 'Content-Length', file.length
+
+ file.readStream.pipe res
+ return
diff --git a/packages/rocketchat-authorization/server/startup.coffee b/packages/rocketchat-authorization/server/startup.coffee
index a938b289c01..e1b57d541e7 100644
--- a/packages/rocketchat-authorization/server/startup.coffee
+++ b/packages/rocketchat-authorization/server/startup.coffee
@@ -89,6 +89,9 @@ Meteor.startup ->
{ _id: 'access-permissions',
roles : ['admin']}
+
+ { _id: 'manage-assets',
+ roles : ['admin']}
]
#alanning:roles
diff --git a/packages/rocketchat-lib/server/functions/settings.coffee b/packages/rocketchat-lib/server/functions/settings.coffee
index 02ade134fca..34f829a6d9d 100644
--- a/packages/rocketchat-lib/server/functions/settings.coffee
+++ b/packages/rocketchat-lib/server/functions/settings.coffee
@@ -91,6 +91,19 @@ RocketChat.settings.updateById = (_id, value) ->
return RocketChat.models.Settings.updateValueById _id, value
+###
+# Update a setting by id
+# @param {String} _id
+###
+RocketChat.settings.clearById = (_id) ->
+ # console.log '[functions] RocketChat.settings.clearById -> '.green, 'arguments:', arguments
+
+ if not _id?
+ return false
+
+ return RocketChat.models.Settings.updateValueById _id, undefined
+
+
###
# Update a setting by id
###
diff --git a/packages/rocketchat-theme/assets/stylesheets/base.less b/packages/rocketchat-theme/assets/stylesheets/base.less
index bfa3d0bf261..a46ab872cad 100644
--- a/packages/rocketchat-theme/assets/stylesheets/base.less
+++ b/packages/rocketchat-theme/assets/stylesheets/base.less
@@ -1729,6 +1729,45 @@ a.github-fork {
width: 100%;
padding: 0;
}
+
+ .settings-file-preview {
+ display: flex;
+ align-items: center;
+
+ input[type=file] {
+ position: absolute !important;
+ width: 100%;
+ top: 0;
+ left: 0;
+ height: 100%;
+ opacity: 0;
+ z-index: 10000;
+ cursor: pointer;
+ * {
+ cursor: pointer;
+ }
+ }
+
+ .preview {
+ height: 50px;
+ width: 100px;
+ border-radius: 4px;
+ overflow: hidden;
+ box-shadow: 0 0 1px rgba(0,0,0,.5) inset;
+ background-size: contain;
+ background-position: center center;
+ background-repeat: no-repeat;
+
+ &.no-file {
+ background-color: #fafafa;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #ccc;
+ font-size: 24px;
+ }
+ }
+ }
}
.page-static {
diff --git a/packages/rocketchat-ui-admin/admin/admin.coffee b/packages/rocketchat-ui-admin/admin/admin.coffee
index f74cc51206a..fe0aeea2948 100644
--- a/packages/rocketchat-ui-admin/admin/admin.coffee
+++ b/packages/rocketchat-ui-admin/admin/admin.coffee
@@ -40,6 +40,9 @@ Template.admin.helpers
selectedOption: (_id, val) ->
return RocketChat.settings.get(_id) is val
+ random: ->
+ return Random.id()
+
Template.admin.events
"click .submit .save": (e, t) ->
group = FlowRouter.getParam('group')
@@ -100,6 +103,33 @@ Template.admin.events
swal config, ->
Meteor.call 'removeOAuthService', name
+ "click .delete-asset": ->
+ Meteor.call 'unsetAsset', @asset
+
+ "change input[type=file]": ->
+ e = event.originalEvent or event
+ files = e.target.files
+ if not files or files.length is 0
+ files = e.dataTransfer?.files or []
+
+ for blob in files
+ toastr.info TAPi18n.__ 'Uploading_file'
+
+ if @fileConstraints.contentType isnt blob.type
+ toastr.error TAPi18n.__ 'Invalid_file_type'
+ return
+
+ reader = new FileReader()
+ reader.readAsBinaryString(blob)
+ reader.onloadend = =>
+ Meteor.call 'setAsset', reader.result, blob.type, @asset, (err, data) ->
+ if err?
+ toastr.error TAPi18n.__ err.error
+ console.log err.error
+ return
+
+ toastr.success TAPi18n.__ 'File_uploaded'
+
Template.admin.onRendered ->
Tracker.afterFlush ->
diff --git a/packages/rocketchat-ui-admin/admin/admin.html b/packages/rocketchat-ui-admin/admin/admin.html
index f8152fc11a6..a13f52cb1b0 100644
--- a/packages/rocketchat-ui-admin/admin/admin.html
+++ b/packages/rocketchat-ui-admin/admin/admin.html
@@ -43,13 +43,16 @@
{{/if}}
{{/if}}
+
{{#if $eq type 'int'}}
{{/if}}
+
{{#if $eq type 'boolean'}}
{{/if}}
+
{{#if $eq type 'select'}}
{{/if}}
+
{{#if $eq type 'color'}}
{{/if}}
+
+ {{#if $eq type 'asset'}}
+ {{#if value}}
+