├── README.md ├── meteor-candy-config ├── .DS_Store ├── LICENSE.md ├── README.md ├── exports │ ├── client.js │ └── server.js ├── imports │ ├── client │ │ ├── README.md │ │ └── security │ │ │ ├── README.md │ │ │ └── index.js │ ├── server │ │ ├── README.md │ │ ├── _helpers │ │ │ ├── README.md │ │ │ ├── account.js │ │ │ ├── data.js │ │ │ └── index.js │ │ ├── account │ │ │ ├── packages │ │ │ │ ├── README.md │ │ │ │ └── index.js │ │ │ └── profile │ │ │ │ ├── README.md │ │ │ │ └── index.js │ │ ├── data │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── security │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── settings │ │ │ ├── README.md │ │ │ └── index.js │ │ └── table │ │ │ ├── README.md │ │ │ └── index.js │ └── shared │ │ ├── README.md │ │ ├── account │ │ └── tasks │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── functions │ │ ├── README.md │ │ └── index.js │ │ └── security │ │ ├── README.md │ │ └── index.js └── package.js ├── screencast.gif └── screenshot.png /README.md: -------------------------------------------------------------------------------- 1 | # Meteor Candy 2 | ### The Fast, Secure and Scalable Admin Panel for Meteor.js 3 | 4 | Manage your production application with Meteor Candy, the admin panel made just for Meteor. With tight integrations into Meteor's core packages, such as `accounts` and `mongo`, this package is the ultimate way to add an admin panel to your application. You can cover just about any feature you'd need with the built-in: 5 | 6 | - Account Management 7 | - Data Reports 8 | - Data Tables 9 | - Custom Functions 10 | - Settings 11 | 12 | **Meteor Candy comes pre-configured with features such as account impersonation, activity logging, and more.** However, the real magic is when you configure it work with your application and collections. For more information, and a live demo, please visit the product website. 13 | 14 | 15 | 16 | ## Getting Started 17 | 18 | Meteor Candy can be installed in a few minutes, and configured in a few hours. A free Development Kit is available for you to try it locally. To add it to your app, just follow these directions: 19 | 20 | 1. Copy the `./meteor-candy-config` folder from this repository to your packages folder 21 | - You folder path should look like this: `mymeteorapp/packages/meteor-candy-config` 22 | - The package.js file path should look like this: `mymeteorapp/packages/meteor-candy-config/package.js` 23 | 2. Install the configuration package, then the front-end package, then the core package 24 | - run `meteor add meteor-candy-config meteorcandy:blaze meteorcandy:core` 25 | 3. Open your web app and press Control + D to launch Meteor Candy 26 | 27 | ## Configuration Sample 28 | 29 | Feel free to browse the `./meteor-candy-config` folder to see configuration options and their associated documentation. Here is an example of how easy it is to configure a table. **Meteor Candy will automatically set up pagination, search, sorting, and more.** Meteor Candy will not release any additional data beyond what you specify in the `fields` array. 30 | 31 | ```javascript 32 | var subscriberData = { 33 | collection: "subscribers", 34 | label: "Subscribers", 35 | queries: [{ 36 | label: "Subscribed Only", 37 | query: '{"active": true}', 38 | }, { 39 | label: "Unsubscribed Only", 40 | query: '{"active": false}', 41 | }], 42 | fields: [{ 43 | field: "_id", 44 | label: "ID", 45 | }, { 46 | field: "email", 47 | label: "Email", 48 | }, { 49 | field: "active", 50 | label: "Active", 51 | },{ 52 | field: "created", 53 | label: "Joined", 54 | sort: -1 55 | }] 56 | } 57 | ``` 58 | 59 | ## Compared to yogiben:meteor-admin, gterrono:houston and related forks 60 | 61 | **Meteor Candy is an up-to-date and constantly updated solution.** First, it uses carefully scoped code and dynamic imports to ensure no burden is added to your client or server. Second, it uses methods to retrieve data in a scalable and performant way. Finally, it's all about your configuration, rather than trying to guess its way through plug and play. 62 | 63 | ## Premium Support 64 | 65 | Every purchase of Meteor Candy comes with a free, one hour consultation. If you need additional help setting up Meteor Candy, or would like to have it done for you, please see Toy Shop. 66 | 67 | ## License 68 | 69 | Meteor Candy is a proprietary product with a custom license. It's free to use in development, but requires a paid license to be used in production. For more information, please visit the product website. 70 | -------------------------------------------------------------------------------- /meteor-candy-config/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msavin/MeteorCandy-meteor-admin-dashboard-devtool/3a0771279c6e8f10026d97c3c252ba371325e8a9/meteor-candy-config/.DS_Store -------------------------------------------------------------------------------- /meteor-candy-config/LICENSE.md: -------------------------------------------------------------------------------- 1 | # End User License Agreement 2 | 3 | This End User License Agreement (the "Agreement") is an agreement between you and FaverSocial, LLC (the "Author"). By installing or using Meteor Candy (the "Software"), you agree to be bound by the terms of this Agreement. Installation or use of the Software signifies that you have read, understood, and agreed to be bound by the Agreement. If you do not agree, then do not download, install, or use the Software. 4 | 5 | # Usage 6 | 7 | This Agreement grants a non-exclusive, non-transferable license to install and use the Software for a single website. Additional Software licenses must be purchased in order to allow use for additional website. The Author reserves the right to determine whether use of the Software qualifies under this Agreement. The Author owns all rights, title and interest to the Software (including all intellectual property rights) and reserves all rights to the Software that are not expressly granted in this Agreement. 8 | 9 | # Backups 10 | 11 | You may make copies of the Software solely for back-up purposes, provided that you reproduce the Software in its original form and with all proprietary notices on the back-up copy. All rights to the Software not expressly granted herein are reserved by the Author. The back-ups may not be publicly viewable. 12 | 13 | # Restrictions 14 | 15 | You understand and agree that you shall only use the Software in a manner that complies with any and all applicable laws in the jurisdictions in which you use the Software. Your use shall be in accordance with applicable restrictions concerning privacy and intellectual property rights. 16 | 17 | You may not: 18 | 19 | Distribute derivative works based on the Software; 20 | Reproduce the Software except as described in this Agreement; 21 | Sell, assign, license, disclose, distribute, or otherwise transfer or make available the Software, license code, or its Source Code (defined as all HTML, CSS, JavaScript and image files), in whole or in part, in any form to any third parties; 22 | Remove or alter any proprietary notices on the Software 23 | No Warranty 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | # Term, Termination, and Modification 28 | 29 | You may use the Software under this Agreement until either party terminates this Agreement as set forth in this paragraph. Either party may terminate the Agreement at any time, upon written notice to the other party. Upon termination, all licenses granted to you will terminate, and you will immediately uninstall and cease all use of the Software. The Sections entitled "No Warranty," "Indemnification," and "Limitation of Liability" will survive any termination of this Agreement. 30 | 31 | The Author may modify the Software and this Agreement with notice to you either in email, by publishing content on the Software website (http://meteorcandy.com and all related links), or by publishing content to the GitHub repository website (http://github.com/MeteorToys/allthings) including but not limited to changing the functionality or design of the Software. Such modifications will become binding on you unless you terminate this Agreement. 32 | 33 | # Indemnification 34 | 35 | By accepting the Agreement, you agree to indemnify and otherwise hold harmless the Author and other partners from any direct, indirect, incidental, special, consequential or exemplary damages arising out of, relating to, or resulting from your use of the Software or any other matter relating to the Software. 36 | 37 | # Limitation of Liability. 38 | 39 | IN NO EVENT WILL AUTHOR BE LIABLE TO YOU FOR ANY LOST PROFITS, LOST SAVINGS OR INCIDENTAL, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF YOUR USE OR INABILITY TO USE THE PRODUCT OR THE BREACH OF THIS AGREEMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL DAMAGES SO THE ABOVE LIMITATION OR EXCLUSION MAY NOT APPLY TO YOU. IN NO EVENT WILL THE AUTHORS'S TOTAL CUMULATIVE DAMAGES EXCEED THE FEES YOU PAID TO THE AUTHOR UNDER THIS AGREEMENT IN THE MOST RECENT TWELVE-MONTH PERIOD. 40 | 41 | # Definition of an Update and Upgrade 42 | 43 | An "Update" of the Software is defined as that which adds minor functionality enhancements, or any bug fix to the current version. An "Upgrade" is a major release of the Software and is defined as that which incorporates major new features or enhancement that increase or change the functionality of the software. The assignment to the category of Update or Upgrade shall be at the sole discretion of the Author. -------------------------------------------------------------------------------- /meteor-candy-config/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Meteor Candy Configuration Package 2 | 3 | # Getting Started 4 | 5 | With-in Meteor Candy, the only files that you need to tinker with are the files with-in the `imports` directory of this package. Each folder represents a feature, and has a README file to explain how things work. 6 | 7 | You should be able to jump into any folder and adjust things as you wish. Enjoy your journey! 8 | 9 | # Package Structure 10 | 11 | Meteor Candy configuration is divided into two folders: imports and exports. The export folder simply assembles all the code with-in `./imports` and readies it to be used by Meteor Candy. The only place you need to work with is `./imports`. 12 | 13 | Meteor Candy's `./imports` folder is divided into three sets of files: client, server, and shared. With-in those folders, you can find different bits of functionality, with code examples and documentation built-in. 14 | 15 | # Server-Side API 16 | 17 | Meteor Candy can be easily integrated into the rest of your application to access features 18 | 19 | ```javascript 20 | // use MC authorizion in any method or publication 21 | Candy.authorize() 22 | 23 | // get settings data as an object 24 | // name - string - optional 25 | // fields - [string] - option 26 | Candy.settings.fetch(name, fields) 27 | 28 | // return settings data into a publication 29 | Candy.settings.publish(name, fields, publicationInstance) 30 | 31 | // 32 | Candy.settings.update() 33 | 34 | // 35 | Candy.settings.reset() 36 | 37 | // 38 | Candy.logging.collection() 39 | 40 | // 41 | Candy.logging.add() 42 | ``` -------------------------------------------------------------------------------- /meteor-candy-config/exports/client.js: -------------------------------------------------------------------------------- 1 | // First, import client-only code 2 | 3 | import { security as clientSecurity } from '../imports/client/security' 4 | 5 | var client = {} 6 | client.security = clientSecurity; 7 | 8 | // Second, import shared code 9 | 10 | import { tasks } from '../imports/shared/account/tasks' 11 | import { security } from '../imports/shared/security'; 12 | import { functions } from '../imports/shared/functions'; 13 | 14 | var shared = {}; 15 | shared.account = {}; 16 | shared.account.tasks = tasks; 17 | shared.security = security; 18 | shared.functions = functions; 19 | 20 | // Finally, assemble and export 21 | 22 | var Config = {} 23 | Config.client = client; 24 | Config.shared = shared; 25 | 26 | export { Config } -------------------------------------------------------------------------------- /meteor-candy-config/exports/server.js: -------------------------------------------------------------------------------- 1 | // First, import server-only code 2 | 3 | import { profile } from '../imports/server/account/profile' 4 | import { packages } from '../imports/server/account/packages' 5 | import { data } from '../imports/server/data' 6 | import { table } from '../imports/server/table' 7 | import { security } from '../imports/server/security' 8 | import { settings } from '../imports/server/settings' 9 | 10 | 11 | var server = { 12 | account: { 13 | profile: profile, 14 | packages: packages, 15 | }, 16 | table: table, 17 | data: data, 18 | security: security, 19 | settings: settings 20 | } 21 | 22 | // Then, import shared code 23 | 24 | import { tasks } from '../imports/shared/account/tasks' 25 | import { security as sharedSecurity } from '../imports/shared/security'; 26 | import { functions } from '../imports/shared/functions'; 27 | 28 | var shared = { 29 | account: { 30 | tasks: tasks, 31 | }, 32 | security: sharedSecurity, 33 | functions: functions 34 | } 35 | 36 | // Finally, assemble and export 37 | 38 | var Config = { 39 | server: server, 40 | shared: shared 41 | } 42 | 43 | export { Config } -------------------------------------------------------------------------------- /meteor-candy-config/imports/client/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Client Files 2 | 3 | Some files in Meteor Candy are client-side only because they have no need to be exposed to the server. 4 | 5 | All the code written with-in the client folder will be viewable on the client. -------------------------------------------------------------------------------- /meteor-candy-config/imports/client/security/README.md: -------------------------------------------------------------------------------- 1 | # Client Security 2 | 3 | The settings here define how the client would be opened, and under what conditions it should happen. 4 | 5 | `permission` refers to which logic must return true for Meteor Candy to be imported. With-in it, you can write any client side logic, such as: 6 | 7 | ```javascript 8 | if (Meteor.user() && Meteor.user().profile && Meteor.user().profile.admin) { 9 | return true; 10 | } 11 | ``` 12 | 13 | Even though the client side code could be patched to return true, it should not matter because Meteor Candy has different permission rules for the server. -------------------------------------------------------------------------------- /meteor-candy-config/imports/client/security/index.js: -------------------------------------------------------------------------------- 1 | var security = { 2 | authorize: function () { 3 | // This function must return true for Meteor Candy to display in production 4 | return true; 5 | } 6 | } 7 | 8 | export { security } -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Server Files 2 | 3 | Some files in Meteor Candy are server-side only because they have no need to be exposed to the client. 4 | 5 | All the code written with-in the server folder will be protected on the server. Only the result of these functions is returned to the client, and only when that is appropriate. -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/_helpers/README.md: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | These are some helper functions. Their main purpose is to cap some of the code to keep the example leans. -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/_helpers/account.js: -------------------------------------------------------------------------------- 1 | import { packages as Packages } from "../account/packages" 2 | 3 | var helpers = { 4 | date: function (date) { 5 | var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 6 | var days = ["Sunday","Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 7 | 8 | month = months[date.getMonth()]; 9 | day = days[date.getDay()]; 10 | dayNumber = date.getDate(); 11 | year = date.getYear() + 1900; 12 | 13 | return day + ", " + month + " " + dayNumber + ", " + year; 14 | }, 15 | time: function (date) { 16 | var time = date.toLocaleString('en-US', { hour: 'numeric',minute:'numeric', hour12: true }); 17 | var timezone = date.toTimeString().match(/\((.+)\)/)[1]; 18 | 19 | return time + " (" + timezone + ")"; 20 | }, 21 | getField: function (userDoc, field, fallback) { 22 | fallback = fallback || null; 23 | 24 | if (field) { 25 | field = field.split('.'); 26 | 27 | for (var i = 0; i < field.length; i++) { 28 | if (typeof userDoc[field[i]] == "undefined") { 29 | return fallback; 30 | } 31 | userDoc = userDoc[field[i]]; 32 | } 33 | } 34 | 35 | return userDoc; 36 | }, 37 | displayName: function (doc) { 38 | var self = this; 39 | 40 | var Namer = { 41 | result: "", 42 | counter: 0, 43 | addToResult: function (name) { 44 | if (counter) this.result = this.result + ", "; 45 | counter = counter + 1; 46 | this.result = this.result + name; 47 | }, 48 | add: function (data) { 49 | if (typeof data === "object") { 50 | data.forEach(function (item) { 51 | Namer.addToResult(item.address) 52 | }) 53 | } 54 | 55 | if (data && typeof data !== "object") { 56 | Namer.addToResult(data); 57 | } 58 | } 59 | } 60 | 61 | Packages.forEach(function (package) { 62 | if (package.field) { 63 | targetField = package.field 64 | 65 | if (package.field === "emails.address") { 66 | targetField = "emails"; 67 | } 68 | 69 | value = self.getField(doc, targetField); 70 | 71 | if (value) { 72 | Namer.add(value); 73 | } 74 | } 75 | }); 76 | 77 | return Namer.result; 78 | }, 79 | avatar: function (document) { 80 | var self = this; 81 | var photo = null; 82 | 83 | Packages.forEach(function (package) { 84 | if (package.avatar) { 85 | targetField = package.avatar 86 | 87 | if (package.avatar === "emails.address") { 88 | targetField = "emails"; 89 | } 90 | 91 | target = self.getField(document, targetField); 92 | 93 | if (target) { 94 | if (package.avatarLogic) { 95 | photo = package.avatarLogic(target); 96 | } else { 97 | photo = target; 98 | } 99 | } 100 | } 101 | }); 102 | 103 | return photo; 104 | }, 105 | email: function (data) { 106 | var emailString = ""; 107 | var number = 0; 108 | 109 | data.forEach(function (email) { 110 | 111 | if (number) { 112 | emailString = emailString + ", "; 113 | } 114 | 115 | number = number + 1; 116 | emailString = emailString + email.address; 117 | }); 118 | 119 | return emailString 120 | } 121 | } 122 | 123 | export { helpers } -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/_helpers/data.js: -------------------------------------------------------------------------------- 1 | import { packages } from "../account/packages" 2 | 3 | var helpers = {} 4 | 5 | helpers.getCount = function (daysAgo, midnight, field) { 6 | var query = {}; 7 | 8 | if (daysAgo) { 9 | timeLimit = new Date(); 10 | timeLimit.setDate(timeLimit.getDate() - daysAgo); 11 | query['createdAt'] = { $gte: timeLimit }; 12 | } 13 | 14 | if (midnight) { 15 | timeLimit = new Date(midnight); 16 | query['createdAt'] = { $gte: timeLimit }; 17 | } 18 | 19 | if (field) { 20 | query[field] = { $exists: true }; 21 | } 22 | if (Package["accounts-base"]) { 23 | return Meteor.users.find(query).count(); 24 | } else { 25 | return 0 26 | } 27 | } 28 | 29 | helpers.getContent = function (daysAgo, midnight) { 30 | var self = this; 31 | 32 | return packages.map(function (type) { 33 | var data; 34 | 35 | if (!daysAgo) { 36 | data = self.getCount(0,0,type.field); 37 | } else { 38 | if (midnight) { 39 | data = self.getCount(daysAgo,midnight,type.field); 40 | } else { 41 | data = self.getCount(daysAgo,0,type.field); 42 | } 43 | } 44 | 45 | return { 46 | name: type.name, 47 | value: data 48 | } 49 | }); 50 | } 51 | 52 | helpers.getDummmy = function () { 53 | return [ 54 | { 55 | name: "Meteor Candy can serve data any way that you would like. It will automatically try to detect the best way to display it." 56 | }, { 57 | name: "Lists Collection", 58 | value: "14 documents" 59 | },{ 60 | name: "Email", 61 | value: "derp@me.com", 62 | sanitize: false 63 | }, { 64 | name: "HTML Injection", 65 | value: "Everything is sanitized by default, but you can opt out of it whenever" 66 | }, { 67 | name: "Hyperlink", 68 | value: "http://www.google.com", 69 | sanitize: false 70 | }, { 71 | name: "Big Hyperlink", 72 | value: "https://www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&campaign=retargeting", 73 | sanitize: false 74 | }, { 75 | name: "Kind-of Invalid Hyperlink", 76 | value: "www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&utm_campaign=retargeting", 77 | sanitize: false 78 | }, { 79 | name: "Image URL", 80 | value: "https://sneakerbardetroit.com/wp-content/uploads/2018/03/Nike-Air-Max-97-Ultra-White-Black-Red-AH6806-005-Release-Date.jpg", 81 | sanitize: false 82 | }, { 83 | name: "Lots of Text", 84 | value: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim." 85 | }, { 86 | name: "The data for this view", 87 | value: function () { 88 | // when returning raw data, Meteor Candy will automatically pretty-print it as JSON 89 | return [{ 90 | name: "Meteor Candy can serve data any way that you would like. It will automatically try to detect the best way to display it." 91 | }, { 92 | name: "Lists Collection", 93 | value: "14 documents" 94 | },{ 95 | name: "Email", 96 | value: "derp@me.com", 97 | sanitize: false 98 | }, { 99 | name: "HTML Injection", 100 | value: "Everything is sanitized by default, but you can opt out of it whenever" 101 | }, { 102 | name: "Hyperlink", 103 | value: "http://www.google.com", 104 | sanitize: false 105 | }, { 106 | name: "Big Hyperlink", 107 | value: "https://www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&campaign=retargeting", 108 | sanitize: false 109 | }, { 110 | name: "Kind-of Invalid Hyperlink", 111 | value: "www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&utm_campaign=retargeting", 112 | sanitize: false 113 | }, { 114 | name: "Image URL", 115 | value: "https://sneakerbardetroit.com/wp-content/uploads/2018/03/Nike-Air-Max-97-Ultra-White-Black-Red-AH6806-005-Release-Date.jpg", 116 | sanitize: false 117 | }, { 118 | name: "Lots of Text", 119 | value: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim." 120 | }] 121 | }() 122 | } 123 | ] 124 | } 125 | export { helpers } -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/_helpers/index.js: -------------------------------------------------------------------------------- 1 | import { helpers as data } from './data.js' 2 | import { helpers as account } from './account.js' 3 | 4 | var helpers = {} 5 | helpers.data = data 6 | helpers.account = account 7 | 8 | export { helpers } -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/account/packages/README.md: -------------------------------------------------------------------------------- 1 | # Package Support 2 | 3 | Every Account package has a slight difference, and its important that we can support these differences. This is why Meteor Candy scans through your package list and tries to determine what things to look for. 4 | 5 | Meteor Candy supports all the vendor-provided Accounts packages. However, if you are using a third-partys account package, Meteor Candy exposes the settings for you to plug it right in. -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/account/packages/index.js: -------------------------------------------------------------------------------- 1 | var packageList = { 2 | email: { 3 | name: "Email", 4 | package: "accounts-password", 5 | field: "emails.address", 6 | avatar: "profile.avatar", 7 | avatarLogic: function (avatar) { 8 | return avatar; 9 | // Could implement Gravatar here but its hard to find an MIT licensed MD5 library 10 | // If you end up doing it, you will probably want to use: emails[0].address; 11 | } 12 | }, 13 | username: { 14 | name: "Username", 15 | package: "accounts-password", 16 | field: "username" 17 | }, 18 | facebook: { 19 | name: "Facebook", 20 | package: "accounts-facebook", 21 | field: "services.facebook.name", 22 | avatar: "services.facebook.id", 23 | avatarLogic: function (avatar) { 24 | return "http://graph.facebook.com/" + avatar + "/picture/?type=large"; 25 | } 26 | }, 27 | github: { 28 | name: "GitHub", 29 | package: "accounts-github", 30 | field: "services.github.username", 31 | avatar: "services.github.username", 32 | avatarLogic: function (avatar) { 33 | return "http://avatars.githubusercontent.com/" + avatar + "?s=200"; 34 | } 35 | }, 36 | google: { 37 | name: "Google", 38 | package: "accounts-google", 39 | field: "services.google.email", 40 | avatar: "services.google.picture", 41 | avatarLogic: function (avatar) { 42 | return avatar; 43 | } 44 | }, 45 | meetup: { 46 | name: "Meetup", 47 | package: "accounts-meetup", 48 | field: "services.meetup.id", 49 | avatar: "" 50 | }, 51 | twitter: { 52 | name: "Twitter", 53 | package: "accounts-twitter", 54 | field: "services.twitter.screenName", 55 | avatar: "services.twitter.profile_image_url_https", 56 | avatarLogic: function (avatar) { 57 | return avatar.replace("_normal", "_bigger"); 58 | } 59 | }, 60 | weibo: { 61 | name: "Weibo", 62 | package: "accounts-weibo", 63 | field: "services.weibo.screenName", 64 | avatar: "" 65 | } 66 | }; 67 | 68 | var packages = function () { 69 | result = [] 70 | 71 | Object.keys(packageList).forEach(function (package) { 72 | packageData = packageList[package]; 73 | 74 | if (Package[packageData.package]) { 75 | result.push(packageData); 76 | } 77 | }); 78 | 79 | return result; 80 | }(); 81 | 82 | export { packages } -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/account/profile/README.md: -------------------------------------------------------------------------------- 1 | # Account Profile 2 | 3 | # How It Works 4 | 5 | Account Profiles are generated created and displayed through the following steps: 6 | 1- Meteor Candy queries the `Meteor.users` collection for the appropriate accounts 7 | 2- Meteor Candy runs the returned documents through a series of functions to create a user profile object that can be displayed by the client 8 | 3- The functions that create the user profile use the profile variable in this folder to generate the profile. 9 | 10 | The other way to do this would have been to publish documents from the user collection. However, that creates a number of risks, like exposing the login tokens and other sensitive data. By taking this approach, we have greater control over what we show, what we don't show, as well as integrating with other data sources. 11 | 12 | # How the Profile is Generated 13 | 14 | Once Meteor Candy queries the `Meteor.users` collection, it then loops each of the documents through the profile configuration. 15 | 16 | The profile configuration is an array of objects, with three fields: 17 | 18 | `name` - this is what you want to display as the "label" in the interface. If you pre-fix the name with _, it will be returned but it will not be displayed in the interface. 19 | 20 | `content` - this is what Meteor Candy will display as the data in the user profile. Meteor Candy will run the function and inside of it, you can specify any logic that you would like. 21 | 22 | `field` - the content function contains the entire user document, meaning, you might have to do some work to extract the right value from it. To save you the trouble of having to do that over and over, you can just tell Meteor Candy where to look in the object. Then, rather than passing in the entire user document, it will just pass in that value. 23 | 24 | # Helpers 25 | 26 | The configuration is totally up to you, but Meteor Candy provides some helper functions here to help you generate timestamps and whatnot. These are yours to keep or remove. 27 | 28 | # Synchronous Code Execution 29 | 30 | An important thing to keep in mind is maintaing synchronous code execution, so that the function returns value at the right time. In the most basic sense, it means defining 31 | -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/account/profile/index.js: -------------------------------------------------------------------------------- 1 | import { helpers } from '../../_helpers' 2 | 3 | var profile = [ 4 | { 5 | name: "_id", 6 | field: "_id", 7 | content: function (data) { 8 | return data; 9 | } 10 | }, { 11 | name: "_displayName", 12 | field: null, 13 | content: function (data) { 14 | return helpers.account.displayName(data); 15 | } 16 | }, { 17 | name: "_avatar", 18 | field: null, 19 | sanitize: false, 20 | content: function (data) { 21 | return helpers.account.avatar(data); 22 | } 23 | }, { 24 | name: '_hasPassword', 25 | field: "services.password", 26 | content: function (data) { 27 | if (data) { 28 | return true; 29 | } 30 | } 31 | }, { 32 | name: "Emails", 33 | field: "emails", 34 | content: function (data) { 35 | return helpers.account.email(data) 36 | } 37 | }, { 38 | name: "Facebook Email", 39 | field: "services.facebook.email" 40 | }, { 41 | name: "Username", 42 | field: "username", 43 | content: function (data) { 44 | return data; 45 | } 46 | }, { 47 | name: "Join Date", 48 | field: "createdAt", 49 | content: function (data) { 50 | return helpers.account.date(data); 51 | } 52 | }, { 53 | name: "Join Time", 54 | field: "createdAt", 55 | content: function (data) { 56 | return helpers.account.time(data); 57 | } 58 | }, { 59 | name: "Login Sessions", 60 | field: "services.resume.loginTokens", 61 | content: function (data) { 62 | return data.length || "0"; 63 | } 64 | } 65 | ] 66 | 67 | export { profile }; -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | The data with-in this object will be used to power the Overview tab of your application. The goal is to give some "at-a-glance" statistics, such as how many users there are in the database or how many people joined in the last month. 4 | 5 | `name` and `value` refer to what you will see on the sidebar. Mainly, its the name of the dataset and a badge. In the example, one of the way its used is to display how many "Total Accounts" there are in the database. 6 | 7 | `content` refers to what data will be displayed in the main area once that section is selected. See the sample below for an example of how it works. 8 | 9 | Meteor Candy UI will automatically sanitize the data and render it in the appropriate form. If you do not wish to sanitize it, you can set `sanitize: false` to the individual field. 10 | 11 | Meteor Candy will run your functions on the server and then send it up to the client. Here is how Meteor Candy UI expects to see the data: 12 | 13 | ```javascript 14 | name: "Total Accounts", 15 | value: 14, 16 | content: [ 17 | { 18 | name: "Meteor Candy can serve data any way that you would like. It will automatically try to detect the best way to display it." 19 | }, { 20 | name: "Lists Collection", 21 | value: "14 documents" 22 | },{ 23 | name: "Email", 24 | value: "derp@me.com", 25 | sanitize: false 26 | }, { 27 | name: "HTML Injection", 28 | value: "Everything is sanitized by default, but you can opt out of it whenever" 29 | }, { 30 | name: "Hyperlink", 31 | value: "http://www.google.com", 32 | sanitize: false 33 | }, { 34 | name: "Big Hyperlink", 35 | value: "https://www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&campaign=retargeting", 36 | sanitize: false 37 | }, { 38 | name: "Kind-of Invalid Hyperlink", 39 | value: "www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&utm_campaign=retargeting", 40 | sanitize: false 41 | }, { 42 | name: "Image URL", 43 | value: "https://sneakerbardetroit.com/wp-content/uploads/2018/03/Nike-Air-Max-97-Ultra-White-Black-Red-AH6806-005-Release-Date.jpg", 44 | sanitize: false 45 | }, { 46 | name: "Lots of Text", 47 | value: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim." 48 | }, { 49 | name: "The data for this view", 50 | value: function () { 51 | // when returning raw data, Meteor Candy will automatically pretty-print it as JSON 52 | return [{ 53 | name: "Meteor Candy can serve data any way that you would like. It will automatically try to detect the best way to display it." 54 | }, { 55 | name: "Lists Collection", 56 | value: "14 documents" 57 | },{ 58 | name: "Email", 59 | value: "derp@me.com", 60 | sanitize: false 61 | }, { 62 | name: "HTML Injection", 63 | value: "Everything is sanitized by default, but you can opt out of it whenever" 64 | }, { 65 | name: "Hyperlink", 66 | value: "http://www.google.com", 67 | sanitize: false 68 | }, { 69 | name: "Big Hyperlink", 70 | value: "https://www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&campaign=retargeting", 71 | sanitize: false 72 | }, { 73 | name: "Kind-of Invalid Hyperlink", 74 | value: "www.footasylum.com/mens/mens-footwear/trainers/nike-air-max-95-essential-trainer-light-pumice-dark-stucco-119233/?src=froogle&utm_source=Criteo&utm_medium=display&utm_campaign=retargeting", 75 | sanitize: false 76 | }, { 77 | name: "Image URL", 78 | value: "https://sneakerbardetroit.com/wp-content/uploads/2018/03/Nike-Air-Max-97-Ultra-White-Black-Red-AH6806-005-Release-Date.jpg", 79 | sanitize: false 80 | }, { 81 | name: "Lots of Text", 82 | value: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim." 83 | }] 84 | }() 85 | } 86 | ] 87 | ``` -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/data/index.js: -------------------------------------------------------------------------------- 1 | import { helpers } from '../_helpers'; 2 | 3 | var data = [ 4 | { 5 | name: "Total Accounts", 6 | value: function () { 7 | return helpers.data.getCount(); 8 | }, 9 | content: function () { 10 | return helpers.data.getContent(); 11 | } 12 | }, { 13 | name: "Past 30 Days", 14 | value: function () { 15 | return helpers.data.getCount(30); 16 | }, 17 | content: function () { 18 | return helpers.data.getContent(30); 19 | } 20 | }, { 21 | name: "Past 7 Days", 22 | value: function () { 23 | return helpers.data.getCount(7); 24 | }, 25 | content: function () { 26 | return helpers.data.getContent(7); 27 | } 28 | }, { 29 | name: "Today", 30 | value: function (dayStart) { 31 | return helpers.data.getCount(1, dayStart); 32 | }, 33 | content: function (dayStart) { 34 | return helpers.data.getContent(1, dayStart); 35 | } 36 | } 37 | ] 38 | 39 | export { data }; -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/security/README.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | `requestLimit`, `requestLimitForSearch`, and `requestTimeout` refer to the DDP rate limiting settings for Meteor Candy's Methods. 4 | 5 | `permission` is a very important function - it must be secure because it is used to allow users to execute Meteor Candy's functionality on the server. When it returns true, the server code will execute. 6 | 7 | `ipWhitelist` lets you list which IP addresses are allowed to access Meteor Candy. If left empty, Meteor Candy will not verify IP addresses. -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/security/index.js: -------------------------------------------------------------------------------- 1 | var security = { 2 | // This function must return true for Meteor Candy to execute tasks on the server 3 | // This function will be bypassed if you whitelist userIds in ../shared/security 4 | permission: function (userId) { 5 | return false; 6 | /* 7 | For example: 8 | 9 | var userDoc = Meteor.users.findOne(userId); 10 | 11 | if (userDoc && userDoc.profile && userDoc.profile.admin === true) { 12 | return true; 13 | } 14 | */ 15 | }, 16 | 17 | // DDP Rate limiting 18 | requestLimit: 10, 19 | requestLimitForSearch: 50, 20 | requestTimeout: 5000, 21 | 22 | // IP White Listing 23 | // List which IP addresses are allowed to access Meteor Candy. 24 | // If left empty, Meteor Candy will not verify IP addresses. 25 | ipWhitelist: [] 26 | } 27 | 28 | export { security }; -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/settings/README.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | Settings is defined as one massive object called `Settings`. Each of its top-level object keys work as an ID for that set of settings information. With-in the value, we can configure how the rest of it should work. 4 | 5 | `label` refers to the name of that group of data, which will be used to display it on the interface. 6 | 7 | `fields` refers to all the fields that will be with-in this group of data. Each of the object keys will act as their id. 8 | 9 | With-in fields: 10 | `label` - is the name that will be used to represent that bit of data 11 | `type` - applicable types are string, boolean and number 12 | `value` - pre-determined value (must match type) -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/settings/index.js: -------------------------------------------------------------------------------- 1 | var settings = { 2 | appControls: { 3 | label: "Application Controls", 4 | fields: { 5 | domain: { 6 | label: "App Domain", 7 | type: "select", 8 | options: [{ 9 | label: "Meteor Candy - Official", 10 | value: "http://meteorcandy.com", 11 | }, { 12 | label: "Meteor Candy - Unofficial", 13 | value: "http://meteorcandies.com", 14 | default: true 15 | }], 16 | }, 17 | description: { 18 | label: "App Description", 19 | type: "string", 20 | value: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", 21 | lines: 10 22 | }, 23 | waitinglist: { 24 | label: "Show Waitinglist", 25 | type: "boolean", 26 | value: true, 27 | }, 28 | showAds: { 29 | label: "Show Advertisements", 30 | type: "boolean", 31 | value: false, 32 | }, 33 | allowPosting: { 34 | label: "Allow Public Posting", 35 | type: "boolean", 36 | value: true, 37 | }, 38 | allowMessaging: { 39 | label: "Allow Private Messaging", 40 | type: "boolean", 41 | value: true, 42 | } 43 | } 44 | }, 45 | accountControls: { 46 | label: "Account Controls", 47 | fields: { 48 | maximum: { 49 | label: "Maximum Accounts", 50 | type: "number", 51 | value: 1000, 52 | }, 53 | registration: { 54 | label: "Allow Registration", 55 | type: "boolean", 56 | value: true, 57 | }, 58 | fbRegistration: { 59 | label: "Allow Facebook Sign-In", 60 | type: "boolean", 61 | value: false, 62 | } 63 | } 64 | }, 65 | abTests: { 66 | label: "A/B Tests", 67 | fields: { 68 | price0: { 69 | label: "Price (USD)", 70 | type: "number", 71 | value: 99.00, 72 | }, 73 | displayPrice1: { 74 | label: "Price Display", 75 | type: "string", 76 | value: "$99", 77 | }, 78 | price2: { 79 | label: "Test Price (USD)", 80 | type: "number", 81 | value: 999.00, 82 | }, 83 | displayPrice2: { 84 | label: "Test Price Display", 85 | type: "string", 86 | value: "$999", 87 | }, 88 | freeTier: { 89 | label: "Display Free Tier", 90 | type: "boolean", 91 | value: true, 92 | }, 93 | freeTrial: { 94 | label: "Display Free Trial", 95 | type: "boolean", 96 | value: true, 97 | } 98 | } 99 | }, 100 | } 101 | 102 | export { settings }; -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/table/README.md: -------------------------------------------------------------------------------- 1 | Tables will take data out of MongoDB database and display it as a table. 2 | 3 | **Here are the core ideas behind it:** 4 | - Automate as much as possible. Everything from sorting, to searching and querying, to pagination, and more, is handled automatically. 5 | - Display data in a human-friendly way. Instead of just showing raw values, Tables lets you choose what data to appear, change how it is returned, grab more data from other data sources, etc. 6 | - Tasks over Direct Editing. Direct editing to the database can be dangerous because the edit does not consider how the database and application as a whole may be impacted. Instead, Meteor Candy encourages you to write logic to control and verify every edit. 7 | 8 | Of course, if you disagree with this approach, you will be happy to know that Meteor Candy also offers a JSON document editor, which let's you update any document. You can limit which fields can be viewed and updated, or disable entirely. 9 | 10 | ---- 11 | 12 | The Tables configuration is designed to be easy to grok. The idea is to make it easy enough for you to copy and paste the snippet below, and tune it to your preferences. All the fields that are optional could be deleted. 13 | 14 | ``` 15 | var table = { 16 | // todos2 is the unique table ID 17 | "todos2": { 18 | 19 | // Required: Specify which collection to get data from 20 | collection: "todos", 21 | 22 | // Optional: Group the table with other items. This is a cosmetic feature for the UI. 23 | group: "Transactions", 24 | 25 | // Optional: This is how you want the name to appear in the UI. 26 | label: "Todo Items", 27 | 28 | // Optional: Allow some queries to be 29 | queries: [{ 30 | label: "Checked Only", 31 | query: '{"checked": true}', 32 | default: true 33 | }, { 34 | label: "Unchecked Only", 35 | query: '{"checked": false}', 36 | }], 37 | 38 | // Optional: Omit count of documents in that table 39 | count: false 40 | 41 | 42 | // Optional: disable or limit the MongoDB document editor. 43 | // Choose one or the other 44 | // `false` value will disable it entirely 45 | // the array will limit reading and writing to the fields outlined 46 | editor: false, 47 | editor: ["name", "createdAt", "photo"], 48 | 49 | // Required: Tell Meteor Candy which fields to return to the client and how to display them 50 | // `field` indicates which field to grab from the document 51 | // `label` indicates how to display that fields name 52 | // `process` lets you mutate the value of that value accordingly 53 | // `column: false` will tell Meteor Candy not to display the item inside the table view 54 | // `editor: false` will tell Meteor Candy not to display the item inside the editor view 55 | // `sort` accepts `1` or `-1`, and will set that field to be the default sorting field 56 | // `search: false` will tell Meteor Candy to skip over the field while doing a keyword search 57 | 58 | fields: [ { 59 | field: "_id", 60 | label: "ID", 61 | column: false 62 | }, { 63 | label: "Amount", 64 | field: "amount", 65 | width: 200 66 | }, { 67 | label: "Customer", 68 | field: "business", 69 | sort: -1, 70 | column: false 71 | }, { 72 | label: "Name", 73 | field: "name", 74 | search: false, 75 | process: function (doc, field) { 76 | var document = SomeCollection.findOne(doc._id); 77 | return document[field]; 78 | } 79 | }, { 80 | label: "Type", 81 | field: "type" 82 | }, { 83 | field: "date", 84 | label: "Date" 85 | }] 86 | 87 | // Optional: tasks let you perform actions on a particular row/document 88 | // They interact with the data that your table outputs, as is designed in the `fields` parameter 89 | // name - name to display on button 90 | // hide / show will hide or show the item of the field specified is truthy/falsey 91 | // hide / show accept a string or array 92 | // log specifies if the item should be logged or not 93 | // options are: true, [] for 94 | // - `true` to log all data 95 | // - ["inputs"] or ["outputs"] to log the event and then just the input or output passed into function 96 | // server is for the logic to be executed on the server 97 | // prompt - to prompt for additional input 98 | tasks: [{ 99 | name: "Flag Item", 100 | hide: "flagged", 101 | log: true, 102 | server: function (docId, prompt) { 103 | Data.update({_id: docId}, { 104 | $set: { 105 | flagged: true 106 | } 107 | }) 108 | } 109 | },{ 110 | name: "Unflag Item", 111 | show: ["flagged"], 112 | log: true, 113 | server: function (docId, prompt) { 114 | Data.update({_id: docId}, { 115 | $set: { 116 | flagged: false 117 | } 118 | }) 119 | } 120 | }] 121 | }, 122 | ``` 123 | 124 | Note: similarly to Meteor's oplog tailing, Mongol currently only supports editing on the top fields of the document. This means if you were to edit `profile.name`, the entire field would be overwritten. -------------------------------------------------------------------------------- /meteor-candy-config/imports/server/table/index.js: -------------------------------------------------------------------------------- 1 | var getCollectionByName = function (name) { 2 | return Package['mongo'].MongoInternals.defaultRemoteCollectionDriver().open(name); 3 | } 4 | 5 | var table = { 6 | "logs": { 7 | collection: "candyLogs", 8 | group: "Meteor Candy", 9 | label: "Activity Log", 10 | count: false, 11 | editor: false, 12 | fields: [{ 13 | field: "_id", 14 | label: "ID", 15 | column: false 16 | }, { 17 | label: "Name", 18 | field: "name", 19 | sanitize: false 20 | }, { 21 | label: "Date", 22 | field: "date", 23 | width: 250, 24 | sort: -1 25 | }, { 26 | label: "User ID", 27 | field: "userId", 28 | width: 250 29 | }, { 30 | label: "IP", 31 | field: "ip", 32 | width: 250 33 | }, { 34 | field: "data", 35 | label: "Data", 36 | column: false 37 | }] 38 | } 39 | } 40 | 41 | export { table } -------------------------------------------------------------------------------- /meteor-candy-config/imports/shared/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Shared Files 2 | 3 | Some files in Meteor Candy are shared because its easier to maintain their feature in one place instead of many. And since Meteor Candy use dynamic imports, we do not have to worry about the extra bit of space they will take up. 4 | 5 | Due to the nature of shared code, everything you write here will exposed to the client. -------------------------------------------------------------------------------- /meteor-candy-config/imports/shared/account/tasks/README.md: -------------------------------------------------------------------------------- 1 | # Account Actions 2 | 3 | Account Actions are quick tasks that you can perform on accounts. By default, they has no inputs, or if they do, it would just be one input from a prompt window. 4 | 5 | Each action is placed into an array as an object, with the following configuration options; 6 | 7 | - `name`: a string to represent the name of the action 8 | - `_impersonation`: an internal function that triggers the impersonation widget 9 | - `server`: a function that runs of server 10 | - `client`: a function that runs on the client 11 | - `refresh`: tells Meteor Candy to refresh the accounts data after the action has executed 12 | - `require`: this will check the user profile that you generated in `imports/server/account/profile` for a specific field. If that field is not present, then Meteor Candy will not display field to the end users. In the code examples, you can see how it's used to ensure that the "Change Password" button is only available for accounts that actually have a password. 13 | 14 | Meteor Candy will take the userId, parameter (if exists), and the parent of the method (so you can access internal properties of the method, such as setUserId), and pass it into the server-side function. 15 | 16 | Assuming the server function executes successfully, Meteor Candy will then run the client side callback if there is one. -------------------------------------------------------------------------------- /meteor-candy-config/imports/shared/account/tasks/index.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from "meteor/meteor" 2 | 3 | if (Package['accounts-base']) { 4 | import { Accounts } from 'meteor/accounts-base'; 5 | } 6 | 7 | var tasks = [ 8 | { 9 | name: "Sign Into Account", 10 | _impersonation: true, 11 | debugOnly: true, 12 | server: function (userId, param, parent) { 13 | parent._setUserId(userId); 14 | return userId; 15 | }, 16 | client: function (userId) { 17 | Meteor.connection.setUserId(userId); 18 | } 19 | }, { 20 | name: "Change Password", 21 | prompt: "To what would you like to change the password to?", 22 | show: "_hasPassword", 23 | // hide: ["_avatar"], 24 | log: true, 25 | server: function (userId, param) { 26 | if (Package['accounts-base']) { 27 | return Accounts.setPassword(userId, param) 28 | } 29 | }, 30 | client: function (result) { 31 | if (result) { 32 | alert("The password has been changed."); 33 | } 34 | } 35 | }, { 36 | name: "Delete Account", 37 | prompt: "To remove, please enter REMOVE to confirm.", 38 | refresh: true, 39 | log: true, 40 | server: function (userId, param) { 41 | if (param === "REMOVE") { 42 | return Meteor.users.remove(userId) 43 | } 44 | }, 45 | client: function (result) { 46 | if (result === 1) { 47 | alert("The account has been deleted."); 48 | } else { 49 | alert("There might have been an error removing this account. Please reload and check."); 50 | } 51 | } 52 | } 53 | ] 54 | 55 | export { tasks }; -------------------------------------------------------------------------------- /meteor-candy-config/imports/shared/functions/README.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | The Functions array contains objects that can be configured to execute code on the server, and to process the result on the client. 4 | 5 | Structure: 6 | - name 7 | - parameters 8 | - label 9 | - type 10 | - value: 30 11 | - required 12 | - server 13 | - client 14 | 15 | `name` should be a string that will be used to represent the function on the interface 16 | 17 | `parameters` is an object that will be used to generate the form for the Meteor Candy interface. The inputs of the form will be verified on the client and then again on the server to ensure they are filled in (if necessary) and that they are the correct type. 18 | 19 | The options for objects with-in for `parameters`: 20 | - `label` (required, string): label to to represent the input on the interface 21 | - `type` (required, string): the type of data; options are `readOnly`, `number`, `boolean` and `string` 22 | - the `readOnly` type is cosmetic; it can be used to write in descriptions of what the function will do for the interface 23 | - `value` (optional, must match type): a pre-filled value for the input 24 | - `required` (optional): require the field to have something in it 25 | 26 | `server` is a function that will run once the inputs are submitted from the client to the server. The inputs from the form will be passed in to the function. The key of the object will be used to mark it. 27 | 28 | `client` is a function that will run once the result is returned from the server to the client. The result from the server will be passed in to the function. -------------------------------------------------------------------------------- /meteor-candy-config/imports/shared/functions/index.js: -------------------------------------------------------------------------------- 1 | var functions = [ 2 | { 3 | name: "Test HTML Printing", 4 | parameters: { 5 | filler: { 6 | type: "readOnly", 7 | value: 'This function demonstrates how Meteor Candy would print HTML. Click "Submit" to try it.' 8 | }, 9 | text: { 10 | label: "Text", 11 | type: "string", 12 | value: "Greetings", 13 | required: true 14 | }, 15 | number: { 16 | label: "Number", 17 | type: "number", 18 | value: "91310", 19 | required: true 20 | }, 21 | image: { 22 | label: "Image URL", 23 | type: "string", 24 | value: "http://meteorcandy.com/vector/donut-planet.png", 25 | required: true 26 | }, 27 | select: { 28 | label: "Selector", 29 | type: "select", 30 | required: true, 31 | options: [{ 32 | label: "10% off", 33 | value: 10, 34 | }, { 35 | label: "20% off", 36 | value: 20, 37 | default: true 38 | }] 39 | } 40 | }, 41 | server: function (parameters) { 42 | Meteor._sleepForMs(10000) 43 | 44 | var output = "You passed in the following data:"; 45 | 46 | Object.keys(parameters).forEach(function (key) { 47 | addOn = "
 • " + key + ", which was set to " + parameters[key]; 48 | output = output + addOn; 49 | }); 50 | 51 | output = output + "

