@skylord123/node-red-pebble-timeline 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/examples/README.md +7 -0
- package/examples/example.json +598 -0
- package/examples/example.png +0 -0
- package/package.json +25 -0
- package/pebble-timeline-add.html +926 -0
- package/pebble-timeline-add.js +744 -0
- package/pebble-timeline-config.html +63 -0
- package/pebble-timeline-config.js +13 -0
- package/pebble-timeline-delete.html +122 -0
- package/pebble-timeline-delete.js +190 -0
- package/pebble-timeline-list.html +151 -0
- package/pebble-timeline-list.js +195 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('pebble-timeline-config', {
|
|
3
|
+
category: 'config',
|
|
4
|
+
defaults: {
|
|
5
|
+
name: { value: "" },
|
|
6
|
+
apiUrl: { value: "https://timeline-api.rebble.io", required: true }
|
|
7
|
+
},
|
|
8
|
+
credentials: {
|
|
9
|
+
timelineToken: { type: "password" }
|
|
10
|
+
},
|
|
11
|
+
label: function() {
|
|
12
|
+
return this.name || "Pebble Timeline Config";
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<script type="text/html" data-template-name="pebble-timeline-config">
|
|
18
|
+
<div class="form-row">
|
|
19
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
20
|
+
<input type="text" id="node-config-input-name" placeholder="Name">
|
|
21
|
+
<div class="form-tips">Optional name to identify this configuration in the flow</div>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-config-input-apiUrl"><i class="fa fa-globe"></i> API URL</label>
|
|
25
|
+
<input type="text" id="node-config-input-apiUrl" placeholder="https://timeline-api.rebble.io">
|
|
26
|
+
<div class="form-tips">The URL of the Pebble Timeline API. The default URL (https://timeline-api.rebble.io) is
|
|
27
|
+
provided by the Rebble service which maintains Pebble functionality after official support ended.
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="form-row">
|
|
31
|
+
<label for="node-config-input-timelineToken"><i class="fa fa-key"></i> Timeline Token</label>
|
|
32
|
+
<input type="password" id="node-config-input-timelineToken">
|
|
33
|
+
<div class="form-tips">Your timeline token for authentication with the API. This token is used to authenticate
|
|
34
|
+
your app with the Pebble Timeline service.
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<script type="text/html" data-help-name="pebble-timeline-config">
|
|
40
|
+
<p>Configuration for connecting to the Pebble Timeline API.</p>
|
|
41
|
+
|
|
42
|
+
<h3>Details</h3>
|
|
43
|
+
<dl class="message-properties">
|
|
44
|
+
<dt>Name <span class="property-type">string</span></dt>
|
|
45
|
+
<dd>Optional name for this configuration. Used to identify this configuration in your flows.</dd>
|
|
46
|
+
|
|
47
|
+
<dt>API URL <span class="property-type">string</span></dt>
|
|
48
|
+
<dd>The URL of the Pebble Timeline API. Defaults to https://timeline-api.rebble.io, which is the Rebble service
|
|
49
|
+
that maintains Pebble functionality.
|
|
50
|
+
</dd>
|
|
51
|
+
|
|
52
|
+
<dt>Timeline Token <span class="property-type">string</span></dt>
|
|
53
|
+
<dd>Your timeline token for authentication with the API. This is used to authenticate your app with the timeline
|
|
54
|
+
service. Each token has its own separate list of pins, and pins are automatically cleaned up after one month.
|
|
55
|
+
</dd>
|
|
56
|
+
</dl>
|
|
57
|
+
|
|
58
|
+
<h3>References</h3>
|
|
59
|
+
<ul>
|
|
60
|
+
<li><a href="https://developer.pebble.com/guides/pebble-timeline/">Pebble Timeline Developer Guide</a></li>
|
|
61
|
+
<li><a href="https://rebble.io">Rebble.io - The community maintaining Pebble services</a></li>
|
|
62
|
+
</ul>
|
|
63
|
+
</script>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
function PebbleTimelineConfigNode(n) {
|
|
3
|
+
RED.nodes.createNode(this, n);
|
|
4
|
+
this.name = n.name;
|
|
5
|
+
this.apiUrl = n.apiUrl;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
RED.nodes.registerType("pebble-timeline-config", PebbleTimelineConfigNode, {
|
|
9
|
+
credentials: {
|
|
10
|
+
timelineToken: { type: "password" }
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('pebble-timeline-delete', {
|
|
3
|
+
category: 'Pebble',
|
|
4
|
+
color: '#1da1f2',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: {value: ""},
|
|
7
|
+
config: {type: "pebble-timeline-config", required: true},
|
|
8
|
+
|
|
9
|
+
// Server override options
|
|
10
|
+
apiUrl: {value: "null"},
|
|
11
|
+
apiUrlType: {value: "jsonata"},
|
|
12
|
+
|
|
13
|
+
token: {value: "null"},
|
|
14
|
+
tokenType: {value: "jsonata"},
|
|
15
|
+
|
|
16
|
+
// Pin ID property
|
|
17
|
+
pinId: {value: "payload.id"},
|
|
18
|
+
pinIdType: {value: "msg"}
|
|
19
|
+
},
|
|
20
|
+
inputs: 1,
|
|
21
|
+
outputs: 1,
|
|
22
|
+
icon: "font-awesome/fa-trash-o",
|
|
23
|
+
label: function () {
|
|
24
|
+
return this.name || "Delete Timeline Pin";
|
|
25
|
+
},
|
|
26
|
+
oneditprepare: function () {
|
|
27
|
+
// Setup TypedInput for server override options
|
|
28
|
+
$("#node-input-apiUrl").typedInput({
|
|
29
|
+
types: ["msg", "flow", "global", "str", "jsonata"],
|
|
30
|
+
typeField: "#node-input-apiUrlType"
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
$("#node-input-token").typedInput({
|
|
34
|
+
types: ["msg", "flow", "global", "str", "jsonata"],
|
|
35
|
+
typeField: "#node-input-tokenType"
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Setup TypedInput for pinId
|
|
39
|
+
$("#node-input-pinId").typedInput({
|
|
40
|
+
types: ["msg", "flow", "global", "str", "jsonata"],
|
|
41
|
+
typeField: "#node-input-pinIdType"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<script type="text/html" data-template-name="pebble-timeline-delete">
|
|
48
|
+
<div class="form-row">
|
|
49
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
50
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="form-row">
|
|
54
|
+
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
|
55
|
+
<input type="text" id="node-input-config">
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="form-section">
|
|
59
|
+
<div class="form-section-title">Server Overrides (Optional)</div>
|
|
60
|
+
|
|
61
|
+
<div class="form-row">
|
|
62
|
+
<label for="node-input-apiUrl"><i class="fa fa-globe"></i> API URL</label>
|
|
63
|
+
<input type="text" id="node-input-apiUrl" style="width: 70%">
|
|
64
|
+
<input type="hidden" id="node-input-apiUrlType">
|
|
65
|
+
<div class="form-tips">Override the API URL configured in the config node. Defaults to
|
|
66
|
+
https://timeline-api.rebble.io
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="form-row">
|
|
71
|
+
<label for="node-input-token"><i class="fa fa-key"></i> Token</label>
|
|
72
|
+
<input type="text" id="node-input-token" style="width: 70%">
|
|
73
|
+
<input type="hidden" id="node-input-tokenType">
|
|
74
|
+
<div class="form-tips">Override the timeline token configured in the config node</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="form-row">
|
|
79
|
+
<label for="node-input-pinId"><i class="fa fa-id-card"></i> Pin ID</label>
|
|
80
|
+
<input type="text" id="node-input-pinId" style="width: 70%">
|
|
81
|
+
<input type="hidden" id="node-input-pinIdType">
|
|
82
|
+
<div class="form-tips">The ID of the pin to delete from the timeline. This must match exactly the ID that was
|
|
83
|
+
used when the pin was created.
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<script type="text/html" data-help-name="pebble-timeline-delete">
|
|
89
|
+
<p>Deletes a pin from the Pebble Timeline.</p>
|
|
90
|
+
|
|
91
|
+
<h3>Inputs</h3>
|
|
92
|
+
<dl class="message-properties">
|
|
93
|
+
<dt>payload.id <span class="property-type">string</span></dt>
|
|
94
|
+
<dd>The ID of the pin to delete. This can be overridden by the node's configuration.</dd>
|
|
95
|
+
<dt>payload <span class="property-type">string</span></dt>
|
|
96
|
+
<dd>If payload is a string, it will be treated as the pin ID.</dd>
|
|
97
|
+
</dl>
|
|
98
|
+
|
|
99
|
+
<h3>Outputs</h3>
|
|
100
|
+
<dl class="message-properties">
|
|
101
|
+
<dt>payload <span class="property-type">object</span></dt>
|
|
102
|
+
<dd>The result of the API call, including success status and any error information.</dd>
|
|
103
|
+
<dt>payload.success <span class="property-type">boolean</span></dt>
|
|
104
|
+
<dd>Whether the delete operation was successful.</dd>
|
|
105
|
+
<dt>payload.pinId <span class="property-type">string</span></dt>
|
|
106
|
+
<dd>The ID of the pin that was deleted or attempted to be deleted.</dd>
|
|
107
|
+
</dl>
|
|
108
|
+
|
|
109
|
+
<h3>Details</h3>
|
|
110
|
+
<p>This node deletes a pin from the Pebble Timeline. You need to provide the ID of the pin to delete.</p>
|
|
111
|
+
<p>You can configure the pin ID directly in the node or provide it in the input message. If both are provided, the
|
|
112
|
+
node's configuration takes precedence.</p>
|
|
113
|
+
<p>The pin will be deleted from both the Pebble Timeline service and the local storage for the current timeline token.</p>
|
|
114
|
+
<p>When deleting pins, the system also automatically cleans up any pins older than one month from all tokens to prevent the storage file from growing too large.</p>
|
|
115
|
+
<p>Note that once a pin ID has been deleted, it cannot be reused for future pins.</p>
|
|
116
|
+
|
|
117
|
+
<h3>References</h3>
|
|
118
|
+
<ul>
|
|
119
|
+
<li><a href="https://developer.pebble.com/guides/pebble-timeline/pin-structure/">Pebble Timeline Pin
|
|
120
|
+
Structure</a></li>
|
|
121
|
+
</ul>
|
|
122
|
+
</script>
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
module.exports = function(RED) {
|
|
6
|
+
function PebbleTimelineDeleteNode(config) {
|
|
7
|
+
RED.nodes.createNode(this, config);
|
|
8
|
+
const node = this;
|
|
9
|
+
|
|
10
|
+
// Get the config node
|
|
11
|
+
const configNode = RED.nodes.getNode(config.config);
|
|
12
|
+
if (!configNode) {
|
|
13
|
+
node.error("No Pebble Timeline configuration found");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Make sure storage directory exists
|
|
18
|
+
const storageDir = path.join(RED.settings.userDir, 'pebble-timeline');
|
|
19
|
+
fs.ensureDirSync(storageDir);
|
|
20
|
+
const pinsFile = path.join(storageDir, 'timeline-pins.json');
|
|
21
|
+
|
|
22
|
+
// Load existing pins (organized by token)
|
|
23
|
+
let pinsData = {};
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(pinsFile)) {
|
|
26
|
+
pinsData = JSON.parse(fs.readFileSync(pinsFile, 'utf8'));
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
node.warn(`Error loading pins file: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
node.on('input', function(msg, send, done) {
|
|
33
|
+
// Backwards compatibility with Node-RED 0.x
|
|
34
|
+
send = send || function() { node.send.apply(node, arguments) };
|
|
35
|
+
|
|
36
|
+
// Get the pin ID to delete
|
|
37
|
+
let pinId;
|
|
38
|
+
// Process parameters in sequence
|
|
39
|
+
Promise.all([
|
|
40
|
+
new Promise(resolve => {
|
|
41
|
+
RED.util.evaluateNodeProperty(config.pinId, config.pinIdType, node, msg, (err, result) => {
|
|
42
|
+
if (err) {
|
|
43
|
+
node.error(`Error evaluating pin ID: ${err.message}`, msg);
|
|
44
|
+
if (done) done(err);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pinId = result;
|
|
49
|
+
|
|
50
|
+
if (!pinId) {
|
|
51
|
+
node.error("Pin ID is required", msg);
|
|
52
|
+
if (done) done("Pin ID is required");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
resolve();
|
|
56
|
+
});
|
|
57
|
+
}),
|
|
58
|
+
|
|
59
|
+
// Check for server override options
|
|
60
|
+
new Promise(resolve => {
|
|
61
|
+
if (config.apiUrl) {
|
|
62
|
+
RED.util.evaluateNodeProperty(config.apiUrl, config.apiUrlType, node, msg, (err, result) => {
|
|
63
|
+
if (!err && result) {
|
|
64
|
+
node.apiUrlOverride = result;
|
|
65
|
+
}
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
resolve();
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
|
|
73
|
+
new Promise(resolve => {
|
|
74
|
+
if (config.token) {
|
|
75
|
+
RED.util.evaluateNodeProperty(config.token, config.tokenType, node, msg, (err, result) => {
|
|
76
|
+
if (!err && result) {
|
|
77
|
+
node.tokenOverride = result;
|
|
78
|
+
}
|
|
79
|
+
resolve();
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
resolve();
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
]).then(() => {
|
|
86
|
+
// Use overrides if provided, otherwise use config node values
|
|
87
|
+
const apiUrl = `${node.apiUrlOverride || configNode.apiUrl}/v1/user/pins/${pinId}`;
|
|
88
|
+
const timelineToken = node.tokenOverride || configNode.credentials.timelineToken;
|
|
89
|
+
|
|
90
|
+
if (!timelineToken) {
|
|
91
|
+
node.error("Timeline token is required", msg);
|
|
92
|
+
if (done) done("Timeline token is required");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
axios.delete(apiUrl, {
|
|
97
|
+
headers: {
|
|
98
|
+
'X-User-Token': timelineToken
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
.then(response => {
|
|
102
|
+
node.status({fill: "green", shape: "dot", text: "Pin deleted"});
|
|
103
|
+
|
|
104
|
+
// Remove the pin from our local storage
|
|
105
|
+
removePin(pinId);
|
|
106
|
+
|
|
107
|
+
// Prepare the output message
|
|
108
|
+
msg.payload = {
|
|
109
|
+
success: true,
|
|
110
|
+
pinId: pinId,
|
|
111
|
+
response: response.data
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
send(msg);
|
|
115
|
+
if (done) done();
|
|
116
|
+
})
|
|
117
|
+
.catch(error => {
|
|
118
|
+
node.status({fill: "red", shape: "dot", text: "Error: " + (error.response ? error.response.status : error.message)});
|
|
119
|
+
|
|
120
|
+
msg.payload = {
|
|
121
|
+
success: false,
|
|
122
|
+
pinId: pinId,
|
|
123
|
+
error: error.message,
|
|
124
|
+
response: error.response ? error.response.data : null
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
send(msg);
|
|
128
|
+
if (done) done(error);
|
|
129
|
+
});
|
|
130
|
+
}).catch(err => {
|
|
131
|
+
if (done) done(err);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Helper to remove a pin from local storage
|
|
136
|
+
function removePin(pinId) {
|
|
137
|
+
const timelineToken = node.tokenOverride || configNode.credentials.timelineToken;
|
|
138
|
+
|
|
139
|
+
// Check if this token has any pins
|
|
140
|
+
if (!pinsData[timelineToken]) {
|
|
141
|
+
return; // No pins for this token
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Remove the pin with the specified ID from this token's pins
|
|
145
|
+
const initialCount = pinsData[timelineToken].length;
|
|
146
|
+
pinsData[timelineToken] = pinsData[timelineToken].filter(p => p.id !== pinId);
|
|
147
|
+
|
|
148
|
+
// Clean up old pins (older than 1 month) from all tokens
|
|
149
|
+
cleanupOldPins();
|
|
150
|
+
|
|
151
|
+
// Only write if we actually removed something
|
|
152
|
+
if (pinsData[timelineToken].length !== initialCount) {
|
|
153
|
+
try {
|
|
154
|
+
fs.writeFileSync(pinsFile, JSON.stringify(pinsData, null, 2));
|
|
155
|
+
} catch (error) {
|
|
156
|
+
node.warn(`Error saving pins to file: ${error.message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Helper to clean up pins older than 1 month
|
|
162
|
+
function cleanupOldPins() {
|
|
163
|
+
const oneMonthAgo = new Date();
|
|
164
|
+
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
|
165
|
+
|
|
166
|
+
// Iterate through all tokens
|
|
167
|
+
Object.keys(pinsData).forEach(token => {
|
|
168
|
+
// Filter out pins older than 1 month
|
|
169
|
+
const initialCount = pinsData[token].length;
|
|
170
|
+
pinsData[token] = pinsData[token].filter(pin => {
|
|
171
|
+
const storedDate = new Date(pin._stored);
|
|
172
|
+
return storedDate >= oneMonthAgo;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Log if pins were removed
|
|
176
|
+
if (pinsData[token].length < initialCount) {
|
|
177
|
+
node.debug(`Removed ${initialCount - pinsData[token].length} old pins for token ${token.substring(0, 8)}...`);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
node.on('close', function() {
|
|
183
|
+
// Clean up any resources
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
RED.nodes.registerType("pebble-timeline-delete", PebbleTimelineDeleteNode, {
|
|
188
|
+
credentials: {}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('pebble-timeline-list', {
|
|
3
|
+
category: 'Pebble',
|
|
4
|
+
color: '#1da1f2',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: {value: ""},
|
|
7
|
+
config: {type: "pebble-timeline-config", required: true},
|
|
8
|
+
|
|
9
|
+
// Server override options
|
|
10
|
+
apiUrl: {value: "null"},
|
|
11
|
+
apiUrlType: {value: "jsonata"},
|
|
12
|
+
|
|
13
|
+
token: {value: "null"},
|
|
14
|
+
tokenType: {value: "jsonata"},
|
|
15
|
+
|
|
16
|
+
// Filter properties
|
|
17
|
+
startTime: {value: "payload.startTime"},
|
|
18
|
+
startTimeType: {value: "msg"},
|
|
19
|
+
|
|
20
|
+
endTime: {value: "payload.endTime"},
|
|
21
|
+
endTimeType: {value: "msg"}
|
|
22
|
+
},
|
|
23
|
+
inputs: 1,
|
|
24
|
+
outputs: 1,
|
|
25
|
+
icon: "font-awesome/fa-list",
|
|
26
|
+
label: function () {
|
|
27
|
+
return this.name || "List Timeline Pins";
|
|
28
|
+
},
|
|
29
|
+
oneditprepare: function () {
|
|
30
|
+
// Setup TypedInput for server override options
|
|
31
|
+
$("#node-input-apiUrl").typedInput({
|
|
32
|
+
types: ["msg", "flow", "global", "str", "jsonata"],
|
|
33
|
+
typeField: "#node-input-apiUrlType"
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
$("#node-input-token").typedInput({
|
|
37
|
+
types: ["msg", "flow", "global", "str", "jsonata"],
|
|
38
|
+
typeField: "#node-input-tokenType"
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Setup TypedInput for filter fields
|
|
42
|
+
$("#node-input-startTime").typedInput({
|
|
43
|
+
types: ["msg", "flow", "global", "str", "date", "jsonata"],
|
|
44
|
+
typeField: "#node-input-startTimeType"
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
$("#node-input-endTime").typedInput({
|
|
48
|
+
types: ["msg", "flow", "global", "str", "date", "jsonata"],
|
|
49
|
+
typeField: "#node-input-endTimeType"
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<script type="text/html" data-template-name="pebble-timeline-list">
|
|
56
|
+
<div class="form-row">
|
|
57
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
58
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="form-row">
|
|
62
|
+
<label for="node-input-config"><i class="fa fa-cog"></i> Config</label>
|
|
63
|
+
<input type="text" id="node-input-config">
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="form-section">
|
|
67
|
+
<div class="form-section-title">Server Overrides (Optional)</div>
|
|
68
|
+
|
|
69
|
+
<div class="form-row">
|
|
70
|
+
<label for="node-input-apiUrl"><i class="fa fa-globe"></i> API URL</label>
|
|
71
|
+
<input type="text" id="node-input-apiUrl" style="width: 70%">
|
|
72
|
+
<input type="hidden" id="node-input-apiUrlType">
|
|
73
|
+
<div class="form-tips">Override the API URL configured in the config node. Defaults to
|
|
74
|
+
https://timeline-api.rebble.io
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="form-row">
|
|
79
|
+
<label for="node-input-token"><i class="fa fa-key"></i> Token</label>
|
|
80
|
+
<input type="text" id="node-input-token" style="width: 70%">
|
|
81
|
+
<input type="hidden" id="node-input-tokenType">
|
|
82
|
+
<div class="form-tips">Override the timeline token configured in the config node</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="form-section">
|
|
87
|
+
<div class="form-section-title">Filter Options</div>
|
|
88
|
+
|
|
89
|
+
<div class="form-row">
|
|
90
|
+
<label for="node-input-startTime"><i class="fa fa-calendar"></i> Start Time</label>
|
|
91
|
+
<input type="text" id="node-input-startTime" style="width: 70%">
|
|
92
|
+
<input type="hidden" id="node-input-startTimeType">
|
|
93
|
+
<div class="form-tips">Filter pins with a time greater than or equal to this value. Use ISO date-time format
|
|
94
|
+
(e.g., 2023-01-01T00:00:00Z).
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div class="form-row">
|
|
99
|
+
<label for="node-input-endTime"><i class="fa fa-calendar"></i> End Time</label>
|
|
100
|
+
<input type="text" id="node-input-endTime" style="width: 70%">
|
|
101
|
+
<input type="hidden" id="node-input-endTimeType">
|
|
102
|
+
<div class="form-tips">Filter pins with a time less than or equal to this value. Use ISO date-time format
|
|
103
|
+
(e.g., 2023-01-31T23:59:59Z).
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</script>
|
|
108
|
+
|
|
109
|
+
<script type="text/html" data-help-name="pebble-timeline-list">
|
|
110
|
+
<p>Lists pins that have been added to the Pebble Timeline.</p>
|
|
111
|
+
|
|
112
|
+
<h3>Inputs</h3>
|
|
113
|
+
<dl class="message-properties">
|
|
114
|
+
<dt class="optional">payload <span class="property-type">object</span></dt>
|
|
115
|
+
<dd>Optional filter criteria for the pins.</dd>
|
|
116
|
+
<dt class="optional">payload.startTime <span class="property-type">string | date</span></dt>
|
|
117
|
+
<dd>Filter pins with a time greater than or equal to this value. Use ISO date-time format.</dd>
|
|
118
|
+
<dt class="optional">payload.endTime <span class="property-type">string | date</span></dt>
|
|
119
|
+
<dd>Filter pins with a time less than or equal to this value. Use ISO date-time format.</dd>
|
|
120
|
+
</dl>
|
|
121
|
+
|
|
122
|
+
<h3>Outputs</h3>
|
|
123
|
+
<dl class="message-properties">
|
|
124
|
+
<dt>payload <span class="property-type">array</span></dt>
|
|
125
|
+
<dd>An array of pins that match the filter criteria.</dd>
|
|
126
|
+
<dt>count <span class="property-type">number</span></dt>
|
|
127
|
+
<dd>The number of pins that match the filter criteria.</dd>
|
|
128
|
+
</dl>
|
|
129
|
+
|
|
130
|
+
<h3>Details</h3>
|
|
131
|
+
<p>This node lists pins that have been successfully added to the Pebble Timeline. The pins are stored locally in the
|
|
132
|
+
Node-RED installation directory, organized by timeline token.</p>
|
|
133
|
+
<p>Only pins associated with the current timeline token will be listed. Each token maintains its own separate list of pins.</p>
|
|
134
|
+
<p>You can filter the pins by start and end times. The times should be in ISO date-time format (e.g.,
|
|
135
|
+
2023-01-01T12:00:00Z).</p>
|
|
136
|
+
<p>By default, if no filters are specified, all pins for the current token will be returned.</p>
|
|
137
|
+
<p>Pins older than one month are automatically cleaned up when listing pins to prevent the storage file from growing too large.</p>
|
|
138
|
+
|
|
139
|
+
<h4>Example Use Cases</h4>
|
|
140
|
+
<ul>
|
|
141
|
+
<li>List all upcoming events for the day</li>
|
|
142
|
+
<li>Filter pins for a specific date range for reporting</li>
|
|
143
|
+
<li>Show pins for a specific event type based on time criteria</li>
|
|
144
|
+
</ul>
|
|
145
|
+
|
|
146
|
+
<h3>References</h3>
|
|
147
|
+
<ul>
|
|
148
|
+
<li><a href="https://developer.pebble.com/guides/pebble-timeline/pin-structure/">Pebble Timeline Pin
|
|
149
|
+
Structure</a></li>
|
|
150
|
+
</ul>
|
|
151
|
+
</script>
|