As our thank you, please enjoy the image below:

" 52 | 53 | return output; 54 | }, 55 | client: function () { 56 | console.log('yo') 57 | } 58 | }, { 59 | name: "Test JSON Printing", 60 | log: true, 61 | logInputs: false, 62 | logOutputs: false, 63 | parameters: { 64 | filler: { 65 | type: "readOnly", 66 | value: 'This function demonstrates how Meteor Candy would print JSON. Click "Submit" to try it.' 67 | }, 68 | number: { 69 | label: "Number", 70 | type: "number", 71 | value: "123", 72 | required: true 73 | }, 74 | string1: { 75 | label: "String (required)", 76 | type: "string", 77 | value: "Greetings!", 78 | lines: 4, 79 | required: false 80 | }, 81 | string2: { 82 | label: "String (not required)", 83 | type: "string", 84 | value: "", 85 | required: false 86 | }, 87 | boolean: { 88 | label: "boolean", 89 | type: "boolean", 90 | value: false, 91 | required: true 92 | } 93 | }, 94 | 95 | server: function (parameters) { 96 | return { 97 | number: parameters.number, 98 | string_r: parameters.string1, 99 | string_nr: parameters.string2, 100 | boolean: parameters.boolean, 101 | theVoid: null, 102 | candy: { 103 | ingredients: ['sugar','spice','everything nice'] 104 | } 105 | } 106 | }, 107 | client: function () { 108 | // ... 109 | } 110 | }, { 111 | name: "Test String Printing", 112 | parameters: { 113 | filler: { 114 | type: "readOnly", 115 | value: 'This function demonstrates how Meteor Candy would print regular text. Click "Submit" to try it.' 116 | }, 117 | text: { 118 | label: "Text", 119 | type: "string", 120 | value: "Greetings?", 121 | required: true 122 | }, 123 | }, 124 | server: function (parameters) { 125 | return parameters.text; 126 | }, 127 | client: function () { 128 | // ... 129 | } 130 | }, { 131 | name: "Clear Temporary Data", 132 | debugOnly: true, 133 | parameters: { 134 | filler: { 135 | type: "readOnly", 136 | value: "This is for routine data cleaning. For example, maybe you want to clear your MongoDB logs to save on storage, but prefer to do it on your initiative rather than a systems." 137 | }, 138 | days: { 139 | label: "Number of Days Ago", 140 | type: "number", 141 | value: 30, 142 | required: true 143 | }, 144 | confirm: { 145 | label: "Confirm", 146 | type: "boolean", 147 | value: false, 148 | } 149 | }, 150 | server: function (parameters) { 151 | if (parameters.confirm) { 152 | // runs Magic.DeleteLogs() ... 153 | return "OK - logs are deleted!"; 154 | } else { 155 | return "You must confirm the action." 156 | } 157 | 158 | }, 159 | client: function () { 160 | // ... 161 | } 162 | }, { 163 | name: "Send Push Notification", 164 | parameters: { 165 | freeUsers: { 166 | label: "Send to Free Users", 167 | type: "boolean", 168 | value: true, 169 | required: true 170 | }, 171 | paidUsers: { 172 | label: "Send to Paid Users", 173 | value: true, 174 | type: "boolean", 175 | required: true 176 | }, 177 | admins: { 178 | label: "Send to Admins", 179 | value: true, 180 | type: "boolean", 181 | required: true 182 | }, 183 | content: { 184 | label: "Content", 185 | type: "string", 186 | value: "There's an exciting new feature we just added to our app. We bet you'll spot it right away!", 187 | required: true 188 | }, 189 | confirm: { 190 | label: "Confirm", 191 | type: "boolean", 192 | value: false, 193 | required: true 194 | } 195 | }, 196 | server: function (parameters) { 197 | if (parameters.confirm) { 198 | // Magic.sendPushNotification(parameters) 199 | return "Account has been created, and an email was sent to " + parameters.email; 200 | } else { 201 | return "Please confirm action." 202 | } 203 | } 204 | }, { 205 | name: "Get Customer Emails (CSV) ", 206 | parameters: { 207 | email: { 208 | label: "Send to Email Address", 209 | type: "string", 210 | value: "!", 211 | required: true 212 | }, 213 | confirm: { 214 | label: "Confirm", 215 | type: "boolean", 216 | value: false, 217 | required: true 218 | }, 219 | 220 | }, 221 | server: function (parameters) { 222 | 223 | if (parameters.confirm) { 224 | function validateEmail(email) { 225 | var re = /\S+@\S+\.\S+/; 226 | return re.test(email); 227 | } 228 | 229 | if (validateEmail(parameters.email)) { 230 | // Magic.createCSV() 231 | return "CSV is being generated and will be sent to " + parameters.email; 232 | } else { 233 | return "Invalid email entered. Please try again." 234 | } 235 | } else { 236 | return "Please confirm this action, otherwise, it cannot run." 237 | } 238 | 239 | }, 240 | client: function (parameters) { 241 | console.log('hi'); 242 | console.log(parameters) 243 | } 244 | }, { 245 | name: "Get Package List", 246 | parameters: { 247 | filler: { 248 | type: "readOnly", 249 | value: 'This function has no inputs. Once you run it, it will return some data from the server.' 250 | }, 251 | }, 252 | server: function () { 253 | return Object.keys(Package).sort(); 254 | } 255 | }, { 256 | name: "Get Free Coupon", 257 | parameters: { 258 | filler: { 259 | type: "readOnly", 260 | value: 'This function has no inputs. Once you run it, it will return some data from the server.' 261 | }, 262 | }, 263 | server: function () { 264 | return "Thank you for your curiosity! The coupon code is 'inputs'." 265 | } 266 | }, { 267 | name: "Test Client Callback", 268 | parameters: { 269 | filler: { 270 | type: "readOnly", 271 | value: 'This function will run a callback in the client after it gets the result from the server.' 272 | }, 273 | name: { 274 | label: "Name", 275 | type: "string", 276 | value: "Max", 277 | required: true 278 | } 279 | }, 280 | server: function (parameters) { 281 | return parameters; 282 | }, 283 | client: function (parameters) { 284 | return "Hi " + parameters.name; 285 | } 286 | }, { 287 | name: "Test Client-Only Action", 288 | parameters: { 289 | filler: { 290 | type: "readOnly", 291 | value: 'This function will only run on the client. Could be handy for interacting with data on the client.' 292 | }, 293 | name: { 294 | label: "Name", 295 | type: "string", 296 | value: "Max", 297 | required: true 298 | } 299 | }, 300 | client: function (parameters) { 301 | return "Hi " + parameters.name; 302 | } 303 | } 304 | ] 305 | 306 | export { functions }; -------------------------------------------------------------------------------- /meteor-candy-config/imports/shared/security/README.md: -------------------------------------------------------------------------------- 1 | # Shared Security 2 | 3 | `whitelist` lets you enter a list of userIds that are allowed to access Meteor Candy. 4 | 5 | This could be handy for projects where its not necessary to build a permission system. 6 | 7 | When you use the whitelist feature, Meteor Candy will use it in place of your client and server security rules. 8 | 9 | `disableDevelopmentMode` lets you disable Meteor Candy's use of Meteor.isDevelopment flag. This flag is typically used to lift authorization rules during development for an improved debugging experience. This only works in Meteor Candy Business Edition, because the Developer Edition is `debugOnly`. -------------------------------------------------------------------------------- /meteor-candy-config/imports/shared/security/index.js: -------------------------------------------------------------------------------- 1 | var security = { 2 | // Instead of defining permissions, 3 | // you can just list the userId's of who is allowed to use Meteor Candy 4 | whitelist: [], 5 | 6 | // Meteor Candy detects if your application is in development mode using `Meteor.isDevelopment` 7 | // However, you can disable development mode using the flag below 8 | disableDevelopmentMode: false 9 | } 10 | 11 | export { security }; -------------------------------------------------------------------------------- /meteor-candy-config/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'meteor-candy-config', 3 | summary: 'Meteor Candy configuration package', 4 | version: '4.0.0' 5 | }); 6 | 7 | Package.onUse(function(api) { 8 | api.mainModule('exports/client.js', 'client', {lazy: true}); 9 | api.mainModule('exports/server.js', 'server'); 10 | api.use('ecmascript'); 11 | }); -------------------------------------------------------------------------------- /screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msavin/MeteorCandy-meteor-admin-dashboard-devtool/3a0771279c6e8f10026d97c3c252ba371325e8a9/screencast.gif -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msavin/MeteorCandy-meteor-admin-dashboard-devtool/3a0771279c6e8f10026d97c3c252ba371325e8a9/screenshot.png --------------------------------------------------------------------------------